@circle-fin/provider-cctp-v2 1.6.2 → 1.6.3

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.
Files changed (5) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/index.cjs +1593 -925
  3. package/index.d.ts +470 -4
  4. package/index.mjs +1593 -925
  5. package/package.json +2 -1
package/index.cjs CHANGED
@@ -335,6 +335,57 @@ var BridgeChain;
335
335
  BridgeChain["XDC_Apothem"] = "XDC_Apothem";
336
336
  })(BridgeChain || (BridgeChain = {}));
337
337
  // -----------------------------------------------------------------------------
338
+ // Unified Balance Chain Enum (Gateway V1 Supported Chains)
339
+ // -----------------------------------------------------------------------------
340
+ /**
341
+ * Enumeration of blockchains that support Gateway V1 operations
342
+ * (deposit, spend, balance, delegate, removeFund).
343
+ *
344
+ * Derived from the full {@link Blockchain} enum but filtered to only
345
+ * include chains with active Gateway V1 contract support. When new chains
346
+ * gain Gateway V1 support, they are added to this enum.
347
+ *
348
+ * @enum
349
+ * @category Enums
350
+ *
351
+ * @remarks
352
+ * - This enum is the **canonical source** of Gateway-supported chains.
353
+ * - Use this enum (or its string literals) in unified-balance-kit calls
354
+ * for type safety.
355
+ *
356
+ * @see {@link Blockchain} for the complete list of all known blockchains.
357
+ * @see {@link UnifiedBalanceChainIdentifier} for the type that accepts these values.
358
+ */
359
+ var UnifiedBalanceChain;
360
+ (function (UnifiedBalanceChain) {
361
+ // Mainnet chains with Gateway V1 support
362
+ UnifiedBalanceChain["Arbitrum"] = "Arbitrum";
363
+ UnifiedBalanceChain["Avalanche"] = "Avalanche";
364
+ UnifiedBalanceChain["Base"] = "Base";
365
+ UnifiedBalanceChain["Ethereum"] = "Ethereum";
366
+ UnifiedBalanceChain["HyperEVM"] = "HyperEVM";
367
+ UnifiedBalanceChain["Optimism"] = "Optimism";
368
+ UnifiedBalanceChain["Polygon"] = "Polygon";
369
+ UnifiedBalanceChain["Sei"] = "Sei";
370
+ UnifiedBalanceChain["Solana"] = "Solana";
371
+ UnifiedBalanceChain["Sonic"] = "Sonic";
372
+ UnifiedBalanceChain["Unichain"] = "Unichain";
373
+ UnifiedBalanceChain["World_Chain"] = "World_Chain";
374
+ // Testnet chains with Gateway V1 support
375
+ UnifiedBalanceChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
376
+ UnifiedBalanceChain["Arc_Testnet"] = "Arc_Testnet";
377
+ UnifiedBalanceChain["Avalanche_Fuji"] = "Avalanche_Fuji";
378
+ UnifiedBalanceChain["Base_Sepolia"] = "Base_Sepolia";
379
+ UnifiedBalanceChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
380
+ UnifiedBalanceChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
381
+ UnifiedBalanceChain["Optimism_Sepolia"] = "Optimism_Sepolia";
382
+ UnifiedBalanceChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
383
+ UnifiedBalanceChain["Sei_Testnet"] = "Sei_Testnet";
384
+ UnifiedBalanceChain["Solana_Devnet"] = "Solana_Devnet";
385
+ UnifiedBalanceChain["Sonic_Testnet"] = "Sonic_Testnet";
386
+ UnifiedBalanceChain["Unichain_Sepolia"] = "Unichain_Sepolia";
387
+ UnifiedBalanceChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
388
+ })(UnifiedBalanceChain || (UnifiedBalanceChain = {}));
338
389
  // Earn Chain Enum
339
390
  // -----------------------------------------------------------------------------
340
391
  /**
@@ -669,6 +720,62 @@ const ADAPTER_CONTRACT_EVM_MAINNET = '0x7FB8c7260b63934d8da38aF902f87ae6e284a845
669
720
  * integrations (e.g., Arc Testnet).
670
721
  */
671
722
  const ADAPTER_CONTRACT_EVM_TESTNET = '0xBBD70b01a1CAbc96d5b7b129Ae1AAabdf50dd40b';
723
+ /**
724
+ * The GatewayWallet contract address for EVM mainnet networks.
725
+ *
726
+ * This contract manages wallet operations for Gateway transactions
727
+ * on mainnet environments across EVM-compatible chains.
728
+ */
729
+ const GATEWAY_WALLET_EVM_MAINNET = '0x77777777Dcc4d5A8B6E418Fd04D8997ef11000eE';
730
+ /**
731
+ * The GatewayMinter contract address for EVM mainnet networks.
732
+ *
733
+ * This contract handles minting operations for Gateway transactions
734
+ * on mainnet environments across EVM-compatible chains.
735
+ */
736
+ const GATEWAY_MINTER_EVM_MAINNET = '0x2222222d7164433c4C09B0b0D809a9b52C04C205';
737
+ /**
738
+ * The GatewayWallet contract address for EVM testnet networks.
739
+ *
740
+ * This contract manages wallet operations for Gateway transactions
741
+ * on testnet environments across EVM-compatible chains.
742
+ */
743
+ const GATEWAY_WALLET_EVM_TESTNET = '0x0077777d7EBA4688BDeF3E311b846F25870A19B9';
744
+ /**
745
+ * The GatewayMinter contract address for EVM testnet networks.
746
+ *
747
+ * This contract handles minting operations for Gateway transactions
748
+ * on testnet environments across EVM-compatible chains.
749
+ */
750
+ const GATEWAY_MINTER_EVM_TESTNET = '0x0022222ABE238Cc2C7Bb1f21003F0a260052475B';
751
+ /**
752
+ * The GatewayWallet program address for Solana mainnet.
753
+ *
754
+ * This program manages wallet operations for Gateway transactions
755
+ * on Solana mainnet.
756
+ */
757
+ const GATEWAY_WALLET_SOLANA_MAINNET = 'GATEwy4YxeiEbRJLwB6dXgg7q61e6zBPrMzYj5h1pRXQ';
758
+ /**
759
+ * The GatewayMinter program address for Solana mainnet.
760
+ *
761
+ * This program handles minting operations for Gateway transactions
762
+ * on Solana mainnet.
763
+ */
764
+ const GATEWAY_MINTER_SOLANA_MAINNET = 'GATEm5SoBJiSw1v2Pz1iPBgUYkXzCUJ27XSXhDfSyzVZ';
765
+ /**
766
+ * The GatewayWallet program address for Solana devnet.
767
+ *
768
+ * This program manages wallet operations for Gateway transactions
769
+ * on Solana devnet.
770
+ */
771
+ const GATEWAY_WALLET_SOLANA_DEVNET = 'GATEwdfmYNELfp5wDmmR6noSr2vHnAfBPMm2PvCzX5vu';
772
+ /**
773
+ * The GatewayMinter program address for Solana devnet.
774
+ *
775
+ * This program handles minting operations for Gateway transactions
776
+ * on Solana devnet.
777
+ */
778
+ const GATEWAY_MINTER_SOLANA_DEVNET = 'GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr';
672
779
 
673
780
  /**
674
781
  * Arc Testnet chain definition
@@ -719,6 +826,19 @@ const ArcTestnet = defineChain({
719
826
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
720
827
  adapter: ADAPTER_CONTRACT_EVM_TESTNET,
721
828
  },
829
+ gateway: {
830
+ domain: 26,
831
+ contracts: {
832
+ v1: {
833
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
834
+ minter: GATEWAY_MINTER_EVM_TESTNET,
835
+ },
836
+ },
837
+ forwarderSupported: {
838
+ source: true,
839
+ destination: true,
840
+ },
841
+ },
722
842
  });
723
843
 
724
844
  /**
@@ -769,6 +889,19 @@ const Arbitrum = defineChain({
769
889
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
770
890
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
771
891
  },
892
+ gateway: {
893
+ domain: 3,
894
+ contracts: {
895
+ v1: {
896
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
897
+ minter: GATEWAY_MINTER_EVM_MAINNET,
898
+ },
899
+ },
900
+ forwarderSupported: {
901
+ source: true,
902
+ destination: true,
903
+ },
904
+ },
772
905
  });
773
906
 
774
907
  /**
@@ -818,6 +951,19 @@ const ArbitrumSepolia = defineChain({
818
951
  kitContracts: {
819
952
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
820
953
  },
954
+ gateway: {
955
+ domain: 3,
956
+ contracts: {
957
+ v1: {
958
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
959
+ minter: GATEWAY_MINTER_EVM_TESTNET,
960
+ },
961
+ },
962
+ forwarderSupported: {
963
+ source: true,
964
+ destination: true,
965
+ },
966
+ },
821
967
  });
822
968
 
823
969
  /**
@@ -868,6 +1014,19 @@ const Avalanche = defineChain({
868
1014
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
869
1015
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
870
1016
  },
1017
+ gateway: {
1018
+ domain: 1,
1019
+ contracts: {
1020
+ v1: {
1021
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1022
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1023
+ },
1024
+ },
1025
+ forwarderSupported: {
1026
+ source: true,
1027
+ destination: true,
1028
+ },
1029
+ },
871
1030
  });
872
1031
 
873
1032
  /**
@@ -917,6 +1076,19 @@ const AvalancheFuji = defineChain({
917
1076
  kitContracts: {
918
1077
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
919
1078
  },
1079
+ gateway: {
1080
+ domain: 1,
1081
+ contracts: {
1082
+ v1: {
1083
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1084
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1085
+ },
1086
+ },
1087
+ forwarderSupported: {
1088
+ source: true,
1089
+ destination: true,
1090
+ },
1091
+ },
920
1092
  });
921
1093
 
922
1094
  /**
@@ -967,6 +1139,19 @@ const Base = defineChain({
967
1139
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
968
1140
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
969
1141
  },
1142
+ gateway: {
1143
+ domain: 6,
1144
+ contracts: {
1145
+ v1: {
1146
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1147
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1148
+ },
1149
+ },
1150
+ forwarderSupported: {
1151
+ source: true,
1152
+ destination: true,
1153
+ },
1154
+ },
970
1155
  });
971
1156
 
972
1157
  /**
@@ -1016,6 +1201,19 @@ const BaseSepolia = defineChain({
1016
1201
  kitContracts: {
1017
1202
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1018
1203
  },
1204
+ gateway: {
1205
+ domain: 6,
1206
+ contracts: {
1207
+ v1: {
1208
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1209
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1210
+ },
1211
+ },
1212
+ forwarderSupported: {
1213
+ source: true,
1214
+ destination: true,
1215
+ },
1216
+ },
1019
1217
  });
1020
1218
 
1021
1219
  /**
@@ -1260,7 +1458,10 @@ const Ethereum = defineChain({
1260
1458
  chainId: 1,
1261
1459
  isTestnet: false,
1262
1460
  explorerUrl: 'https://etherscan.io/tx/{hash}',
1263
- rpcEndpoints: ['https://eth.merkle.io', 'https://ethereum.publicnode.com'],
1461
+ rpcEndpoints: [
1462
+ 'https://ethereum-rpc.publicnode.com',
1463
+ 'https://ethereum.publicnode.com',
1464
+ ],
1264
1465
  eurcAddress: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
1265
1466
  usdcAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
1266
1467
  usdtAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7',
@@ -1290,6 +1491,19 @@ const Ethereum = defineChain({
1290
1491
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1291
1492
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1292
1493
  },
1494
+ gateway: {
1495
+ domain: 0,
1496
+ contracts: {
1497
+ v1: {
1498
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1499
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1500
+ },
1501
+ },
1502
+ forwarderSupported: {
1503
+ source: true,
1504
+ destination: true,
1505
+ },
1506
+ },
1293
1507
  });
1294
1508
 
1295
1509
  /**
@@ -1310,7 +1524,7 @@ const EthereumSepolia = defineChain({
1310
1524
  chainId: 11155111,
1311
1525
  isTestnet: true,
1312
1526
  explorerUrl: 'https://sepolia.etherscan.io/tx/{hash}',
1313
- rpcEndpoints: ['https://sepolia.drpc.org'],
1527
+ rpcEndpoints: ['https://ethereum-sepolia-rpc.publicnode.com'],
1314
1528
  eurcAddress: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
1315
1529
  usdcAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
1316
1530
  usdtAddress: null,
@@ -1339,6 +1553,19 @@ const EthereumSepolia = defineChain({
1339
1553
  kitContracts: {
1340
1554
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1341
1555
  },
1556
+ gateway: {
1557
+ domain: 0,
1558
+ contracts: {
1559
+ v1: {
1560
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1561
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1562
+ },
1563
+ },
1564
+ forwarderSupported: {
1565
+ source: true,
1566
+ destination: true,
1567
+ },
1568
+ },
1342
1569
  });
1343
1570
 
1344
1571
  /**
@@ -1433,6 +1660,19 @@ const HyperEVM = defineChain({
1433
1660
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1434
1661
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1435
1662
  },
1663
+ gateway: {
1664
+ domain: 19,
1665
+ contracts: {
1666
+ v1: {
1667
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1668
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1669
+ },
1670
+ },
1671
+ forwarderSupported: {
1672
+ source: true,
1673
+ destination: true,
1674
+ },
1675
+ },
1436
1676
  });
1437
1677
 
1438
1678
  /**
@@ -1477,6 +1717,19 @@ const HyperEVMTestnet = defineChain({
1477
1717
  kitContracts: {
1478
1718
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1479
1719
  },
1720
+ gateway: {
1721
+ domain: 19,
1722
+ contracts: {
1723
+ v1: {
1724
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1725
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1726
+ },
1727
+ },
1728
+ forwarderSupported: {
1729
+ source: true,
1730
+ destination: true,
1731
+ },
1732
+ },
1480
1733
  });
1481
1734
 
1482
1735
  /**
@@ -2011,6 +2264,19 @@ const Optimism = defineChain({
2011
2264
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2012
2265
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2013
2266
  },
2267
+ gateway: {
2268
+ domain: 2,
2269
+ contracts: {
2270
+ v1: {
2271
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2272
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2273
+ },
2274
+ },
2275
+ forwarderSupported: {
2276
+ source: true,
2277
+ destination: true,
2278
+ },
2279
+ },
2014
2280
  });
2015
2281
 
2016
2282
  /**
@@ -2060,6 +2326,19 @@ const OptimismSepolia = defineChain({
2060
2326
  kitContracts: {
2061
2327
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2062
2328
  },
2329
+ gateway: {
2330
+ domain: 2,
2331
+ contracts: {
2332
+ v1: {
2333
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2334
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2335
+ },
2336
+ },
2337
+ forwarderSupported: {
2338
+ source: true,
2339
+ destination: true,
2340
+ },
2341
+ },
2063
2342
  });
2064
2343
 
2065
2344
  /**
@@ -2248,6 +2527,19 @@ const Polygon = defineChain({
2248
2527
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2249
2528
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2250
2529
  },
2530
+ gateway: {
2531
+ domain: 7,
2532
+ contracts: {
2533
+ v1: {
2534
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2535
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2536
+ },
2537
+ },
2538
+ forwarderSupported: {
2539
+ source: true,
2540
+ destination: true,
2541
+ },
2542
+ },
2251
2543
  });
2252
2544
 
2253
2545
  /**
@@ -2297,6 +2589,19 @@ const PolygonAmoy = defineChain({
2297
2589
  kitContracts: {
2298
2590
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2299
2591
  },
2592
+ gateway: {
2593
+ domain: 7,
2594
+ contracts: {
2595
+ v1: {
2596
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2597
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2598
+ },
2599
+ },
2600
+ forwarderSupported: {
2601
+ source: true,
2602
+ destination: true,
2603
+ },
2604
+ },
2300
2605
  });
2301
2606
 
2302
2607
  /**
@@ -2343,6 +2648,19 @@ const Sei = defineChain({
2343
2648
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2344
2649
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2345
2650
  },
2651
+ gateway: {
2652
+ domain: 16,
2653
+ contracts: {
2654
+ v1: {
2655
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2656
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2657
+ },
2658
+ },
2659
+ forwarderSupported: {
2660
+ source: true,
2661
+ destination: true,
2662
+ },
2663
+ },
2346
2664
  });
2347
2665
 
2348
2666
  /**
@@ -2387,6 +2705,19 @@ const SeiTestnet = defineChain({
2387
2705
  kitContracts: {
2388
2706
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2389
2707
  },
2708
+ gateway: {
2709
+ domain: 16,
2710
+ contracts: {
2711
+ v1: {
2712
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2713
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2714
+ },
2715
+ },
2716
+ forwarderSupported: {
2717
+ source: true,
2718
+ destination: true,
2719
+ },
2720
+ },
2390
2721
  });
2391
2722
 
2392
2723
  /**
@@ -2431,6 +2762,19 @@ const Sonic = defineChain({
2431
2762
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2432
2763
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2433
2764
  },
2765
+ gateway: {
2766
+ domain: 13,
2767
+ contracts: {
2768
+ v1: {
2769
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2770
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2771
+ },
2772
+ },
2773
+ forwarderSupported: {
2774
+ source: true,
2775
+ destination: true,
2776
+ },
2777
+ },
2434
2778
  });
2435
2779
 
2436
2780
  /**
@@ -2474,6 +2818,19 @@ const SonicTestnet = defineChain({
2474
2818
  kitContracts: {
2475
2819
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2476
2820
  },
2821
+ gateway: {
2822
+ domain: 13,
2823
+ contracts: {
2824
+ v1: {
2825
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2826
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2827
+ },
2828
+ },
2829
+ forwarderSupported: {
2830
+ source: true,
2831
+ destination: true,
2832
+ },
2833
+ },
2477
2834
  });
2478
2835
 
2479
2836
  /**
@@ -2522,6 +2879,19 @@ const Solana = defineChain({
2522
2879
  kitContracts: {
2523
2880
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
2524
2881
  },
2882
+ gateway: {
2883
+ domain: 5,
2884
+ contracts: {
2885
+ v1: {
2886
+ wallet: GATEWAY_WALLET_SOLANA_MAINNET,
2887
+ minter: GATEWAY_MINTER_SOLANA_MAINNET,
2888
+ },
2889
+ },
2890
+ forwarderSupported: {
2891
+ source: true,
2892
+ destination: false,
2893
+ },
2894
+ },
2525
2895
  });
2526
2896
 
2527
2897
  /**
@@ -2570,6 +2940,19 @@ const SolanaDevnet = defineChain({
2570
2940
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
2571
2941
  },
2572
2942
  rpcEndpoints: ['https://api.devnet.solana.com'],
2943
+ gateway: {
2944
+ domain: 5,
2945
+ contracts: {
2946
+ v1: {
2947
+ wallet: GATEWAY_WALLET_SOLANA_DEVNET,
2948
+ minter: GATEWAY_MINTER_SOLANA_DEVNET,
2949
+ },
2950
+ },
2951
+ forwarderSupported: {
2952
+ source: true,
2953
+ destination: false,
2954
+ },
2955
+ },
2573
2956
  });
2574
2957
 
2575
2958
  /**
@@ -2744,6 +3127,19 @@ const Unichain = defineChain({
2744
3127
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2745
3128
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2746
3129
  },
3130
+ gateway: {
3131
+ domain: 10,
3132
+ contracts: {
3133
+ v1: {
3134
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
3135
+ minter: GATEWAY_MINTER_EVM_MAINNET,
3136
+ },
3137
+ },
3138
+ forwarderSupported: {
3139
+ source: true,
3140
+ destination: true,
3141
+ },
3142
+ },
2747
3143
  });
2748
3144
 
2749
3145
  /**
@@ -2793,6 +3189,19 @@ const UnichainSepolia = defineChain({
2793
3189
  kitContracts: {
2794
3190
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2795
3191
  },
3192
+ gateway: {
3193
+ domain: 10,
3194
+ contracts: {
3195
+ v1: {
3196
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
3197
+ minter: GATEWAY_MINTER_EVM_TESTNET,
3198
+ },
3199
+ },
3200
+ forwarderSupported: {
3201
+ source: true,
3202
+ destination: true,
3203
+ },
3204
+ },
2796
3205
  });
2797
3206
 
2798
3207
  /**
@@ -2837,6 +3246,19 @@ const WorldChain = defineChain({
2837
3246
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2838
3247
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2839
3248
  },
3249
+ gateway: {
3250
+ domain: 14,
3251
+ contracts: {
3252
+ v1: {
3253
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
3254
+ minter: GATEWAY_MINTER_EVM_MAINNET,
3255
+ },
3256
+ },
3257
+ forwarderSupported: {
3258
+ source: true,
3259
+ destination: true,
3260
+ },
3261
+ },
2840
3262
  });
2841
3263
 
2842
3264
  /**
@@ -2883,6 +3305,19 @@ const WorldChainSepolia = defineChain({
2883
3305
  kitContracts: {
2884
3306
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2885
3307
  },
3308
+ gateway: {
3309
+ domain: 14,
3310
+ contracts: {
3311
+ v1: {
3312
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
3313
+ minter: GATEWAY_MINTER_EVM_TESTNET,
3314
+ },
3315
+ },
3316
+ forwarderSupported: {
3317
+ source: true,
3318
+ destination: true,
3319
+ },
3320
+ },
2886
3321
  });
2887
3322
 
2888
3323
  /**
@@ -3162,6 +3597,87 @@ function hasCustomContractSupport(chain, contractType) {
3162
3597
  return (typeof contractAddress === 'string' && contractAddress.trim().length > 0);
3163
3598
  }
3164
3599
 
3600
+ /**
3601
+ * Zod schema for validating Gateway v1 contract addresses.
3602
+ *
3603
+ * @example
3604
+ * ```typescript
3605
+ * gatewayV1ContractsSchema.parse({
3606
+ * wallet: '0x1234567890abcdef1234567890abcdef12345678',
3607
+ * minter: '0xabcdef1234567890abcdef1234567890abcdef12'
3608
+ * })
3609
+ * ```
3610
+ */
3611
+ const gatewayV1ContractsSchema = zod.z
3612
+ .object({
3613
+ wallet: zod.z
3614
+ .string({
3615
+ required_error: 'Gateway wallet address is required. Please provide a valid contract address.',
3616
+ invalid_type_error: 'Gateway wallet address must be a string.',
3617
+ })
3618
+ .min(1, 'Gateway wallet address cannot be empty.'),
3619
+ minter: zod.z
3620
+ .string({
3621
+ required_error: 'Gateway minter address is required. Please provide a valid contract address.',
3622
+ invalid_type_error: 'Gateway minter address must be a string.',
3623
+ })
3624
+ .min(1, 'Gateway minter address cannot be empty.'),
3625
+ })
3626
+ .strict(); // Reject any additional properties not defined in the schema
3627
+ /**
3628
+ * Zod schema for validating the versioned Gateway contracts map.
3629
+ *
3630
+ * @description Mirrors the {@link GatewayContracts} type: a partial map of
3631
+ * protocol versions to their contract addresses, following the same pattern
3632
+ * as {@link CCTPContracts}.
3633
+ *
3634
+ * @example
3635
+ * ```typescript
3636
+ * gatewayContractsSchema.parse({
3637
+ * v1: {
3638
+ * wallet: '0x1234567890abcdef1234567890abcdef12345678',
3639
+ * minter: '0xabcdef1234567890abcdef1234567890abcdef12'
3640
+ * }
3641
+ * })
3642
+ * ```
3643
+ */
3644
+ const gatewayContractsSchema = zod.z
3645
+ .object({
3646
+ v1: gatewayV1ContractsSchema.optional(),
3647
+ })
3648
+ .strict(); // Reject any additional properties not defined in the schema
3649
+ /**
3650
+ * Zod schema for validating the full Gateway configuration.
3651
+ *
3652
+ * @description Mirrors the {@link GatewayConfig} type: a domain number plus
3653
+ * a versioned contracts map, following the same pattern as {@link CCTPConfig}.
3654
+ *
3655
+ * @example
3656
+ * ```typescript
3657
+ * gatewayConfigSchema.parse({
3658
+ * domain: 6,
3659
+ * contracts: {
3660
+ * v1: {
3661
+ * wallet: '0x1234567890abcdef1234567890abcdef12345678',
3662
+ * minter: '0xabcdef1234567890abcdef1234567890abcdef12'
3663
+ * }
3664
+ * }
3665
+ * })
3666
+ * ```
3667
+ */
3668
+ const gatewayConfigSchema = zod.z
3669
+ .object({
3670
+ domain: zod.z.number({
3671
+ required_error: 'Gateway domain is required. Please provide a valid domain number.',
3672
+ invalid_type_error: 'Gateway domain must be a number.',
3673
+ }),
3674
+ contracts: gatewayContractsSchema,
3675
+ forwarderSupported: zod.z.object({
3676
+ source: zod.z.boolean(),
3677
+ destination: zod.z.boolean(),
3678
+ }),
3679
+ })
3680
+ .strict(); // Reject any additional properties not defined in the schema
3165
3681
  /**
3166
3682
  * Base schema for common chain definition properties.
3167
3683
  * This contains all properties shared between EVM and non-EVM chains.
@@ -3200,6 +3716,7 @@ const baseChainDefinitionSchema = zod.z.object({
3200
3716
  adapter: zod.z.string().optional(),
3201
3717
  })
3202
3718
  .optional(),
3719
+ gateway: gatewayConfigSchema.optional(),
3203
3720
  });
3204
3721
  /**
3205
3722
  * Zod schema for validating EVM chain definitions specifically.
@@ -3408,6 +3925,42 @@ zod.z.union([
3408
3925
  `Supported chains: ${Object.values(EarnChain).join(', ')}`,
3409
3926
  })),
3410
3927
  ]);
3928
+ /**
3929
+ * Zod schema for validating unified balance chain identifiers.
3930
+ *
3931
+ * This schema validates that the provided chain is supported for unified balance operations.
3932
+ * It accepts either a UnifiedBalanceChain enum value, a string matching a UnifiedBalanceChain value,
3933
+ * or a ChainDefinition for a supported chain.
3934
+ *
3935
+ * Use this schema when validating chain parameters for unified balance operations to ensure
3936
+ * only Gateway V1-supported chains are accepted at runtime.
3937
+ *
3938
+ * @example
3939
+ * ```typescript
3940
+ * import { unifiedBalanceChainIdentifierSchema } from '@core/chains/validation'
3941
+ * import { UnifiedBalanceChain, Chains } from '@core/chains'
3942
+ *
3943
+ * // Valid - UnifiedBalanceChain enum value
3944
+ * unifiedBalanceChainIdentifierSchema.parse(UnifiedBalanceChain.Ethereum)
3945
+ *
3946
+ * // Valid - string literal
3947
+ * unifiedBalanceChainIdentifierSchema.parse('Ethereum')
3948
+ *
3949
+ * // Invalid - Algorand is not in UnifiedBalanceChain (throws ZodError)
3950
+ * unifiedBalanceChainIdentifierSchema.parse('Algorand')
3951
+ * ```
3952
+ *
3953
+ * @see {@link UnifiedBalanceChain} for the enum of supported chains.
3954
+ */
3955
+ const supportedUnifiedBalanceChains = Object.keys(UnifiedBalanceChain).join(', ');
3956
+ zod.z.union([
3957
+ zod.z.string().refine((val) => val in UnifiedBalanceChain, (val) => ({
3958
+ message: `Chain "${val}" is not supported for unified balance operations. Supported chains: ${supportedUnifiedBalanceChains}.`,
3959
+ })),
3960
+ chainDefinitionSchema$2.refine((chainDef) => chainDef.chain in UnifiedBalanceChain, (chainDef) => ({
3961
+ message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for unified balance operations. Supported chains: ${supportedUnifiedBalanceChains}.`,
3962
+ })),
3963
+ ]);
3411
3964
 
3412
3965
  /**
3413
3966
  * @packageDocumentation
@@ -3716,6 +4269,23 @@ class BridgingProvider {
3716
4269
  }
3717
4270
  }
3718
4271
 
4272
+ /**
4273
+ * Check whether the current runtime is Node.js.
4274
+ *
4275
+ * @returns `true` when running in Node.js, `false` otherwise.
4276
+ *
4277
+ * @example
4278
+ * ```typescript
4279
+ * import { isNodeEnvironment } from '@core/utils'
4280
+ *
4281
+ * if (isNodeEnvironment()) {
4282
+ * console.log('Running in Node.js')
4283
+ * }
4284
+ * ```
4285
+ */
4286
+ const isNodeEnvironment = () => typeof process !== 'undefined' &&
4287
+ typeof process.versions === 'object' &&
4288
+ typeof process.versions.node === 'string';
3719
4289
  /**
3720
4290
  * Detect the runtime environment and return a shortened identifier.
3721
4291
  *
@@ -3723,9 +4293,7 @@ class BridgingProvider {
3723
4293
  */
3724
4294
  const getRuntime = () => {
3725
4295
  // Node.js environment
3726
- if (typeof process !== 'undefined' &&
3727
- typeof process.versions === 'object' &&
3728
- typeof process.versions.node === 'string') {
4296
+ if (isNodeEnvironment()) {
3729
4297
  // Shorten to major version only
3730
4298
  const majorVersion = process.versions.node.split('.')[0] ?? 'unknown';
3731
4299
  return `node/${majorVersion}`;
@@ -4714,6 +5282,7 @@ function createInsufficientTokenBalanceError(chain, token, trace) {
4714
5282
  * as it requires user intervention to add gas funds.
4715
5283
  *
4716
5284
  * @param chain - The blockchain network where the gas check failed
5285
+ * @param nativeToken - Native token symbol (e.g. 'ETH', 'SOL', 'AVAX') for a specific message, defaults to 'native token'
4717
5286
  * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
4718
5287
  * @returns KitError with insufficient gas details
4719
5288
  *
@@ -4722,25 +5291,25 @@ function createInsufficientTokenBalanceError(chain, token, trace) {
4722
5291
  * import { createInsufficientGasError } from '@core/errors'
4723
5292
  *
4724
5293
  * throw createInsufficientGasError('Ethereum')
4725
- * // Message: "Insufficient gas funds on Ethereum"
5294
+ * // Message: "Insufficient native token on Ethereum to cover gas fees"
5295
+ *
5296
+ * throw createInsufficientGasError('Ethereum', 'ETH')
5297
+ * // Message: "Insufficient ETH on Ethereum to cover gas fees"
4726
5298
  * ```
4727
5299
  *
4728
5300
  * @example
4729
5301
  * ```typescript
4730
- * // With trace context for debugging
4731
- * throw createInsufficientGasError('Ethereum', {
5302
+ * throw createInsufficientGasError('Ethereum', 'ETH', {
4732
5303
  * rawError: error,
4733
- * gasRequired: '21000',
4734
- * gasAvailable: '10000',
4735
5304
  * walletAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
4736
5305
  * })
4737
5306
  * ```
4738
5307
  */
4739
- function createInsufficientGasError(chain, trace) {
5308
+ function createInsufficientGasError(chain, nativeToken = 'native token', trace) {
4740
5309
  return new KitError({
4741
5310
  ...BalanceError.INSUFFICIENT_GAS,
4742
5311
  recoverability: 'FATAL',
4743
- message: `Insufficient gas funds on ${chain}`,
5312
+ message: `Insufficient ${nativeToken} on ${chain} to cover gas fees`,
4744
5313
  cause: {
4745
5314
  trace: {
4746
5315
  ...trace,
@@ -5106,7 +5675,7 @@ const SLIPPAGE_REVERT_PATTERN = /slippage|lower than the minimum required|less t
5106
5675
  *
5107
5676
  * @internal
5108
5677
  */
5109
- function handleRevertError(msg, error, context) {
5678
+ function parseRevertError(msg, error, context) {
5110
5679
  const reason = extractRevertReason(msg, error) ?? 'Transaction reverted';
5111
5680
  if (SLIPPAGE_REVERT_PATTERN.test(reason)) {
5112
5681
  return new KitError({
@@ -5195,17 +5764,34 @@ function handleRevertError(msg, error, context) {
5195
5764
  function parseBlockchainError(error, context) {
5196
5765
  const msg = extractMessage(error);
5197
5766
  const token = context.token ?? 'token';
5198
- // Pattern 1: Insufficient balance errors
5767
+ // Pattern 0: Insufficient native gas token errors
5768
+ // Must run BEFORE the generic balance pattern because RPC messages like
5769
+ // "insufficient funds for gas * price + value" and "insufficient funds
5770
+ // for intrinsic transaction cost" contain "insufficient funds" which
5771
+ // would otherwise match the token balance pattern below.
5772
+ if (/insufficient funds for (gas|intrinsic transaction cost)|sender doesn't have enough funds to send tx/i.test(msg)) {
5773
+ return createInsufficientGasError(context.chain, undefined, {
5774
+ rawError: error,
5775
+ });
5776
+ }
5777
+ // Pattern 1: Insufficient token balance errors
5199
5778
  // Matches balance-related errors from ERC20 contracts, native transfers, and Solana programs
5200
5779
  if (/transfer amount exceeds balance|insufficient (balance|funds)|burn amount exceeded/i.test(msg)) {
5201
5780
  return createInsufficientTokenBalanceError(context.chain, token, {
5202
5781
  rawError: error,
5203
5782
  });
5204
5783
  }
5784
+ // Pattern 1b: Gateway-specific contract reverts
5785
+ // Matched before the generic simulation/revert pattern so the error
5786
+ // messages are actionable rather than opaque hex selectors.
5787
+ const gatewayError = parseGatewayContractError(msg, context, error);
5788
+ if (gatewayError !== null) {
5789
+ return gatewayError;
5790
+ }
5205
5791
  // Pattern 2: Simulation and execution reverts
5206
5792
  // Matches contract revert errors and simulation failures
5207
5793
  if (/execution reverted|simulation failed|transaction reverted|transaction failed/i.test(msg)) {
5208
- return handleRevertError(msg, error, context);
5794
+ return parseRevertError(msg, error, context);
5209
5795
  }
5210
5796
  // Pattern 3: Gas-related errors
5211
5797
  // Matches gas estimation failures and gas exhaustion
@@ -5220,10 +5806,6 @@ function parseBlockchainError(error, context) {
5220
5806
  const { txHash, explorerUrl } = normalizeTransactionDetails(context.txHash, context.explorerUrl);
5221
5807
  return createOutOfGasError(context.chain, { rawError: error }, txHash, explorerUrl);
5222
5808
  }
5223
- // Insufficient funds for gas
5224
- if (/insufficient funds for gas/i.test(msg)) {
5225
- return createInsufficientGasError(context.chain, { rawError: error });
5226
- }
5227
5809
  // Pattern 4: Network connectivity errors
5228
5810
  // Matches connection failures, DNS errors, and timeouts
5229
5811
  if (/connection (refused|failed)|network|timeout|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
@@ -5475,6 +6057,59 @@ function extractRevertReason(msg, error) {
5475
6057
  }
5476
6058
  return null;
5477
6059
  }
6060
+ /**
6061
+ * Parses Gateway smart-contract revert selectors into specific KitError types.
6062
+ *
6063
+ * Returns `null` when the message does not match any known Gateway revert,
6064
+ * allowing the caller to fall through to generic patterns.
6065
+ *
6066
+ * @param msg - The extracted error message string.
6067
+ * @param context - Parse error context (chain, token, operation).
6068
+ * @param error - The original raw error for the trace payload.
6069
+ * @returns A KitError if a Gateway pattern matched, otherwise `null`.
6070
+ *
6071
+ * @internal Called by {@link parseBlockchainError} — not re-exported from
6072
+ * the `@core/errors` barrel.
6073
+ *
6074
+ * @example
6075
+ * ```typescript
6076
+ * // Internal usage within parseBlockchainError:
6077
+ * import { parseGatewayContractError } from './parseBlockchainError'
6078
+ *
6079
+ * const gatewayError = parseGatewayContractError(
6080
+ * 'execution reverted: WithdrawalValueExceedsAvailableBalance',
6081
+ * { chain: 'Ethereum', token: 'USDC', operation: 'withdraw' },
6082
+ * new Error('execution reverted'),
6083
+ * )
6084
+ * if (gatewayError !== null) {
6085
+ * // KitError with INSUFFICIENT_TOKEN balance details
6086
+ * console.log(gatewayError.message)
6087
+ * }
6088
+ *
6089
+ * // Returns null for unrecognised reverts (caller falls through to generic parsing)
6090
+ * const unknown = parseGatewayContractError(
6091
+ * 'execution reverted: SomeUnknownError()',
6092
+ * { chain: 'Base', token: 'USDC', operation: 'deposit' },
6093
+ * new Error('execution reverted'),
6094
+ * )
6095
+ * console.log(unknown) // null
6096
+ * ```
6097
+ */
6098
+ function parseGatewayContractError(msg, context, error) {
6099
+ const token = context.token ?? 'token';
6100
+ if (/WithdrawalValueExceedsAvailableBalance|InsufficientDepositBalance/i.test(msg)) {
6101
+ return createInsufficientTokenBalanceError(context.chain, token, {
6102
+ rawError: error,
6103
+ });
6104
+ }
6105
+ if (/WithdrawalNotYetAvailable|WithdrawalDelayNotElapsed/i.test(msg)) {
6106
+ return createTransactionRevertedError(context.chain, 'Withdrawal is not yet available — the withdrawal delay has not elapsed', { rawError: error });
6107
+ }
6108
+ if (/NoWithdrawingBalance/i.test(msg)) {
6109
+ return createTransactionRevertedError(context.chain, 'No pending withdrawal balance — call initiateWithdrawal() first', { rawError: error });
6110
+ }
6111
+ return null;
6112
+ }
5478
6113
 
5479
6114
  /**
5480
6115
  * Type guard to check if an error is a KitError instance.
@@ -6089,12 +6724,12 @@ function validate(value, schema, context) {
6089
6724
  }
6090
6725
 
6091
6726
  /**
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.
6727
+ * Module-level WeakMap for tracking which (schema, validator) combinations
6728
+ * have already processed a given object. This avoids mutating user-supplied
6729
+ * input objects while ensuring re-validation occurs when schemas change.
6095
6730
  * @internal
6096
6731
  */
6097
- const VALIDATION_STATE = Symbol('validationState');
6732
+ const validationStateMap = new WeakMap();
6098
6733
  /**
6099
6734
  * Validates data against a Zod schema with state tracking and enhanced error reporting.
6100
6735
  *
@@ -6111,11 +6746,12 @@ const VALIDATION_STATE = Symbol('validationState');
6111
6746
  *
6112
6747
  * @example
6113
6748
  * ```typescript
6114
- * const result = validateWithStateTracking(BridgeParamsSchema, params, 'bridge parameters')
6749
+ * const VALIDATOR = Symbol('bridgeValidator')
6750
+ * validateWithStateTracking(params, BridgeParamsSchema, 'bridge parameters', VALIDATOR)
6751
+ * // params is now narrowed to the schema's output type
6115
6752
  * ```
6116
6753
  */
6117
6754
  function validateWithStateTracking(value, schema, context, validatorName) {
6118
- // Skip validation for null or undefined values
6119
6755
  if (value === null) {
6120
6756
  throw new KitError({
6121
6757
  ...InputError.VALIDATION_FAILED,
@@ -6140,7 +6776,6 @@ function validateWithStateTracking(value, schema, context, validatorName) {
6140
6776
  },
6141
6777
  });
6142
6778
  }
6143
- // Ensure value is an object that can hold validation state
6144
6779
  if (typeof value !== 'object') {
6145
6780
  throw new KitError({
6146
6781
  ...InputError.VALIDATION_FAILED,
@@ -6153,18 +6788,28 @@ function validateWithStateTracking(value, schema, context, validatorName) {
6153
6788
  },
6154
6789
  });
6155
6790
  }
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)) {
6791
+ const state = validationStateMap.get(value) ?? {
6792
+ validatedSchemasByValidator: new Map(),
6793
+ };
6794
+ // Get the set of schemas that have already validated this object
6795
+ // for the given validator. Create a new WeakSet if this is first time.
6796
+ const validatedSchemas = state.validatedSchemasByValidator.get(validatorName) ?? new WeakSet();
6797
+ if (!(validatedSchemas instanceof WeakSet)) {
6798
+ throw new KitError({
6799
+ ...InputError.VALIDATION_FAILED,
6800
+ recoverability: 'FATAL',
6801
+ message: 'Invalid validation state: expected WeakSet',
6802
+ });
6803
+ }
6804
+ // Check if this exact schema instance has already validated this object
6805
+ if (validatedSchemas.has(schema)) {
6161
6806
  return;
6162
6807
  }
6163
- // Delegate to the validate function for actual validation (now throws KitError)
6164
6808
  validate(value, schema, context);
6165
- // Update validation state
6166
- state.validatedBy.push(validatorName);
6167
- valueWithState[VALIDATION_STATE] = state;
6809
+ // Record that this schema has validated the object for this validator
6810
+ validatedSchemas.add(schema);
6811
+ state.validatedSchemasByValidator.set(validatorName, validatedSchemas);
6812
+ validationStateMap.set(value, state);
6168
6813
  }
6169
6814
 
6170
6815
  /**
@@ -9013,123 +9658,723 @@ async function assertCCTPv2AttestationParams(attestation, params) {
9013
9658
  }
9014
9659
 
9015
9660
  /**
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
9661
+ * CCTP bridge step names that can occur in the bridging flow.
9030
9662
  *
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
- * ```
9663
+ * This object provides type safety for step names and represents all possible
9664
+ * steps that can be executed during a CCTP bridge operation. Using const assertions
9665
+ * makes this tree-shakable and follows modern TypeScript best practices.
9042
9666
  */
9043
- async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, }) {
9044
- const step = { name, state: 'pending' };
9045
- try {
9046
- /**
9047
- * No-op requests are not executed.
9048
- * We return a noop step instead.
9049
- */
9050
- if (request.type === 'noop') {
9051
- step.state = 'noop';
9052
- return step;
9053
- }
9054
- const txHash = await request.execute();
9055
- step.txHash = txHash;
9056
- const retryOptions = {
9057
- isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
9058
- };
9059
- if (timeout !== undefined) {
9060
- retryOptions.deadlineMs = Date.now() + timeout;
9061
- }
9062
- const transaction = await retryAsync(async () => adapter.waitForTransaction(txHash, { confirmations, timeout }, chain), retryOptions);
9063
- step.state = transaction.blockNumber ? 'success' : 'error';
9064
- step.data = transaction;
9065
- // Generate explorer URL for the step
9066
- step.explorerUrl = buildExplorerUrl(chain, txHash);
9067
- if (!transaction.blockNumber) {
9068
- step.errorMessage = 'Transaction was not confirmed on-chain.';
9069
- }
9070
- }
9071
- catch (err) {
9072
- step.state = 'error';
9073
- step.error = err;
9074
- // Optionally parse for common blockchain error formats
9075
- if (err instanceof Error) {
9076
- step.errorMessage = err.message;
9077
- }
9078
- else if (typeof err === 'object' && err != null && 'message' in err) {
9079
- step.errorMessage = String(err.message);
9080
- }
9081
- else {
9082
- step.errorMessage = 'Unknown error occurred during approval step.';
9083
- }
9084
- }
9085
- return step;
9086
- }
9087
-
9667
+ const CCTPv2StepName = {
9668
+ approve: 'approve',
9669
+ burn: 'burn',
9670
+ fetchAttestation: 'fetchAttestation',
9671
+ mint: 'mint',
9672
+ reAttest: 'reAttest',
9673
+ };
9088
9674
  /**
9089
- * Approves the TokenMessenger contract to spend USDC tokens for a bridge operation.
9090
- *
9091
- * This function handles the approval step of the CCTP v2 bridge process, allowing
9092
- * the TokenMessenger contract to spend the specified amount of USDC tokens on behalf
9093
- * of the user. This is a prerequisite for the subsequent burn operation.
9094
- *
9095
- * @param params - The bridge parameters containing source, destination, amount and optional config
9096
- * @param sourceChain - The source chain definition where the approval will occur
9097
- * @returns Promise resolving to the bridge step with transaction details
9098
- * @throws {KitError} If the parameters are invalid
9099
- * @throws {BridgeError} If the approval transaction fails
9675
+ * Conditional step transition rules for CCTP bridge flow.
9100
9676
  *
9101
- * @example
9102
- * ```typescript
9103
- * const approveStep = await approve(params, sourceChain)
9104
- * console.log('Approval tx:', approveStep.transactionHash)
9105
- * ```
9677
+ * Rules are evaluated in order - the first matching condition determines the next step.
9678
+ * This approach supports flexible flow logic and makes it easy to extend with new patterns.
9106
9679
  */
9107
- async function bridgeApproval({ params, provider, }) {
9108
- // Calculate the approval amount
9109
- const customFee = BigInt(params.config?.customFee?.value ?? '0');
9110
- const amountBigInt = BigInt(params.amount);
9111
- const approvalAmount = (amountBigInt + customFee).toString();
9112
- return await executePreparedChainRequest({
9113
- name: 'approve',
9114
- adapter: params.source.adapter,
9115
- chain: params.source.chain,
9116
- request: await provider.approve(params.source, approvalAmount),
9117
- });
9118
- }
9119
-
9120
- /**
9121
- * Executes a deposit-for-burn operation on the source chain to initiate a bridge.
9122
- *
9123
- * This function handles the burning step of the CCTP v2 bridge process, where USDC tokens
9124
- * are burned on the source chain to create a message that can be used to mint equivalent
9125
- * tokens on the destination chain.
9126
- *
9127
- * @param params - The bridge parameters containing source, destination, amount and optional config
9128
- * @param sourceChain - The source chain definition where the burn will occur
9129
- * @returns Promise resolving to the bridge step with transaction details
9130
- * @throws {KitError} If the parameters are invalid
9131
- * @throws \{BridgeError\} If the burn transaction fails
9132
- *
9680
+ const STEP_TRANSITION_RULES = {
9681
+ // Starting state - no steps executed yet
9682
+ '': [
9683
+ {
9684
+ condition: () => true,
9685
+ nextStep: CCTPv2StepName.approve,
9686
+ reason: 'Start with approval step',
9687
+ isActionable: true,
9688
+ },
9689
+ ],
9690
+ // After Approve step
9691
+ [CCTPv2StepName.approve]: [
9692
+ {
9693
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9694
+ nextStep: CCTPv2StepName.burn,
9695
+ reason: 'Approval successful, proceed to burn',
9696
+ isActionable: true,
9697
+ },
9698
+ {
9699
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9700
+ nextStep: CCTPv2StepName.approve,
9701
+ reason: 'Retry failed approval',
9702
+ isActionable: true,
9703
+ },
9704
+ {
9705
+ condition: (ctx) => ctx.lastStep?.state === 'noop',
9706
+ nextStep: CCTPv2StepName.burn,
9707
+ reason: 'No approval needed, proceed to burn',
9708
+ isActionable: true,
9709
+ },
9710
+ {
9711
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9712
+ nextStep: CCTPv2StepName.approve,
9713
+ reason: 'Continue pending approval',
9714
+ isActionable: false, // Waiting for pending transaction
9715
+ },
9716
+ ],
9717
+ // After Burn step
9718
+ [CCTPv2StepName.burn]: [
9719
+ {
9720
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9721
+ nextStep: CCTPv2StepName.fetchAttestation,
9722
+ reason: 'Burn successful, fetch attestation',
9723
+ isActionable: true,
9724
+ },
9725
+ {
9726
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9727
+ nextStep: CCTPv2StepName.burn,
9728
+ reason: 'Retry failed burn',
9729
+ isActionable: true,
9730
+ },
9731
+ {
9732
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9733
+ nextStep: CCTPv2StepName.burn,
9734
+ reason: 'Continue pending burn',
9735
+ isActionable: false, // Waiting for pending transaction
9736
+ },
9737
+ ],
9738
+ // After FetchAttestation step
9739
+ [CCTPv2StepName.fetchAttestation]: [
9740
+ {
9741
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9742
+ nextStep: CCTPv2StepName.mint,
9743
+ reason: 'Attestation fetched, proceed to mint',
9744
+ isActionable: true,
9745
+ },
9746
+ {
9747
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9748
+ nextStep: CCTPv2StepName.fetchAttestation,
9749
+ reason: 'Retry fetching attestation',
9750
+ isActionable: true,
9751
+ },
9752
+ {
9753
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9754
+ nextStep: CCTPv2StepName.fetchAttestation,
9755
+ reason: 'Continue pending attestation fetch',
9756
+ isActionable: false, // Waiting for attestation to be ready
9757
+ },
9758
+ ],
9759
+ // After Mint step
9760
+ [CCTPv2StepName.mint]: [
9761
+ {
9762
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9763
+ nextStep: null,
9764
+ reason: 'Bridge completed successfully',
9765
+ isActionable: false, // Nothing more to do
9766
+ },
9767
+ {
9768
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9769
+ nextStep: CCTPv2StepName.mint,
9770
+ reason: 'Retry failed mint',
9771
+ isActionable: true,
9772
+ },
9773
+ {
9774
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9775
+ nextStep: CCTPv2StepName.mint,
9776
+ reason: 'Continue pending mint',
9777
+ isActionable: false, // Waiting for pending transaction
9778
+ },
9779
+ ],
9780
+ // After ReAttest step
9781
+ [CCTPv2StepName.reAttest]: [
9782
+ {
9783
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9784
+ nextStep: CCTPv2StepName.mint,
9785
+ reason: 'Re-attestation successful, proceed to mint',
9786
+ isActionable: true,
9787
+ },
9788
+ {
9789
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9790
+ nextStep: CCTPv2StepName.mint,
9791
+ reason: 'Re-attestation failed, retry mint to re-initiate recovery',
9792
+ isActionable: true,
9793
+ },
9794
+ {
9795
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9796
+ nextStep: CCTPv2StepName.mint,
9797
+ reason: 'Re-attestation pending, retry mint to re-initiate recovery',
9798
+ isActionable: true,
9799
+ },
9800
+ ],
9801
+ };
9802
+ /**
9803
+ * Analyze bridge steps to determine retry feasibility and continuation point.
9804
+ *
9805
+ * This function examines the current state of bridge steps to determine the optimal
9806
+ * continuation strategy. It uses a rule-based approach that makes it easy to extend
9807
+ * with new flow patterns and step types in the future.
9808
+ *
9809
+ * The current analysis supports the standard CCTP flow:
9810
+ * **Traditional flow**: Approve → Burn → FetchAttestation → Mint
9811
+ *
9812
+ * Key features:
9813
+ * - Rule-based transitions: Easy to extend with new step types and logic
9814
+ * - Context-aware decisions: Considers execution history and step states
9815
+ * - Actionable logic: Distinguishes between steps requiring user action vs waiting
9816
+ * - Terminal states: Properly handles completion and non-actionable states
9817
+ *
9818
+ * @param bridgeResult - The bridge result containing step execution history.
9819
+ * @returns Analysis result with continuation step and actionability information.
9820
+ * @throws Error when bridgeResult is invalid or contains no steps array.
9821
+ *
9822
+ * @example
9823
+ * ```typescript
9824
+ * import { analyzeSteps } from './analyzeSteps'
9825
+ *
9826
+ * // Failed approval step (requires user action)
9827
+ * const bridgeResult = {
9828
+ * steps: [
9829
+ * { name: 'Approve', state: 'error', errorMessage: 'User rejected' }
9830
+ * ]
9831
+ * }
9832
+ *
9833
+ * const analysis = analyzeSteps(bridgeResult)
9834
+ * // Result: { continuationStep: 'Approve', isRetryable: true,
9835
+ * // reason: 'Retry failed approval' }
9836
+ * ```
9837
+ *
9838
+ * @example
9839
+ * ```typescript
9840
+ * // Pending transaction (requires waiting, not actionable)
9841
+ * const bridgeResult = {
9842
+ * steps: [
9843
+ * { name: 'Approve', state: 'pending' }
9844
+ * ]
9845
+ * }
9846
+ *
9847
+ * const analysis = analyzeSteps(bridgeResult)
9848
+ * // Result: { continuationStep: 'Approve', isRetryable: false,
9849
+ * // reason: 'Continue pending approval' }
9850
+ * ```
9851
+ *
9852
+ * @example
9853
+ * ```typescript
9854
+ * // Completed bridge (nothing to do)
9855
+ * const bridgeResult = {
9856
+ * steps: [
9857
+ * { name: 'Approve', state: 'success' },
9858
+ * { name: 'Burn', state: 'success' },
9859
+ * { name: 'FetchAttestation', state: 'success' },
9860
+ * { name: 'Mint', state: 'success' }
9861
+ * ]
9862
+ * }
9863
+ *
9864
+ * const analysis = analyzeSteps(bridgeResult)
9865
+ * // Result: { continuationStep: null, isRetryable: false,
9866
+ * // reason: 'Bridge completed successfully' }
9867
+ * ```
9868
+ */
9869
+ const analyzeSteps = (bridgeResult) => {
9870
+ // Input validation
9871
+ if (!bridgeResult || !Array.isArray(bridgeResult.steps)) {
9872
+ throw new Error('Invalid bridgeResult: must contain a steps array');
9873
+ }
9874
+ const { steps } = bridgeResult;
9875
+ // Build execution context from step history
9876
+ const context = buildFlowContext(steps);
9877
+ // Determine continuation logic using rule engine
9878
+ const continuation = determineContinuationFromRules(context);
9879
+ return {
9880
+ continuationStep: continuation.nextStep,
9881
+ isActionable: continuation.isActionable,
9882
+ completedSteps: Array.from(context.completedSteps),
9883
+ failedSteps: Array.from(context.failedSteps),
9884
+ reason: continuation.reason,
9885
+ };
9886
+ };
9887
+ /**
9888
+ * Build flow context from the execution history.
9889
+ *
9890
+ * @param steps - Array of executed bridge steps.
9891
+ * @returns Flow context with execution state and history.
9892
+ */
9893
+ function buildFlowContext(steps) {
9894
+ const completedSteps = new Set();
9895
+ const failedSteps = new Set();
9896
+ let lastStep;
9897
+ // Process step history to build context
9898
+ for (const step of steps) {
9899
+ if (step.state === 'success' || step.state === 'noop') {
9900
+ completedSteps.add(step.name);
9901
+ }
9902
+ else if (step.state === 'error') {
9903
+ failedSteps.add(step.name);
9904
+ }
9905
+ // Track the last step for continuation logic
9906
+ lastStep = {
9907
+ name: step.name,
9908
+ state: step.state,
9909
+ };
9910
+ }
9911
+ return {
9912
+ completedSteps,
9913
+ failedSteps,
9914
+ ...(lastStep && { lastStep }),
9915
+ };
9916
+ }
9917
+ /**
9918
+ * Determine continuation step using the rule engine.
9919
+ *
9920
+ * @param context - The flow context with execution history.
9921
+ * @returns Continuation decision with next step and actionability information.
9922
+ */
9923
+ function determineContinuationFromRules(context) {
9924
+ const lastStepName = context.lastStep?.name;
9925
+ // Handle initial state when no steps have been executed
9926
+ if (lastStepName === undefined) {
9927
+ const rules = STEP_TRANSITION_RULES[''];
9928
+ const matchingRule = rules?.find((rule) => rule.condition(context));
9929
+ if (!matchingRule) {
9930
+ return {
9931
+ nextStep: null,
9932
+ isActionable: false,
9933
+ reason: 'No initial state rule found',
9934
+ };
9935
+ }
9936
+ return {
9937
+ nextStep: matchingRule.nextStep,
9938
+ isActionable: matchingRule.isActionable,
9939
+ reason: matchingRule.reason,
9940
+ };
9941
+ }
9942
+ // A step with an empty name is ambiguous and should be treated as an unrecoverable state.
9943
+ if (lastStepName === '') {
9944
+ return {
9945
+ nextStep: null,
9946
+ isActionable: false,
9947
+ reason: 'No transition rules defined for step with empty name',
9948
+ };
9949
+ }
9950
+ const rules = STEP_TRANSITION_RULES[lastStepName];
9951
+ if (!rules) {
9952
+ return {
9953
+ nextStep: null,
9954
+ isActionable: false,
9955
+ reason: `No transition rules defined for step: ${lastStepName}`,
9956
+ };
9957
+ }
9958
+ // Find the first matching rule
9959
+ const matchingRule = rules.find((rule) => rule.condition(context));
9960
+ if (!matchingRule) {
9961
+ return {
9962
+ nextStep: null,
9963
+ isActionable: false,
9964
+ reason: `No matching transition rule for current context`,
9965
+ };
9966
+ }
9967
+ return {
9968
+ nextStep: matchingRule.nextStep,
9969
+ isActionable: matchingRule.isActionable,
9970
+ reason: matchingRule.reason,
9971
+ };
9972
+ }
9973
+
9974
+ /**
9975
+ * Find a step by name in the bridge result.
9976
+ *
9977
+ * @param result - The bridge result to search.
9978
+ * @param stepName - The name of the step to find.
9979
+ * @returns The step if found, undefined otherwise.
9980
+ *
9981
+ * @example
9982
+ * ```typescript
9983
+ * import { findStepByName } from './findStep'
9984
+ *
9985
+ * const burnStep = findStepByName(result, 'burn')
9986
+ * if (burnStep) {
9987
+ * console.log('Burn tx:', burnStep.txHash)
9988
+ * }
9989
+ * ```
9990
+ */
9991
+ function findStepByName(result, stepName) {
9992
+ return result.steps.find((step) => step.name === stepName);
9993
+ }
9994
+ /**
9995
+ * Find a pending step by name and return it with its index.
9996
+ *
9997
+ * Searches for a step that matches both the step name and has a pending state.
9998
+ *
9999
+ * @param result - The bridge result containing steps to search through.
10000
+ * @param stepName - The step name to find (e.g., 'burn', 'mint', 'fetchAttestation').
10001
+ * @returns An object containing the step and its index in the steps array.
10002
+ * @throws KitError if the specified pending step is not found.
10003
+ *
10004
+ * @example
10005
+ * ```typescript
10006
+ * import { findPendingStep } from './findStep'
10007
+ *
10008
+ * const { step, index } = findPendingStep(result, 'burn')
10009
+ * console.log('Pending step:', step.name, 'at index:', index)
10010
+ * ```
10011
+ */
10012
+ function findPendingStep(result, stepName) {
10013
+ const index = result.steps.findIndex((step) => step.name === stepName && step.state === 'pending');
10014
+ if (index === -1) {
10015
+ throw new KitError({
10016
+ ...InputError.VALIDATION_FAILED,
10017
+ recoverability: 'FATAL',
10018
+ message: `Pending step "${stepName}" not found in result`,
10019
+ });
10020
+ }
10021
+ const step = result.steps[index];
10022
+ if (!step) {
10023
+ throw new KitError({
10024
+ ...InputError.VALIDATION_FAILED,
10025
+ recoverability: 'FATAL',
10026
+ message: 'Pending step is undefined',
10027
+ });
10028
+ }
10029
+ return { step, index };
10030
+ }
10031
+ /**
10032
+ * Get the burn transaction hash from bridge result.
10033
+ *
10034
+ * @param result - The bridge result.
10035
+ * @returns The burn transaction hash, or undefined if not found.
10036
+ *
10037
+ * @example
10038
+ * ```typescript
10039
+ * import { getBurnTxHash } from './findStep'
10040
+ *
10041
+ * const burnTxHash = getBurnTxHash(result)
10042
+ * if (burnTxHash) {
10043
+ * console.log('Burn tx hash:', burnTxHash)
10044
+ * }
10045
+ * ```
10046
+ */
10047
+ function getBurnTxHash(result) {
10048
+ return findStepByName(result, CCTPv2StepName.burn)?.txHash;
10049
+ }
10050
+ /**
10051
+ * Get the attestation data from bridge result.
10052
+ *
10053
+ * @param result - The bridge result.
10054
+ * @returns The attestation data, or undefined if not found.
10055
+ *
10056
+ * @example
10057
+ * ```typescript
10058
+ * import { getAttestationData } from './findStep'
10059
+ *
10060
+ * const attestation = getAttestationData(result)
10061
+ * if (attestation) {
10062
+ * console.log('Attestation:', attestation.message)
10063
+ * }
10064
+ * ```
10065
+ */
10066
+ function getAttestationData(result) {
10067
+ // Prefer reAttest data (most recent attestation after expiry)
10068
+ const reAttestStep = findStepByName(result, CCTPv2StepName.reAttest);
10069
+ if (reAttestStep?.state === 'success' && reAttestStep.data) {
10070
+ return reAttestStep.data;
10071
+ }
10072
+ // Fall back to fetchAttestation step
10073
+ const fetchStep = findStepByName(result, CCTPv2StepName.fetchAttestation);
10074
+ return fetchStep?.data;
10075
+ }
10076
+
10077
+ /**
10078
+ * Check if the analysis indicates a non-actionable pending state.
10079
+ *
10080
+ * A pending state is non-actionable when there's a continuation step but
10081
+ * the analysis marks it as not actionable, typically because we need to
10082
+ * wait for an ongoing operation to complete.
10083
+ *
10084
+ * @param analysis - The step analysis result from analyzeSteps.
10085
+ * @param result - The bridge result to check for pending steps.
10086
+ * @returns True if there is a pending step that we should wait for.
10087
+ *
10088
+ * @example
10089
+ * ```typescript
10090
+ * import { hasPendingState } from './stepUtils'
10091
+ * import { analyzeSteps } from '../analyzeSteps'
10092
+ *
10093
+ * const analysis = analyzeSteps(bridgeResult)
10094
+ * if (hasPendingState(analysis, bridgeResult)) {
10095
+ * // Wait for the pending operation to complete
10096
+ * }
10097
+ * ```
10098
+ */
10099
+ /**
10100
+ * Evaluate a transaction receipt and return the corresponding step state
10101
+ * and error message. Centralises the success/revert/unconfirmed logic so
10102
+ * every call-site behaves identically.
10103
+ *
10104
+ * @param receipt - The transaction receipt containing status and block info.
10105
+ * @param txHash - The transaction hash used in error messages.
10106
+ * @returns An object with `state` and an optional `errorMessage`.
10107
+ *
10108
+ * @example
10109
+ * ```typescript
10110
+ * const outcome = evaluateTransactionOutcome(receipt, '0xabc...')
10111
+ * step.state = outcome.state
10112
+ * if (outcome.errorMessage) step.errorMessage = outcome.errorMessage
10113
+ * ```
10114
+ */
10115
+ function evaluateTransactionOutcome(receipt, txHash) {
10116
+ if (receipt.status === 'success' && receipt.blockNumber) {
10117
+ return { state: 'success' };
10118
+ }
10119
+ return {
10120
+ state: 'error',
10121
+ errorMessage: receipt.status === 'reverted'
10122
+ ? `Transaction ${txHash} was reverted`
10123
+ : 'Transaction was not confirmed on-chain',
10124
+ };
10125
+ }
10126
+ function hasPendingState(analysis, result) {
10127
+ // Check if there's a continuation step that's marked as non-actionable
10128
+ if (analysis.continuationStep === null || analysis.isActionable) {
10129
+ return false;
10130
+ }
10131
+ // Verify that the continuation step actually exists and is in pending state
10132
+ const pendingStep = result.steps.find((step) => step.name === analysis.continuationStep && step.state === 'pending');
10133
+ return pendingStep !== undefined;
10134
+ }
10135
+ /**
10136
+ * Check if the step is the last one in the execution flow.
10137
+ *
10138
+ * @param step - The step object to check.
10139
+ * @param stepNames - The ordered list of step names in the execution flow.
10140
+ * @returns True if this is the last step in the flow.
10141
+ *
10142
+ * @example
10143
+ * ```typescript
10144
+ * import { isLastStep } from './stepUtils'
10145
+ *
10146
+ * const stepNames = ['approve', 'burn', 'fetchAttestation', 'mint']
10147
+ * isLastStep({ name: 'mint' }, stepNames) // true
10148
+ * isLastStep({ name: 'burn' }, stepNames) // false
10149
+ * ```
10150
+ */
10151
+ function isLastStep(step, stepNames) {
10152
+ const stepIndex = stepNames.indexOf(step.name);
10153
+ return stepIndex === -1 || stepIndex >= stepNames.length - 1;
10154
+ }
10155
+ /**
10156
+ * Wait for a pending transaction to complete.
10157
+ *
10158
+ * Poll the adapter until the transaction is confirmed on-chain and return
10159
+ * the updated step with success or error state based on the receipt.
10160
+ *
10161
+ * @param pendingStep - The full step object containing the transaction hash.
10162
+ * @param adapter - The adapter to use for waiting.
10163
+ * @param chain - The chain where the transaction was submitted.
10164
+ * @returns The updated step object with success or error state.
10165
+ *
10166
+ * @throws KitError when the pending step has no transaction hash.
10167
+ *
10168
+ * @example
10169
+ * ```typescript
10170
+ * import { waitForPendingTransaction } from './bridgeStepUtils'
10171
+ *
10172
+ * const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
10173
+ * const updatedStep = await waitForPendingTransaction(pendingStep, adapter, chain)
10174
+ * // updatedStep.state is now 'success' or 'error'
10175
+ * ```
10176
+ */
10177
+ async function waitForPendingTransaction(pendingStep, adapter, chain) {
10178
+ if (!pendingStep.txHash) {
10179
+ throw new KitError({
10180
+ ...InputError.VALIDATION_FAILED,
10181
+ recoverability: 'FATAL',
10182
+ message: `Cannot wait for pending ${pendingStep.name}: no transaction hash available`,
10183
+ });
10184
+ }
10185
+ const txHash = pendingStep.txHash;
10186
+ const txReceipt = await retryAsync(async () => adapter.waitForTransaction(txHash, undefined, chain), {
10187
+ isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
10188
+ });
10189
+ const outcome = evaluateTransactionOutcome(txReceipt, txHash);
10190
+ return {
10191
+ ...pendingStep,
10192
+ state: outcome.state,
10193
+ data: txReceipt,
10194
+ explorerUrl: buildExplorerUrl(chain, txHash),
10195
+ ...(outcome.errorMessage ? { errorMessage: outcome.errorMessage } : {}),
10196
+ };
10197
+ }
10198
+ /**
10199
+ * Wait for a pending step to complete.
10200
+ *
10201
+ * For transaction steps: waits for the transaction to be confirmed.
10202
+ * For attestation: re-executes the attestation fetch.
10203
+ *
10204
+ * @typeParam TFromAdapterCapabilities - The capabilities of the source adapter.
10205
+ * @typeParam TToAdapterCapabilities - The capabilities of the destination adapter.
10206
+ * @param pendingStep - The full step object (with name, state, txHash, data, etc.) to resolve.
10207
+ * @param adapter - The adapter to use.
10208
+ * @param chain - The chain where the step is executing.
10209
+ * @param context - The retry context.
10210
+ * @param result - The bridge result.
10211
+ * @param provider - The CCTP v2 bridging provider.
10212
+ * @returns The resolved step object with updated state.
10213
+ *
10214
+ * @throws KitError when fetching attestation but burn transaction hash is not found.
10215
+ *
10216
+ * @example
10217
+ * ```typescript
10218
+ * import { waitForStepToComplete } from './bridgeStepUtils'
10219
+ *
10220
+ * const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
10221
+ * const updatedStep = await waitForStepToComplete(
10222
+ * pendingStep,
10223
+ * adapter,
10224
+ * chain,
10225
+ * context,
10226
+ * result,
10227
+ * provider,
10228
+ * )
10229
+ * // updatedStep.state is now 'success' or 'error'
10230
+ * ```
10231
+ */
10232
+ async function waitForStepToComplete(pendingStep, adapter, chain, context, result, provider) {
10233
+ if (pendingStep.name === CCTPv2StepName.fetchAttestation) {
10234
+ // For attestation, re-run the fetch (it has built-in polling)
10235
+ const burnTxHash = getBurnTxHash(result);
10236
+ if (!burnTxHash) {
10237
+ throw new KitError({
10238
+ ...InputError.VALIDATION_FAILED,
10239
+ recoverability: 'FATAL',
10240
+ message: 'Cannot fetch attestation: burn transaction hash not found',
10241
+ });
10242
+ }
10243
+ const sourceAddress = result.source.address;
10244
+ const attestation = await provider.fetchAttestation({
10245
+ chain: result.source.chain,
10246
+ adapter: context.from,
10247
+ address: sourceAddress,
10248
+ }, burnTxHash);
10249
+ return {
10250
+ ...pendingStep,
10251
+ state: 'success',
10252
+ data: attestation,
10253
+ };
10254
+ }
10255
+ // For transaction steps, wait for the transaction to complete
10256
+ return waitForPendingTransaction(pendingStep, adapter, chain);
10257
+ }
10258
+
10259
+ /**
10260
+ * Executes a prepared chain request and returns the result as a bridge step.
10261
+ *
10262
+ * This function takes a prepared chain request (containing transaction data) and executes
10263
+ * it using the appropriate adapter. It handles the execution details and formats
10264
+ * the result as a standardized bridge step with transaction details and explorer URLs.
10265
+ *
10266
+ * @param params - The execution parameters containing:
10267
+ * - `name`: The name of the step
10268
+ * - `request`: The prepared chain request containing transaction data
10269
+ * - `adapter`: The adapter that will execute the transaction
10270
+ * - `confirmations`: The number of confirmations to wait for (defaults to 1)
10271
+ * - `timeout`: The timeout for the request in milliseconds
10272
+ * @returns The bridge step with the transaction details and explorer URL
10273
+ * @throws If the transaction execution fails
10274
+ *
10275
+ * @example
10276
+ * ```typescript
10277
+ * const step = await executePreparedChainRequest({
10278
+ * name: 'approve',
10279
+ * request: preparedRequest,
10280
+ * adapter: adapter,
10281
+ * confirmations: 2,
10282
+ * timeout: 30000
10283
+ * })
10284
+ * console.log('Transaction hash:', step.txHash)
10285
+ * ```
10286
+ */
10287
+ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, }) {
10288
+ const step = { name, state: 'pending' };
10289
+ try {
10290
+ /**
10291
+ * No-op requests are not executed.
10292
+ * We return a noop step instead.
10293
+ */
10294
+ if (request.type === 'noop') {
10295
+ step.state = 'noop';
10296
+ return step;
10297
+ }
10298
+ const txHash = await request.execute();
10299
+ step.txHash = txHash;
10300
+ const retryOptions = {
10301
+ isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
10302
+ };
10303
+ if (timeout !== undefined) {
10304
+ retryOptions.deadlineMs = Date.now() + timeout;
10305
+ }
10306
+ const transaction = await retryAsync(async () => adapter.waitForTransaction(txHash, { confirmations, timeout }, chain), retryOptions);
10307
+ const outcome = evaluateTransactionOutcome(transaction, txHash);
10308
+ step.state = outcome.state;
10309
+ step.data = transaction;
10310
+ // Generate explorer URL for the step
10311
+ step.explorerUrl = buildExplorerUrl(chain, txHash);
10312
+ if (outcome.errorMessage) {
10313
+ step.errorMessage = outcome.errorMessage;
10314
+ }
10315
+ }
10316
+ catch (err) {
10317
+ step.state = 'error';
10318
+ step.error = err;
10319
+ // Optionally parse for common blockchain error formats
10320
+ if (err instanceof Error) {
10321
+ step.errorMessage = err.message;
10322
+ }
10323
+ else if (typeof err === 'object' && err != null && 'message' in err) {
10324
+ step.errorMessage = String(err.message);
10325
+ }
10326
+ else {
10327
+ step.errorMessage = `Unknown error occurred during ${name} step.`;
10328
+ }
10329
+ }
10330
+ return step;
10331
+ }
10332
+
10333
+ /**
10334
+ * Approves the TokenMessenger contract to spend USDC tokens for a bridge operation.
10335
+ *
10336
+ * This function handles the approval step of the CCTP v2 bridge process, allowing
10337
+ * the TokenMessenger contract to spend the specified amount of USDC tokens on behalf
10338
+ * of the user. This is a prerequisite for the subsequent burn operation.
10339
+ *
10340
+ * @param params - The bridge parameters containing source, destination, amount and optional config
10341
+ * @param sourceChain - The source chain definition where the approval will occur
10342
+ * @returns Promise resolving to the bridge step with transaction details
10343
+ * @throws {KitError} If the parameters are invalid
10344
+ * @throws {BridgeError} If the approval transaction fails
10345
+ *
10346
+ * @example
10347
+ * ```typescript
10348
+ * const approveStep = await approve(params, sourceChain)
10349
+ * console.log('Approval tx:', approveStep.transactionHash)
10350
+ * ```
10351
+ */
10352
+ async function bridgeApproval({ params, provider, }) {
10353
+ // Calculate the approval amount
10354
+ const customFee = BigInt(params.config?.customFee?.value ?? '0');
10355
+ const amountBigInt = BigInt(params.amount);
10356
+ const approvalAmount = (amountBigInt + customFee).toString();
10357
+ return await executePreparedChainRequest({
10358
+ name: 'approve',
10359
+ adapter: params.source.adapter,
10360
+ chain: params.source.chain,
10361
+ request: await provider.approve(params.source, approvalAmount),
10362
+ });
10363
+ }
10364
+
10365
+ /**
10366
+ * Executes a deposit-for-burn operation on the source chain to initiate a bridge.
10367
+ *
10368
+ * This function handles the burning step of the CCTP v2 bridge process, where USDC tokens
10369
+ * are burned on the source chain to create a message that can be used to mint equivalent
10370
+ * tokens on the destination chain.
10371
+ *
10372
+ * @param params - The bridge parameters containing source, destination, amount and optional config
10373
+ * @param sourceChain - The source chain definition where the burn will occur
10374
+ * @returns Promise resolving to the bridge step with transaction details
10375
+ * @throws {KitError} If the parameters are invalid
10376
+ * @throws \{BridgeError\} If the burn transaction fails
10377
+ *
9133
10378
  * @example
9134
10379
  * ```typescript
9135
10380
  * const burnStep = await burn(params, sourceChain)
@@ -10680,10 +11925,11 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
10680
11925
  txHash: receipt.txHash,
10681
11926
  })),
10682
11927
  });
10683
- step.state = transaction.blockNumber === undefined ? 'error' : 'success';
11928
+ const outcome = evaluateTransactionOutcome(transaction, receipt.txHash);
11929
+ step.state = outcome.state;
10684
11930
  step.data = transaction;
10685
- if (transaction.blockNumber === undefined) {
10686
- step.errorMessage = 'Transaction was not confirmed on-chain.';
11931
+ if (outcome.errorMessage) {
11932
+ step.errorMessage = outcome.errorMessage;
10687
11933
  }
10688
11934
  }
10689
11935
  catch (err) {
@@ -10695,7 +11941,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
10695
11941
  return step;
10696
11942
  }
10697
11943
 
10698
- var version = "1.6.2";
11944
+ var version = "1.6.3";
10699
11945
  var pkg = {
10700
11946
  version: version};
10701
11947
 
@@ -11003,681 +12249,266 @@ const hexStringSchema = zod.z
11003
12249
  .refine((value) => value.trim().length > 0, 'Hex string cannot be empty')
11004
12250
  .refine((value) => value.startsWith('0x'), 'Hex string must start with 0x prefix')
11005
12251
  .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
-
12252
+ const hexPattern = /^0x[0-9a-fA-F]+$/;
12253
+ return hexPattern.test(value);
12254
+ }, 'Hex string contains invalid characters. Only hexadecimal characters (0-9, a-f, A-F) are allowed after 0x');
11195
12255
  /**
11196
- * Validate that the adapter has sufficient native token balance for transaction fees.
12256
+ * Schema for validating EVM addresses.
11197
12257
  *
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.
12258
+ * This schema validates that a string is a properly formatted EVM address:
12259
+ * - Must be a valid hex string with '0x' prefix
12260
+ * - Must be exactly 42 characters long (0x + 40 hex characters)
11201
12261
  *
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).
12262
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11205
12263
  *
11206
12264
  * @example
11207
12265
  * ```typescript
11208
- * import { validateNativeBalanceForTransaction } from '@core/adapter'
11209
- * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
11210
- * import { isKitError, ERROR_TYPES } from '@core/errors'
12266
+ * import { evmAddressSchema } from '@core/adapter'
11211
12267
  *
11212
- * const adapter = createViemAdapterFromPrivateKey({
11213
- * privateKey: '0x...',
11214
- * chain: 'Ethereum',
11215
- * })
12268
+ * const validAddress = '0x1234567890123456789012345678901234567890'
11216
12269
  *
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
- * }
12270
+ * const result = evmAddressSchema.safeParse(validAddress)
12271
+ * console.log(result.success) // true
11228
12272
  * ```
11229
12273
  */
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
-
12274
+ hexStringSchema.refine((value) => value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)');
11247
12275
  /**
11248
- * Permit signature standards for gasless token approvals.
12276
+ * Schema for validating transaction hashes.
11249
12277
  *
11250
- * Defines the permit types that can be used to approve token spending
11251
- * without requiring a separate approval transaction.
12278
+ * This schema validates that a string is a properly formatted transaction hash:
12279
+ * - Must be a valid hex string with '0x' prefix
12280
+ * - Must be exactly 66 characters long (0x + 64 hex characters)
11252
12281
  *
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.
12282
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11267
12283
  *
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.
12284
+ * @example
12285
+ * ```typescript
12286
+ * import { evmTransactionHashSchema } from '@core/adapter'
11281
12287
  *
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.
12288
+ * const validTxHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
12289
+ *
12290
+ * const result = evmTransactionHashSchema.safeParse(validTxHash)
12291
+ * console.log(result.success) // true
12292
+ * ```
11284
12293
  */
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
- };
12294
+ hexStringSchema.refine((value) => value.length === 66, 'Transaction hash must be exactly 66 characters long (0x + 64 hex characters)');
11407
12295
  /**
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.
12296
+ * Schema for validating base58-encoded strings.
11426
12297
  *
11427
- * @example
11428
- * ```typescript
11429
- * import { analyzeSteps } from './analyzeSteps'
12298
+ * This schema validates that a string:
12299
+ * - Is a string type
12300
+ * - Is not empty after trimming
12301
+ * - Contains only valid base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z)
12302
+ * - Does not contain commonly confused characters (0, O, I, l)
11430
12303
  *
11431
- * // Failed approval step (requires user action)
11432
- * const bridgeResult = {
11433
- * steps: [
11434
- * { name: 'Approve', state: 'error', errorMessage: 'User rejected' }
11435
- * ]
11436
- * }
12304
+ * @remarks
12305
+ * This schema does not validate length, making it suitable for various base58-encoded data
12306
+ * like Solana addresses, transaction signatures, and other base58-encoded data.
11437
12307
  *
11438
- * const analysis = analyzeSteps(bridgeResult)
11439
- * // Result: { continuationStep: 'Approve', isRetryable: true,
11440
- * // reason: 'Retry failed approval' }
11441
- * ```
12308
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11442
12309
  *
11443
12310
  * @example
11444
12311
  * ```typescript
11445
- * // Pending transaction (requires waiting, not actionable)
11446
- * const bridgeResult = {
11447
- * steps: [
11448
- * { name: 'Approve', state: 'pending' }
11449
- * ]
11450
- * }
12312
+ * import { base58StringSchema } from '@core/adapter'
11451
12313
  *
11452
- * const analysis = analyzeSteps(bridgeResult)
11453
- * // Result: { continuationStep: 'Approve', isRetryable: false,
11454
- * // reason: 'Continue pending approval' }
12314
+ * const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
12315
+ * const validTxHash = '3Jf8k2L5mN9pQ7rS1tV4wX6yZ8aB2cD4eF5gH7iJ9kL1mN3oP5qR7sT9uV1wX3yZ5'
12316
+ *
12317
+ * const addressResult = base58StringSchema.safeParse(validAddress)
12318
+ * const txHashResult = base58StringSchema.safeParse(validTxHash)
12319
+ * console.log(addressResult.success) // true
12320
+ * console.log(txHashResult.success) // true
11455
12321
  * ```
12322
+ */
12323
+ const base58StringSchema = zod.z
12324
+ .string()
12325
+ .min(1, 'Base58 string is required')
12326
+ .refine((value) => value.trim().length > 0, 'Base58 string cannot be empty')
12327
+ .refine((value) => {
12328
+ // Base58 alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
12329
+ // Excludes: 0, O, I, l to avoid confusion
12330
+ const base58Pattern = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/;
12331
+ return base58Pattern.test(value);
12332
+ }, 'Base58 string contains invalid characters. Only base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z) are allowed');
12333
+ /**
12334
+ * Schema for validating Solana addresses.
12335
+ *
12336
+ * This schema validates that a string is a properly formatted Solana address:
12337
+ * - Must be a valid base58-encoded string
12338
+ * - Must be between 32-44 characters long (typical length for base58-encoded 32-byte addresses)
12339
+ *
12340
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11456
12341
  *
11457
12342
  * @example
11458
12343
  * ```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
- * }
12344
+ * import { solanaAddressSchema } from '@core/adapter'
11468
12345
  *
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.
12346
+ * const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
11494
12347
  *
11495
- * @param steps - Array of executed bridge steps.
11496
- * @returns Flow context with execution state and history.
12348
+ * const result = solanaAddressSchema.safeParse(validAddress)
12349
+ * console.log(result.success) // true
12350
+ * ```
11497
12351
  */
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
- }
12352
+ base58StringSchema.refine((value) => value.length >= 32 && value.length <= 44, 'Solana address must be between 32-44 characters long (base58-encoded 32-byte address)');
11522
12353
  /**
11523
- * Determine continuation step using the rule engine.
12354
+ * Schema for validating Solana transaction hashes.
11524
12355
  *
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.
12356
+ * This schema validates that a string is a properly formatted Solana transaction hash:
12357
+ * - Must be a valid base58-encoded string
12358
+ * - Must be between 86-88 characters long (typical length for base58-encoded 64-byte signatures)
11581
12359
  *
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.
12360
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11585
12361
  *
11586
12362
  * @example
11587
12363
  * ```typescript
11588
- * import { findStepByName } from './findStep'
12364
+ * import { solanaTransactionHashSchema } from '@core/adapter'
11589
12365
  *
11590
- * const burnStep = findStepByName(result, 'burn')
11591
- * if (burnStep) {
11592
- * console.log('Burn tx:', burnStep.txHash)
11593
- * }
12366
+ * const validTxHash = '5VfYmGBjvQKe3xgLtTQPSMEUdpEVHrJwLK7pKBJWKzYpNBE2g3kJrq7RSe9M8DqzQJ5J2aZPTjHLvd4WgxPpJKS'
12367
+ *
12368
+ * const result = solanaTransactionHashSchema.safeParse(validTxHash)
12369
+ * console.log(result.success) // true
11594
12370
  * ```
11595
12371
  */
11596
- function findStepByName(result, stepName) {
11597
- return result.steps.find((step) => step.name === stepName);
11598
- }
12372
+ 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
12373
  /**
11600
- * Find a pending step by name and return it with its index.
12374
+ * Schema for validating Adapter objects.
12375
+ * Checks for the required methods that define an Adapter.
12376
+ */
12377
+ zod.z.object({
12378
+ prepare: zod.z.function(),
12379
+ waitForTransaction: zod.z.function(),
12380
+ getAddress: zod.z.function(),
12381
+ });
12382
+
12383
+ /**
12384
+ * Validate that the adapter has sufficient token balance for a transaction.
11601
12385
  *
11602
- * Searches for a step that matches both the step name and has a pending state.
12386
+ * This function checks if the adapter's current token balance is greater than or equal
12387
+ * to the requested transaction amount. It throws a KitError with code 9001
12388
+ * (BALANCE_INSUFFICIENT_TOKEN) if the balance is insufficient, providing detailed
12389
+ * information about the shortfall.
11603
12390
  *
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.
12391
+ * @param params - The validation parameters containing adapter, amount, token, and token address.
12392
+ * @returns A promise that resolves to void if validation passes.
12393
+ * @throws {KitError} When the adapter's balance is less than the required amount (code: 9001).
11608
12394
  *
11609
12395
  * @example
11610
12396
  * ```typescript
11611
- * import { findPendingStep } from './findStep'
12397
+ * import { validateBalanceForTransaction } from '@core/adapter'
12398
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
12399
+ * import { isKitError, ERROR_TYPES } from '@core/errors'
11612
12400
  *
11613
- * const { step, index } = findPendingStep(result, 'burn')
11614
- * console.log('Pending step:', step.name, 'at index:', index)
12401
+ * const adapter = createViemAdapterFromPrivateKey({
12402
+ * privateKey: '0x...',
12403
+ * chain: 'Ethereum',
12404
+ * })
12405
+ *
12406
+ * try {
12407
+ * await validateBalanceForTransaction({
12408
+ * adapter,
12409
+ * amount: '1000000', // 1 USDC (6 decimals)
12410
+ * token: 'USDC',
12411
+ * tokenAddress: '0xA0b86a33E6441c8C1c7C16e4c5e3e5b5e4c5e3e5b5e4c5e',
12412
+ * operationContext: { chain: 'Ethereum' },
12413
+ * })
12414
+ * console.log('Balance validation passed')
12415
+ * } catch (error) {
12416
+ * if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
12417
+ * console.error('Insufficient funds:', error.message)
12418
+ * }
12419
+ * }
11615
12420
  * ```
11616
12421
  */
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',
12422
+ const validateBalanceForTransaction = async (params) => {
12423
+ const { amount, adapter, token, tokenAddress, operationContext } = params;
12424
+ const balancePrepared = await adapter.prepareAction('usdc.balanceOf', {
12425
+ walletAddress: operationContext.address,
12426
+ }, operationContext);
12427
+ const balance = await balancePrepared.execute();
12428
+ if (BigInt(balance) < BigInt(amount)) {
12429
+ // Extract chain name from operationContext
12430
+ const chainName = extractChainInfo(operationContext.chain).name;
12431
+ // Create KitError with rich context in trace
12432
+ throw createInsufficientTokenBalanceError(chainName, token, {
12433
+ balance: balance.toString(),
12434
+ amount,
12435
+ tokenAddress,
12436
+ walletAddress: operationContext.address,
11632
12437
  });
11633
12438
  }
11634
- return { step, index };
11635
- }
12439
+ };
12440
+
11636
12441
  /**
11637
- * Get the burn transaction hash from bridge result.
12442
+ * Validate that the adapter has sufficient native token balance for transaction fees.
11638
12443
  *
11639
- * @param result - The bridge result.
11640
- * @returns The burn transaction hash, or undefined if not found.
12444
+ * This function checks if the adapter's current native token balance (ETH, SOL, etc.)
12445
+ * is greater than zero. It throws a KitError with code 9002 (BALANCE_INSUFFICIENT_GAS)
12446
+ * if the balance is zero, indicating the wallet cannot pay for transaction fees.
12447
+ *
12448
+ * @param params - The validation parameters containing adapter and operation context.
12449
+ * @returns A promise that resolves to void if validation passes.
12450
+ * @throws {KitError} When the adapter's native balance is zero (code: 9002).
11641
12451
  *
11642
12452
  * @example
11643
12453
  * ```typescript
11644
- * import { getBurnTxHash } from './findStep'
12454
+ * import { validateNativeBalanceForTransaction } from '@core/adapter'
12455
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
12456
+ * import { isKitError, ERROR_TYPES } from '@core/errors'
11645
12457
  *
11646
- * const burnTxHash = getBurnTxHash(result)
11647
- * if (burnTxHash) {
11648
- * console.log('Burn tx hash:', burnTxHash)
12458
+ * const adapter = createViemAdapterFromPrivateKey({
12459
+ * privateKey: '0x...',
12460
+ * chain: 'Ethereum',
12461
+ * })
12462
+ *
12463
+ * try {
12464
+ * await validateNativeBalanceForTransaction({
12465
+ * adapter,
12466
+ * operationContext: { chain: 'Ethereum' },
12467
+ * })
12468
+ * console.log('Native balance validation passed')
12469
+ * } catch (error) {
12470
+ * if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
12471
+ * console.error('Insufficient gas funds:', error.message)
12472
+ * }
11649
12473
  * }
11650
12474
  * ```
11651
12475
  */
11652
- function getBurnTxHash(result) {
11653
- return findStepByName(result, CCTPv2StepName.burn)?.txHash;
11654
- }
12476
+ const validateNativeBalanceForTransaction = async (params) => {
12477
+ const { adapter, operationContext } = params;
12478
+ const balancePrepared = await adapter.prepareAction('native.balanceOf', {
12479
+ walletAddress: operationContext.address,
12480
+ }, operationContext);
12481
+ const balance = await balancePrepared.execute();
12482
+ if (BigInt(balance) === 0n) {
12483
+ const { chain } = operationContext;
12484
+ const chainName = extractChainInfo(chain).name;
12485
+ const nativeSymbol = typeof chain === 'object' && chain !== null && 'nativeCurrency' in chain
12486
+ ? chain.nativeCurrency.symbol
12487
+ : undefined;
12488
+ throw createInsufficientGasError(chainName, nativeSymbol, {
12489
+ balance: '0',
12490
+ walletAddress: operationContext.address,
12491
+ });
12492
+ }
12493
+ };
12494
+
11655
12495
  /**
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.
12496
+ * Permit signature standards for gasless token approvals.
11660
12497
  *
11661
- * @example
11662
- * ```typescript
11663
- * import { getAttestationData } from './findStep'
12498
+ * Defines the permit types that can be used to approve token spending
12499
+ * without requiring a separate approval transaction.
11664
12500
  *
11665
- * const attestation = getAttestationData(result)
11666
- * if (attestation) {
11667
- * console.log('Attestation:', attestation.message)
11668
- * }
11669
- * ```
12501
+ * @remarks
12502
+ * - NONE: No permit, tokens must be pre-approved via separate transaction
12503
+ * - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
11670
12504
  */
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
- }
12505
+ var PermitType;
12506
+ (function (PermitType) {
12507
+ /** No permit required - tokens must be pre-approved */
12508
+ PermitType[PermitType["NONE"] = 0] = "NONE";
12509
+ /** EIP-2612 standard permit */
12510
+ PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
12511
+ })(PermitType || (PermitType = {}));
11681
12512
 
11682
12513
  /**
11683
12514
  * Determine if a step executes on the source chain.
@@ -11730,169 +12561,6 @@ function getStepAdapterAndChain(step, context, result) {
11730
12561
  };
11731
12562
  }
11732
12563
 
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
12564
  /**
11897
12565
  * Executes a re-attestation operation to obtain a fresh attestation for an expired message.
11898
12566
  *