@deserialize/multi-vm-wallet 1.4.2 → 1.5.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/.claude/settings.local.json +7 -1
- package/BUILD_OPTIMIZATION_PLAN.md +640 -0
- package/BUILD_RESULTS.md +282 -0
- package/BUN_MIGRATION.md +415 -0
- package/CHANGELOG_SECURITY.md +573 -0
- package/IMPLEMENTATION_SUMMARY.md +494 -0
- package/SECURITY_AUDIT.md +1124 -0
- package/bun.lock +553 -0
- package/dist/IChainWallet.js +0 -5
- package/dist/bip32Old.js +0 -885
- package/dist/bip32Small.js +0 -79
- package/dist/bipTest.js +0 -362
- package/dist/constant.js +0 -17
- package/dist/english.js +0 -1
- package/dist/evm/aa-service/index.d.ts +0 -5
- package/dist/evm/aa-service/index.js +0 -14
- package/dist/evm/aa-service/lib/account-adapter.d.ts +0 -22
- package/dist/evm/aa-service/lib/account-adapter.js +0 -24
- package/dist/evm/aa-service/lib/kernel-account.d.ts +0 -30
- package/dist/evm/aa-service/lib/kernel-account.js +2 -67
- package/dist/evm/aa-service/lib/kernel-modules.d.ts +0 -177
- package/dist/evm/aa-service/lib/kernel-modules.js +4 -202
- package/dist/evm/aa-service/lib/session-keys.d.ts +0 -118
- package/dist/evm/aa-service/lib/session-keys.js +7 -151
- package/dist/evm/aa-service/lib/type.d.ts +0 -55
- package/dist/evm/aa-service/lib/type.js +0 -10
- package/dist/evm/aa-service/services/account-abstraction.d.ts +0 -426
- package/dist/evm/aa-service/services/account-abstraction.js +0 -461
- package/dist/evm/aa-service/services/bundler.d.ts +0 -6
- package/dist/evm/aa-service/services/bundler.js +0 -54
- package/dist/evm/evm.d.ts +9 -51
- package/dist/evm/evm.js +338 -76
- package/dist/evm/index.js +0 -3
- package/dist/evm/script.js +3 -17
- package/dist/evm/smartWallet.d.ts +0 -173
- package/dist/evm/smartWallet.js +0 -206
- package/dist/evm/smartWallet.types.d.ts +0 -6
- package/dist/evm/smartWallet.types.js +0 -8
- package/dist/evm/transaction.utils.d.ts +0 -242
- package/dist/evm/transaction.utils.js +4 -320
- package/dist/evm/transactionParsing.d.ts +0 -11
- package/dist/evm/transactionParsing.js +28 -147
- package/dist/evm/utils.d.ts +0 -46
- package/dist/evm/utils.js +1 -57
- package/dist/helpers/index.d.ts +0 -4
- package/dist/helpers/index.js +8 -44
- package/dist/helpers/routeScan.js +0 -1
- package/dist/index.js +0 -1
- package/dist/old.js +0 -884
- package/dist/price.js +0 -1
- package/dist/price.types.js +0 -2
- package/dist/rate-limiter.d.ts +28 -0
- package/dist/rate-limiter.js +95 -0
- package/dist/retry-logic.d.ts +14 -0
- package/dist/retry-logic.js +120 -0
- package/dist/savings/index.js +0 -1
- package/dist/savings/saving-manager.d.ts +10 -11
- package/dist/savings/saving-manager.js +79 -22
- package/dist/savings/savings-operations.d.ts +39 -0
- package/dist/savings/savings-operations.js +141 -0
- package/dist/savings/smart-savings.d.ts +0 -63
- package/dist/savings/smart-savings.js +0 -78
- package/dist/savings/types.d.ts +0 -69
- package/dist/savings/types.js +0 -7
- package/dist/savings/validation.d.ts +9 -0
- package/dist/savings/validation.js +85 -0
- package/dist/svm/constant.js +0 -1
- package/dist/svm/index.js +0 -1
- package/dist/svm/svm.d.ts +11 -1
- package/dist/svm/svm.js +267 -27
- package/dist/svm/transactionParsing.d.ts +0 -7
- package/dist/svm/transactionParsing.js +3 -41
- package/dist/svm/transactionSender.js +0 -9
- package/dist/svm/utils.d.ts +0 -12
- package/dist/svm/utils.js +9 -60
- package/dist/test.d.ts +0 -4
- package/dist/test.js +6 -98
- package/dist/transaction-utils.d.ts +38 -0
- package/dist/transaction-utils.js +168 -0
- package/dist/types.d.ts +36 -0
- package/dist/types.js +0 -1
- package/dist/utils.js +0 -1
- package/dist/vm-validation.d.ts +11 -0
- package/dist/vm-validation.js +151 -0
- package/dist/vm.d.ts +12 -2
- package/dist/vm.js +61 -16
- package/dist/walletBip32.js +15 -70
- package/package.json +9 -4
- package/test-discovery.ts +235 -0
- package/test-pocket-discovery.ts +84 -0
- package/tsconfig.json +18 -11
- package/tsconfig.prod.json +10 -0
- package/utils/evm/evm.ts +554 -8
- package/utils/rate-limiter.ts +179 -0
- package/utils/retry-logic.ts +271 -0
- package/utils/savings/EXAMPLES.md +883 -0
- package/utils/savings/SECURITY.md +731 -0
- package/utils/savings/saving-manager.ts +526 -16
- package/utils/savings/savings-operations.ts +509 -0
- package/utils/savings/validation.ts +187 -0
- package/utils/svm/svm.ts +476 -5
- package/utils/test.ts +2 -2
- package/utils/transaction-utils.ts +394 -0
- package/utils/types.ts +100 -0
- package/utils/vm-validation.ts +280 -0
- package/utils/vm.ts +197 -10
- package/utils/walletBip32.ts +39 -3
- package/dist/IChainWallet.js.map +0 -1
- package/dist/bip32.d.ts +0 -9
- package/dist/bip32.js +0 -172
- package/dist/bip32.js.map +0 -1
- package/dist/bip32Old.js.map +0 -1
- package/dist/bip32Small.js.map +0 -1
- package/dist/bipTest.js.map +0 -1
- package/dist/constant.js.map +0 -1
- package/dist/english.js.map +0 -1
- package/dist/evm/SMART_WALLET_EXAMPLES.d.ts +0 -20
- package/dist/evm/SMART_WALLET_EXAMPLES.js +0 -451
- package/dist/evm/SMART_WALLET_EXAMPLES.js.map +0 -1
- package/dist/evm/aa-service/index.js.map +0 -1
- package/dist/evm/aa-service/lib/account-adapter.js.map +0 -1
- package/dist/evm/aa-service/lib/kernel-account.js.map +0 -1
- package/dist/evm/aa-service/lib/kernel-modules.js.map +0 -1
- package/dist/evm/aa-service/lib/session-keys.js.map +0 -1
- package/dist/evm/aa-service/lib/type.js.map +0 -1
- package/dist/evm/aa-service/services/account-abstraction.js.map +0 -1
- package/dist/evm/aa-service/services/bundler.js.map +0 -1
- package/dist/evm/evm.js.map +0 -1
- package/dist/evm/index.js.map +0 -1
- package/dist/evm/script.js.map +0 -1
- package/dist/evm/smartWallet.js.map +0 -1
- package/dist/evm/smartWallet.types.js.map +0 -1
- package/dist/evm/transaction.utils.js.map +0 -1
- package/dist/evm/transactionParsing.js.map +0 -1
- package/dist/evm/utils.js.map +0 -1
- package/dist/helpers/index.js.map +0 -1
- package/dist/helpers/routeScan.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/old.js.map +0 -1
- package/dist/price.js.map +0 -1
- package/dist/price.types.js.map +0 -1
- package/dist/privacy/artifact-manager.d.ts +0 -117
- package/dist/privacy/artifact-manager.js +0 -251
- package/dist/privacy/artifact-manager.js.map +0 -1
- package/dist/privacy/broadcaster-client.d.ts +0 -166
- package/dist/privacy/broadcaster-client.js +0 -261
- package/dist/privacy/broadcaster-client.js.map +0 -1
- package/dist/privacy/index.d.ts +0 -34
- package/dist/privacy/index.js +0 -56
- package/dist/privacy/index.js.map +0 -1
- package/dist/privacy/network-config.d.ts +0 -57
- package/dist/privacy/network-config.js +0 -118
- package/dist/privacy/network-config.js.map +0 -1
- package/dist/privacy/poi-helper.d.ts +0 -161
- package/dist/privacy/poi-helper.js +0 -249
- package/dist/privacy/poi-helper.js.map +0 -1
- package/dist/privacy/railgun-engine.d.ts +0 -135
- package/dist/privacy/railgun-engine.js +0 -205
- package/dist/privacy/railgun-engine.js.map +0 -1
- package/dist/privacy/railgun-privacy-wallet.d.ts +0 -288
- package/dist/privacy/railgun-privacy-wallet.js +0 -539
- package/dist/privacy/railgun-privacy-wallet.js.map +0 -1
- package/dist/privacy/types.d.ts +0 -229
- package/dist/privacy/types.js +0 -26
- package/dist/privacy/types.js.map +0 -1
- package/dist/savings/index.js.map +0 -1
- package/dist/savings/saving-actions.d.ts +0 -0
- package/dist/savings/saving-actions.js +0 -78
- package/dist/savings/saving-actions.js.map +0 -1
- package/dist/savings/saving-manager.js.map +0 -1
- package/dist/savings/savings-manager.d.ts +0 -126
- package/dist/savings/savings-manager.js +0 -234
- package/dist/savings/savings-manager.js.map +0 -1
- package/dist/savings/smart-savings.js.map +0 -1
- package/dist/savings/types.js.map +0 -1
- package/dist/svm/constant.js.map +0 -1
- package/dist/svm/index.js.map +0 -1
- package/dist/svm/svm.js.map +0 -1
- package/dist/svm/transactionParsing.js.map +0 -1
- package/dist/svm/transactionSender.js.map +0 -1
- package/dist/svm/utils.js.map +0 -1
- package/dist/test.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/dist/vm.js.map +0 -1
- package/dist/walletBip32.js.map +0 -1
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter for RPC calls
|
|
3
|
+
*
|
|
4
|
+
* Prevents overwhelming RPC endpoints with too many concurrent requests.
|
|
5
|
+
* Useful for wallet discovery, batch operations, and API calls.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface RateLimiterOptions {
|
|
9
|
+
/** Maximum concurrent operations */
|
|
10
|
+
maxConcurrent?: number;
|
|
11
|
+
/** Minimum delay between operations (ms) */
|
|
12
|
+
delayMs?: number;
|
|
13
|
+
/** Maximum queue size (0 = unlimited) */
|
|
14
|
+
maxQueueSize?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Simple rate limiter with concurrency control
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const limiter = new RateLimiter({ maxConcurrent: 5, delayMs: 200 });
|
|
23
|
+
*
|
|
24
|
+
* // Schedule multiple operations
|
|
25
|
+
* const results = await Promise.all(
|
|
26
|
+
* addresses.map(addr =>
|
|
27
|
+
* limiter.schedule(() => provider.getBalance(addr))
|
|
28
|
+
* )
|
|
29
|
+
* );
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export class RateLimiter {
|
|
33
|
+
private running = 0;
|
|
34
|
+
private queue: Array<() => void> = [];
|
|
35
|
+
private lastExecutionTime = 0;
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
protected options: RateLimiterOptions = {}
|
|
39
|
+
) {
|
|
40
|
+
this.options.maxConcurrent = options.maxConcurrent || 5;
|
|
41
|
+
this.options.delayMs = options.delayMs || 100;
|
|
42
|
+
this.options.maxQueueSize = options.maxQueueSize || 0; // 0 = unlimited
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Schedule an async operation with rate limiting
|
|
47
|
+
*
|
|
48
|
+
* @param fn - Async function to execute
|
|
49
|
+
* @returns Promise resolving to function result
|
|
50
|
+
* @throws Error if queue is full
|
|
51
|
+
*/
|
|
52
|
+
async schedule<T>(fn: () => Promise<T>): Promise<T> {
|
|
53
|
+
// Check queue size
|
|
54
|
+
if (this.options.maxQueueSize! > 0 && this.queue.length >= this.options.maxQueueSize!) {
|
|
55
|
+
throw new Error(`Rate limiter queue full (${this.queue.length}/${this.options.maxQueueSize})`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Wait for available slot
|
|
59
|
+
await this.waitForSlot();
|
|
60
|
+
|
|
61
|
+
this.running++;
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
// Enforce minimum delay between operations
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const timeSinceLastExecution = now - this.lastExecutionTime;
|
|
67
|
+
|
|
68
|
+
if (timeSinceLastExecution < this.options.delayMs!) {
|
|
69
|
+
await new Promise(resolve =>
|
|
70
|
+
setTimeout(resolve, this.options.delayMs! - timeSinceLastExecution)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.lastExecutionTime = Date.now();
|
|
75
|
+
|
|
76
|
+
// Execute the function
|
|
77
|
+
return await fn();
|
|
78
|
+
} finally {
|
|
79
|
+
this.running--;
|
|
80
|
+
this.processQueue();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Wait for an available execution slot
|
|
86
|
+
*/
|
|
87
|
+
private async waitForSlot(): Promise<void> {
|
|
88
|
+
if (this.running < this.options.maxConcurrent!) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return new Promise<void>(resolve => {
|
|
93
|
+
this.queue.push(resolve);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Process queued operations
|
|
99
|
+
*/
|
|
100
|
+
private processQueue(): void {
|
|
101
|
+
if (this.queue.length > 0 && this.running < this.options.maxConcurrent!) {
|
|
102
|
+
const resolve = this.queue.shift();
|
|
103
|
+
if (resolve) {
|
|
104
|
+
resolve();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get current stats
|
|
111
|
+
*/
|
|
112
|
+
getStats() {
|
|
113
|
+
return {
|
|
114
|
+
running: this.running,
|
|
115
|
+
queued: this.queue.length,
|
|
116
|
+
maxConcurrent: this.options.maxConcurrent
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Clear the queue
|
|
122
|
+
*/
|
|
123
|
+
clear(): void {
|
|
124
|
+
this.queue = [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Adaptive rate limiter that adjusts based on errors
|
|
130
|
+
*
|
|
131
|
+
* Automatically backs off when rate limit errors are detected.
|
|
132
|
+
*/
|
|
133
|
+
export class AdaptiveRateLimiter extends RateLimiter {
|
|
134
|
+
private consecutiveErrors = 0;
|
|
135
|
+
private baseDelayMs: number;
|
|
136
|
+
|
|
137
|
+
constructor(options: RateLimiterOptions = {}) {
|
|
138
|
+
super(options);
|
|
139
|
+
this.baseDelayMs = options.delayMs || 100;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async schedule<T>(fn: () => Promise<T>): Promise<T> {
|
|
143
|
+
try {
|
|
144
|
+
const result = await super.schedule(fn);
|
|
145
|
+
// Success - reset error count
|
|
146
|
+
this.consecutiveErrors = 0;
|
|
147
|
+
this.options.delayMs = this.baseDelayMs;
|
|
148
|
+
return result;
|
|
149
|
+
} catch (error: any) {
|
|
150
|
+
// Check if it's a rate limit error
|
|
151
|
+
if (this.isRateLimitError(error)) {
|
|
152
|
+
this.consecutiveErrors++;
|
|
153
|
+
|
|
154
|
+
// Exponential backoff
|
|
155
|
+
this.options.delayMs = Math.min(
|
|
156
|
+
this.baseDelayMs * Math.pow(2, this.consecutiveErrors),
|
|
157
|
+
10000 // Max 10 seconds
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
console.warn(
|
|
161
|
+
`Rate limit detected. Backing off to ${this.options.delayMs}ms delay`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private isRateLimitError(error: any): boolean {
|
|
170
|
+
const message = error.message?.toLowerCase() || '';
|
|
171
|
+
return (
|
|
172
|
+
message.includes('rate limit') ||
|
|
173
|
+
message.includes('too many requests') ||
|
|
174
|
+
message.includes('429') ||
|
|
175
|
+
error.code === 'RATE_LIMIT' ||
|
|
176
|
+
error.code === 429
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intelligent retry logic for blockchain operations
|
|
3
|
+
*
|
|
4
|
+
* Distinguishes between transient and permanent errors,
|
|
5
|
+
* only retrying operations that have a chance of success.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface RetryOptions {
|
|
9
|
+
/** Maximum number of retries */
|
|
10
|
+
maxRetries?: number;
|
|
11
|
+
/** Initial delay in ms */
|
|
12
|
+
initialDelay?: number;
|
|
13
|
+
/** Maximum delay in ms */
|
|
14
|
+
maxDelay?: number;
|
|
15
|
+
/** Backoff multiplier */
|
|
16
|
+
backoffMultiplier?: number;
|
|
17
|
+
/** Custom retry predicate */
|
|
18
|
+
shouldRetry?: (error: any, attempt: number) => boolean;
|
|
19
|
+
/** Callback for retry attempts */
|
|
20
|
+
onRetry?: (error: any, attempt: number, delay: number) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Retryable error codes
|
|
25
|
+
*/
|
|
26
|
+
export const RETRYABLE_ERROR_CODES = new Set([
|
|
27
|
+
// Network errors
|
|
28
|
+
'ECONNRESET',
|
|
29
|
+
'ETIMEDOUT',
|
|
30
|
+
'ENOTFOUND',
|
|
31
|
+
'ECONNREFUSED',
|
|
32
|
+
'ENETUNREACH',
|
|
33
|
+
'EAI_AGAIN',
|
|
34
|
+
|
|
35
|
+
// HTTP errors
|
|
36
|
+
'NETWORK_ERROR',
|
|
37
|
+
'TIMEOUT',
|
|
38
|
+
'SERVER_ERROR',
|
|
39
|
+
|
|
40
|
+
// RPC errors
|
|
41
|
+
'RATE_LIMIT',
|
|
42
|
+
'TOO_MANY_REQUESTS',
|
|
43
|
+
'SERVICE_UNAVAILABLE',
|
|
44
|
+
'GATEWAY_TIMEOUT',
|
|
45
|
+
|
|
46
|
+
// Blockchain errors
|
|
47
|
+
'NONCE_EXPIRED',
|
|
48
|
+
'REPLACEMENT_UNDERPRICED',
|
|
49
|
+
'NETWORK_CHANGED'
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Non-retryable error patterns
|
|
54
|
+
*/
|
|
55
|
+
const NON_RETRYABLE_PATTERNS = [
|
|
56
|
+
'insufficient funds',
|
|
57
|
+
'invalid address',
|
|
58
|
+
'invalid signature',
|
|
59
|
+
'nonce too low',
|
|
60
|
+
'gas too low',
|
|
61
|
+
'execution reverted',
|
|
62
|
+
'invalid opcode',
|
|
63
|
+
'out of gas',
|
|
64
|
+
'user denied',
|
|
65
|
+
'user rejected',
|
|
66
|
+
'already known',
|
|
67
|
+
'intrinsic gas too low'
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if an error is retryable
|
|
72
|
+
*
|
|
73
|
+
* @param error - Error to check
|
|
74
|
+
* @returns true if error might be transient
|
|
75
|
+
*/
|
|
76
|
+
export function isRetryableError(error: any): boolean {
|
|
77
|
+
// Check error code
|
|
78
|
+
if (error.code && RETRYABLE_ERROR_CODES.has(error.code)) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check status code
|
|
83
|
+
if (error.status === 429 || error.status === 503 || error.status === 504) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check error message
|
|
88
|
+
const message = error.message?.toLowerCase() || '';
|
|
89
|
+
|
|
90
|
+
// Check for retryable patterns
|
|
91
|
+
if (
|
|
92
|
+
message.includes('rate limit') ||
|
|
93
|
+
message.includes('timeout') ||
|
|
94
|
+
message.includes('network') ||
|
|
95
|
+
message.includes('connection') ||
|
|
96
|
+
message.includes('temporary') ||
|
|
97
|
+
message.includes('try again')
|
|
98
|
+
) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check for non-retryable patterns
|
|
103
|
+
for (const pattern of NON_RETRYABLE_PATTERNS) {
|
|
104
|
+
if (message.includes(pattern)) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Default: don't retry unknown errors
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Calculate delay with exponential backoff and jitter
|
|
115
|
+
*
|
|
116
|
+
* @param attempt - Current attempt number (1-based)
|
|
117
|
+
* @param initialDelay - Initial delay in ms
|
|
118
|
+
* @param maxDelay - Maximum delay in ms
|
|
119
|
+
* @param multiplier - Backoff multiplier
|
|
120
|
+
* @returns Delay in ms
|
|
121
|
+
*/
|
|
122
|
+
export function calculateBackoffDelay(
|
|
123
|
+
attempt: number,
|
|
124
|
+
initialDelay: number = 1000,
|
|
125
|
+
maxDelay: number = 30000,
|
|
126
|
+
multiplier: number = 2
|
|
127
|
+
): number {
|
|
128
|
+
// Exponential backoff: initialDelay * multiplier^(attempt-1)
|
|
129
|
+
const exponentialDelay = initialDelay * Math.pow(multiplier, attempt - 1);
|
|
130
|
+
|
|
131
|
+
// Cap at maxDelay
|
|
132
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
133
|
+
|
|
134
|
+
// Add jitter (±20%) to prevent thundering herd
|
|
135
|
+
const jitter = cappedDelay * 0.2 * (Math.random() - 0.5);
|
|
136
|
+
|
|
137
|
+
return Math.floor(cappedDelay + jitter);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Retry an async operation with intelligent backoff
|
|
142
|
+
*
|
|
143
|
+
* @param operation - Async function to retry
|
|
144
|
+
* @param options - Retry options
|
|
145
|
+
* @returns Result of the operation
|
|
146
|
+
* @throws Last error if all retries exhausted
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```typescript
|
|
150
|
+
* const balance = await retryWithBackoff(
|
|
151
|
+
* () => provider.getBalance(address),
|
|
152
|
+
* {
|
|
153
|
+
* maxRetries: 3,
|
|
154
|
+
* initialDelay: 1000,
|
|
155
|
+
* onRetry: (error, attempt, delay) => {
|
|
156
|
+
* console.log(`Retry ${attempt} after ${delay}ms:`, error.message);
|
|
157
|
+
* }
|
|
158
|
+
* }
|
|
159
|
+
* );
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export async function retryWithBackoff<T>(
|
|
163
|
+
operation: () => Promise<T>,
|
|
164
|
+
options: RetryOptions = {}
|
|
165
|
+
): Promise<T> {
|
|
166
|
+
const {
|
|
167
|
+
maxRetries = 3,
|
|
168
|
+
initialDelay = 1000,
|
|
169
|
+
maxDelay = 30000,
|
|
170
|
+
backoffMultiplier = 2,
|
|
171
|
+
shouldRetry = isRetryableError,
|
|
172
|
+
onRetry
|
|
173
|
+
} = options;
|
|
174
|
+
|
|
175
|
+
let lastError: any;
|
|
176
|
+
|
|
177
|
+
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
|
178
|
+
try {
|
|
179
|
+
return await operation();
|
|
180
|
+
} catch (error: any) {
|
|
181
|
+
lastError = error;
|
|
182
|
+
|
|
183
|
+
// Check if we should retry
|
|
184
|
+
const isLastAttempt = attempt === maxRetries + 1;
|
|
185
|
+
const canRetry = shouldRetry(error, attempt);
|
|
186
|
+
|
|
187
|
+
if (isLastAttempt || !canRetry) {
|
|
188
|
+
// Attach retry metadata to error
|
|
189
|
+
error.attempts = attempt;
|
|
190
|
+
error.retryable = canRetry;
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Calculate delay
|
|
195
|
+
const delay = calculateBackoffDelay(
|
|
196
|
+
attempt,
|
|
197
|
+
initialDelay,
|
|
198
|
+
maxDelay,
|
|
199
|
+
backoffMultiplier
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Notify caller
|
|
203
|
+
if (onRetry) {
|
|
204
|
+
onRetry(error, attempt, delay);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Wait before retry
|
|
208
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Should not reach here, but TypeScript needs it
|
|
213
|
+
throw lastError;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Retry with linear backoff (simpler, more predictable)
|
|
218
|
+
*
|
|
219
|
+
* @param operation - Async function to retry
|
|
220
|
+
* @param maxRetries - Maximum retries
|
|
221
|
+
* @param delayMs - Fixed delay between retries
|
|
222
|
+
* @returns Result of the operation
|
|
223
|
+
*/
|
|
224
|
+
export async function retryWithLinearBackoff<T>(
|
|
225
|
+
operation: () => Promise<T>,
|
|
226
|
+
maxRetries: number = 3,
|
|
227
|
+
delayMs: number = 1000
|
|
228
|
+
): Promise<T> {
|
|
229
|
+
return retryWithBackoff(operation, {
|
|
230
|
+
maxRetries,
|
|
231
|
+
initialDelay: delayMs,
|
|
232
|
+
maxDelay: delayMs,
|
|
233
|
+
backoffMultiplier: 1 // No exponential growth
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Retry for blockchain transactions with custom logic
|
|
239
|
+
*
|
|
240
|
+
* @param operation - Transaction operation
|
|
241
|
+
* @param options - Retry options
|
|
242
|
+
* @returns Transaction result
|
|
243
|
+
*/
|
|
244
|
+
export async function retryTransaction<T>(
|
|
245
|
+
operation: () => Promise<T>,
|
|
246
|
+
options: RetryOptions = {}
|
|
247
|
+
): Promise<T> {
|
|
248
|
+
return retryWithBackoff(operation, {
|
|
249
|
+
maxRetries: 5, // More retries for transactions
|
|
250
|
+
initialDelay: 2000, // Longer initial delay
|
|
251
|
+
maxDelay: 60000, // Up to 1 minute
|
|
252
|
+
...options,
|
|
253
|
+
shouldRetry: (error, attempt) => {
|
|
254
|
+
// Custom logic for transactions
|
|
255
|
+
const message = error.message?.toLowerCase() || '';
|
|
256
|
+
|
|
257
|
+
// Don't retry user rejections
|
|
258
|
+
if (message.includes('user denied') || message.includes('user rejected')) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Don't retry if transaction already mined
|
|
263
|
+
if (message.includes('already known') || message.includes('nonce too low')) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Retry network/rate limit errors
|
|
268
|
+
return isRetryableError(error);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|