@mantle-rwa/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/cjs/client.js +198 -0
  2. package/dist/cjs/client.js.map +1 -0
  3. package/dist/cjs/constants/index.js +211 -0
  4. package/dist/cjs/constants/index.js.map +1 -0
  5. package/dist/cjs/errors/index.js +218 -0
  6. package/dist/cjs/errors/index.js.map +1 -0
  7. package/dist/cjs/index.js +51 -0
  8. package/dist/cjs/index.js.map +1 -0
  9. package/dist/cjs/modules/compliance.js +202 -0
  10. package/dist/cjs/modules/compliance.js.map +1 -0
  11. package/dist/cjs/modules/index.js +18 -0
  12. package/dist/cjs/modules/index.js.map +1 -0
  13. package/dist/cjs/modules/kyc.js +278 -0
  14. package/dist/cjs/modules/kyc.js.map +1 -0
  15. package/dist/cjs/modules/token.js +365 -0
  16. package/dist/cjs/modules/token.js.map +1 -0
  17. package/dist/cjs/modules/yield.js +406 -0
  18. package/dist/cjs/modules/yield.js.map +1 -0
  19. package/dist/cjs/package.json +3 -0
  20. package/dist/cjs/types/index.js +20 -0
  21. package/dist/cjs/types/index.js.map +1 -0
  22. package/dist/cjs/utils/index.js +206 -0
  23. package/dist/cjs/utils/index.js.map +1 -0
  24. package/dist/esm/client.js +194 -0
  25. package/dist/esm/client.js.map +1 -0
  26. package/dist/esm/constants/index.js +208 -0
  27. package/dist/esm/constants/index.js.map +1 -0
  28. package/dist/esm/errors/index.js +209 -0
  29. package/dist/esm/errors/index.js.map +1 -0
  30. package/dist/esm/index.js +17 -0
  31. package/dist/esm/index.js.map +1 -0
  32. package/dist/esm/modules/compliance.js +198 -0
  33. package/dist/esm/modules/compliance.js.map +1 -0
  34. package/dist/esm/modules/index.js +8 -0
  35. package/dist/esm/modules/index.js.map +1 -0
  36. package/dist/esm/modules/kyc.js +273 -0
  37. package/dist/esm/modules/kyc.js.map +1 -0
  38. package/dist/esm/modules/token.js +360 -0
  39. package/dist/esm/modules/token.js.map +1 -0
  40. package/dist/esm/modules/yield.js +401 -0
  41. package/dist/esm/modules/yield.js.map +1 -0
  42. package/dist/esm/types/index.js +17 -0
  43. package/dist/esm/types/index.js.map +1 -0
  44. package/dist/esm/utils/index.js +188 -0
  45. package/dist/esm/utils/index.js.map +1 -0
  46. package/dist/types/client.d.ts +93 -0
  47. package/dist/types/client.d.ts.map +1 -0
  48. package/dist/types/constants/index.d.ts +83 -0
  49. package/dist/types/constants/index.d.ts.map +1 -0
  50. package/dist/types/errors/index.d.ts +83 -0
  51. package/dist/types/errors/index.d.ts.map +1 -0
  52. package/dist/types/index.d.ts +13 -0
  53. package/dist/types/index.d.ts.map +1 -0
  54. package/dist/types/modules/compliance.d.ts +29 -0
  55. package/dist/types/modules/compliance.d.ts.map +1 -0
  56. package/dist/types/modules/index.d.ts +8 -0
  57. package/dist/types/modules/index.d.ts.map +1 -0
  58. package/dist/types/modules/kyc.d.ts +131 -0
  59. package/dist/types/modules/kyc.d.ts.map +1 -0
  60. package/dist/types/modules/token.d.ts +145 -0
  61. package/dist/types/modules/token.d.ts.map +1 -0
  62. package/dist/types/modules/yield.d.ts +143 -0
  63. package/dist/types/modules/yield.d.ts.map +1 -0
  64. package/dist/types/types/index.d.ts +254 -0
  65. package/dist/types/types/index.d.ts.map +1 -0
  66. package/dist/types/utils/index.d.ts +80 -0
  67. package/dist/types/utils/index.d.ts.map +1 -0
  68. package/package.json +52 -0
  69. package/src/client.ts +258 -0
  70. package/src/constants/index.ts +226 -0
  71. package/src/errors/index.ts +291 -0
  72. package/src/index.ts +42 -0
  73. package/src/modules/compliance.ts +252 -0
  74. package/src/modules/index.ts +8 -0
  75. package/src/modules/kyc.ts +446 -0
  76. package/src/modules/token.ts +488 -0
  77. package/src/modules/yield.ts +566 -0
  78. package/src/types/index.ts +326 -0
  79. package/src/utils/index.ts +240 -0
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Error handling for the Mantle RWA SDK
3
+ * Provides descriptive error messages with suggested fixes
4
+ */
5
+
6
+ /**
7
+ * Error codes for categorizing errors
8
+ */
9
+ export enum ErrorCode {
10
+ // Contract errors
11
+ NOT_VERIFIED = 'NOT_VERIFIED',
12
+ KYC_EXPIRED = 'KYC_EXPIRED',
13
+ TRANSFER_RESTRICTED = 'TRANSFER_RESTRICTED',
14
+ TOKENS_PAUSED = 'TOKENS_PAUSED',
15
+ UNAUTHORIZED = 'UNAUTHORIZED',
16
+ INSUFFICIENT_BALANCE = 'INSUFFICIENT_BALANCE',
17
+ INVALID_RECIPIENT = 'INVALID_RECIPIENT',
18
+ CLAIM_WINDOW_EXPIRED = 'CLAIM_WINDOW_EXPIRED',
19
+ ALREADY_CLAIMED = 'ALREADY_CLAIMED',
20
+ WITHDRAWAL_THRESHOLD_NOT_MET = 'WITHDRAWAL_THRESHOLD_NOT_MET',
21
+ EMERGENCY_NOT_DECLARED = 'EMERGENCY_NOT_DECLARED',
22
+ REENTRANCY = 'REENTRANCY',
23
+
24
+ // Network errors
25
+ RPC_ERROR = 'RPC_ERROR',
26
+ TIMEOUT = 'TIMEOUT',
27
+ NONCE_TOO_LOW = 'NONCE_TOO_LOW',
28
+ INSUFFICIENT_FUNDS = 'INSUFFICIENT_FUNDS',
29
+ GAS_ESTIMATION_FAILED = 'GAS_ESTIMATION_FAILED',
30
+ NETWORK_MISMATCH = 'NETWORK_MISMATCH',
31
+
32
+ // Validation errors
33
+ INVALID_ADDRESS = 'INVALID_ADDRESS',
34
+ INVALID_AMOUNT = 'INVALID_AMOUNT',
35
+ MISSING_PARAMETER = 'MISSING_PARAMETER',
36
+ INVALID_CONFIGURATION = 'INVALID_CONFIGURATION',
37
+
38
+ // Provider errors
39
+ PROVIDER_NOT_CONFIGURED = 'PROVIDER_NOT_CONFIGURED',
40
+ SIGNER_REQUIRED = 'SIGNER_REQUIRED',
41
+
42
+ // Unknown
43
+ UNKNOWN = 'UNKNOWN',
44
+ }
45
+
46
+ /**
47
+ * Suggested fixes for common errors
48
+ */
49
+ const ERROR_SUGGESTIONS: Record<ErrorCode, string> = {
50
+ [ErrorCode.NOT_VERIFIED]:
51
+ 'Ensure the address is registered in the KYC registry. Use kycModule.updateRegistry() to add the investor.',
52
+ [ErrorCode.KYC_EXPIRED]:
53
+ 'The KYC verification has expired. Update the investor expiry using kycModule.updateRegistry().',
54
+ [ErrorCode.TRANSFER_RESTRICTED]:
55
+ 'Check compliance requirements using complianceModule.checkTransferEligibility() before attempting transfer.',
56
+ [ErrorCode.TOKENS_PAUSED]:
57
+ 'Token transfers are currently paused. Contact the token issuer or wait for tokens to be unpaused.',
58
+ [ErrorCode.UNAUTHORIZED]:
59
+ 'The connected account does not have the required role. Ensure you are using the correct signer.',
60
+ [ErrorCode.INSUFFICIENT_BALANCE]:
61
+ 'The account does not have enough tokens. Check balance using tokenInstance.balanceOf().',
62
+ [ErrorCode.INVALID_RECIPIENT]:
63
+ 'The recipient address is invalid or the zero address. Verify the address format.',
64
+ [ErrorCode.CLAIM_WINDOW_EXPIRED]:
65
+ 'The claim window for this distribution has expired. Unclaimed funds may have been handled.',
66
+ [ErrorCode.ALREADY_CLAIMED]:
67
+ 'This distribution has already been claimed by this account.',
68
+ [ErrorCode.WITHDRAWAL_THRESHOLD_NOT_MET]:
69
+ 'Not enough signers have approved this withdrawal. Additional approvals are required.',
70
+ [ErrorCode.EMERGENCY_NOT_DECLARED]:
71
+ 'Emergency mode must be declared before emergency withdrawals can be executed.',
72
+ [ErrorCode.REENTRANCY]:
73
+ 'Reentrancy detected. This is a security measure. Do not call vault functions from within callbacks.',
74
+ [ErrorCode.RPC_ERROR]:
75
+ 'Failed to connect to the RPC endpoint. Check your network configuration and try again.',
76
+ [ErrorCode.TIMEOUT]:
77
+ 'The request timed out. The network may be congested. Try again with higher gas price.',
78
+ [ErrorCode.NONCE_TOO_LOW]:
79
+ 'Transaction nonce is too low. A pending transaction may exist. Wait for it to confirm or increase nonce.',
80
+ [ErrorCode.INSUFFICIENT_FUNDS]:
81
+ 'Insufficient funds for gas. Ensure the account has enough native tokens (MNT) for transaction fees.',
82
+ [ErrorCode.GAS_ESTIMATION_FAILED]:
83
+ 'Gas estimation failed. The transaction may revert. Check parameters and try again.',
84
+ [ErrorCode.NETWORK_MISMATCH]:
85
+ 'Connected to wrong network. Ensure your wallet is connected to the correct chain.',
86
+ [ErrorCode.INVALID_ADDRESS]:
87
+ 'The provided address is not a valid Ethereum address. Check the format (0x followed by 40 hex characters).',
88
+ [ErrorCode.INVALID_AMOUNT]:
89
+ 'The amount is invalid. Ensure it is a positive number and properly formatted.',
90
+ [ErrorCode.MISSING_PARAMETER]:
91
+ 'A required parameter is missing. Check the function documentation for required parameters.',
92
+ [ErrorCode.INVALID_CONFIGURATION]:
93
+ 'The configuration is invalid. Review the configuration object and ensure all required fields are set.',
94
+ [ErrorCode.PROVIDER_NOT_CONFIGURED]:
95
+ 'No provider is configured. Initialize RWAClient with a network configuration or provider.',
96
+ [ErrorCode.SIGNER_REQUIRED]:
97
+ 'A signer is required for this operation. Provide a private key or signer in the client configuration.',
98
+ [ErrorCode.UNKNOWN]:
99
+ 'An unknown error occurred. Check the error details for more information.',
100
+ };
101
+
102
+ /**
103
+ * Base error class for RWA SDK errors
104
+ */
105
+ export class RWAError extends Error {
106
+ readonly code: ErrorCode;
107
+ readonly suggestion: string;
108
+ readonly details?: Record<string, unknown>;
109
+
110
+ constructor(
111
+ code: ErrorCode,
112
+ message: string,
113
+ details?: Record<string, unknown>
114
+ ) {
115
+ super(message);
116
+ this.name = 'RWAError';
117
+ this.code = code;
118
+ this.suggestion = ERROR_SUGGESTIONS[code] || ERROR_SUGGESTIONS[ErrorCode.UNKNOWN];
119
+ this.details = details;
120
+
121
+ // Maintains proper stack trace for where error was thrown
122
+ if (Error.captureStackTrace) {
123
+ Error.captureStackTrace(this, RWAError);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Get a formatted error message including suggestion
129
+ */
130
+ toFormattedString(): string {
131
+ let result = `[${this.code}] ${this.message}`;
132
+ result += `\n\nSuggested fix: ${this.suggestion}`;
133
+ if (this.details) {
134
+ result += `\n\nDetails: ${JSON.stringify(this.details, null, 2)}`;
135
+ }
136
+ return result;
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Error class for contract-related errors
142
+ */
143
+ export class ContractError extends RWAError {
144
+ readonly contractAddress: string;
145
+ readonly functionName: string;
146
+ readonly revertReason?: string;
147
+
148
+ constructor(
149
+ code: ErrorCode,
150
+ message: string,
151
+ contractAddress: string,
152
+ functionName: string,
153
+ revertReason?: string,
154
+ details?: Record<string, unknown>
155
+ ) {
156
+ super(code, message, details);
157
+ this.name = 'ContractError';
158
+ this.contractAddress = contractAddress;
159
+ this.functionName = functionName;
160
+ this.revertReason = revertReason;
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Error class for network-related errors
166
+ */
167
+ export class NetworkError extends RWAError {
168
+ readonly chainId?: number;
169
+ readonly rpcUrl?: string;
170
+ readonly retryable: boolean;
171
+
172
+ constructor(
173
+ code: ErrorCode,
174
+ message: string,
175
+ retryable: boolean,
176
+ chainId?: number,
177
+ rpcUrl?: string,
178
+ details?: Record<string, unknown>
179
+ ) {
180
+ super(code, message, details);
181
+ this.name = 'NetworkError';
182
+ this.chainId = chainId;
183
+ this.rpcUrl = rpcUrl;
184
+ this.retryable = retryable;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Error class for validation errors
190
+ */
191
+ export class ValidationError extends RWAError {
192
+ readonly field: string;
193
+ readonly constraint: string;
194
+ readonly value: unknown;
195
+
196
+ constructor(
197
+ code: ErrorCode,
198
+ message: string,
199
+ field: string,
200
+ constraint: string,
201
+ value: unknown,
202
+ details?: Record<string, unknown>
203
+ ) {
204
+ super(code, message, details);
205
+ this.name = 'ValidationError';
206
+ this.field = field;
207
+ this.constraint = constraint;
208
+ this.value = value;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Parse a contract revert reason and return the appropriate error
214
+ */
215
+ export function parseContractError(
216
+ error: unknown,
217
+ contractAddress: string,
218
+ functionName: string
219
+ ): ContractError {
220
+ const errorObj = error as { reason?: string; message?: string; data?: string };
221
+ const reason = errorObj.reason || errorObj.message || 'Unknown contract error';
222
+
223
+ // Map common revert reasons to error codes
224
+ let code = ErrorCode.UNKNOWN;
225
+ if (reason.includes('NotVerified') || reason.includes('not verified')) {
226
+ code = ErrorCode.NOT_VERIFIED;
227
+ } else if (reason.includes('KYCExpired') || reason.includes('expired')) {
228
+ code = ErrorCode.KYC_EXPIRED;
229
+ } else if (reason.includes('TransferRestricted') || reason.includes('restricted')) {
230
+ code = ErrorCode.TRANSFER_RESTRICTED;
231
+ } else if (reason.includes('Paused') || reason.includes('paused')) {
232
+ code = ErrorCode.TOKENS_PAUSED;
233
+ } else if (reason.includes('AccessControl') || reason.includes('unauthorized') || reason.includes('Ownable')) {
234
+ code = ErrorCode.UNAUTHORIZED;
235
+ } else if (reason.includes('insufficient') || reason.includes('balance')) {
236
+ code = ErrorCode.INSUFFICIENT_BALANCE;
237
+ } else if (reason.includes('ClaimWindowExpired')) {
238
+ code = ErrorCode.CLAIM_WINDOW_EXPIRED;
239
+ } else if (reason.includes('AlreadyClaimed')) {
240
+ code = ErrorCode.ALREADY_CLAIMED;
241
+ } else if (reason.includes('ReentrancyGuard')) {
242
+ code = ErrorCode.REENTRANCY;
243
+ }
244
+
245
+ return new ContractError(
246
+ code,
247
+ `Contract call failed: ${reason}`,
248
+ contractAddress,
249
+ functionName,
250
+ reason,
251
+ { originalError: errorObj.message }
252
+ );
253
+ }
254
+
255
+ /**
256
+ * Parse a network error and return the appropriate error
257
+ */
258
+ export function parseNetworkError(
259
+ error: unknown,
260
+ chainId?: number,
261
+ rpcUrl?: string
262
+ ): NetworkError {
263
+ const errorObj = error as { code?: string; message?: string };
264
+ const message = errorObj.message || 'Unknown network error';
265
+
266
+ let code = ErrorCode.RPC_ERROR;
267
+ let retryable = true;
268
+
269
+ if (message.includes('nonce') && message.includes('low')) {
270
+ code = ErrorCode.NONCE_TOO_LOW;
271
+ retryable = true;
272
+ } else if (message.includes('timeout') || message.includes('ETIMEDOUT')) {
273
+ code = ErrorCode.TIMEOUT;
274
+ retryable = true;
275
+ } else if (message.includes('insufficient funds')) {
276
+ code = ErrorCode.INSUFFICIENT_FUNDS;
277
+ retryable = false;
278
+ } else if (message.includes('gas')) {
279
+ code = ErrorCode.GAS_ESTIMATION_FAILED;
280
+ retryable = false;
281
+ }
282
+
283
+ return new NetworkError(
284
+ code,
285
+ `Network error: ${message}`,
286
+ retryable,
287
+ chainId,
288
+ rpcUrl,
289
+ { originalError: message }
290
+ );
291
+ }
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @mantle-rwa/sdk
3
+ * TypeScript SDK for Real-World Asset tokenization on Mantle Network
4
+ */
5
+
6
+ // Core client
7
+ export { RWAClient } from './client';
8
+ export type { RWAClientConfig } from './client';
9
+
10
+ // Modules
11
+ export {
12
+ TokenModule,
13
+ TokenInstance,
14
+ KYCModule,
15
+ KYCRegistryInstance,
16
+ YieldModule,
17
+ YieldDistributorInstance,
18
+ ComplianceModule,
19
+ } from './modules';
20
+ export type { KYCProvider } from './modules';
21
+
22
+ // Types
23
+ export * from './types';
24
+
25
+ // Errors
26
+ export * from './errors';
27
+
28
+ // Constants
29
+ export * from './constants';
30
+
31
+ // Utilities
32
+ export {
33
+ isValidAddress,
34
+ normalizeAddress,
35
+ parseAmount,
36
+ formatAmount,
37
+ hashIdentityData,
38
+ timestampToDate,
39
+ dateToTimestamp,
40
+ isExpired,
41
+ calculatePercentage,
42
+ } from './utils';
@@ -0,0 +1,252 @@
1
+ /**
2
+ * ComplianceModule - Handles transfer eligibility and compliance reporting
3
+ */
4
+
5
+ import { ethers, type Provider, type Signer } from 'ethers';
6
+ import type {
7
+ TransferEligibility,
8
+ TransferCheck,
9
+ ComplianceReport,
10
+ FilingData,
11
+ } from '../types';
12
+ import { RWA_TOKEN_ABI, KYC_REGISTRY_ABI } from '../constants';
13
+ import { RWAError, ErrorCode } from '../errors';
14
+ import { isValidAddress, normalizeAddress, parseAmount } from '../utils';
15
+
16
+ /**
17
+ * Module for compliance operations
18
+ */
19
+ export class ComplianceModule {
20
+ private readonly _provider: Provider;
21
+
22
+ constructor(provider: Provider, _signer: Signer | null) {
23
+ this._provider = provider;
24
+ }
25
+
26
+ /**
27
+ * Check if a transfer is eligible
28
+ */
29
+ async checkTransferEligibility(
30
+ tokenAddress: string,
31
+ from: string,
32
+ to: string,
33
+ amount: string
34
+ ): Promise<TransferEligibility> {
35
+ if (!isValidAddress(tokenAddress)) {
36
+ throw new RWAError(ErrorCode.INVALID_ADDRESS, `Invalid token address: ${tokenAddress}`);
37
+ }
38
+ if (!isValidAddress(from)) {
39
+ throw new RWAError(ErrorCode.INVALID_ADDRESS, `Invalid from address: ${from}`);
40
+ }
41
+ if (!isValidAddress(to)) {
42
+ throw new RWAError(ErrorCode.INVALID_ADDRESS, `Invalid to address: ${to}`);
43
+ }
44
+
45
+ const token = new ethers.Contract(tokenAddress, RWA_TOKEN_ABI, this._provider);
46
+ const checks: TransferCheck[] = [];
47
+
48
+ // Check 1: Token not paused
49
+ try {
50
+ const paused = await token.paused();
51
+ checks.push({
52
+ name: 'Token Active',
53
+ passed: !paused,
54
+ details: paused ? 'Token transfers are currently paused' : 'Token is active',
55
+ });
56
+ } catch {
57
+ checks.push({
58
+ name: 'Token Active',
59
+ passed: false,
60
+ details: 'Unable to check pause status',
61
+ });
62
+ }
63
+
64
+ // Check 2: Sender has sufficient balance
65
+ try {
66
+ const balance = await token.balanceOf(normalizeAddress(from));
67
+ const amountWei = parseAmount(amount);
68
+ const hasBalance = balance >= amountWei;
69
+ checks.push({
70
+ name: 'Sufficient Balance',
71
+ passed: hasBalance,
72
+ details: hasBalance
73
+ ? `Balance: ${balance.toString()}`
74
+ : `Insufficient balance. Has: ${balance.toString()}, Needs: ${amountWei.toString()}`,
75
+ });
76
+ } catch {
77
+ checks.push({
78
+ name: 'Sufficient Balance',
79
+ passed: false,
80
+ details: 'Unable to check balance',
81
+ });
82
+ }
83
+
84
+ // Check 3: KYC verification for sender and recipient
85
+ try {
86
+ const kycRegistryAddress = await token.kycRegistry();
87
+ if (kycRegistryAddress !== ethers.ZeroAddress) {
88
+ const kycRegistry = new ethers.Contract(
89
+ kycRegistryAddress,
90
+ KYC_REGISTRY_ABI,
91
+ this._provider
92
+ );
93
+
94
+ // Check sender (skip for minting from zero address)
95
+ if (from !== ethers.ZeroAddress) {
96
+ const senderVerified = await kycRegistry.isVerified(normalizeAddress(from));
97
+ checks.push({
98
+ name: 'Sender KYC Verified',
99
+ passed: senderVerified,
100
+ details: senderVerified
101
+ ? 'Sender is KYC verified'
102
+ : 'Sender is not KYC verified or verification has expired',
103
+ });
104
+ }
105
+
106
+ // Check recipient
107
+ const recipientVerified = await kycRegistry.isVerified(normalizeAddress(to));
108
+ checks.push({
109
+ name: 'Recipient KYC Verified',
110
+ passed: recipientVerified,
111
+ details: recipientVerified
112
+ ? 'Recipient is KYC verified'
113
+ : 'Recipient is not KYC verified or verification has expired',
114
+ });
115
+ }
116
+ } catch {
117
+ checks.push({
118
+ name: 'KYC Verification',
119
+ passed: false,
120
+ details: 'Unable to check KYC status',
121
+ });
122
+ }
123
+
124
+ // Check 4: Compliance modules
125
+ try {
126
+ const [allowed, reason] = await token.isTransferAllowed(
127
+ normalizeAddress(from),
128
+ normalizeAddress(to),
129
+ parseAmount(amount)
130
+ );
131
+ checks.push({
132
+ name: 'Compliance Modules',
133
+ passed: allowed,
134
+ details: allowed ? 'All compliance checks passed' : reason,
135
+ });
136
+ } catch {
137
+ checks.push({
138
+ name: 'Compliance Modules',
139
+ passed: false,
140
+ details: 'Unable to check compliance modules',
141
+ });
142
+ }
143
+
144
+ // Determine overall eligibility
145
+ const allPassed = checks.every((c) => c.passed);
146
+ const failedCheck = checks.find((c) => !c.passed);
147
+
148
+ return {
149
+ eligible: allPassed,
150
+ reason: allPassed ? undefined : failedCheck?.details,
151
+ checks,
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Generate a compliance report for a token
157
+ */
158
+ async generateComplianceReport(tokenAddress: string): Promise<ComplianceReport> {
159
+ if (!isValidAddress(tokenAddress)) {
160
+ throw new RWAError(ErrorCode.INVALID_ADDRESS, `Invalid token address: ${tokenAddress}`);
161
+ }
162
+
163
+ const token = new ethers.Contract(tokenAddress, RWA_TOKEN_ABI, this._provider);
164
+
165
+ // Get KYC registry - verify it exists
166
+ try {
167
+ await token.kycRegistry();
168
+ } catch {
169
+ throw new RWAError(
170
+ ErrorCode.INVALID_CONFIGURATION,
171
+ 'Unable to get KYC registry from token contract'
172
+ );
173
+ }
174
+
175
+ // Note: In a production implementation, this would require an indexer
176
+ // to track all token holders and their KYC status. This is a simplified
177
+ // version that returns placeholder data.
178
+ const report: ComplianceReport = {
179
+ generatedAt: new Date(),
180
+ tokenAddress: normalizeAddress(tokenAddress),
181
+ totalHolders: 0, // Would need indexer
182
+ verifiedHolders: 0, // Would need indexer
183
+ accreditedHolders: 0, // Would need indexer
184
+ transfersBlocked: 0, // Would need event indexing
185
+ complianceScore: 100, // Placeholder
186
+ };
187
+
188
+ return report;
189
+ }
190
+
191
+ /**
192
+ * Export a compliance report in various formats
193
+ */
194
+ async exportReport(
195
+ report: ComplianceReport,
196
+ format: 'json' | 'csv' | 'pdf'
197
+ ): Promise<Buffer> {
198
+ switch (format) {
199
+ case 'json':
200
+ return Buffer.from(JSON.stringify(report, null, 2));
201
+
202
+ case 'csv': {
203
+ const headers = Object.keys(report).join(',');
204
+ const values = Object.values(report)
205
+ .map((v) => (v instanceof Date ? v.toISOString() : String(v)))
206
+ .join(',');
207
+ return Buffer.from(`${headers}\n${values}`);
208
+ }
209
+
210
+ case 'pdf':
211
+ // PDF generation would require a library like pdfkit
212
+ throw new RWAError(
213
+ ErrorCode.INVALID_CONFIGURATION,
214
+ 'PDF export requires additional dependencies. Use JSON or CSV format.'
215
+ );
216
+
217
+ default:
218
+ throw new RWAError(
219
+ ErrorCode.INVALID_CONFIGURATION,
220
+ `Unsupported export format: ${format}`
221
+ );
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Prepare filing data for regulatory submissions
227
+ */
228
+ async prepareFilingData(
229
+ tokenAddress: string,
230
+ filingType: string,
231
+ startDate?: Date,
232
+ endDate?: Date
233
+ ): Promise<FilingData> {
234
+ const report = await this.generateComplianceReport(tokenAddress);
235
+
236
+ return {
237
+ filingType,
238
+ tokenAddress: normalizeAddress(tokenAddress),
239
+ reportingPeriod: {
240
+ start: startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // Default: 30 days ago
241
+ end: endDate || new Date(),
242
+ },
243
+ data: {
244
+ totalHolders: report.totalHolders,
245
+ verifiedHolders: report.verifiedHolders,
246
+ accreditedHolders: report.accreditedHolders,
247
+ complianceScore: report.complianceScore,
248
+ generatedAt: report.generatedAt.toISOString(),
249
+ },
250
+ };
251
+ }
252
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SDK Modules
3
+ */
4
+
5
+ export { TokenModule, TokenInstance } from './token';
6
+ export { KYCModule, KYCRegistryInstance, type KYCProvider } from './kyc';
7
+ export { YieldModule, YieldDistributorInstance } from './yield';
8
+ export { ComplianceModule } from './compliance';