@circle-fin/provider-cctp-v2 1.6.3 → 1.8.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/index.cjs CHANGED
@@ -81,6 +81,8 @@ var Blockchain;
81
81
  Blockchain["Hedera_Testnet"] = "Hedera_Testnet";
82
82
  Blockchain["HyperEVM"] = "HyperEVM";
83
83
  Blockchain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
84
+ Blockchain["Injective"] = "Injective";
85
+ Blockchain["Injective_Testnet"] = "Injective_Testnet";
84
86
  Blockchain["Ink"] = "Ink";
85
87
  Blockchain["Ink_Testnet"] = "Ink_Testnet";
86
88
  Blockchain["Linea"] = "Linea";
@@ -95,6 +97,8 @@ var Blockchain;
95
97
  Blockchain["Noble_Testnet"] = "Noble_Testnet";
96
98
  Blockchain["Optimism"] = "Optimism";
97
99
  Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
100
+ Blockchain["Pharos"] = "Pharos";
101
+ Blockchain["Pharos_Testnet"] = "Pharos_Testnet";
98
102
  Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
99
103
  Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
100
104
  Blockchain["Plume"] = "Plume";
@@ -298,11 +302,13 @@ var BridgeChain;
298
302
  BridgeChain["Edge"] = "Edge";
299
303
  BridgeChain["Ethereum"] = "Ethereum";
300
304
  BridgeChain["HyperEVM"] = "HyperEVM";
305
+ BridgeChain["Injective"] = "Injective";
301
306
  BridgeChain["Ink"] = "Ink";
302
307
  BridgeChain["Linea"] = "Linea";
303
308
  BridgeChain["Monad"] = "Monad";
304
309
  BridgeChain["Morph"] = "Morph";
305
310
  BridgeChain["Optimism"] = "Optimism";
311
+ BridgeChain["Pharos"] = "Pharos";
306
312
  BridgeChain["Plume"] = "Plume";
307
313
  BridgeChain["Polygon"] = "Polygon";
308
314
  BridgeChain["Sei"] = "Sei";
@@ -320,11 +326,13 @@ var BridgeChain;
320
326
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
321
327
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
322
328
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
329
+ BridgeChain["Injective_Testnet"] = "Injective_Testnet";
323
330
  BridgeChain["Ink_Testnet"] = "Ink_Testnet";
324
331
  BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
325
332
  BridgeChain["Monad_Testnet"] = "Monad_Testnet";
326
333
  BridgeChain["Morph_Testnet"] = "Morph_Testnet";
327
334
  BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
335
+ BridgeChain["Pharos_Testnet"] = "Pharos_Testnet";
328
336
  BridgeChain["Plume_Testnet"] = "Plume_Testnet";
329
337
  BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
330
338
  BridgeChain["Sei_Testnet"] = "Sei_Testnet";
@@ -670,6 +678,12 @@ const SWAP_TOKEN_REGISTRY = {
670
678
  category: 'wrapped',
671
679
  description: 'Wrapped Polygon',
672
680
  },
681
+ CIRBTC: {
682
+ symbol: 'CIRBTC',
683
+ decimals: 8,
684
+ category: 'wrapped',
685
+ description: 'Circle Bitcoin',
686
+ },
673
687
  };
674
688
  /**
675
689
  * Special NATIVE token constant for swap operations.
@@ -1732,6 +1746,98 @@ const HyperEVMTestnet = defineChain({
1732
1746
  },
1733
1747
  });
1734
1748
 
1749
+ /**
1750
+ * Injective Mainnet chain definition
1751
+ * @remarks
1752
+ * This represents the official production network for the Injective blockchain.
1753
+ * Injective is a high-performance, interoperable Layer-1 blockchain built for
1754
+ * finance, with an EVM execution layer on top of a Cosmos SDK base and
1755
+ * sub-second block finality.
1756
+ */
1757
+ const Injective = defineChain({
1758
+ type: 'evm',
1759
+ chain: Blockchain.Injective,
1760
+ name: 'Injective',
1761
+ title: 'Injective Mainnet',
1762
+ nativeCurrency: {
1763
+ name: 'Injective',
1764
+ symbol: 'INJ',
1765
+ decimals: 18,
1766
+ },
1767
+ chainId: 1776,
1768
+ isTestnet: false,
1769
+ explorerUrl: 'https://injscan.com/transaction/{hash}',
1770
+ rpcEndpoints: ['https://sentry.evm-rpc.injective.network'],
1771
+ eurcAddress: null,
1772
+ usdcAddress: '0xa00C59fF5a080D2b954d0c75e46E22a0c371235a',
1773
+ usdtAddress: null,
1774
+ cctp: {
1775
+ domain: 29,
1776
+ contracts: {
1777
+ v2: {
1778
+ type: 'split',
1779
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
1780
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
1781
+ confirmations: 1,
1782
+ fastConfirmations: 1,
1783
+ },
1784
+ },
1785
+ forwarderSupported: {
1786
+ source: false,
1787
+ destination: false,
1788
+ },
1789
+ },
1790
+ kitContracts: {
1791
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1792
+ },
1793
+ });
1794
+
1795
+ /**
1796
+ * Injective Testnet chain definition
1797
+ * @remarks
1798
+ * This represents the official test network for the Injective blockchain.
1799
+ * Injective is a high-performance, interoperable Layer-1 blockchain built for
1800
+ * finance, with an EVM execution layer on top of a Cosmos SDK base and
1801
+ * sub-second block finality.
1802
+ */
1803
+ const InjectiveTestnet = defineChain({
1804
+ type: 'evm',
1805
+ chain: Blockchain.Injective_Testnet,
1806
+ name: 'Injective Testnet',
1807
+ title: 'Injective Testnet',
1808
+ nativeCurrency: {
1809
+ name: 'Injective',
1810
+ symbol: 'INJ',
1811
+ decimals: 18,
1812
+ },
1813
+ chainId: 1439,
1814
+ isTestnet: true,
1815
+ explorerUrl: 'https://testnet.explorer.injective.network/transaction/{hash}',
1816
+ rpcEndpoints: ['https://k8s.testnet.json-rpc.injective.network'],
1817
+ eurcAddress: null,
1818
+ usdcAddress: '0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d',
1819
+ usdtAddress: null,
1820
+ cctp: {
1821
+ domain: 29,
1822
+ contracts: {
1823
+ v2: {
1824
+ type: 'split',
1825
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
1826
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
1827
+ confirmations: 1,
1828
+ fastConfirmations: 1,
1829
+ },
1830
+ },
1831
+ forwarderSupported: {
1832
+ source: false,
1833
+ destination: false,
1834
+ },
1835
+ },
1836
+ kitContracts: {
1837
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1838
+ },
1839
+ });
1840
+
1735
1841
  /**
1736
1842
  * Ink Mainnet chain definition
1737
1843
  * @remarks
@@ -2341,6 +2447,96 @@ const OptimismSepolia = defineChain({
2341
2447
  },
2342
2448
  });
2343
2449
 
2450
+ /**
2451
+ * Pharos Mainnet chain definition
2452
+ * @remarks
2453
+ * This represents the official production network for the Pharos blockchain.
2454
+ * Pharos is a modular, full-stack parallel Layer 1 blockchain with
2455
+ * sub-second finality and EVM compatibility.
2456
+ */
2457
+ const Pharos = defineChain({
2458
+ type: 'evm',
2459
+ chain: Blockchain.Pharos,
2460
+ name: 'Pharos',
2461
+ title: 'Pharos Mainnet',
2462
+ nativeCurrency: {
2463
+ name: 'Pharos',
2464
+ symbol: 'PHAROS',
2465
+ decimals: 18,
2466
+ },
2467
+ chainId: 1672,
2468
+ isTestnet: false,
2469
+ explorerUrl: 'https://pharos.socialscan.io/tx/{hash}',
2470
+ rpcEndpoints: ['https://rpc.pharos.xyz'],
2471
+ eurcAddress: null,
2472
+ usdcAddress: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
2473
+ usdtAddress: null,
2474
+ cctp: {
2475
+ domain: 31,
2476
+ contracts: {
2477
+ v2: {
2478
+ type: 'split',
2479
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
2480
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
2481
+ confirmations: 1,
2482
+ fastConfirmations: 1,
2483
+ },
2484
+ },
2485
+ forwarderSupported: {
2486
+ source: false,
2487
+ destination: false,
2488
+ },
2489
+ },
2490
+ kitContracts: {
2491
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2492
+ },
2493
+ });
2494
+
2495
+ /**
2496
+ * Pharos Atlantic Testnet chain definition
2497
+ * @remarks
2498
+ * This represents the official test network for the Pharos blockchain.
2499
+ * Pharos is a modular, full-stack parallel Layer 1 blockchain with
2500
+ * sub-second finality and EVM compatibility.
2501
+ */
2502
+ const PharosTestnet = defineChain({
2503
+ type: 'evm',
2504
+ chain: Blockchain.Pharos_Testnet,
2505
+ name: 'Pharos Atlantic',
2506
+ title: 'Pharos Atlantic Testnet',
2507
+ nativeCurrency: {
2508
+ name: 'Pharos',
2509
+ symbol: 'PHAROS',
2510
+ decimals: 18,
2511
+ },
2512
+ chainId: 688689,
2513
+ isTestnet: true,
2514
+ explorerUrl: 'https://atlantic.pharosscan.xyz/tx/{hash}',
2515
+ rpcEndpoints: ['https://atlantic.dplabs-internal.com'],
2516
+ eurcAddress: null,
2517
+ usdcAddress: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
2518
+ usdtAddress: null,
2519
+ cctp: {
2520
+ domain: 31,
2521
+ contracts: {
2522
+ v2: {
2523
+ type: 'split',
2524
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
2525
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
2526
+ confirmations: 1,
2527
+ fastConfirmations: 1,
2528
+ },
2529
+ },
2530
+ forwarderSupported: {
2531
+ source: false,
2532
+ destination: false,
2533
+ },
2534
+ },
2535
+ kitContracts: {
2536
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2537
+ },
2538
+ });
2539
+
2344
2540
  /**
2345
2541
  * Plume Mainnet chain definition
2346
2542
  * @remarks
@@ -2623,7 +2819,7 @@ const Sei = defineChain({
2623
2819
  },
2624
2820
  chainId: 1329,
2625
2821
  isTestnet: false,
2626
- explorerUrl: 'https://seitrace.com/tx/{hash}?chain=pacific-1',
2822
+ explorerUrl: 'https://seiscan.io/tx/{hash}',
2627
2823
  rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
2628
2824
  eurcAddress: null,
2629
2825
  usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
@@ -2681,7 +2877,7 @@ const SeiTestnet = defineChain({
2681
2877
  },
2682
2878
  chainId: 1328,
2683
2879
  isTestnet: true,
2684
- explorerUrl: 'https://seitrace.com/tx/{hash}?chain=atlantic-2',
2880
+ explorerUrl: 'https://testnet.seiscan.io/tx/{hash}',
2685
2881
  rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
2686
2882
  eurcAddress: null,
2687
2883
  usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
@@ -3484,6 +3680,8 @@ var Chains = {
3484
3680
  HederaTestnet: HederaTestnet,
3485
3681
  HyperEVM: HyperEVM,
3486
3682
  HyperEVMTestnet: HyperEVMTestnet,
3683
+ Injective: Injective,
3684
+ InjectiveTestnet: InjectiveTestnet,
3487
3685
  Ink: Ink,
3488
3686
  InkTestnet: InkTestnet,
3489
3687
  Linea: Linea,
@@ -3498,6 +3696,8 @@ var Chains = {
3498
3696
  NobleTestnet: NobleTestnet,
3499
3697
  Optimism: Optimism,
3500
3698
  OptimismSepolia: OptimismSepolia,
3699
+ Pharos: Pharos,
3700
+ PharosTestnet: PharosTestnet,
3501
3701
  Plume: Plume,
3502
3702
  PlumeTestnet: PlumeTestnet,
3503
3703
  PolkadotAssetHub: PolkadotAssetHub,
@@ -5002,6 +5202,9 @@ const NetworkError = {
5002
5202
  name: 'NETWORK_CONNECTION_FAILED',
5003
5203
  type: 'NETWORK',
5004
5204
  },
5205
+ /** Network request timeout */
5206
+ TIMEOUT: {
5207
+ code: 3002},
5005
5208
  /** Circle relayer failed to process the forwarding/mint transaction */
5006
5209
  RELAYER_FORWARD_FAILED: {
5007
5210
  code: 3003,
@@ -7178,6 +7381,7 @@ const USDC = {
7178
7381
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
7179
7382
  [Blockchain.Hedera]: '0.0.456858',
7180
7383
  [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
7384
+ [Blockchain.Injective]: '0xa00C59fF5a080D2b954d0c75e46E22a0c371235a',
7181
7385
  [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
7182
7386
  [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
7183
7387
  [Blockchain.Monad]: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
@@ -7185,6 +7389,7 @@ const USDC = {
7185
7389
  [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
7186
7390
  [Blockchain.Noble]: 'uusdc',
7187
7391
  [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
7392
+ [Blockchain.Pharos]: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
7188
7393
  [Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
7189
7394
  [Blockchain.Polkadot_Asset_Hub]: '1337',
7190
7395
  [Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
@@ -7209,6 +7414,7 @@ const USDC = {
7209
7414
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
7210
7415
  [Blockchain.Hedera_Testnet]: '0.0.429274',
7211
7416
  [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
7417
+ [Blockchain.Injective_Testnet]: '0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d',
7212
7418
  [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
7213
7419
  [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
7214
7420
  [Blockchain.Monad_Testnet]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
@@ -7216,6 +7422,7 @@ const USDC = {
7216
7422
  [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
7217
7423
  [Blockchain.Noble_Testnet]: 'uusdc',
7218
7424
  [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
7425
+ [Blockchain.Pharos_Testnet]: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
7219
7426
  [Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
7220
7427
  [Blockchain.Polkadot_Westmint]: '31337',
7221
7428
  [Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
@@ -7496,6 +7703,32 @@ const MON = {
7496
7703
  },
7497
7704
  };
7498
7705
 
7706
+ /**
7707
+ * cirBTC (Circle Bitcoin) token definition with addresses and metadata.
7708
+ *
7709
+ * @remarks
7710
+ * Built-in cirBTC definition for the TokenRegistry. Currently deployed
7711
+ * on Arc Testnet.
7712
+ *
7713
+ * @example
7714
+ * ```typescript
7715
+ * import { CIRBTC } from '@core/tokens'
7716
+ * import { Blockchain } from '@core/chains'
7717
+ *
7718
+ * console.log(CIRBTC.symbol) // 'cirBTC'
7719
+ * console.log(CIRBTC.decimals) // 8
7720
+ * console.log(CIRBTC.locators[Blockchain.Arc_Testnet])
7721
+ * // '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF'
7722
+ * ```
7723
+ */
7724
+ const CIRBTC = {
7725
+ symbol: 'cirBTC',
7726
+ decimals: 8,
7727
+ locators: {
7728
+ [Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF',
7729
+ },
7730
+ };
7731
+
7499
7732
  // Re-export for consumers
7500
7733
  /**
7501
7734
  * All default token definitions.
@@ -7504,7 +7737,7 @@ const MON = {
7504
7737
  * These tokens are automatically included in the TokenRegistry when created
7505
7738
  * without explicit defaults. Extensions can override these definitions.
7506
7739
  * Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
7507
- * WPOL, ETH, POL, PLUME, and MON.
7740
+ * WPOL, ETH, POL, PLUME, MON, and cirBTC.
7508
7741
  *
7509
7742
  * @example
7510
7743
  * ```typescript
@@ -7535,6 +7768,7 @@ const DEFAULT_TOKENS = [
7535
7768
  POL,
7536
7769
  PLUME,
7537
7770
  MON,
7771
+ CIRBTC,
7538
7772
  ];
7539
7773
  /**
7540
7774
  * Uppercased token symbols approved for swap fee collection.
@@ -9145,18 +9379,16 @@ const buildIrisUrl = (sourceDomainId, transactionHash, isTestnet) => {
9145
9379
  };
9146
9380
  /**
9147
9381
  * Fetches attestation data from the IRIS API with retry and timeout handling.
9148
- * Since fetching starts after a confirmed burn transaction, we attempt up to 10 retries.
9149
- * Implements a conservative delay between requests to respect the API rate
9150
- * limit of 35 requests per second. To avoid rate limiting when multiple processes are
9151
- * running concurrently, we enforce a 200ms delay between retries.
9152
9382
  *
9153
- * Each HTTP request is given a maximum of 2 seconds before it is aborted.
9154
- * We retry up to 10 times, waiting 200ms between attempts.
9383
+ * Polls the IRIS API until a complete attestation is available. The default
9384
+ * window is sized for slow source chains where finality may take many
9385
+ * confirmations.
9155
9386
  *
9156
- * The total maximum time this function might take (worst case) is:
9157
- * - Perattempt timeout: 2 000 ms
9158
- * - Retry delays: 9 × 200 ms = 1 800 ms
9159
- * - Total max time: 2 000 ms + 1 800 ms = 3 800 ms
9387
+ * Defaults (see `DEFAULT_CONFIG`):
9388
+ * - Per-attempt timeout: 2 000 ms (each HTTP request aborts after 2 s)
9389
+ * - Retry delay: 2 000 ms between attempts
9390
+ * - Max retries: 600 (30 × 20)
9391
+ * - Total worst-case polling window: 600 × (2 000 ms + 2 000 ms) ≈ 40 minutes
9160
9392
  *
9161
9393
  * @param sourceDomainId - The CCTP domain ID.
9162
9394
  * @param transactionHash - The transaction hash to fetch attestation for.
@@ -10311,11 +10543,18 @@ async function executePreparedChainRequest({ name, request, adapter, chain, conf
10311
10543
  step.explorerUrl = buildExplorerUrl(chain, txHash);
10312
10544
  if (outcome.errorMessage) {
10313
10545
  step.errorMessage = outcome.errorMessage;
10546
+ // Transaction was mined but reverted on-chain.
10547
+ step.errorCategory = 'chain_revert';
10314
10548
  }
10315
10549
  }
10316
10550
  catch (err) {
10317
10551
  step.state = 'error';
10318
10552
  step.error = err;
10553
+ // Sequential path does not yet attempt fine-grained classification of
10554
+ // pre-submission errors (user_rejected, capability errors, etc.). Mark
10555
+ // as `unknown` so consumers can at least detect the category is
10556
+ // populated uniformly across batched and sequential flows.
10557
+ step.errorCategory = 'unknown';
10319
10558
  // Optionally parse for common blockchain error formats
10320
10559
  if (err instanceof Error) {
10321
10560
  step.errorMessage = err.message;
@@ -11864,16 +12103,70 @@ async function executeBatchedApproveAndBurn(params, provider) {
11864
12103
  const batchResult = await adapter.batchExecute([approveCallData, burnCallData], chain);
11865
12104
  const approveReceipt = batchResult.receipts[0];
11866
12105
  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);
12106
+ const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
12107
+ const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
11869
12108
  if (burnStep.state !== 'error' && !burnStep.txHash) {
11870
12109
  burnStep.state = 'error';
11871
12110
  burnStep.errorMessage =
11872
12111
  'Batched burn step completed but no transaction hash was returned.';
12112
+ burnStep.errorCategory = 'unknown';
11873
12113
  }
11874
12114
  const context = { burnTxHash: burnStep.txHash ?? '' };
11875
12115
  return { approveStep, burnStep, context };
11876
12116
  }
12117
+ /**
12118
+ * Derive a {@link BridgeStepErrorCategory} for a missing receipt.
12119
+ *
12120
+ * Combines the EIP-5792 `statusCode` (when present) with the underlying
12121
+ * polling error (when set) to produce the most specific category available.
12122
+ * Falls back to `'unknown'` when neither signal is conclusive.
12123
+ *
12124
+ * @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
12125
+ * @param batchError - The polling error from `batchExecute`, if any.
12126
+ * @returns The derived error category for a missing-receipt step.
12127
+ *
12128
+ * @internal
12129
+ */
12130
+ function categorizeMissingReceipt(statusCode, batchError) {
12131
+ if (statusCode === 400)
12132
+ return 'failed_offchain';
12133
+ if (statusCode === 500)
12134
+ return 'reverted_onchain';
12135
+ if (statusCode === 600)
12136
+ return 'partial_reverted';
12137
+ if (batchError instanceof KitError &&
12138
+ batchError.code === NetworkError.TIMEOUT.code) {
12139
+ return 'polling_timeout';
12140
+ }
12141
+ return 'unknown';
12142
+ }
12143
+ /**
12144
+ * Derive a {@link BridgeStepErrorCategory} for a receipt that was returned
12145
+ * but whose per-call `status` is not `'success'`.
12146
+ *
12147
+ * A numeric EIP-5792 `statusCode` of 500 or 600 tells us the whole batch
12148
+ * reverted on-chain (completely or partially); otherwise the receipt
12149
+ * itself signalled a revert without a distinguishing code, so classify
12150
+ * as a plain on-chain revert.
12151
+ *
12152
+ * @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
12153
+ * @returns The derived error category for a failed-receipt step.
12154
+ *
12155
+ * @internal
12156
+ */
12157
+ function categorizeFailedReceipt(statusCode) {
12158
+ // Mirror categorizeMissingReceipt: if the wallet's terminal status is
12159
+ // 400 ("batch not included onchain"), that judgement is authoritative
12160
+ // even when a non-success receipt is attached. Without this, a wrapped
12161
+ // 400 receipt would fall through to `chain_revert` and mislead UX.
12162
+ if (statusCode === 400)
12163
+ return 'failed_offchain';
12164
+ if (statusCode === 600)
12165
+ return 'partial_reverted';
12166
+ if (statusCode === 500)
12167
+ return 'reverted_onchain';
12168
+ return 'chain_revert';
12169
+ }
11877
12170
  /**
11878
12171
  * Build a {@link BridgeStep} from a single receipt within a batch.
11879
12172
  *
@@ -11888,11 +12181,17 @@ async function executeBatchedApproveAndBurn(params, provider) {
11888
12181
  * @param batchId - Wallet-assigned batch identifier.
11889
12182
  * @param adapter - The batch-capable adapter (used for confirmation).
11890
12183
  * @param chain - The EVM chain the batch was executed on.
12184
+ * @param statusCode - Optional EIP-5792 `statusCode` for the batch.
12185
+ * Used to classify the step's error category when the receipt is
12186
+ * missing or failed.
12187
+ * @param batchError - Optional polling error from `batchExecute`.
12188
+ * Preserved on the step so callers can inspect underlying timeouts
12189
+ * or RPC failures.
11891
12190
  * @returns A fully-populated bridge step with state, tx hash and explorer URL.
11892
12191
  *
11893
12192
  * @internal
11894
12193
  */
11895
- async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
12194
+ async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCode, batchError) {
11896
12195
  const step = {
11897
12196
  name,
11898
12197
  state: 'pending',
@@ -11902,6 +12201,10 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
11902
12201
  if (!receipt) {
11903
12202
  step.state = 'error';
11904
12203
  step.errorMessage = `No receipt returned for ${name} in batch ${batchId}.`;
12204
+ step.errorCategory = categorizeMissingReceipt(statusCode, batchError);
12205
+ if (batchError !== undefined) {
12206
+ step.error = batchError;
12207
+ }
11905
12208
  return step;
11906
12209
  }
11907
12210
  step.txHash = receipt.txHash;
@@ -11911,11 +12214,13 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
11911
12214
  if (receipt.status !== 'success') {
11912
12215
  step.state = 'error';
11913
12216
  step.errorMessage = `${name} call failed within batch ${batchId}.`;
12217
+ step.errorCategory = categorizeFailedReceipt(statusCode);
11914
12218
  return step;
11915
12219
  }
11916
12220
  if (!receipt.txHash) {
11917
12221
  step.state = 'error';
11918
12222
  step.errorMessage = `${name} succeeded in batch but returned an empty transaction hash.`;
12223
+ step.errorCategory = 'unknown';
11919
12224
  return step;
11920
12225
  }
11921
12226
  try {
@@ -11930,6 +12235,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
11930
12235
  step.data = transaction;
11931
12236
  if (outcome.errorMessage) {
11932
12237
  step.errorMessage = outcome.errorMessage;
12238
+ step.errorCategory = 'chain_revert';
11933
12239
  }
11934
12240
  }
11935
12241
  catch (err) {
@@ -11937,11 +12243,12 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
11937
12243
  step.error = err;
11938
12244
  step.errorMessage =
11939
12245
  err instanceof Error ? err.message : 'Unknown error during confirmation.';
12246
+ step.errorCategory = 'unknown';
11940
12247
  }
11941
12248
  return step;
11942
12249
  }
11943
12250
 
11944
- var version = "1.6.3";
12251
+ var version = "1.8.0";
11945
12252
  var pkg = {
11946
12253
  version: version};
11947
12254
 
@@ -12017,6 +12324,7 @@ async function executeBatchedPath(params, provider, result, invocation) {
12017
12324
  errorMessage: error_ instanceof Error
12018
12325
  ? error_.message
12019
12326
  : 'Batched approve + burn failed.',
12327
+ errorCategory: classifyPreSubmissionError(error_),
12020
12328
  });
12021
12329
  return undefined;
12022
12330
  }
@@ -12031,6 +12339,89 @@ function ensureStepErrorMessage(name, step) {
12031
12339
  step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
12032
12340
  }
12033
12341
  }
12342
+ /**
12343
+ * Coerce a raw JSON-RPC `code` to a number.
12344
+ *
12345
+ * Some wallet SDKs serialize the JSON-RPC `code` as a string ("4001")
12346
+ * after round-tripping through JSON; accept both shapes so strict `===`
12347
+ * comparisons downstream still classify 5720/5730/5740 correctly — those
12348
+ * codes have no message-pattern fallback.
12349
+ *
12350
+ * @param rawCode - The raw `code` extracted from the error object.
12351
+ * @returns The numeric code, or `undefined` if the value cannot be parsed.
12352
+ *
12353
+ * @internal
12354
+ */
12355
+ function coerceRpcCode(rawCode) {
12356
+ if (typeof rawCode === 'number') {
12357
+ return rawCode;
12358
+ }
12359
+ if (typeof rawCode === 'string') {
12360
+ return Number.parseInt(rawCode, 10);
12361
+ }
12362
+ return undefined;
12363
+ }
12364
+ /**
12365
+ * Classify a pre-submission error thrown during `wallet_sendCalls`.
12366
+ *
12367
+ * Inspect the error's JSON-RPC `code` (falling back to message pattern
12368
+ * matching for wrapper errors like viem's `ChainMismatchError`) and map
12369
+ * it to a {@link BridgeStepErrorCategory}. This lets downstream consumers
12370
+ * distinguish user rejections, wallet capability gaps, and unknown
12371
+ * failures without parsing error messages.
12372
+ *
12373
+ * @remarks
12374
+ * Does NOT alter control flow — the SDK continues to surface a
12375
+ * `state: 'error'` step. Auto-fallback to sequential execution is
12376
+ * intentionally out of scope for this helper.
12377
+ *
12378
+ * @param err - The error thrown by `wallet_sendCalls`.
12379
+ * @returns The derived error category, or `'unknown'` if no match.
12380
+ *
12381
+ * @internal
12382
+ */
12383
+ function classifyPreSubmissionError(err) {
12384
+ // Cross-realm-safe duck typing: `instanceof Error` returns false for
12385
+ // errors thrown in a different JavaScript realm (e.g., a wallet
12386
+ // provider running inside an iframe, which is common with WalletConnect
12387
+ // and the Coinbase Wallet SDK).
12388
+ if (typeof err !== 'object' || err === null || !('message' in err)) {
12389
+ return 'unknown';
12390
+ }
12391
+ const code = coerceRpcCode(err.code);
12392
+ const message = String(err.message);
12393
+ // Numeric JSON-RPC codes are authoritative; check them before falling
12394
+ // back to message-pattern matching. Order matters: an error carrying
12395
+ // `code === 5750` with a message like "user rejected the upgrade"
12396
+ // is a capability problem, not a plain user rejection.
12397
+ if (code === 4001) {
12398
+ return 'user_rejected';
12399
+ }
12400
+ if (code === 5700 || code === 5710 || code === 5750) {
12401
+ return 'atomic_unsupported';
12402
+ }
12403
+ if (code === 5720) {
12404
+ return 'duplicate_batch_id';
12405
+ }
12406
+ if (code === 5730) {
12407
+ return 'unknown_bundle';
12408
+ }
12409
+ if (code === 5740) {
12410
+ return 'batch_too_large';
12411
+ }
12412
+ // Fall back to message patterns when no specific code is available —
12413
+ // viem (and other wrapper layers) sometimes strip the numeric code
12414
+ // while preserving the original wallet message in `Details:`.
12415
+ if (/EIP-7702 not supported/i.test(message) ||
12416
+ /does not support the requested chain/i.test(message) ||
12417
+ /rejected the upgrade/i.test(message)) {
12418
+ return 'atomic_unsupported';
12419
+ }
12420
+ if (/user rejected/i.test(message)) {
12421
+ return 'user_rejected';
12422
+ }
12423
+ return 'unknown';
12424
+ }
12034
12425
  /**
12035
12426
  * Execute a cross-chain USDC bridge using the CCTP v2 protocol.
12036
12427
  *