@circle-fin/provider-cctp-v2 1.6.3 → 1.7.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/CHANGELOG.md +7 -0
- package/README.md +5 -5
- package/index.cjs +300 -7
- package/index.d.ts +75 -0
- package/index.mjs +300 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @circle-fin/provider-cctp-v2
|
|
2
2
|
|
|
3
|
+
## 1.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add support for bridging USDC to and from Pharos (mainnet and testnet) via CCTP v2.
|
|
8
|
+
- Bridge errors now expose a machine-readable `errorCategory` so apps can distinguish user rejections, wallet capability errors, and offchain vs onchain failures without string-matching.
|
|
9
|
+
|
|
3
10
|
## 1.6.3
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
**Circle's Cross-Chain Transfer Protocol v2 provider for Bridge Kit**
|
|
11
11
|
|
|
12
|
-
_Native USDC bridging across
|
|
12
|
+
_Native USDC bridging across 43 chains using Circle's battle-tested protocols._
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
@@ -130,7 +130,7 @@ const result = await provider.bridge({
|
|
|
130
130
|
- ✅ **Native USDC bridging** - Move real USDC between supported networks
|
|
131
131
|
- ✅ **CCTP v2 integration** - Direct integration with Circle's CCTP v2 protocol
|
|
132
132
|
- ✅ **Comprehensive validation** - Route validation and parameter checking
|
|
133
|
-
- ✅ **Multi-chain support** - Works across all
|
|
133
|
+
- ✅ **Multi-chain support** - Works across all 43 CCTPv2-supported chains
|
|
134
134
|
- ✅ **Type safety** - Full TypeScript support with detailed error handling
|
|
135
135
|
- ✅ **Bridge speeds** - Support for both FAST and SLOW bridge configurations
|
|
136
136
|
- ✅ **Forwarder integration** - Circle's Orbit relayer handles attestation and mint automatically
|
|
@@ -138,15 +138,15 @@ const result = await provider.bridge({
|
|
|
138
138
|
|
|
139
139
|
## Supported Chains & Routes
|
|
140
140
|
|
|
141
|
-
The provider supports **
|
|
141
|
+
The provider supports **882 total bridge routes** across these chains:
|
|
142
142
|
|
|
143
143
|
### Mainnet Chains
|
|
144
144
|
|
|
145
|
-
**Arbitrum**, **Avalanche**, **Base**, **Codex**, **Edge**, **Ethereum**, **HyperEVM**, **Ink**, **Linea**, **Monad**, **Morph**, **OP Mainnet**, **Plume**, **Polygon PoS**, **Sei**, **Solana**, **Sonic**, **Unichain**, **World Chain**, **XDC**
|
|
145
|
+
**Arbitrum**, **Avalanche**, **Base**, **Codex**, **Edge**, **Ethereum**, **HyperEVM**, **Ink**, **Linea**, **Monad**, **Morph**, **OP Mainnet**, **Pharos**, **Plume**, **Polygon PoS**, **Sei**, **Solana**, **Sonic**, **Unichain**, **World Chain**, **XDC**
|
|
146
146
|
|
|
147
147
|
### Testnet Chains
|
|
148
148
|
|
|
149
|
-
**Arc Testnet**, **Arbitrum Sepolia**, **Avalanche Fuji**, **Base Sepolia**, **Codex Testnet**, **Edge Testnet**, **Ethereum Sepolia**, **HyperEVM Testnet**, **Ink Testnet**, **Linea Sepolia**, **Monad Testnet**, **Morph Testnet**, **OP Sepolia**, **Plume Testnet**, **Polygon PoS Amoy**, **Sei Testnet**, **Solana Devnet**, **Sonic Testnet**, **Unichain Sepolia**, **World Chain Sepolia**, **XDC Apothem**
|
|
149
|
+
**Arc Testnet**, **Arbitrum Sepolia**, **Avalanche Fuji**, **Base Sepolia**, **Codex Testnet**, **Edge Testnet**, **Ethereum Sepolia**, **HyperEVM Testnet**, **Ink Testnet**, **Linea Sepolia**, **Monad Testnet**, **Morph Testnet**, **OP Sepolia**, **Pharos Atlantic**, **Plume Testnet**, **Polygon PoS Amoy**, **Sei Testnet**, **Solana Devnet**, **Sonic Testnet**, **Unichain Sepolia**, **World Chain Sepolia**, **XDC Apothem**
|
|
150
150
|
|
|
151
151
|
## Error Handling
|
|
152
152
|
|
package/index.cjs
CHANGED
|
@@ -95,6 +95,8 @@ var Blockchain;
|
|
|
95
95
|
Blockchain["Noble_Testnet"] = "Noble_Testnet";
|
|
96
96
|
Blockchain["Optimism"] = "Optimism";
|
|
97
97
|
Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
98
|
+
Blockchain["Pharos"] = "Pharos";
|
|
99
|
+
Blockchain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
98
100
|
Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
|
|
99
101
|
Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
|
|
100
102
|
Blockchain["Plume"] = "Plume";
|
|
@@ -303,6 +305,7 @@ var BridgeChain;
|
|
|
303
305
|
BridgeChain["Monad"] = "Monad";
|
|
304
306
|
BridgeChain["Morph"] = "Morph";
|
|
305
307
|
BridgeChain["Optimism"] = "Optimism";
|
|
308
|
+
BridgeChain["Pharos"] = "Pharos";
|
|
306
309
|
BridgeChain["Plume"] = "Plume";
|
|
307
310
|
BridgeChain["Polygon"] = "Polygon";
|
|
308
311
|
BridgeChain["Sei"] = "Sei";
|
|
@@ -325,6 +328,7 @@ var BridgeChain;
|
|
|
325
328
|
BridgeChain["Monad_Testnet"] = "Monad_Testnet";
|
|
326
329
|
BridgeChain["Morph_Testnet"] = "Morph_Testnet";
|
|
327
330
|
BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
331
|
+
BridgeChain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
328
332
|
BridgeChain["Plume_Testnet"] = "Plume_Testnet";
|
|
329
333
|
BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
330
334
|
BridgeChain["Sei_Testnet"] = "Sei_Testnet";
|
|
@@ -670,6 +674,12 @@ const SWAP_TOKEN_REGISTRY = {
|
|
|
670
674
|
category: 'wrapped',
|
|
671
675
|
description: 'Wrapped Polygon',
|
|
672
676
|
},
|
|
677
|
+
CIRBTC: {
|
|
678
|
+
symbol: 'CIRBTC',
|
|
679
|
+
decimals: 8,
|
|
680
|
+
category: 'wrapped',
|
|
681
|
+
description: 'Circle Bitcoin',
|
|
682
|
+
},
|
|
673
683
|
};
|
|
674
684
|
/**
|
|
675
685
|
* Special NATIVE token constant for swap operations.
|
|
@@ -2341,6 +2351,96 @@ const OptimismSepolia = defineChain({
|
|
|
2341
2351
|
},
|
|
2342
2352
|
});
|
|
2343
2353
|
|
|
2354
|
+
/**
|
|
2355
|
+
* Pharos Mainnet chain definition
|
|
2356
|
+
* @remarks
|
|
2357
|
+
* This represents the official production network for the Pharos blockchain.
|
|
2358
|
+
* Pharos is a modular, full-stack parallel Layer 1 blockchain with
|
|
2359
|
+
* sub-second finality and EVM compatibility.
|
|
2360
|
+
*/
|
|
2361
|
+
const Pharos = defineChain({
|
|
2362
|
+
type: 'evm',
|
|
2363
|
+
chain: Blockchain.Pharos,
|
|
2364
|
+
name: 'Pharos',
|
|
2365
|
+
title: 'Pharos Mainnet',
|
|
2366
|
+
nativeCurrency: {
|
|
2367
|
+
name: 'Pharos',
|
|
2368
|
+
symbol: 'PHAROS',
|
|
2369
|
+
decimals: 18,
|
|
2370
|
+
},
|
|
2371
|
+
chainId: 1672,
|
|
2372
|
+
isTestnet: false,
|
|
2373
|
+
explorerUrl: 'https://pharos.socialscan.io/tx/{hash}',
|
|
2374
|
+
rpcEndpoints: ['https://rpc.pharos.xyz'],
|
|
2375
|
+
eurcAddress: null,
|
|
2376
|
+
usdcAddress: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
|
|
2377
|
+
usdtAddress: null,
|
|
2378
|
+
cctp: {
|
|
2379
|
+
domain: 31,
|
|
2380
|
+
contracts: {
|
|
2381
|
+
v2: {
|
|
2382
|
+
type: 'split',
|
|
2383
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
2384
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
2385
|
+
confirmations: 1,
|
|
2386
|
+
fastConfirmations: 1,
|
|
2387
|
+
},
|
|
2388
|
+
},
|
|
2389
|
+
forwarderSupported: {
|
|
2390
|
+
source: false,
|
|
2391
|
+
destination: false,
|
|
2392
|
+
},
|
|
2393
|
+
},
|
|
2394
|
+
kitContracts: {
|
|
2395
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2396
|
+
},
|
|
2397
|
+
});
|
|
2398
|
+
|
|
2399
|
+
/**
|
|
2400
|
+
* Pharos Atlantic Testnet chain definition
|
|
2401
|
+
* @remarks
|
|
2402
|
+
* This represents the official test network for the Pharos blockchain.
|
|
2403
|
+
* Pharos is a modular, full-stack parallel Layer 1 blockchain with
|
|
2404
|
+
* sub-second finality and EVM compatibility.
|
|
2405
|
+
*/
|
|
2406
|
+
const PharosTestnet = defineChain({
|
|
2407
|
+
type: 'evm',
|
|
2408
|
+
chain: Blockchain.Pharos_Testnet,
|
|
2409
|
+
name: 'Pharos Atlantic',
|
|
2410
|
+
title: 'Pharos Atlantic Testnet',
|
|
2411
|
+
nativeCurrency: {
|
|
2412
|
+
name: 'Pharos',
|
|
2413
|
+
symbol: 'PHAROS',
|
|
2414
|
+
decimals: 18,
|
|
2415
|
+
},
|
|
2416
|
+
chainId: 688689,
|
|
2417
|
+
isTestnet: true,
|
|
2418
|
+
explorerUrl: 'https://atlantic.pharosscan.xyz/tx/{hash}',
|
|
2419
|
+
rpcEndpoints: ['https://atlantic.dplabs-internal.com'],
|
|
2420
|
+
eurcAddress: null,
|
|
2421
|
+
usdcAddress: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
|
|
2422
|
+
usdtAddress: null,
|
|
2423
|
+
cctp: {
|
|
2424
|
+
domain: 31,
|
|
2425
|
+
contracts: {
|
|
2426
|
+
v2: {
|
|
2427
|
+
type: 'split',
|
|
2428
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
2429
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
2430
|
+
confirmations: 1,
|
|
2431
|
+
fastConfirmations: 1,
|
|
2432
|
+
},
|
|
2433
|
+
},
|
|
2434
|
+
forwarderSupported: {
|
|
2435
|
+
source: false,
|
|
2436
|
+
destination: false,
|
|
2437
|
+
},
|
|
2438
|
+
},
|
|
2439
|
+
kitContracts: {
|
|
2440
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2441
|
+
},
|
|
2442
|
+
});
|
|
2443
|
+
|
|
2344
2444
|
/**
|
|
2345
2445
|
* Plume Mainnet chain definition
|
|
2346
2446
|
* @remarks
|
|
@@ -2623,7 +2723,7 @@ const Sei = defineChain({
|
|
|
2623
2723
|
},
|
|
2624
2724
|
chainId: 1329,
|
|
2625
2725
|
isTestnet: false,
|
|
2626
|
-
explorerUrl: 'https://
|
|
2726
|
+
explorerUrl: 'https://seiscan.io/tx/{hash}',
|
|
2627
2727
|
rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
|
|
2628
2728
|
eurcAddress: null,
|
|
2629
2729
|
usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
|
|
@@ -2681,7 +2781,7 @@ const SeiTestnet = defineChain({
|
|
|
2681
2781
|
},
|
|
2682
2782
|
chainId: 1328,
|
|
2683
2783
|
isTestnet: true,
|
|
2684
|
-
explorerUrl: 'https://
|
|
2784
|
+
explorerUrl: 'https://testnet.seiscan.io/tx/{hash}',
|
|
2685
2785
|
rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
|
|
2686
2786
|
eurcAddress: null,
|
|
2687
2787
|
usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
|
|
@@ -3498,6 +3598,8 @@ var Chains = {
|
|
|
3498
3598
|
NobleTestnet: NobleTestnet,
|
|
3499
3599
|
Optimism: Optimism,
|
|
3500
3600
|
OptimismSepolia: OptimismSepolia,
|
|
3601
|
+
Pharos: Pharos,
|
|
3602
|
+
PharosTestnet: PharosTestnet,
|
|
3501
3603
|
Plume: Plume,
|
|
3502
3604
|
PlumeTestnet: PlumeTestnet,
|
|
3503
3605
|
PolkadotAssetHub: PolkadotAssetHub,
|
|
@@ -5002,6 +5104,9 @@ const NetworkError = {
|
|
|
5002
5104
|
name: 'NETWORK_CONNECTION_FAILED',
|
|
5003
5105
|
type: 'NETWORK',
|
|
5004
5106
|
},
|
|
5107
|
+
/** Network request timeout */
|
|
5108
|
+
TIMEOUT: {
|
|
5109
|
+
code: 3002},
|
|
5005
5110
|
/** Circle relayer failed to process the forwarding/mint transaction */
|
|
5006
5111
|
RELAYER_FORWARD_FAILED: {
|
|
5007
5112
|
code: 3003,
|
|
@@ -7185,6 +7290,7 @@ const USDC = {
|
|
|
7185
7290
|
[Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
|
|
7186
7291
|
[Blockchain.Noble]: 'uusdc',
|
|
7187
7292
|
[Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
|
|
7293
|
+
[Blockchain.Pharos]: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
|
|
7188
7294
|
[Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
|
|
7189
7295
|
[Blockchain.Polkadot_Asset_Hub]: '1337',
|
|
7190
7296
|
[Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
|
|
@@ -7216,6 +7322,7 @@ const USDC = {
|
|
|
7216
7322
|
[Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
|
|
7217
7323
|
[Blockchain.Noble_Testnet]: 'uusdc',
|
|
7218
7324
|
[Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
|
|
7325
|
+
[Blockchain.Pharos_Testnet]: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
|
|
7219
7326
|
[Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
|
|
7220
7327
|
[Blockchain.Polkadot_Westmint]: '31337',
|
|
7221
7328
|
[Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -7496,6 +7603,32 @@ const MON = {
|
|
|
7496
7603
|
},
|
|
7497
7604
|
};
|
|
7498
7605
|
|
|
7606
|
+
/**
|
|
7607
|
+
* cirBTC (Circle Bitcoin) token definition with addresses and metadata.
|
|
7608
|
+
*
|
|
7609
|
+
* @remarks
|
|
7610
|
+
* Built-in cirBTC definition for the TokenRegistry. Currently deployed
|
|
7611
|
+
* on Arc Testnet.
|
|
7612
|
+
*
|
|
7613
|
+
* @example
|
|
7614
|
+
* ```typescript
|
|
7615
|
+
* import { CIRBTC } from '@core/tokens'
|
|
7616
|
+
* import { Blockchain } from '@core/chains'
|
|
7617
|
+
*
|
|
7618
|
+
* console.log(CIRBTC.symbol) // 'cirBTC'
|
|
7619
|
+
* console.log(CIRBTC.decimals) // 8
|
|
7620
|
+
* console.log(CIRBTC.locators[Blockchain.Arc_Testnet])
|
|
7621
|
+
* // '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF'
|
|
7622
|
+
* ```
|
|
7623
|
+
*/
|
|
7624
|
+
const CIRBTC = {
|
|
7625
|
+
symbol: 'cirBTC',
|
|
7626
|
+
decimals: 8,
|
|
7627
|
+
locators: {
|
|
7628
|
+
[Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF',
|
|
7629
|
+
},
|
|
7630
|
+
};
|
|
7631
|
+
|
|
7499
7632
|
// Re-export for consumers
|
|
7500
7633
|
/**
|
|
7501
7634
|
* All default token definitions.
|
|
@@ -7504,7 +7637,7 @@ const MON = {
|
|
|
7504
7637
|
* These tokens are automatically included in the TokenRegistry when created
|
|
7505
7638
|
* without explicit defaults. Extensions can override these definitions.
|
|
7506
7639
|
* Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
|
|
7507
|
-
* WPOL, ETH, POL, PLUME, and
|
|
7640
|
+
* WPOL, ETH, POL, PLUME, MON, and cirBTC.
|
|
7508
7641
|
*
|
|
7509
7642
|
* @example
|
|
7510
7643
|
* ```typescript
|
|
@@ -7535,6 +7668,7 @@ const DEFAULT_TOKENS = [
|
|
|
7535
7668
|
POL,
|
|
7536
7669
|
PLUME,
|
|
7537
7670
|
MON,
|
|
7671
|
+
CIRBTC,
|
|
7538
7672
|
];
|
|
7539
7673
|
/**
|
|
7540
7674
|
* Uppercased token symbols approved for swap fee collection.
|
|
@@ -10311,11 +10445,18 @@ async function executePreparedChainRequest({ name, request, adapter, chain, conf
|
|
|
10311
10445
|
step.explorerUrl = buildExplorerUrl(chain, txHash);
|
|
10312
10446
|
if (outcome.errorMessage) {
|
|
10313
10447
|
step.errorMessage = outcome.errorMessage;
|
|
10448
|
+
// Transaction was mined but reverted on-chain.
|
|
10449
|
+
step.errorCategory = 'chain_revert';
|
|
10314
10450
|
}
|
|
10315
10451
|
}
|
|
10316
10452
|
catch (err) {
|
|
10317
10453
|
step.state = 'error';
|
|
10318
10454
|
step.error = err;
|
|
10455
|
+
// Sequential path does not yet attempt fine-grained classification of
|
|
10456
|
+
// pre-submission errors (user_rejected, capability errors, etc.). Mark
|
|
10457
|
+
// as `unknown` so consumers can at least detect the category is
|
|
10458
|
+
// populated uniformly across batched and sequential flows.
|
|
10459
|
+
step.errorCategory = 'unknown';
|
|
10319
10460
|
// Optionally parse for common blockchain error formats
|
|
10320
10461
|
if (err instanceof Error) {
|
|
10321
10462
|
step.errorMessage = err.message;
|
|
@@ -11864,16 +12005,70 @@ async function executeBatchedApproveAndBurn(params, provider) {
|
|
|
11864
12005
|
const batchResult = await adapter.batchExecute([approveCallData, burnCallData], chain);
|
|
11865
12006
|
const approveReceipt = batchResult.receipts[0];
|
|
11866
12007
|
const burnReceipt = batchResult.receipts[1];
|
|
11867
|
-
const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain);
|
|
11868
|
-
const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain);
|
|
12008
|
+
const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
|
|
12009
|
+
const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
|
|
11869
12010
|
if (burnStep.state !== 'error' && !burnStep.txHash) {
|
|
11870
12011
|
burnStep.state = 'error';
|
|
11871
12012
|
burnStep.errorMessage =
|
|
11872
12013
|
'Batched burn step completed but no transaction hash was returned.';
|
|
12014
|
+
burnStep.errorCategory = 'unknown';
|
|
11873
12015
|
}
|
|
11874
12016
|
const context = { burnTxHash: burnStep.txHash ?? '' };
|
|
11875
12017
|
return { approveStep, burnStep, context };
|
|
11876
12018
|
}
|
|
12019
|
+
/**
|
|
12020
|
+
* Derive a {@link BridgeStepErrorCategory} for a missing receipt.
|
|
12021
|
+
*
|
|
12022
|
+
* Combines the EIP-5792 `statusCode` (when present) with the underlying
|
|
12023
|
+
* polling error (when set) to produce the most specific category available.
|
|
12024
|
+
* Falls back to `'unknown'` when neither signal is conclusive.
|
|
12025
|
+
*
|
|
12026
|
+
* @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
|
|
12027
|
+
* @param batchError - The polling error from `batchExecute`, if any.
|
|
12028
|
+
* @returns The derived error category for a missing-receipt step.
|
|
12029
|
+
*
|
|
12030
|
+
* @internal
|
|
12031
|
+
*/
|
|
12032
|
+
function categorizeMissingReceipt(statusCode, batchError) {
|
|
12033
|
+
if (statusCode === 400)
|
|
12034
|
+
return 'failed_offchain';
|
|
12035
|
+
if (statusCode === 500)
|
|
12036
|
+
return 'reverted_onchain';
|
|
12037
|
+
if (statusCode === 600)
|
|
12038
|
+
return 'partial_reverted';
|
|
12039
|
+
if (batchError instanceof KitError &&
|
|
12040
|
+
batchError.code === NetworkError.TIMEOUT.code) {
|
|
12041
|
+
return 'polling_timeout';
|
|
12042
|
+
}
|
|
12043
|
+
return 'unknown';
|
|
12044
|
+
}
|
|
12045
|
+
/**
|
|
12046
|
+
* Derive a {@link BridgeStepErrorCategory} for a receipt that was returned
|
|
12047
|
+
* but whose per-call `status` is not `'success'`.
|
|
12048
|
+
*
|
|
12049
|
+
* A numeric EIP-5792 `statusCode` of 500 or 600 tells us the whole batch
|
|
12050
|
+
* reverted on-chain (completely or partially); otherwise the receipt
|
|
12051
|
+
* itself signalled a revert without a distinguishing code, so classify
|
|
12052
|
+
* as a plain on-chain revert.
|
|
12053
|
+
*
|
|
12054
|
+
* @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
|
|
12055
|
+
* @returns The derived error category for a failed-receipt step.
|
|
12056
|
+
*
|
|
12057
|
+
* @internal
|
|
12058
|
+
*/
|
|
12059
|
+
function categorizeFailedReceipt(statusCode) {
|
|
12060
|
+
// Mirror categorizeMissingReceipt: if the wallet's terminal status is
|
|
12061
|
+
// 400 ("batch not included onchain"), that judgement is authoritative
|
|
12062
|
+
// even when a non-success receipt is attached. Without this, a wrapped
|
|
12063
|
+
// 400 receipt would fall through to `chain_revert` and mislead UX.
|
|
12064
|
+
if (statusCode === 400)
|
|
12065
|
+
return 'failed_offchain';
|
|
12066
|
+
if (statusCode === 600)
|
|
12067
|
+
return 'partial_reverted';
|
|
12068
|
+
if (statusCode === 500)
|
|
12069
|
+
return 'reverted_onchain';
|
|
12070
|
+
return 'chain_revert';
|
|
12071
|
+
}
|
|
11877
12072
|
/**
|
|
11878
12073
|
* Build a {@link BridgeStep} from a single receipt within a batch.
|
|
11879
12074
|
*
|
|
@@ -11888,11 +12083,17 @@ async function executeBatchedApproveAndBurn(params, provider) {
|
|
|
11888
12083
|
* @param batchId - Wallet-assigned batch identifier.
|
|
11889
12084
|
* @param adapter - The batch-capable adapter (used for confirmation).
|
|
11890
12085
|
* @param chain - The EVM chain the batch was executed on.
|
|
12086
|
+
* @param statusCode - Optional EIP-5792 `statusCode` for the batch.
|
|
12087
|
+
* Used to classify the step's error category when the receipt is
|
|
12088
|
+
* missing or failed.
|
|
12089
|
+
* @param batchError - Optional polling error from `batchExecute`.
|
|
12090
|
+
* Preserved on the step so callers can inspect underlying timeouts
|
|
12091
|
+
* or RPC failures.
|
|
11891
12092
|
* @returns A fully-populated bridge step with state, tx hash and explorer URL.
|
|
11892
12093
|
*
|
|
11893
12094
|
* @internal
|
|
11894
12095
|
*/
|
|
11895
|
-
async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
12096
|
+
async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCode, batchError) {
|
|
11896
12097
|
const step = {
|
|
11897
12098
|
name,
|
|
11898
12099
|
state: 'pending',
|
|
@@ -11902,6 +12103,10 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
11902
12103
|
if (!receipt) {
|
|
11903
12104
|
step.state = 'error';
|
|
11904
12105
|
step.errorMessage = `No receipt returned for ${name} in batch ${batchId}.`;
|
|
12106
|
+
step.errorCategory = categorizeMissingReceipt(statusCode, batchError);
|
|
12107
|
+
if (batchError !== undefined) {
|
|
12108
|
+
step.error = batchError;
|
|
12109
|
+
}
|
|
11905
12110
|
return step;
|
|
11906
12111
|
}
|
|
11907
12112
|
step.txHash = receipt.txHash;
|
|
@@ -11911,11 +12116,13 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
11911
12116
|
if (receipt.status !== 'success') {
|
|
11912
12117
|
step.state = 'error';
|
|
11913
12118
|
step.errorMessage = `${name} call failed within batch ${batchId}.`;
|
|
12119
|
+
step.errorCategory = categorizeFailedReceipt(statusCode);
|
|
11914
12120
|
return step;
|
|
11915
12121
|
}
|
|
11916
12122
|
if (!receipt.txHash) {
|
|
11917
12123
|
step.state = 'error';
|
|
11918
12124
|
step.errorMessage = `${name} succeeded in batch but returned an empty transaction hash.`;
|
|
12125
|
+
step.errorCategory = 'unknown';
|
|
11919
12126
|
return step;
|
|
11920
12127
|
}
|
|
11921
12128
|
try {
|
|
@@ -11930,6 +12137,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
11930
12137
|
step.data = transaction;
|
|
11931
12138
|
if (outcome.errorMessage) {
|
|
11932
12139
|
step.errorMessage = outcome.errorMessage;
|
|
12140
|
+
step.errorCategory = 'chain_revert';
|
|
11933
12141
|
}
|
|
11934
12142
|
}
|
|
11935
12143
|
catch (err) {
|
|
@@ -11937,11 +12145,12 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
11937
12145
|
step.error = err;
|
|
11938
12146
|
step.errorMessage =
|
|
11939
12147
|
err instanceof Error ? err.message : 'Unknown error during confirmation.';
|
|
12148
|
+
step.errorCategory = 'unknown';
|
|
11940
12149
|
}
|
|
11941
12150
|
return step;
|
|
11942
12151
|
}
|
|
11943
12152
|
|
|
11944
|
-
var version = "1.
|
|
12153
|
+
var version = "1.7.0";
|
|
11945
12154
|
var pkg = {
|
|
11946
12155
|
version: version};
|
|
11947
12156
|
|
|
@@ -12017,6 +12226,7 @@ async function executeBatchedPath(params, provider, result, invocation) {
|
|
|
12017
12226
|
errorMessage: error_ instanceof Error
|
|
12018
12227
|
? error_.message
|
|
12019
12228
|
: 'Batched approve + burn failed.',
|
|
12229
|
+
errorCategory: classifyPreSubmissionError(error_),
|
|
12020
12230
|
});
|
|
12021
12231
|
return undefined;
|
|
12022
12232
|
}
|
|
@@ -12031,6 +12241,89 @@ function ensureStepErrorMessage(name, step) {
|
|
|
12031
12241
|
step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
|
|
12032
12242
|
}
|
|
12033
12243
|
}
|
|
12244
|
+
/**
|
|
12245
|
+
* Coerce a raw JSON-RPC `code` to a number.
|
|
12246
|
+
*
|
|
12247
|
+
* Some wallet SDKs serialize the JSON-RPC `code` as a string ("4001")
|
|
12248
|
+
* after round-tripping through JSON; accept both shapes so strict `===`
|
|
12249
|
+
* comparisons downstream still classify 5720/5730/5740 correctly — those
|
|
12250
|
+
* codes have no message-pattern fallback.
|
|
12251
|
+
*
|
|
12252
|
+
* @param rawCode - The raw `code` extracted from the error object.
|
|
12253
|
+
* @returns The numeric code, or `undefined` if the value cannot be parsed.
|
|
12254
|
+
*
|
|
12255
|
+
* @internal
|
|
12256
|
+
*/
|
|
12257
|
+
function coerceRpcCode(rawCode) {
|
|
12258
|
+
if (typeof rawCode === 'number') {
|
|
12259
|
+
return rawCode;
|
|
12260
|
+
}
|
|
12261
|
+
if (typeof rawCode === 'string') {
|
|
12262
|
+
return Number.parseInt(rawCode, 10);
|
|
12263
|
+
}
|
|
12264
|
+
return undefined;
|
|
12265
|
+
}
|
|
12266
|
+
/**
|
|
12267
|
+
* Classify a pre-submission error thrown during `wallet_sendCalls`.
|
|
12268
|
+
*
|
|
12269
|
+
* Inspect the error's JSON-RPC `code` (falling back to message pattern
|
|
12270
|
+
* matching for wrapper errors like viem's `ChainMismatchError`) and map
|
|
12271
|
+
* it to a {@link BridgeStepErrorCategory}. This lets downstream consumers
|
|
12272
|
+
* distinguish user rejections, wallet capability gaps, and unknown
|
|
12273
|
+
* failures without parsing error messages.
|
|
12274
|
+
*
|
|
12275
|
+
* @remarks
|
|
12276
|
+
* Does NOT alter control flow — the SDK continues to surface a
|
|
12277
|
+
* `state: 'error'` step. Auto-fallback to sequential execution is
|
|
12278
|
+
* intentionally out of scope for this helper.
|
|
12279
|
+
*
|
|
12280
|
+
* @param err - The error thrown by `wallet_sendCalls`.
|
|
12281
|
+
* @returns The derived error category, or `'unknown'` if no match.
|
|
12282
|
+
*
|
|
12283
|
+
* @internal
|
|
12284
|
+
*/
|
|
12285
|
+
function classifyPreSubmissionError(err) {
|
|
12286
|
+
// Cross-realm-safe duck typing: `instanceof Error` returns false for
|
|
12287
|
+
// errors thrown in a different JavaScript realm (e.g., a wallet
|
|
12288
|
+
// provider running inside an iframe, which is common with WalletConnect
|
|
12289
|
+
// and the Coinbase Wallet SDK).
|
|
12290
|
+
if (typeof err !== 'object' || err === null || !('message' in err)) {
|
|
12291
|
+
return 'unknown';
|
|
12292
|
+
}
|
|
12293
|
+
const code = coerceRpcCode(err.code);
|
|
12294
|
+
const message = String(err.message);
|
|
12295
|
+
// Numeric JSON-RPC codes are authoritative; check them before falling
|
|
12296
|
+
// back to message-pattern matching. Order matters: an error carrying
|
|
12297
|
+
// `code === 5750` with a message like "user rejected the upgrade"
|
|
12298
|
+
// is a capability problem, not a plain user rejection.
|
|
12299
|
+
if (code === 4001) {
|
|
12300
|
+
return 'user_rejected';
|
|
12301
|
+
}
|
|
12302
|
+
if (code === 5700 || code === 5710 || code === 5750) {
|
|
12303
|
+
return 'atomic_unsupported';
|
|
12304
|
+
}
|
|
12305
|
+
if (code === 5720) {
|
|
12306
|
+
return 'duplicate_batch_id';
|
|
12307
|
+
}
|
|
12308
|
+
if (code === 5730) {
|
|
12309
|
+
return 'unknown_bundle';
|
|
12310
|
+
}
|
|
12311
|
+
if (code === 5740) {
|
|
12312
|
+
return 'batch_too_large';
|
|
12313
|
+
}
|
|
12314
|
+
// Fall back to message patterns when no specific code is available —
|
|
12315
|
+
// viem (and other wrapper layers) sometimes strip the numeric code
|
|
12316
|
+
// while preserving the original wallet message in `Details:`.
|
|
12317
|
+
if (/EIP-7702 not supported/i.test(message) ||
|
|
12318
|
+
/does not support the requested chain/i.test(message) ||
|
|
12319
|
+
/rejected the upgrade/i.test(message)) {
|
|
12320
|
+
return 'atomic_unsupported';
|
|
12321
|
+
}
|
|
12322
|
+
if (/user rejected/i.test(message)) {
|
|
12323
|
+
return 'user_rejected';
|
|
12324
|
+
}
|
|
12325
|
+
return 'unknown';
|
|
12326
|
+
}
|
|
12034
12327
|
/**
|
|
12035
12328
|
* Execute a cross-chain USDC bridge using the CCTP v2 protocol.
|
|
12036
12329
|
*
|
package/index.d.ts
CHANGED
|
@@ -680,6 +680,8 @@ declare enum Blockchain {
|
|
|
680
680
|
Noble_Testnet = "Noble_Testnet",
|
|
681
681
|
Optimism = "Optimism",
|
|
682
682
|
Optimism_Sepolia = "Optimism_Sepolia",
|
|
683
|
+
Pharos = "Pharos",
|
|
684
|
+
Pharos_Testnet = "Pharos_Testnet",
|
|
683
685
|
Polkadot_Asset_Hub = "Polkadot_Asset_Hub",
|
|
684
686
|
Polkadot_Westmint = "Polkadot_Westmint",
|
|
685
687
|
Plume = "Plume",
|
|
@@ -3700,6 +3702,7 @@ declare module './types' {
|
|
|
3700
3702
|
POL: true;
|
|
3701
3703
|
PLUME: true;
|
|
3702
3704
|
MON: true;
|
|
3705
|
+
cirBTC: true;
|
|
3703
3706
|
}
|
|
3704
3707
|
}
|
|
3705
3708
|
|
|
@@ -4894,6 +4897,63 @@ TChainDefinition extends ChainDefinition = ChainDefinition> {
|
|
|
4894
4897
|
*/
|
|
4895
4898
|
invocationMeta?: InvocationMeta;
|
|
4896
4899
|
}
|
|
4900
|
+
/**
|
|
4901
|
+
* Machine-readable classification of a {@link BridgeStep} error.
|
|
4902
|
+
*
|
|
4903
|
+
* Consumers may use this field for UX decisions (e.g. distinguish a user
|
|
4904
|
+
* rejection from a wallet capability error) without string-matching on
|
|
4905
|
+
* {@link BridgeStep.errorMessage}. The original human-readable error
|
|
4906
|
+
* message is always preserved for display/logging.
|
|
4907
|
+
*
|
|
4908
|
+
* @remarks
|
|
4909
|
+
* The categories map to the most common failure shapes observed across
|
|
4910
|
+
* the EIP-5792 batched bridge path and the sequential bridge path:
|
|
4911
|
+
*
|
|
4912
|
+
* - `user_rejected` — user declined a wallet request (JSON-RPC `4001`).
|
|
4913
|
+
* - `atomic_unsupported` — wallet reported it cannot perform EIP-5792
|
|
4914
|
+
* atomic batching on this chain. Covers JSON-RPC `5700` (unsupported
|
|
4915
|
+
* capabilities), `5710` (chain not supported for the requested
|
|
4916
|
+
* capability), and `5750` (atomicity requires a wallet upgrade which
|
|
4917
|
+
* the user declined), or equivalent viem-wrapped messages.
|
|
4918
|
+
* - `batch_too_large` — wallet rejected the batch for exceeding its
|
|
4919
|
+
* size limit (JSON-RPC `5740`).
|
|
4920
|
+
* - `duplicate_batch_id` — wallet reported a duplicate batchId
|
|
4921
|
+
* (JSON-RPC `5720`).
|
|
4922
|
+
* - `unknown_bundle` — wallet reported an unknown bundle id during
|
|
4923
|
+
* status polling (JSON-RPC `5730`).
|
|
4924
|
+
* - `polling_timeout` — SDK polled `wallet_getCallsStatus` until the
|
|
4925
|
+
* configured timeout without receiving a terminal status.
|
|
4926
|
+
* - `failed_offchain` — wallet reported EIP-5792 `statusCode: 400`
|
|
4927
|
+
* (batch not included onchain, wallet will not retry).
|
|
4928
|
+
* - `reverted_onchain` — wallet reported EIP-5792 `statusCode: 500`
|
|
4929
|
+
* (batch reverted completely onchain).
|
|
4930
|
+
* - `partial_reverted` — wallet reported EIP-5792 `statusCode: 600`
|
|
4931
|
+
* (batch reverted partially onchain).
|
|
4932
|
+
* - `chain_revert` — transaction was mined but reverted on-chain
|
|
4933
|
+
* (sequential path).
|
|
4934
|
+
* - `unknown` — error did not match any of the above categories.
|
|
4935
|
+
*
|
|
4936
|
+
* @since 2.0.0
|
|
4937
|
+
*
|
|
4938
|
+
* @example
|
|
4939
|
+
* ```typescript
|
|
4940
|
+
* import type { BridgeStep } from '@core/provider'
|
|
4941
|
+
*
|
|
4942
|
+
* const step: BridgeStep = {
|
|
4943
|
+
* name: 'approve',
|
|
4944
|
+
* state: 'error',
|
|
4945
|
+
* errorMessage: 'User rejected the request',
|
|
4946
|
+
* errorCategory: 'user_rejected',
|
|
4947
|
+
* }
|
|
4948
|
+
*
|
|
4949
|
+
* if (step.errorCategory === 'user_rejected') {
|
|
4950
|
+
* // silent abort: user intentionally cancelled
|
|
4951
|
+
* } else if (step.errorCategory === 'atomic_unsupported') {
|
|
4952
|
+
* // hint the user about switching to step-by-step signing
|
|
4953
|
+
* }
|
|
4954
|
+
* ```
|
|
4955
|
+
*/
|
|
4956
|
+
type BridgeStepErrorCategory = 'user_rejected' | 'atomic_unsupported' | 'batch_too_large' | 'duplicate_batch_id' | 'unknown_bundle' | 'polling_timeout' | 'failed_offchain' | 'reverted_onchain' | 'partial_reverted' | 'chain_revert' | 'unknown';
|
|
4897
4957
|
/**
|
|
4898
4958
|
* A step in the bridge process.
|
|
4899
4959
|
*
|
|
@@ -4950,6 +5010,21 @@ interface BridgeStep {
|
|
|
4950
5010
|
errorMessage?: string;
|
|
4951
5011
|
/** Optional raw error object (can be Viem/Ethers/Chain error) */
|
|
4952
5012
|
error?: unknown;
|
|
5013
|
+
/**
|
|
5014
|
+
* Optional machine-readable classification of the error.
|
|
5015
|
+
*
|
|
5016
|
+
* Present when the step is in `state: 'error'` and the SDK was able to
|
|
5017
|
+
* categorize the failure. See {@link BridgeStepErrorCategory} for the
|
|
5018
|
+
* list of categories and how they map to underlying error shapes.
|
|
5019
|
+
*
|
|
5020
|
+
* @remarks
|
|
5021
|
+
* The human-readable {@link errorMessage} is always preserved for
|
|
5022
|
+
* logging and display; this field is additive and should be preferred
|
|
5023
|
+
* over string-matching `errorMessage` for machine decisions.
|
|
5024
|
+
*
|
|
5025
|
+
* @since 2.0.0
|
|
5026
|
+
*/
|
|
5027
|
+
errorCategory?: BridgeStepErrorCategory;
|
|
4953
5028
|
}
|
|
4954
5029
|
/**
|
|
4955
5030
|
* Result object returned after a successful cross-chain bridge operation.
|
package/index.mjs
CHANGED
|
@@ -88,6 +88,8 @@ var Blockchain;
|
|
|
88
88
|
Blockchain["Noble_Testnet"] = "Noble_Testnet";
|
|
89
89
|
Blockchain["Optimism"] = "Optimism";
|
|
90
90
|
Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
91
|
+
Blockchain["Pharos"] = "Pharos";
|
|
92
|
+
Blockchain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
91
93
|
Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
|
|
92
94
|
Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
|
|
93
95
|
Blockchain["Plume"] = "Plume";
|
|
@@ -296,6 +298,7 @@ var BridgeChain;
|
|
|
296
298
|
BridgeChain["Monad"] = "Monad";
|
|
297
299
|
BridgeChain["Morph"] = "Morph";
|
|
298
300
|
BridgeChain["Optimism"] = "Optimism";
|
|
301
|
+
BridgeChain["Pharos"] = "Pharos";
|
|
299
302
|
BridgeChain["Plume"] = "Plume";
|
|
300
303
|
BridgeChain["Polygon"] = "Polygon";
|
|
301
304
|
BridgeChain["Sei"] = "Sei";
|
|
@@ -318,6 +321,7 @@ var BridgeChain;
|
|
|
318
321
|
BridgeChain["Monad_Testnet"] = "Monad_Testnet";
|
|
319
322
|
BridgeChain["Morph_Testnet"] = "Morph_Testnet";
|
|
320
323
|
BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
324
|
+
BridgeChain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
321
325
|
BridgeChain["Plume_Testnet"] = "Plume_Testnet";
|
|
322
326
|
BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
323
327
|
BridgeChain["Sei_Testnet"] = "Sei_Testnet";
|
|
@@ -663,6 +667,12 @@ const SWAP_TOKEN_REGISTRY = {
|
|
|
663
667
|
category: 'wrapped',
|
|
664
668
|
description: 'Wrapped Polygon',
|
|
665
669
|
},
|
|
670
|
+
CIRBTC: {
|
|
671
|
+
symbol: 'CIRBTC',
|
|
672
|
+
decimals: 8,
|
|
673
|
+
category: 'wrapped',
|
|
674
|
+
description: 'Circle Bitcoin',
|
|
675
|
+
},
|
|
666
676
|
};
|
|
667
677
|
/**
|
|
668
678
|
* Special NATIVE token constant for swap operations.
|
|
@@ -2334,6 +2344,96 @@ const OptimismSepolia = defineChain({
|
|
|
2334
2344
|
},
|
|
2335
2345
|
});
|
|
2336
2346
|
|
|
2347
|
+
/**
|
|
2348
|
+
* Pharos Mainnet chain definition
|
|
2349
|
+
* @remarks
|
|
2350
|
+
* This represents the official production network for the Pharos blockchain.
|
|
2351
|
+
* Pharos is a modular, full-stack parallel Layer 1 blockchain with
|
|
2352
|
+
* sub-second finality and EVM compatibility.
|
|
2353
|
+
*/
|
|
2354
|
+
const Pharos = defineChain({
|
|
2355
|
+
type: 'evm',
|
|
2356
|
+
chain: Blockchain.Pharos,
|
|
2357
|
+
name: 'Pharos',
|
|
2358
|
+
title: 'Pharos Mainnet',
|
|
2359
|
+
nativeCurrency: {
|
|
2360
|
+
name: 'Pharos',
|
|
2361
|
+
symbol: 'PHAROS',
|
|
2362
|
+
decimals: 18,
|
|
2363
|
+
},
|
|
2364
|
+
chainId: 1672,
|
|
2365
|
+
isTestnet: false,
|
|
2366
|
+
explorerUrl: 'https://pharos.socialscan.io/tx/{hash}',
|
|
2367
|
+
rpcEndpoints: ['https://rpc.pharos.xyz'],
|
|
2368
|
+
eurcAddress: null,
|
|
2369
|
+
usdcAddress: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
|
|
2370
|
+
usdtAddress: null,
|
|
2371
|
+
cctp: {
|
|
2372
|
+
domain: 31,
|
|
2373
|
+
contracts: {
|
|
2374
|
+
v2: {
|
|
2375
|
+
type: 'split',
|
|
2376
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
2377
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
2378
|
+
confirmations: 1,
|
|
2379
|
+
fastConfirmations: 1,
|
|
2380
|
+
},
|
|
2381
|
+
},
|
|
2382
|
+
forwarderSupported: {
|
|
2383
|
+
source: false,
|
|
2384
|
+
destination: false,
|
|
2385
|
+
},
|
|
2386
|
+
},
|
|
2387
|
+
kitContracts: {
|
|
2388
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2389
|
+
},
|
|
2390
|
+
});
|
|
2391
|
+
|
|
2392
|
+
/**
|
|
2393
|
+
* Pharos Atlantic Testnet chain definition
|
|
2394
|
+
* @remarks
|
|
2395
|
+
* This represents the official test network for the Pharos blockchain.
|
|
2396
|
+
* Pharos is a modular, full-stack parallel Layer 1 blockchain with
|
|
2397
|
+
* sub-second finality and EVM compatibility.
|
|
2398
|
+
*/
|
|
2399
|
+
const PharosTestnet = defineChain({
|
|
2400
|
+
type: 'evm',
|
|
2401
|
+
chain: Blockchain.Pharos_Testnet,
|
|
2402
|
+
name: 'Pharos Atlantic',
|
|
2403
|
+
title: 'Pharos Atlantic Testnet',
|
|
2404
|
+
nativeCurrency: {
|
|
2405
|
+
name: 'Pharos',
|
|
2406
|
+
symbol: 'PHAROS',
|
|
2407
|
+
decimals: 18,
|
|
2408
|
+
},
|
|
2409
|
+
chainId: 688689,
|
|
2410
|
+
isTestnet: true,
|
|
2411
|
+
explorerUrl: 'https://atlantic.pharosscan.xyz/tx/{hash}',
|
|
2412
|
+
rpcEndpoints: ['https://atlantic.dplabs-internal.com'],
|
|
2413
|
+
eurcAddress: null,
|
|
2414
|
+
usdcAddress: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
|
|
2415
|
+
usdtAddress: null,
|
|
2416
|
+
cctp: {
|
|
2417
|
+
domain: 31,
|
|
2418
|
+
contracts: {
|
|
2419
|
+
v2: {
|
|
2420
|
+
type: 'split',
|
|
2421
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
2422
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
2423
|
+
confirmations: 1,
|
|
2424
|
+
fastConfirmations: 1,
|
|
2425
|
+
},
|
|
2426
|
+
},
|
|
2427
|
+
forwarderSupported: {
|
|
2428
|
+
source: false,
|
|
2429
|
+
destination: false,
|
|
2430
|
+
},
|
|
2431
|
+
},
|
|
2432
|
+
kitContracts: {
|
|
2433
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2434
|
+
},
|
|
2435
|
+
});
|
|
2436
|
+
|
|
2337
2437
|
/**
|
|
2338
2438
|
* Plume Mainnet chain definition
|
|
2339
2439
|
* @remarks
|
|
@@ -2616,7 +2716,7 @@ const Sei = defineChain({
|
|
|
2616
2716
|
},
|
|
2617
2717
|
chainId: 1329,
|
|
2618
2718
|
isTestnet: false,
|
|
2619
|
-
explorerUrl: 'https://
|
|
2719
|
+
explorerUrl: 'https://seiscan.io/tx/{hash}',
|
|
2620
2720
|
rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
|
|
2621
2721
|
eurcAddress: null,
|
|
2622
2722
|
usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
|
|
@@ -2674,7 +2774,7 @@ const SeiTestnet = defineChain({
|
|
|
2674
2774
|
},
|
|
2675
2775
|
chainId: 1328,
|
|
2676
2776
|
isTestnet: true,
|
|
2677
|
-
explorerUrl: 'https://
|
|
2777
|
+
explorerUrl: 'https://testnet.seiscan.io/tx/{hash}',
|
|
2678
2778
|
rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
|
|
2679
2779
|
eurcAddress: null,
|
|
2680
2780
|
usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
|
|
@@ -3491,6 +3591,8 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
3491
3591
|
NobleTestnet: NobleTestnet,
|
|
3492
3592
|
Optimism: Optimism,
|
|
3493
3593
|
OptimismSepolia: OptimismSepolia,
|
|
3594
|
+
Pharos: Pharos,
|
|
3595
|
+
PharosTestnet: PharosTestnet,
|
|
3494
3596
|
Plume: Plume,
|
|
3495
3597
|
PlumeTestnet: PlumeTestnet,
|
|
3496
3598
|
PolkadotAssetHub: PolkadotAssetHub,
|
|
@@ -4995,6 +5097,9 @@ const NetworkError = {
|
|
|
4995
5097
|
name: 'NETWORK_CONNECTION_FAILED',
|
|
4996
5098
|
type: 'NETWORK',
|
|
4997
5099
|
},
|
|
5100
|
+
/** Network request timeout */
|
|
5101
|
+
TIMEOUT: {
|
|
5102
|
+
code: 3002},
|
|
4998
5103
|
/** Circle relayer failed to process the forwarding/mint transaction */
|
|
4999
5104
|
RELAYER_FORWARD_FAILED: {
|
|
5000
5105
|
code: 3003,
|
|
@@ -7178,6 +7283,7 @@ const USDC = {
|
|
|
7178
7283
|
[Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
|
|
7179
7284
|
[Blockchain.Noble]: 'uusdc',
|
|
7180
7285
|
[Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
|
|
7286
|
+
[Blockchain.Pharos]: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
|
|
7181
7287
|
[Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
|
|
7182
7288
|
[Blockchain.Polkadot_Asset_Hub]: '1337',
|
|
7183
7289
|
[Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
|
|
@@ -7209,6 +7315,7 @@ const USDC = {
|
|
|
7209
7315
|
[Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
|
|
7210
7316
|
[Blockchain.Noble_Testnet]: 'uusdc',
|
|
7211
7317
|
[Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
|
|
7318
|
+
[Blockchain.Pharos_Testnet]: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
|
|
7212
7319
|
[Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
|
|
7213
7320
|
[Blockchain.Polkadot_Westmint]: '31337',
|
|
7214
7321
|
[Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -7489,6 +7596,32 @@ const MON = {
|
|
|
7489
7596
|
},
|
|
7490
7597
|
};
|
|
7491
7598
|
|
|
7599
|
+
/**
|
|
7600
|
+
* cirBTC (Circle Bitcoin) token definition with addresses and metadata.
|
|
7601
|
+
*
|
|
7602
|
+
* @remarks
|
|
7603
|
+
* Built-in cirBTC definition for the TokenRegistry. Currently deployed
|
|
7604
|
+
* on Arc Testnet.
|
|
7605
|
+
*
|
|
7606
|
+
* @example
|
|
7607
|
+
* ```typescript
|
|
7608
|
+
* import { CIRBTC } from '@core/tokens'
|
|
7609
|
+
* import { Blockchain } from '@core/chains'
|
|
7610
|
+
*
|
|
7611
|
+
* console.log(CIRBTC.symbol) // 'cirBTC'
|
|
7612
|
+
* console.log(CIRBTC.decimals) // 8
|
|
7613
|
+
* console.log(CIRBTC.locators[Blockchain.Arc_Testnet])
|
|
7614
|
+
* // '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF'
|
|
7615
|
+
* ```
|
|
7616
|
+
*/
|
|
7617
|
+
const CIRBTC = {
|
|
7618
|
+
symbol: 'cirBTC',
|
|
7619
|
+
decimals: 8,
|
|
7620
|
+
locators: {
|
|
7621
|
+
[Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF',
|
|
7622
|
+
},
|
|
7623
|
+
};
|
|
7624
|
+
|
|
7492
7625
|
// Re-export for consumers
|
|
7493
7626
|
/**
|
|
7494
7627
|
* All default token definitions.
|
|
@@ -7497,7 +7630,7 @@ const MON = {
|
|
|
7497
7630
|
* These tokens are automatically included in the TokenRegistry when created
|
|
7498
7631
|
* without explicit defaults. Extensions can override these definitions.
|
|
7499
7632
|
* Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
|
|
7500
|
-
* WPOL, ETH, POL, PLUME, and
|
|
7633
|
+
* WPOL, ETH, POL, PLUME, MON, and cirBTC.
|
|
7501
7634
|
*
|
|
7502
7635
|
* @example
|
|
7503
7636
|
* ```typescript
|
|
@@ -7528,6 +7661,7 @@ const DEFAULT_TOKENS = [
|
|
|
7528
7661
|
POL,
|
|
7529
7662
|
PLUME,
|
|
7530
7663
|
MON,
|
|
7664
|
+
CIRBTC,
|
|
7531
7665
|
];
|
|
7532
7666
|
/**
|
|
7533
7667
|
* Uppercased token symbols approved for swap fee collection.
|
|
@@ -10304,11 +10438,18 @@ async function executePreparedChainRequest({ name, request, adapter, chain, conf
|
|
|
10304
10438
|
step.explorerUrl = buildExplorerUrl(chain, txHash);
|
|
10305
10439
|
if (outcome.errorMessage) {
|
|
10306
10440
|
step.errorMessage = outcome.errorMessage;
|
|
10441
|
+
// Transaction was mined but reverted on-chain.
|
|
10442
|
+
step.errorCategory = 'chain_revert';
|
|
10307
10443
|
}
|
|
10308
10444
|
}
|
|
10309
10445
|
catch (err) {
|
|
10310
10446
|
step.state = 'error';
|
|
10311
10447
|
step.error = err;
|
|
10448
|
+
// Sequential path does not yet attempt fine-grained classification of
|
|
10449
|
+
// pre-submission errors (user_rejected, capability errors, etc.). Mark
|
|
10450
|
+
// as `unknown` so consumers can at least detect the category is
|
|
10451
|
+
// populated uniformly across batched and sequential flows.
|
|
10452
|
+
step.errorCategory = 'unknown';
|
|
10312
10453
|
// Optionally parse for common blockchain error formats
|
|
10313
10454
|
if (err instanceof Error) {
|
|
10314
10455
|
step.errorMessage = err.message;
|
|
@@ -11857,16 +11998,70 @@ async function executeBatchedApproveAndBurn(params, provider) {
|
|
|
11857
11998
|
const batchResult = await adapter.batchExecute([approveCallData, burnCallData], chain);
|
|
11858
11999
|
const approveReceipt = batchResult.receipts[0];
|
|
11859
12000
|
const burnReceipt = batchResult.receipts[1];
|
|
11860
|
-
const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain);
|
|
11861
|
-
const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain);
|
|
12001
|
+
const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
|
|
12002
|
+
const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
|
|
11862
12003
|
if (burnStep.state !== 'error' && !burnStep.txHash) {
|
|
11863
12004
|
burnStep.state = 'error';
|
|
11864
12005
|
burnStep.errorMessage =
|
|
11865
12006
|
'Batched burn step completed but no transaction hash was returned.';
|
|
12007
|
+
burnStep.errorCategory = 'unknown';
|
|
11866
12008
|
}
|
|
11867
12009
|
const context = { burnTxHash: burnStep.txHash ?? '' };
|
|
11868
12010
|
return { approveStep, burnStep, context };
|
|
11869
12011
|
}
|
|
12012
|
+
/**
|
|
12013
|
+
* Derive a {@link BridgeStepErrorCategory} for a missing receipt.
|
|
12014
|
+
*
|
|
12015
|
+
* Combines the EIP-5792 `statusCode` (when present) with the underlying
|
|
12016
|
+
* polling error (when set) to produce the most specific category available.
|
|
12017
|
+
* Falls back to `'unknown'` when neither signal is conclusive.
|
|
12018
|
+
*
|
|
12019
|
+
* @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
|
|
12020
|
+
* @param batchError - The polling error from `batchExecute`, if any.
|
|
12021
|
+
* @returns The derived error category for a missing-receipt step.
|
|
12022
|
+
*
|
|
12023
|
+
* @internal
|
|
12024
|
+
*/
|
|
12025
|
+
function categorizeMissingReceipt(statusCode, batchError) {
|
|
12026
|
+
if (statusCode === 400)
|
|
12027
|
+
return 'failed_offchain';
|
|
12028
|
+
if (statusCode === 500)
|
|
12029
|
+
return 'reverted_onchain';
|
|
12030
|
+
if (statusCode === 600)
|
|
12031
|
+
return 'partial_reverted';
|
|
12032
|
+
if (batchError instanceof KitError &&
|
|
12033
|
+
batchError.code === NetworkError.TIMEOUT.code) {
|
|
12034
|
+
return 'polling_timeout';
|
|
12035
|
+
}
|
|
12036
|
+
return 'unknown';
|
|
12037
|
+
}
|
|
12038
|
+
/**
|
|
12039
|
+
* Derive a {@link BridgeStepErrorCategory} for a receipt that was returned
|
|
12040
|
+
* but whose per-call `status` is not `'success'`.
|
|
12041
|
+
*
|
|
12042
|
+
* A numeric EIP-5792 `statusCode` of 500 or 600 tells us the whole batch
|
|
12043
|
+
* reverted on-chain (completely or partially); otherwise the receipt
|
|
12044
|
+
* itself signalled a revert without a distinguishing code, so classify
|
|
12045
|
+
* as a plain on-chain revert.
|
|
12046
|
+
*
|
|
12047
|
+
* @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
|
|
12048
|
+
* @returns The derived error category for a failed-receipt step.
|
|
12049
|
+
*
|
|
12050
|
+
* @internal
|
|
12051
|
+
*/
|
|
12052
|
+
function categorizeFailedReceipt(statusCode) {
|
|
12053
|
+
// Mirror categorizeMissingReceipt: if the wallet's terminal status is
|
|
12054
|
+
// 400 ("batch not included onchain"), that judgement is authoritative
|
|
12055
|
+
// even when a non-success receipt is attached. Without this, a wrapped
|
|
12056
|
+
// 400 receipt would fall through to `chain_revert` and mislead UX.
|
|
12057
|
+
if (statusCode === 400)
|
|
12058
|
+
return 'failed_offchain';
|
|
12059
|
+
if (statusCode === 600)
|
|
12060
|
+
return 'partial_reverted';
|
|
12061
|
+
if (statusCode === 500)
|
|
12062
|
+
return 'reverted_onchain';
|
|
12063
|
+
return 'chain_revert';
|
|
12064
|
+
}
|
|
11870
12065
|
/**
|
|
11871
12066
|
* Build a {@link BridgeStep} from a single receipt within a batch.
|
|
11872
12067
|
*
|
|
@@ -11881,11 +12076,17 @@ async function executeBatchedApproveAndBurn(params, provider) {
|
|
|
11881
12076
|
* @param batchId - Wallet-assigned batch identifier.
|
|
11882
12077
|
* @param adapter - The batch-capable adapter (used for confirmation).
|
|
11883
12078
|
* @param chain - The EVM chain the batch was executed on.
|
|
12079
|
+
* @param statusCode - Optional EIP-5792 `statusCode` for the batch.
|
|
12080
|
+
* Used to classify the step's error category when the receipt is
|
|
12081
|
+
* missing or failed.
|
|
12082
|
+
* @param batchError - Optional polling error from `batchExecute`.
|
|
12083
|
+
* Preserved on the step so callers can inspect underlying timeouts
|
|
12084
|
+
* or RPC failures.
|
|
11884
12085
|
* @returns A fully-populated bridge step with state, tx hash and explorer URL.
|
|
11885
12086
|
*
|
|
11886
12087
|
* @internal
|
|
11887
12088
|
*/
|
|
11888
|
-
async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
12089
|
+
async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCode, batchError) {
|
|
11889
12090
|
const step = {
|
|
11890
12091
|
name,
|
|
11891
12092
|
state: 'pending',
|
|
@@ -11895,6 +12096,10 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
11895
12096
|
if (!receipt) {
|
|
11896
12097
|
step.state = 'error';
|
|
11897
12098
|
step.errorMessage = `No receipt returned for ${name} in batch ${batchId}.`;
|
|
12099
|
+
step.errorCategory = categorizeMissingReceipt(statusCode, batchError);
|
|
12100
|
+
if (batchError !== undefined) {
|
|
12101
|
+
step.error = batchError;
|
|
12102
|
+
}
|
|
11898
12103
|
return step;
|
|
11899
12104
|
}
|
|
11900
12105
|
step.txHash = receipt.txHash;
|
|
@@ -11904,11 +12109,13 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
11904
12109
|
if (receipt.status !== 'success') {
|
|
11905
12110
|
step.state = 'error';
|
|
11906
12111
|
step.errorMessage = `${name} call failed within batch ${batchId}.`;
|
|
12112
|
+
step.errorCategory = categorizeFailedReceipt(statusCode);
|
|
11907
12113
|
return step;
|
|
11908
12114
|
}
|
|
11909
12115
|
if (!receipt.txHash) {
|
|
11910
12116
|
step.state = 'error';
|
|
11911
12117
|
step.errorMessage = `${name} succeeded in batch but returned an empty transaction hash.`;
|
|
12118
|
+
step.errorCategory = 'unknown';
|
|
11912
12119
|
return step;
|
|
11913
12120
|
}
|
|
11914
12121
|
try {
|
|
@@ -11923,6 +12130,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
11923
12130
|
step.data = transaction;
|
|
11924
12131
|
if (outcome.errorMessage) {
|
|
11925
12132
|
step.errorMessage = outcome.errorMessage;
|
|
12133
|
+
step.errorCategory = 'chain_revert';
|
|
11926
12134
|
}
|
|
11927
12135
|
}
|
|
11928
12136
|
catch (err) {
|
|
@@ -11930,11 +12138,12 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
11930
12138
|
step.error = err;
|
|
11931
12139
|
step.errorMessage =
|
|
11932
12140
|
err instanceof Error ? err.message : 'Unknown error during confirmation.';
|
|
12141
|
+
step.errorCategory = 'unknown';
|
|
11933
12142
|
}
|
|
11934
12143
|
return step;
|
|
11935
12144
|
}
|
|
11936
12145
|
|
|
11937
|
-
var version = "1.
|
|
12146
|
+
var version = "1.7.0";
|
|
11938
12147
|
var pkg = {
|
|
11939
12148
|
version: version};
|
|
11940
12149
|
|
|
@@ -12010,6 +12219,7 @@ async function executeBatchedPath(params, provider, result, invocation) {
|
|
|
12010
12219
|
errorMessage: error_ instanceof Error
|
|
12011
12220
|
? error_.message
|
|
12012
12221
|
: 'Batched approve + burn failed.',
|
|
12222
|
+
errorCategory: classifyPreSubmissionError(error_),
|
|
12013
12223
|
});
|
|
12014
12224
|
return undefined;
|
|
12015
12225
|
}
|
|
@@ -12024,6 +12234,89 @@ function ensureStepErrorMessage(name, step) {
|
|
|
12024
12234
|
step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
|
|
12025
12235
|
}
|
|
12026
12236
|
}
|
|
12237
|
+
/**
|
|
12238
|
+
* Coerce a raw JSON-RPC `code` to a number.
|
|
12239
|
+
*
|
|
12240
|
+
* Some wallet SDKs serialize the JSON-RPC `code` as a string ("4001")
|
|
12241
|
+
* after round-tripping through JSON; accept both shapes so strict `===`
|
|
12242
|
+
* comparisons downstream still classify 5720/5730/5740 correctly — those
|
|
12243
|
+
* codes have no message-pattern fallback.
|
|
12244
|
+
*
|
|
12245
|
+
* @param rawCode - The raw `code` extracted from the error object.
|
|
12246
|
+
* @returns The numeric code, or `undefined` if the value cannot be parsed.
|
|
12247
|
+
*
|
|
12248
|
+
* @internal
|
|
12249
|
+
*/
|
|
12250
|
+
function coerceRpcCode(rawCode) {
|
|
12251
|
+
if (typeof rawCode === 'number') {
|
|
12252
|
+
return rawCode;
|
|
12253
|
+
}
|
|
12254
|
+
if (typeof rawCode === 'string') {
|
|
12255
|
+
return Number.parseInt(rawCode, 10);
|
|
12256
|
+
}
|
|
12257
|
+
return undefined;
|
|
12258
|
+
}
|
|
12259
|
+
/**
|
|
12260
|
+
* Classify a pre-submission error thrown during `wallet_sendCalls`.
|
|
12261
|
+
*
|
|
12262
|
+
* Inspect the error's JSON-RPC `code` (falling back to message pattern
|
|
12263
|
+
* matching for wrapper errors like viem's `ChainMismatchError`) and map
|
|
12264
|
+
* it to a {@link BridgeStepErrorCategory}. This lets downstream consumers
|
|
12265
|
+
* distinguish user rejections, wallet capability gaps, and unknown
|
|
12266
|
+
* failures without parsing error messages.
|
|
12267
|
+
*
|
|
12268
|
+
* @remarks
|
|
12269
|
+
* Does NOT alter control flow — the SDK continues to surface a
|
|
12270
|
+
* `state: 'error'` step. Auto-fallback to sequential execution is
|
|
12271
|
+
* intentionally out of scope for this helper.
|
|
12272
|
+
*
|
|
12273
|
+
* @param err - The error thrown by `wallet_sendCalls`.
|
|
12274
|
+
* @returns The derived error category, or `'unknown'` if no match.
|
|
12275
|
+
*
|
|
12276
|
+
* @internal
|
|
12277
|
+
*/
|
|
12278
|
+
function classifyPreSubmissionError(err) {
|
|
12279
|
+
// Cross-realm-safe duck typing: `instanceof Error` returns false for
|
|
12280
|
+
// errors thrown in a different JavaScript realm (e.g., a wallet
|
|
12281
|
+
// provider running inside an iframe, which is common with WalletConnect
|
|
12282
|
+
// and the Coinbase Wallet SDK).
|
|
12283
|
+
if (typeof err !== 'object' || err === null || !('message' in err)) {
|
|
12284
|
+
return 'unknown';
|
|
12285
|
+
}
|
|
12286
|
+
const code = coerceRpcCode(err.code);
|
|
12287
|
+
const message = String(err.message);
|
|
12288
|
+
// Numeric JSON-RPC codes are authoritative; check them before falling
|
|
12289
|
+
// back to message-pattern matching. Order matters: an error carrying
|
|
12290
|
+
// `code === 5750` with a message like "user rejected the upgrade"
|
|
12291
|
+
// is a capability problem, not a plain user rejection.
|
|
12292
|
+
if (code === 4001) {
|
|
12293
|
+
return 'user_rejected';
|
|
12294
|
+
}
|
|
12295
|
+
if (code === 5700 || code === 5710 || code === 5750) {
|
|
12296
|
+
return 'atomic_unsupported';
|
|
12297
|
+
}
|
|
12298
|
+
if (code === 5720) {
|
|
12299
|
+
return 'duplicate_batch_id';
|
|
12300
|
+
}
|
|
12301
|
+
if (code === 5730) {
|
|
12302
|
+
return 'unknown_bundle';
|
|
12303
|
+
}
|
|
12304
|
+
if (code === 5740) {
|
|
12305
|
+
return 'batch_too_large';
|
|
12306
|
+
}
|
|
12307
|
+
// Fall back to message patterns when no specific code is available —
|
|
12308
|
+
// viem (and other wrapper layers) sometimes strip the numeric code
|
|
12309
|
+
// while preserving the original wallet message in `Details:`.
|
|
12310
|
+
if (/EIP-7702 not supported/i.test(message) ||
|
|
12311
|
+
/does not support the requested chain/i.test(message) ||
|
|
12312
|
+
/rejected the upgrade/i.test(message)) {
|
|
12313
|
+
return 'atomic_unsupported';
|
|
12314
|
+
}
|
|
12315
|
+
if (/user rejected/i.test(message)) {
|
|
12316
|
+
return 'user_rejected';
|
|
12317
|
+
}
|
|
12318
|
+
return 'unknown';
|
|
12319
|
+
}
|
|
12027
12320
|
/**
|
|
12028
12321
|
* Execute a cross-chain USDC bridge using the CCTP v2 protocol.
|
|
12029
12322
|
*
|