@t402/wdk 2.0.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/README.md +511 -0
- package/dist/cjs/index.d.ts +1285 -0
- package/dist/cjs/index.js +2252 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.d.mts +1285 -0
- package/dist/esm/index.mjs +2193 -0
- package/dist/esm/index.mjs.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,2252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BalanceCache: () => BalanceCache,
|
|
24
|
+
BalanceError: () => BalanceError,
|
|
25
|
+
BridgeError: () => BridgeError,
|
|
26
|
+
CHAIN_TOKENS: () => CHAIN_TOKENS,
|
|
27
|
+
ChainError: () => ChainError,
|
|
28
|
+
DEFAULT_BALANCE_CACHE_CONFIG: () => DEFAULT_BALANCE_CACHE_CONFIG,
|
|
29
|
+
DEFAULT_CACHE_CONFIG: () => DEFAULT_CACHE_CONFIG,
|
|
30
|
+
DEFAULT_CHAINS: () => DEFAULT_CHAINS,
|
|
31
|
+
DEFAULT_RETRY_CONFIG: () => DEFAULT_RETRY_CONFIG,
|
|
32
|
+
DEFAULT_RPC_ENDPOINTS: () => DEFAULT_RPC_ENDPOINTS,
|
|
33
|
+
LAYERZERO_ENDPOINT_IDS: () => import_evm3.LAYERZERO_ENDPOINT_IDS,
|
|
34
|
+
MockWDKSigner: () => MockWDKSigner,
|
|
35
|
+
RPCError: () => RPCError,
|
|
36
|
+
SignerError: () => SignerError,
|
|
37
|
+
SigningError: () => SigningError,
|
|
38
|
+
T402WDK: () => T402WDK,
|
|
39
|
+
TTLCache: () => TTLCache,
|
|
40
|
+
TransactionError: () => TransactionError,
|
|
41
|
+
USDC_ADDRESSES: () => USDC_ADDRESSES,
|
|
42
|
+
USDT0_ADDRESSES: () => USDT0_ADDRESSES,
|
|
43
|
+
USDT0_OFT_ADDRESSES: () => import_evm3.USDT0_OFT_ADDRESSES,
|
|
44
|
+
USDT_LEGACY_ADDRESSES: () => USDT_LEGACY_ADDRESSES,
|
|
45
|
+
Usdt0Bridge: () => import_evm3.Usdt0Bridge,
|
|
46
|
+
WDKError: () => WDKError,
|
|
47
|
+
WDKErrorCode: () => WDKErrorCode,
|
|
48
|
+
WDKInitializationError: () => WDKInitializationError,
|
|
49
|
+
WDKSigner: () => WDKSigner,
|
|
50
|
+
WdkBridge: () => WdkBridge,
|
|
51
|
+
createDirectBridge: () => createDirectBridge,
|
|
52
|
+
createWDKSigner: () => createWDKSigner,
|
|
53
|
+
getBridgeableChains: () => import_evm3.getBridgeableChains,
|
|
54
|
+
getChainFromNetwork: () => getChainFromNetwork,
|
|
55
|
+
getChainId: () => getChainId,
|
|
56
|
+
getNetworkFromChain: () => getNetworkFromChain,
|
|
57
|
+
getPreferredToken: () => getPreferredToken,
|
|
58
|
+
getUsdt0Chains: () => getUsdt0Chains,
|
|
59
|
+
hasErrorCode: () => hasErrorCode,
|
|
60
|
+
isWDKError: () => isWDKError,
|
|
61
|
+
normalizeChainConfig: () => normalizeChainConfig,
|
|
62
|
+
supportsBridging: () => import_evm3.supportsBridging,
|
|
63
|
+
withRetry: () => withRetry,
|
|
64
|
+
withTimeout: () => withTimeout,
|
|
65
|
+
wrapError: () => wrapError
|
|
66
|
+
});
|
|
67
|
+
module.exports = __toCommonJS(index_exports);
|
|
68
|
+
|
|
69
|
+
// src/cache.ts
|
|
70
|
+
var DEFAULT_CACHE_CONFIG = {
|
|
71
|
+
defaultTTL: 3e4,
|
|
72
|
+
// 30 seconds
|
|
73
|
+
maxSize: 1e3,
|
|
74
|
+
refreshOnAccess: false
|
|
75
|
+
};
|
|
76
|
+
var TTLCache = class {
|
|
77
|
+
constructor(config = {}) {
|
|
78
|
+
this._cache = /* @__PURE__ */ new Map();
|
|
79
|
+
this._cleanupInterval = null;
|
|
80
|
+
this._config = { ...DEFAULT_CACHE_CONFIG, ...config };
|
|
81
|
+
if (this._config.maxSize > 0) {
|
|
82
|
+
this._startCleanup();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get a value from the cache
|
|
87
|
+
*
|
|
88
|
+
* @param key - Cache key
|
|
89
|
+
* @returns The cached value or undefined if not found/expired
|
|
90
|
+
*/
|
|
91
|
+
get(key) {
|
|
92
|
+
const entry = this._cache.get(key);
|
|
93
|
+
if (!entry) {
|
|
94
|
+
return void 0;
|
|
95
|
+
}
|
|
96
|
+
if (Date.now() > entry.expiresAt) {
|
|
97
|
+
this._cache.delete(key);
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
if (this._config.refreshOnAccess) {
|
|
101
|
+
entry.expiresAt = Date.now() + this._config.defaultTTL;
|
|
102
|
+
}
|
|
103
|
+
return entry.value;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Set a value in the cache
|
|
107
|
+
*
|
|
108
|
+
* @param key - Cache key
|
|
109
|
+
* @param value - Value to cache
|
|
110
|
+
* @param ttl - TTL in milliseconds (optional, uses default if not provided)
|
|
111
|
+
*/
|
|
112
|
+
set(key, value, ttl) {
|
|
113
|
+
if (this._cache.size >= this._config.maxSize) {
|
|
114
|
+
this._evictOldest();
|
|
115
|
+
}
|
|
116
|
+
const expiresAt = Date.now() + (ttl ?? this._config.defaultTTL);
|
|
117
|
+
this._cache.set(key, { value, expiresAt });
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if a key exists and is not expired
|
|
121
|
+
*
|
|
122
|
+
* @param key - Cache key
|
|
123
|
+
* @returns true if key exists and is not expired
|
|
124
|
+
*/
|
|
125
|
+
has(key) {
|
|
126
|
+
const entry = this._cache.get(key);
|
|
127
|
+
if (!entry) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
if (Date.now() > entry.expiresAt) {
|
|
131
|
+
this._cache.delete(key);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Delete a key from the cache
|
|
138
|
+
*
|
|
139
|
+
* @param key - Cache key
|
|
140
|
+
* @returns true if key was deleted
|
|
141
|
+
*/
|
|
142
|
+
delete(key) {
|
|
143
|
+
return this._cache.delete(key);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Delete all keys matching a prefix
|
|
147
|
+
*
|
|
148
|
+
* @param prefix - Key prefix to match
|
|
149
|
+
* @returns Number of keys deleted
|
|
150
|
+
*/
|
|
151
|
+
deleteByPrefix(prefix) {
|
|
152
|
+
let count = 0;
|
|
153
|
+
for (const key of this._cache.keys()) {
|
|
154
|
+
if (key.startsWith(prefix)) {
|
|
155
|
+
this._cache.delete(key);
|
|
156
|
+
count++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return count;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Clear all entries from the cache
|
|
163
|
+
*/
|
|
164
|
+
clear() {
|
|
165
|
+
this._cache.clear();
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get the number of entries in the cache (including expired)
|
|
169
|
+
*/
|
|
170
|
+
get size() {
|
|
171
|
+
return this._cache.size;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get the number of valid (non-expired) entries
|
|
175
|
+
*/
|
|
176
|
+
get validSize() {
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
let count = 0;
|
|
179
|
+
for (const entry of this._cache.values()) {
|
|
180
|
+
if (now <= entry.expiresAt) {
|
|
181
|
+
count++;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return count;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get all valid keys
|
|
188
|
+
*/
|
|
189
|
+
keys() {
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
const validKeys = [];
|
|
192
|
+
for (const [key, entry] of this._cache.entries()) {
|
|
193
|
+
if (now <= entry.expiresAt) {
|
|
194
|
+
validKeys.push(key);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return validKeys;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get remaining TTL for a key in milliseconds
|
|
201
|
+
*
|
|
202
|
+
* @param key - Cache key
|
|
203
|
+
* @returns Remaining TTL in ms, or -1 if not found/expired
|
|
204
|
+
*/
|
|
205
|
+
getTTL(key) {
|
|
206
|
+
const entry = this._cache.get(key);
|
|
207
|
+
if (!entry) {
|
|
208
|
+
return -1;
|
|
209
|
+
}
|
|
210
|
+
const remaining = entry.expiresAt - Date.now();
|
|
211
|
+
if (remaining <= 0) {
|
|
212
|
+
this._cache.delete(key);
|
|
213
|
+
return -1;
|
|
214
|
+
}
|
|
215
|
+
return remaining;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Update TTL for an existing key
|
|
219
|
+
*
|
|
220
|
+
* @param key - Cache key
|
|
221
|
+
* @param ttl - New TTL in milliseconds
|
|
222
|
+
* @returns true if key exists and TTL was updated
|
|
223
|
+
*/
|
|
224
|
+
touch(key, ttl) {
|
|
225
|
+
const entry = this._cache.get(key);
|
|
226
|
+
if (!entry) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
if (Date.now() > entry.expiresAt) {
|
|
230
|
+
this._cache.delete(key);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
entry.expiresAt = Date.now() + (ttl ?? this._config.defaultTTL);
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get or set a value using a factory function
|
|
238
|
+
*
|
|
239
|
+
* @param key - Cache key
|
|
240
|
+
* @param factory - Function to create value if not cached
|
|
241
|
+
* @param ttl - TTL in milliseconds (optional)
|
|
242
|
+
* @returns The cached or newly created value
|
|
243
|
+
*/
|
|
244
|
+
async getOrSet(key, factory, ttl) {
|
|
245
|
+
const cached = this.get(key);
|
|
246
|
+
if (cached !== void 0) {
|
|
247
|
+
return cached;
|
|
248
|
+
}
|
|
249
|
+
const value = await factory();
|
|
250
|
+
this.set(key, value, ttl);
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get cache statistics
|
|
255
|
+
*/
|
|
256
|
+
getStats() {
|
|
257
|
+
const now = Date.now();
|
|
258
|
+
let validCount = 0;
|
|
259
|
+
let expiredCount = 0;
|
|
260
|
+
let oldestExpiry = Infinity;
|
|
261
|
+
let newestExpiry = 0;
|
|
262
|
+
for (const entry of this._cache.values()) {
|
|
263
|
+
if (now <= entry.expiresAt) {
|
|
264
|
+
validCount++;
|
|
265
|
+
if (entry.expiresAt < oldestExpiry) oldestExpiry = entry.expiresAt;
|
|
266
|
+
if (entry.expiresAt > newestExpiry) newestExpiry = entry.expiresAt;
|
|
267
|
+
} else {
|
|
268
|
+
expiredCount++;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
totalSize: this._cache.size,
|
|
273
|
+
validSize: validCount,
|
|
274
|
+
expiredSize: expiredCount,
|
|
275
|
+
maxSize: this._config.maxSize,
|
|
276
|
+
defaultTTL: this._config.defaultTTL,
|
|
277
|
+
oldestExpiryMs: oldestExpiry === Infinity ? 0 : oldestExpiry - now,
|
|
278
|
+
newestExpiryMs: newestExpiry === 0 ? 0 : newestExpiry - now
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Stop the cleanup interval
|
|
283
|
+
*/
|
|
284
|
+
dispose() {
|
|
285
|
+
if (this._cleanupInterval) {
|
|
286
|
+
clearInterval(this._cleanupInterval);
|
|
287
|
+
this._cleanupInterval = null;
|
|
288
|
+
}
|
|
289
|
+
this._cache.clear();
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Start periodic cleanup of expired entries
|
|
293
|
+
*/
|
|
294
|
+
_startCleanup() {
|
|
295
|
+
this._cleanupInterval = setInterval(() => {
|
|
296
|
+
this._removeExpired();
|
|
297
|
+
}, 6e4);
|
|
298
|
+
if (this._cleanupInterval.unref) {
|
|
299
|
+
this._cleanupInterval.unref();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Remove all expired entries
|
|
304
|
+
*/
|
|
305
|
+
_removeExpired() {
|
|
306
|
+
const now = Date.now();
|
|
307
|
+
let count = 0;
|
|
308
|
+
for (const [key, entry] of this._cache.entries()) {
|
|
309
|
+
if (now > entry.expiresAt) {
|
|
310
|
+
this._cache.delete(key);
|
|
311
|
+
count++;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return count;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Evict oldest entries to make room
|
|
318
|
+
*/
|
|
319
|
+
_evictOldest() {
|
|
320
|
+
this._removeExpired();
|
|
321
|
+
if (this._cache.size >= this._config.maxSize) {
|
|
322
|
+
let oldestKey = null;
|
|
323
|
+
let oldestExpiry = Infinity;
|
|
324
|
+
for (const [key, entry] of this._cache.entries()) {
|
|
325
|
+
if (entry.expiresAt < oldestExpiry) {
|
|
326
|
+
oldestExpiry = entry.expiresAt;
|
|
327
|
+
oldestKey = key;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (oldestKey) {
|
|
331
|
+
this._cache.delete(oldestKey);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
var DEFAULT_BALANCE_CACHE_CONFIG = {
|
|
337
|
+
enabled: true,
|
|
338
|
+
nativeBalanceTTL: 15e3,
|
|
339
|
+
// 15 seconds
|
|
340
|
+
tokenBalanceTTL: 3e4,
|
|
341
|
+
// 30 seconds
|
|
342
|
+
aggregatedBalanceTTL: 6e4,
|
|
343
|
+
// 60 seconds
|
|
344
|
+
maxSize: 500
|
|
345
|
+
};
|
|
346
|
+
var BalanceCache = class {
|
|
347
|
+
constructor(config = {}) {
|
|
348
|
+
this._config = { ...DEFAULT_BALANCE_CACHE_CONFIG, ...config };
|
|
349
|
+
this._cache = new TTLCache({
|
|
350
|
+
defaultTTL: this._config.tokenBalanceTTL,
|
|
351
|
+
maxSize: this._config.maxSize
|
|
352
|
+
});
|
|
353
|
+
this._aggregatedCache = new TTLCache({
|
|
354
|
+
defaultTTL: this._config.aggregatedBalanceTTL,
|
|
355
|
+
maxSize: 100
|
|
356
|
+
// Fewer aggregated balance entries
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Check if caching is enabled
|
|
361
|
+
*/
|
|
362
|
+
get enabled() {
|
|
363
|
+
return this._config.enabled;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get cache configuration
|
|
367
|
+
*/
|
|
368
|
+
get config() {
|
|
369
|
+
return { ...this._config };
|
|
370
|
+
}
|
|
371
|
+
// ========== Native Balance Methods ==========
|
|
372
|
+
/**
|
|
373
|
+
* Get cached native balance
|
|
374
|
+
*/
|
|
375
|
+
getNativeBalance(chain, address) {
|
|
376
|
+
if (!this._config.enabled) return void 0;
|
|
377
|
+
return this._cache.get(this._nativeKey(chain, address));
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Set native balance in cache
|
|
381
|
+
*/
|
|
382
|
+
setNativeBalance(chain, address, balance) {
|
|
383
|
+
if (!this._config.enabled) return;
|
|
384
|
+
this._cache.set(this._nativeKey(chain, address), balance, this._config.nativeBalanceTTL);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Get or fetch native balance
|
|
388
|
+
*/
|
|
389
|
+
async getOrFetchNativeBalance(chain, address, fetcher) {
|
|
390
|
+
if (!this._config.enabled) {
|
|
391
|
+
return fetcher();
|
|
392
|
+
}
|
|
393
|
+
return this._cache.getOrSet(
|
|
394
|
+
this._nativeKey(chain, address),
|
|
395
|
+
fetcher,
|
|
396
|
+
this._config.nativeBalanceTTL
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
// ========== Token Balance Methods ==========
|
|
400
|
+
/**
|
|
401
|
+
* Get cached token balance
|
|
402
|
+
*/
|
|
403
|
+
getTokenBalance(chain, token, address) {
|
|
404
|
+
if (!this._config.enabled) return void 0;
|
|
405
|
+
return this._cache.get(this._tokenKey(chain, token, address));
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Set token balance in cache
|
|
409
|
+
*/
|
|
410
|
+
setTokenBalance(chain, token, address, balance) {
|
|
411
|
+
if (!this._config.enabled) return;
|
|
412
|
+
this._cache.set(this._tokenKey(chain, token, address), balance, this._config.tokenBalanceTTL);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Get or fetch token balance
|
|
416
|
+
*/
|
|
417
|
+
async getOrFetchTokenBalance(chain, token, address, fetcher) {
|
|
418
|
+
if (!this._config.enabled) {
|
|
419
|
+
return fetcher();
|
|
420
|
+
}
|
|
421
|
+
return this._cache.getOrSet(
|
|
422
|
+
this._tokenKey(chain, token, address),
|
|
423
|
+
fetcher,
|
|
424
|
+
this._config.tokenBalanceTTL
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
// ========== Aggregated Balance Methods ==========
|
|
428
|
+
/**
|
|
429
|
+
* Get cached aggregated balance
|
|
430
|
+
*/
|
|
431
|
+
getAggregatedBalance(key) {
|
|
432
|
+
if (!this._config.enabled) return void 0;
|
|
433
|
+
return this._aggregatedCache.get(this._aggregatedKey(key));
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Set aggregated balance in cache
|
|
437
|
+
*/
|
|
438
|
+
setAggregatedBalance(key, value) {
|
|
439
|
+
if (!this._config.enabled) return;
|
|
440
|
+
this._aggregatedCache.set(this._aggregatedKey(key), value, this._config.aggregatedBalanceTTL);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Get or fetch aggregated balance
|
|
444
|
+
*/
|
|
445
|
+
async getOrFetchAggregatedBalance(key, fetcher) {
|
|
446
|
+
if (!this._config.enabled) {
|
|
447
|
+
return fetcher();
|
|
448
|
+
}
|
|
449
|
+
return this._aggregatedCache.getOrSet(
|
|
450
|
+
this._aggregatedKey(key),
|
|
451
|
+
fetcher,
|
|
452
|
+
this._config.aggregatedBalanceTTL
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
// ========== Cache Management ==========
|
|
456
|
+
/**
|
|
457
|
+
* Invalidate all balances for a chain
|
|
458
|
+
*/
|
|
459
|
+
invalidateChain(chain) {
|
|
460
|
+
const count1 = this._cache.deleteByPrefix(`native:${chain}:`);
|
|
461
|
+
const count2 = this._cache.deleteByPrefix(`token:${chain}:`);
|
|
462
|
+
const count3 = this._aggregatedCache.deleteByPrefix(`agg:`);
|
|
463
|
+
return count1 + count2 + count3;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Invalidate all balances for an address
|
|
467
|
+
*/
|
|
468
|
+
invalidateAddress(address) {
|
|
469
|
+
let count = 0;
|
|
470
|
+
const lowerAddress = address.toLowerCase();
|
|
471
|
+
for (const key of this._cache.keys()) {
|
|
472
|
+
if (key.toLowerCase().includes(lowerAddress)) {
|
|
473
|
+
this._cache.delete(key);
|
|
474
|
+
count++;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
this._aggregatedCache.clear();
|
|
478
|
+
return count;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Invalidate specific token balance
|
|
482
|
+
*/
|
|
483
|
+
invalidateTokenBalance(chain, token, address) {
|
|
484
|
+
return this._cache.delete(this._tokenKey(chain, token, address));
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Clear all caches
|
|
488
|
+
*/
|
|
489
|
+
clear() {
|
|
490
|
+
this._cache.clear();
|
|
491
|
+
this._aggregatedCache.clear();
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Get cache statistics
|
|
495
|
+
*/
|
|
496
|
+
getStats() {
|
|
497
|
+
return {
|
|
498
|
+
balanceCache: this._cache.getStats(),
|
|
499
|
+
aggregatedCache: this._aggregatedCache.getStats(),
|
|
500
|
+
config: this.config
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Dispose of cache resources
|
|
505
|
+
*/
|
|
506
|
+
dispose() {
|
|
507
|
+
this._cache.dispose();
|
|
508
|
+
this._aggregatedCache.dispose();
|
|
509
|
+
}
|
|
510
|
+
// ========== Private Key Generators ==========
|
|
511
|
+
_nativeKey(chain, address) {
|
|
512
|
+
return `native:${chain}:${address.toLowerCase()}`;
|
|
513
|
+
}
|
|
514
|
+
_tokenKey(chain, token, address) {
|
|
515
|
+
return `token:${chain}:${token.toLowerCase()}:${address.toLowerCase()}`;
|
|
516
|
+
}
|
|
517
|
+
_aggregatedKey(key) {
|
|
518
|
+
return `agg:${key}`;
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// src/chains.ts
|
|
523
|
+
var DEFAULT_CHAINS = {
|
|
524
|
+
ethereum: {
|
|
525
|
+
chainId: 1,
|
|
526
|
+
network: "eip155:1",
|
|
527
|
+
name: "ethereum"
|
|
528
|
+
},
|
|
529
|
+
arbitrum: {
|
|
530
|
+
chainId: 42161,
|
|
531
|
+
network: "eip155:42161",
|
|
532
|
+
name: "arbitrum"
|
|
533
|
+
},
|
|
534
|
+
base: {
|
|
535
|
+
chainId: 8453,
|
|
536
|
+
network: "eip155:8453",
|
|
537
|
+
name: "base"
|
|
538
|
+
},
|
|
539
|
+
ink: {
|
|
540
|
+
chainId: 57073,
|
|
541
|
+
network: "eip155:57073",
|
|
542
|
+
name: "ink"
|
|
543
|
+
},
|
|
544
|
+
berachain: {
|
|
545
|
+
chainId: 80094,
|
|
546
|
+
network: "eip155:80094",
|
|
547
|
+
name: "berachain"
|
|
548
|
+
},
|
|
549
|
+
unichain: {
|
|
550
|
+
chainId: 130,
|
|
551
|
+
network: "eip155:130",
|
|
552
|
+
name: "unichain"
|
|
553
|
+
},
|
|
554
|
+
polygon: {
|
|
555
|
+
chainId: 137,
|
|
556
|
+
network: "eip155:137",
|
|
557
|
+
name: "polygon"
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
var DEFAULT_RPC_ENDPOINTS = {
|
|
561
|
+
ethereum: "https://eth.drpc.org",
|
|
562
|
+
arbitrum: "https://arb1.arbitrum.io/rpc",
|
|
563
|
+
base: "https://mainnet.base.org",
|
|
564
|
+
ink: "https://rpc-gel.inkonchain.com",
|
|
565
|
+
polygon: "https://polygon-rpc.com"
|
|
566
|
+
};
|
|
567
|
+
var USDT0_ADDRESSES = {
|
|
568
|
+
ethereum: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
|
|
569
|
+
arbitrum: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
|
|
570
|
+
ink: "0x0200C29006150606B650577BBE7B6248F58470c1",
|
|
571
|
+
berachain: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
572
|
+
unichain: "0x588ce4F028D8e7B53B687865d6A67b3A54C75518"
|
|
573
|
+
};
|
|
574
|
+
var USDC_ADDRESSES = {
|
|
575
|
+
ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
576
|
+
arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
577
|
+
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
578
|
+
polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
|
|
579
|
+
};
|
|
580
|
+
var USDT_LEGACY_ADDRESSES = {
|
|
581
|
+
ethereum: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
582
|
+
polygon: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
|
|
583
|
+
};
|
|
584
|
+
var CHAIN_TOKENS = {
|
|
585
|
+
ethereum: [
|
|
586
|
+
{
|
|
587
|
+
address: USDT0_ADDRESSES.ethereum,
|
|
588
|
+
symbol: "USDT0",
|
|
589
|
+
name: "TetherToken",
|
|
590
|
+
decimals: 6,
|
|
591
|
+
supportsEIP3009: true
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
address: USDC_ADDRESSES.ethereum,
|
|
595
|
+
symbol: "USDC",
|
|
596
|
+
name: "USD Coin",
|
|
597
|
+
decimals: 6,
|
|
598
|
+
supportsEIP3009: true
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
address: USDT_LEGACY_ADDRESSES.ethereum,
|
|
602
|
+
symbol: "USDT",
|
|
603
|
+
name: "Tether USD",
|
|
604
|
+
decimals: 6,
|
|
605
|
+
supportsEIP3009: false
|
|
606
|
+
}
|
|
607
|
+
],
|
|
608
|
+
arbitrum: [
|
|
609
|
+
{
|
|
610
|
+
address: USDT0_ADDRESSES.arbitrum,
|
|
611
|
+
symbol: "USDT0",
|
|
612
|
+
name: "TetherToken",
|
|
613
|
+
decimals: 6,
|
|
614
|
+
supportsEIP3009: true
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
address: USDC_ADDRESSES.arbitrum,
|
|
618
|
+
symbol: "USDC",
|
|
619
|
+
name: "USD Coin",
|
|
620
|
+
decimals: 6,
|
|
621
|
+
supportsEIP3009: true
|
|
622
|
+
}
|
|
623
|
+
],
|
|
624
|
+
base: [
|
|
625
|
+
{
|
|
626
|
+
address: USDC_ADDRESSES.base,
|
|
627
|
+
symbol: "USDC",
|
|
628
|
+
name: "USD Coin",
|
|
629
|
+
decimals: 6,
|
|
630
|
+
supportsEIP3009: true
|
|
631
|
+
}
|
|
632
|
+
],
|
|
633
|
+
ink: [
|
|
634
|
+
{
|
|
635
|
+
address: USDT0_ADDRESSES.ink,
|
|
636
|
+
symbol: "USDT0",
|
|
637
|
+
name: "TetherToken",
|
|
638
|
+
decimals: 6,
|
|
639
|
+
supportsEIP3009: true
|
|
640
|
+
}
|
|
641
|
+
],
|
|
642
|
+
berachain: [
|
|
643
|
+
{
|
|
644
|
+
address: USDT0_ADDRESSES.berachain,
|
|
645
|
+
symbol: "USDT0",
|
|
646
|
+
name: "TetherToken",
|
|
647
|
+
decimals: 6,
|
|
648
|
+
supportsEIP3009: true
|
|
649
|
+
}
|
|
650
|
+
],
|
|
651
|
+
unichain: [
|
|
652
|
+
{
|
|
653
|
+
address: USDT0_ADDRESSES.unichain,
|
|
654
|
+
symbol: "USDT0",
|
|
655
|
+
name: "TetherToken",
|
|
656
|
+
decimals: 6,
|
|
657
|
+
supportsEIP3009: true
|
|
658
|
+
}
|
|
659
|
+
],
|
|
660
|
+
polygon: [
|
|
661
|
+
{
|
|
662
|
+
address: USDC_ADDRESSES.polygon,
|
|
663
|
+
symbol: "USDC",
|
|
664
|
+
name: "USD Coin",
|
|
665
|
+
decimals: 6,
|
|
666
|
+
supportsEIP3009: true
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
address: USDT_LEGACY_ADDRESSES.polygon,
|
|
670
|
+
symbol: "USDT",
|
|
671
|
+
name: "Tether USD",
|
|
672
|
+
decimals: 6,
|
|
673
|
+
supportsEIP3009: false
|
|
674
|
+
}
|
|
675
|
+
]
|
|
676
|
+
};
|
|
677
|
+
function normalizeChainConfig(chainName, config) {
|
|
678
|
+
const defaultConfig = DEFAULT_CHAINS[chainName];
|
|
679
|
+
if (typeof config === "string") {
|
|
680
|
+
return {
|
|
681
|
+
provider: config,
|
|
682
|
+
chainId: defaultConfig?.chainId ?? 1,
|
|
683
|
+
network: defaultConfig?.network ?? `eip155:1`,
|
|
684
|
+
name: chainName
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
return {
|
|
688
|
+
provider: config.provider,
|
|
689
|
+
chainId: config.chainId ?? defaultConfig?.chainId ?? 1,
|
|
690
|
+
network: config.network ?? defaultConfig?.network ?? `eip155:${config.chainId}`,
|
|
691
|
+
name: chainName
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function getNetworkFromChain(chain) {
|
|
695
|
+
return DEFAULT_CHAINS[chain]?.network ?? `eip155:1`;
|
|
696
|
+
}
|
|
697
|
+
function getChainFromNetwork(network) {
|
|
698
|
+
for (const [chain, config] of Object.entries(DEFAULT_CHAINS)) {
|
|
699
|
+
if (config.network === network) {
|
|
700
|
+
return chain;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return void 0;
|
|
704
|
+
}
|
|
705
|
+
function getChainId(chain) {
|
|
706
|
+
return DEFAULT_CHAINS[chain]?.chainId ?? 1;
|
|
707
|
+
}
|
|
708
|
+
function getUsdt0Chains() {
|
|
709
|
+
return Object.keys(USDT0_ADDRESSES);
|
|
710
|
+
}
|
|
711
|
+
function getPreferredToken(chain) {
|
|
712
|
+
const tokens = CHAIN_TOKENS[chain];
|
|
713
|
+
if (!tokens || tokens.length === 0) return void 0;
|
|
714
|
+
return tokens.find((t) => t.symbol === "USDT0") ?? tokens.find((t) => t.symbol === "USDC") ?? tokens[0];
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/errors.ts
|
|
718
|
+
var WDKErrorCode = /* @__PURE__ */ ((WDKErrorCode2) => {
|
|
719
|
+
WDKErrorCode2[WDKErrorCode2["WDK_NOT_REGISTERED"] = 1001] = "WDK_NOT_REGISTERED";
|
|
720
|
+
WDKErrorCode2[WDKErrorCode2["WDK_NOT_INITIALIZED"] = 1002] = "WDK_NOT_INITIALIZED";
|
|
721
|
+
WDKErrorCode2[WDKErrorCode2["INVALID_SEED_PHRASE"] = 1003] = "INVALID_SEED_PHRASE";
|
|
722
|
+
WDKErrorCode2[WDKErrorCode2["WALLET_MANAGER_NOT_REGISTERED"] = 1004] = "WALLET_MANAGER_NOT_REGISTERED";
|
|
723
|
+
WDKErrorCode2[WDKErrorCode2["CHAIN_NOT_CONFIGURED"] = 2001] = "CHAIN_NOT_CONFIGURED";
|
|
724
|
+
WDKErrorCode2[WDKErrorCode2["CHAIN_NOT_SUPPORTED"] = 2002] = "CHAIN_NOT_SUPPORTED";
|
|
725
|
+
WDKErrorCode2[WDKErrorCode2["INVALID_CHAIN_CONFIG"] = 2003] = "INVALID_CHAIN_CONFIG";
|
|
726
|
+
WDKErrorCode2[WDKErrorCode2["UNKNOWN_CHAIN_ID"] = 2004] = "UNKNOWN_CHAIN_ID";
|
|
727
|
+
WDKErrorCode2[WDKErrorCode2["SIGNER_NOT_INITIALIZED"] = 3001] = "SIGNER_NOT_INITIALIZED";
|
|
728
|
+
WDKErrorCode2[WDKErrorCode2["ACCOUNT_FETCH_FAILED"] = 3002] = "ACCOUNT_FETCH_FAILED";
|
|
729
|
+
WDKErrorCode2[WDKErrorCode2["ADDRESS_FETCH_FAILED"] = 3003] = "ADDRESS_FETCH_FAILED";
|
|
730
|
+
WDKErrorCode2[WDKErrorCode2["SIGN_TYPED_DATA_FAILED"] = 4001] = "SIGN_TYPED_DATA_FAILED";
|
|
731
|
+
WDKErrorCode2[WDKErrorCode2["SIGN_MESSAGE_FAILED"] = 4002] = "SIGN_MESSAGE_FAILED";
|
|
732
|
+
WDKErrorCode2[WDKErrorCode2["INVALID_TYPED_DATA"] = 4003] = "INVALID_TYPED_DATA";
|
|
733
|
+
WDKErrorCode2[WDKErrorCode2["INVALID_MESSAGE"] = 4004] = "INVALID_MESSAGE";
|
|
734
|
+
WDKErrorCode2[WDKErrorCode2["USER_REJECTED_SIGNATURE"] = 4005] = "USER_REJECTED_SIGNATURE";
|
|
735
|
+
WDKErrorCode2[WDKErrorCode2["BALANCE_FETCH_FAILED"] = 5001] = "BALANCE_FETCH_FAILED";
|
|
736
|
+
WDKErrorCode2[WDKErrorCode2["TOKEN_BALANCE_FETCH_FAILED"] = 5002] = "TOKEN_BALANCE_FETCH_FAILED";
|
|
737
|
+
WDKErrorCode2[WDKErrorCode2["INVALID_TOKEN_ADDRESS"] = 5003] = "INVALID_TOKEN_ADDRESS";
|
|
738
|
+
WDKErrorCode2[WDKErrorCode2["TRANSACTION_FAILED"] = 6001] = "TRANSACTION_FAILED";
|
|
739
|
+
WDKErrorCode2[WDKErrorCode2["GAS_ESTIMATION_FAILED"] = 6002] = "GAS_ESTIMATION_FAILED";
|
|
740
|
+
WDKErrorCode2[WDKErrorCode2["INSUFFICIENT_BALANCE"] = 6003] = "INSUFFICIENT_BALANCE";
|
|
741
|
+
WDKErrorCode2[WDKErrorCode2["TRANSACTION_REVERTED"] = 6004] = "TRANSACTION_REVERTED";
|
|
742
|
+
WDKErrorCode2[WDKErrorCode2["TRANSACTION_TIMEOUT"] = 6005] = "TRANSACTION_TIMEOUT";
|
|
743
|
+
WDKErrorCode2[WDKErrorCode2["BRIDGE_NOT_AVAILABLE"] = 7001] = "BRIDGE_NOT_AVAILABLE";
|
|
744
|
+
WDKErrorCode2[WDKErrorCode2["BRIDGE_NOT_SUPPORTED"] = 7002] = "BRIDGE_NOT_SUPPORTED";
|
|
745
|
+
WDKErrorCode2[WDKErrorCode2["BRIDGE_FAILED"] = 7003] = "BRIDGE_FAILED";
|
|
746
|
+
WDKErrorCode2[WDKErrorCode2["INSUFFICIENT_BRIDGE_FEE"] = 7004] = "INSUFFICIENT_BRIDGE_FEE";
|
|
747
|
+
WDKErrorCode2[WDKErrorCode2["RPC_ERROR"] = 8001] = "RPC_ERROR";
|
|
748
|
+
WDKErrorCode2[WDKErrorCode2["RPC_TIMEOUT"] = 8002] = "RPC_TIMEOUT";
|
|
749
|
+
WDKErrorCode2[WDKErrorCode2["RPC_RATE_LIMITED"] = 8003] = "RPC_RATE_LIMITED";
|
|
750
|
+
WDKErrorCode2[WDKErrorCode2["RPC_CONNECTION_FAILED"] = 8004] = "RPC_CONNECTION_FAILED";
|
|
751
|
+
WDKErrorCode2[WDKErrorCode2["UNKNOWN_ERROR"] = 9999] = "UNKNOWN_ERROR";
|
|
752
|
+
return WDKErrorCode2;
|
|
753
|
+
})(WDKErrorCode || {});
|
|
754
|
+
var WDKError = class _WDKError extends Error {
|
|
755
|
+
constructor(code, message, options) {
|
|
756
|
+
super(message);
|
|
757
|
+
this.name = "WDKError";
|
|
758
|
+
this.code = code;
|
|
759
|
+
this.cause = options?.cause;
|
|
760
|
+
this.context = options?.context;
|
|
761
|
+
if (Error.captureStackTrace) {
|
|
762
|
+
Error.captureStackTrace(this, _WDKError);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Create a JSON-serializable representation
|
|
767
|
+
*/
|
|
768
|
+
toJSON() {
|
|
769
|
+
return {
|
|
770
|
+
name: this.name,
|
|
771
|
+
code: this.code,
|
|
772
|
+
message: this.message,
|
|
773
|
+
context: this.context,
|
|
774
|
+
cause: this.cause?.message,
|
|
775
|
+
stack: this.stack
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Check if error is retryable
|
|
780
|
+
*/
|
|
781
|
+
isRetryable() {
|
|
782
|
+
return [
|
|
783
|
+
8002 /* RPC_TIMEOUT */,
|
|
784
|
+
8003 /* RPC_RATE_LIMITED */,
|
|
785
|
+
8004 /* RPC_CONNECTION_FAILED */,
|
|
786
|
+
5001 /* BALANCE_FETCH_FAILED */,
|
|
787
|
+
5002 /* TOKEN_BALANCE_FETCH_FAILED */,
|
|
788
|
+
6002 /* GAS_ESTIMATION_FAILED */
|
|
789
|
+
].includes(this.code);
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
var WDKInitializationError = class extends WDKError {
|
|
793
|
+
constructor(message, options) {
|
|
794
|
+
super(1002 /* WDK_NOT_INITIALIZED */, message, options);
|
|
795
|
+
this.name = "WDKInitializationError";
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
var ChainError = class extends WDKError {
|
|
799
|
+
constructor(code, message, options) {
|
|
800
|
+
super(code, message, {
|
|
801
|
+
cause: options?.cause,
|
|
802
|
+
context: { ...options?.context, chain: options?.chain }
|
|
803
|
+
});
|
|
804
|
+
this.name = "ChainError";
|
|
805
|
+
this.chain = options?.chain;
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
var SignerError = class extends WDKError {
|
|
809
|
+
constructor(code, message, options) {
|
|
810
|
+
super(code, message, {
|
|
811
|
+
cause: options?.cause,
|
|
812
|
+
context: { ...options?.context, chain: options?.chain, address: options?.address }
|
|
813
|
+
});
|
|
814
|
+
this.name = "SignerError";
|
|
815
|
+
this.chain = options?.chain;
|
|
816
|
+
this.address = options?.address;
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
var SigningError = class extends WDKError {
|
|
820
|
+
constructor(code, message, options) {
|
|
821
|
+
super(code, message, {
|
|
822
|
+
cause: options.cause,
|
|
823
|
+
context: { ...options.context, operation: options.operation }
|
|
824
|
+
});
|
|
825
|
+
this.name = "SigningError";
|
|
826
|
+
this.operation = options.operation;
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
var BalanceError = class extends WDKError {
|
|
830
|
+
constructor(code, message, options) {
|
|
831
|
+
super(code, message, {
|
|
832
|
+
cause: options?.cause,
|
|
833
|
+
context: { ...options?.context, chain: options?.chain, token: options?.token }
|
|
834
|
+
});
|
|
835
|
+
this.name = "BalanceError";
|
|
836
|
+
this.chain = options?.chain;
|
|
837
|
+
this.token = options?.token;
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
var TransactionError = class extends WDKError {
|
|
841
|
+
constructor(code, message, options) {
|
|
842
|
+
super(code, message, {
|
|
843
|
+
cause: options?.cause,
|
|
844
|
+
context: { ...options?.context, chain: options?.chain, txHash: options?.txHash }
|
|
845
|
+
});
|
|
846
|
+
this.name = "TransactionError";
|
|
847
|
+
this.chain = options?.chain;
|
|
848
|
+
this.txHash = options?.txHash;
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
var BridgeError = class extends WDKError {
|
|
852
|
+
constructor(code, message, options) {
|
|
853
|
+
super(code, message, {
|
|
854
|
+
cause: options?.cause,
|
|
855
|
+
context: {
|
|
856
|
+
...options?.context,
|
|
857
|
+
fromChain: options?.fromChain,
|
|
858
|
+
toChain: options?.toChain
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
this.name = "BridgeError";
|
|
862
|
+
this.fromChain = options?.fromChain;
|
|
863
|
+
this.toChain = options?.toChain;
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
var RPCError = class extends WDKError {
|
|
867
|
+
constructor(code, message, options) {
|
|
868
|
+
super(code, message, {
|
|
869
|
+
cause: options?.cause,
|
|
870
|
+
context: {
|
|
871
|
+
...options?.context,
|
|
872
|
+
endpoint: options?.endpoint,
|
|
873
|
+
rpcCode: options?.rpcCode
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
this.name = "RPCError";
|
|
877
|
+
this.endpoint = options?.endpoint;
|
|
878
|
+
this.rpcCode = options?.rpcCode;
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
function wrapError(error, defaultCode = 9999 /* UNKNOWN_ERROR */, defaultMessage = "An unknown error occurred", context) {
|
|
882
|
+
if (error instanceof WDKError) {
|
|
883
|
+
return error;
|
|
884
|
+
}
|
|
885
|
+
if (error instanceof Error) {
|
|
886
|
+
const msg = error.message.toLowerCase();
|
|
887
|
+
if (msg.includes("timeout") || msg.includes("timed out")) {
|
|
888
|
+
return new RPCError(8002 /* RPC_TIMEOUT */, `Request timeout: ${error.message}`, {
|
|
889
|
+
cause: error,
|
|
890
|
+
context
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
if (msg.includes("rate limit") || msg.includes("too many requests") || msg.includes("429")) {
|
|
894
|
+
return new RPCError(
|
|
895
|
+
8003 /* RPC_RATE_LIMITED */,
|
|
896
|
+
`Rate limited: ${error.message}`,
|
|
897
|
+
{ cause: error, context }
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
if (msg.includes("connection") || msg.includes("network") || msg.includes("econnrefused") || msg.includes("enotfound")) {
|
|
901
|
+
return new RPCError(
|
|
902
|
+
8004 /* RPC_CONNECTION_FAILED */,
|
|
903
|
+
`Connection failed: ${error.message}`,
|
|
904
|
+
{ cause: error, context }
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
if (msg.includes("insufficient funds") || msg.includes("insufficient balance")) {
|
|
908
|
+
return new TransactionError(
|
|
909
|
+
6003 /* INSUFFICIENT_BALANCE */,
|
|
910
|
+
`Insufficient balance: ${error.message}`,
|
|
911
|
+
{ cause: error, context }
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
if (msg.includes("user rejected") || msg.includes("user denied")) {
|
|
915
|
+
return new SigningError(
|
|
916
|
+
4005 /* USER_REJECTED_SIGNATURE */,
|
|
917
|
+
"User rejected the signature request",
|
|
918
|
+
{ operation: "signTypedData", cause: error, context }
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
if (msg.includes("reverted") || msg.includes("revert")) {
|
|
922
|
+
return new TransactionError(
|
|
923
|
+
6004 /* TRANSACTION_REVERTED */,
|
|
924
|
+
`Transaction reverted: ${error.message}`,
|
|
925
|
+
{ cause: error, context }
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
return new WDKError(defaultCode, error.message || defaultMessage, {
|
|
929
|
+
cause: error,
|
|
930
|
+
context
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
return new WDKError(defaultCode, String(error) || defaultMessage, { context });
|
|
934
|
+
}
|
|
935
|
+
function isWDKError(error) {
|
|
936
|
+
return error instanceof WDKError;
|
|
937
|
+
}
|
|
938
|
+
function hasErrorCode(error, code) {
|
|
939
|
+
return isWDKError(error) && error.code === code;
|
|
940
|
+
}
|
|
941
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
942
|
+
maxRetries: 3,
|
|
943
|
+
baseDelay: 1e3,
|
|
944
|
+
maxDelay: 1e4,
|
|
945
|
+
exponentialBackoff: true
|
|
946
|
+
};
|
|
947
|
+
async function withRetry(fn, config = {}) {
|
|
948
|
+
const { maxRetries, baseDelay, maxDelay, exponentialBackoff } = {
|
|
949
|
+
...DEFAULT_RETRY_CONFIG,
|
|
950
|
+
...config
|
|
951
|
+
};
|
|
952
|
+
let lastError;
|
|
953
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
954
|
+
try {
|
|
955
|
+
return await fn();
|
|
956
|
+
} catch (error) {
|
|
957
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
958
|
+
const wdkError = wrapError(error);
|
|
959
|
+
if (!wdkError.isRetryable() || attempt >= maxRetries) {
|
|
960
|
+
throw wdkError;
|
|
961
|
+
}
|
|
962
|
+
const delay = exponentialBackoff ? Math.min(baseDelay * Math.pow(2, attempt), maxDelay) : baseDelay;
|
|
963
|
+
const jitter = Math.random() * delay * 0.1;
|
|
964
|
+
await sleep(delay + jitter);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
throw lastError ?? new Error("Retry failed with unknown error");
|
|
968
|
+
}
|
|
969
|
+
function sleep(ms) {
|
|
970
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
971
|
+
}
|
|
972
|
+
async function withTimeout(promise, timeoutMs, operation = "Operation") {
|
|
973
|
+
let timeoutId;
|
|
974
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
975
|
+
timeoutId = setTimeout(() => {
|
|
976
|
+
reject(
|
|
977
|
+
new RPCError(
|
|
978
|
+
8002 /* RPC_TIMEOUT */,
|
|
979
|
+
`${operation} timed out after ${timeoutMs}ms`
|
|
980
|
+
)
|
|
981
|
+
);
|
|
982
|
+
}, timeoutMs);
|
|
983
|
+
});
|
|
984
|
+
try {
|
|
985
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
986
|
+
} finally {
|
|
987
|
+
clearTimeout(timeoutId);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// src/signer.ts
|
|
992
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
993
|
+
var DEFAULT_BALANCE_RETRY = {
|
|
994
|
+
maxRetries: 2,
|
|
995
|
+
baseDelay: 500
|
|
996
|
+
};
|
|
997
|
+
var WDKSigner = class {
|
|
998
|
+
/**
|
|
999
|
+
* Create a new WDK signer
|
|
1000
|
+
*
|
|
1001
|
+
* @param wdk - The WDK instance
|
|
1002
|
+
* @param chain - Chain name (e.g., "arbitrum", "ethereum")
|
|
1003
|
+
* @param accountIndex - HD wallet account index (default: 0)
|
|
1004
|
+
* @param timeoutMs - Timeout for operations in milliseconds (default: 30000)
|
|
1005
|
+
*/
|
|
1006
|
+
constructor(wdk, chain, accountIndex = 0, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
1007
|
+
this._account = null;
|
|
1008
|
+
this._address = null;
|
|
1009
|
+
if (!wdk) {
|
|
1010
|
+
throw new SignerError(
|
|
1011
|
+
1002 /* WDK_NOT_INITIALIZED */,
|
|
1012
|
+
"WDK instance is required",
|
|
1013
|
+
{ chain }
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
if (!chain || typeof chain !== "string") {
|
|
1017
|
+
throw new SignerError(
|
|
1018
|
+
2001 /* CHAIN_NOT_CONFIGURED */,
|
|
1019
|
+
"Chain name is required and must be a string",
|
|
1020
|
+
{ chain }
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
if (accountIndex < 0 || !Number.isInteger(accountIndex)) {
|
|
1024
|
+
throw new SignerError(
|
|
1025
|
+
3001 /* SIGNER_NOT_INITIALIZED */,
|
|
1026
|
+
"Account index must be a non-negative integer",
|
|
1027
|
+
{ chain, context: { accountIndex } }
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
this._wdk = wdk;
|
|
1031
|
+
this._chain = chain;
|
|
1032
|
+
this._accountIndex = accountIndex;
|
|
1033
|
+
this._timeoutMs = timeoutMs;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Get the wallet address
|
|
1037
|
+
* Throws if signer is not initialized
|
|
1038
|
+
*/
|
|
1039
|
+
get address() {
|
|
1040
|
+
if (!this._address) {
|
|
1041
|
+
throw new SignerError(
|
|
1042
|
+
3001 /* SIGNER_NOT_INITIALIZED */,
|
|
1043
|
+
`Signer not initialized for chain "${this._chain}". Call initialize() first or use createWDKSigner() async factory.`,
|
|
1044
|
+
{ chain: this._chain }
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
return this._address;
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Check if the signer is initialized
|
|
1051
|
+
*/
|
|
1052
|
+
get isInitialized() {
|
|
1053
|
+
return this._account !== null && this._address !== null;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Initialize the signer by fetching the account
|
|
1057
|
+
* Must be called before using the signer
|
|
1058
|
+
*
|
|
1059
|
+
* @throws {SignerError} If account fetch fails
|
|
1060
|
+
*/
|
|
1061
|
+
async initialize() {
|
|
1062
|
+
if (this._account) return;
|
|
1063
|
+
try {
|
|
1064
|
+
const accountPromise = this._wdk.getAccount(this._chain, this._accountIndex);
|
|
1065
|
+
this._account = await withTimeout(
|
|
1066
|
+
accountPromise,
|
|
1067
|
+
this._timeoutMs,
|
|
1068
|
+
`Fetching account for ${this._chain}`
|
|
1069
|
+
);
|
|
1070
|
+
const addressPromise = this._account.getAddress();
|
|
1071
|
+
const addressString = await withTimeout(
|
|
1072
|
+
addressPromise,
|
|
1073
|
+
this._timeoutMs,
|
|
1074
|
+
`Fetching address for ${this._chain}`
|
|
1075
|
+
);
|
|
1076
|
+
if (!addressString || !addressString.startsWith("0x")) {
|
|
1077
|
+
throw new SignerError(
|
|
1078
|
+
3003 /* ADDRESS_FETCH_FAILED */,
|
|
1079
|
+
`Invalid address format received: ${addressString}`,
|
|
1080
|
+
{ chain: this._chain }
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
this._address = addressString;
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
this._account = null;
|
|
1086
|
+
this._address = null;
|
|
1087
|
+
if (error instanceof SignerError) {
|
|
1088
|
+
throw error;
|
|
1089
|
+
}
|
|
1090
|
+
throw new SignerError(
|
|
1091
|
+
3002 /* ACCOUNT_FETCH_FAILED */,
|
|
1092
|
+
`Failed to initialize signer for chain "${this._chain}": ${error instanceof Error ? error.message : String(error)}`,
|
|
1093
|
+
{
|
|
1094
|
+
chain: this._chain,
|
|
1095
|
+
cause: error instanceof Error ? error : void 0,
|
|
1096
|
+
context: { accountIndex: this._accountIndex }
|
|
1097
|
+
}
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Get the underlying WDK account
|
|
1103
|
+
* Initializes if not already done
|
|
1104
|
+
*
|
|
1105
|
+
* @throws {SignerError} If initialization fails
|
|
1106
|
+
*/
|
|
1107
|
+
async getAccount() {
|
|
1108
|
+
if (!this._account) {
|
|
1109
|
+
await this.initialize();
|
|
1110
|
+
}
|
|
1111
|
+
return this._account;
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Sign EIP-712 typed data for T402 payments
|
|
1115
|
+
*
|
|
1116
|
+
* This is the primary signing method used by T402 for EIP-3009
|
|
1117
|
+
* transferWithAuthorization payments.
|
|
1118
|
+
*
|
|
1119
|
+
* @throws {SigningError} If signing fails
|
|
1120
|
+
*/
|
|
1121
|
+
async signTypedData(message) {
|
|
1122
|
+
if (!message || typeof message !== "object") {
|
|
1123
|
+
throw new SigningError(
|
|
1124
|
+
4003 /* INVALID_TYPED_DATA */,
|
|
1125
|
+
"Invalid typed data: message object is required",
|
|
1126
|
+
{ operation: "signTypedData", context: { chain: this._chain } }
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
if (!message.domain || !message.types || !message.primaryType || !message.message) {
|
|
1130
|
+
throw new SigningError(
|
|
1131
|
+
4003 /* INVALID_TYPED_DATA */,
|
|
1132
|
+
"Invalid typed data: domain, types, primaryType, and message are required",
|
|
1133
|
+
{
|
|
1134
|
+
operation: "signTypedData",
|
|
1135
|
+
context: {
|
|
1136
|
+
chain: this._chain,
|
|
1137
|
+
hasFields: {
|
|
1138
|
+
domain: !!message.domain,
|
|
1139
|
+
types: !!message.types,
|
|
1140
|
+
primaryType: !!message.primaryType,
|
|
1141
|
+
message: !!message.message
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
try {
|
|
1148
|
+
const account = await this.getAccount();
|
|
1149
|
+
const signPromise = account.signTypedData({
|
|
1150
|
+
domain: message.domain,
|
|
1151
|
+
types: message.types,
|
|
1152
|
+
primaryType: message.primaryType,
|
|
1153
|
+
message: message.message
|
|
1154
|
+
});
|
|
1155
|
+
const signature = await withTimeout(
|
|
1156
|
+
signPromise,
|
|
1157
|
+
this._timeoutMs,
|
|
1158
|
+
"Signing typed data"
|
|
1159
|
+
);
|
|
1160
|
+
if (!signature || !signature.startsWith("0x")) {
|
|
1161
|
+
throw new SigningError(
|
|
1162
|
+
4001 /* SIGN_TYPED_DATA_FAILED */,
|
|
1163
|
+
`Invalid signature format received: ${signature?.substring(0, 10)}...`,
|
|
1164
|
+
{ operation: "signTypedData", context: { chain: this._chain } }
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
return signature;
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
if (error instanceof SigningError) {
|
|
1170
|
+
throw error;
|
|
1171
|
+
}
|
|
1172
|
+
const wrapped = wrapError(error, 4001 /* SIGN_TYPED_DATA_FAILED */, "Failed to sign typed data", {
|
|
1173
|
+
chain: this._chain,
|
|
1174
|
+
primaryType: message.primaryType
|
|
1175
|
+
});
|
|
1176
|
+
throw new SigningError(
|
|
1177
|
+
wrapped.code,
|
|
1178
|
+
wrapped.message,
|
|
1179
|
+
{
|
|
1180
|
+
operation: "signTypedData",
|
|
1181
|
+
cause: wrapped.cause,
|
|
1182
|
+
context: wrapped.context
|
|
1183
|
+
}
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Sign a personal message
|
|
1189
|
+
*
|
|
1190
|
+
* @throws {SigningError} If signing fails
|
|
1191
|
+
*/
|
|
1192
|
+
async signMessage(message) {
|
|
1193
|
+
if (message === void 0 || message === null) {
|
|
1194
|
+
throw new SigningError(
|
|
1195
|
+
4004 /* INVALID_MESSAGE */,
|
|
1196
|
+
"Message is required for signing",
|
|
1197
|
+
{ operation: "signMessage", context: { chain: this._chain } }
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
if (typeof message !== "string" && !(message instanceof Uint8Array)) {
|
|
1201
|
+
throw new SigningError(
|
|
1202
|
+
4004 /* INVALID_MESSAGE */,
|
|
1203
|
+
"Message must be a string or Uint8Array",
|
|
1204
|
+
{ operation: "signMessage", context: { chain: this._chain, type: typeof message } }
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
try {
|
|
1208
|
+
const account = await this.getAccount();
|
|
1209
|
+
const messageStr = typeof message === "string" ? message : Buffer.from(message).toString("utf-8");
|
|
1210
|
+
const signPromise = account.signMessage(messageStr);
|
|
1211
|
+
const signature = await withTimeout(
|
|
1212
|
+
signPromise,
|
|
1213
|
+
this._timeoutMs,
|
|
1214
|
+
"Signing message"
|
|
1215
|
+
);
|
|
1216
|
+
if (!signature || !signature.startsWith("0x")) {
|
|
1217
|
+
throw new SigningError(
|
|
1218
|
+
4002 /* SIGN_MESSAGE_FAILED */,
|
|
1219
|
+
`Invalid signature format received: ${signature?.substring(0, 10)}...`,
|
|
1220
|
+
{ operation: "signMessage", context: { chain: this._chain } }
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
return signature;
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
if (error instanceof SigningError) {
|
|
1226
|
+
throw error;
|
|
1227
|
+
}
|
|
1228
|
+
const wrapped = wrapError(error, 4002 /* SIGN_MESSAGE_FAILED */, "Failed to sign message", {
|
|
1229
|
+
chain: this._chain
|
|
1230
|
+
});
|
|
1231
|
+
throw new SigningError(
|
|
1232
|
+
wrapped.code,
|
|
1233
|
+
wrapped.message,
|
|
1234
|
+
{
|
|
1235
|
+
operation: "signMessage",
|
|
1236
|
+
cause: wrapped.cause,
|
|
1237
|
+
context: wrapped.context
|
|
1238
|
+
}
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Get the chain name
|
|
1244
|
+
*/
|
|
1245
|
+
getChain() {
|
|
1246
|
+
return this._chain;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Get the chain ID
|
|
1250
|
+
*/
|
|
1251
|
+
getChainId() {
|
|
1252
|
+
return getChainId(this._chain);
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Get the account index
|
|
1256
|
+
*/
|
|
1257
|
+
getAccountIndex() {
|
|
1258
|
+
return this._accountIndex;
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Get native token balance (ETH, etc.)
|
|
1262
|
+
*
|
|
1263
|
+
* @throws {BalanceError} If balance fetch fails
|
|
1264
|
+
*/
|
|
1265
|
+
async getBalance() {
|
|
1266
|
+
try {
|
|
1267
|
+
const account = await this.getAccount();
|
|
1268
|
+
return await withRetry(
|
|
1269
|
+
async () => {
|
|
1270
|
+
const balancePromise = account.getBalance();
|
|
1271
|
+
return withTimeout(balancePromise, this._timeoutMs, "Fetching native balance");
|
|
1272
|
+
},
|
|
1273
|
+
DEFAULT_BALANCE_RETRY
|
|
1274
|
+
);
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
if (error instanceof BalanceError) {
|
|
1277
|
+
throw error;
|
|
1278
|
+
}
|
|
1279
|
+
throw new BalanceError(
|
|
1280
|
+
5001 /* BALANCE_FETCH_FAILED */,
|
|
1281
|
+
`Failed to get native balance for ${this._chain}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1282
|
+
{
|
|
1283
|
+
chain: this._chain,
|
|
1284
|
+
cause: error instanceof Error ? error : void 0
|
|
1285
|
+
}
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Get ERC20 token balance
|
|
1291
|
+
*
|
|
1292
|
+
* @throws {BalanceError} If balance fetch fails
|
|
1293
|
+
*/
|
|
1294
|
+
async getTokenBalance(tokenAddress) {
|
|
1295
|
+
if (!tokenAddress || !tokenAddress.startsWith("0x")) {
|
|
1296
|
+
throw new BalanceError(
|
|
1297
|
+
5003 /* INVALID_TOKEN_ADDRESS */,
|
|
1298
|
+
`Invalid token address: ${tokenAddress}`,
|
|
1299
|
+
{ chain: this._chain, token: tokenAddress }
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
try {
|
|
1303
|
+
const account = await this.getAccount();
|
|
1304
|
+
return await withRetry(
|
|
1305
|
+
async () => {
|
|
1306
|
+
const balancePromise = account.getTokenBalance(tokenAddress);
|
|
1307
|
+
return withTimeout(balancePromise, this._timeoutMs, "Fetching token balance");
|
|
1308
|
+
},
|
|
1309
|
+
DEFAULT_BALANCE_RETRY
|
|
1310
|
+
);
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
if (error instanceof BalanceError) {
|
|
1313
|
+
throw error;
|
|
1314
|
+
}
|
|
1315
|
+
throw new BalanceError(
|
|
1316
|
+
5002 /* TOKEN_BALANCE_FETCH_FAILED */,
|
|
1317
|
+
`Failed to get token balance for ${tokenAddress} on ${this._chain}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1318
|
+
{
|
|
1319
|
+
chain: this._chain,
|
|
1320
|
+
token: tokenAddress,
|
|
1321
|
+
cause: error instanceof Error ? error : void 0
|
|
1322
|
+
}
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Estimate gas for a transaction
|
|
1328
|
+
*
|
|
1329
|
+
* @throws {TransactionError} If gas estimation fails
|
|
1330
|
+
*/
|
|
1331
|
+
async estimateGas(params) {
|
|
1332
|
+
if (!params.to || !params.to.startsWith("0x")) {
|
|
1333
|
+
throw new TransactionError(
|
|
1334
|
+
6002 /* GAS_ESTIMATION_FAILED */,
|
|
1335
|
+
`Invalid 'to' address: ${params.to}`,
|
|
1336
|
+
{ chain: this._chain, context: params }
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
try {
|
|
1340
|
+
const account = await this.getAccount();
|
|
1341
|
+
return await withRetry(
|
|
1342
|
+
async () => {
|
|
1343
|
+
const estimatePromise = account.estimateGas({
|
|
1344
|
+
to: params.to,
|
|
1345
|
+
value: params.value,
|
|
1346
|
+
data: params.data
|
|
1347
|
+
});
|
|
1348
|
+
return withTimeout(estimatePromise, this._timeoutMs, "Estimating gas");
|
|
1349
|
+
},
|
|
1350
|
+
DEFAULT_BALANCE_RETRY
|
|
1351
|
+
);
|
|
1352
|
+
} catch (error) {
|
|
1353
|
+
if (error instanceof TransactionError) {
|
|
1354
|
+
throw error;
|
|
1355
|
+
}
|
|
1356
|
+
throw new TransactionError(
|
|
1357
|
+
6002 /* GAS_ESTIMATION_FAILED */,
|
|
1358
|
+
`Failed to estimate gas on ${this._chain}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1359
|
+
{
|
|
1360
|
+
chain: this._chain,
|
|
1361
|
+
cause: error instanceof Error ? error : void 0,
|
|
1362
|
+
context: { to: params.to, value: params.value?.toString() }
|
|
1363
|
+
}
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Send a transaction (for advanced use cases)
|
|
1369
|
+
*
|
|
1370
|
+
* @throws {TransactionError} If transaction fails
|
|
1371
|
+
*/
|
|
1372
|
+
async sendTransaction(params) {
|
|
1373
|
+
if (!params.to || !params.to.startsWith("0x")) {
|
|
1374
|
+
throw new TransactionError(
|
|
1375
|
+
6001 /* TRANSACTION_FAILED */,
|
|
1376
|
+
`Invalid 'to' address: ${params.to}`,
|
|
1377
|
+
{ chain: this._chain, context: params }
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
try {
|
|
1381
|
+
const account = await this.getAccount();
|
|
1382
|
+
const sendPromise = account.sendTransaction({
|
|
1383
|
+
to: params.to,
|
|
1384
|
+
value: params.value,
|
|
1385
|
+
data: params.data
|
|
1386
|
+
});
|
|
1387
|
+
const hash = await withTimeout(
|
|
1388
|
+
sendPromise,
|
|
1389
|
+
this._timeoutMs * 2,
|
|
1390
|
+
// Double timeout for transaction submission
|
|
1391
|
+
"Sending transaction"
|
|
1392
|
+
);
|
|
1393
|
+
if (!hash || !hash.startsWith("0x")) {
|
|
1394
|
+
throw new TransactionError(
|
|
1395
|
+
6001 /* TRANSACTION_FAILED */,
|
|
1396
|
+
`Invalid transaction hash received: ${hash?.substring(0, 10)}...`,
|
|
1397
|
+
{ chain: this._chain }
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
return { hash };
|
|
1401
|
+
} catch (error) {
|
|
1402
|
+
if (error instanceof TransactionError) {
|
|
1403
|
+
throw error;
|
|
1404
|
+
}
|
|
1405
|
+
const wrapped = wrapError(error, 6001 /* TRANSACTION_FAILED */, "Transaction failed", {
|
|
1406
|
+
chain: this._chain,
|
|
1407
|
+
to: params.to
|
|
1408
|
+
});
|
|
1409
|
+
throw new TransactionError(
|
|
1410
|
+
wrapped.code,
|
|
1411
|
+
wrapped.message,
|
|
1412
|
+
{
|
|
1413
|
+
chain: this._chain,
|
|
1414
|
+
cause: wrapped.cause,
|
|
1415
|
+
context: wrapped.context
|
|
1416
|
+
}
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
async function createWDKSigner(wdk, chain, accountIndex = 0, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
1422
|
+
const signer = new WDKSigner(wdk, chain, accountIndex, timeoutMs);
|
|
1423
|
+
await signer.initialize();
|
|
1424
|
+
return signer;
|
|
1425
|
+
}
|
|
1426
|
+
var MockWDKSigner = class {
|
|
1427
|
+
constructor(address, privateKey) {
|
|
1428
|
+
this.address = address;
|
|
1429
|
+
this._privateKey = privateKey;
|
|
1430
|
+
}
|
|
1431
|
+
async signTypedData(_message) {
|
|
1432
|
+
return "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
|
|
1433
|
+
}
|
|
1434
|
+
async signMessage(_message) {
|
|
1435
|
+
return "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
|
|
1439
|
+
// src/t402wdk.ts
|
|
1440
|
+
var import_evm = require("@t402/evm");
|
|
1441
|
+
var _T402WDK = class _T402WDK {
|
|
1442
|
+
/**
|
|
1443
|
+
* Create a new T402WDK instance
|
|
1444
|
+
*
|
|
1445
|
+
* @param seedPhrase - BIP-39 mnemonic seed phrase
|
|
1446
|
+
* @param config - Chain configuration (RPC endpoints)
|
|
1447
|
+
* @param options - Additional options (cache configuration, etc.)
|
|
1448
|
+
* @throws {WDKInitializationError} If seed phrase is invalid
|
|
1449
|
+
*/
|
|
1450
|
+
constructor(seedPhrase, config = {}, options = {}) {
|
|
1451
|
+
this._wdk = null;
|
|
1452
|
+
this._normalizedChains = /* @__PURE__ */ new Map();
|
|
1453
|
+
this._signerCache = /* @__PURE__ */ new Map();
|
|
1454
|
+
this._initializationError = null;
|
|
1455
|
+
if (!seedPhrase || typeof seedPhrase !== "string") {
|
|
1456
|
+
throw new WDKInitializationError("Seed phrase is required and must be a string");
|
|
1457
|
+
}
|
|
1458
|
+
const words = seedPhrase.trim().split(/\s+/);
|
|
1459
|
+
const validWordCounts = [12, 15, 18, 21, 24];
|
|
1460
|
+
if (!validWordCounts.includes(words.length)) {
|
|
1461
|
+
throw new WDKInitializationError(
|
|
1462
|
+
`Invalid seed phrase: expected 12, 15, 18, 21, or 24 words, got ${words.length}`,
|
|
1463
|
+
{ context: { wordCount: words.length } }
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
this._seedPhrase = seedPhrase;
|
|
1467
|
+
this._config = config;
|
|
1468
|
+
this._options = options;
|
|
1469
|
+
this._balanceCache = new BalanceCache(options.cache);
|
|
1470
|
+
for (const [chain, chainConfig] of Object.entries(config)) {
|
|
1471
|
+
if (chainConfig) {
|
|
1472
|
+
try {
|
|
1473
|
+
this._normalizedChains.set(chain, normalizeChainConfig(chain, chainConfig));
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
throw new ChainError(
|
|
1476
|
+
2003 /* INVALID_CHAIN_CONFIG */,
|
|
1477
|
+
`Invalid configuration for chain "${chain}": ${error instanceof Error ? error.message : String(error)}`,
|
|
1478
|
+
{ chain, cause: error instanceof Error ? error : void 0 }
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
this._addDefaultChainsIfNeeded();
|
|
1484
|
+
if (_T402WDK._WDK) {
|
|
1485
|
+
this._initializeWDK();
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Register the Tether WDK modules
|
|
1490
|
+
*
|
|
1491
|
+
* This must be called before creating T402WDK instances if you want
|
|
1492
|
+
* to use the actual WDK. Otherwise, a mock implementation is used.
|
|
1493
|
+
*
|
|
1494
|
+
* @throws {WDKInitializationError} If registration fails
|
|
1495
|
+
*
|
|
1496
|
+
* @example
|
|
1497
|
+
* ```typescript
|
|
1498
|
+
* import WDK from '@tetherto/wdk';
|
|
1499
|
+
* import WalletManagerEvm from '@tetherto/wdk-wallet-evm';
|
|
1500
|
+
* import BridgeUsdt0Evm from '@tetherto/wdk-protocol-bridge-usdt0-evm';
|
|
1501
|
+
*
|
|
1502
|
+
* T402WDK.registerWDK(WDK, WalletManagerEvm, BridgeUsdt0Evm);
|
|
1503
|
+
* ```
|
|
1504
|
+
*/
|
|
1505
|
+
static registerWDK(WDK, WalletManagerEvm, BridgeUsdt0Evm) {
|
|
1506
|
+
if (!WDK) {
|
|
1507
|
+
throw new WDKInitializationError("WDK constructor is required");
|
|
1508
|
+
}
|
|
1509
|
+
if (typeof WDK !== "function") {
|
|
1510
|
+
throw new WDKInitializationError("WDK must be a constructor function");
|
|
1511
|
+
}
|
|
1512
|
+
_T402WDK._WDK = WDK;
|
|
1513
|
+
_T402WDK._WalletManagerEvm = WalletManagerEvm ?? null;
|
|
1514
|
+
_T402WDK._BridgeUsdt0Evm = BridgeUsdt0Evm ?? null;
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Check if WDK is registered
|
|
1518
|
+
*/
|
|
1519
|
+
static isWDKRegistered() {
|
|
1520
|
+
return _T402WDK._WDK !== null;
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Check if wallet manager is registered
|
|
1524
|
+
*/
|
|
1525
|
+
static isWalletManagerRegistered() {
|
|
1526
|
+
return _T402WDK._WalletManagerEvm !== null;
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Check if bridge protocol is registered
|
|
1530
|
+
*/
|
|
1531
|
+
static isBridgeRegistered() {
|
|
1532
|
+
return _T402WDK._BridgeUsdt0Evm !== null;
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Generate a new random seed phrase
|
|
1536
|
+
*
|
|
1537
|
+
* @throws {WDKInitializationError} If WDK is not registered
|
|
1538
|
+
* @returns A new BIP-39 mnemonic seed phrase
|
|
1539
|
+
*/
|
|
1540
|
+
static generateSeedPhrase() {
|
|
1541
|
+
if (!_T402WDK._WDK) {
|
|
1542
|
+
throw new WDKInitializationError(
|
|
1543
|
+
"WDK not registered. Call T402WDK.registerWDK() first, or use a mock seed phrase for testing."
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
try {
|
|
1547
|
+
return _T402WDK._WDK.getRandomSeedPhrase();
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
throw new WDKInitializationError(
|
|
1550
|
+
`Failed to generate seed phrase: ${error instanceof Error ? error.message : String(error)}`,
|
|
1551
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Add default chain configurations for common chains
|
|
1557
|
+
*/
|
|
1558
|
+
_addDefaultChainsIfNeeded() {
|
|
1559
|
+
if (this._normalizedChains.size === 0) {
|
|
1560
|
+
const defaultEndpoint = DEFAULT_RPC_ENDPOINTS.arbitrum;
|
|
1561
|
+
if (defaultEndpoint) {
|
|
1562
|
+
this._normalizedChains.set(
|
|
1563
|
+
"arbitrum",
|
|
1564
|
+
normalizeChainConfig("arbitrum", defaultEndpoint)
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Initialize the underlying WDK instance
|
|
1571
|
+
*/
|
|
1572
|
+
_initializeWDK() {
|
|
1573
|
+
if (!_T402WDK._WDK) {
|
|
1574
|
+
this._initializationError = new WDKInitializationError("WDK not registered");
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
if (!_T402WDK._WalletManagerEvm) {
|
|
1578
|
+
this._initializationError = new WDKInitializationError(
|
|
1579
|
+
"WalletManagerEvm not registered. Call T402WDK.registerWDK(WDK, WalletManagerEvm) to enable wallet functionality."
|
|
1580
|
+
);
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
try {
|
|
1584
|
+
let wdk = new _T402WDK._WDK(this._seedPhrase);
|
|
1585
|
+
for (const [chain, config] of this._normalizedChains) {
|
|
1586
|
+
try {
|
|
1587
|
+
wdk = wdk.registerWallet(chain, _T402WDK._WalletManagerEvm, {
|
|
1588
|
+
provider: config.provider,
|
|
1589
|
+
chainId: config.chainId
|
|
1590
|
+
});
|
|
1591
|
+
} catch (error) {
|
|
1592
|
+
throw new ChainError(
|
|
1593
|
+
2002 /* CHAIN_NOT_SUPPORTED */,
|
|
1594
|
+
`Failed to register wallet for chain "${chain}": ${error instanceof Error ? error.message : String(error)}`,
|
|
1595
|
+
{ chain, cause: error instanceof Error ? error : void 0 }
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
if (_T402WDK._BridgeUsdt0Evm) {
|
|
1600
|
+
try {
|
|
1601
|
+
wdk = wdk.registerProtocol("bridge-usdt0", _T402WDK._BridgeUsdt0Evm);
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
console.warn(
|
|
1604
|
+
`Failed to register USDT0 bridge protocol: ${error instanceof Error ? error.message : String(error)}`
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
this._wdk = wdk;
|
|
1609
|
+
this._initializationError = null;
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
this._initializationError = error instanceof Error ? error : new Error(String(error));
|
|
1612
|
+
this._wdk = null;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Get the underlying WDK instance
|
|
1617
|
+
*
|
|
1618
|
+
* @throws {WDKInitializationError} If WDK is not initialized
|
|
1619
|
+
*/
|
|
1620
|
+
get wdk() {
|
|
1621
|
+
if (this._initializationError) {
|
|
1622
|
+
throw this._initializationError instanceof WDKError ? this._initializationError : new WDKInitializationError(
|
|
1623
|
+
`WDK initialization failed: ${this._initializationError.message}`,
|
|
1624
|
+
{ cause: this._initializationError }
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
if (!this._wdk) {
|
|
1628
|
+
throw new WDKInitializationError(
|
|
1629
|
+
"WDK not initialized. Call T402WDK.registerWDK() before creating instances."
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
return this._wdk;
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Check if WDK is properly initialized
|
|
1636
|
+
*/
|
|
1637
|
+
get isInitialized() {
|
|
1638
|
+
return this._wdk !== null && this._initializationError === null;
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Get initialization error if any
|
|
1642
|
+
*/
|
|
1643
|
+
get initializationError() {
|
|
1644
|
+
return this._initializationError;
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Get all configured chains
|
|
1648
|
+
*/
|
|
1649
|
+
getConfiguredChains() {
|
|
1650
|
+
return Array.from(this._normalizedChains.keys());
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Get chain configuration
|
|
1654
|
+
*/
|
|
1655
|
+
getChainConfig(chain) {
|
|
1656
|
+
return this._normalizedChains.get(chain);
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Check if a chain is configured
|
|
1660
|
+
*/
|
|
1661
|
+
isChainConfigured(chain) {
|
|
1662
|
+
return this._normalizedChains.has(chain);
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Get a T402-compatible signer for a chain
|
|
1666
|
+
*
|
|
1667
|
+
* @param chain - Chain name (e.g., "arbitrum", "ethereum")
|
|
1668
|
+
* @param accountIndex - HD wallet account index (default: 0)
|
|
1669
|
+
* @throws {ChainError} If chain is not configured
|
|
1670
|
+
* @throws {SignerError} If signer creation fails
|
|
1671
|
+
* @returns An initialized WDKSigner
|
|
1672
|
+
*/
|
|
1673
|
+
async getSigner(chain, accountIndex = 0) {
|
|
1674
|
+
if (!chain || typeof chain !== "string") {
|
|
1675
|
+
throw new ChainError(
|
|
1676
|
+
2001 /* CHAIN_NOT_CONFIGURED */,
|
|
1677
|
+
"Chain name is required and must be a string",
|
|
1678
|
+
{ chain }
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
const cacheKey = `${chain}:${accountIndex}`;
|
|
1682
|
+
const cached = this._signerCache.get(cacheKey);
|
|
1683
|
+
if (cached) {
|
|
1684
|
+
return cached;
|
|
1685
|
+
}
|
|
1686
|
+
if (!this._normalizedChains.has(chain)) {
|
|
1687
|
+
const availableChains = this.getConfiguredChains();
|
|
1688
|
+
throw new ChainError(
|
|
1689
|
+
2001 /* CHAIN_NOT_CONFIGURED */,
|
|
1690
|
+
`Chain "${chain}" not configured. Available chains: ${availableChains.length > 0 ? availableChains.join(", ") : "(none)"}`,
|
|
1691
|
+
{ chain, context: { availableChains } }
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
try {
|
|
1695
|
+
const signer = await createWDKSigner(this.wdk, chain, accountIndex);
|
|
1696
|
+
this._signerCache.set(cacheKey, signer);
|
|
1697
|
+
return signer;
|
|
1698
|
+
} catch (error) {
|
|
1699
|
+
if (isWDKError(error)) {
|
|
1700
|
+
throw error;
|
|
1701
|
+
}
|
|
1702
|
+
throw wrapError(
|
|
1703
|
+
error,
|
|
1704
|
+
3001 /* SIGNER_NOT_INITIALIZED */,
|
|
1705
|
+
`Failed to create signer for chain "${chain}"`,
|
|
1706
|
+
{ chain, accountIndex }
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Clear the signer cache
|
|
1712
|
+
* Useful for forcing re-initialization of signers
|
|
1713
|
+
*/
|
|
1714
|
+
clearSignerCache() {
|
|
1715
|
+
this._signerCache.clear();
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Get wallet address for a chain
|
|
1719
|
+
*
|
|
1720
|
+
* @param chain - Chain name
|
|
1721
|
+
* @param accountIndex - HD wallet account index (default: 0)
|
|
1722
|
+
* @throws {ChainError} If chain is not configured
|
|
1723
|
+
* @throws {SignerError} If address fetch fails
|
|
1724
|
+
*/
|
|
1725
|
+
async getAddress(chain, accountIndex = 0) {
|
|
1726
|
+
const signer = await this.getSigner(chain, accountIndex);
|
|
1727
|
+
return signer.address;
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Get USDT0 balance for a chain
|
|
1731
|
+
*
|
|
1732
|
+
* Uses cache if enabled to reduce RPC calls.
|
|
1733
|
+
*
|
|
1734
|
+
* @throws {BalanceError} If balance fetch fails
|
|
1735
|
+
*/
|
|
1736
|
+
async getUsdt0Balance(chain, accountIndex = 0) {
|
|
1737
|
+
const usdt0Address = USDT0_ADDRESSES[chain];
|
|
1738
|
+
if (!usdt0Address) {
|
|
1739
|
+
return 0n;
|
|
1740
|
+
}
|
|
1741
|
+
try {
|
|
1742
|
+
const signer = await this.getSigner(chain, accountIndex);
|
|
1743
|
+
const address = signer.address;
|
|
1744
|
+
return await this._balanceCache.getOrFetchTokenBalance(
|
|
1745
|
+
chain,
|
|
1746
|
+
usdt0Address,
|
|
1747
|
+
address,
|
|
1748
|
+
async () => signer.getTokenBalance(usdt0Address)
|
|
1749
|
+
);
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
if (isWDKError(error) && error.code === 5002 /* TOKEN_BALANCE_FETCH_FAILED */) {
|
|
1752
|
+
return 0n;
|
|
1753
|
+
}
|
|
1754
|
+
throw error;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Get USDC balance for a chain
|
|
1759
|
+
*
|
|
1760
|
+
* Uses cache if enabled to reduce RPC calls.
|
|
1761
|
+
*
|
|
1762
|
+
* @throws {BalanceError} If balance fetch fails
|
|
1763
|
+
*/
|
|
1764
|
+
async getUsdcBalance(chain, accountIndex = 0) {
|
|
1765
|
+
const usdcAddress = USDC_ADDRESSES[chain];
|
|
1766
|
+
if (!usdcAddress) {
|
|
1767
|
+
return 0n;
|
|
1768
|
+
}
|
|
1769
|
+
try {
|
|
1770
|
+
const signer = await this.getSigner(chain, accountIndex);
|
|
1771
|
+
const address = signer.address;
|
|
1772
|
+
return await this._balanceCache.getOrFetchTokenBalance(
|
|
1773
|
+
chain,
|
|
1774
|
+
usdcAddress,
|
|
1775
|
+
address,
|
|
1776
|
+
async () => signer.getTokenBalance(usdcAddress)
|
|
1777
|
+
);
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
if (isWDKError(error) && error.code === 5002 /* TOKEN_BALANCE_FETCH_FAILED */) {
|
|
1780
|
+
return 0n;
|
|
1781
|
+
}
|
|
1782
|
+
throw error;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Get all token balances for a chain
|
|
1787
|
+
*
|
|
1788
|
+
* Uses cache if enabled to reduce RPC calls.
|
|
1789
|
+
*
|
|
1790
|
+
* @throws {ChainError} If chain is not configured
|
|
1791
|
+
* @throws {BalanceError} If balance fetch fails
|
|
1792
|
+
*/
|
|
1793
|
+
async getChainBalances(chain, accountIndex = 0) {
|
|
1794
|
+
const config = this._normalizedChains.get(chain);
|
|
1795
|
+
if (!config) {
|
|
1796
|
+
throw new ChainError(
|
|
1797
|
+
2001 /* CHAIN_NOT_CONFIGURED */,
|
|
1798
|
+
`Chain "${chain}" not configured`,
|
|
1799
|
+
{ chain }
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
try {
|
|
1803
|
+
const signer = await this.getSigner(chain, accountIndex);
|
|
1804
|
+
const address = signer.address;
|
|
1805
|
+
const tokens = CHAIN_TOKENS[chain] || [];
|
|
1806
|
+
const tokenBalanceResults = await Promise.allSettled(
|
|
1807
|
+
tokens.map(async (token) => {
|
|
1808
|
+
const balance = await this._balanceCache.getOrFetchTokenBalance(
|
|
1809
|
+
chain,
|
|
1810
|
+
token.address,
|
|
1811
|
+
address,
|
|
1812
|
+
async () => signer.getTokenBalance(token.address)
|
|
1813
|
+
);
|
|
1814
|
+
return {
|
|
1815
|
+
token: token.address,
|
|
1816
|
+
symbol: token.symbol,
|
|
1817
|
+
balance,
|
|
1818
|
+
formatted: formatTokenAmount(balance, token.decimals),
|
|
1819
|
+
decimals: token.decimals
|
|
1820
|
+
};
|
|
1821
|
+
})
|
|
1822
|
+
);
|
|
1823
|
+
const tokenBalances = tokenBalanceResults.map((result, index) => {
|
|
1824
|
+
if (result.status === "fulfilled") {
|
|
1825
|
+
return result.value;
|
|
1826
|
+
}
|
|
1827
|
+
const token = tokens[index];
|
|
1828
|
+
return {
|
|
1829
|
+
token: token.address,
|
|
1830
|
+
symbol: token.symbol,
|
|
1831
|
+
balance: 0n,
|
|
1832
|
+
formatted: "0",
|
|
1833
|
+
decimals: token.decimals
|
|
1834
|
+
};
|
|
1835
|
+
});
|
|
1836
|
+
let nativeBalance;
|
|
1837
|
+
try {
|
|
1838
|
+
nativeBalance = await this._balanceCache.getOrFetchNativeBalance(
|
|
1839
|
+
chain,
|
|
1840
|
+
address,
|
|
1841
|
+
async () => signer.getBalance()
|
|
1842
|
+
);
|
|
1843
|
+
} catch {
|
|
1844
|
+
nativeBalance = 0n;
|
|
1845
|
+
}
|
|
1846
|
+
return {
|
|
1847
|
+
chain,
|
|
1848
|
+
network: config.network,
|
|
1849
|
+
native: nativeBalance,
|
|
1850
|
+
tokens: tokenBalances
|
|
1851
|
+
};
|
|
1852
|
+
} catch (error) {
|
|
1853
|
+
if (isWDKError(error)) {
|
|
1854
|
+
throw error;
|
|
1855
|
+
}
|
|
1856
|
+
throw new BalanceError(
|
|
1857
|
+
5001 /* BALANCE_FETCH_FAILED */,
|
|
1858
|
+
`Failed to get balances for chain "${chain}": ${error instanceof Error ? error.message : String(error)}`,
|
|
1859
|
+
{ chain, cause: error instanceof Error ? error : void 0 }
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
/**
|
|
1864
|
+
* Get aggregated balances across all configured chains
|
|
1865
|
+
*
|
|
1866
|
+
* @param accountIndex - HD wallet account index (default: 0)
|
|
1867
|
+
* @param options - Options for balance aggregation
|
|
1868
|
+
*/
|
|
1869
|
+
async getAggregatedBalances(accountIndex = 0, options = {}) {
|
|
1870
|
+
const { continueOnError = true } = options;
|
|
1871
|
+
const chains = this.getConfiguredChains();
|
|
1872
|
+
const results = await Promise.allSettled(
|
|
1873
|
+
chains.map((chain) => this.getChainBalances(chain, accountIndex))
|
|
1874
|
+
);
|
|
1875
|
+
const chainBalances = [];
|
|
1876
|
+
const errors = [];
|
|
1877
|
+
for (let i = 0; i < results.length; i++) {
|
|
1878
|
+
const result = results[i];
|
|
1879
|
+
if (result.status === "fulfilled") {
|
|
1880
|
+
chainBalances.push(result.value);
|
|
1881
|
+
} else {
|
|
1882
|
+
errors.push(result.reason);
|
|
1883
|
+
if (!continueOnError) {
|
|
1884
|
+
throw result.reason;
|
|
1885
|
+
}
|
|
1886
|
+
const config = this._normalizedChains.get(chains[i]);
|
|
1887
|
+
if (config) {
|
|
1888
|
+
chainBalances.push({
|
|
1889
|
+
chain: chains[i],
|
|
1890
|
+
network: config.network,
|
|
1891
|
+
native: 0n,
|
|
1892
|
+
tokens: []
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
let totalUsdt0 = 0n;
|
|
1898
|
+
let totalUsdc = 0n;
|
|
1899
|
+
for (const chainBalance of chainBalances) {
|
|
1900
|
+
for (const token of chainBalance.tokens) {
|
|
1901
|
+
if (token.symbol === "USDT0") {
|
|
1902
|
+
totalUsdt0 += token.balance;
|
|
1903
|
+
} else if (token.symbol === "USDC") {
|
|
1904
|
+
totalUsdc += token.balance;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
return {
|
|
1909
|
+
totalUsdt0,
|
|
1910
|
+
totalUsdc,
|
|
1911
|
+
chains: chainBalances
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Find the best chain for a payment
|
|
1916
|
+
*
|
|
1917
|
+
* Looks for the chain with sufficient balance, prioritizing USDT0.
|
|
1918
|
+
*
|
|
1919
|
+
* @param amount - Required amount in smallest units
|
|
1920
|
+
* @param preferredToken - Preferred token ("USDT0" | "USDC")
|
|
1921
|
+
* @throws {BalanceError} If balance aggregation fails
|
|
1922
|
+
*/
|
|
1923
|
+
async findBestChainForPayment(amount, preferredToken = "USDT0") {
|
|
1924
|
+
if (amount <= 0n) {
|
|
1925
|
+
return null;
|
|
1926
|
+
}
|
|
1927
|
+
try {
|
|
1928
|
+
const balances = await this.getAggregatedBalances(0, { continueOnError: true });
|
|
1929
|
+
const tokenPriority = preferredToken === "USDT0" ? ["USDT0", "USDC"] : ["USDC", "USDT0"];
|
|
1930
|
+
for (const tokenSymbol of tokenPriority) {
|
|
1931
|
+
for (const chainBalance of balances.chains) {
|
|
1932
|
+
const tokenBalance = chainBalance.tokens.find((t) => t.symbol === tokenSymbol);
|
|
1933
|
+
if (tokenBalance && tokenBalance.balance >= amount) {
|
|
1934
|
+
return {
|
|
1935
|
+
chain: chainBalance.chain,
|
|
1936
|
+
token: tokenSymbol,
|
|
1937
|
+
balance: tokenBalance.balance
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
return null;
|
|
1943
|
+
} catch (error) {
|
|
1944
|
+
if (isWDKError(error)) {
|
|
1945
|
+
throw error;
|
|
1946
|
+
}
|
|
1947
|
+
throw new BalanceError(
|
|
1948
|
+
5001 /* BALANCE_FETCH_FAILED */,
|
|
1949
|
+
`Failed to find best chain for payment: ${error instanceof Error ? error.message : String(error)}`,
|
|
1950
|
+
{ cause: error instanceof Error ? error : void 0, context: { amount: amount.toString() } }
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Bridge USDT0 between chains
|
|
1956
|
+
*
|
|
1957
|
+
* Uses LayerZero OFT for cross-chain transfers.
|
|
1958
|
+
*
|
|
1959
|
+
* @param params - Bridge parameters
|
|
1960
|
+
* @throws {BridgeError} If bridge is not available or fails
|
|
1961
|
+
* @returns Bridge result with transaction hash
|
|
1962
|
+
*/
|
|
1963
|
+
async bridgeUsdt0(params) {
|
|
1964
|
+
if (!_T402WDK._BridgeUsdt0Evm) {
|
|
1965
|
+
throw new BridgeError(
|
|
1966
|
+
7001 /* BRIDGE_NOT_AVAILABLE */,
|
|
1967
|
+
"USDT0 bridge not available. Register BridgeUsdt0Evm with T402WDK.registerWDK().",
|
|
1968
|
+
{ fromChain: params.fromChain, toChain: params.toChain }
|
|
1969
|
+
);
|
|
1970
|
+
}
|
|
1971
|
+
if (!params.fromChain || !params.toChain) {
|
|
1972
|
+
throw new BridgeError(
|
|
1973
|
+
7003 /* BRIDGE_FAILED */,
|
|
1974
|
+
"Both fromChain and toChain are required",
|
|
1975
|
+
{ fromChain: params.fromChain, toChain: params.toChain }
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1978
|
+
if (params.fromChain === params.toChain) {
|
|
1979
|
+
throw new BridgeError(
|
|
1980
|
+
7002 /* BRIDGE_NOT_SUPPORTED */,
|
|
1981
|
+
"Cannot bridge to the same chain",
|
|
1982
|
+
{ fromChain: params.fromChain, toChain: params.toChain }
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
if (!params.amount || params.amount <= 0n) {
|
|
1986
|
+
throw new BridgeError(
|
|
1987
|
+
7003 /* BRIDGE_FAILED */,
|
|
1988
|
+
"Amount must be greater than 0",
|
|
1989
|
+
{ fromChain: params.fromChain, toChain: params.toChain, context: { amount: params.amount?.toString() } }
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
if (!this.canBridge(params.fromChain, params.toChain)) {
|
|
1993
|
+
throw new BridgeError(
|
|
1994
|
+
7002 /* BRIDGE_NOT_SUPPORTED */,
|
|
1995
|
+
`Bridging from "${params.fromChain}" to "${params.toChain}" is not supported`,
|
|
1996
|
+
{ fromChain: params.fromChain, toChain: params.toChain }
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
try {
|
|
2000
|
+
const recipient = params.recipient ?? await this.getAddress(params.toChain);
|
|
2001
|
+
const result = await this.wdk.executeProtocol("bridge-usdt0", {
|
|
2002
|
+
fromChain: params.fromChain,
|
|
2003
|
+
toChain: params.toChain,
|
|
2004
|
+
amount: params.amount,
|
|
2005
|
+
recipient
|
|
2006
|
+
});
|
|
2007
|
+
if (!result || !result.txHash) {
|
|
2008
|
+
throw new BridgeError(
|
|
2009
|
+
7003 /* BRIDGE_FAILED */,
|
|
2010
|
+
"Bridge transaction did not return a transaction hash",
|
|
2011
|
+
{ fromChain: params.fromChain, toChain: params.toChain }
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
return {
|
|
2015
|
+
txHash: result.txHash,
|
|
2016
|
+
estimatedTime: 300
|
|
2017
|
+
// ~5 minutes typical for LayerZero
|
|
2018
|
+
};
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
if (error instanceof BridgeError) {
|
|
2021
|
+
throw error;
|
|
2022
|
+
}
|
|
2023
|
+
throw new BridgeError(
|
|
2024
|
+
7003 /* BRIDGE_FAILED */,
|
|
2025
|
+
`Bridge operation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
2026
|
+
{
|
|
2027
|
+
fromChain: params.fromChain,
|
|
2028
|
+
toChain: params.toChain,
|
|
2029
|
+
cause: error instanceof Error ? error : void 0,
|
|
2030
|
+
context: { amount: params.amount.toString() }
|
|
2031
|
+
}
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Get chains that support USDT0
|
|
2037
|
+
*/
|
|
2038
|
+
getUsdt0Chains() {
|
|
2039
|
+
return this.getConfiguredChains().filter((chain) => USDT0_ADDRESSES[chain]);
|
|
2040
|
+
}
|
|
2041
|
+
/**
|
|
2042
|
+
* Get chains that support USDT0 bridging
|
|
2043
|
+
*
|
|
2044
|
+
* Returns configured chains that have LayerZero OFT bridge support.
|
|
2045
|
+
*/
|
|
2046
|
+
getBridgeableChains() {
|
|
2047
|
+
return this.getConfiguredChains().filter((chain) => (0, import_evm.supportsBridging)(chain));
|
|
2048
|
+
}
|
|
2049
|
+
/**
|
|
2050
|
+
* Check if bridging is supported between two chains
|
|
2051
|
+
*/
|
|
2052
|
+
canBridge(fromChain, toChain) {
|
|
2053
|
+
return fromChain !== toChain && (0, import_evm.supportsBridging)(fromChain) && (0, import_evm.supportsBridging)(toChain) && this._normalizedChains.has(fromChain);
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Get all possible bridge destinations from a chain
|
|
2057
|
+
*/
|
|
2058
|
+
getBridgeDestinations(fromChain) {
|
|
2059
|
+
if (!(0, import_evm.supportsBridging)(fromChain)) {
|
|
2060
|
+
return [];
|
|
2061
|
+
}
|
|
2062
|
+
return (0, import_evm.getBridgeableChains)().filter((chain) => chain !== fromChain);
|
|
2063
|
+
}
|
|
2064
|
+
// ========== Cache Management ==========
|
|
2065
|
+
/**
|
|
2066
|
+
* Check if balance caching is enabled
|
|
2067
|
+
*/
|
|
2068
|
+
get isCacheEnabled() {
|
|
2069
|
+
return this._balanceCache.enabled;
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Get cache configuration
|
|
2073
|
+
*/
|
|
2074
|
+
getCacheConfig() {
|
|
2075
|
+
return this._balanceCache.config;
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Get cache statistics
|
|
2079
|
+
*/
|
|
2080
|
+
getCacheStats() {
|
|
2081
|
+
return this._balanceCache.getStats();
|
|
2082
|
+
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Invalidate all cached balances
|
|
2085
|
+
*
|
|
2086
|
+
* Call this after sending transactions to ensure fresh balance data.
|
|
2087
|
+
*/
|
|
2088
|
+
invalidateBalanceCache() {
|
|
2089
|
+
this._balanceCache.clear();
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Invalidate cached balances for a specific chain
|
|
2093
|
+
*
|
|
2094
|
+
* @param chain - Chain name to invalidate
|
|
2095
|
+
* @returns Number of cache entries invalidated
|
|
2096
|
+
*/
|
|
2097
|
+
invalidateChainCache(chain) {
|
|
2098
|
+
return this._balanceCache.invalidateChain(chain);
|
|
2099
|
+
}
|
|
2100
|
+
/**
|
|
2101
|
+
* Invalidate cached balances for a specific address
|
|
2102
|
+
*
|
|
2103
|
+
* @param address - Address to invalidate (case-insensitive)
|
|
2104
|
+
* @returns Number of cache entries invalidated
|
|
2105
|
+
*/
|
|
2106
|
+
invalidateAddressCache(address) {
|
|
2107
|
+
return this._balanceCache.invalidateAddress(address);
|
|
2108
|
+
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Dispose of cache resources
|
|
2111
|
+
*
|
|
2112
|
+
* Call this when the T402WDK instance is no longer needed.
|
|
2113
|
+
*/
|
|
2114
|
+
dispose() {
|
|
2115
|
+
this._balanceCache.dispose();
|
|
2116
|
+
this._signerCache.clear();
|
|
2117
|
+
}
|
|
2118
|
+
};
|
|
2119
|
+
// WDK module references (set via registerWDK)
|
|
2120
|
+
_T402WDK._WDK = null;
|
|
2121
|
+
_T402WDK._WalletManagerEvm = null;
|
|
2122
|
+
_T402WDK._BridgeUsdt0Evm = null;
|
|
2123
|
+
var T402WDK = _T402WDK;
|
|
2124
|
+
function formatTokenAmount(amount, decimals) {
|
|
2125
|
+
if (amount === 0n) {
|
|
2126
|
+
return "0";
|
|
2127
|
+
}
|
|
2128
|
+
const divisor = BigInt(10 ** decimals);
|
|
2129
|
+
const whole = amount / divisor;
|
|
2130
|
+
const fraction = amount % divisor;
|
|
2131
|
+
if (fraction === 0n) {
|
|
2132
|
+
return whole.toString();
|
|
2133
|
+
}
|
|
2134
|
+
const fractionStr = fraction.toString().padStart(decimals, "0");
|
|
2135
|
+
const trimmed = fractionStr.replace(/0+$/, "");
|
|
2136
|
+
return `${whole}.${trimmed}`;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
// src/bridge.ts
|
|
2140
|
+
var import_evm2 = require("@t402/evm");
|
|
2141
|
+
var WdkBridge = class {
|
|
2142
|
+
constructor() {
|
|
2143
|
+
this.bridges = /* @__PURE__ */ new Map();
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Create bridge signer adapter from WDK signer
|
|
2147
|
+
*/
|
|
2148
|
+
createBridgeSigner(signer) {
|
|
2149
|
+
return {
|
|
2150
|
+
address: signer.address,
|
|
2151
|
+
readContract: async (args) => {
|
|
2152
|
+
throw new Error(
|
|
2153
|
+
"readContract not available on WDKSigner. Use T402WDK.bridgeUsdt0() instead."
|
|
2154
|
+
);
|
|
2155
|
+
},
|
|
2156
|
+
writeContract: async (args) => {
|
|
2157
|
+
throw new Error(
|
|
2158
|
+
"writeContract not available on WDKSigner. Use T402WDK.bridgeUsdt0() instead."
|
|
2159
|
+
);
|
|
2160
|
+
},
|
|
2161
|
+
waitForTransactionReceipt: async (args) => {
|
|
2162
|
+
throw new Error(
|
|
2163
|
+
"waitForTransactionReceipt not available on WDKSigner. Use T402WDK.bridgeUsdt0() instead."
|
|
2164
|
+
);
|
|
2165
|
+
}
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Get or create a bridge instance for a chain
|
|
2170
|
+
*/
|
|
2171
|
+
getBridge(chain, signer) {
|
|
2172
|
+
const cached = this.bridges.get(chain);
|
|
2173
|
+
if (cached) {
|
|
2174
|
+
return cached;
|
|
2175
|
+
}
|
|
2176
|
+
const bridgeSigner = this.createBridgeSigner(signer);
|
|
2177
|
+
const bridge = new import_evm2.Usdt0Bridge(bridgeSigner, chain);
|
|
2178
|
+
this.bridges.set(chain, bridge);
|
|
2179
|
+
return bridge;
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Check if a chain supports USDT0 bridging
|
|
2183
|
+
*/
|
|
2184
|
+
static supportsBridging(chain) {
|
|
2185
|
+
return (0, import_evm2.supportsBridging)(chain);
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Get all chains that support USDT0 bridging
|
|
2189
|
+
*/
|
|
2190
|
+
static getBridgeableChains() {
|
|
2191
|
+
return (0, import_evm2.getBridgeableChains)();
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Get supported destinations from a source chain
|
|
2195
|
+
*/
|
|
2196
|
+
static getSupportedDestinations(fromChain) {
|
|
2197
|
+
return (0, import_evm2.getBridgeableChains)().filter((chain) => chain !== fromChain);
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
2200
|
+
function createDirectBridge(signer, chain) {
|
|
2201
|
+
return new import_evm2.Usdt0Bridge(signer, chain);
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
// src/index.ts
|
|
2205
|
+
var import_evm3 = require("@t402/evm");
|
|
2206
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2207
|
+
0 && (module.exports = {
|
|
2208
|
+
BalanceCache,
|
|
2209
|
+
BalanceError,
|
|
2210
|
+
BridgeError,
|
|
2211
|
+
CHAIN_TOKENS,
|
|
2212
|
+
ChainError,
|
|
2213
|
+
DEFAULT_BALANCE_CACHE_CONFIG,
|
|
2214
|
+
DEFAULT_CACHE_CONFIG,
|
|
2215
|
+
DEFAULT_CHAINS,
|
|
2216
|
+
DEFAULT_RETRY_CONFIG,
|
|
2217
|
+
DEFAULT_RPC_ENDPOINTS,
|
|
2218
|
+
LAYERZERO_ENDPOINT_IDS,
|
|
2219
|
+
MockWDKSigner,
|
|
2220
|
+
RPCError,
|
|
2221
|
+
SignerError,
|
|
2222
|
+
SigningError,
|
|
2223
|
+
T402WDK,
|
|
2224
|
+
TTLCache,
|
|
2225
|
+
TransactionError,
|
|
2226
|
+
USDC_ADDRESSES,
|
|
2227
|
+
USDT0_ADDRESSES,
|
|
2228
|
+
USDT0_OFT_ADDRESSES,
|
|
2229
|
+
USDT_LEGACY_ADDRESSES,
|
|
2230
|
+
Usdt0Bridge,
|
|
2231
|
+
WDKError,
|
|
2232
|
+
WDKErrorCode,
|
|
2233
|
+
WDKInitializationError,
|
|
2234
|
+
WDKSigner,
|
|
2235
|
+
WdkBridge,
|
|
2236
|
+
createDirectBridge,
|
|
2237
|
+
createWDKSigner,
|
|
2238
|
+
getBridgeableChains,
|
|
2239
|
+
getChainFromNetwork,
|
|
2240
|
+
getChainId,
|
|
2241
|
+
getNetworkFromChain,
|
|
2242
|
+
getPreferredToken,
|
|
2243
|
+
getUsdt0Chains,
|
|
2244
|
+
hasErrorCode,
|
|
2245
|
+
isWDKError,
|
|
2246
|
+
normalizeChainConfig,
|
|
2247
|
+
supportsBridging,
|
|
2248
|
+
withRetry,
|
|
2249
|
+
withTimeout,
|
|
2250
|
+
wrapError
|
|
2251
|
+
});
|
|
2252
|
+
//# sourceMappingURL=index.js.map
|