@getomnichain/omnichain 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/chain.base.js.map +1 -1
- package/dist/cjs/errors.js +55 -6
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/evm/evm_chain.js +421 -2
- package/dist/cjs/evm/evm_chain.js.map +1 -1
- package/dist/cjs/evm/evm_transaction_status.js +4 -0
- package/dist/cjs/evm/evm_transaction_status.js.map +1 -1
- package/dist/cjs/evm/unsigned_evm_transaction.js +12 -0
- package/dist/cjs/evm/unsigned_evm_transaction.js.map +1 -1
- package/dist/cjs/solana/solana_chain.js +618 -21
- package/dist/cjs/solana/solana_chain.js.map +1 -1
- package/dist/cjs/solana/unsigned_solana_transaction.js +33 -0
- package/dist/cjs/solana/unsigned_solana_transaction.js.map +1 -1
- package/dist/cjs/utxo/utxo_chain.js +94 -10
- package/dist/cjs/utxo/utxo_chain.js.map +1 -1
- package/dist/esm/chain.base.d.ts +38 -1
- package/dist/esm/chain.base.d.ts.map +1 -1
- package/dist/esm/chain.base.js.map +1 -1
- package/dist/esm/errors.d.ts +13 -0
- package/dist/esm/errors.d.ts.map +1 -1
- package/dist/esm/errors.js +51 -6
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/evm/evm_chain.d.ts +48 -2
- package/dist/esm/evm/evm_chain.d.ts.map +1 -1
- package/dist/esm/evm/evm_chain.js +423 -4
- package/dist/esm/evm/evm_chain.js.map +1 -1
- package/dist/esm/evm/evm_transaction_status.d.ts +4 -0
- package/dist/esm/evm/evm_transaction_status.d.ts.map +1 -1
- package/dist/esm/evm/evm_transaction_status.js +4 -0
- package/dist/esm/evm/evm_transaction_status.js.map +1 -1
- package/dist/esm/evm/unsigned_evm_transaction.d.ts +13 -0
- package/dist/esm/evm/unsigned_evm_transaction.d.ts.map +1 -1
- package/dist/esm/evm/unsigned_evm_transaction.js +12 -0
- package/dist/esm/evm/unsigned_evm_transaction.js.map +1 -1
- package/dist/esm/solana/solana_chain.d.ts +87 -3
- package/dist/esm/solana/solana_chain.d.ts.map +1 -1
- package/dist/esm/solana/solana_chain.js +618 -23
- package/dist/esm/solana/solana_chain.js.map +1 -1
- package/dist/esm/solana/unsigned_solana_transaction.d.ts +20 -1
- package/dist/esm/solana/unsigned_solana_transaction.d.ts.map +1 -1
- package/dist/esm/solana/unsigned_solana_transaction.js +33 -0
- package/dist/esm/solana/unsigned_solana_transaction.js.map +1 -1
- package/dist/esm/utxo/utxo_chain.d.ts +15 -2
- package/dist/esm/utxo/utxo_chain.d.ts.map +1 -1
- package/dist/esm/utxo/utxo_chain.js +96 -13
- package/dist/esm/utxo/utxo_chain.js.map +1 -1
- package/docs/CONNECTIONS.md +33 -0
- package/docs/EIP7702.md +77 -0
- package/docs/JITO.md +41 -0
- package/docs/MULTI_RPC.md +28 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chain.base.js","sourceRoot":"","sources":["../../chain.base.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"chain.base.js","sourceRoot":"","sources":["../../chain.base.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/errors.js
CHANGED
|
@@ -4,23 +4,51 @@ exports.ChainError = exports.ChainErrorKinds = void 0;
|
|
|
4
4
|
exports.sanitizeMessage = sanitizeMessage;
|
|
5
5
|
exports.sanitizeCause = sanitizeCause;
|
|
6
6
|
exports.isChainError = isChainError;
|
|
7
|
+
exports.isBlockhashExpiredError = isBlockhashExpiredError;
|
|
8
|
+
exports.isSimulationError = isSimulationError;
|
|
9
|
+
exports.isNonceError = isNonceError;
|
|
10
|
+
exports.isTransactionTooLargeError = isTransactionTooLargeError;
|
|
7
11
|
/**
|
|
8
12
|
* Replace `rpcUrl` in `message` with its host-only form so API keys carried
|
|
9
13
|
* in the URL path/query don't leak into error strings. Falls back to a
|
|
10
14
|
* `<rpc>` placeholder if the URL is unparseable.
|
|
11
15
|
*/
|
|
12
16
|
function sanitizeMessage(message, rpcUrl) {
|
|
17
|
+
let out = message;
|
|
18
|
+
// Long hex runs (with or without 0x prefix) are signed-tx bytes or opaque
|
|
19
|
+
// payloads. Threshold 128 hex chars = 64 bytes, above the largest transient
|
|
20
|
+
// identifier (tx hash / signature = 64 hex / 88 base58) so we don't clobber
|
|
21
|
+
// hashes but do catch even a minimal signed EVM tx (~110 bytes).
|
|
22
|
+
out = out.replace(/(?:0x)?[0-9a-fA-F]{128,}/g, (m) => {
|
|
23
|
+
const hexLen = m.startsWith('0x') || m.startsWith('0X') ? m.length - 2 : m.length;
|
|
24
|
+
return `<signed-bytes:${hexLen / 2}B>`;
|
|
25
|
+
});
|
|
26
|
+
// Long base64 runs (Solana / Jito payloads). base64 signed-tx is ~200 chars.
|
|
27
|
+
out = out.replace(/[A-Za-z0-9+/]{160,}={0,2}/g, (m) => `<signed-bytes-b64:${m.length}c>`);
|
|
28
|
+
out = out.replace(/([?&])([a-zA-Z_-]*(?:api[-_]?key|key|token|access[-_]?token|secret|auth))=[^\s"&]+/gi, '$1$2=<redacted>');
|
|
29
|
+
out = out.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer <redacted>');
|
|
30
|
+
out = out.replace(/Authorization:\s*[A-Za-z0-9._~+/=-]+/gi, 'Authorization: <redacted>');
|
|
31
|
+
out = out.replace(/((?:x-)?api[-_]?key)\s*:\s*[A-Za-z0-9._~+/=-]+/gi, '$1: <redacted>');
|
|
32
|
+
out = out.replace(/(\/v\d+|\/api|\/rpc)\/[A-Za-z0-9_-]{16,}(?=[\s/,)\];.'"]|$)/gi, '$1/<redacted>');
|
|
33
|
+
out = out.replace(/https?:\/\/[^\s"'`]*\.(?:alchemy\.com|infura\.io|quiknode\.pro|helius-rpc\.com|ankr\.com|blastapi\.io|drpc\.org)\/[A-Za-z0-9_./=-]{8,}/gi, (m) => {
|
|
34
|
+
try {
|
|
35
|
+
const u = new URL(m);
|
|
36
|
+
return `${u.protocol}//${u.host}/<redacted>`;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return '<rpc>';
|
|
40
|
+
}
|
|
41
|
+
});
|
|
13
42
|
if (!rpcUrl)
|
|
14
|
-
return
|
|
15
|
-
let host;
|
|
43
|
+
return out;
|
|
16
44
|
try {
|
|
17
45
|
const u = new URL(rpcUrl);
|
|
18
|
-
host = `${u.protocol}//${u.host}`;
|
|
46
|
+
const host = `${u.protocol}//${u.host}`;
|
|
47
|
+
return out.replaceAll(rpcUrl, host);
|
|
19
48
|
}
|
|
20
49
|
catch {
|
|
21
|
-
return
|
|
50
|
+
return out.replaceAll(rpcUrl, '<rpc>');
|
|
22
51
|
}
|
|
23
|
-
return message.replaceAll(rpcUrl, host);
|
|
24
52
|
}
|
|
25
53
|
/**
|
|
26
54
|
* Build a fresh `Error` from `cause` with a sanitized message and stack,
|
|
@@ -34,7 +62,9 @@ function sanitizeMessage(message, rpcUrl) {
|
|
|
34
62
|
function sanitizeCause(cause, rpcUrl) {
|
|
35
63
|
if (!(cause instanceof Error))
|
|
36
64
|
return undefined;
|
|
37
|
-
const
|
|
65
|
+
const short = cause.shortMessage;
|
|
66
|
+
const source = typeof short === 'string' && short.length > 0 ? short : cause.message;
|
|
67
|
+
const safe = new Error(sanitizeMessage(source, rpcUrl));
|
|
38
68
|
safe.name = cause.name;
|
|
39
69
|
if (cause.stack)
|
|
40
70
|
safe.stack = sanitizeMessage(cause.stack, rpcUrl);
|
|
@@ -49,6 +79,13 @@ exports.ChainErrorKinds = {
|
|
|
49
79
|
InvalidAddress: 'invalid_address',
|
|
50
80
|
InvalidTokenIdentifier: 'invalid_token_identifier',
|
|
51
81
|
InvalidArgument: 'invalid_argument',
|
|
82
|
+
BroadcastRejected: 'broadcast_rejected',
|
|
83
|
+
NonceTooLow: 'nonce_too_low',
|
|
84
|
+
InsufficientFunds: 'insufficient_funds',
|
|
85
|
+
BlockhashExpired: 'blockhash_expired',
|
|
86
|
+
SimulationFailed: 'simulation_failed',
|
|
87
|
+
TransactionTooLarge: 'transaction_too_large',
|
|
88
|
+
FeatureNotSupported: 'feature_not_supported',
|
|
52
89
|
};
|
|
53
90
|
class ChainError extends Error {
|
|
54
91
|
kind;
|
|
@@ -66,4 +103,16 @@ function isChainError(err, kind) {
|
|
|
66
103
|
return false;
|
|
67
104
|
return kind === undefined || err.kind === kind;
|
|
68
105
|
}
|
|
106
|
+
function isBlockhashExpiredError(err) {
|
|
107
|
+
return isChainError(err, exports.ChainErrorKinds.BlockhashExpired);
|
|
108
|
+
}
|
|
109
|
+
function isSimulationError(err) {
|
|
110
|
+
return isChainError(err, exports.ChainErrorKinds.SimulationFailed);
|
|
111
|
+
}
|
|
112
|
+
function isNonceError(err) {
|
|
113
|
+
return isChainError(err, exports.ChainErrorKinds.NonceTooLow);
|
|
114
|
+
}
|
|
115
|
+
function isTransactionTooLargeError(err) {
|
|
116
|
+
return isChainError(err, exports.ChainErrorKinds.TransactionTooLarge);
|
|
117
|
+
}
|
|
69
118
|
//# sourceMappingURL=errors.js.map
|
package/dist/cjs/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../errors.ts"],"names":[],"mappings":";;;AAKA,
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -57,6 +57,7 @@ const ERC20_ABI = [
|
|
|
57
57
|
const ERC20_INTERFACE = new ethers_1.Interface(ERC20_ABI);
|
|
58
58
|
class EvmChain extends chain_base_ts_1.Chain {
|
|
59
59
|
rpcUrl;
|
|
60
|
+
rpcUrls;
|
|
60
61
|
supportsEip1559;
|
|
61
62
|
hasL1Fee;
|
|
62
63
|
/**
|
|
@@ -74,6 +75,7 @@ class EvmChain extends chain_base_ts_1.Chain {
|
|
|
74
75
|
* `nativeTransferGasLimit`.
|
|
75
76
|
*/
|
|
76
77
|
nativeTransferGasMultiplier;
|
|
78
|
+
supports7702;
|
|
77
79
|
_nativeToken;
|
|
78
80
|
_provider = null;
|
|
79
81
|
_resolvedRpcUrl = null;
|
|
@@ -90,10 +92,15 @@ class EvmChain extends chain_base_ts_1.Chain {
|
|
|
90
92
|
}
|
|
91
93
|
super(init.chainId, init.name, network_type_ts_1.NetworkType.EVM, init.blockTimeSeconds, init.nativeSymbol, init.explorerBaseUrl);
|
|
92
94
|
this.rpcUrl = init.rpcUrl;
|
|
95
|
+
this.rpcUrls = init.rpcUrls ?? [];
|
|
96
|
+
if (this.rpcUrls.length > 1) {
|
|
97
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `EvmChainInit.rpcUrls accepts only ONE endpoint in 0.3.0 (got ${this.rpcUrls.length}). Automatic failover retry is deferred to a follow-up release; passing >1 endpoint would silently discard entries.`, { chainId: init.chainId });
|
|
98
|
+
}
|
|
93
99
|
this.supportsEip1559 = init.supportsEip1559 ?? true;
|
|
94
100
|
this.hasL1Fee = init.hasL1Fee ?? false;
|
|
95
101
|
this.nativeTransferGasLimit = init.nativeTransferGasLimit ?? 21000;
|
|
96
102
|
this.nativeTransferGasMultiplier = init.nativeTransferGasMultiplier ?? 1.4;
|
|
103
|
+
this.supports7702 = init.supports7702 ?? false;
|
|
97
104
|
this._nativeToken = evm_token_ts_1.EvmToken.native(init.chainId, init.nativeSymbol, init.nativeDecimals ?? 18);
|
|
98
105
|
// NOTE: no `registerNonEvmChain(id, EVM)` call — a module-scope write on
|
|
99
106
|
// 48 pre-baked chains would let a consumer's earlier
|
|
@@ -236,6 +243,13 @@ class EvmChain extends chain_base_ts_1.Chain {
|
|
|
236
243
|
readRpcUrl() {
|
|
237
244
|
if (this.rpcUrl && this.rpcUrl.trim().length > 0)
|
|
238
245
|
return this.rpcUrl.trim();
|
|
246
|
+
if (this.rpcUrls.length > 0) {
|
|
247
|
+
for (const candidate of this.rpcUrls) {
|
|
248
|
+
if (candidate && candidate.trim().length > 0)
|
|
249
|
+
return candidate.trim();
|
|
250
|
+
}
|
|
251
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.RpcNotConfigured, `${this.name}: rpcUrls was supplied but every entry is blank; refusing to fall back to env or public defaults`, { chainId: this.chainId });
|
|
252
|
+
}
|
|
239
253
|
const env = globalThis.process?.env;
|
|
240
254
|
const candidates = envCandidatesFor(this.name, this.chainId);
|
|
241
255
|
for (const key of candidates) {
|
|
@@ -243,7 +257,7 @@ class EvmChain extends chain_base_ts_1.Chain {
|
|
|
243
257
|
if (value && value.trim().length > 0)
|
|
244
258
|
return value.trim();
|
|
245
259
|
}
|
|
246
|
-
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.RpcNotConfigured, `${this.name} RPC URL is not configured (pass rpcUrl at construction, or set one of: ${candidates.join(', ')})`, { chainId: this.chainId, envCandidates: candidates });
|
|
260
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.RpcNotConfigured, `${this.name} RPC URL is not configured (pass rpcUrl, rpcUrls at construction, or set one of: ${candidates.join(', ')})`, { chainId: this.chainId, envCandidates: candidates });
|
|
247
261
|
}
|
|
248
262
|
rpcHost() {
|
|
249
263
|
const url = this._resolvedRpcUrl ?? this.rpcUrl;
|
|
@@ -279,6 +293,15 @@ class EvmChain extends chain_base_ts_1.Chain {
|
|
|
279
293
|
return false;
|
|
280
294
|
}
|
|
281
295
|
}
|
|
296
|
+
normalizeAddressOrThrow(raw, field) {
|
|
297
|
+
const body = raw.replace(/^0[xX]/, '');
|
|
298
|
+
try {
|
|
299
|
+
return (0, ethers_1.getAddress)(`0x${body}`);
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidAddress, `Invalid EVM address for ${field}: ${raw}`, { chainId: this.chainId, address: raw }, err instanceof Error ? err : undefined);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
282
305
|
validateTokenIdentifier(raw) {
|
|
283
306
|
if (raw === undefined)
|
|
284
307
|
return true;
|
|
@@ -377,7 +400,323 @@ class EvmChain extends chain_base_ts_1.Chain {
|
|
|
377
400
|
throw this.rpcError('Failed to read chain tip', err);
|
|
378
401
|
}
|
|
379
402
|
}
|
|
380
|
-
async
|
|
403
|
+
async broadcast(signed, opts) {
|
|
404
|
+
if (opts && opts.signal !== undefined) {
|
|
405
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.FeatureNotSupported, `EVM broadcast: signal is not honored in 0.3.0 (silently ignoring would let a caller conclude 'not sent' while the tx still lands). Cancellation returns in 0.3.1.`, { chainId: this.chainId });
|
|
406
|
+
}
|
|
407
|
+
let hex;
|
|
408
|
+
if (typeof signed === 'string') {
|
|
409
|
+
const stripped = signed.startsWith('0x') ? signed.slice(2) : signed;
|
|
410
|
+
if (!/^[0-9a-fA-F]+$/.test(stripped) || stripped.length % 2 !== 0 || stripped.length === 0) {
|
|
411
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `EVM broadcast: signed transaction must be 0x-prefixed hex or Uint8Array (got malformed string of length ${signed.length})`, { chainId: this.chainId });
|
|
412
|
+
}
|
|
413
|
+
hex = `0x${stripped}`;
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
if (signed.length === 0) {
|
|
417
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `EVM broadcast: signed transaction bytes are empty`, { chainId: this.chainId });
|
|
418
|
+
}
|
|
419
|
+
hex = (0, ethers_1.hexlify)(signed);
|
|
420
|
+
}
|
|
421
|
+
try {
|
|
422
|
+
const resp = await this.getProvider().broadcastTransaction(hex);
|
|
423
|
+
return resp.hash;
|
|
424
|
+
}
|
|
425
|
+
catch (err) {
|
|
426
|
+
const rawMsg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
427
|
+
if (/already[-_ ]?known|known\s+transaction|transaction[-_ ]?already[-_ ]?known|already\s+in\s+(?:the\s+)?(?:mempool|pool)|already[-_ ]?present/.test(rawMsg)) {
|
|
428
|
+
// Node "already known" is authoritative for THAT node's mempool —
|
|
429
|
+
// the identical signed bytes are pending or landed. Return the
|
|
430
|
+
// deterministic hash so the consumer stays on the polling path.
|
|
431
|
+
// Do NOT gate on a follow-up getTransaction read: load-balanced
|
|
432
|
+
// providers (Alchemy/Infura/QuickNode round-robin) routinely serve
|
|
433
|
+
// the confirmation read from a different backend that has not yet
|
|
434
|
+
// seen the pending tx → null → falsely-terminal BroadcastRejected
|
|
435
|
+
// → consumer re-signs against a fresh nonce → double-send while
|
|
436
|
+
// the original is live. If the tx really never lands, the
|
|
437
|
+
// consumer's getTransactionStatus poll will surface NotFound and
|
|
438
|
+
// the safe-broadcast rule (re-broadcast same bytes, never re-sign)
|
|
439
|
+
// still applies. Matches SolanaChain/UtxoChain already-known paths.
|
|
440
|
+
return (0, ethers_1.keccak256)(hex);
|
|
441
|
+
}
|
|
442
|
+
throw this.classifyBroadcastError(err);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async getPendingNonce(address) {
|
|
446
|
+
if (!this.validateAddress(address)) {
|
|
447
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidAddress, `Invalid EVM address for getPendingNonce: ${address}`, { chainId: this.chainId, address });
|
|
448
|
+
}
|
|
449
|
+
const normalized = this.normalizeAddressOrThrow(address, 'getPendingNonce.address');
|
|
450
|
+
try {
|
|
451
|
+
const n = await this.getProvider().getTransactionCount(normalized, 'pending');
|
|
452
|
+
return BigInt(n);
|
|
453
|
+
}
|
|
454
|
+
catch (err) {
|
|
455
|
+
throw this.rpcError(`Failed to read pending nonce for ${normalized}`, err, { address: normalized });
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
async getDelegation(address) {
|
|
459
|
+
if (!this.supports7702) {
|
|
460
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.FeatureNotSupported, `EVM chain ${this.name} was not initialized with supports7702: true`, { chainId: this.chainId });
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
(0, ethers_1.getAddress)(address);
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidAddress, `Invalid EVM address: ${address}`, { chainId: this.chainId, address }, err instanceof Error ? err : undefined);
|
|
467
|
+
}
|
|
468
|
+
let code;
|
|
469
|
+
try {
|
|
470
|
+
code = await this.getProvider().getCode(address);
|
|
471
|
+
}
|
|
472
|
+
catch (err) {
|
|
473
|
+
throw this.rpcError(`Failed to read code for ${address}`, err, { address });
|
|
474
|
+
}
|
|
475
|
+
if (!code.startsWith('0x'))
|
|
476
|
+
return null;
|
|
477
|
+
const stripped = code.slice(2).toLowerCase();
|
|
478
|
+
if (stripped.length !== 46)
|
|
479
|
+
return null;
|
|
480
|
+
if (!stripped.startsWith('ef0100'))
|
|
481
|
+
return null;
|
|
482
|
+
return { delegate: (0, ethers_1.getAddress)(`0x${stripped.slice(6)}`) };
|
|
483
|
+
}
|
|
484
|
+
async call(req) {
|
|
485
|
+
if (!this.validateAddress(req.to)) {
|
|
486
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidAddress, `Invalid EVM 'to' address for call: ${req.to}`, { chainId: this.chainId, address: req.to });
|
|
487
|
+
}
|
|
488
|
+
if (req.from !== undefined && !this.validateAddress(req.from)) {
|
|
489
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidAddress, `Invalid EVM 'from' address for call: ${req.from}`, { chainId: this.chainId, address: req.from });
|
|
490
|
+
}
|
|
491
|
+
if (!/^0x([0-9a-fA-F]{2})*$/.test(req.data)) {
|
|
492
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `EvmCallRequest.data must be 0x-prefixed even-length hex`, { chainId: this.chainId });
|
|
493
|
+
}
|
|
494
|
+
const provider = this.getProvider();
|
|
495
|
+
const tx = {
|
|
496
|
+
to: this.normalizeAddressOrThrow(req.to, 'call.to'),
|
|
497
|
+
data: req.data,
|
|
498
|
+
from: req.from === undefined ? undefined : this.normalizeAddressOrThrow(req.from, 'call.from'),
|
|
499
|
+
value: req.value,
|
|
500
|
+
};
|
|
501
|
+
if (req.estimateGas && req.blockTag !== undefined) {
|
|
502
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `EvmChain.call: blockTag is not honored when estimateGas: true (ethers v6 eth_estimateGas has no block-tag plumbing). Split the call: use estimateGas without blockTag, or drop estimateGas to run eth_call at the tag.`, { chainId: this.chainId });
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
if (req.estimateGas) {
|
|
506
|
+
const g = await provider.estimateGas(tx);
|
|
507
|
+
return { gasEstimate: BigInt(g) };
|
|
508
|
+
}
|
|
509
|
+
const result = await provider.call(req.blockTag !== undefined ? { ...tx, blockTag: req.blockTag } : tx);
|
|
510
|
+
return { result };
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
const strCode = ethersErrCode(err);
|
|
514
|
+
const rawData = err.data
|
|
515
|
+
?? err.info?.error?.data;
|
|
516
|
+
const revertData = typeof rawData === 'string' && /^0x[0-9a-fA-F]*$/.test(rawData) ? rawData : undefined;
|
|
517
|
+
if (strCode === 'CALL_EXCEPTION' || revertData !== undefined) {
|
|
518
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.SimulationFailed, (0, errors_ts_1.sanitizeMessage)(`eth_${req.estimateGas ? 'estimateGas' : 'call'} on ${req.to} reverted`, this._resolvedRpcUrl), { chainId: this.chainId, revertData }, (0, errors_ts_1.sanitizeCause)(err, this._resolvedRpcUrl));
|
|
519
|
+
}
|
|
520
|
+
if (strCode === 'INSUFFICIENT_FUNDS') {
|
|
521
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InsufficientFunds, (0, errors_ts_1.sanitizeMessage)(`eth_${req.estimateGas ? 'estimateGas' : 'call'} on ${req.to}: insufficient funds`, this._resolvedRpcUrl), { chainId: this.chainId }, (0, errors_ts_1.sanitizeCause)(err, this._resolvedRpcUrl));
|
|
522
|
+
}
|
|
523
|
+
throw this.rpcError(`eth_${req.estimateGas ? 'estimateGas' : 'call'} on ${req.to} failed`, err);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
buildAuthorizationDigest(input) {
|
|
527
|
+
if (!this.supports7702) {
|
|
528
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.FeatureNotSupported, `EVM chain ${this.name} was not initialized with supports7702: true`, { chainId: this.chainId });
|
|
529
|
+
}
|
|
530
|
+
if (input.chainId !== this.chainId) {
|
|
531
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `Authorization chainId ${input.chainId} does not match ${this.chainId}. Cross-chain replay guard: chainId=0 wildcards are rejected by default.`, { chainId: this.chainId });
|
|
532
|
+
}
|
|
533
|
+
if (input.nonce < 0n) {
|
|
534
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `Authorization nonce must be >= 0`, { chainId: this.chainId });
|
|
535
|
+
}
|
|
536
|
+
let normalizedDelegate;
|
|
537
|
+
try {
|
|
538
|
+
normalizedDelegate = (0, ethers_1.getAddress)(input.delegate);
|
|
539
|
+
}
|
|
540
|
+
catch (err) {
|
|
541
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidAddress, `buildAuthorizationDigest: delegate is not a valid EVM address: ${input.delegate}`, { chainId: this.chainId, address: input.delegate }, err instanceof Error ? err : undefined);
|
|
542
|
+
}
|
|
543
|
+
const rlp = (0, ethers_1.encodeRlp)([
|
|
544
|
+
(0, ethers_1.toBeArray)(BigInt(input.chainId)),
|
|
545
|
+
normalizedDelegate,
|
|
546
|
+
(0, ethers_1.toBeArray)(input.nonce),
|
|
547
|
+
]);
|
|
548
|
+
const payload = (0, ethers_1.concat)(['0x05', rlp]);
|
|
549
|
+
return (0, ethers_1.getBytes)((0, ethers_1.keccak256)(payload));
|
|
550
|
+
}
|
|
551
|
+
async createUnsignedTransaction(req) {
|
|
552
|
+
if (req && req.signal !== undefined) {
|
|
553
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.FeatureNotSupported, `EVM createUnsignedTransaction: signal is not honored in 0.3.0 (silently ignoring would let a caller conclude 'not built' while the blockhash fetch/build proceeds against stale state). Cancellation returns in 0.3.1.`, { chainId: this.chainId });
|
|
554
|
+
}
|
|
555
|
+
if (!req.from) {
|
|
556
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, 'CreateEvmUnsignedTransactionRequest.from is required', { chainId: this.chainId });
|
|
557
|
+
}
|
|
558
|
+
if (!this.validateAddress(req.from)) {
|
|
559
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidAddress, `Invalid EVM 'from' address (fails EIP-55 checksum or byte length): ${req.from}`, { chainId: this.chainId, address: req.from });
|
|
560
|
+
}
|
|
561
|
+
if (!req.to) {
|
|
562
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, 'CreateEvmUnsignedTransactionRequest.to is required', { chainId: this.chainId });
|
|
563
|
+
}
|
|
564
|
+
if (!this.validateAddress(req.to)) {
|
|
565
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidAddress, `Invalid EVM 'to' address (fails EIP-55 checksum or byte length): ${req.to}`, { chainId: this.chainId, address: req.to });
|
|
566
|
+
}
|
|
567
|
+
if (req.data !== undefined && !/^0x([0-9a-fA-F]{2})*$/.test(req.data)) {
|
|
568
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `CreateEvmUnsignedTransactionRequest.data must be a 0x-prefixed even-length hex string`, { chainId: this.chainId });
|
|
569
|
+
}
|
|
570
|
+
if (req.authorizationList !== undefined) {
|
|
571
|
+
if (!Array.isArray(req.authorizationList) || req.authorizationList.length === 0) {
|
|
572
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, 'authorizationList must be a non-empty array when provided', { chainId: this.chainId });
|
|
573
|
+
}
|
|
574
|
+
if (!this.supports7702) {
|
|
575
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.FeatureNotSupported, `EVM chain ${this.name} was not initialized with supports7702: true`, { chainId: this.chainId });
|
|
576
|
+
}
|
|
577
|
+
for (let i = 0; i < req.authorizationList.length; i++) {
|
|
578
|
+
const auth = req.authorizationList[i];
|
|
579
|
+
if (auth.chainId !== this.chainId) {
|
|
580
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `Authorization[${i}].chainId ${auth.chainId} does not match ${this.chainId}. Cross-chain replay guard: chainId=0 wildcards are rejected by default.`, { chainId: this.chainId });
|
|
581
|
+
}
|
|
582
|
+
if (!this.validateAddress(auth.address)) {
|
|
583
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidAddress, `Authorization[${i}].address is not a valid EVM address: ${auth.address}`, { chainId: this.chainId, address: auth.address });
|
|
584
|
+
}
|
|
585
|
+
if (typeof auth.nonce !== 'bigint' || auth.nonce < 0n) {
|
|
586
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `Authorization[${i}].nonce must be a non-negative bigint (got ${typeof auth.nonce})`, { chainId: this.chainId });
|
|
587
|
+
}
|
|
588
|
+
const sig = auth.signature;
|
|
589
|
+
if (!sig || typeof sig !== 'object'
|
|
590
|
+
|| typeof sig.r !== 'string' || !/^0x[0-9a-fA-F]{64}$/.test(sig.r)
|
|
591
|
+
|| typeof sig.s !== 'string' || !/^0x[0-9a-fA-F]{64}$/.test(sig.s)
|
|
592
|
+
|| (sig.yParity !== 0 && sig.yParity !== 1)) {
|
|
593
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `Authorization[${i}].signature must be { r: 0x + 64-hex, s: 0x + 64-hex, yParity: 0 | 1 }`, { chainId: this.chainId });
|
|
594
|
+
}
|
|
595
|
+
const rBig = BigInt(sig.r);
|
|
596
|
+
const sBig = BigInt(sig.s);
|
|
597
|
+
if (rBig === 0n || sBig === 0n) {
|
|
598
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `Authorization[${i}].signature: r and s must both be non-zero`, { chainId: this.chainId });
|
|
599
|
+
}
|
|
600
|
+
// EIP-2 low-s canonical form. An authorization with s > secp256k1n/2
|
|
601
|
+
// is silently rejected by the EVM at execution — tx lands, gas is
|
|
602
|
+
// spent, no delegation installed. Catch at build time.
|
|
603
|
+
const SECP256K1_HALF_N = 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0n;
|
|
604
|
+
if (sBig > SECP256K1_HALF_N) {
|
|
605
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `Authorization[${i}].signature.s violates EIP-2 low-s canonical form (s must be <= secp256k1n/2). Non-canonical authorizations are silently skipped by the EVM.`, { chainId: this.chainId });
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (!this.supportsEip1559 && (req.maxFeePerGas !== undefined || req.maxPriorityFeePerGas !== undefined)) {
|
|
610
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `EVM chain ${this.name} is legacy (supportsEip1559=false); maxFeePerGas/maxPriorityFeePerGas are not accepted. Use gasPrice via UnsignedEvmTransaction consumers.`, { chainId: this.chainId });
|
|
611
|
+
}
|
|
612
|
+
const emittedType = req.authorizationList !== undefined ? 4 : this.supportsEip1559 ? 2 : 0;
|
|
613
|
+
return new unsigned_evm_transaction_ts_1.UnsignedEvmTransaction({
|
|
614
|
+
chainId: this.chainId,
|
|
615
|
+
from: this.normalizeAddressOrThrow(req.from, 'createUnsignedTransaction.from'),
|
|
616
|
+
to: this.normalizeAddressOrThrow(req.to, 'createUnsignedTransaction.to'),
|
|
617
|
+
value: req.value ?? 0n,
|
|
618
|
+
data: req.data ?? '0x',
|
|
619
|
+
type: emittedType,
|
|
620
|
+
authorizationList: req.authorizationList,
|
|
621
|
+
gasLimit: req.gasLimit,
|
|
622
|
+
nonce: req.nonce,
|
|
623
|
+
maxFeePerGas: req.maxFeePerGas,
|
|
624
|
+
maxPriorityFeePerGas: req.maxPriorityFeePerGas,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
classifyBroadcastError(err) {
|
|
628
|
+
const rpc = this._resolvedRpcUrl;
|
|
629
|
+
const strCode = ethersErrCode(err);
|
|
630
|
+
const numCode = errCode(err);
|
|
631
|
+
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
632
|
+
const msg = rawMsg.toLowerCase();
|
|
633
|
+
const safeCause = (0, errors_ts_1.sanitizeCause)(err, rpc);
|
|
634
|
+
if (strCode === 'NONCE_EXPIRED' || msg.includes('nonce too low') || msg.includes('nonce_too_low') || msg.includes('nonce has already been used')) {
|
|
635
|
+
return new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.NonceTooLow, (0, errors_ts_1.sanitizeMessage)(`EVM broadcast rejected: nonce too low on ${this.name}`, rpc), { chainId: this.chainId, rpcHost: this.rpcHost() }, safeCause);
|
|
636
|
+
}
|
|
637
|
+
if (strCode === 'INSUFFICIENT_FUNDS' || msg.includes('insufficient funds')) {
|
|
638
|
+
return new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InsufficientFunds, (0, errors_ts_1.sanitizeMessage)(`EVM broadcast rejected: insufficient funds on ${this.name}`, rpc), { chainId: this.chainId, rpcHost: this.rpcHost() }, safeCause);
|
|
639
|
+
}
|
|
640
|
+
if (strCode === 'REPLACEMENT_UNDERPRICED' || msg.includes('replacement') || msg.includes('underpriced')) {
|
|
641
|
+
return new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.BroadcastRejected, (0, errors_ts_1.sanitizeMessage)(`EVM broadcast rejected on ${this.name} (replacement/known-nonce)`, rpc), { chainId: this.chainId, rpcHost: this.rpcHost() }, safeCause);
|
|
642
|
+
}
|
|
643
|
+
const rateLimitCodes = new Set([-32005, -32007, -32016, -32029, 429]);
|
|
644
|
+
if (numCode !== undefined && rateLimitCodes.has(numCode)) {
|
|
645
|
+
return this.rpcError(`EVM broadcast rate-limited on ${this.name}`, err);
|
|
646
|
+
}
|
|
647
|
+
const transportSignals = /econnreset|econnrefused|econnaborted|etimedout|enotfound|network request failed|fetch failed|socket hang up|too\s+many\s+requests|rate.?limit|network error/;
|
|
648
|
+
const httpStatus = err.info?.status
|
|
649
|
+
?? err.status;
|
|
650
|
+
const isTransportHttp = typeof httpStatus === 'number' && (httpStatus === 429 || httpStatus === 502 || httpStatus === 503 || httpStatus === 504);
|
|
651
|
+
if (transportSignals.test(msg) || isTransportHttp
|
|
652
|
+
|| strCode === 'NETWORK_ERROR' || strCode === 'SERVER_ERROR' || strCode === 'TIMEOUT') {
|
|
653
|
+
return this.rpcError(`EVM broadcast transport failure on ${this.name}`, err);
|
|
654
|
+
}
|
|
655
|
+
// Default terminal: any unrecognized rejection is a node reject, NOT a
|
|
656
|
+
// transient transport failure. This matches Solana/UTXO defaults and
|
|
657
|
+
// prevents a relayer retry-loop from re-broadcasting a permanently-
|
|
658
|
+
// invalid tx forever.
|
|
659
|
+
return new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.BroadcastRejected, (0, errors_ts_1.sanitizeMessage)(`EVM broadcast rejected on ${this.name}`, rpc), { chainId: this.chainId, rpcHost: this.rpcHost() }, safeCause);
|
|
660
|
+
}
|
|
661
|
+
async getTransactionStatus(txHash, opts) {
|
|
662
|
+
if (Array.isArray(txHash)) {
|
|
663
|
+
return runBatchStatus(txHash, (h, batchSignal) => {
|
|
664
|
+
const composedSignal = opts?.signal
|
|
665
|
+
? anySignal(opts.signal, batchSignal)
|
|
666
|
+
: batchSignal;
|
|
667
|
+
return this.getSingleTransactionStatus(h, { ...opts, signal: composedSignal });
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
return this.getSingleTransactionStatus(txHash, opts);
|
|
671
|
+
}
|
|
672
|
+
async getSingleTransactionStatus(txHash, opts) {
|
|
673
|
+
if (opts?.confirmations !== undefined && opts.confirmations > 1 && !opts.wait) {
|
|
674
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `getTransactionStatus: confirmations > 1 requires wait: true (a single status read cannot enforce block depth)`, { chainId: this.chainId, txHash });
|
|
675
|
+
}
|
|
676
|
+
if (!opts?.wait)
|
|
677
|
+
return this.getTransactionStatusOnce(txHash);
|
|
678
|
+
if (opts.signal?.aborted) {
|
|
679
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `getTransactionStatus aborted before first poll`, { chainId: this.chainId, txHash });
|
|
680
|
+
}
|
|
681
|
+
if (opts.timeoutMs === undefined) {
|
|
682
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `getTransactionStatus: wait: true requires an explicit timeoutMs. Unbounded polling would pin an RPC worker + connection forever if the tx is dropped.`, { chainId: this.chainId, txHash });
|
|
683
|
+
}
|
|
684
|
+
if (opts.timeoutMs < 0) {
|
|
685
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `getTransactionStatus: timeoutMs must be >= 0`, { chainId: this.chainId, txHash });
|
|
686
|
+
}
|
|
687
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
688
|
+
const pollMs = Math.max(500, this.blockTimeSeconds * 1000);
|
|
689
|
+
const minConfirmations = Math.max(1, opts.confirmations ?? 1);
|
|
690
|
+
let last;
|
|
691
|
+
while (true) {
|
|
692
|
+
last = await this.getTransactionStatusOnce(txHash);
|
|
693
|
+
if (last.status === 'Success' || last.status === 'Failed') {
|
|
694
|
+
if (minConfirmations <= 1)
|
|
695
|
+
return last;
|
|
696
|
+
let tip;
|
|
697
|
+
try {
|
|
698
|
+
tip = await this.getProvider().getBlockNumber();
|
|
699
|
+
}
|
|
700
|
+
catch (err) {
|
|
701
|
+
throw this.rpcError(`Failed to read chain tip for confirmations check`, err, { txHash });
|
|
702
|
+
}
|
|
703
|
+
const depth = last.blockNumber === null ? 0 : tip - last.blockNumber + 1;
|
|
704
|
+
if (depth >= minConfirmations)
|
|
705
|
+
return last;
|
|
706
|
+
if (Date.now() >= deadline) {
|
|
707
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.RpcError, `getTransactionStatus timed out after ${opts.timeoutMs}ms with only ${depth} confirmation(s); required ${minConfirmations}. Consumer must NOT credit as final.`, { chainId: this.chainId, txHash });
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
else if (Date.now() >= deadline) {
|
|
711
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.RpcError, `getTransactionStatus timed out after ${opts.timeoutMs}ms; last observed status was ${last.status}. Consumer must NOT credit as final AND must NOT re-sign — the tx may still land. Re-broadcast the same signed bytes or continue polling with the same txHash.`, { chainId: this.chainId, txHash });
|
|
712
|
+
}
|
|
713
|
+
await interruptibleSleep(pollMs, opts.signal);
|
|
714
|
+
if (opts.signal?.aborted) {
|
|
715
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `getTransactionStatus aborted mid-poll`, { chainId: this.chainId, txHash });
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
async getTransactionStatusOnce(txHash) {
|
|
381
720
|
const provider = this.getProvider();
|
|
382
721
|
let tx = null;
|
|
383
722
|
let receipt = null;
|
|
@@ -473,6 +812,7 @@ class EvmChain extends chain_base_ts_1.Chain {
|
|
|
473
812
|
inclusionAt,
|
|
474
813
|
error: errorInfo,
|
|
475
814
|
fees,
|
|
815
|
+
blockNumber: receipt.blockNumber,
|
|
476
816
|
});
|
|
477
817
|
}
|
|
478
818
|
// Only allocated on the Success path — failed txs carry logs: null.
|
|
@@ -499,6 +839,7 @@ class EvmChain extends chain_base_ts_1.Chain {
|
|
|
499
839
|
balanceChanges,
|
|
500
840
|
logs,
|
|
501
841
|
fees,
|
|
842
|
+
blockNumber: receipt.blockNumber,
|
|
502
843
|
});
|
|
503
844
|
}
|
|
504
845
|
async decodeBalanceChanges(args) {
|
|
@@ -683,6 +1024,84 @@ function stringifyErr(err) {
|
|
|
683
1024
|
return err.message;
|
|
684
1025
|
return String(err);
|
|
685
1026
|
}
|
|
1027
|
+
const BATCH_STATUS_CONCURRENCY = 8;
|
|
1028
|
+
async function runBatchStatus(items, fetchOne) {
|
|
1029
|
+
const results = new Array(items.length);
|
|
1030
|
+
const ac = new AbortController();
|
|
1031
|
+
let cursor = 0;
|
|
1032
|
+
const workers = [];
|
|
1033
|
+
const spawn = async () => {
|
|
1034
|
+
while (!ac.signal.aborted) {
|
|
1035
|
+
const idx = cursor++;
|
|
1036
|
+
if (idx >= items.length)
|
|
1037
|
+
return;
|
|
1038
|
+
try {
|
|
1039
|
+
results[idx] = await fetchOne(items[idx], ac.signal);
|
|
1040
|
+
}
|
|
1041
|
+
catch (err) {
|
|
1042
|
+
ac.abort();
|
|
1043
|
+
throw err;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
const workerCount = Math.min(BATCH_STATUS_CONCURRENCY, items.length);
|
|
1048
|
+
for (let i = 0; i < workerCount; i++)
|
|
1049
|
+
workers.push(spawn());
|
|
1050
|
+
await Promise.all(workers);
|
|
1051
|
+
return results;
|
|
1052
|
+
}
|
|
1053
|
+
function anySignal(a, b) {
|
|
1054
|
+
if (typeof AbortSignal.any === 'function')
|
|
1055
|
+
return AbortSignal.any([a, b]);
|
|
1056
|
+
const ac = new AbortController();
|
|
1057
|
+
const onAbort = () => ac.abort();
|
|
1058
|
+
if (a.aborted || b.aborted)
|
|
1059
|
+
ac.abort();
|
|
1060
|
+
else {
|
|
1061
|
+
a.addEventListener('abort', onAbort, { once: true });
|
|
1062
|
+
b.addEventListener('abort', onAbort, { once: true });
|
|
1063
|
+
}
|
|
1064
|
+
return ac.signal;
|
|
1065
|
+
}
|
|
1066
|
+
async function interruptibleSleep(ms, signal) {
|
|
1067
|
+
if (signal?.aborted)
|
|
1068
|
+
return;
|
|
1069
|
+
await new Promise((resolve) => {
|
|
1070
|
+
const t = setTimeout(() => {
|
|
1071
|
+
signal?.removeEventListener('abort', onAbort);
|
|
1072
|
+
resolve();
|
|
1073
|
+
}, ms);
|
|
1074
|
+
const onAbort = () => {
|
|
1075
|
+
clearTimeout(t);
|
|
1076
|
+
resolve();
|
|
1077
|
+
};
|
|
1078
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
function errCode(err) {
|
|
1082
|
+
if (typeof err !== 'object' || err === null)
|
|
1083
|
+
return undefined;
|
|
1084
|
+
const e = err;
|
|
1085
|
+
const candidates = [
|
|
1086
|
+
typeof e.code === 'number' ? e.code : undefined,
|
|
1087
|
+
e.error?.code,
|
|
1088
|
+
e.info?.error?.code,
|
|
1089
|
+
typeof e.code === 'string' && /^-?\d+$/.test(e.code) ? e.code : undefined,
|
|
1090
|
+
];
|
|
1091
|
+
for (const raw of candidates) {
|
|
1092
|
+
if (typeof raw === 'number')
|
|
1093
|
+
return raw;
|
|
1094
|
+
if (typeof raw === 'string' && /^-?\d+$/.test(raw))
|
|
1095
|
+
return Number(raw);
|
|
1096
|
+
}
|
|
1097
|
+
return undefined;
|
|
1098
|
+
}
|
|
1099
|
+
function ethersErrCode(err) {
|
|
1100
|
+
if (typeof err !== 'object' || err === null)
|
|
1101
|
+
return undefined;
|
|
1102
|
+
const e = err;
|
|
1103
|
+
return typeof e.code === 'string' ? e.code : undefined;
|
|
1104
|
+
}
|
|
686
1105
|
function envCandidatesFor(name, chainId) {
|
|
687
1106
|
const normalized = name.replace(/ /g, '_').toUpperCase() + '_RPC_URL';
|
|
688
1107
|
return [normalized, `EVM_${chainId}_RPC_URL`];
|