@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,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for the Mantle RWA SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { TransactionReceipt } from 'ethers';
|
|
6
|
+
|
|
7
|
+
/*//////////////////////////////////////////////////////////////
|
|
8
|
+
NETWORK TYPES
|
|
9
|
+
//////////////////////////////////////////////////////////////*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Supported network names
|
|
13
|
+
*/
|
|
14
|
+
export type NetworkName = 'mantle' | 'mantle-sepolia';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Custom network configuration
|
|
18
|
+
*/
|
|
19
|
+
export interface CustomNetwork {
|
|
20
|
+
rpcUrl: string;
|
|
21
|
+
chainId: number;
|
|
22
|
+
name?: string;
|
|
23
|
+
explorerUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Network configuration
|
|
28
|
+
*/
|
|
29
|
+
export interface NetworkConfig {
|
|
30
|
+
name: string;
|
|
31
|
+
chainId: number;
|
|
32
|
+
rpcUrl: string;
|
|
33
|
+
explorerUrl: string;
|
|
34
|
+
contracts?: {
|
|
35
|
+
factory?: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/*//////////////////////////////////////////////////////////////
|
|
40
|
+
ACCREDITATION TYPES
|
|
41
|
+
//////////////////////////////////////////////////////////////*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Accreditation tiers matching the smart contract enum
|
|
45
|
+
*/
|
|
46
|
+
export enum AccreditationTier {
|
|
47
|
+
None = 0,
|
|
48
|
+
Retail = 1,
|
|
49
|
+
Accredited = 2,
|
|
50
|
+
Institutional = 3,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/*//////////////////////////////////////////////////////////////
|
|
54
|
+
TRANSACTION TYPES
|
|
55
|
+
//////////////////////////////////////////////////////////////*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Parsed event from a transaction
|
|
59
|
+
*/
|
|
60
|
+
export interface ParsedEvent {
|
|
61
|
+
name: string;
|
|
62
|
+
args: Record<string, unknown>;
|
|
63
|
+
address: string;
|
|
64
|
+
blockNumber: number;
|
|
65
|
+
transactionHash: string;
|
|
66
|
+
logIndex: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Result of a transaction
|
|
71
|
+
*/
|
|
72
|
+
export interface TransactionResult {
|
|
73
|
+
hash: string;
|
|
74
|
+
blockNumber: number;
|
|
75
|
+
gasUsed: bigint;
|
|
76
|
+
status: 'success' | 'failed';
|
|
77
|
+
events: ParsedEvent[];
|
|
78
|
+
receipt: TransactionReceipt;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Options for transaction execution
|
|
83
|
+
*/
|
|
84
|
+
export interface TransactionOptions {
|
|
85
|
+
gasLimit?: bigint;
|
|
86
|
+
maxFeePerGas?: bigint;
|
|
87
|
+
maxPriorityFeePerGas?: bigint;
|
|
88
|
+
nonce?: number;
|
|
89
|
+
retries?: number;
|
|
90
|
+
retryDelay?: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/*//////////////////////////////////////////////////////////////
|
|
94
|
+
COMPLIANCE TYPES
|
|
95
|
+
//////////////////////////////////////////////////////////////*/
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Available compliance rule types
|
|
99
|
+
*/
|
|
100
|
+
export type ComplianceRule =
|
|
101
|
+
| 'accredited-investor'
|
|
102
|
+
| 'transfer-limit-24h'
|
|
103
|
+
| 'transfer-limit-30d'
|
|
104
|
+
| 'holding-period'
|
|
105
|
+
| 'max-investors'
|
|
106
|
+
| 'geographic-restriction';
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Transfer eligibility check result
|
|
110
|
+
*/
|
|
111
|
+
export interface TransferEligibility {
|
|
112
|
+
eligible: boolean;
|
|
113
|
+
reason?: string;
|
|
114
|
+
checks: TransferCheck[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Individual transfer check result
|
|
119
|
+
*/
|
|
120
|
+
export interface TransferCheck {
|
|
121
|
+
name: string;
|
|
122
|
+
passed: boolean;
|
|
123
|
+
details?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/*//////////////////////////////////////////////////////////////
|
|
127
|
+
DEPLOYMENT TYPES
|
|
128
|
+
//////////////////////////////////////////////////////////////*/
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Configuration for deploying a new RWA system
|
|
132
|
+
*/
|
|
133
|
+
export interface DeploymentConfig {
|
|
134
|
+
tokenName: string;
|
|
135
|
+
tokenSymbol: string;
|
|
136
|
+
initialSupply: string;
|
|
137
|
+
complianceModules?: string[];
|
|
138
|
+
yieldClaimWindowDays?: number;
|
|
139
|
+
vaultSigners: string[];
|
|
140
|
+
vaultThreshold: number;
|
|
141
|
+
vaultWithdrawalThreshold?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Addresses of deployed contracts
|
|
146
|
+
*/
|
|
147
|
+
export interface DeployedContracts {
|
|
148
|
+
token: string;
|
|
149
|
+
vault: string;
|
|
150
|
+
yieldDistributor: string;
|
|
151
|
+
kycRegistry: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/*//////////////////////////////////////////////////////////////
|
|
155
|
+
TOKEN TYPES
|
|
156
|
+
//////////////////////////////////////////////////////////////*/
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Configuration for deploying a token
|
|
160
|
+
*/
|
|
161
|
+
export interface TokenDeployConfig {
|
|
162
|
+
name: string;
|
|
163
|
+
symbol: string;
|
|
164
|
+
totalSupply: string;
|
|
165
|
+
complianceRules?: ComplianceRule[];
|
|
166
|
+
kycRegistryAddress?: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Token information
|
|
171
|
+
*/
|
|
172
|
+
export interface TokenInfo {
|
|
173
|
+
address: string;
|
|
174
|
+
name: string;
|
|
175
|
+
symbol: string;
|
|
176
|
+
decimals: number;
|
|
177
|
+
totalSupply: bigint;
|
|
178
|
+
paused: boolean;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/*//////////////////////////////////////////////////////////////
|
|
182
|
+
KYC TYPES
|
|
183
|
+
//////////////////////////////////////////////////////////////*/
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Investor data from the KYC registry
|
|
187
|
+
*/
|
|
188
|
+
export interface InvestorData {
|
|
189
|
+
verified: boolean;
|
|
190
|
+
tier: AccreditationTier;
|
|
191
|
+
expiry: Date;
|
|
192
|
+
identityHash: string;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Verification session for KYC flow
|
|
197
|
+
*/
|
|
198
|
+
export interface VerificationSession {
|
|
199
|
+
sessionId: string;
|
|
200
|
+
provider: string;
|
|
201
|
+
status: 'pending' | 'in_progress' | 'completed' | 'failed';
|
|
202
|
+
redirectUrl?: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Result of a KYC verification
|
|
207
|
+
*/
|
|
208
|
+
export interface VerificationResult {
|
|
209
|
+
verified: boolean;
|
|
210
|
+
tier: AccreditationTier;
|
|
211
|
+
identityHash: string;
|
|
212
|
+
expiryDate: Date;
|
|
213
|
+
rawData?: unknown;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/*//////////////////////////////////////////////////////////////
|
|
217
|
+
YIELD TYPES
|
|
218
|
+
//////////////////////////////////////////////////////////////*/
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Configuration for creating a distribution
|
|
222
|
+
*/
|
|
223
|
+
export interface DistributionConfig {
|
|
224
|
+
tokenAddress: string;
|
|
225
|
+
paymentToken: string;
|
|
226
|
+
totalAmount: string;
|
|
227
|
+
snapshotDate?: Date;
|
|
228
|
+
claimWindowDays?: number;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Preview of a distribution
|
|
233
|
+
*/
|
|
234
|
+
export interface DistributionPreview {
|
|
235
|
+
totalHolders: number;
|
|
236
|
+
totalSupplyAtSnapshot: bigint;
|
|
237
|
+
distributions: HolderDistribution[];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Distribution amount for a single holder
|
|
242
|
+
*/
|
|
243
|
+
export interface HolderDistribution {
|
|
244
|
+
address: string;
|
|
245
|
+
balance: bigint;
|
|
246
|
+
yieldAmount: bigint;
|
|
247
|
+
percentage: number;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Distribution information
|
|
252
|
+
*/
|
|
253
|
+
export interface Distribution {
|
|
254
|
+
id: number;
|
|
255
|
+
paymentToken: string;
|
|
256
|
+
totalAmount: bigint;
|
|
257
|
+
snapshotId: bigint;
|
|
258
|
+
claimDeadline: Date;
|
|
259
|
+
claimedAmount: bigint;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Pending claim for an account
|
|
264
|
+
*/
|
|
265
|
+
export interface PendingClaim {
|
|
266
|
+
distributionId: number;
|
|
267
|
+
amount: bigint;
|
|
268
|
+
paymentToken: string;
|
|
269
|
+
deadline: Date;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/*//////////////////////////////////////////////////////////////
|
|
273
|
+
VAULT TYPES
|
|
274
|
+
//////////////////////////////////////////////////////////////*/
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Withdrawal proposal
|
|
278
|
+
*/
|
|
279
|
+
export interface WithdrawalProposal {
|
|
280
|
+
id: number;
|
|
281
|
+
token: string;
|
|
282
|
+
amount: bigint;
|
|
283
|
+
recipient: string;
|
|
284
|
+
approvalCount: number;
|
|
285
|
+
executed: boolean;
|
|
286
|
+
approvers: string[];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Vault status information
|
|
291
|
+
*/
|
|
292
|
+
export interface VaultStatus {
|
|
293
|
+
isEmergency: boolean;
|
|
294
|
+
collateralizationRatio: number;
|
|
295
|
+
backingVerified: boolean;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/*//////////////////////////////////////////////////////////////
|
|
299
|
+
COMPLIANCE REPORT TYPES
|
|
300
|
+
//////////////////////////////////////////////////////////////*/
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Compliance report for regulatory purposes
|
|
304
|
+
*/
|
|
305
|
+
export interface ComplianceReport {
|
|
306
|
+
generatedAt: Date;
|
|
307
|
+
tokenAddress: string;
|
|
308
|
+
totalHolders: number;
|
|
309
|
+
verifiedHolders: number;
|
|
310
|
+
accreditedHolders: number;
|
|
311
|
+
transfersBlocked: number;
|
|
312
|
+
complianceScore: number;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Filing data for regulatory submissions
|
|
317
|
+
*/
|
|
318
|
+
export interface FilingData {
|
|
319
|
+
filingType: string;
|
|
320
|
+
tokenAddress: string;
|
|
321
|
+
reportingPeriod: {
|
|
322
|
+
start: Date;
|
|
323
|
+
end: Date;
|
|
324
|
+
};
|
|
325
|
+
data: Record<string, unknown>;
|
|
326
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for the Mantle RWA SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ethers, type TransactionReceipt, type Interface } from 'ethers';
|
|
6
|
+
import type { ParsedEvent, TransactionResult } from '../types';
|
|
7
|
+
import { NetworkError, ErrorCode, parseNetworkError } from '../errors';
|
|
8
|
+
import { DEFAULTS } from '../constants';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validate an Ethereum address
|
|
12
|
+
*/
|
|
13
|
+
export function isValidAddress(address: string): boolean {
|
|
14
|
+
try {
|
|
15
|
+
return ethers.isAddress(address);
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate and normalize an Ethereum address
|
|
23
|
+
*/
|
|
24
|
+
export function normalizeAddress(address: string): string {
|
|
25
|
+
if (!isValidAddress(address)) {
|
|
26
|
+
throw new Error(`Invalid address: ${address}`);
|
|
27
|
+
}
|
|
28
|
+
return ethers.getAddress(address);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parse a string amount to bigint with decimals
|
|
33
|
+
*/
|
|
34
|
+
export function parseAmount(amount: string, decimals: number = 18): bigint {
|
|
35
|
+
return ethers.parseUnits(amount, decimals);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Format a bigint amount to string with decimals
|
|
40
|
+
*/
|
|
41
|
+
export function formatAmount(amount: bigint, decimals: number = 18): string {
|
|
42
|
+
return ethers.formatUnits(amount, decimals);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Parse events from a transaction receipt
|
|
47
|
+
*/
|
|
48
|
+
export function parseEvents(
|
|
49
|
+
receipt: TransactionReceipt,
|
|
50
|
+
contractInterface: Interface
|
|
51
|
+
): ParsedEvent[] {
|
|
52
|
+
const events: ParsedEvent[] = [];
|
|
53
|
+
|
|
54
|
+
for (const log of receipt.logs) {
|
|
55
|
+
try {
|
|
56
|
+
const parsed = contractInterface.parseLog({
|
|
57
|
+
topics: log.topics as string[],
|
|
58
|
+
data: log.data,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (parsed) {
|
|
62
|
+
const args: Record<string, unknown> = {};
|
|
63
|
+
for (const [key, value] of Object.entries(parsed.args)) {
|
|
64
|
+
// Skip numeric indices
|
|
65
|
+
if (!/^\d+$/.test(key)) {
|
|
66
|
+
args[key] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
events.push({
|
|
71
|
+
name: parsed.name,
|
|
72
|
+
args,
|
|
73
|
+
address: log.address,
|
|
74
|
+
blockNumber: log.blockNumber,
|
|
75
|
+
transactionHash: log.transactionHash,
|
|
76
|
+
logIndex: log.index,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Skip logs that don't match the interface
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return events;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create a transaction result from a receipt
|
|
89
|
+
*/
|
|
90
|
+
export function createTransactionResult(
|
|
91
|
+
receipt: TransactionReceipt,
|
|
92
|
+
events: ParsedEvent[]
|
|
93
|
+
): TransactionResult {
|
|
94
|
+
return {
|
|
95
|
+
hash: receipt.hash,
|
|
96
|
+
blockNumber: receipt.blockNumber,
|
|
97
|
+
gasUsed: receipt.gasUsed,
|
|
98
|
+
status: receipt.status === 1 ? 'success' : 'failed',
|
|
99
|
+
events,
|
|
100
|
+
receipt,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Sleep for a specified duration
|
|
106
|
+
*/
|
|
107
|
+
export function sleep(ms: number): Promise<void> {
|
|
108
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Retry a function with exponential backoff
|
|
113
|
+
*/
|
|
114
|
+
export async function retry<T>(
|
|
115
|
+
fn: () => Promise<T>,
|
|
116
|
+
options: {
|
|
117
|
+
retries?: number;
|
|
118
|
+
delay?: number;
|
|
119
|
+
onRetry?: (error: unknown, attempt: number) => void;
|
|
120
|
+
} = {}
|
|
121
|
+
): Promise<T> {
|
|
122
|
+
const { retries = DEFAULTS.TRANSACTION_RETRIES, delay = DEFAULTS.RETRY_DELAY_MS, onRetry } = options;
|
|
123
|
+
|
|
124
|
+
let lastError: unknown;
|
|
125
|
+
|
|
126
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
127
|
+
try {
|
|
128
|
+
return await fn();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
lastError = error;
|
|
131
|
+
|
|
132
|
+
// Check if error is retryable
|
|
133
|
+
const networkError = parseNetworkError(error);
|
|
134
|
+
if (!networkError.retryable) {
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (attempt < retries) {
|
|
139
|
+
onRetry?.(error, attempt + 1);
|
|
140
|
+
await sleep(delay * Math.pow(2, attempt)); // Exponential backoff
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw lastError;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Estimate gas with buffer
|
|
150
|
+
*/
|
|
151
|
+
export async function estimateGasWithBuffer(
|
|
152
|
+
estimateFn: () => Promise<bigint>,
|
|
153
|
+
bufferPercent: number = DEFAULTS.GAS_BUFFER_PERCENT
|
|
154
|
+
): Promise<bigint> {
|
|
155
|
+
const estimate = await estimateFn();
|
|
156
|
+
const buffer = (estimate * BigInt(bufferPercent)) / 100n;
|
|
157
|
+
return estimate + buffer;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Generate a keccak256 hash of identity data
|
|
162
|
+
*/
|
|
163
|
+
export function hashIdentityData(data: {
|
|
164
|
+
firstName?: string;
|
|
165
|
+
lastName?: string;
|
|
166
|
+
dateOfBirth?: string;
|
|
167
|
+
documentNumber?: string;
|
|
168
|
+
country?: string;
|
|
169
|
+
}): string {
|
|
170
|
+
const normalized = JSON.stringify({
|
|
171
|
+
firstName: data.firstName?.toLowerCase().trim(),
|
|
172
|
+
lastName: data.lastName?.toLowerCase().trim(),
|
|
173
|
+
dateOfBirth: data.dateOfBirth,
|
|
174
|
+
documentNumber: data.documentNumber?.toUpperCase().trim(),
|
|
175
|
+
country: data.country?.toUpperCase().trim(),
|
|
176
|
+
});
|
|
177
|
+
return ethers.keccak256(ethers.toUtf8Bytes(normalized));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Convert a timestamp to a Date object
|
|
182
|
+
*/
|
|
183
|
+
export function timestampToDate(timestamp: bigint | number): Date {
|
|
184
|
+
const ts = typeof timestamp === 'bigint' ? Number(timestamp) : timestamp;
|
|
185
|
+
return new Date(ts * 1000);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Convert a Date to a Unix timestamp
|
|
190
|
+
*/
|
|
191
|
+
export function dateToTimestamp(date: Date): number {
|
|
192
|
+
return Math.floor(date.getTime() / 1000);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Check if a timestamp is expired
|
|
197
|
+
*/
|
|
198
|
+
export function isExpired(timestamp: bigint | number): boolean {
|
|
199
|
+
const now = Math.floor(Date.now() / 1000);
|
|
200
|
+
const ts = typeof timestamp === 'bigint' ? Number(timestamp) : timestamp;
|
|
201
|
+
return ts < now;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Calculate percentage
|
|
206
|
+
*/
|
|
207
|
+
export function calculatePercentage(part: bigint, total: bigint): number {
|
|
208
|
+
if (total === 0n) return 0;
|
|
209
|
+
return Number((part * 10000n) / total) / 100;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Chunk an array into smaller arrays
|
|
214
|
+
*/
|
|
215
|
+
export function chunk<T>(array: T[], size: number): T[][] {
|
|
216
|
+
const chunks: T[][] = [];
|
|
217
|
+
for (let i = 0; i < array.length; i += size) {
|
|
218
|
+
chunks.push(array.slice(i, i + size));
|
|
219
|
+
}
|
|
220
|
+
return chunks;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Wait for a transaction to be mined
|
|
225
|
+
*/
|
|
226
|
+
export async function waitForTransaction(
|
|
227
|
+
provider: ethers.Provider,
|
|
228
|
+
hash: string,
|
|
229
|
+
confirmations: number = 1
|
|
230
|
+
): Promise<TransactionReceipt> {
|
|
231
|
+
const receipt = await provider.waitForTransaction(hash, confirmations);
|
|
232
|
+
if (!receipt) {
|
|
233
|
+
throw new NetworkError(
|
|
234
|
+
ErrorCode.TIMEOUT,
|
|
235
|
+
`Transaction ${hash} was not mined`,
|
|
236
|
+
true
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return receipt;
|
|
240
|
+
}
|