@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.
- package/dist/cjs/client.js +198 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/constants/index.js +211 -0
- package/dist/cjs/constants/index.js.map +1 -0
- package/dist/cjs/errors/index.js +218 -0
- package/dist/cjs/errors/index.js.map +1 -0
- package/dist/cjs/index.js +51 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/modules/compliance.js +202 -0
- package/dist/cjs/modules/compliance.js.map +1 -0
- package/dist/cjs/modules/index.js +18 -0
- package/dist/cjs/modules/index.js.map +1 -0
- package/dist/cjs/modules/kyc.js +278 -0
- package/dist/cjs/modules/kyc.js.map +1 -0
- package/dist/cjs/modules/token.js +365 -0
- package/dist/cjs/modules/token.js.map +1 -0
- package/dist/cjs/modules/yield.js +406 -0
- package/dist/cjs/modules/yield.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/types/index.js +20 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/utils/index.js +206 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/esm/client.js +194 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/constants/index.js +208 -0
- package/dist/esm/constants/index.js.map +1 -0
- package/dist/esm/errors/index.js +209 -0
- package/dist/esm/errors/index.js.map +1 -0
- package/dist/esm/index.js +17 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/modules/compliance.js +198 -0
- package/dist/esm/modules/compliance.js.map +1 -0
- package/dist/esm/modules/index.js +8 -0
- package/dist/esm/modules/index.js.map +1 -0
- package/dist/esm/modules/kyc.js +273 -0
- package/dist/esm/modules/kyc.js.map +1 -0
- package/dist/esm/modules/token.js +360 -0
- package/dist/esm/modules/token.js.map +1 -0
- package/dist/esm/modules/yield.js +401 -0
- package/dist/esm/modules/yield.js.map +1 -0
- package/dist/esm/types/index.js +17 -0
- package/dist/esm/types/index.js.map +1 -0
- package/dist/esm/utils/index.js +188 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/dist/types/client.d.ts +93 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/constants/index.d.ts +83 -0
- package/dist/types/constants/index.d.ts.map +1 -0
- package/dist/types/errors/index.d.ts +83 -0
- package/dist/types/errors/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/modules/compliance.d.ts +29 -0
- package/dist/types/modules/compliance.d.ts.map +1 -0
- package/dist/types/modules/index.d.ts +8 -0
- package/dist/types/modules/index.d.ts.map +1 -0
- package/dist/types/modules/kyc.d.ts +131 -0
- package/dist/types/modules/kyc.d.ts.map +1 -0
- package/dist/types/modules/token.d.ts +145 -0
- package/dist/types/modules/token.d.ts.map +1 -0
- package/dist/types/modules/yield.d.ts +143 -0
- package/dist/types/modules/yield.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +254 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +80 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/package.json +52 -0
- package/src/client.ts +258 -0
- package/src/constants/index.ts +226 -0
- package/src/errors/index.ts +291 -0
- package/src/index.ts +42 -0
- package/src/modules/compliance.ts +252 -0
- package/src/modules/index.ts +8 -0
- package/src/modules/kyc.ts +446 -0
- package/src/modules/token.ts +488 -0
- package/src/modules/yield.ts +566 -0
- package/src/types/index.ts +326 -0
- 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';
|