@circle-fin/provider-cctp-v2 1.6.2 → 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/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";
@@ -335,6 +339,57 @@ var BridgeChain;
335
339
  BridgeChain["XDC_Apothem"] = "XDC_Apothem";
336
340
  })(BridgeChain || (BridgeChain = {}));
337
341
  // -----------------------------------------------------------------------------
342
+ // Unified Balance Chain Enum (Gateway V1 Supported Chains)
343
+ // -----------------------------------------------------------------------------
344
+ /**
345
+ * Enumeration of blockchains that support Gateway V1 operations
346
+ * (deposit, spend, balance, delegate, removeFund).
347
+ *
348
+ * Derived from the full {@link Blockchain} enum but filtered to only
349
+ * include chains with active Gateway V1 contract support. When new chains
350
+ * gain Gateway V1 support, they are added to this enum.
351
+ *
352
+ * @enum
353
+ * @category Enums
354
+ *
355
+ * @remarks
356
+ * - This enum is the **canonical source** of Gateway-supported chains.
357
+ * - Use this enum (or its string literals) in unified-balance-kit calls
358
+ * for type safety.
359
+ *
360
+ * @see {@link Blockchain} for the complete list of all known blockchains.
361
+ * @see {@link UnifiedBalanceChainIdentifier} for the type that accepts these values.
362
+ */
363
+ var UnifiedBalanceChain;
364
+ (function (UnifiedBalanceChain) {
365
+ // Mainnet chains with Gateway V1 support
366
+ UnifiedBalanceChain["Arbitrum"] = "Arbitrum";
367
+ UnifiedBalanceChain["Avalanche"] = "Avalanche";
368
+ UnifiedBalanceChain["Base"] = "Base";
369
+ UnifiedBalanceChain["Ethereum"] = "Ethereum";
370
+ UnifiedBalanceChain["HyperEVM"] = "HyperEVM";
371
+ UnifiedBalanceChain["Optimism"] = "Optimism";
372
+ UnifiedBalanceChain["Polygon"] = "Polygon";
373
+ UnifiedBalanceChain["Sei"] = "Sei";
374
+ UnifiedBalanceChain["Solana"] = "Solana";
375
+ UnifiedBalanceChain["Sonic"] = "Sonic";
376
+ UnifiedBalanceChain["Unichain"] = "Unichain";
377
+ UnifiedBalanceChain["World_Chain"] = "World_Chain";
378
+ // Testnet chains with Gateway V1 support
379
+ UnifiedBalanceChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
380
+ UnifiedBalanceChain["Arc_Testnet"] = "Arc_Testnet";
381
+ UnifiedBalanceChain["Avalanche_Fuji"] = "Avalanche_Fuji";
382
+ UnifiedBalanceChain["Base_Sepolia"] = "Base_Sepolia";
383
+ UnifiedBalanceChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
384
+ UnifiedBalanceChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
385
+ UnifiedBalanceChain["Optimism_Sepolia"] = "Optimism_Sepolia";
386
+ UnifiedBalanceChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
387
+ UnifiedBalanceChain["Sei_Testnet"] = "Sei_Testnet";
388
+ UnifiedBalanceChain["Solana_Devnet"] = "Solana_Devnet";
389
+ UnifiedBalanceChain["Sonic_Testnet"] = "Sonic_Testnet";
390
+ UnifiedBalanceChain["Unichain_Sepolia"] = "Unichain_Sepolia";
391
+ UnifiedBalanceChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
392
+ })(UnifiedBalanceChain || (UnifiedBalanceChain = {}));
338
393
  // Earn Chain Enum
339
394
  // -----------------------------------------------------------------------------
340
395
  /**
@@ -619,6 +674,12 @@ const SWAP_TOKEN_REGISTRY = {
619
674
  category: 'wrapped',
620
675
  description: 'Wrapped Polygon',
621
676
  },
677
+ CIRBTC: {
678
+ symbol: 'CIRBTC',
679
+ decimals: 8,
680
+ category: 'wrapped',
681
+ description: 'Circle Bitcoin',
682
+ },
622
683
  };
623
684
  /**
624
685
  * Special NATIVE token constant for swap operations.
@@ -669,6 +730,62 @@ const ADAPTER_CONTRACT_EVM_MAINNET = '0x7FB8c7260b63934d8da38aF902f87ae6e284a845
669
730
  * integrations (e.g., Arc Testnet).
670
731
  */
671
732
  const ADAPTER_CONTRACT_EVM_TESTNET = '0xBBD70b01a1CAbc96d5b7b129Ae1AAabdf50dd40b';
733
+ /**
734
+ * The GatewayWallet contract address for EVM mainnet networks.
735
+ *
736
+ * This contract manages wallet operations for Gateway transactions
737
+ * on mainnet environments across EVM-compatible chains.
738
+ */
739
+ const GATEWAY_WALLET_EVM_MAINNET = '0x77777777Dcc4d5A8B6E418Fd04D8997ef11000eE';
740
+ /**
741
+ * The GatewayMinter contract address for EVM mainnet networks.
742
+ *
743
+ * This contract handles minting operations for Gateway transactions
744
+ * on mainnet environments across EVM-compatible chains.
745
+ */
746
+ const GATEWAY_MINTER_EVM_MAINNET = '0x2222222d7164433c4C09B0b0D809a9b52C04C205';
747
+ /**
748
+ * The GatewayWallet contract address for EVM testnet networks.
749
+ *
750
+ * This contract manages wallet operations for Gateway transactions
751
+ * on testnet environments across EVM-compatible chains.
752
+ */
753
+ const GATEWAY_WALLET_EVM_TESTNET = '0x0077777d7EBA4688BDeF3E311b846F25870A19B9';
754
+ /**
755
+ * The GatewayMinter contract address for EVM testnet networks.
756
+ *
757
+ * This contract handles minting operations for Gateway transactions
758
+ * on testnet environments across EVM-compatible chains.
759
+ */
760
+ const GATEWAY_MINTER_EVM_TESTNET = '0x0022222ABE238Cc2C7Bb1f21003F0a260052475B';
761
+ /**
762
+ * The GatewayWallet program address for Solana mainnet.
763
+ *
764
+ * This program manages wallet operations for Gateway transactions
765
+ * on Solana mainnet.
766
+ */
767
+ const GATEWAY_WALLET_SOLANA_MAINNET = 'GATEwy4YxeiEbRJLwB6dXgg7q61e6zBPrMzYj5h1pRXQ';
768
+ /**
769
+ * The GatewayMinter program address for Solana mainnet.
770
+ *
771
+ * This program handles minting operations for Gateway transactions
772
+ * on Solana mainnet.
773
+ */
774
+ const GATEWAY_MINTER_SOLANA_MAINNET = 'GATEm5SoBJiSw1v2Pz1iPBgUYkXzCUJ27XSXhDfSyzVZ';
775
+ /**
776
+ * The GatewayWallet program address for Solana devnet.
777
+ *
778
+ * This program manages wallet operations for Gateway transactions
779
+ * on Solana devnet.
780
+ */
781
+ const GATEWAY_WALLET_SOLANA_DEVNET = 'GATEwdfmYNELfp5wDmmR6noSr2vHnAfBPMm2PvCzX5vu';
782
+ /**
783
+ * The GatewayMinter program address for Solana devnet.
784
+ *
785
+ * This program handles minting operations for Gateway transactions
786
+ * on Solana devnet.
787
+ */
788
+ const GATEWAY_MINTER_SOLANA_DEVNET = 'GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr';
672
789
 
673
790
  /**
674
791
  * Arc Testnet chain definition
@@ -719,6 +836,19 @@ const ArcTestnet = defineChain({
719
836
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
720
837
  adapter: ADAPTER_CONTRACT_EVM_TESTNET,
721
838
  },
839
+ gateway: {
840
+ domain: 26,
841
+ contracts: {
842
+ v1: {
843
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
844
+ minter: GATEWAY_MINTER_EVM_TESTNET,
845
+ },
846
+ },
847
+ forwarderSupported: {
848
+ source: true,
849
+ destination: true,
850
+ },
851
+ },
722
852
  });
723
853
 
724
854
  /**
@@ -769,6 +899,19 @@ const Arbitrum = defineChain({
769
899
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
770
900
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
771
901
  },
902
+ gateway: {
903
+ domain: 3,
904
+ contracts: {
905
+ v1: {
906
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
907
+ minter: GATEWAY_MINTER_EVM_MAINNET,
908
+ },
909
+ },
910
+ forwarderSupported: {
911
+ source: true,
912
+ destination: true,
913
+ },
914
+ },
772
915
  });
773
916
 
774
917
  /**
@@ -818,6 +961,19 @@ const ArbitrumSepolia = defineChain({
818
961
  kitContracts: {
819
962
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
820
963
  },
964
+ gateway: {
965
+ domain: 3,
966
+ contracts: {
967
+ v1: {
968
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
969
+ minter: GATEWAY_MINTER_EVM_TESTNET,
970
+ },
971
+ },
972
+ forwarderSupported: {
973
+ source: true,
974
+ destination: true,
975
+ },
976
+ },
821
977
  });
822
978
 
823
979
  /**
@@ -868,6 +1024,19 @@ const Avalanche = defineChain({
868
1024
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
869
1025
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
870
1026
  },
1027
+ gateway: {
1028
+ domain: 1,
1029
+ contracts: {
1030
+ v1: {
1031
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1032
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1033
+ },
1034
+ },
1035
+ forwarderSupported: {
1036
+ source: true,
1037
+ destination: true,
1038
+ },
1039
+ },
871
1040
  });
872
1041
 
873
1042
  /**
@@ -917,6 +1086,19 @@ const AvalancheFuji = defineChain({
917
1086
  kitContracts: {
918
1087
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
919
1088
  },
1089
+ gateway: {
1090
+ domain: 1,
1091
+ contracts: {
1092
+ v1: {
1093
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1094
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1095
+ },
1096
+ },
1097
+ forwarderSupported: {
1098
+ source: true,
1099
+ destination: true,
1100
+ },
1101
+ },
920
1102
  });
921
1103
 
922
1104
  /**
@@ -967,6 +1149,19 @@ const Base = defineChain({
967
1149
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
968
1150
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
969
1151
  },
1152
+ gateway: {
1153
+ domain: 6,
1154
+ contracts: {
1155
+ v1: {
1156
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1157
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1158
+ },
1159
+ },
1160
+ forwarderSupported: {
1161
+ source: true,
1162
+ destination: true,
1163
+ },
1164
+ },
970
1165
  });
971
1166
 
972
1167
  /**
@@ -1016,6 +1211,19 @@ const BaseSepolia = defineChain({
1016
1211
  kitContracts: {
1017
1212
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1018
1213
  },
1214
+ gateway: {
1215
+ domain: 6,
1216
+ contracts: {
1217
+ v1: {
1218
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1219
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1220
+ },
1221
+ },
1222
+ forwarderSupported: {
1223
+ source: true,
1224
+ destination: true,
1225
+ },
1226
+ },
1019
1227
  });
1020
1228
 
1021
1229
  /**
@@ -1260,7 +1468,10 @@ const Ethereum = defineChain({
1260
1468
  chainId: 1,
1261
1469
  isTestnet: false,
1262
1470
  explorerUrl: 'https://etherscan.io/tx/{hash}',
1263
- rpcEndpoints: ['https://eth.merkle.io', 'https://ethereum.publicnode.com'],
1471
+ rpcEndpoints: [
1472
+ 'https://ethereum-rpc.publicnode.com',
1473
+ 'https://ethereum.publicnode.com',
1474
+ ],
1264
1475
  eurcAddress: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
1265
1476
  usdcAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
1266
1477
  usdtAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7',
@@ -1290,6 +1501,19 @@ const Ethereum = defineChain({
1290
1501
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1291
1502
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1292
1503
  },
1504
+ gateway: {
1505
+ domain: 0,
1506
+ contracts: {
1507
+ v1: {
1508
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1509
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1510
+ },
1511
+ },
1512
+ forwarderSupported: {
1513
+ source: true,
1514
+ destination: true,
1515
+ },
1516
+ },
1293
1517
  });
1294
1518
 
1295
1519
  /**
@@ -1310,7 +1534,7 @@ const EthereumSepolia = defineChain({
1310
1534
  chainId: 11155111,
1311
1535
  isTestnet: true,
1312
1536
  explorerUrl: 'https://sepolia.etherscan.io/tx/{hash}',
1313
- rpcEndpoints: ['https://sepolia.drpc.org'],
1537
+ rpcEndpoints: ['https://ethereum-sepolia-rpc.publicnode.com'],
1314
1538
  eurcAddress: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
1315
1539
  usdcAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
1316
1540
  usdtAddress: null,
@@ -1339,6 +1563,19 @@ const EthereumSepolia = defineChain({
1339
1563
  kitContracts: {
1340
1564
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1341
1565
  },
1566
+ gateway: {
1567
+ domain: 0,
1568
+ contracts: {
1569
+ v1: {
1570
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1571
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1572
+ },
1573
+ },
1574
+ forwarderSupported: {
1575
+ source: true,
1576
+ destination: true,
1577
+ },
1578
+ },
1342
1579
  });
1343
1580
 
1344
1581
  /**
@@ -1433,6 +1670,19 @@ const HyperEVM = defineChain({
1433
1670
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1434
1671
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1435
1672
  },
1673
+ gateway: {
1674
+ domain: 19,
1675
+ contracts: {
1676
+ v1: {
1677
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1678
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1679
+ },
1680
+ },
1681
+ forwarderSupported: {
1682
+ source: true,
1683
+ destination: true,
1684
+ },
1685
+ },
1436
1686
  });
1437
1687
 
1438
1688
  /**
@@ -1477,6 +1727,19 @@ const HyperEVMTestnet = defineChain({
1477
1727
  kitContracts: {
1478
1728
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1479
1729
  },
1730
+ gateway: {
1731
+ domain: 19,
1732
+ contracts: {
1733
+ v1: {
1734
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1735
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1736
+ },
1737
+ },
1738
+ forwarderSupported: {
1739
+ source: true,
1740
+ destination: true,
1741
+ },
1742
+ },
1480
1743
  });
1481
1744
 
1482
1745
  /**
@@ -2011,6 +2274,19 @@ const Optimism = defineChain({
2011
2274
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2012
2275
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2013
2276
  },
2277
+ gateway: {
2278
+ domain: 2,
2279
+ contracts: {
2280
+ v1: {
2281
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2282
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2283
+ },
2284
+ },
2285
+ forwarderSupported: {
2286
+ source: true,
2287
+ destination: true,
2288
+ },
2289
+ },
2014
2290
  });
2015
2291
 
2016
2292
  /**
@@ -2060,6 +2336,109 @@ const OptimismSepolia = defineChain({
2060
2336
  kitContracts: {
2061
2337
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2062
2338
  },
2339
+ gateway: {
2340
+ domain: 2,
2341
+ contracts: {
2342
+ v1: {
2343
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2344
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2345
+ },
2346
+ },
2347
+ forwarderSupported: {
2348
+ source: true,
2349
+ destination: true,
2350
+ },
2351
+ },
2352
+ });
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
+ },
2063
2442
  });
2064
2443
 
2065
2444
  /**
@@ -2248,6 +2627,19 @@ const Polygon = defineChain({
2248
2627
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2249
2628
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2250
2629
  },
2630
+ gateway: {
2631
+ domain: 7,
2632
+ contracts: {
2633
+ v1: {
2634
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2635
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2636
+ },
2637
+ },
2638
+ forwarderSupported: {
2639
+ source: true,
2640
+ destination: true,
2641
+ },
2642
+ },
2251
2643
  });
2252
2644
 
2253
2645
  /**
@@ -2297,6 +2689,19 @@ const PolygonAmoy = defineChain({
2297
2689
  kitContracts: {
2298
2690
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2299
2691
  },
2692
+ gateway: {
2693
+ domain: 7,
2694
+ contracts: {
2695
+ v1: {
2696
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2697
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2698
+ },
2699
+ },
2700
+ forwarderSupported: {
2701
+ source: true,
2702
+ destination: true,
2703
+ },
2704
+ },
2300
2705
  });
2301
2706
 
2302
2707
  /**
@@ -2318,7 +2723,7 @@ const Sei = defineChain({
2318
2723
  },
2319
2724
  chainId: 1329,
2320
2725
  isTestnet: false,
2321
- explorerUrl: 'https://seitrace.com/tx/{hash}?chain=pacific-1',
2726
+ explorerUrl: 'https://seiscan.io/tx/{hash}',
2322
2727
  rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
2323
2728
  eurcAddress: null,
2324
2729
  usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
@@ -2343,13 +2748,26 @@ const Sei = defineChain({
2343
2748
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2344
2749
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2345
2750
  },
2346
- });
2347
-
2348
- /**
2349
- * Sei Testnet chain definition
2350
- * @remarks
2351
- * This represents the official testnet for the Sei blockchain.
2352
- * Used for development and testing purposes before deploying to mainnet.
2751
+ gateway: {
2752
+ domain: 16,
2753
+ contracts: {
2754
+ v1: {
2755
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2756
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2757
+ },
2758
+ },
2759
+ forwarderSupported: {
2760
+ source: true,
2761
+ destination: true,
2762
+ },
2763
+ },
2764
+ });
2765
+
2766
+ /**
2767
+ * Sei Testnet chain definition
2768
+ * @remarks
2769
+ * This represents the official testnet for the Sei blockchain.
2770
+ * Used for development and testing purposes before deploying to mainnet.
2353
2771
  */
2354
2772
  const SeiTestnet = defineChain({
2355
2773
  type: 'evm',
@@ -2363,7 +2781,7 @@ const SeiTestnet = defineChain({
2363
2781
  },
2364
2782
  chainId: 1328,
2365
2783
  isTestnet: true,
2366
- explorerUrl: 'https://seitrace.com/tx/{hash}?chain=atlantic-2',
2784
+ explorerUrl: 'https://testnet.seiscan.io/tx/{hash}',
2367
2785
  rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
2368
2786
  eurcAddress: null,
2369
2787
  usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
@@ -2387,6 +2805,19 @@ const SeiTestnet = defineChain({
2387
2805
  kitContracts: {
2388
2806
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2389
2807
  },
2808
+ gateway: {
2809
+ domain: 16,
2810
+ contracts: {
2811
+ v1: {
2812
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2813
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2814
+ },
2815
+ },
2816
+ forwarderSupported: {
2817
+ source: true,
2818
+ destination: true,
2819
+ },
2820
+ },
2390
2821
  });
2391
2822
 
2392
2823
  /**
@@ -2431,6 +2862,19 @@ const Sonic = defineChain({
2431
2862
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2432
2863
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2433
2864
  },
2865
+ gateway: {
2866
+ domain: 13,
2867
+ contracts: {
2868
+ v1: {
2869
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2870
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2871
+ },
2872
+ },
2873
+ forwarderSupported: {
2874
+ source: true,
2875
+ destination: true,
2876
+ },
2877
+ },
2434
2878
  });
2435
2879
 
2436
2880
  /**
@@ -2474,6 +2918,19 @@ const SonicTestnet = defineChain({
2474
2918
  kitContracts: {
2475
2919
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2476
2920
  },
2921
+ gateway: {
2922
+ domain: 13,
2923
+ contracts: {
2924
+ v1: {
2925
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2926
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2927
+ },
2928
+ },
2929
+ forwarderSupported: {
2930
+ source: true,
2931
+ destination: true,
2932
+ },
2933
+ },
2477
2934
  });
2478
2935
 
2479
2936
  /**
@@ -2522,6 +2979,19 @@ const Solana = defineChain({
2522
2979
  kitContracts: {
2523
2980
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
2524
2981
  },
2982
+ gateway: {
2983
+ domain: 5,
2984
+ contracts: {
2985
+ v1: {
2986
+ wallet: GATEWAY_WALLET_SOLANA_MAINNET,
2987
+ minter: GATEWAY_MINTER_SOLANA_MAINNET,
2988
+ },
2989
+ },
2990
+ forwarderSupported: {
2991
+ source: true,
2992
+ destination: false,
2993
+ },
2994
+ },
2525
2995
  });
2526
2996
 
2527
2997
  /**
@@ -2570,6 +3040,19 @@ const SolanaDevnet = defineChain({
2570
3040
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
2571
3041
  },
2572
3042
  rpcEndpoints: ['https://api.devnet.solana.com'],
3043
+ gateway: {
3044
+ domain: 5,
3045
+ contracts: {
3046
+ v1: {
3047
+ wallet: GATEWAY_WALLET_SOLANA_DEVNET,
3048
+ minter: GATEWAY_MINTER_SOLANA_DEVNET,
3049
+ },
3050
+ },
3051
+ forwarderSupported: {
3052
+ source: true,
3053
+ destination: false,
3054
+ },
3055
+ },
2573
3056
  });
2574
3057
 
2575
3058
  /**
@@ -2744,6 +3227,19 @@ const Unichain = defineChain({
2744
3227
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2745
3228
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2746
3229
  },
3230
+ gateway: {
3231
+ domain: 10,
3232
+ contracts: {
3233
+ v1: {
3234
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
3235
+ minter: GATEWAY_MINTER_EVM_MAINNET,
3236
+ },
3237
+ },
3238
+ forwarderSupported: {
3239
+ source: true,
3240
+ destination: true,
3241
+ },
3242
+ },
2747
3243
  });
2748
3244
 
2749
3245
  /**
@@ -2793,6 +3289,19 @@ const UnichainSepolia = defineChain({
2793
3289
  kitContracts: {
2794
3290
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2795
3291
  },
3292
+ gateway: {
3293
+ domain: 10,
3294
+ contracts: {
3295
+ v1: {
3296
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
3297
+ minter: GATEWAY_MINTER_EVM_TESTNET,
3298
+ },
3299
+ },
3300
+ forwarderSupported: {
3301
+ source: true,
3302
+ destination: true,
3303
+ },
3304
+ },
2796
3305
  });
2797
3306
 
2798
3307
  /**
@@ -2837,6 +3346,19 @@ const WorldChain = defineChain({
2837
3346
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2838
3347
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2839
3348
  },
3349
+ gateway: {
3350
+ domain: 14,
3351
+ contracts: {
3352
+ v1: {
3353
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
3354
+ minter: GATEWAY_MINTER_EVM_MAINNET,
3355
+ },
3356
+ },
3357
+ forwarderSupported: {
3358
+ source: true,
3359
+ destination: true,
3360
+ },
3361
+ },
2840
3362
  });
2841
3363
 
2842
3364
  /**
@@ -2883,6 +3405,19 @@ const WorldChainSepolia = defineChain({
2883
3405
  kitContracts: {
2884
3406
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2885
3407
  },
3408
+ gateway: {
3409
+ domain: 14,
3410
+ contracts: {
3411
+ v1: {
3412
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
3413
+ minter: GATEWAY_MINTER_EVM_TESTNET,
3414
+ },
3415
+ },
3416
+ forwarderSupported: {
3417
+ source: true,
3418
+ destination: true,
3419
+ },
3420
+ },
2886
3421
  });
2887
3422
 
2888
3423
  /**
@@ -3063,6 +3598,8 @@ var Chains = {
3063
3598
  NobleTestnet: NobleTestnet,
3064
3599
  Optimism: Optimism,
3065
3600
  OptimismSepolia: OptimismSepolia,
3601
+ Pharos: Pharos,
3602
+ PharosTestnet: PharosTestnet,
3066
3603
  Plume: Plume,
3067
3604
  PlumeTestnet: PlumeTestnet,
3068
3605
  PolkadotAssetHub: PolkadotAssetHub,
@@ -3162,6 +3699,87 @@ function hasCustomContractSupport(chain, contractType) {
3162
3699
  return (typeof contractAddress === 'string' && contractAddress.trim().length > 0);
3163
3700
  }
3164
3701
 
3702
+ /**
3703
+ * Zod schema for validating Gateway v1 contract addresses.
3704
+ *
3705
+ * @example
3706
+ * ```typescript
3707
+ * gatewayV1ContractsSchema.parse({
3708
+ * wallet: '0x1234567890abcdef1234567890abcdef12345678',
3709
+ * minter: '0xabcdef1234567890abcdef1234567890abcdef12'
3710
+ * })
3711
+ * ```
3712
+ */
3713
+ const gatewayV1ContractsSchema = zod.z
3714
+ .object({
3715
+ wallet: zod.z
3716
+ .string({
3717
+ required_error: 'Gateway wallet address is required. Please provide a valid contract address.',
3718
+ invalid_type_error: 'Gateway wallet address must be a string.',
3719
+ })
3720
+ .min(1, 'Gateway wallet address cannot be empty.'),
3721
+ minter: zod.z
3722
+ .string({
3723
+ required_error: 'Gateway minter address is required. Please provide a valid contract address.',
3724
+ invalid_type_error: 'Gateway minter address must be a string.',
3725
+ })
3726
+ .min(1, 'Gateway minter address cannot be empty.'),
3727
+ })
3728
+ .strict(); // Reject any additional properties not defined in the schema
3729
+ /**
3730
+ * Zod schema for validating the versioned Gateway contracts map.
3731
+ *
3732
+ * @description Mirrors the {@link GatewayContracts} type: a partial map of
3733
+ * protocol versions to their contract addresses, following the same pattern
3734
+ * as {@link CCTPContracts}.
3735
+ *
3736
+ * @example
3737
+ * ```typescript
3738
+ * gatewayContractsSchema.parse({
3739
+ * v1: {
3740
+ * wallet: '0x1234567890abcdef1234567890abcdef12345678',
3741
+ * minter: '0xabcdef1234567890abcdef1234567890abcdef12'
3742
+ * }
3743
+ * })
3744
+ * ```
3745
+ */
3746
+ const gatewayContractsSchema = zod.z
3747
+ .object({
3748
+ v1: gatewayV1ContractsSchema.optional(),
3749
+ })
3750
+ .strict(); // Reject any additional properties not defined in the schema
3751
+ /**
3752
+ * Zod schema for validating the full Gateway configuration.
3753
+ *
3754
+ * @description Mirrors the {@link GatewayConfig} type: a domain number plus
3755
+ * a versioned contracts map, following the same pattern as {@link CCTPConfig}.
3756
+ *
3757
+ * @example
3758
+ * ```typescript
3759
+ * gatewayConfigSchema.parse({
3760
+ * domain: 6,
3761
+ * contracts: {
3762
+ * v1: {
3763
+ * wallet: '0x1234567890abcdef1234567890abcdef12345678',
3764
+ * minter: '0xabcdef1234567890abcdef1234567890abcdef12'
3765
+ * }
3766
+ * }
3767
+ * })
3768
+ * ```
3769
+ */
3770
+ const gatewayConfigSchema = zod.z
3771
+ .object({
3772
+ domain: zod.z.number({
3773
+ required_error: 'Gateway domain is required. Please provide a valid domain number.',
3774
+ invalid_type_error: 'Gateway domain must be a number.',
3775
+ }),
3776
+ contracts: gatewayContractsSchema,
3777
+ forwarderSupported: zod.z.object({
3778
+ source: zod.z.boolean(),
3779
+ destination: zod.z.boolean(),
3780
+ }),
3781
+ })
3782
+ .strict(); // Reject any additional properties not defined in the schema
3165
3783
  /**
3166
3784
  * Base schema for common chain definition properties.
3167
3785
  * This contains all properties shared between EVM and non-EVM chains.
@@ -3200,6 +3818,7 @@ const baseChainDefinitionSchema = zod.z.object({
3200
3818
  adapter: zod.z.string().optional(),
3201
3819
  })
3202
3820
  .optional(),
3821
+ gateway: gatewayConfigSchema.optional(),
3203
3822
  });
3204
3823
  /**
3205
3824
  * Zod schema for validating EVM chain definitions specifically.
@@ -3408,6 +4027,42 @@ zod.z.union([
3408
4027
  `Supported chains: ${Object.values(EarnChain).join(', ')}`,
3409
4028
  })),
3410
4029
  ]);
4030
+ /**
4031
+ * Zod schema for validating unified balance chain identifiers.
4032
+ *
4033
+ * This schema validates that the provided chain is supported for unified balance operations.
4034
+ * It accepts either a UnifiedBalanceChain enum value, a string matching a UnifiedBalanceChain value,
4035
+ * or a ChainDefinition for a supported chain.
4036
+ *
4037
+ * Use this schema when validating chain parameters for unified balance operations to ensure
4038
+ * only Gateway V1-supported chains are accepted at runtime.
4039
+ *
4040
+ * @example
4041
+ * ```typescript
4042
+ * import { unifiedBalanceChainIdentifierSchema } from '@core/chains/validation'
4043
+ * import { UnifiedBalanceChain, Chains } from '@core/chains'
4044
+ *
4045
+ * // Valid - UnifiedBalanceChain enum value
4046
+ * unifiedBalanceChainIdentifierSchema.parse(UnifiedBalanceChain.Ethereum)
4047
+ *
4048
+ * // Valid - string literal
4049
+ * unifiedBalanceChainIdentifierSchema.parse('Ethereum')
4050
+ *
4051
+ * // Invalid - Algorand is not in UnifiedBalanceChain (throws ZodError)
4052
+ * unifiedBalanceChainIdentifierSchema.parse('Algorand')
4053
+ * ```
4054
+ *
4055
+ * @see {@link UnifiedBalanceChain} for the enum of supported chains.
4056
+ */
4057
+ const supportedUnifiedBalanceChains = Object.keys(UnifiedBalanceChain).join(', ');
4058
+ zod.z.union([
4059
+ zod.z.string().refine((val) => val in UnifiedBalanceChain, (val) => ({
4060
+ message: `Chain "${val}" is not supported for unified balance operations. Supported chains: ${supportedUnifiedBalanceChains}.`,
4061
+ })),
4062
+ chainDefinitionSchema$2.refine((chainDef) => chainDef.chain in UnifiedBalanceChain, (chainDef) => ({
4063
+ message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for unified balance operations. Supported chains: ${supportedUnifiedBalanceChains}.`,
4064
+ })),
4065
+ ]);
3411
4066
 
3412
4067
  /**
3413
4068
  * @packageDocumentation
@@ -3716,6 +4371,23 @@ class BridgingProvider {
3716
4371
  }
3717
4372
  }
3718
4373
 
4374
+ /**
4375
+ * Check whether the current runtime is Node.js.
4376
+ *
4377
+ * @returns `true` when running in Node.js, `false` otherwise.
4378
+ *
4379
+ * @example
4380
+ * ```typescript
4381
+ * import { isNodeEnvironment } from '@core/utils'
4382
+ *
4383
+ * if (isNodeEnvironment()) {
4384
+ * console.log('Running in Node.js')
4385
+ * }
4386
+ * ```
4387
+ */
4388
+ const isNodeEnvironment = () => typeof process !== 'undefined' &&
4389
+ typeof process.versions === 'object' &&
4390
+ typeof process.versions.node === 'string';
3719
4391
  /**
3720
4392
  * Detect the runtime environment and return a shortened identifier.
3721
4393
  *
@@ -3723,9 +4395,7 @@ class BridgingProvider {
3723
4395
  */
3724
4396
  const getRuntime = () => {
3725
4397
  // Node.js environment
3726
- if (typeof process !== 'undefined' &&
3727
- typeof process.versions === 'object' &&
3728
- typeof process.versions.node === 'string') {
4398
+ if (isNodeEnvironment()) {
3729
4399
  // Shorten to major version only
3730
4400
  const majorVersion = process.versions.node.split('.')[0] ?? 'unknown';
3731
4401
  return `node/${majorVersion}`;
@@ -4434,6 +5104,9 @@ const NetworkError = {
4434
5104
  name: 'NETWORK_CONNECTION_FAILED',
4435
5105
  type: 'NETWORK',
4436
5106
  },
5107
+ /** Network request timeout */
5108
+ TIMEOUT: {
5109
+ code: 3002},
4437
5110
  /** Circle relayer failed to process the forwarding/mint transaction */
4438
5111
  RELAYER_FORWARD_FAILED: {
4439
5112
  code: 3003,
@@ -4714,6 +5387,7 @@ function createInsufficientTokenBalanceError(chain, token, trace) {
4714
5387
  * as it requires user intervention to add gas funds.
4715
5388
  *
4716
5389
  * @param chain - The blockchain network where the gas check failed
5390
+ * @param nativeToken - Native token symbol (e.g. 'ETH', 'SOL', 'AVAX') for a specific message, defaults to 'native token'
4717
5391
  * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
4718
5392
  * @returns KitError with insufficient gas details
4719
5393
  *
@@ -4722,25 +5396,25 @@ function createInsufficientTokenBalanceError(chain, token, trace) {
4722
5396
  * import { createInsufficientGasError } from '@core/errors'
4723
5397
  *
4724
5398
  * throw createInsufficientGasError('Ethereum')
4725
- * // Message: "Insufficient gas funds on Ethereum"
5399
+ * // Message: "Insufficient native token on Ethereum to cover gas fees"
5400
+ *
5401
+ * throw createInsufficientGasError('Ethereum', 'ETH')
5402
+ * // Message: "Insufficient ETH on Ethereum to cover gas fees"
4726
5403
  * ```
4727
5404
  *
4728
5405
  * @example
4729
5406
  * ```typescript
4730
- * // With trace context for debugging
4731
- * throw createInsufficientGasError('Ethereum', {
5407
+ * throw createInsufficientGasError('Ethereum', 'ETH', {
4732
5408
  * rawError: error,
4733
- * gasRequired: '21000',
4734
- * gasAvailable: '10000',
4735
5409
  * walletAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
4736
5410
  * })
4737
5411
  * ```
4738
5412
  */
4739
- function createInsufficientGasError(chain, trace) {
5413
+ function createInsufficientGasError(chain, nativeToken = 'native token', trace) {
4740
5414
  return new KitError({
4741
5415
  ...BalanceError.INSUFFICIENT_GAS,
4742
5416
  recoverability: 'FATAL',
4743
- message: `Insufficient gas funds on ${chain}`,
5417
+ message: `Insufficient ${nativeToken} on ${chain} to cover gas fees`,
4744
5418
  cause: {
4745
5419
  trace: {
4746
5420
  ...trace,
@@ -5106,7 +5780,7 @@ const SLIPPAGE_REVERT_PATTERN = /slippage|lower than the minimum required|less t
5106
5780
  *
5107
5781
  * @internal
5108
5782
  */
5109
- function handleRevertError(msg, error, context) {
5783
+ function parseRevertError(msg, error, context) {
5110
5784
  const reason = extractRevertReason(msg, error) ?? 'Transaction reverted';
5111
5785
  if (SLIPPAGE_REVERT_PATTERN.test(reason)) {
5112
5786
  return new KitError({
@@ -5195,17 +5869,34 @@ function handleRevertError(msg, error, context) {
5195
5869
  function parseBlockchainError(error, context) {
5196
5870
  const msg = extractMessage(error);
5197
5871
  const token = context.token ?? 'token';
5198
- // Pattern 1: Insufficient balance errors
5872
+ // Pattern 0: Insufficient native gas token errors
5873
+ // Must run BEFORE the generic balance pattern because RPC messages like
5874
+ // "insufficient funds for gas * price + value" and "insufficient funds
5875
+ // for intrinsic transaction cost" contain "insufficient funds" which
5876
+ // would otherwise match the token balance pattern below.
5877
+ if (/insufficient funds for (gas|intrinsic transaction cost)|sender doesn't have enough funds to send tx/i.test(msg)) {
5878
+ return createInsufficientGasError(context.chain, undefined, {
5879
+ rawError: error,
5880
+ });
5881
+ }
5882
+ // Pattern 1: Insufficient token balance errors
5199
5883
  // Matches balance-related errors from ERC20 contracts, native transfers, and Solana programs
5200
5884
  if (/transfer amount exceeds balance|insufficient (balance|funds)|burn amount exceeded/i.test(msg)) {
5201
5885
  return createInsufficientTokenBalanceError(context.chain, token, {
5202
5886
  rawError: error,
5203
5887
  });
5204
5888
  }
5889
+ // Pattern 1b: Gateway-specific contract reverts
5890
+ // Matched before the generic simulation/revert pattern so the error
5891
+ // messages are actionable rather than opaque hex selectors.
5892
+ const gatewayError = parseGatewayContractError(msg, context, error);
5893
+ if (gatewayError !== null) {
5894
+ return gatewayError;
5895
+ }
5205
5896
  // Pattern 2: Simulation and execution reverts
5206
5897
  // Matches contract revert errors and simulation failures
5207
5898
  if (/execution reverted|simulation failed|transaction reverted|transaction failed/i.test(msg)) {
5208
- return handleRevertError(msg, error, context);
5899
+ return parseRevertError(msg, error, context);
5209
5900
  }
5210
5901
  // Pattern 3: Gas-related errors
5211
5902
  // Matches gas estimation failures and gas exhaustion
@@ -5220,10 +5911,6 @@ function parseBlockchainError(error, context) {
5220
5911
  const { txHash, explorerUrl } = normalizeTransactionDetails(context.txHash, context.explorerUrl);
5221
5912
  return createOutOfGasError(context.chain, { rawError: error }, txHash, explorerUrl);
5222
5913
  }
5223
- // Insufficient funds for gas
5224
- if (/insufficient funds for gas/i.test(msg)) {
5225
- return createInsufficientGasError(context.chain, { rawError: error });
5226
- }
5227
5914
  // Pattern 4: Network connectivity errors
5228
5915
  // Matches connection failures, DNS errors, and timeouts
5229
5916
  if (/connection (refused|failed)|network|timeout|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
@@ -5475,6 +6162,59 @@ function extractRevertReason(msg, error) {
5475
6162
  }
5476
6163
  return null;
5477
6164
  }
6165
+ /**
6166
+ * Parses Gateway smart-contract revert selectors into specific KitError types.
6167
+ *
6168
+ * Returns `null` when the message does not match any known Gateway revert,
6169
+ * allowing the caller to fall through to generic patterns.
6170
+ *
6171
+ * @param msg - The extracted error message string.
6172
+ * @param context - Parse error context (chain, token, operation).
6173
+ * @param error - The original raw error for the trace payload.
6174
+ * @returns A KitError if a Gateway pattern matched, otherwise `null`.
6175
+ *
6176
+ * @internal Called by {@link parseBlockchainError} — not re-exported from
6177
+ * the `@core/errors` barrel.
6178
+ *
6179
+ * @example
6180
+ * ```typescript
6181
+ * // Internal usage within parseBlockchainError:
6182
+ * import { parseGatewayContractError } from './parseBlockchainError'
6183
+ *
6184
+ * const gatewayError = parseGatewayContractError(
6185
+ * 'execution reverted: WithdrawalValueExceedsAvailableBalance',
6186
+ * { chain: 'Ethereum', token: 'USDC', operation: 'withdraw' },
6187
+ * new Error('execution reverted'),
6188
+ * )
6189
+ * if (gatewayError !== null) {
6190
+ * // KitError with INSUFFICIENT_TOKEN balance details
6191
+ * console.log(gatewayError.message)
6192
+ * }
6193
+ *
6194
+ * // Returns null for unrecognised reverts (caller falls through to generic parsing)
6195
+ * const unknown = parseGatewayContractError(
6196
+ * 'execution reverted: SomeUnknownError()',
6197
+ * { chain: 'Base', token: 'USDC', operation: 'deposit' },
6198
+ * new Error('execution reverted'),
6199
+ * )
6200
+ * console.log(unknown) // null
6201
+ * ```
6202
+ */
6203
+ function parseGatewayContractError(msg, context, error) {
6204
+ const token = context.token ?? 'token';
6205
+ if (/WithdrawalValueExceedsAvailableBalance|InsufficientDepositBalance/i.test(msg)) {
6206
+ return createInsufficientTokenBalanceError(context.chain, token, {
6207
+ rawError: error,
6208
+ });
6209
+ }
6210
+ if (/WithdrawalNotYetAvailable|WithdrawalDelayNotElapsed/i.test(msg)) {
6211
+ return createTransactionRevertedError(context.chain, 'Withdrawal is not yet available — the withdrawal delay has not elapsed', { rawError: error });
6212
+ }
6213
+ if (/NoWithdrawingBalance/i.test(msg)) {
6214
+ return createTransactionRevertedError(context.chain, 'No pending withdrawal balance — call initiateWithdrawal() first', { rawError: error });
6215
+ }
6216
+ return null;
6217
+ }
5478
6218
 
5479
6219
  /**
5480
6220
  * Type guard to check if an error is a KitError instance.
@@ -6089,12 +6829,12 @@ function validate(value, schema, context) {
6089
6829
  }
6090
6830
 
6091
6831
  /**
6092
- * Symbol used to track validation state on objects.
6093
- * This allows us to attach metadata to objects without interfering with their structure,
6094
- * enabling optimized validation by skipping already validated objects.
6832
+ * Module-level WeakMap for tracking which (schema, validator) combinations
6833
+ * have already processed a given object. This avoids mutating user-supplied
6834
+ * input objects while ensuring re-validation occurs when schemas change.
6095
6835
  * @internal
6096
6836
  */
6097
- const VALIDATION_STATE = Symbol('validationState');
6837
+ const validationStateMap = new WeakMap();
6098
6838
  /**
6099
6839
  * Validates data against a Zod schema with state tracking and enhanced error reporting.
6100
6840
  *
@@ -6111,11 +6851,12 @@ const VALIDATION_STATE = Symbol('validationState');
6111
6851
  *
6112
6852
  * @example
6113
6853
  * ```typescript
6114
- * const result = validateWithStateTracking(BridgeParamsSchema, params, 'bridge parameters')
6854
+ * const VALIDATOR = Symbol('bridgeValidator')
6855
+ * validateWithStateTracking(params, BridgeParamsSchema, 'bridge parameters', VALIDATOR)
6856
+ * // params is now narrowed to the schema's output type
6115
6857
  * ```
6116
6858
  */
6117
6859
  function validateWithStateTracking(value, schema, context, validatorName) {
6118
- // Skip validation for null or undefined values
6119
6860
  if (value === null) {
6120
6861
  throw new KitError({
6121
6862
  ...InputError.VALIDATION_FAILED,
@@ -6140,7 +6881,6 @@ function validateWithStateTracking(value, schema, context, validatorName) {
6140
6881
  },
6141
6882
  });
6142
6883
  }
6143
- // Ensure value is an object that can hold validation state
6144
6884
  if (typeof value !== 'object') {
6145
6885
  throw new KitError({
6146
6886
  ...InputError.VALIDATION_FAILED,
@@ -6153,18 +6893,28 @@ function validateWithStateTracking(value, schema, context, validatorName) {
6153
6893
  },
6154
6894
  });
6155
6895
  }
6156
- // Get or initialize validation state
6157
- const valueWithState = value;
6158
- const state = valueWithState[VALIDATION_STATE] ?? { validatedBy: [] };
6159
- // Skip validation if already validated by this validator
6160
- if (state.validatedBy.includes(validatorName)) {
6896
+ const state = validationStateMap.get(value) ?? {
6897
+ validatedSchemasByValidator: new Map(),
6898
+ };
6899
+ // Get the set of schemas that have already validated this object
6900
+ // for the given validator. Create a new WeakSet if this is first time.
6901
+ const validatedSchemas = state.validatedSchemasByValidator.get(validatorName) ?? new WeakSet();
6902
+ if (!(validatedSchemas instanceof WeakSet)) {
6903
+ throw new KitError({
6904
+ ...InputError.VALIDATION_FAILED,
6905
+ recoverability: 'FATAL',
6906
+ message: 'Invalid validation state: expected WeakSet',
6907
+ });
6908
+ }
6909
+ // Check if this exact schema instance has already validated this object
6910
+ if (validatedSchemas.has(schema)) {
6161
6911
  return;
6162
6912
  }
6163
- // Delegate to the validate function for actual validation (now throws KitError)
6164
6913
  validate(value, schema, context);
6165
- // Update validation state
6166
- state.validatedBy.push(validatorName);
6167
- valueWithState[VALIDATION_STATE] = state;
6914
+ // Record that this schema has validated the object for this validator
6915
+ validatedSchemas.add(schema);
6916
+ state.validatedSchemasByValidator.set(validatorName, validatedSchemas);
6917
+ validationStateMap.set(value, state);
6168
6918
  }
6169
6919
 
6170
6920
  /**
@@ -6540,6 +7290,7 @@ const USDC = {
6540
7290
  [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
6541
7291
  [Blockchain.Noble]: 'uusdc',
6542
7292
  [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
7293
+ [Blockchain.Pharos]: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
6543
7294
  [Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
6544
7295
  [Blockchain.Polkadot_Asset_Hub]: '1337',
6545
7296
  [Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
@@ -6571,6 +7322,7 @@ const USDC = {
6571
7322
  [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
6572
7323
  [Blockchain.Noble_Testnet]: 'uusdc',
6573
7324
  [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
7325
+ [Blockchain.Pharos_Testnet]: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
6574
7326
  [Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
6575
7327
  [Blockchain.Polkadot_Westmint]: '31337',
6576
7328
  [Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
@@ -6851,6 +7603,32 @@ const MON = {
6851
7603
  },
6852
7604
  };
6853
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
+
6854
7632
  // Re-export for consumers
6855
7633
  /**
6856
7634
  * All default token definitions.
@@ -6859,7 +7637,7 @@ const MON = {
6859
7637
  * These tokens are automatically included in the TokenRegistry when created
6860
7638
  * without explicit defaults. Extensions can override these definitions.
6861
7639
  * Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
6862
- * WPOL, ETH, POL, PLUME, and MON.
7640
+ * WPOL, ETH, POL, PLUME, MON, and cirBTC.
6863
7641
  *
6864
7642
  * @example
6865
7643
  * ```typescript
@@ -6890,6 +7668,7 @@ const DEFAULT_TOKENS = [
6890
7668
  POL,
6891
7669
  PLUME,
6892
7670
  MON,
7671
+ CIRBTC,
6893
7672
  ];
6894
7673
  /**
6895
7674
  * Uppercased token symbols approved for swap fee collection.
@@ -9013,34 +9792,633 @@ async function assertCCTPv2AttestationParams(attestation, params) {
9013
9792
  }
9014
9793
 
9015
9794
  /**
9016
- * Executes a prepared chain request and returns the result as a bridge step.
9017
- *
9018
- * This function takes a prepared chain request (containing transaction data) and executes
9019
- * it using the appropriate adapter. It handles the execution details and formats
9020
- * the result as a standardized bridge step with transaction details and explorer URLs.
9021
- *
9022
- * @param params - The execution parameters containing:
9023
- * - `name`: The name of the step
9024
- * - `request`: The prepared chain request containing transaction data
9025
- * - `adapter`: The adapter that will execute the transaction
9026
- * - `confirmations`: The number of confirmations to wait for (defaults to 1)
9027
- * - `timeout`: The timeout for the request in milliseconds
9028
- * @returns The bridge step with the transaction details and explorer URL
9029
- * @throws If the transaction execution fails
9795
+ * CCTP bridge step names that can occur in the bridging flow.
9030
9796
  *
9031
- * @example
9032
- * ```typescript
9033
- * const step = await executePreparedChainRequest({
9034
- * name: 'approve',
9035
- * request: preparedRequest,
9036
- * adapter: adapter,
9037
- * confirmations: 2,
9038
- * timeout: 30000
9039
- * })
9040
- * console.log('Transaction hash:', step.txHash)
9041
- * ```
9797
+ * This object provides type safety for step names and represents all possible
9798
+ * steps that can be executed during a CCTP bridge operation. Using const assertions
9799
+ * makes this tree-shakable and follows modern TypeScript best practices.
9042
9800
  */
9043
- async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, }) {
9801
+ const CCTPv2StepName = {
9802
+ approve: 'approve',
9803
+ burn: 'burn',
9804
+ fetchAttestation: 'fetchAttestation',
9805
+ mint: 'mint',
9806
+ reAttest: 'reAttest',
9807
+ };
9808
+ /**
9809
+ * Conditional step transition rules for CCTP bridge flow.
9810
+ *
9811
+ * Rules are evaluated in order - the first matching condition determines the next step.
9812
+ * This approach supports flexible flow logic and makes it easy to extend with new patterns.
9813
+ */
9814
+ const STEP_TRANSITION_RULES = {
9815
+ // Starting state - no steps executed yet
9816
+ '': [
9817
+ {
9818
+ condition: () => true,
9819
+ nextStep: CCTPv2StepName.approve,
9820
+ reason: 'Start with approval step',
9821
+ isActionable: true,
9822
+ },
9823
+ ],
9824
+ // After Approve step
9825
+ [CCTPv2StepName.approve]: [
9826
+ {
9827
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9828
+ nextStep: CCTPv2StepName.burn,
9829
+ reason: 'Approval successful, proceed to burn',
9830
+ isActionable: true,
9831
+ },
9832
+ {
9833
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9834
+ nextStep: CCTPv2StepName.approve,
9835
+ reason: 'Retry failed approval',
9836
+ isActionable: true,
9837
+ },
9838
+ {
9839
+ condition: (ctx) => ctx.lastStep?.state === 'noop',
9840
+ nextStep: CCTPv2StepName.burn,
9841
+ reason: 'No approval needed, proceed to burn',
9842
+ isActionable: true,
9843
+ },
9844
+ {
9845
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9846
+ nextStep: CCTPv2StepName.approve,
9847
+ reason: 'Continue pending approval',
9848
+ isActionable: false, // Waiting for pending transaction
9849
+ },
9850
+ ],
9851
+ // After Burn step
9852
+ [CCTPv2StepName.burn]: [
9853
+ {
9854
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9855
+ nextStep: CCTPv2StepName.fetchAttestation,
9856
+ reason: 'Burn successful, fetch attestation',
9857
+ isActionable: true,
9858
+ },
9859
+ {
9860
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9861
+ nextStep: CCTPv2StepName.burn,
9862
+ reason: 'Retry failed burn',
9863
+ isActionable: true,
9864
+ },
9865
+ {
9866
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9867
+ nextStep: CCTPv2StepName.burn,
9868
+ reason: 'Continue pending burn',
9869
+ isActionable: false, // Waiting for pending transaction
9870
+ },
9871
+ ],
9872
+ // After FetchAttestation step
9873
+ [CCTPv2StepName.fetchAttestation]: [
9874
+ {
9875
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9876
+ nextStep: CCTPv2StepName.mint,
9877
+ reason: 'Attestation fetched, proceed to mint',
9878
+ isActionable: true,
9879
+ },
9880
+ {
9881
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9882
+ nextStep: CCTPv2StepName.fetchAttestation,
9883
+ reason: 'Retry fetching attestation',
9884
+ isActionable: true,
9885
+ },
9886
+ {
9887
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9888
+ nextStep: CCTPv2StepName.fetchAttestation,
9889
+ reason: 'Continue pending attestation fetch',
9890
+ isActionable: false, // Waiting for attestation to be ready
9891
+ },
9892
+ ],
9893
+ // After Mint step
9894
+ [CCTPv2StepName.mint]: [
9895
+ {
9896
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9897
+ nextStep: null,
9898
+ reason: 'Bridge completed successfully',
9899
+ isActionable: false, // Nothing more to do
9900
+ },
9901
+ {
9902
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9903
+ nextStep: CCTPv2StepName.mint,
9904
+ reason: 'Retry failed mint',
9905
+ isActionable: true,
9906
+ },
9907
+ {
9908
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9909
+ nextStep: CCTPv2StepName.mint,
9910
+ reason: 'Continue pending mint',
9911
+ isActionable: false, // Waiting for pending transaction
9912
+ },
9913
+ ],
9914
+ // After ReAttest step
9915
+ [CCTPv2StepName.reAttest]: [
9916
+ {
9917
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9918
+ nextStep: CCTPv2StepName.mint,
9919
+ reason: 'Re-attestation successful, proceed to mint',
9920
+ isActionable: true,
9921
+ },
9922
+ {
9923
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9924
+ nextStep: CCTPv2StepName.mint,
9925
+ reason: 'Re-attestation failed, retry mint to re-initiate recovery',
9926
+ isActionable: true,
9927
+ },
9928
+ {
9929
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9930
+ nextStep: CCTPv2StepName.mint,
9931
+ reason: 'Re-attestation pending, retry mint to re-initiate recovery',
9932
+ isActionable: true,
9933
+ },
9934
+ ],
9935
+ };
9936
+ /**
9937
+ * Analyze bridge steps to determine retry feasibility and continuation point.
9938
+ *
9939
+ * This function examines the current state of bridge steps to determine the optimal
9940
+ * continuation strategy. It uses a rule-based approach that makes it easy to extend
9941
+ * with new flow patterns and step types in the future.
9942
+ *
9943
+ * The current analysis supports the standard CCTP flow:
9944
+ * **Traditional flow**: Approve → Burn → FetchAttestation → Mint
9945
+ *
9946
+ * Key features:
9947
+ * - Rule-based transitions: Easy to extend with new step types and logic
9948
+ * - Context-aware decisions: Considers execution history and step states
9949
+ * - Actionable logic: Distinguishes between steps requiring user action vs waiting
9950
+ * - Terminal states: Properly handles completion and non-actionable states
9951
+ *
9952
+ * @param bridgeResult - The bridge result containing step execution history.
9953
+ * @returns Analysis result with continuation step and actionability information.
9954
+ * @throws Error when bridgeResult is invalid or contains no steps array.
9955
+ *
9956
+ * @example
9957
+ * ```typescript
9958
+ * import { analyzeSteps } from './analyzeSteps'
9959
+ *
9960
+ * // Failed approval step (requires user action)
9961
+ * const bridgeResult = {
9962
+ * steps: [
9963
+ * { name: 'Approve', state: 'error', errorMessage: 'User rejected' }
9964
+ * ]
9965
+ * }
9966
+ *
9967
+ * const analysis = analyzeSteps(bridgeResult)
9968
+ * // Result: { continuationStep: 'Approve', isRetryable: true,
9969
+ * // reason: 'Retry failed approval' }
9970
+ * ```
9971
+ *
9972
+ * @example
9973
+ * ```typescript
9974
+ * // Pending transaction (requires waiting, not actionable)
9975
+ * const bridgeResult = {
9976
+ * steps: [
9977
+ * { name: 'Approve', state: 'pending' }
9978
+ * ]
9979
+ * }
9980
+ *
9981
+ * const analysis = analyzeSteps(bridgeResult)
9982
+ * // Result: { continuationStep: 'Approve', isRetryable: false,
9983
+ * // reason: 'Continue pending approval' }
9984
+ * ```
9985
+ *
9986
+ * @example
9987
+ * ```typescript
9988
+ * // Completed bridge (nothing to do)
9989
+ * const bridgeResult = {
9990
+ * steps: [
9991
+ * { name: 'Approve', state: 'success' },
9992
+ * { name: 'Burn', state: 'success' },
9993
+ * { name: 'FetchAttestation', state: 'success' },
9994
+ * { name: 'Mint', state: 'success' }
9995
+ * ]
9996
+ * }
9997
+ *
9998
+ * const analysis = analyzeSteps(bridgeResult)
9999
+ * // Result: { continuationStep: null, isRetryable: false,
10000
+ * // reason: 'Bridge completed successfully' }
10001
+ * ```
10002
+ */
10003
+ const analyzeSteps = (bridgeResult) => {
10004
+ // Input validation
10005
+ if (!bridgeResult || !Array.isArray(bridgeResult.steps)) {
10006
+ throw new Error('Invalid bridgeResult: must contain a steps array');
10007
+ }
10008
+ const { steps } = bridgeResult;
10009
+ // Build execution context from step history
10010
+ const context = buildFlowContext(steps);
10011
+ // Determine continuation logic using rule engine
10012
+ const continuation = determineContinuationFromRules(context);
10013
+ return {
10014
+ continuationStep: continuation.nextStep,
10015
+ isActionable: continuation.isActionable,
10016
+ completedSteps: Array.from(context.completedSteps),
10017
+ failedSteps: Array.from(context.failedSteps),
10018
+ reason: continuation.reason,
10019
+ };
10020
+ };
10021
+ /**
10022
+ * Build flow context from the execution history.
10023
+ *
10024
+ * @param steps - Array of executed bridge steps.
10025
+ * @returns Flow context with execution state and history.
10026
+ */
10027
+ function buildFlowContext(steps) {
10028
+ const completedSteps = new Set();
10029
+ const failedSteps = new Set();
10030
+ let lastStep;
10031
+ // Process step history to build context
10032
+ for (const step of steps) {
10033
+ if (step.state === 'success' || step.state === 'noop') {
10034
+ completedSteps.add(step.name);
10035
+ }
10036
+ else if (step.state === 'error') {
10037
+ failedSteps.add(step.name);
10038
+ }
10039
+ // Track the last step for continuation logic
10040
+ lastStep = {
10041
+ name: step.name,
10042
+ state: step.state,
10043
+ };
10044
+ }
10045
+ return {
10046
+ completedSteps,
10047
+ failedSteps,
10048
+ ...(lastStep && { lastStep }),
10049
+ };
10050
+ }
10051
+ /**
10052
+ * Determine continuation step using the rule engine.
10053
+ *
10054
+ * @param context - The flow context with execution history.
10055
+ * @returns Continuation decision with next step and actionability information.
10056
+ */
10057
+ function determineContinuationFromRules(context) {
10058
+ const lastStepName = context.lastStep?.name;
10059
+ // Handle initial state when no steps have been executed
10060
+ if (lastStepName === undefined) {
10061
+ const rules = STEP_TRANSITION_RULES[''];
10062
+ const matchingRule = rules?.find((rule) => rule.condition(context));
10063
+ if (!matchingRule) {
10064
+ return {
10065
+ nextStep: null,
10066
+ isActionable: false,
10067
+ reason: 'No initial state rule found',
10068
+ };
10069
+ }
10070
+ return {
10071
+ nextStep: matchingRule.nextStep,
10072
+ isActionable: matchingRule.isActionable,
10073
+ reason: matchingRule.reason,
10074
+ };
10075
+ }
10076
+ // A step with an empty name is ambiguous and should be treated as an unrecoverable state.
10077
+ if (lastStepName === '') {
10078
+ return {
10079
+ nextStep: null,
10080
+ isActionable: false,
10081
+ reason: 'No transition rules defined for step with empty name',
10082
+ };
10083
+ }
10084
+ const rules = STEP_TRANSITION_RULES[lastStepName];
10085
+ if (!rules) {
10086
+ return {
10087
+ nextStep: null,
10088
+ isActionable: false,
10089
+ reason: `No transition rules defined for step: ${lastStepName}`,
10090
+ };
10091
+ }
10092
+ // Find the first matching rule
10093
+ const matchingRule = rules.find((rule) => rule.condition(context));
10094
+ if (!matchingRule) {
10095
+ return {
10096
+ nextStep: null,
10097
+ isActionable: false,
10098
+ reason: `No matching transition rule for current context`,
10099
+ };
10100
+ }
10101
+ return {
10102
+ nextStep: matchingRule.nextStep,
10103
+ isActionable: matchingRule.isActionable,
10104
+ reason: matchingRule.reason,
10105
+ };
10106
+ }
10107
+
10108
+ /**
10109
+ * Find a step by name in the bridge result.
10110
+ *
10111
+ * @param result - The bridge result to search.
10112
+ * @param stepName - The name of the step to find.
10113
+ * @returns The step if found, undefined otherwise.
10114
+ *
10115
+ * @example
10116
+ * ```typescript
10117
+ * import { findStepByName } from './findStep'
10118
+ *
10119
+ * const burnStep = findStepByName(result, 'burn')
10120
+ * if (burnStep) {
10121
+ * console.log('Burn tx:', burnStep.txHash)
10122
+ * }
10123
+ * ```
10124
+ */
10125
+ function findStepByName(result, stepName) {
10126
+ return result.steps.find((step) => step.name === stepName);
10127
+ }
10128
+ /**
10129
+ * Find a pending step by name and return it with its index.
10130
+ *
10131
+ * Searches for a step that matches both the step name and has a pending state.
10132
+ *
10133
+ * @param result - The bridge result containing steps to search through.
10134
+ * @param stepName - The step name to find (e.g., 'burn', 'mint', 'fetchAttestation').
10135
+ * @returns An object containing the step and its index in the steps array.
10136
+ * @throws KitError if the specified pending step is not found.
10137
+ *
10138
+ * @example
10139
+ * ```typescript
10140
+ * import { findPendingStep } from './findStep'
10141
+ *
10142
+ * const { step, index } = findPendingStep(result, 'burn')
10143
+ * console.log('Pending step:', step.name, 'at index:', index)
10144
+ * ```
10145
+ */
10146
+ function findPendingStep(result, stepName) {
10147
+ const index = result.steps.findIndex((step) => step.name === stepName && step.state === 'pending');
10148
+ if (index === -1) {
10149
+ throw new KitError({
10150
+ ...InputError.VALIDATION_FAILED,
10151
+ recoverability: 'FATAL',
10152
+ message: `Pending step "${stepName}" not found in result`,
10153
+ });
10154
+ }
10155
+ const step = result.steps[index];
10156
+ if (!step) {
10157
+ throw new KitError({
10158
+ ...InputError.VALIDATION_FAILED,
10159
+ recoverability: 'FATAL',
10160
+ message: 'Pending step is undefined',
10161
+ });
10162
+ }
10163
+ return { step, index };
10164
+ }
10165
+ /**
10166
+ * Get the burn transaction hash from bridge result.
10167
+ *
10168
+ * @param result - The bridge result.
10169
+ * @returns The burn transaction hash, or undefined if not found.
10170
+ *
10171
+ * @example
10172
+ * ```typescript
10173
+ * import { getBurnTxHash } from './findStep'
10174
+ *
10175
+ * const burnTxHash = getBurnTxHash(result)
10176
+ * if (burnTxHash) {
10177
+ * console.log('Burn tx hash:', burnTxHash)
10178
+ * }
10179
+ * ```
10180
+ */
10181
+ function getBurnTxHash(result) {
10182
+ return findStepByName(result, CCTPv2StepName.burn)?.txHash;
10183
+ }
10184
+ /**
10185
+ * Get the attestation data from bridge result.
10186
+ *
10187
+ * @param result - The bridge result.
10188
+ * @returns The attestation data, or undefined if not found.
10189
+ *
10190
+ * @example
10191
+ * ```typescript
10192
+ * import { getAttestationData } from './findStep'
10193
+ *
10194
+ * const attestation = getAttestationData(result)
10195
+ * if (attestation) {
10196
+ * console.log('Attestation:', attestation.message)
10197
+ * }
10198
+ * ```
10199
+ */
10200
+ function getAttestationData(result) {
10201
+ // Prefer reAttest data (most recent attestation after expiry)
10202
+ const reAttestStep = findStepByName(result, CCTPv2StepName.reAttest);
10203
+ if (reAttestStep?.state === 'success' && reAttestStep.data) {
10204
+ return reAttestStep.data;
10205
+ }
10206
+ // Fall back to fetchAttestation step
10207
+ const fetchStep = findStepByName(result, CCTPv2StepName.fetchAttestation);
10208
+ return fetchStep?.data;
10209
+ }
10210
+
10211
+ /**
10212
+ * Check if the analysis indicates a non-actionable pending state.
10213
+ *
10214
+ * A pending state is non-actionable when there's a continuation step but
10215
+ * the analysis marks it as not actionable, typically because we need to
10216
+ * wait for an ongoing operation to complete.
10217
+ *
10218
+ * @param analysis - The step analysis result from analyzeSteps.
10219
+ * @param result - The bridge result to check for pending steps.
10220
+ * @returns True if there is a pending step that we should wait for.
10221
+ *
10222
+ * @example
10223
+ * ```typescript
10224
+ * import { hasPendingState } from './stepUtils'
10225
+ * import { analyzeSteps } from '../analyzeSteps'
10226
+ *
10227
+ * const analysis = analyzeSteps(bridgeResult)
10228
+ * if (hasPendingState(analysis, bridgeResult)) {
10229
+ * // Wait for the pending operation to complete
10230
+ * }
10231
+ * ```
10232
+ */
10233
+ /**
10234
+ * Evaluate a transaction receipt and return the corresponding step state
10235
+ * and error message. Centralises the success/revert/unconfirmed logic so
10236
+ * every call-site behaves identically.
10237
+ *
10238
+ * @param receipt - The transaction receipt containing status and block info.
10239
+ * @param txHash - The transaction hash used in error messages.
10240
+ * @returns An object with `state` and an optional `errorMessage`.
10241
+ *
10242
+ * @example
10243
+ * ```typescript
10244
+ * const outcome = evaluateTransactionOutcome(receipt, '0xabc...')
10245
+ * step.state = outcome.state
10246
+ * if (outcome.errorMessage) step.errorMessage = outcome.errorMessage
10247
+ * ```
10248
+ */
10249
+ function evaluateTransactionOutcome(receipt, txHash) {
10250
+ if (receipt.status === 'success' && receipt.blockNumber) {
10251
+ return { state: 'success' };
10252
+ }
10253
+ return {
10254
+ state: 'error',
10255
+ errorMessage: receipt.status === 'reverted'
10256
+ ? `Transaction ${txHash} was reverted`
10257
+ : 'Transaction was not confirmed on-chain',
10258
+ };
10259
+ }
10260
+ function hasPendingState(analysis, result) {
10261
+ // Check if there's a continuation step that's marked as non-actionable
10262
+ if (analysis.continuationStep === null || analysis.isActionable) {
10263
+ return false;
10264
+ }
10265
+ // Verify that the continuation step actually exists and is in pending state
10266
+ const pendingStep = result.steps.find((step) => step.name === analysis.continuationStep && step.state === 'pending');
10267
+ return pendingStep !== undefined;
10268
+ }
10269
+ /**
10270
+ * Check if the step is the last one in the execution flow.
10271
+ *
10272
+ * @param step - The step object to check.
10273
+ * @param stepNames - The ordered list of step names in the execution flow.
10274
+ * @returns True if this is the last step in the flow.
10275
+ *
10276
+ * @example
10277
+ * ```typescript
10278
+ * import { isLastStep } from './stepUtils'
10279
+ *
10280
+ * const stepNames = ['approve', 'burn', 'fetchAttestation', 'mint']
10281
+ * isLastStep({ name: 'mint' }, stepNames) // true
10282
+ * isLastStep({ name: 'burn' }, stepNames) // false
10283
+ * ```
10284
+ */
10285
+ function isLastStep(step, stepNames) {
10286
+ const stepIndex = stepNames.indexOf(step.name);
10287
+ return stepIndex === -1 || stepIndex >= stepNames.length - 1;
10288
+ }
10289
+ /**
10290
+ * Wait for a pending transaction to complete.
10291
+ *
10292
+ * Poll the adapter until the transaction is confirmed on-chain and return
10293
+ * the updated step with success or error state based on the receipt.
10294
+ *
10295
+ * @param pendingStep - The full step object containing the transaction hash.
10296
+ * @param adapter - The adapter to use for waiting.
10297
+ * @param chain - The chain where the transaction was submitted.
10298
+ * @returns The updated step object with success or error state.
10299
+ *
10300
+ * @throws KitError when the pending step has no transaction hash.
10301
+ *
10302
+ * @example
10303
+ * ```typescript
10304
+ * import { waitForPendingTransaction } from './bridgeStepUtils'
10305
+ *
10306
+ * const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
10307
+ * const updatedStep = await waitForPendingTransaction(pendingStep, adapter, chain)
10308
+ * // updatedStep.state is now 'success' or 'error'
10309
+ * ```
10310
+ */
10311
+ async function waitForPendingTransaction(pendingStep, adapter, chain) {
10312
+ if (!pendingStep.txHash) {
10313
+ throw new KitError({
10314
+ ...InputError.VALIDATION_FAILED,
10315
+ recoverability: 'FATAL',
10316
+ message: `Cannot wait for pending ${pendingStep.name}: no transaction hash available`,
10317
+ });
10318
+ }
10319
+ const txHash = pendingStep.txHash;
10320
+ const txReceipt = await retryAsync(async () => adapter.waitForTransaction(txHash, undefined, chain), {
10321
+ isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
10322
+ });
10323
+ const outcome = evaluateTransactionOutcome(txReceipt, txHash);
10324
+ return {
10325
+ ...pendingStep,
10326
+ state: outcome.state,
10327
+ data: txReceipt,
10328
+ explorerUrl: buildExplorerUrl(chain, txHash),
10329
+ ...(outcome.errorMessage ? { errorMessage: outcome.errorMessage } : {}),
10330
+ };
10331
+ }
10332
+ /**
10333
+ * Wait for a pending step to complete.
10334
+ *
10335
+ * For transaction steps: waits for the transaction to be confirmed.
10336
+ * For attestation: re-executes the attestation fetch.
10337
+ *
10338
+ * @typeParam TFromAdapterCapabilities - The capabilities of the source adapter.
10339
+ * @typeParam TToAdapterCapabilities - The capabilities of the destination adapter.
10340
+ * @param pendingStep - The full step object (with name, state, txHash, data, etc.) to resolve.
10341
+ * @param adapter - The adapter to use.
10342
+ * @param chain - The chain where the step is executing.
10343
+ * @param context - The retry context.
10344
+ * @param result - The bridge result.
10345
+ * @param provider - The CCTP v2 bridging provider.
10346
+ * @returns The resolved step object with updated state.
10347
+ *
10348
+ * @throws KitError when fetching attestation but burn transaction hash is not found.
10349
+ *
10350
+ * @example
10351
+ * ```typescript
10352
+ * import { waitForStepToComplete } from './bridgeStepUtils'
10353
+ *
10354
+ * const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
10355
+ * const updatedStep = await waitForStepToComplete(
10356
+ * pendingStep,
10357
+ * adapter,
10358
+ * chain,
10359
+ * context,
10360
+ * result,
10361
+ * provider,
10362
+ * )
10363
+ * // updatedStep.state is now 'success' or 'error'
10364
+ * ```
10365
+ */
10366
+ async function waitForStepToComplete(pendingStep, adapter, chain, context, result, provider) {
10367
+ if (pendingStep.name === CCTPv2StepName.fetchAttestation) {
10368
+ // For attestation, re-run the fetch (it has built-in polling)
10369
+ const burnTxHash = getBurnTxHash(result);
10370
+ if (!burnTxHash) {
10371
+ throw new KitError({
10372
+ ...InputError.VALIDATION_FAILED,
10373
+ recoverability: 'FATAL',
10374
+ message: 'Cannot fetch attestation: burn transaction hash not found',
10375
+ });
10376
+ }
10377
+ const sourceAddress = result.source.address;
10378
+ const attestation = await provider.fetchAttestation({
10379
+ chain: result.source.chain,
10380
+ adapter: context.from,
10381
+ address: sourceAddress,
10382
+ }, burnTxHash);
10383
+ return {
10384
+ ...pendingStep,
10385
+ state: 'success',
10386
+ data: attestation,
10387
+ };
10388
+ }
10389
+ // For transaction steps, wait for the transaction to complete
10390
+ return waitForPendingTransaction(pendingStep, adapter, chain);
10391
+ }
10392
+
10393
+ /**
10394
+ * Executes a prepared chain request and returns the result as a bridge step.
10395
+ *
10396
+ * This function takes a prepared chain request (containing transaction data) and executes
10397
+ * it using the appropriate adapter. It handles the execution details and formats
10398
+ * the result as a standardized bridge step with transaction details and explorer URLs.
10399
+ *
10400
+ * @param params - The execution parameters containing:
10401
+ * - `name`: The name of the step
10402
+ * - `request`: The prepared chain request containing transaction data
10403
+ * - `adapter`: The adapter that will execute the transaction
10404
+ * - `confirmations`: The number of confirmations to wait for (defaults to 1)
10405
+ * - `timeout`: The timeout for the request in milliseconds
10406
+ * @returns The bridge step with the transaction details and explorer URL
10407
+ * @throws If the transaction execution fails
10408
+ *
10409
+ * @example
10410
+ * ```typescript
10411
+ * const step = await executePreparedChainRequest({
10412
+ * name: 'approve',
10413
+ * request: preparedRequest,
10414
+ * adapter: adapter,
10415
+ * confirmations: 2,
10416
+ * timeout: 30000
10417
+ * })
10418
+ * console.log('Transaction hash:', step.txHash)
10419
+ * ```
10420
+ */
10421
+ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, }) {
9044
10422
  const step = { name, state: 'pending' };
9045
10423
  try {
9046
10424
  /**
@@ -9060,17 +10438,25 @@ async function executePreparedChainRequest({ name, request, adapter, chain, conf
9060
10438
  retryOptions.deadlineMs = Date.now() + timeout;
9061
10439
  }
9062
10440
  const transaction = await retryAsync(async () => adapter.waitForTransaction(txHash, { confirmations, timeout }, chain), retryOptions);
9063
- step.state = transaction.blockNumber ? 'success' : 'error';
10441
+ const outcome = evaluateTransactionOutcome(transaction, txHash);
10442
+ step.state = outcome.state;
9064
10443
  step.data = transaction;
9065
10444
  // Generate explorer URL for the step
9066
10445
  step.explorerUrl = buildExplorerUrl(chain, txHash);
9067
- if (!transaction.blockNumber) {
9068
- step.errorMessage = 'Transaction was not confirmed on-chain.';
10446
+ if (outcome.errorMessage) {
10447
+ step.errorMessage = outcome.errorMessage;
10448
+ // Transaction was mined but reverted on-chain.
10449
+ step.errorCategory = 'chain_revert';
9069
10450
  }
9070
10451
  }
9071
10452
  catch (err) {
9072
10453
  step.state = 'error';
9073
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';
9074
10460
  // Optionally parse for common blockchain error formats
9075
10461
  if (err instanceof Error) {
9076
10462
  step.errorMessage = err.message;
@@ -9079,7 +10465,7 @@ async function executePreparedChainRequest({ name, request, adapter, chain, conf
9079
10465
  step.errorMessage = String(err.message);
9080
10466
  }
9081
10467
  else {
9082
- step.errorMessage = 'Unknown error occurred during approval step.';
10468
+ step.errorMessage = `Unknown error occurred during ${name} step.`;
9083
10469
  }
9084
10470
  }
9085
10471
  return step;
@@ -10619,16 +12005,70 @@ async function executeBatchedApproveAndBurn(params, provider) {
10619
12005
  const batchResult = await adapter.batchExecute([approveCallData, burnCallData], chain);
10620
12006
  const approveReceipt = batchResult.receipts[0];
10621
12007
  const burnReceipt = batchResult.receipts[1];
10622
- const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain);
10623
- 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);
10624
12010
  if (burnStep.state !== 'error' && !burnStep.txHash) {
10625
12011
  burnStep.state = 'error';
10626
12012
  burnStep.errorMessage =
10627
12013
  'Batched burn step completed but no transaction hash was returned.';
12014
+ burnStep.errorCategory = 'unknown';
10628
12015
  }
10629
12016
  const context = { burnTxHash: burnStep.txHash ?? '' };
10630
12017
  return { approveStep, burnStep, context };
10631
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
+ }
10632
12072
  /**
10633
12073
  * Build a {@link BridgeStep} from a single receipt within a batch.
10634
12074
  *
@@ -10643,11 +12083,17 @@ async function executeBatchedApproveAndBurn(params, provider) {
10643
12083
  * @param batchId - Wallet-assigned batch identifier.
10644
12084
  * @param adapter - The batch-capable adapter (used for confirmation).
10645
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.
10646
12092
  * @returns A fully-populated bridge step with state, tx hash and explorer URL.
10647
12093
  *
10648
12094
  * @internal
10649
12095
  */
10650
- async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
12096
+ async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCode, batchError) {
10651
12097
  const step = {
10652
12098
  name,
10653
12099
  state: 'pending',
@@ -10657,6 +12103,10 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
10657
12103
  if (!receipt) {
10658
12104
  step.state = 'error';
10659
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
+ }
10660
12110
  return step;
10661
12111
  }
10662
12112
  step.txHash = receipt.txHash;
@@ -10666,11 +12116,13 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
10666
12116
  if (receipt.status !== 'success') {
10667
12117
  step.state = 'error';
10668
12118
  step.errorMessage = `${name} call failed within batch ${batchId}.`;
12119
+ step.errorCategory = categorizeFailedReceipt(statusCode);
10669
12120
  return step;
10670
12121
  }
10671
12122
  if (!receipt.txHash) {
10672
12123
  step.state = 'error';
10673
12124
  step.errorMessage = `${name} succeeded in batch but returned an empty transaction hash.`;
12125
+ step.errorCategory = 'unknown';
10674
12126
  return step;
10675
12127
  }
10676
12128
  try {
@@ -10680,10 +12132,12 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
10680
12132
  txHash: receipt.txHash,
10681
12133
  })),
10682
12134
  });
10683
- step.state = transaction.blockNumber === undefined ? 'error' : 'success';
12135
+ const outcome = evaluateTransactionOutcome(transaction, receipt.txHash);
12136
+ step.state = outcome.state;
10684
12137
  step.data = transaction;
10685
- if (transaction.blockNumber === undefined) {
10686
- step.errorMessage = 'Transaction was not confirmed on-chain.';
12138
+ if (outcome.errorMessage) {
12139
+ step.errorMessage = outcome.errorMessage;
12140
+ step.errorCategory = 'chain_revert';
10687
12141
  }
10688
12142
  }
10689
12143
  catch (err) {
@@ -10691,11 +12145,12 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
10691
12145
  step.error = err;
10692
12146
  step.errorMessage =
10693
12147
  err instanceof Error ? err.message : 'Unknown error during confirmation.';
12148
+ step.errorCategory = 'unknown';
10694
12149
  }
10695
12150
  return step;
10696
12151
  }
10697
12152
 
10698
- var version = "1.6.2";
12153
+ var version = "1.7.0";
10699
12154
  var pkg = {
10700
12155
  version: version};
10701
12156
 
@@ -10771,6 +12226,7 @@ async function executeBatchedPath(params, provider, result, invocation) {
10771
12226
  errorMessage: error_ instanceof Error
10772
12227
  ? error_.message
10773
12228
  : 'Batched approve + burn failed.',
12229
+ errorCategory: classifyPreSubmissionError(error_),
10774
12230
  });
10775
12231
  return undefined;
10776
12232
  }
@@ -10785,6 +12241,89 @@ function ensureStepErrorMessage(name, step) {
10785
12241
  step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
10786
12242
  }
10787
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
+ }
10788
12327
  /**
10789
12328
  * Execute a cross-chain USDC bridge using the CCTP v2 protocol.
10790
12329
  *
@@ -11003,681 +12542,266 @@ const hexStringSchema = zod.z
11003
12542
  .refine((value) => value.trim().length > 0, 'Hex string cannot be empty')
11004
12543
  .refine((value) => value.startsWith('0x'), 'Hex string must start with 0x prefix')
11005
12544
  .refine((value) => {
11006
- const hexPattern = /^0x[0-9a-fA-F]+$/;
11007
- return hexPattern.test(value);
11008
- }, 'Hex string contains invalid characters. Only hexadecimal characters (0-9, a-f, A-F) are allowed after 0x');
11009
- /**
11010
- * Schema for validating EVM addresses.
11011
- *
11012
- * This schema validates that a string is a properly formatted EVM address:
11013
- * - Must be a valid hex string with '0x' prefix
11014
- * - Must be exactly 42 characters long (0x + 40 hex characters)
11015
- *
11016
- * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11017
- *
11018
- * @example
11019
- * ```typescript
11020
- * import { evmAddressSchema } from '@core/adapter'
11021
- *
11022
- * const validAddress = '0x1234567890123456789012345678901234567890'
11023
- *
11024
- * const result = evmAddressSchema.safeParse(validAddress)
11025
- * console.log(result.success) // true
11026
- * ```
11027
- */
11028
- hexStringSchema.refine((value) => value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)');
11029
- /**
11030
- * Schema for validating transaction hashes.
11031
- *
11032
- * This schema validates that a string is a properly formatted transaction hash:
11033
- * - Must be a valid hex string with '0x' prefix
11034
- * - Must be exactly 66 characters long (0x + 64 hex characters)
11035
- *
11036
- * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11037
- *
11038
- * @example
11039
- * ```typescript
11040
- * import { evmTransactionHashSchema } from '@core/adapter'
11041
- *
11042
- * const validTxHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
11043
- *
11044
- * const result = evmTransactionHashSchema.safeParse(validTxHash)
11045
- * console.log(result.success) // true
11046
- * ```
11047
- */
11048
- hexStringSchema.refine((value) => value.length === 66, 'Transaction hash must be exactly 66 characters long (0x + 64 hex characters)');
11049
- /**
11050
- * Schema for validating base58-encoded strings.
11051
- *
11052
- * This schema validates that a string:
11053
- * - Is a string type
11054
- * - Is not empty after trimming
11055
- * - Contains only valid base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z)
11056
- * - Does not contain commonly confused characters (0, O, I, l)
11057
- *
11058
- * @remarks
11059
- * This schema does not validate length, making it suitable for various base58-encoded data
11060
- * like Solana addresses, transaction signatures, and other base58-encoded data.
11061
- *
11062
- * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11063
- *
11064
- * @example
11065
- * ```typescript
11066
- * import { base58StringSchema } from '@core/adapter'
11067
- *
11068
- * const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
11069
- * const validTxHash = '3Jf8k2L5mN9pQ7rS1tV4wX6yZ8aB2cD4eF5gH7iJ9kL1mN3oP5qR7sT9uV1wX3yZ5'
11070
- *
11071
- * const addressResult = base58StringSchema.safeParse(validAddress)
11072
- * const txHashResult = base58StringSchema.safeParse(validTxHash)
11073
- * console.log(addressResult.success) // true
11074
- * console.log(txHashResult.success) // true
11075
- * ```
11076
- */
11077
- const base58StringSchema = zod.z
11078
- .string()
11079
- .min(1, 'Base58 string is required')
11080
- .refine((value) => value.trim().length > 0, 'Base58 string cannot be empty')
11081
- .refine((value) => {
11082
- // Base58 alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
11083
- // Excludes: 0, O, I, l to avoid confusion
11084
- const base58Pattern = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/;
11085
- return base58Pattern.test(value);
11086
- }, 'Base58 string contains invalid characters. Only base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z) are allowed');
11087
- /**
11088
- * Schema for validating Solana addresses.
11089
- *
11090
- * This schema validates that a string is a properly formatted Solana address:
11091
- * - Must be a valid base58-encoded string
11092
- * - Must be between 32-44 characters long (typical length for base58-encoded 32-byte addresses)
11093
- *
11094
- * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11095
- *
11096
- * @example
11097
- * ```typescript
11098
- * import { solanaAddressSchema } from '@core/adapter'
11099
- *
11100
- * const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
11101
- *
11102
- * const result = solanaAddressSchema.safeParse(validAddress)
11103
- * console.log(result.success) // true
11104
- * ```
11105
- */
11106
- base58StringSchema.refine((value) => value.length >= 32 && value.length <= 44, 'Solana address must be between 32-44 characters long (base58-encoded 32-byte address)');
11107
- /**
11108
- * Schema for validating Solana transaction hashes.
11109
- *
11110
- * This schema validates that a string is a properly formatted Solana transaction hash:
11111
- * - Must be a valid base58-encoded string
11112
- * - Must be between 86-88 characters long (typical length for base58-encoded 64-byte signatures)
11113
- *
11114
- * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11115
- *
11116
- * @example
11117
- * ```typescript
11118
- * import { solanaTransactionHashSchema } from '@core/adapter'
11119
- *
11120
- * const validTxHash = '5VfYmGBjvQKe3xgLtTQPSMEUdpEVHrJwLK7pKBJWKzYpNBE2g3kJrq7RSe9M8DqzQJ5J2aZPTjHLvd4WgxPpJKS'
11121
- *
11122
- * const result = solanaTransactionHashSchema.safeParse(validTxHash)
11123
- * console.log(result.success) // true
11124
- * ```
11125
- */
11126
- base58StringSchema.refine((value) => value.length >= 86 && value.length <= 88, 'Solana transaction hash must be between 86-88 characters long (base58-encoded 64-byte signature)');
11127
- /**
11128
- * Schema for validating Adapter objects.
11129
- * Checks for the required methods that define an Adapter.
11130
- */
11131
- zod.z.object({
11132
- prepare: zod.z.function(),
11133
- waitForTransaction: zod.z.function(),
11134
- getAddress: zod.z.function(),
11135
- });
11136
-
11137
- /**
11138
- * Validate that the adapter has sufficient token balance for a transaction.
11139
- *
11140
- * This function checks if the adapter's current token balance is greater than or equal
11141
- * to the requested transaction amount. It throws a KitError with code 9001
11142
- * (BALANCE_INSUFFICIENT_TOKEN) if the balance is insufficient, providing detailed
11143
- * information about the shortfall.
11144
- *
11145
- * @param params - The validation parameters containing adapter, amount, token, and token address.
11146
- * @returns A promise that resolves to void if validation passes.
11147
- * @throws {KitError} When the adapter's balance is less than the required amount (code: 9001).
11148
- *
11149
- * @example
11150
- * ```typescript
11151
- * import { validateBalanceForTransaction } from '@core/adapter'
11152
- * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
11153
- * import { isKitError, ERROR_TYPES } from '@core/errors'
11154
- *
11155
- * const adapter = createViemAdapterFromPrivateKey({
11156
- * privateKey: '0x...',
11157
- * chain: 'Ethereum',
11158
- * })
11159
- *
11160
- * try {
11161
- * await validateBalanceForTransaction({
11162
- * adapter,
11163
- * amount: '1000000', // 1 USDC (6 decimals)
11164
- * token: 'USDC',
11165
- * tokenAddress: '0xA0b86a33E6441c8C1c7C16e4c5e3e5b5e4c5e3e5b5e4c5e',
11166
- * operationContext: { chain: 'Ethereum' },
11167
- * })
11168
- * console.log('Balance validation passed')
11169
- * } catch (error) {
11170
- * if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
11171
- * console.error('Insufficient funds:', error.message)
11172
- * }
11173
- * }
11174
- * ```
11175
- */
11176
- const validateBalanceForTransaction = async (params) => {
11177
- const { amount, adapter, token, tokenAddress, operationContext } = params;
11178
- const balancePrepared = await adapter.prepareAction('usdc.balanceOf', {
11179
- walletAddress: operationContext.address,
11180
- }, operationContext);
11181
- const balance = await balancePrepared.execute();
11182
- if (BigInt(balance) < BigInt(amount)) {
11183
- // Extract chain name from operationContext
11184
- const chainName = extractChainInfo(operationContext.chain).name;
11185
- // Create KitError with rich context in trace
11186
- throw createInsufficientTokenBalanceError(chainName, token, {
11187
- balance: balance.toString(),
11188
- amount,
11189
- tokenAddress,
11190
- walletAddress: operationContext.address,
11191
- });
11192
- }
11193
- };
11194
-
12545
+ const hexPattern = /^0x[0-9a-fA-F]+$/;
12546
+ return hexPattern.test(value);
12547
+ }, 'Hex string contains invalid characters. Only hexadecimal characters (0-9, a-f, A-F) are allowed after 0x');
11195
12548
  /**
11196
- * Validate that the adapter has sufficient native token balance for transaction fees.
12549
+ * Schema for validating EVM addresses.
11197
12550
  *
11198
- * This function checks if the adapter's current native token balance (ETH, SOL, etc.)
11199
- * is greater than zero. It throws a KitError with code 9002 (BALANCE_INSUFFICIENT_GAS)
11200
- * if the balance is zero, indicating the wallet cannot pay for transaction fees.
12551
+ * This schema validates that a string is a properly formatted EVM address:
12552
+ * - Must be a valid hex string with '0x' prefix
12553
+ * - Must be exactly 42 characters long (0x + 40 hex characters)
11201
12554
  *
11202
- * @param params - The validation parameters containing adapter and operation context.
11203
- * @returns A promise that resolves to void if validation passes.
11204
- * @throws {KitError} When the adapter's native balance is zero (code: 9002).
12555
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11205
12556
  *
11206
12557
  * @example
11207
12558
  * ```typescript
11208
- * import { validateNativeBalanceForTransaction } from '@core/adapter'
11209
- * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
11210
- * import { isKitError, ERROR_TYPES } from '@core/errors'
12559
+ * import { evmAddressSchema } from '@core/adapter'
11211
12560
  *
11212
- * const adapter = createViemAdapterFromPrivateKey({
11213
- * privateKey: '0x...',
11214
- * chain: 'Ethereum',
11215
- * })
12561
+ * const validAddress = '0x1234567890123456789012345678901234567890'
11216
12562
  *
11217
- * try {
11218
- * await validateNativeBalanceForTransaction({
11219
- * adapter,
11220
- * operationContext: { chain: 'Ethereum' },
11221
- * })
11222
- * console.log('Native balance validation passed')
11223
- * } catch (error) {
11224
- * if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
11225
- * console.error('Insufficient gas funds:', error.message)
11226
- * }
11227
- * }
12563
+ * const result = evmAddressSchema.safeParse(validAddress)
12564
+ * console.log(result.success) // true
11228
12565
  * ```
11229
12566
  */
11230
- const validateNativeBalanceForTransaction = async (params) => {
11231
- const { adapter, operationContext } = params;
11232
- const balancePrepared = await adapter.prepareAction('native.balanceOf', {
11233
- walletAddress: operationContext.address,
11234
- }, operationContext);
11235
- const balance = await balancePrepared.execute();
11236
- if (BigInt(balance) === 0n) {
11237
- // Extract chain name from operationContext
11238
- const chainName = extractChainInfo(operationContext.chain).name;
11239
- // Create KitError with rich context in trace
11240
- throw createInsufficientGasError(chainName, {
11241
- balance: '0',
11242
- walletAddress: operationContext.address,
11243
- });
11244
- }
11245
- };
11246
-
12567
+ hexStringSchema.refine((value) => value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)');
11247
12568
  /**
11248
- * Permit signature standards for gasless token approvals.
12569
+ * Schema for validating transaction hashes.
11249
12570
  *
11250
- * Defines the permit types that can be used to approve token spending
11251
- * without requiring a separate approval transaction.
12571
+ * This schema validates that a string is a properly formatted transaction hash:
12572
+ * - Must be a valid hex string with '0x' prefix
12573
+ * - Must be exactly 66 characters long (0x + 64 hex characters)
11252
12574
  *
11253
- * @remarks
11254
- * - NONE: No permit, tokens must be pre-approved via separate transaction
11255
- * - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
11256
- */
11257
- var PermitType;
11258
- (function (PermitType) {
11259
- /** No permit required - tokens must be pre-approved */
11260
- PermitType[PermitType["NONE"] = 0] = "NONE";
11261
- /** EIP-2612 standard permit */
11262
- PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
11263
- })(PermitType || (PermitType = {}));
11264
-
11265
- /**
11266
- * CCTP bridge step names that can occur in the bridging flow.
12575
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11267
12576
  *
11268
- * This object provides type safety for step names and represents all possible
11269
- * steps that can be executed during a CCTP bridge operation. Using const assertions
11270
- * makes this tree-shakable and follows modern TypeScript best practices.
11271
- */
11272
- const CCTPv2StepName = {
11273
- approve: 'approve',
11274
- burn: 'burn',
11275
- fetchAttestation: 'fetchAttestation',
11276
- mint: 'mint',
11277
- reAttest: 'reAttest',
11278
- };
11279
- /**
11280
- * Conditional step transition rules for CCTP bridge flow.
12577
+ * @example
12578
+ * ```typescript
12579
+ * import { evmTransactionHashSchema } from '@core/adapter'
11281
12580
  *
11282
- * Rules are evaluated in order - the first matching condition determines the next step.
11283
- * This approach supports flexible flow logic and makes it easy to extend with new patterns.
12581
+ * const validTxHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
12582
+ *
12583
+ * const result = evmTransactionHashSchema.safeParse(validTxHash)
12584
+ * console.log(result.success) // true
12585
+ * ```
11284
12586
  */
11285
- const STEP_TRANSITION_RULES = {
11286
- // Starting state - no steps executed yet
11287
- '': [
11288
- {
11289
- condition: () => true,
11290
- nextStep: CCTPv2StepName.approve,
11291
- reason: 'Start with approval step',
11292
- isActionable: true,
11293
- },
11294
- ],
11295
- // After Approve step
11296
- [CCTPv2StepName.approve]: [
11297
- {
11298
- condition: (ctx) => ctx.lastStep?.state === 'success',
11299
- nextStep: CCTPv2StepName.burn,
11300
- reason: 'Approval successful, proceed to burn',
11301
- isActionable: true,
11302
- },
11303
- {
11304
- condition: (ctx) => ctx.lastStep?.state === 'error',
11305
- nextStep: CCTPv2StepName.approve,
11306
- reason: 'Retry failed approval',
11307
- isActionable: true,
11308
- },
11309
- {
11310
- condition: (ctx) => ctx.lastStep?.state === 'noop',
11311
- nextStep: CCTPv2StepName.burn,
11312
- reason: 'No approval needed, proceed to burn',
11313
- isActionable: true,
11314
- },
11315
- {
11316
- condition: (ctx) => ctx.lastStep?.state === 'pending',
11317
- nextStep: CCTPv2StepName.approve,
11318
- reason: 'Continue pending approval',
11319
- isActionable: false, // Waiting for pending transaction
11320
- },
11321
- ],
11322
- // After Burn step
11323
- [CCTPv2StepName.burn]: [
11324
- {
11325
- condition: (ctx) => ctx.lastStep?.state === 'success',
11326
- nextStep: CCTPv2StepName.fetchAttestation,
11327
- reason: 'Burn successful, fetch attestation',
11328
- isActionable: true,
11329
- },
11330
- {
11331
- condition: (ctx) => ctx.lastStep?.state === 'error',
11332
- nextStep: CCTPv2StepName.burn,
11333
- reason: 'Retry failed burn',
11334
- isActionable: true,
11335
- },
11336
- {
11337
- condition: (ctx) => ctx.lastStep?.state === 'pending',
11338
- nextStep: CCTPv2StepName.burn,
11339
- reason: 'Continue pending burn',
11340
- isActionable: false, // Waiting for pending transaction
11341
- },
11342
- ],
11343
- // After FetchAttestation step
11344
- [CCTPv2StepName.fetchAttestation]: [
11345
- {
11346
- condition: (ctx) => ctx.lastStep?.state === 'success',
11347
- nextStep: CCTPv2StepName.mint,
11348
- reason: 'Attestation fetched, proceed to mint',
11349
- isActionable: true,
11350
- },
11351
- {
11352
- condition: (ctx) => ctx.lastStep?.state === 'error',
11353
- nextStep: CCTPv2StepName.fetchAttestation,
11354
- reason: 'Retry fetching attestation',
11355
- isActionable: true,
11356
- },
11357
- {
11358
- condition: (ctx) => ctx.lastStep?.state === 'pending',
11359
- nextStep: CCTPv2StepName.fetchAttestation,
11360
- reason: 'Continue pending attestation fetch',
11361
- isActionable: false, // Waiting for attestation to be ready
11362
- },
11363
- ],
11364
- // After Mint step
11365
- [CCTPv2StepName.mint]: [
11366
- {
11367
- condition: (ctx) => ctx.lastStep?.state === 'success',
11368
- nextStep: null,
11369
- reason: 'Bridge completed successfully',
11370
- isActionable: false, // Nothing more to do
11371
- },
11372
- {
11373
- condition: (ctx) => ctx.lastStep?.state === 'error',
11374
- nextStep: CCTPv2StepName.mint,
11375
- reason: 'Retry failed mint',
11376
- isActionable: true,
11377
- },
11378
- {
11379
- condition: (ctx) => ctx.lastStep?.state === 'pending',
11380
- nextStep: CCTPv2StepName.mint,
11381
- reason: 'Continue pending mint',
11382
- isActionable: false, // Waiting for pending transaction
11383
- },
11384
- ],
11385
- // After ReAttest step
11386
- [CCTPv2StepName.reAttest]: [
11387
- {
11388
- condition: (ctx) => ctx.lastStep?.state === 'success',
11389
- nextStep: CCTPv2StepName.mint,
11390
- reason: 'Re-attestation successful, proceed to mint',
11391
- isActionable: true,
11392
- },
11393
- {
11394
- condition: (ctx) => ctx.lastStep?.state === 'error',
11395
- nextStep: CCTPv2StepName.mint,
11396
- reason: 'Re-attestation failed, retry mint to re-initiate recovery',
11397
- isActionable: true,
11398
- },
11399
- {
11400
- condition: (ctx) => ctx.lastStep?.state === 'pending',
11401
- nextStep: CCTPv2StepName.mint,
11402
- reason: 'Re-attestation pending, retry mint to re-initiate recovery',
11403
- isActionable: true,
11404
- },
11405
- ],
11406
- };
12587
+ hexStringSchema.refine((value) => value.length === 66, 'Transaction hash must be exactly 66 characters long (0x + 64 hex characters)');
11407
12588
  /**
11408
- * Analyze bridge steps to determine retry feasibility and continuation point.
11409
- *
11410
- * This function examines the current state of bridge steps to determine the optimal
11411
- * continuation strategy. It uses a rule-based approach that makes it easy to extend
11412
- * with new flow patterns and step types in the future.
11413
- *
11414
- * The current analysis supports the standard CCTP flow:
11415
- * **Traditional flow**: Approve → Burn → FetchAttestation → Mint
11416
- *
11417
- * Key features:
11418
- * - Rule-based transitions: Easy to extend with new step types and logic
11419
- * - Context-aware decisions: Considers execution history and step states
11420
- * - Actionable logic: Distinguishes between steps requiring user action vs waiting
11421
- * - Terminal states: Properly handles completion and non-actionable states
11422
- *
11423
- * @param bridgeResult - The bridge result containing step execution history.
11424
- * @returns Analysis result with continuation step and actionability information.
11425
- * @throws Error when bridgeResult is invalid or contains no steps array.
12589
+ * Schema for validating base58-encoded strings.
11426
12590
  *
11427
- * @example
11428
- * ```typescript
11429
- * import { analyzeSteps } from './analyzeSteps'
12591
+ * This schema validates that a string:
12592
+ * - Is a string type
12593
+ * - Is not empty after trimming
12594
+ * - Contains only valid base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z)
12595
+ * - Does not contain commonly confused characters (0, O, I, l)
11430
12596
  *
11431
- * // Failed approval step (requires user action)
11432
- * const bridgeResult = {
11433
- * steps: [
11434
- * { name: 'Approve', state: 'error', errorMessage: 'User rejected' }
11435
- * ]
11436
- * }
12597
+ * @remarks
12598
+ * This schema does not validate length, making it suitable for various base58-encoded data
12599
+ * like Solana addresses, transaction signatures, and other base58-encoded data.
11437
12600
  *
11438
- * const analysis = analyzeSteps(bridgeResult)
11439
- * // Result: { continuationStep: 'Approve', isRetryable: true,
11440
- * // reason: 'Retry failed approval' }
11441
- * ```
12601
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11442
12602
  *
11443
12603
  * @example
11444
12604
  * ```typescript
11445
- * // Pending transaction (requires waiting, not actionable)
11446
- * const bridgeResult = {
11447
- * steps: [
11448
- * { name: 'Approve', state: 'pending' }
11449
- * ]
11450
- * }
12605
+ * import { base58StringSchema } from '@core/adapter'
11451
12606
  *
11452
- * const analysis = analyzeSteps(bridgeResult)
11453
- * // Result: { continuationStep: 'Approve', isRetryable: false,
11454
- * // reason: 'Continue pending approval' }
12607
+ * const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
12608
+ * const validTxHash = '3Jf8k2L5mN9pQ7rS1tV4wX6yZ8aB2cD4eF5gH7iJ9kL1mN3oP5qR7sT9uV1wX3yZ5'
12609
+ *
12610
+ * const addressResult = base58StringSchema.safeParse(validAddress)
12611
+ * const txHashResult = base58StringSchema.safeParse(validTxHash)
12612
+ * console.log(addressResult.success) // true
12613
+ * console.log(txHashResult.success) // true
11455
12614
  * ```
12615
+ */
12616
+ const base58StringSchema = zod.z
12617
+ .string()
12618
+ .min(1, 'Base58 string is required')
12619
+ .refine((value) => value.trim().length > 0, 'Base58 string cannot be empty')
12620
+ .refine((value) => {
12621
+ // Base58 alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
12622
+ // Excludes: 0, O, I, l to avoid confusion
12623
+ const base58Pattern = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/;
12624
+ return base58Pattern.test(value);
12625
+ }, 'Base58 string contains invalid characters. Only base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z) are allowed');
12626
+ /**
12627
+ * Schema for validating Solana addresses.
12628
+ *
12629
+ * This schema validates that a string is a properly formatted Solana address:
12630
+ * - Must be a valid base58-encoded string
12631
+ * - Must be between 32-44 characters long (typical length for base58-encoded 32-byte addresses)
12632
+ *
12633
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11456
12634
  *
11457
12635
  * @example
11458
12636
  * ```typescript
11459
- * // Completed bridge (nothing to do)
11460
- * const bridgeResult = {
11461
- * steps: [
11462
- * { name: 'Approve', state: 'success' },
11463
- * { name: 'Burn', state: 'success' },
11464
- * { name: 'FetchAttestation', state: 'success' },
11465
- * { name: 'Mint', state: 'success' }
11466
- * ]
11467
- * }
12637
+ * import { solanaAddressSchema } from '@core/adapter'
11468
12638
  *
11469
- * const analysis = analyzeSteps(bridgeResult)
11470
- * // Result: { continuationStep: null, isRetryable: false,
11471
- * // reason: 'Bridge completed successfully' }
11472
- * ```
11473
- */
11474
- const analyzeSteps = (bridgeResult) => {
11475
- // Input validation
11476
- if (!bridgeResult || !Array.isArray(bridgeResult.steps)) {
11477
- throw new Error('Invalid bridgeResult: must contain a steps array');
11478
- }
11479
- const { steps } = bridgeResult;
11480
- // Build execution context from step history
11481
- const context = buildFlowContext(steps);
11482
- // Determine continuation logic using rule engine
11483
- const continuation = determineContinuationFromRules(context);
11484
- return {
11485
- continuationStep: continuation.nextStep,
11486
- isActionable: continuation.isActionable,
11487
- completedSteps: Array.from(context.completedSteps),
11488
- failedSteps: Array.from(context.failedSteps),
11489
- reason: continuation.reason,
11490
- };
11491
- };
11492
- /**
11493
- * Build flow context from the execution history.
12639
+ * const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
11494
12640
  *
11495
- * @param steps - Array of executed bridge steps.
11496
- * @returns Flow context with execution state and history.
12641
+ * const result = solanaAddressSchema.safeParse(validAddress)
12642
+ * console.log(result.success) // true
12643
+ * ```
11497
12644
  */
11498
- function buildFlowContext(steps) {
11499
- const completedSteps = new Set();
11500
- const failedSteps = new Set();
11501
- let lastStep;
11502
- // Process step history to build context
11503
- for (const step of steps) {
11504
- if (step.state === 'success' || step.state === 'noop') {
11505
- completedSteps.add(step.name);
11506
- }
11507
- else if (step.state === 'error') {
11508
- failedSteps.add(step.name);
11509
- }
11510
- // Track the last step for continuation logic
11511
- lastStep = {
11512
- name: step.name,
11513
- state: step.state,
11514
- };
11515
- }
11516
- return {
11517
- completedSteps,
11518
- failedSteps,
11519
- ...(lastStep && { lastStep }),
11520
- };
11521
- }
12645
+ base58StringSchema.refine((value) => value.length >= 32 && value.length <= 44, 'Solana address must be between 32-44 characters long (base58-encoded 32-byte address)');
11522
12646
  /**
11523
- * Determine continuation step using the rule engine.
12647
+ * Schema for validating Solana transaction hashes.
11524
12648
  *
11525
- * @param context - The flow context with execution history.
11526
- * @returns Continuation decision with next step and actionability information.
11527
- */
11528
- function determineContinuationFromRules(context) {
11529
- const lastStepName = context.lastStep?.name;
11530
- // Handle initial state when no steps have been executed
11531
- if (lastStepName === undefined) {
11532
- const rules = STEP_TRANSITION_RULES[''];
11533
- const matchingRule = rules?.find((rule) => rule.condition(context));
11534
- if (!matchingRule) {
11535
- return {
11536
- nextStep: null,
11537
- isActionable: false,
11538
- reason: 'No initial state rule found',
11539
- };
11540
- }
11541
- return {
11542
- nextStep: matchingRule.nextStep,
11543
- isActionable: matchingRule.isActionable,
11544
- reason: matchingRule.reason,
11545
- };
11546
- }
11547
- // A step with an empty name is ambiguous and should be treated as an unrecoverable state.
11548
- if (lastStepName === '') {
11549
- return {
11550
- nextStep: null,
11551
- isActionable: false,
11552
- reason: 'No transition rules defined for step with empty name',
11553
- };
11554
- }
11555
- const rules = STEP_TRANSITION_RULES[lastStepName];
11556
- if (!rules) {
11557
- return {
11558
- nextStep: null,
11559
- isActionable: false,
11560
- reason: `No transition rules defined for step: ${lastStepName}`,
11561
- };
11562
- }
11563
- // Find the first matching rule
11564
- const matchingRule = rules.find((rule) => rule.condition(context));
11565
- if (!matchingRule) {
11566
- return {
11567
- nextStep: null,
11568
- isActionable: false,
11569
- reason: `No matching transition rule for current context`,
11570
- };
11571
- }
11572
- return {
11573
- nextStep: matchingRule.nextStep,
11574
- isActionable: matchingRule.isActionable,
11575
- reason: matchingRule.reason,
11576
- };
11577
- }
11578
-
11579
- /**
11580
- * Find a step by name in the bridge result.
12649
+ * This schema validates that a string is a properly formatted Solana transaction hash:
12650
+ * - Must be a valid base58-encoded string
12651
+ * - Must be between 86-88 characters long (typical length for base58-encoded 64-byte signatures)
11581
12652
  *
11582
- * @param result - The bridge result to search.
11583
- * @param stepName - The name of the step to find.
11584
- * @returns The step if found, undefined otherwise.
12653
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11585
12654
  *
11586
12655
  * @example
11587
12656
  * ```typescript
11588
- * import { findStepByName } from './findStep'
12657
+ * import { solanaTransactionHashSchema } from '@core/adapter'
11589
12658
  *
11590
- * const burnStep = findStepByName(result, 'burn')
11591
- * if (burnStep) {
11592
- * console.log('Burn tx:', burnStep.txHash)
11593
- * }
12659
+ * const validTxHash = '5VfYmGBjvQKe3xgLtTQPSMEUdpEVHrJwLK7pKBJWKzYpNBE2g3kJrq7RSe9M8DqzQJ5J2aZPTjHLvd4WgxPpJKS'
12660
+ *
12661
+ * const result = solanaTransactionHashSchema.safeParse(validTxHash)
12662
+ * console.log(result.success) // true
11594
12663
  * ```
11595
12664
  */
11596
- function findStepByName(result, stepName) {
11597
- return result.steps.find((step) => step.name === stepName);
11598
- }
12665
+ base58StringSchema.refine((value) => value.length >= 86 && value.length <= 88, 'Solana transaction hash must be between 86-88 characters long (base58-encoded 64-byte signature)');
11599
12666
  /**
11600
- * Find a pending step by name and return it with its index.
12667
+ * Schema for validating Adapter objects.
12668
+ * Checks for the required methods that define an Adapter.
12669
+ */
12670
+ zod.z.object({
12671
+ prepare: zod.z.function(),
12672
+ waitForTransaction: zod.z.function(),
12673
+ getAddress: zod.z.function(),
12674
+ });
12675
+
12676
+ /**
12677
+ * Validate that the adapter has sufficient token balance for a transaction.
11601
12678
  *
11602
- * Searches for a step that matches both the step name and has a pending state.
12679
+ * This function checks if the adapter's current token balance is greater than or equal
12680
+ * to the requested transaction amount. It throws a KitError with code 9001
12681
+ * (BALANCE_INSUFFICIENT_TOKEN) if the balance is insufficient, providing detailed
12682
+ * information about the shortfall.
11603
12683
  *
11604
- * @param result - The bridge result containing steps to search through.
11605
- * @param stepName - The step name to find (e.g., 'burn', 'mint', 'fetchAttestation').
11606
- * @returns An object containing the step and its index in the steps array.
11607
- * @throws KitError if the specified pending step is not found.
12684
+ * @param params - The validation parameters containing adapter, amount, token, and token address.
12685
+ * @returns A promise that resolves to void if validation passes.
12686
+ * @throws {KitError} When the adapter's balance is less than the required amount (code: 9001).
11608
12687
  *
11609
12688
  * @example
11610
12689
  * ```typescript
11611
- * import { findPendingStep } from './findStep'
12690
+ * import { validateBalanceForTransaction } from '@core/adapter'
12691
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
12692
+ * import { isKitError, ERROR_TYPES } from '@core/errors'
11612
12693
  *
11613
- * const { step, index } = findPendingStep(result, 'burn')
11614
- * console.log('Pending step:', step.name, 'at index:', index)
12694
+ * const adapter = createViemAdapterFromPrivateKey({
12695
+ * privateKey: '0x...',
12696
+ * chain: 'Ethereum',
12697
+ * })
12698
+ *
12699
+ * try {
12700
+ * await validateBalanceForTransaction({
12701
+ * adapter,
12702
+ * amount: '1000000', // 1 USDC (6 decimals)
12703
+ * token: 'USDC',
12704
+ * tokenAddress: '0xA0b86a33E6441c8C1c7C16e4c5e3e5b5e4c5e3e5b5e4c5e',
12705
+ * operationContext: { chain: 'Ethereum' },
12706
+ * })
12707
+ * console.log('Balance validation passed')
12708
+ * } catch (error) {
12709
+ * if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
12710
+ * console.error('Insufficient funds:', error.message)
12711
+ * }
12712
+ * }
11615
12713
  * ```
11616
12714
  */
11617
- function findPendingStep(result, stepName) {
11618
- const index = result.steps.findIndex((step) => step.name === stepName && step.state === 'pending');
11619
- if (index === -1) {
11620
- throw new KitError({
11621
- ...InputError.VALIDATION_FAILED,
11622
- recoverability: 'FATAL',
11623
- message: `Pending step "${stepName}" not found in result`,
11624
- });
11625
- }
11626
- const step = result.steps[index];
11627
- if (!step) {
11628
- throw new KitError({
11629
- ...InputError.VALIDATION_FAILED,
11630
- recoverability: 'FATAL',
11631
- message: 'Pending step is undefined',
12715
+ const validateBalanceForTransaction = async (params) => {
12716
+ const { amount, adapter, token, tokenAddress, operationContext } = params;
12717
+ const balancePrepared = await adapter.prepareAction('usdc.balanceOf', {
12718
+ walletAddress: operationContext.address,
12719
+ }, operationContext);
12720
+ const balance = await balancePrepared.execute();
12721
+ if (BigInt(balance) < BigInt(amount)) {
12722
+ // Extract chain name from operationContext
12723
+ const chainName = extractChainInfo(operationContext.chain).name;
12724
+ // Create KitError with rich context in trace
12725
+ throw createInsufficientTokenBalanceError(chainName, token, {
12726
+ balance: balance.toString(),
12727
+ amount,
12728
+ tokenAddress,
12729
+ walletAddress: operationContext.address,
11632
12730
  });
11633
12731
  }
11634
- return { step, index };
11635
- }
12732
+ };
12733
+
11636
12734
  /**
11637
- * Get the burn transaction hash from bridge result.
12735
+ * Validate that the adapter has sufficient native token balance for transaction fees.
11638
12736
  *
11639
- * @param result - The bridge result.
11640
- * @returns The burn transaction hash, or undefined if not found.
12737
+ * This function checks if the adapter's current native token balance (ETH, SOL, etc.)
12738
+ * is greater than zero. It throws a KitError with code 9002 (BALANCE_INSUFFICIENT_GAS)
12739
+ * if the balance is zero, indicating the wallet cannot pay for transaction fees.
12740
+ *
12741
+ * @param params - The validation parameters containing adapter and operation context.
12742
+ * @returns A promise that resolves to void if validation passes.
12743
+ * @throws {KitError} When the adapter's native balance is zero (code: 9002).
11641
12744
  *
11642
12745
  * @example
11643
12746
  * ```typescript
11644
- * import { getBurnTxHash } from './findStep'
12747
+ * import { validateNativeBalanceForTransaction } from '@core/adapter'
12748
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
12749
+ * import { isKitError, ERROR_TYPES } from '@core/errors'
11645
12750
  *
11646
- * const burnTxHash = getBurnTxHash(result)
11647
- * if (burnTxHash) {
11648
- * console.log('Burn tx hash:', burnTxHash)
12751
+ * const adapter = createViemAdapterFromPrivateKey({
12752
+ * privateKey: '0x...',
12753
+ * chain: 'Ethereum',
12754
+ * })
12755
+ *
12756
+ * try {
12757
+ * await validateNativeBalanceForTransaction({
12758
+ * adapter,
12759
+ * operationContext: { chain: 'Ethereum' },
12760
+ * })
12761
+ * console.log('Native balance validation passed')
12762
+ * } catch (error) {
12763
+ * if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
12764
+ * console.error('Insufficient gas funds:', error.message)
12765
+ * }
11649
12766
  * }
11650
12767
  * ```
11651
12768
  */
11652
- function getBurnTxHash(result) {
11653
- return findStepByName(result, CCTPv2StepName.burn)?.txHash;
11654
- }
12769
+ const validateNativeBalanceForTransaction = async (params) => {
12770
+ const { adapter, operationContext } = params;
12771
+ const balancePrepared = await adapter.prepareAction('native.balanceOf', {
12772
+ walletAddress: operationContext.address,
12773
+ }, operationContext);
12774
+ const balance = await balancePrepared.execute();
12775
+ if (BigInt(balance) === 0n) {
12776
+ const { chain } = operationContext;
12777
+ const chainName = extractChainInfo(chain).name;
12778
+ const nativeSymbol = typeof chain === 'object' && chain !== null && 'nativeCurrency' in chain
12779
+ ? chain.nativeCurrency.symbol
12780
+ : undefined;
12781
+ throw createInsufficientGasError(chainName, nativeSymbol, {
12782
+ balance: '0',
12783
+ walletAddress: operationContext.address,
12784
+ });
12785
+ }
12786
+ };
12787
+
11655
12788
  /**
11656
- * Get the attestation data from bridge result.
11657
- *
11658
- * @param result - The bridge result.
11659
- * @returns The attestation data, or undefined if not found.
12789
+ * Permit signature standards for gasless token approvals.
11660
12790
  *
11661
- * @example
11662
- * ```typescript
11663
- * import { getAttestationData } from './findStep'
12791
+ * Defines the permit types that can be used to approve token spending
12792
+ * without requiring a separate approval transaction.
11664
12793
  *
11665
- * const attestation = getAttestationData(result)
11666
- * if (attestation) {
11667
- * console.log('Attestation:', attestation.message)
11668
- * }
11669
- * ```
12794
+ * @remarks
12795
+ * - NONE: No permit, tokens must be pre-approved via separate transaction
12796
+ * - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
11670
12797
  */
11671
- function getAttestationData(result) {
11672
- // Prefer reAttest data (most recent attestation after expiry)
11673
- const reAttestStep = findStepByName(result, CCTPv2StepName.reAttest);
11674
- if (reAttestStep?.state === 'success' && reAttestStep.data) {
11675
- return reAttestStep.data;
11676
- }
11677
- // Fall back to fetchAttestation step
11678
- const fetchStep = findStepByName(result, CCTPv2StepName.fetchAttestation);
11679
- return fetchStep?.data;
11680
- }
12798
+ var PermitType;
12799
+ (function (PermitType) {
12800
+ /** No permit required - tokens must be pre-approved */
12801
+ PermitType[PermitType["NONE"] = 0] = "NONE";
12802
+ /** EIP-2612 standard permit */
12803
+ PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
12804
+ })(PermitType || (PermitType = {}));
11681
12805
 
11682
12806
  /**
11683
12807
  * Determine if a step executes on the source chain.
@@ -11730,169 +12854,6 @@ function getStepAdapterAndChain(step, context, result) {
11730
12854
  };
11731
12855
  }
11732
12856
 
11733
- /**
11734
- * Check if the analysis indicates a non-actionable pending state.
11735
- *
11736
- * A pending state is non-actionable when there's a continuation step but
11737
- * the analysis marks it as not actionable, typically because we need to
11738
- * wait for an ongoing operation to complete.
11739
- *
11740
- * @param analysis - The step analysis result from analyzeSteps.
11741
- * @param result - The bridge result to check for pending steps.
11742
- * @returns True if there is a pending step that we should wait for.
11743
- *
11744
- * @example
11745
- * ```typescript
11746
- * import { hasPendingState } from './stepUtils'
11747
- * import { analyzeSteps } from '../analyzeSteps'
11748
- *
11749
- * const analysis = analyzeSteps(bridgeResult)
11750
- * if (hasPendingState(analysis, bridgeResult)) {
11751
- * // Wait for the pending operation to complete
11752
- * }
11753
- * ```
11754
- */
11755
- function hasPendingState(analysis, result) {
11756
- // Check if there's a continuation step that's marked as non-actionable
11757
- if (analysis.continuationStep === null || analysis.isActionable) {
11758
- return false;
11759
- }
11760
- // Verify that the continuation step actually exists and is in pending state
11761
- const pendingStep = result.steps.find((step) => step.name === analysis.continuationStep && step.state === 'pending');
11762
- return pendingStep !== undefined;
11763
- }
11764
- /**
11765
- * Check if the step is the last one in the execution flow.
11766
- *
11767
- * @param step - The step object to check.
11768
- * @param stepNames - The ordered list of step names in the execution flow.
11769
- * @returns True if this is the last step in the flow.
11770
- *
11771
- * @example
11772
- * ```typescript
11773
- * import { isLastStep } from './stepUtils'
11774
- *
11775
- * const stepNames = ['approve', 'burn', 'fetchAttestation', 'mint']
11776
- * isLastStep({ name: 'mint' }, stepNames) // true
11777
- * isLastStep({ name: 'burn' }, stepNames) // false
11778
- * ```
11779
- */
11780
- function isLastStep(step, stepNames) {
11781
- const stepIndex = stepNames.indexOf(step.name);
11782
- return stepIndex === -1 || stepIndex >= stepNames.length - 1;
11783
- }
11784
- /**
11785
- * Wait for a pending transaction to complete.
11786
- *
11787
- * Poll the adapter until the transaction is confirmed on-chain and return
11788
- * the updated step with success or error state based on the receipt.
11789
- *
11790
- * @param pendingStep - The full step object containing the transaction hash.
11791
- * @param adapter - The adapter to use for waiting.
11792
- * @param chain - The chain where the transaction was submitted.
11793
- * @returns The updated step object with success or error state.
11794
- *
11795
- * @throws KitError when the pending step has no transaction hash.
11796
- *
11797
- * @example
11798
- * ```typescript
11799
- * import { waitForPendingTransaction } from './bridgeStepUtils'
11800
- *
11801
- * const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
11802
- * const updatedStep = await waitForPendingTransaction(pendingStep, adapter, chain)
11803
- * // updatedStep.state is now 'success' or 'error'
11804
- * ```
11805
- */
11806
- async function waitForPendingTransaction(pendingStep, adapter, chain) {
11807
- if (!pendingStep.txHash) {
11808
- throw new KitError({
11809
- ...InputError.VALIDATION_FAILED,
11810
- recoverability: 'FATAL',
11811
- message: `Cannot wait for pending ${pendingStep.name}: no transaction hash available`,
11812
- });
11813
- }
11814
- const txHash = pendingStep.txHash;
11815
- const txReceipt = await retryAsync(async () => adapter.waitForTransaction(txHash, undefined, chain), {
11816
- isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
11817
- });
11818
- // Check if transaction was confirmed on-chain
11819
- if (!txReceipt.blockNumber) {
11820
- return {
11821
- ...pendingStep,
11822
- state: 'error',
11823
- errorMessage: txReceipt.status === 'reverted'
11824
- ? `Transaction ${pendingStep.txHash} was reverted`
11825
- : 'Transaction was not confirmed on-chain',
11826
- data: txReceipt,
11827
- };
11828
- }
11829
- return {
11830
- ...pendingStep,
11831
- state: 'success',
11832
- data: txReceipt,
11833
- };
11834
- }
11835
- /**
11836
- * Wait for a pending step to complete.
11837
- *
11838
- * For transaction steps: waits for the transaction to be confirmed.
11839
- * For attestation: re-executes the attestation fetch.
11840
- *
11841
- * @typeParam TFromAdapterCapabilities - The capabilities of the source adapter.
11842
- * @typeParam TToAdapterCapabilities - The capabilities of the destination adapter.
11843
- * @param pendingStep - The full step object (with name, state, txHash, data, etc.) to resolve.
11844
- * @param adapter - The adapter to use.
11845
- * @param chain - The chain where the step is executing.
11846
- * @param context - The retry context.
11847
- * @param result - The bridge result.
11848
- * @param provider - The CCTP v2 bridging provider.
11849
- * @returns The resolved step object with updated state.
11850
- *
11851
- * @throws KitError when fetching attestation but burn transaction hash is not found.
11852
- *
11853
- * @example
11854
- * ```typescript
11855
- * import { waitForStepToComplete } from './bridgeStepUtils'
11856
- *
11857
- * const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
11858
- * const updatedStep = await waitForStepToComplete(
11859
- * pendingStep,
11860
- * adapter,
11861
- * chain,
11862
- * context,
11863
- * result,
11864
- * provider,
11865
- * )
11866
- * // updatedStep.state is now 'success' or 'error'
11867
- * ```
11868
- */
11869
- async function waitForStepToComplete(pendingStep, adapter, chain, context, result, provider) {
11870
- if (pendingStep.name === CCTPv2StepName.fetchAttestation) {
11871
- // For attestation, re-run the fetch (it has built-in polling)
11872
- const burnTxHash = getBurnTxHash(result);
11873
- if (!burnTxHash) {
11874
- throw new KitError({
11875
- ...InputError.VALIDATION_FAILED,
11876
- recoverability: 'FATAL',
11877
- message: 'Cannot fetch attestation: burn transaction hash not found',
11878
- });
11879
- }
11880
- const sourceAddress = result.source.address;
11881
- const attestation = await provider.fetchAttestation({
11882
- chain: result.source.chain,
11883
- adapter: context.from,
11884
- address: sourceAddress,
11885
- }, burnTxHash);
11886
- return {
11887
- ...pendingStep,
11888
- state: 'success',
11889
- data: attestation,
11890
- };
11891
- }
11892
- // For transaction steps, wait for the transaction to complete
11893
- return waitForPendingTransaction(pendingStep, adapter, chain);
11894
- }
11895
-
11896
12857
  /**
11897
12858
  * Executes a re-attestation operation to obtain a fresh attestation for an expired message.
11898
12859
  *