@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.mjs CHANGED
@@ -328,6 +328,57 @@ var BridgeChain;
328
328
  BridgeChain["XDC_Apothem"] = "XDC_Apothem";
329
329
  })(BridgeChain || (BridgeChain = {}));
330
330
  // -----------------------------------------------------------------------------
331
+ // Unified Balance Chain Enum (Gateway V1 Supported Chains)
332
+ // -----------------------------------------------------------------------------
333
+ /**
334
+ * Enumeration of blockchains that support Gateway V1 operations
335
+ * (deposit, spend, balance, delegate, removeFund).
336
+ *
337
+ * Derived from the full {@link Blockchain} enum but filtered to only
338
+ * include chains with active Gateway V1 contract support. When new chains
339
+ * gain Gateway V1 support, they are added to this enum.
340
+ *
341
+ * @enum
342
+ * @category Enums
343
+ *
344
+ * @remarks
345
+ * - This enum is the **canonical source** of Gateway-supported chains.
346
+ * - Use this enum (or its string literals) in unified-balance-kit calls
347
+ * for type safety.
348
+ *
349
+ * @see {@link Blockchain} for the complete list of all known blockchains.
350
+ * @see {@link UnifiedBalanceChainIdentifier} for the type that accepts these values.
351
+ */
352
+ var UnifiedBalanceChain;
353
+ (function (UnifiedBalanceChain) {
354
+ // Mainnet chains with Gateway V1 support
355
+ UnifiedBalanceChain["Arbitrum"] = "Arbitrum";
356
+ UnifiedBalanceChain["Avalanche"] = "Avalanche";
357
+ UnifiedBalanceChain["Base"] = "Base";
358
+ UnifiedBalanceChain["Ethereum"] = "Ethereum";
359
+ UnifiedBalanceChain["HyperEVM"] = "HyperEVM";
360
+ UnifiedBalanceChain["Optimism"] = "Optimism";
361
+ UnifiedBalanceChain["Polygon"] = "Polygon";
362
+ UnifiedBalanceChain["Sei"] = "Sei";
363
+ UnifiedBalanceChain["Solana"] = "Solana";
364
+ UnifiedBalanceChain["Sonic"] = "Sonic";
365
+ UnifiedBalanceChain["Unichain"] = "Unichain";
366
+ UnifiedBalanceChain["World_Chain"] = "World_Chain";
367
+ // Testnet chains with Gateway V1 support
368
+ UnifiedBalanceChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
369
+ UnifiedBalanceChain["Arc_Testnet"] = "Arc_Testnet";
370
+ UnifiedBalanceChain["Avalanche_Fuji"] = "Avalanche_Fuji";
371
+ UnifiedBalanceChain["Base_Sepolia"] = "Base_Sepolia";
372
+ UnifiedBalanceChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
373
+ UnifiedBalanceChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
374
+ UnifiedBalanceChain["Optimism_Sepolia"] = "Optimism_Sepolia";
375
+ UnifiedBalanceChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
376
+ UnifiedBalanceChain["Sei_Testnet"] = "Sei_Testnet";
377
+ UnifiedBalanceChain["Solana_Devnet"] = "Solana_Devnet";
378
+ UnifiedBalanceChain["Sonic_Testnet"] = "Sonic_Testnet";
379
+ UnifiedBalanceChain["Unichain_Sepolia"] = "Unichain_Sepolia";
380
+ UnifiedBalanceChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
381
+ })(UnifiedBalanceChain || (UnifiedBalanceChain = {}));
331
382
  // Earn Chain Enum
332
383
  // -----------------------------------------------------------------------------
333
384
  /**
@@ -662,6 +713,62 @@ const ADAPTER_CONTRACT_EVM_MAINNET = '0x7FB8c7260b63934d8da38aF902f87ae6e284a845
662
713
  * integrations (e.g., Arc Testnet).
663
714
  */
664
715
  const ADAPTER_CONTRACT_EVM_TESTNET = '0xBBD70b01a1CAbc96d5b7b129Ae1AAabdf50dd40b';
716
+ /**
717
+ * The GatewayWallet contract address for EVM mainnet networks.
718
+ *
719
+ * This contract manages wallet operations for Gateway transactions
720
+ * on mainnet environments across EVM-compatible chains.
721
+ */
722
+ const GATEWAY_WALLET_EVM_MAINNET = '0x77777777Dcc4d5A8B6E418Fd04D8997ef11000eE';
723
+ /**
724
+ * The GatewayMinter contract address for EVM mainnet networks.
725
+ *
726
+ * This contract handles minting operations for Gateway transactions
727
+ * on mainnet environments across EVM-compatible chains.
728
+ */
729
+ const GATEWAY_MINTER_EVM_MAINNET = '0x2222222d7164433c4C09B0b0D809a9b52C04C205';
730
+ /**
731
+ * The GatewayWallet contract address for EVM testnet networks.
732
+ *
733
+ * This contract manages wallet operations for Gateway transactions
734
+ * on testnet environments across EVM-compatible chains.
735
+ */
736
+ const GATEWAY_WALLET_EVM_TESTNET = '0x0077777d7EBA4688BDeF3E311b846F25870A19B9';
737
+ /**
738
+ * The GatewayMinter contract address for EVM testnet networks.
739
+ *
740
+ * This contract handles minting operations for Gateway transactions
741
+ * on testnet environments across EVM-compatible chains.
742
+ */
743
+ const GATEWAY_MINTER_EVM_TESTNET = '0x0022222ABE238Cc2C7Bb1f21003F0a260052475B';
744
+ /**
745
+ * The GatewayWallet program address for Solana mainnet.
746
+ *
747
+ * This program manages wallet operations for Gateway transactions
748
+ * on Solana mainnet.
749
+ */
750
+ const GATEWAY_WALLET_SOLANA_MAINNET = 'GATEwy4YxeiEbRJLwB6dXgg7q61e6zBPrMzYj5h1pRXQ';
751
+ /**
752
+ * The GatewayMinter program address for Solana mainnet.
753
+ *
754
+ * This program handles minting operations for Gateway transactions
755
+ * on Solana mainnet.
756
+ */
757
+ const GATEWAY_MINTER_SOLANA_MAINNET = 'GATEm5SoBJiSw1v2Pz1iPBgUYkXzCUJ27XSXhDfSyzVZ';
758
+ /**
759
+ * The GatewayWallet program address for Solana devnet.
760
+ *
761
+ * This program manages wallet operations for Gateway transactions
762
+ * on Solana devnet.
763
+ */
764
+ const GATEWAY_WALLET_SOLANA_DEVNET = 'GATEwdfmYNELfp5wDmmR6noSr2vHnAfBPMm2PvCzX5vu';
765
+ /**
766
+ * The GatewayMinter program address for Solana devnet.
767
+ *
768
+ * This program handles minting operations for Gateway transactions
769
+ * on Solana devnet.
770
+ */
771
+ const GATEWAY_MINTER_SOLANA_DEVNET = 'GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr';
665
772
 
666
773
  /**
667
774
  * Arc Testnet chain definition
@@ -712,6 +819,19 @@ const ArcTestnet = defineChain({
712
819
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
713
820
  adapter: ADAPTER_CONTRACT_EVM_TESTNET,
714
821
  },
822
+ gateway: {
823
+ domain: 26,
824
+ contracts: {
825
+ v1: {
826
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
827
+ minter: GATEWAY_MINTER_EVM_TESTNET,
828
+ },
829
+ },
830
+ forwarderSupported: {
831
+ source: true,
832
+ destination: true,
833
+ },
834
+ },
715
835
  });
716
836
 
717
837
  /**
@@ -762,6 +882,19 @@ const Arbitrum = defineChain({
762
882
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
763
883
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
764
884
  },
885
+ gateway: {
886
+ domain: 3,
887
+ contracts: {
888
+ v1: {
889
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
890
+ minter: GATEWAY_MINTER_EVM_MAINNET,
891
+ },
892
+ },
893
+ forwarderSupported: {
894
+ source: true,
895
+ destination: true,
896
+ },
897
+ },
765
898
  });
766
899
 
767
900
  /**
@@ -811,6 +944,19 @@ const ArbitrumSepolia = defineChain({
811
944
  kitContracts: {
812
945
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
813
946
  },
947
+ gateway: {
948
+ domain: 3,
949
+ contracts: {
950
+ v1: {
951
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
952
+ minter: GATEWAY_MINTER_EVM_TESTNET,
953
+ },
954
+ },
955
+ forwarderSupported: {
956
+ source: true,
957
+ destination: true,
958
+ },
959
+ },
814
960
  });
815
961
 
816
962
  /**
@@ -861,6 +1007,19 @@ const Avalanche = defineChain({
861
1007
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
862
1008
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
863
1009
  },
1010
+ gateway: {
1011
+ domain: 1,
1012
+ contracts: {
1013
+ v1: {
1014
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1015
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1016
+ },
1017
+ },
1018
+ forwarderSupported: {
1019
+ source: true,
1020
+ destination: true,
1021
+ },
1022
+ },
864
1023
  });
865
1024
 
866
1025
  /**
@@ -910,6 +1069,19 @@ const AvalancheFuji = defineChain({
910
1069
  kitContracts: {
911
1070
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
912
1071
  },
1072
+ gateway: {
1073
+ domain: 1,
1074
+ contracts: {
1075
+ v1: {
1076
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1077
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1078
+ },
1079
+ },
1080
+ forwarderSupported: {
1081
+ source: true,
1082
+ destination: true,
1083
+ },
1084
+ },
913
1085
  });
914
1086
 
915
1087
  /**
@@ -960,6 +1132,19 @@ const Base = defineChain({
960
1132
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
961
1133
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
962
1134
  },
1135
+ gateway: {
1136
+ domain: 6,
1137
+ contracts: {
1138
+ v1: {
1139
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1140
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1141
+ },
1142
+ },
1143
+ forwarderSupported: {
1144
+ source: true,
1145
+ destination: true,
1146
+ },
1147
+ },
963
1148
  });
964
1149
 
965
1150
  /**
@@ -1009,6 +1194,19 @@ const BaseSepolia = defineChain({
1009
1194
  kitContracts: {
1010
1195
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1011
1196
  },
1197
+ gateway: {
1198
+ domain: 6,
1199
+ contracts: {
1200
+ v1: {
1201
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1202
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1203
+ },
1204
+ },
1205
+ forwarderSupported: {
1206
+ source: true,
1207
+ destination: true,
1208
+ },
1209
+ },
1012
1210
  });
1013
1211
 
1014
1212
  /**
@@ -1253,7 +1451,10 @@ const Ethereum = defineChain({
1253
1451
  chainId: 1,
1254
1452
  isTestnet: false,
1255
1453
  explorerUrl: 'https://etherscan.io/tx/{hash}',
1256
- rpcEndpoints: ['https://eth.merkle.io', 'https://ethereum.publicnode.com'],
1454
+ rpcEndpoints: [
1455
+ 'https://ethereum-rpc.publicnode.com',
1456
+ 'https://ethereum.publicnode.com',
1457
+ ],
1257
1458
  eurcAddress: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
1258
1459
  usdcAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
1259
1460
  usdtAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7',
@@ -1283,6 +1484,19 @@ const Ethereum = defineChain({
1283
1484
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1284
1485
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1285
1486
  },
1487
+ gateway: {
1488
+ domain: 0,
1489
+ contracts: {
1490
+ v1: {
1491
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1492
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1493
+ },
1494
+ },
1495
+ forwarderSupported: {
1496
+ source: true,
1497
+ destination: true,
1498
+ },
1499
+ },
1286
1500
  });
1287
1501
 
1288
1502
  /**
@@ -1303,7 +1517,7 @@ const EthereumSepolia = defineChain({
1303
1517
  chainId: 11155111,
1304
1518
  isTestnet: true,
1305
1519
  explorerUrl: 'https://sepolia.etherscan.io/tx/{hash}',
1306
- rpcEndpoints: ['https://sepolia.drpc.org'],
1520
+ rpcEndpoints: ['https://ethereum-sepolia-rpc.publicnode.com'],
1307
1521
  eurcAddress: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
1308
1522
  usdcAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
1309
1523
  usdtAddress: null,
@@ -1332,6 +1546,19 @@ const EthereumSepolia = defineChain({
1332
1546
  kitContracts: {
1333
1547
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1334
1548
  },
1549
+ gateway: {
1550
+ domain: 0,
1551
+ contracts: {
1552
+ v1: {
1553
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1554
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1555
+ },
1556
+ },
1557
+ forwarderSupported: {
1558
+ source: true,
1559
+ destination: true,
1560
+ },
1561
+ },
1335
1562
  });
1336
1563
 
1337
1564
  /**
@@ -1426,6 +1653,19 @@ const HyperEVM = defineChain({
1426
1653
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1427
1654
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1428
1655
  },
1656
+ gateway: {
1657
+ domain: 19,
1658
+ contracts: {
1659
+ v1: {
1660
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
1661
+ minter: GATEWAY_MINTER_EVM_MAINNET,
1662
+ },
1663
+ },
1664
+ forwarderSupported: {
1665
+ source: true,
1666
+ destination: true,
1667
+ },
1668
+ },
1429
1669
  });
1430
1670
 
1431
1671
  /**
@@ -1470,6 +1710,19 @@ const HyperEVMTestnet = defineChain({
1470
1710
  kitContracts: {
1471
1711
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1472
1712
  },
1713
+ gateway: {
1714
+ domain: 19,
1715
+ contracts: {
1716
+ v1: {
1717
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
1718
+ minter: GATEWAY_MINTER_EVM_TESTNET,
1719
+ },
1720
+ },
1721
+ forwarderSupported: {
1722
+ source: true,
1723
+ destination: true,
1724
+ },
1725
+ },
1473
1726
  });
1474
1727
 
1475
1728
  /**
@@ -2004,6 +2257,19 @@ const Optimism = defineChain({
2004
2257
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2005
2258
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2006
2259
  },
2260
+ gateway: {
2261
+ domain: 2,
2262
+ contracts: {
2263
+ v1: {
2264
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2265
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2266
+ },
2267
+ },
2268
+ forwarderSupported: {
2269
+ source: true,
2270
+ destination: true,
2271
+ },
2272
+ },
2007
2273
  });
2008
2274
 
2009
2275
  /**
@@ -2053,6 +2319,19 @@ const OptimismSepolia = defineChain({
2053
2319
  kitContracts: {
2054
2320
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2055
2321
  },
2322
+ gateway: {
2323
+ domain: 2,
2324
+ contracts: {
2325
+ v1: {
2326
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2327
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2328
+ },
2329
+ },
2330
+ forwarderSupported: {
2331
+ source: true,
2332
+ destination: true,
2333
+ },
2334
+ },
2056
2335
  });
2057
2336
 
2058
2337
  /**
@@ -2241,6 +2520,19 @@ const Polygon = defineChain({
2241
2520
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2242
2521
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2243
2522
  },
2523
+ gateway: {
2524
+ domain: 7,
2525
+ contracts: {
2526
+ v1: {
2527
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2528
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2529
+ },
2530
+ },
2531
+ forwarderSupported: {
2532
+ source: true,
2533
+ destination: true,
2534
+ },
2535
+ },
2244
2536
  });
2245
2537
 
2246
2538
  /**
@@ -2290,6 +2582,19 @@ const PolygonAmoy = defineChain({
2290
2582
  kitContracts: {
2291
2583
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2292
2584
  },
2585
+ gateway: {
2586
+ domain: 7,
2587
+ contracts: {
2588
+ v1: {
2589
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2590
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2591
+ },
2592
+ },
2593
+ forwarderSupported: {
2594
+ source: true,
2595
+ destination: true,
2596
+ },
2597
+ },
2293
2598
  });
2294
2599
 
2295
2600
  /**
@@ -2336,6 +2641,19 @@ const Sei = defineChain({
2336
2641
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2337
2642
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2338
2643
  },
2644
+ gateway: {
2645
+ domain: 16,
2646
+ contracts: {
2647
+ v1: {
2648
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2649
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2650
+ },
2651
+ },
2652
+ forwarderSupported: {
2653
+ source: true,
2654
+ destination: true,
2655
+ },
2656
+ },
2339
2657
  });
2340
2658
 
2341
2659
  /**
@@ -2380,6 +2698,19 @@ const SeiTestnet = defineChain({
2380
2698
  kitContracts: {
2381
2699
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2382
2700
  },
2701
+ gateway: {
2702
+ domain: 16,
2703
+ contracts: {
2704
+ v1: {
2705
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2706
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2707
+ },
2708
+ },
2709
+ forwarderSupported: {
2710
+ source: true,
2711
+ destination: true,
2712
+ },
2713
+ },
2383
2714
  });
2384
2715
 
2385
2716
  /**
@@ -2424,6 +2755,19 @@ const Sonic = defineChain({
2424
2755
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2425
2756
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2426
2757
  },
2758
+ gateway: {
2759
+ domain: 13,
2760
+ contracts: {
2761
+ v1: {
2762
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
2763
+ minter: GATEWAY_MINTER_EVM_MAINNET,
2764
+ },
2765
+ },
2766
+ forwarderSupported: {
2767
+ source: true,
2768
+ destination: true,
2769
+ },
2770
+ },
2427
2771
  });
2428
2772
 
2429
2773
  /**
@@ -2467,6 +2811,19 @@ const SonicTestnet = defineChain({
2467
2811
  kitContracts: {
2468
2812
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2469
2813
  },
2814
+ gateway: {
2815
+ domain: 13,
2816
+ contracts: {
2817
+ v1: {
2818
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
2819
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2820
+ },
2821
+ },
2822
+ forwarderSupported: {
2823
+ source: true,
2824
+ destination: true,
2825
+ },
2826
+ },
2470
2827
  });
2471
2828
 
2472
2829
  /**
@@ -2515,6 +2872,19 @@ const Solana = defineChain({
2515
2872
  kitContracts: {
2516
2873
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
2517
2874
  },
2875
+ gateway: {
2876
+ domain: 5,
2877
+ contracts: {
2878
+ v1: {
2879
+ wallet: GATEWAY_WALLET_SOLANA_MAINNET,
2880
+ minter: GATEWAY_MINTER_SOLANA_MAINNET,
2881
+ },
2882
+ },
2883
+ forwarderSupported: {
2884
+ source: true,
2885
+ destination: false,
2886
+ },
2887
+ },
2518
2888
  });
2519
2889
 
2520
2890
  /**
@@ -2563,6 +2933,19 @@ const SolanaDevnet = defineChain({
2563
2933
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
2564
2934
  },
2565
2935
  rpcEndpoints: ['https://api.devnet.solana.com'],
2936
+ gateway: {
2937
+ domain: 5,
2938
+ contracts: {
2939
+ v1: {
2940
+ wallet: GATEWAY_WALLET_SOLANA_DEVNET,
2941
+ minter: GATEWAY_MINTER_SOLANA_DEVNET,
2942
+ },
2943
+ },
2944
+ forwarderSupported: {
2945
+ source: true,
2946
+ destination: false,
2947
+ },
2948
+ },
2566
2949
  });
2567
2950
 
2568
2951
  /**
@@ -2737,6 +3120,19 @@ const Unichain = defineChain({
2737
3120
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2738
3121
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2739
3122
  },
3123
+ gateway: {
3124
+ domain: 10,
3125
+ contracts: {
3126
+ v1: {
3127
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
3128
+ minter: GATEWAY_MINTER_EVM_MAINNET,
3129
+ },
3130
+ },
3131
+ forwarderSupported: {
3132
+ source: true,
3133
+ destination: true,
3134
+ },
3135
+ },
2740
3136
  });
2741
3137
 
2742
3138
  /**
@@ -2786,6 +3182,19 @@ const UnichainSepolia = defineChain({
2786
3182
  kitContracts: {
2787
3183
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2788
3184
  },
3185
+ gateway: {
3186
+ domain: 10,
3187
+ contracts: {
3188
+ v1: {
3189
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
3190
+ minter: GATEWAY_MINTER_EVM_TESTNET,
3191
+ },
3192
+ },
3193
+ forwarderSupported: {
3194
+ source: true,
3195
+ destination: true,
3196
+ },
3197
+ },
2789
3198
  });
2790
3199
 
2791
3200
  /**
@@ -2830,6 +3239,19 @@ const WorldChain = defineChain({
2830
3239
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2831
3240
  adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2832
3241
  },
3242
+ gateway: {
3243
+ domain: 14,
3244
+ contracts: {
3245
+ v1: {
3246
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
3247
+ minter: GATEWAY_MINTER_EVM_MAINNET,
3248
+ },
3249
+ },
3250
+ forwarderSupported: {
3251
+ source: true,
3252
+ destination: true,
3253
+ },
3254
+ },
2833
3255
  });
2834
3256
 
2835
3257
  /**
@@ -2876,6 +3298,19 @@ const WorldChainSepolia = defineChain({
2876
3298
  kitContracts: {
2877
3299
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2878
3300
  },
3301
+ gateway: {
3302
+ domain: 14,
3303
+ contracts: {
3304
+ v1: {
3305
+ wallet: GATEWAY_WALLET_EVM_TESTNET,
3306
+ minter: GATEWAY_MINTER_EVM_TESTNET,
3307
+ },
3308
+ },
3309
+ forwarderSupported: {
3310
+ source: true,
3311
+ destination: true,
3312
+ },
3313
+ },
2879
3314
  });
2880
3315
 
2881
3316
  /**
@@ -3155,6 +3590,87 @@ function hasCustomContractSupport(chain, contractType) {
3155
3590
  return (typeof contractAddress === 'string' && contractAddress.trim().length > 0);
3156
3591
  }
3157
3592
 
3593
+ /**
3594
+ * Zod schema for validating Gateway v1 contract addresses.
3595
+ *
3596
+ * @example
3597
+ * ```typescript
3598
+ * gatewayV1ContractsSchema.parse({
3599
+ * wallet: '0x1234567890abcdef1234567890abcdef12345678',
3600
+ * minter: '0xabcdef1234567890abcdef1234567890abcdef12'
3601
+ * })
3602
+ * ```
3603
+ */
3604
+ const gatewayV1ContractsSchema = z
3605
+ .object({
3606
+ wallet: z
3607
+ .string({
3608
+ required_error: 'Gateway wallet address is required. Please provide a valid contract address.',
3609
+ invalid_type_error: 'Gateway wallet address must be a string.',
3610
+ })
3611
+ .min(1, 'Gateway wallet address cannot be empty.'),
3612
+ minter: z
3613
+ .string({
3614
+ required_error: 'Gateway minter address is required. Please provide a valid contract address.',
3615
+ invalid_type_error: 'Gateway minter address must be a string.',
3616
+ })
3617
+ .min(1, 'Gateway minter address cannot be empty.'),
3618
+ })
3619
+ .strict(); // Reject any additional properties not defined in the schema
3620
+ /**
3621
+ * Zod schema for validating the versioned Gateway contracts map.
3622
+ *
3623
+ * @description Mirrors the {@link GatewayContracts} type: a partial map of
3624
+ * protocol versions to their contract addresses, following the same pattern
3625
+ * as {@link CCTPContracts}.
3626
+ *
3627
+ * @example
3628
+ * ```typescript
3629
+ * gatewayContractsSchema.parse({
3630
+ * v1: {
3631
+ * wallet: '0x1234567890abcdef1234567890abcdef12345678',
3632
+ * minter: '0xabcdef1234567890abcdef1234567890abcdef12'
3633
+ * }
3634
+ * })
3635
+ * ```
3636
+ */
3637
+ const gatewayContractsSchema = z
3638
+ .object({
3639
+ v1: gatewayV1ContractsSchema.optional(),
3640
+ })
3641
+ .strict(); // Reject any additional properties not defined in the schema
3642
+ /**
3643
+ * Zod schema for validating the full Gateway configuration.
3644
+ *
3645
+ * @description Mirrors the {@link GatewayConfig} type: a domain number plus
3646
+ * a versioned contracts map, following the same pattern as {@link CCTPConfig}.
3647
+ *
3648
+ * @example
3649
+ * ```typescript
3650
+ * gatewayConfigSchema.parse({
3651
+ * domain: 6,
3652
+ * contracts: {
3653
+ * v1: {
3654
+ * wallet: '0x1234567890abcdef1234567890abcdef12345678',
3655
+ * minter: '0xabcdef1234567890abcdef1234567890abcdef12'
3656
+ * }
3657
+ * }
3658
+ * })
3659
+ * ```
3660
+ */
3661
+ const gatewayConfigSchema = z
3662
+ .object({
3663
+ domain: z.number({
3664
+ required_error: 'Gateway domain is required. Please provide a valid domain number.',
3665
+ invalid_type_error: 'Gateway domain must be a number.',
3666
+ }),
3667
+ contracts: gatewayContractsSchema,
3668
+ forwarderSupported: z.object({
3669
+ source: z.boolean(),
3670
+ destination: z.boolean(),
3671
+ }),
3672
+ })
3673
+ .strict(); // Reject any additional properties not defined in the schema
3158
3674
  /**
3159
3675
  * Base schema for common chain definition properties.
3160
3676
  * This contains all properties shared between EVM and non-EVM chains.
@@ -3193,6 +3709,7 @@ const baseChainDefinitionSchema = z.object({
3193
3709
  adapter: z.string().optional(),
3194
3710
  })
3195
3711
  .optional(),
3712
+ gateway: gatewayConfigSchema.optional(),
3196
3713
  });
3197
3714
  /**
3198
3715
  * Zod schema for validating EVM chain definitions specifically.
@@ -3401,6 +3918,42 @@ z.union([
3401
3918
  `Supported chains: ${Object.values(EarnChain).join(', ')}`,
3402
3919
  })),
3403
3920
  ]);
3921
+ /**
3922
+ * Zod schema for validating unified balance chain identifiers.
3923
+ *
3924
+ * This schema validates that the provided chain is supported for unified balance operations.
3925
+ * It accepts either a UnifiedBalanceChain enum value, a string matching a UnifiedBalanceChain value,
3926
+ * or a ChainDefinition for a supported chain.
3927
+ *
3928
+ * Use this schema when validating chain parameters for unified balance operations to ensure
3929
+ * only Gateway V1-supported chains are accepted at runtime.
3930
+ *
3931
+ * @example
3932
+ * ```typescript
3933
+ * import { unifiedBalanceChainIdentifierSchema } from '@core/chains/validation'
3934
+ * import { UnifiedBalanceChain, Chains } from '@core/chains'
3935
+ *
3936
+ * // Valid - UnifiedBalanceChain enum value
3937
+ * unifiedBalanceChainIdentifierSchema.parse(UnifiedBalanceChain.Ethereum)
3938
+ *
3939
+ * // Valid - string literal
3940
+ * unifiedBalanceChainIdentifierSchema.parse('Ethereum')
3941
+ *
3942
+ * // Invalid - Algorand is not in UnifiedBalanceChain (throws ZodError)
3943
+ * unifiedBalanceChainIdentifierSchema.parse('Algorand')
3944
+ * ```
3945
+ *
3946
+ * @see {@link UnifiedBalanceChain} for the enum of supported chains.
3947
+ */
3948
+ const supportedUnifiedBalanceChains = Object.keys(UnifiedBalanceChain).join(', ');
3949
+ z.union([
3950
+ z.string().refine((val) => val in UnifiedBalanceChain, (val) => ({
3951
+ message: `Chain "${val}" is not supported for unified balance operations. Supported chains: ${supportedUnifiedBalanceChains}.`,
3952
+ })),
3953
+ chainDefinitionSchema$2.refine((chainDef) => chainDef.chain in UnifiedBalanceChain, (chainDef) => ({
3954
+ message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for unified balance operations. Supported chains: ${supportedUnifiedBalanceChains}.`,
3955
+ })),
3956
+ ]);
3404
3957
 
3405
3958
  /**
3406
3959
  * @packageDocumentation
@@ -3709,6 +4262,23 @@ class BridgingProvider {
3709
4262
  }
3710
4263
  }
3711
4264
 
4265
+ /**
4266
+ * Check whether the current runtime is Node.js.
4267
+ *
4268
+ * @returns `true` when running in Node.js, `false` otherwise.
4269
+ *
4270
+ * @example
4271
+ * ```typescript
4272
+ * import { isNodeEnvironment } from '@core/utils'
4273
+ *
4274
+ * if (isNodeEnvironment()) {
4275
+ * console.log('Running in Node.js')
4276
+ * }
4277
+ * ```
4278
+ */
4279
+ const isNodeEnvironment = () => typeof process !== 'undefined' &&
4280
+ typeof process.versions === 'object' &&
4281
+ typeof process.versions.node === 'string';
3712
4282
  /**
3713
4283
  * Detect the runtime environment and return a shortened identifier.
3714
4284
  *
@@ -3716,9 +4286,7 @@ class BridgingProvider {
3716
4286
  */
3717
4287
  const getRuntime = () => {
3718
4288
  // Node.js environment
3719
- if (typeof process !== 'undefined' &&
3720
- typeof process.versions === 'object' &&
3721
- typeof process.versions.node === 'string') {
4289
+ if (isNodeEnvironment()) {
3722
4290
  // Shorten to major version only
3723
4291
  const majorVersion = process.versions.node.split('.')[0] ?? 'unknown';
3724
4292
  return `node/${majorVersion}`;
@@ -4707,6 +5275,7 @@ function createInsufficientTokenBalanceError(chain, token, trace) {
4707
5275
  * as it requires user intervention to add gas funds.
4708
5276
  *
4709
5277
  * @param chain - The blockchain network where the gas check failed
5278
+ * @param nativeToken - Native token symbol (e.g. 'ETH', 'SOL', 'AVAX') for a specific message, defaults to 'native token'
4710
5279
  * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
4711
5280
  * @returns KitError with insufficient gas details
4712
5281
  *
@@ -4715,25 +5284,25 @@ function createInsufficientTokenBalanceError(chain, token, trace) {
4715
5284
  * import { createInsufficientGasError } from '@core/errors'
4716
5285
  *
4717
5286
  * throw createInsufficientGasError('Ethereum')
4718
- * // Message: "Insufficient gas funds on Ethereum"
5287
+ * // Message: "Insufficient native token on Ethereum to cover gas fees"
5288
+ *
5289
+ * throw createInsufficientGasError('Ethereum', 'ETH')
5290
+ * // Message: "Insufficient ETH on Ethereum to cover gas fees"
4719
5291
  * ```
4720
5292
  *
4721
5293
  * @example
4722
5294
  * ```typescript
4723
- * // With trace context for debugging
4724
- * throw createInsufficientGasError('Ethereum', {
5295
+ * throw createInsufficientGasError('Ethereum', 'ETH', {
4725
5296
  * rawError: error,
4726
- * gasRequired: '21000',
4727
- * gasAvailable: '10000',
4728
5297
  * walletAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
4729
5298
  * })
4730
5299
  * ```
4731
5300
  */
4732
- function createInsufficientGasError(chain, trace) {
5301
+ function createInsufficientGasError(chain, nativeToken = 'native token', trace) {
4733
5302
  return new KitError({
4734
5303
  ...BalanceError.INSUFFICIENT_GAS,
4735
5304
  recoverability: 'FATAL',
4736
- message: `Insufficient gas funds on ${chain}`,
5305
+ message: `Insufficient ${nativeToken} on ${chain} to cover gas fees`,
4737
5306
  cause: {
4738
5307
  trace: {
4739
5308
  ...trace,
@@ -5099,7 +5668,7 @@ const SLIPPAGE_REVERT_PATTERN = /slippage|lower than the minimum required|less t
5099
5668
  *
5100
5669
  * @internal
5101
5670
  */
5102
- function handleRevertError(msg, error, context) {
5671
+ function parseRevertError(msg, error, context) {
5103
5672
  const reason = extractRevertReason(msg, error) ?? 'Transaction reverted';
5104
5673
  if (SLIPPAGE_REVERT_PATTERN.test(reason)) {
5105
5674
  return new KitError({
@@ -5188,17 +5757,34 @@ function handleRevertError(msg, error, context) {
5188
5757
  function parseBlockchainError(error, context) {
5189
5758
  const msg = extractMessage(error);
5190
5759
  const token = context.token ?? 'token';
5191
- // Pattern 1: Insufficient balance errors
5760
+ // Pattern 0: Insufficient native gas token errors
5761
+ // Must run BEFORE the generic balance pattern because RPC messages like
5762
+ // "insufficient funds for gas * price + value" and "insufficient funds
5763
+ // for intrinsic transaction cost" contain "insufficient funds" which
5764
+ // would otherwise match the token balance pattern below.
5765
+ if (/insufficient funds for (gas|intrinsic transaction cost)|sender doesn't have enough funds to send tx/i.test(msg)) {
5766
+ return createInsufficientGasError(context.chain, undefined, {
5767
+ rawError: error,
5768
+ });
5769
+ }
5770
+ // Pattern 1: Insufficient token balance errors
5192
5771
  // Matches balance-related errors from ERC20 contracts, native transfers, and Solana programs
5193
5772
  if (/transfer amount exceeds balance|insufficient (balance|funds)|burn amount exceeded/i.test(msg)) {
5194
5773
  return createInsufficientTokenBalanceError(context.chain, token, {
5195
5774
  rawError: error,
5196
5775
  });
5197
5776
  }
5777
+ // Pattern 1b: Gateway-specific contract reverts
5778
+ // Matched before the generic simulation/revert pattern so the error
5779
+ // messages are actionable rather than opaque hex selectors.
5780
+ const gatewayError = parseGatewayContractError(msg, context, error);
5781
+ if (gatewayError !== null) {
5782
+ return gatewayError;
5783
+ }
5198
5784
  // Pattern 2: Simulation and execution reverts
5199
5785
  // Matches contract revert errors and simulation failures
5200
5786
  if (/execution reverted|simulation failed|transaction reverted|transaction failed/i.test(msg)) {
5201
- return handleRevertError(msg, error, context);
5787
+ return parseRevertError(msg, error, context);
5202
5788
  }
5203
5789
  // Pattern 3: Gas-related errors
5204
5790
  // Matches gas estimation failures and gas exhaustion
@@ -5213,10 +5799,6 @@ function parseBlockchainError(error, context) {
5213
5799
  const { txHash, explorerUrl } = normalizeTransactionDetails(context.txHash, context.explorerUrl);
5214
5800
  return createOutOfGasError(context.chain, { rawError: error }, txHash, explorerUrl);
5215
5801
  }
5216
- // Insufficient funds for gas
5217
- if (/insufficient funds for gas/i.test(msg)) {
5218
- return createInsufficientGasError(context.chain, { rawError: error });
5219
- }
5220
5802
  // Pattern 4: Network connectivity errors
5221
5803
  // Matches connection failures, DNS errors, and timeouts
5222
5804
  if (/connection (refused|failed)|network|timeout|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
@@ -5468,6 +6050,59 @@ function extractRevertReason(msg, error) {
5468
6050
  }
5469
6051
  return null;
5470
6052
  }
6053
+ /**
6054
+ * Parses Gateway smart-contract revert selectors into specific KitError types.
6055
+ *
6056
+ * Returns `null` when the message does not match any known Gateway revert,
6057
+ * allowing the caller to fall through to generic patterns.
6058
+ *
6059
+ * @param msg - The extracted error message string.
6060
+ * @param context - Parse error context (chain, token, operation).
6061
+ * @param error - The original raw error for the trace payload.
6062
+ * @returns A KitError if a Gateway pattern matched, otherwise `null`.
6063
+ *
6064
+ * @internal Called by {@link parseBlockchainError} — not re-exported from
6065
+ * the `@core/errors` barrel.
6066
+ *
6067
+ * @example
6068
+ * ```typescript
6069
+ * // Internal usage within parseBlockchainError:
6070
+ * import { parseGatewayContractError } from './parseBlockchainError'
6071
+ *
6072
+ * const gatewayError = parseGatewayContractError(
6073
+ * 'execution reverted: WithdrawalValueExceedsAvailableBalance',
6074
+ * { chain: 'Ethereum', token: 'USDC', operation: 'withdraw' },
6075
+ * new Error('execution reverted'),
6076
+ * )
6077
+ * if (gatewayError !== null) {
6078
+ * // KitError with INSUFFICIENT_TOKEN balance details
6079
+ * console.log(gatewayError.message)
6080
+ * }
6081
+ *
6082
+ * // Returns null for unrecognised reverts (caller falls through to generic parsing)
6083
+ * const unknown = parseGatewayContractError(
6084
+ * 'execution reverted: SomeUnknownError()',
6085
+ * { chain: 'Base', token: 'USDC', operation: 'deposit' },
6086
+ * new Error('execution reverted'),
6087
+ * )
6088
+ * console.log(unknown) // null
6089
+ * ```
6090
+ */
6091
+ function parseGatewayContractError(msg, context, error) {
6092
+ const token = context.token ?? 'token';
6093
+ if (/WithdrawalValueExceedsAvailableBalance|InsufficientDepositBalance/i.test(msg)) {
6094
+ return createInsufficientTokenBalanceError(context.chain, token, {
6095
+ rawError: error,
6096
+ });
6097
+ }
6098
+ if (/WithdrawalNotYetAvailable|WithdrawalDelayNotElapsed/i.test(msg)) {
6099
+ return createTransactionRevertedError(context.chain, 'Withdrawal is not yet available — the withdrawal delay has not elapsed', { rawError: error });
6100
+ }
6101
+ if (/NoWithdrawingBalance/i.test(msg)) {
6102
+ return createTransactionRevertedError(context.chain, 'No pending withdrawal balance — call initiateWithdrawal() first', { rawError: error });
6103
+ }
6104
+ return null;
6105
+ }
5471
6106
 
5472
6107
  /**
5473
6108
  * Type guard to check if an error is a KitError instance.
@@ -6082,12 +6717,12 @@ function validate(value, schema, context) {
6082
6717
  }
6083
6718
 
6084
6719
  /**
6085
- * Symbol used to track validation state on objects.
6086
- * This allows us to attach metadata to objects without interfering with their structure,
6087
- * enabling optimized validation by skipping already validated objects.
6720
+ * Module-level WeakMap for tracking which (schema, validator) combinations
6721
+ * have already processed a given object. This avoids mutating user-supplied
6722
+ * input objects while ensuring re-validation occurs when schemas change.
6088
6723
  * @internal
6089
6724
  */
6090
- const VALIDATION_STATE = Symbol('validationState');
6725
+ const validationStateMap = new WeakMap();
6091
6726
  /**
6092
6727
  * Validates data against a Zod schema with state tracking and enhanced error reporting.
6093
6728
  *
@@ -6104,11 +6739,12 @@ const VALIDATION_STATE = Symbol('validationState');
6104
6739
  *
6105
6740
  * @example
6106
6741
  * ```typescript
6107
- * const result = validateWithStateTracking(BridgeParamsSchema, params, 'bridge parameters')
6742
+ * const VALIDATOR = Symbol('bridgeValidator')
6743
+ * validateWithStateTracking(params, BridgeParamsSchema, 'bridge parameters', VALIDATOR)
6744
+ * // params is now narrowed to the schema's output type
6108
6745
  * ```
6109
6746
  */
6110
6747
  function validateWithStateTracking(value, schema, context, validatorName) {
6111
- // Skip validation for null or undefined values
6112
6748
  if (value === null) {
6113
6749
  throw new KitError({
6114
6750
  ...InputError.VALIDATION_FAILED,
@@ -6133,7 +6769,6 @@ function validateWithStateTracking(value, schema, context, validatorName) {
6133
6769
  },
6134
6770
  });
6135
6771
  }
6136
- // Ensure value is an object that can hold validation state
6137
6772
  if (typeof value !== 'object') {
6138
6773
  throw new KitError({
6139
6774
  ...InputError.VALIDATION_FAILED,
@@ -6146,18 +6781,28 @@ function validateWithStateTracking(value, schema, context, validatorName) {
6146
6781
  },
6147
6782
  });
6148
6783
  }
6149
- // Get or initialize validation state
6150
- const valueWithState = value;
6151
- const state = valueWithState[VALIDATION_STATE] ?? { validatedBy: [] };
6152
- // Skip validation if already validated by this validator
6153
- if (state.validatedBy.includes(validatorName)) {
6784
+ const state = validationStateMap.get(value) ?? {
6785
+ validatedSchemasByValidator: new Map(),
6786
+ };
6787
+ // Get the set of schemas that have already validated this object
6788
+ // for the given validator. Create a new WeakSet if this is first time.
6789
+ const validatedSchemas = state.validatedSchemasByValidator.get(validatorName) ?? new WeakSet();
6790
+ if (!(validatedSchemas instanceof WeakSet)) {
6791
+ throw new KitError({
6792
+ ...InputError.VALIDATION_FAILED,
6793
+ recoverability: 'FATAL',
6794
+ message: 'Invalid validation state: expected WeakSet',
6795
+ });
6796
+ }
6797
+ // Check if this exact schema instance has already validated this object
6798
+ if (validatedSchemas.has(schema)) {
6154
6799
  return;
6155
6800
  }
6156
- // Delegate to the validate function for actual validation (now throws KitError)
6157
6801
  validate(value, schema, context);
6158
- // Update validation state
6159
- state.validatedBy.push(validatorName);
6160
- valueWithState[VALIDATION_STATE] = state;
6802
+ // Record that this schema has validated the object for this validator
6803
+ validatedSchemas.add(schema);
6804
+ state.validatedSchemasByValidator.set(validatorName, validatedSchemas);
6805
+ validationStateMap.set(value, state);
6161
6806
  }
6162
6807
 
6163
6808
  /**
@@ -9006,123 +9651,723 @@ async function assertCCTPv2AttestationParams(attestation, params) {
9006
9651
  }
9007
9652
 
9008
9653
  /**
9009
- * Executes a prepared chain request and returns the result as a bridge step.
9010
- *
9011
- * This function takes a prepared chain request (containing transaction data) and executes
9012
- * it using the appropriate adapter. It handles the execution details and formats
9013
- * the result as a standardized bridge step with transaction details and explorer URLs.
9014
- *
9015
- * @param params - The execution parameters containing:
9016
- * - `name`: The name of the step
9017
- * - `request`: The prepared chain request containing transaction data
9018
- * - `adapter`: The adapter that will execute the transaction
9019
- * - `confirmations`: The number of confirmations to wait for (defaults to 1)
9020
- * - `timeout`: The timeout for the request in milliseconds
9021
- * @returns The bridge step with the transaction details and explorer URL
9022
- * @throws If the transaction execution fails
9654
+ * CCTP bridge step names that can occur in the bridging flow.
9023
9655
  *
9024
- * @example
9025
- * ```typescript
9026
- * const step = await executePreparedChainRequest({
9027
- * name: 'approve',
9028
- * request: preparedRequest,
9029
- * adapter: adapter,
9030
- * confirmations: 2,
9031
- * timeout: 30000
9032
- * })
9033
- * console.log('Transaction hash:', step.txHash)
9034
- * ```
9656
+ * This object provides type safety for step names and represents all possible
9657
+ * steps that can be executed during a CCTP bridge operation. Using const assertions
9658
+ * makes this tree-shakable and follows modern TypeScript best practices.
9035
9659
  */
9036
- async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, }) {
9037
- const step = { name, state: 'pending' };
9038
- try {
9039
- /**
9040
- * No-op requests are not executed.
9041
- * We return a noop step instead.
9042
- */
9043
- if (request.type === 'noop') {
9044
- step.state = 'noop';
9045
- return step;
9046
- }
9047
- const txHash = await request.execute();
9048
- step.txHash = txHash;
9049
- const retryOptions = {
9050
- isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
9051
- };
9052
- if (timeout !== undefined) {
9053
- retryOptions.deadlineMs = Date.now() + timeout;
9054
- }
9055
- const transaction = await retryAsync(async () => adapter.waitForTransaction(txHash, { confirmations, timeout }, chain), retryOptions);
9056
- step.state = transaction.blockNumber ? 'success' : 'error';
9057
- step.data = transaction;
9058
- // Generate explorer URL for the step
9059
- step.explorerUrl = buildExplorerUrl(chain, txHash);
9060
- if (!transaction.blockNumber) {
9061
- step.errorMessage = 'Transaction was not confirmed on-chain.';
9062
- }
9063
- }
9064
- catch (err) {
9065
- step.state = 'error';
9066
- step.error = err;
9067
- // Optionally parse for common blockchain error formats
9068
- if (err instanceof Error) {
9069
- step.errorMessage = err.message;
9070
- }
9071
- else if (typeof err === 'object' && err != null && 'message' in err) {
9072
- step.errorMessage = String(err.message);
9073
- }
9074
- else {
9075
- step.errorMessage = 'Unknown error occurred during approval step.';
9076
- }
9077
- }
9078
- return step;
9079
- }
9080
-
9660
+ const CCTPv2StepName = {
9661
+ approve: 'approve',
9662
+ burn: 'burn',
9663
+ fetchAttestation: 'fetchAttestation',
9664
+ mint: 'mint',
9665
+ reAttest: 'reAttest',
9666
+ };
9081
9667
  /**
9082
- * Approves the TokenMessenger contract to spend USDC tokens for a bridge operation.
9083
- *
9084
- * This function handles the approval step of the CCTP v2 bridge process, allowing
9085
- * the TokenMessenger contract to spend the specified amount of USDC tokens on behalf
9086
- * of the user. This is a prerequisite for the subsequent burn operation.
9087
- *
9088
- * @param params - The bridge parameters containing source, destination, amount and optional config
9089
- * @param sourceChain - The source chain definition where the approval will occur
9090
- * @returns Promise resolving to the bridge step with transaction details
9091
- * @throws {KitError} If the parameters are invalid
9092
- * @throws {BridgeError} If the approval transaction fails
9668
+ * Conditional step transition rules for CCTP bridge flow.
9093
9669
  *
9094
- * @example
9095
- * ```typescript
9096
- * const approveStep = await approve(params, sourceChain)
9097
- * console.log('Approval tx:', approveStep.transactionHash)
9098
- * ```
9670
+ * Rules are evaluated in order - the first matching condition determines the next step.
9671
+ * This approach supports flexible flow logic and makes it easy to extend with new patterns.
9099
9672
  */
9100
- async function bridgeApproval({ params, provider, }) {
9101
- // Calculate the approval amount
9102
- const customFee = BigInt(params.config?.customFee?.value ?? '0');
9103
- const amountBigInt = BigInt(params.amount);
9104
- const approvalAmount = (amountBigInt + customFee).toString();
9105
- return await executePreparedChainRequest({
9106
- name: 'approve',
9107
- adapter: params.source.adapter,
9108
- chain: params.source.chain,
9109
- request: await provider.approve(params.source, approvalAmount),
9110
- });
9111
- }
9112
-
9113
- /**
9114
- * Executes a deposit-for-burn operation on the source chain to initiate a bridge.
9115
- *
9116
- * This function handles the burning step of the CCTP v2 bridge process, where USDC tokens
9117
- * are burned on the source chain to create a message that can be used to mint equivalent
9118
- * tokens on the destination chain.
9119
- *
9120
- * @param params - The bridge parameters containing source, destination, amount and optional config
9121
- * @param sourceChain - The source chain definition where the burn will occur
9122
- * @returns Promise resolving to the bridge step with transaction details
9123
- * @throws {KitError} If the parameters are invalid
9124
- * @throws \{BridgeError\} If the burn transaction fails
9125
- *
9673
+ const STEP_TRANSITION_RULES = {
9674
+ // Starting state - no steps executed yet
9675
+ '': [
9676
+ {
9677
+ condition: () => true,
9678
+ nextStep: CCTPv2StepName.approve,
9679
+ reason: 'Start with approval step',
9680
+ isActionable: true,
9681
+ },
9682
+ ],
9683
+ // After Approve step
9684
+ [CCTPv2StepName.approve]: [
9685
+ {
9686
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9687
+ nextStep: CCTPv2StepName.burn,
9688
+ reason: 'Approval successful, proceed to burn',
9689
+ isActionable: true,
9690
+ },
9691
+ {
9692
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9693
+ nextStep: CCTPv2StepName.approve,
9694
+ reason: 'Retry failed approval',
9695
+ isActionable: true,
9696
+ },
9697
+ {
9698
+ condition: (ctx) => ctx.lastStep?.state === 'noop',
9699
+ nextStep: CCTPv2StepName.burn,
9700
+ reason: 'No approval needed, proceed to burn',
9701
+ isActionable: true,
9702
+ },
9703
+ {
9704
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9705
+ nextStep: CCTPv2StepName.approve,
9706
+ reason: 'Continue pending approval',
9707
+ isActionable: false, // Waiting for pending transaction
9708
+ },
9709
+ ],
9710
+ // After Burn step
9711
+ [CCTPv2StepName.burn]: [
9712
+ {
9713
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9714
+ nextStep: CCTPv2StepName.fetchAttestation,
9715
+ reason: 'Burn successful, fetch attestation',
9716
+ isActionable: true,
9717
+ },
9718
+ {
9719
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9720
+ nextStep: CCTPv2StepName.burn,
9721
+ reason: 'Retry failed burn',
9722
+ isActionable: true,
9723
+ },
9724
+ {
9725
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9726
+ nextStep: CCTPv2StepName.burn,
9727
+ reason: 'Continue pending burn',
9728
+ isActionable: false, // Waiting for pending transaction
9729
+ },
9730
+ ],
9731
+ // After FetchAttestation step
9732
+ [CCTPv2StepName.fetchAttestation]: [
9733
+ {
9734
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9735
+ nextStep: CCTPv2StepName.mint,
9736
+ reason: 'Attestation fetched, proceed to mint',
9737
+ isActionable: true,
9738
+ },
9739
+ {
9740
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9741
+ nextStep: CCTPv2StepName.fetchAttestation,
9742
+ reason: 'Retry fetching attestation',
9743
+ isActionable: true,
9744
+ },
9745
+ {
9746
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9747
+ nextStep: CCTPv2StepName.fetchAttestation,
9748
+ reason: 'Continue pending attestation fetch',
9749
+ isActionable: false, // Waiting for attestation to be ready
9750
+ },
9751
+ ],
9752
+ // After Mint step
9753
+ [CCTPv2StepName.mint]: [
9754
+ {
9755
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9756
+ nextStep: null,
9757
+ reason: 'Bridge completed successfully',
9758
+ isActionable: false, // Nothing more to do
9759
+ },
9760
+ {
9761
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9762
+ nextStep: CCTPv2StepName.mint,
9763
+ reason: 'Retry failed mint',
9764
+ isActionable: true,
9765
+ },
9766
+ {
9767
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9768
+ nextStep: CCTPv2StepName.mint,
9769
+ reason: 'Continue pending mint',
9770
+ isActionable: false, // Waiting for pending transaction
9771
+ },
9772
+ ],
9773
+ // After ReAttest step
9774
+ [CCTPv2StepName.reAttest]: [
9775
+ {
9776
+ condition: (ctx) => ctx.lastStep?.state === 'success',
9777
+ nextStep: CCTPv2StepName.mint,
9778
+ reason: 'Re-attestation successful, proceed to mint',
9779
+ isActionable: true,
9780
+ },
9781
+ {
9782
+ condition: (ctx) => ctx.lastStep?.state === 'error',
9783
+ nextStep: CCTPv2StepName.mint,
9784
+ reason: 'Re-attestation failed, retry mint to re-initiate recovery',
9785
+ isActionable: true,
9786
+ },
9787
+ {
9788
+ condition: (ctx) => ctx.lastStep?.state === 'pending',
9789
+ nextStep: CCTPv2StepName.mint,
9790
+ reason: 'Re-attestation pending, retry mint to re-initiate recovery',
9791
+ isActionable: true,
9792
+ },
9793
+ ],
9794
+ };
9795
+ /**
9796
+ * Analyze bridge steps to determine retry feasibility and continuation point.
9797
+ *
9798
+ * This function examines the current state of bridge steps to determine the optimal
9799
+ * continuation strategy. It uses a rule-based approach that makes it easy to extend
9800
+ * with new flow patterns and step types in the future.
9801
+ *
9802
+ * The current analysis supports the standard CCTP flow:
9803
+ * **Traditional flow**: Approve → Burn → FetchAttestation → Mint
9804
+ *
9805
+ * Key features:
9806
+ * - Rule-based transitions: Easy to extend with new step types and logic
9807
+ * - Context-aware decisions: Considers execution history and step states
9808
+ * - Actionable logic: Distinguishes between steps requiring user action vs waiting
9809
+ * - Terminal states: Properly handles completion and non-actionable states
9810
+ *
9811
+ * @param bridgeResult - The bridge result containing step execution history.
9812
+ * @returns Analysis result with continuation step and actionability information.
9813
+ * @throws Error when bridgeResult is invalid or contains no steps array.
9814
+ *
9815
+ * @example
9816
+ * ```typescript
9817
+ * import { analyzeSteps } from './analyzeSteps'
9818
+ *
9819
+ * // Failed approval step (requires user action)
9820
+ * const bridgeResult = {
9821
+ * steps: [
9822
+ * { name: 'Approve', state: 'error', errorMessage: 'User rejected' }
9823
+ * ]
9824
+ * }
9825
+ *
9826
+ * const analysis = analyzeSteps(bridgeResult)
9827
+ * // Result: { continuationStep: 'Approve', isRetryable: true,
9828
+ * // reason: 'Retry failed approval' }
9829
+ * ```
9830
+ *
9831
+ * @example
9832
+ * ```typescript
9833
+ * // Pending transaction (requires waiting, not actionable)
9834
+ * const bridgeResult = {
9835
+ * steps: [
9836
+ * { name: 'Approve', state: 'pending' }
9837
+ * ]
9838
+ * }
9839
+ *
9840
+ * const analysis = analyzeSteps(bridgeResult)
9841
+ * // Result: { continuationStep: 'Approve', isRetryable: false,
9842
+ * // reason: 'Continue pending approval' }
9843
+ * ```
9844
+ *
9845
+ * @example
9846
+ * ```typescript
9847
+ * // Completed bridge (nothing to do)
9848
+ * const bridgeResult = {
9849
+ * steps: [
9850
+ * { name: 'Approve', state: 'success' },
9851
+ * { name: 'Burn', state: 'success' },
9852
+ * { name: 'FetchAttestation', state: 'success' },
9853
+ * { name: 'Mint', state: 'success' }
9854
+ * ]
9855
+ * }
9856
+ *
9857
+ * const analysis = analyzeSteps(bridgeResult)
9858
+ * // Result: { continuationStep: null, isRetryable: false,
9859
+ * // reason: 'Bridge completed successfully' }
9860
+ * ```
9861
+ */
9862
+ const analyzeSteps = (bridgeResult) => {
9863
+ // Input validation
9864
+ if (!bridgeResult || !Array.isArray(bridgeResult.steps)) {
9865
+ throw new Error('Invalid bridgeResult: must contain a steps array');
9866
+ }
9867
+ const { steps } = bridgeResult;
9868
+ // Build execution context from step history
9869
+ const context = buildFlowContext(steps);
9870
+ // Determine continuation logic using rule engine
9871
+ const continuation = determineContinuationFromRules(context);
9872
+ return {
9873
+ continuationStep: continuation.nextStep,
9874
+ isActionable: continuation.isActionable,
9875
+ completedSteps: Array.from(context.completedSteps),
9876
+ failedSteps: Array.from(context.failedSteps),
9877
+ reason: continuation.reason,
9878
+ };
9879
+ };
9880
+ /**
9881
+ * Build flow context from the execution history.
9882
+ *
9883
+ * @param steps - Array of executed bridge steps.
9884
+ * @returns Flow context with execution state and history.
9885
+ */
9886
+ function buildFlowContext(steps) {
9887
+ const completedSteps = new Set();
9888
+ const failedSteps = new Set();
9889
+ let lastStep;
9890
+ // Process step history to build context
9891
+ for (const step of steps) {
9892
+ if (step.state === 'success' || step.state === 'noop') {
9893
+ completedSteps.add(step.name);
9894
+ }
9895
+ else if (step.state === 'error') {
9896
+ failedSteps.add(step.name);
9897
+ }
9898
+ // Track the last step for continuation logic
9899
+ lastStep = {
9900
+ name: step.name,
9901
+ state: step.state,
9902
+ };
9903
+ }
9904
+ return {
9905
+ completedSteps,
9906
+ failedSteps,
9907
+ ...(lastStep && { lastStep }),
9908
+ };
9909
+ }
9910
+ /**
9911
+ * Determine continuation step using the rule engine.
9912
+ *
9913
+ * @param context - The flow context with execution history.
9914
+ * @returns Continuation decision with next step and actionability information.
9915
+ */
9916
+ function determineContinuationFromRules(context) {
9917
+ const lastStepName = context.lastStep?.name;
9918
+ // Handle initial state when no steps have been executed
9919
+ if (lastStepName === undefined) {
9920
+ const rules = STEP_TRANSITION_RULES[''];
9921
+ const matchingRule = rules?.find((rule) => rule.condition(context));
9922
+ if (!matchingRule) {
9923
+ return {
9924
+ nextStep: null,
9925
+ isActionable: false,
9926
+ reason: 'No initial state rule found',
9927
+ };
9928
+ }
9929
+ return {
9930
+ nextStep: matchingRule.nextStep,
9931
+ isActionable: matchingRule.isActionable,
9932
+ reason: matchingRule.reason,
9933
+ };
9934
+ }
9935
+ // A step with an empty name is ambiguous and should be treated as an unrecoverable state.
9936
+ if (lastStepName === '') {
9937
+ return {
9938
+ nextStep: null,
9939
+ isActionable: false,
9940
+ reason: 'No transition rules defined for step with empty name',
9941
+ };
9942
+ }
9943
+ const rules = STEP_TRANSITION_RULES[lastStepName];
9944
+ if (!rules) {
9945
+ return {
9946
+ nextStep: null,
9947
+ isActionable: false,
9948
+ reason: `No transition rules defined for step: ${lastStepName}`,
9949
+ };
9950
+ }
9951
+ // Find the first matching rule
9952
+ const matchingRule = rules.find((rule) => rule.condition(context));
9953
+ if (!matchingRule) {
9954
+ return {
9955
+ nextStep: null,
9956
+ isActionable: false,
9957
+ reason: `No matching transition rule for current context`,
9958
+ };
9959
+ }
9960
+ return {
9961
+ nextStep: matchingRule.nextStep,
9962
+ isActionable: matchingRule.isActionable,
9963
+ reason: matchingRule.reason,
9964
+ };
9965
+ }
9966
+
9967
+ /**
9968
+ * Find a step by name in the bridge result.
9969
+ *
9970
+ * @param result - The bridge result to search.
9971
+ * @param stepName - The name of the step to find.
9972
+ * @returns The step if found, undefined otherwise.
9973
+ *
9974
+ * @example
9975
+ * ```typescript
9976
+ * import { findStepByName } from './findStep'
9977
+ *
9978
+ * const burnStep = findStepByName(result, 'burn')
9979
+ * if (burnStep) {
9980
+ * console.log('Burn tx:', burnStep.txHash)
9981
+ * }
9982
+ * ```
9983
+ */
9984
+ function findStepByName(result, stepName) {
9985
+ return result.steps.find((step) => step.name === stepName);
9986
+ }
9987
+ /**
9988
+ * Find a pending step by name and return it with its index.
9989
+ *
9990
+ * Searches for a step that matches both the step name and has a pending state.
9991
+ *
9992
+ * @param result - The bridge result containing steps to search through.
9993
+ * @param stepName - The step name to find (e.g., 'burn', 'mint', 'fetchAttestation').
9994
+ * @returns An object containing the step and its index in the steps array.
9995
+ * @throws KitError if the specified pending step is not found.
9996
+ *
9997
+ * @example
9998
+ * ```typescript
9999
+ * import { findPendingStep } from './findStep'
10000
+ *
10001
+ * const { step, index } = findPendingStep(result, 'burn')
10002
+ * console.log('Pending step:', step.name, 'at index:', index)
10003
+ * ```
10004
+ */
10005
+ function findPendingStep(result, stepName) {
10006
+ const index = result.steps.findIndex((step) => step.name === stepName && step.state === 'pending');
10007
+ if (index === -1) {
10008
+ throw new KitError({
10009
+ ...InputError.VALIDATION_FAILED,
10010
+ recoverability: 'FATAL',
10011
+ message: `Pending step "${stepName}" not found in result`,
10012
+ });
10013
+ }
10014
+ const step = result.steps[index];
10015
+ if (!step) {
10016
+ throw new KitError({
10017
+ ...InputError.VALIDATION_FAILED,
10018
+ recoverability: 'FATAL',
10019
+ message: 'Pending step is undefined',
10020
+ });
10021
+ }
10022
+ return { step, index };
10023
+ }
10024
+ /**
10025
+ * Get the burn transaction hash from bridge result.
10026
+ *
10027
+ * @param result - The bridge result.
10028
+ * @returns The burn transaction hash, or undefined if not found.
10029
+ *
10030
+ * @example
10031
+ * ```typescript
10032
+ * import { getBurnTxHash } from './findStep'
10033
+ *
10034
+ * const burnTxHash = getBurnTxHash(result)
10035
+ * if (burnTxHash) {
10036
+ * console.log('Burn tx hash:', burnTxHash)
10037
+ * }
10038
+ * ```
10039
+ */
10040
+ function getBurnTxHash(result) {
10041
+ return findStepByName(result, CCTPv2StepName.burn)?.txHash;
10042
+ }
10043
+ /**
10044
+ * Get the attestation data from bridge result.
10045
+ *
10046
+ * @param result - The bridge result.
10047
+ * @returns The attestation data, or undefined if not found.
10048
+ *
10049
+ * @example
10050
+ * ```typescript
10051
+ * import { getAttestationData } from './findStep'
10052
+ *
10053
+ * const attestation = getAttestationData(result)
10054
+ * if (attestation) {
10055
+ * console.log('Attestation:', attestation.message)
10056
+ * }
10057
+ * ```
10058
+ */
10059
+ function getAttestationData(result) {
10060
+ // Prefer reAttest data (most recent attestation after expiry)
10061
+ const reAttestStep = findStepByName(result, CCTPv2StepName.reAttest);
10062
+ if (reAttestStep?.state === 'success' && reAttestStep.data) {
10063
+ return reAttestStep.data;
10064
+ }
10065
+ // Fall back to fetchAttestation step
10066
+ const fetchStep = findStepByName(result, CCTPv2StepName.fetchAttestation);
10067
+ return fetchStep?.data;
10068
+ }
10069
+
10070
+ /**
10071
+ * Check if the analysis indicates a non-actionable pending state.
10072
+ *
10073
+ * A pending state is non-actionable when there's a continuation step but
10074
+ * the analysis marks it as not actionable, typically because we need to
10075
+ * wait for an ongoing operation to complete.
10076
+ *
10077
+ * @param analysis - The step analysis result from analyzeSteps.
10078
+ * @param result - The bridge result to check for pending steps.
10079
+ * @returns True if there is a pending step that we should wait for.
10080
+ *
10081
+ * @example
10082
+ * ```typescript
10083
+ * import { hasPendingState } from './stepUtils'
10084
+ * import { analyzeSteps } from '../analyzeSteps'
10085
+ *
10086
+ * const analysis = analyzeSteps(bridgeResult)
10087
+ * if (hasPendingState(analysis, bridgeResult)) {
10088
+ * // Wait for the pending operation to complete
10089
+ * }
10090
+ * ```
10091
+ */
10092
+ /**
10093
+ * Evaluate a transaction receipt and return the corresponding step state
10094
+ * and error message. Centralises the success/revert/unconfirmed logic so
10095
+ * every call-site behaves identically.
10096
+ *
10097
+ * @param receipt - The transaction receipt containing status and block info.
10098
+ * @param txHash - The transaction hash used in error messages.
10099
+ * @returns An object with `state` and an optional `errorMessage`.
10100
+ *
10101
+ * @example
10102
+ * ```typescript
10103
+ * const outcome = evaluateTransactionOutcome(receipt, '0xabc...')
10104
+ * step.state = outcome.state
10105
+ * if (outcome.errorMessage) step.errorMessage = outcome.errorMessage
10106
+ * ```
10107
+ */
10108
+ function evaluateTransactionOutcome(receipt, txHash) {
10109
+ if (receipt.status === 'success' && receipt.blockNumber) {
10110
+ return { state: 'success' };
10111
+ }
10112
+ return {
10113
+ state: 'error',
10114
+ errorMessage: receipt.status === 'reverted'
10115
+ ? `Transaction ${txHash} was reverted`
10116
+ : 'Transaction was not confirmed on-chain',
10117
+ };
10118
+ }
10119
+ function hasPendingState(analysis, result) {
10120
+ // Check if there's a continuation step that's marked as non-actionable
10121
+ if (analysis.continuationStep === null || analysis.isActionable) {
10122
+ return false;
10123
+ }
10124
+ // Verify that the continuation step actually exists and is in pending state
10125
+ const pendingStep = result.steps.find((step) => step.name === analysis.continuationStep && step.state === 'pending');
10126
+ return pendingStep !== undefined;
10127
+ }
10128
+ /**
10129
+ * Check if the step is the last one in the execution flow.
10130
+ *
10131
+ * @param step - The step object to check.
10132
+ * @param stepNames - The ordered list of step names in the execution flow.
10133
+ * @returns True if this is the last step in the flow.
10134
+ *
10135
+ * @example
10136
+ * ```typescript
10137
+ * import { isLastStep } from './stepUtils'
10138
+ *
10139
+ * const stepNames = ['approve', 'burn', 'fetchAttestation', 'mint']
10140
+ * isLastStep({ name: 'mint' }, stepNames) // true
10141
+ * isLastStep({ name: 'burn' }, stepNames) // false
10142
+ * ```
10143
+ */
10144
+ function isLastStep(step, stepNames) {
10145
+ const stepIndex = stepNames.indexOf(step.name);
10146
+ return stepIndex === -1 || stepIndex >= stepNames.length - 1;
10147
+ }
10148
+ /**
10149
+ * Wait for a pending transaction to complete.
10150
+ *
10151
+ * Poll the adapter until the transaction is confirmed on-chain and return
10152
+ * the updated step with success or error state based on the receipt.
10153
+ *
10154
+ * @param pendingStep - The full step object containing the transaction hash.
10155
+ * @param adapter - The adapter to use for waiting.
10156
+ * @param chain - The chain where the transaction was submitted.
10157
+ * @returns The updated step object with success or error state.
10158
+ *
10159
+ * @throws KitError when the pending step has no transaction hash.
10160
+ *
10161
+ * @example
10162
+ * ```typescript
10163
+ * import { waitForPendingTransaction } from './bridgeStepUtils'
10164
+ *
10165
+ * const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
10166
+ * const updatedStep = await waitForPendingTransaction(pendingStep, adapter, chain)
10167
+ * // updatedStep.state is now 'success' or 'error'
10168
+ * ```
10169
+ */
10170
+ async function waitForPendingTransaction(pendingStep, adapter, chain) {
10171
+ if (!pendingStep.txHash) {
10172
+ throw new KitError({
10173
+ ...InputError.VALIDATION_FAILED,
10174
+ recoverability: 'FATAL',
10175
+ message: `Cannot wait for pending ${pendingStep.name}: no transaction hash available`,
10176
+ });
10177
+ }
10178
+ const txHash = pendingStep.txHash;
10179
+ const txReceipt = await retryAsync(async () => adapter.waitForTransaction(txHash, undefined, chain), {
10180
+ isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
10181
+ });
10182
+ const outcome = evaluateTransactionOutcome(txReceipt, txHash);
10183
+ return {
10184
+ ...pendingStep,
10185
+ state: outcome.state,
10186
+ data: txReceipt,
10187
+ explorerUrl: buildExplorerUrl(chain, txHash),
10188
+ ...(outcome.errorMessage ? { errorMessage: outcome.errorMessage } : {}),
10189
+ };
10190
+ }
10191
+ /**
10192
+ * Wait for a pending step to complete.
10193
+ *
10194
+ * For transaction steps: waits for the transaction to be confirmed.
10195
+ * For attestation: re-executes the attestation fetch.
10196
+ *
10197
+ * @typeParam TFromAdapterCapabilities - The capabilities of the source adapter.
10198
+ * @typeParam TToAdapterCapabilities - The capabilities of the destination adapter.
10199
+ * @param pendingStep - The full step object (with name, state, txHash, data, etc.) to resolve.
10200
+ * @param adapter - The adapter to use.
10201
+ * @param chain - The chain where the step is executing.
10202
+ * @param context - The retry context.
10203
+ * @param result - The bridge result.
10204
+ * @param provider - The CCTP v2 bridging provider.
10205
+ * @returns The resolved step object with updated state.
10206
+ *
10207
+ * @throws KitError when fetching attestation but burn transaction hash is not found.
10208
+ *
10209
+ * @example
10210
+ * ```typescript
10211
+ * import { waitForStepToComplete } from './bridgeStepUtils'
10212
+ *
10213
+ * const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
10214
+ * const updatedStep = await waitForStepToComplete(
10215
+ * pendingStep,
10216
+ * adapter,
10217
+ * chain,
10218
+ * context,
10219
+ * result,
10220
+ * provider,
10221
+ * )
10222
+ * // updatedStep.state is now 'success' or 'error'
10223
+ * ```
10224
+ */
10225
+ async function waitForStepToComplete(pendingStep, adapter, chain, context, result, provider) {
10226
+ if (pendingStep.name === CCTPv2StepName.fetchAttestation) {
10227
+ // For attestation, re-run the fetch (it has built-in polling)
10228
+ const burnTxHash = getBurnTxHash(result);
10229
+ if (!burnTxHash) {
10230
+ throw new KitError({
10231
+ ...InputError.VALIDATION_FAILED,
10232
+ recoverability: 'FATAL',
10233
+ message: 'Cannot fetch attestation: burn transaction hash not found',
10234
+ });
10235
+ }
10236
+ const sourceAddress = result.source.address;
10237
+ const attestation = await provider.fetchAttestation({
10238
+ chain: result.source.chain,
10239
+ adapter: context.from,
10240
+ address: sourceAddress,
10241
+ }, burnTxHash);
10242
+ return {
10243
+ ...pendingStep,
10244
+ state: 'success',
10245
+ data: attestation,
10246
+ };
10247
+ }
10248
+ // For transaction steps, wait for the transaction to complete
10249
+ return waitForPendingTransaction(pendingStep, adapter, chain);
10250
+ }
10251
+
10252
+ /**
10253
+ * Executes a prepared chain request and returns the result as a bridge step.
10254
+ *
10255
+ * This function takes a prepared chain request (containing transaction data) and executes
10256
+ * it using the appropriate adapter. It handles the execution details and formats
10257
+ * the result as a standardized bridge step with transaction details and explorer URLs.
10258
+ *
10259
+ * @param params - The execution parameters containing:
10260
+ * - `name`: The name of the step
10261
+ * - `request`: The prepared chain request containing transaction data
10262
+ * - `adapter`: The adapter that will execute the transaction
10263
+ * - `confirmations`: The number of confirmations to wait for (defaults to 1)
10264
+ * - `timeout`: The timeout for the request in milliseconds
10265
+ * @returns The bridge step with the transaction details and explorer URL
10266
+ * @throws If the transaction execution fails
10267
+ *
10268
+ * @example
10269
+ * ```typescript
10270
+ * const step = await executePreparedChainRequest({
10271
+ * name: 'approve',
10272
+ * request: preparedRequest,
10273
+ * adapter: adapter,
10274
+ * confirmations: 2,
10275
+ * timeout: 30000
10276
+ * })
10277
+ * console.log('Transaction hash:', step.txHash)
10278
+ * ```
10279
+ */
10280
+ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, }) {
10281
+ const step = { name, state: 'pending' };
10282
+ try {
10283
+ /**
10284
+ * No-op requests are not executed.
10285
+ * We return a noop step instead.
10286
+ */
10287
+ if (request.type === 'noop') {
10288
+ step.state = 'noop';
10289
+ return step;
10290
+ }
10291
+ const txHash = await request.execute();
10292
+ step.txHash = txHash;
10293
+ const retryOptions = {
10294
+ isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
10295
+ };
10296
+ if (timeout !== undefined) {
10297
+ retryOptions.deadlineMs = Date.now() + timeout;
10298
+ }
10299
+ const transaction = await retryAsync(async () => adapter.waitForTransaction(txHash, { confirmations, timeout }, chain), retryOptions);
10300
+ const outcome = evaluateTransactionOutcome(transaction, txHash);
10301
+ step.state = outcome.state;
10302
+ step.data = transaction;
10303
+ // Generate explorer URL for the step
10304
+ step.explorerUrl = buildExplorerUrl(chain, txHash);
10305
+ if (outcome.errorMessage) {
10306
+ step.errorMessage = outcome.errorMessage;
10307
+ }
10308
+ }
10309
+ catch (err) {
10310
+ step.state = 'error';
10311
+ step.error = err;
10312
+ // Optionally parse for common blockchain error formats
10313
+ if (err instanceof Error) {
10314
+ step.errorMessage = err.message;
10315
+ }
10316
+ else if (typeof err === 'object' && err != null && 'message' in err) {
10317
+ step.errorMessage = String(err.message);
10318
+ }
10319
+ else {
10320
+ step.errorMessage = `Unknown error occurred during ${name} step.`;
10321
+ }
10322
+ }
10323
+ return step;
10324
+ }
10325
+
10326
+ /**
10327
+ * Approves the TokenMessenger contract to spend USDC tokens for a bridge operation.
10328
+ *
10329
+ * This function handles the approval step of the CCTP v2 bridge process, allowing
10330
+ * the TokenMessenger contract to spend the specified amount of USDC tokens on behalf
10331
+ * of the user. This is a prerequisite for the subsequent burn operation.
10332
+ *
10333
+ * @param params - The bridge parameters containing source, destination, amount and optional config
10334
+ * @param sourceChain - The source chain definition where the approval will occur
10335
+ * @returns Promise resolving to the bridge step with transaction details
10336
+ * @throws {KitError} If the parameters are invalid
10337
+ * @throws {BridgeError} If the approval transaction fails
10338
+ *
10339
+ * @example
10340
+ * ```typescript
10341
+ * const approveStep = await approve(params, sourceChain)
10342
+ * console.log('Approval tx:', approveStep.transactionHash)
10343
+ * ```
10344
+ */
10345
+ async function bridgeApproval({ params, provider, }) {
10346
+ // Calculate the approval amount
10347
+ const customFee = BigInt(params.config?.customFee?.value ?? '0');
10348
+ const amountBigInt = BigInt(params.amount);
10349
+ const approvalAmount = (amountBigInt + customFee).toString();
10350
+ return await executePreparedChainRequest({
10351
+ name: 'approve',
10352
+ adapter: params.source.adapter,
10353
+ chain: params.source.chain,
10354
+ request: await provider.approve(params.source, approvalAmount),
10355
+ });
10356
+ }
10357
+
10358
+ /**
10359
+ * Executes a deposit-for-burn operation on the source chain to initiate a bridge.
10360
+ *
10361
+ * This function handles the burning step of the CCTP v2 bridge process, where USDC tokens
10362
+ * are burned on the source chain to create a message that can be used to mint equivalent
10363
+ * tokens on the destination chain.
10364
+ *
10365
+ * @param params - The bridge parameters containing source, destination, amount and optional config
10366
+ * @param sourceChain - The source chain definition where the burn will occur
10367
+ * @returns Promise resolving to the bridge step with transaction details
10368
+ * @throws {KitError} If the parameters are invalid
10369
+ * @throws \{BridgeError\} If the burn transaction fails
10370
+ *
9126
10371
  * @example
9127
10372
  * ```typescript
9128
10373
  * const burnStep = await burn(params, sourceChain)
@@ -10673,10 +11918,11 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
10673
11918
  txHash: receipt.txHash,
10674
11919
  })),
10675
11920
  });
10676
- step.state = transaction.blockNumber === undefined ? 'error' : 'success';
11921
+ const outcome = evaluateTransactionOutcome(transaction, receipt.txHash);
11922
+ step.state = outcome.state;
10677
11923
  step.data = transaction;
10678
- if (transaction.blockNumber === undefined) {
10679
- step.errorMessage = 'Transaction was not confirmed on-chain.';
11924
+ if (outcome.errorMessage) {
11925
+ step.errorMessage = outcome.errorMessage;
10680
11926
  }
10681
11927
  }
10682
11928
  catch (err) {
@@ -10688,7 +11934,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
10688
11934
  return step;
10689
11935
  }
10690
11936
 
10691
- var version = "1.6.2";
11937
+ var version = "1.6.3";
10692
11938
  var pkg = {
10693
11939
  version: version};
10694
11940
 
@@ -10996,681 +12242,266 @@ const hexStringSchema = z
10996
12242
  .refine((value) => value.trim().length > 0, 'Hex string cannot be empty')
10997
12243
  .refine((value) => value.startsWith('0x'), 'Hex string must start with 0x prefix')
10998
12244
  .refine((value) => {
10999
- const hexPattern = /^0x[0-9a-fA-F]+$/;
11000
- return hexPattern.test(value);
11001
- }, 'Hex string contains invalid characters. Only hexadecimal characters (0-9, a-f, A-F) are allowed after 0x');
11002
- /**
11003
- * Schema for validating EVM addresses.
11004
- *
11005
- * This schema validates that a string is a properly formatted EVM address:
11006
- * - Must be a valid hex string with '0x' prefix
11007
- * - Must be exactly 42 characters long (0x + 40 hex characters)
11008
- *
11009
- * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11010
- *
11011
- * @example
11012
- * ```typescript
11013
- * import { evmAddressSchema } from '@core/adapter'
11014
- *
11015
- * const validAddress = '0x1234567890123456789012345678901234567890'
11016
- *
11017
- * const result = evmAddressSchema.safeParse(validAddress)
11018
- * console.log(result.success) // true
11019
- * ```
11020
- */
11021
- hexStringSchema.refine((value) => value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)');
11022
- /**
11023
- * Schema for validating transaction hashes.
11024
- *
11025
- * This schema validates that a string is a properly formatted transaction hash:
11026
- * - Must be a valid hex string with '0x' prefix
11027
- * - Must be exactly 66 characters long (0x + 64 hex characters)
11028
- *
11029
- * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11030
- *
11031
- * @example
11032
- * ```typescript
11033
- * import { evmTransactionHashSchema } from '@core/adapter'
11034
- *
11035
- * const validTxHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
11036
- *
11037
- * const result = evmTransactionHashSchema.safeParse(validTxHash)
11038
- * console.log(result.success) // true
11039
- * ```
11040
- */
11041
- hexStringSchema.refine((value) => value.length === 66, 'Transaction hash must be exactly 66 characters long (0x + 64 hex characters)');
11042
- /**
11043
- * Schema for validating base58-encoded strings.
11044
- *
11045
- * This schema validates that a string:
11046
- * - Is a string type
11047
- * - Is not empty after trimming
11048
- * - Contains only valid base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z)
11049
- * - Does not contain commonly confused characters (0, O, I, l)
11050
- *
11051
- * @remarks
11052
- * This schema does not validate length, making it suitable for various base58-encoded data
11053
- * like Solana addresses, transaction signatures, and other base58-encoded data.
11054
- *
11055
- * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11056
- *
11057
- * @example
11058
- * ```typescript
11059
- * import { base58StringSchema } from '@core/adapter'
11060
- *
11061
- * const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
11062
- * const validTxHash = '3Jf8k2L5mN9pQ7rS1tV4wX6yZ8aB2cD4eF5gH7iJ9kL1mN3oP5qR7sT9uV1wX3yZ5'
11063
- *
11064
- * const addressResult = base58StringSchema.safeParse(validAddress)
11065
- * const txHashResult = base58StringSchema.safeParse(validTxHash)
11066
- * console.log(addressResult.success) // true
11067
- * console.log(txHashResult.success) // true
11068
- * ```
11069
- */
11070
- const base58StringSchema = z
11071
- .string()
11072
- .min(1, 'Base58 string is required')
11073
- .refine((value) => value.trim().length > 0, 'Base58 string cannot be empty')
11074
- .refine((value) => {
11075
- // Base58 alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
11076
- // Excludes: 0, O, I, l to avoid confusion
11077
- const base58Pattern = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/;
11078
- return base58Pattern.test(value);
11079
- }, 'Base58 string contains invalid characters. Only base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z) are allowed');
11080
- /**
11081
- * Schema for validating Solana addresses.
11082
- *
11083
- * This schema validates that a string is a properly formatted Solana address:
11084
- * - Must be a valid base58-encoded string
11085
- * - Must be between 32-44 characters long (typical length for base58-encoded 32-byte addresses)
11086
- *
11087
- * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11088
- *
11089
- * @example
11090
- * ```typescript
11091
- * import { solanaAddressSchema } from '@core/adapter'
11092
- *
11093
- * const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
11094
- *
11095
- * const result = solanaAddressSchema.safeParse(validAddress)
11096
- * console.log(result.success) // true
11097
- * ```
11098
- */
11099
- base58StringSchema.refine((value) => value.length >= 32 && value.length <= 44, 'Solana address must be between 32-44 characters long (base58-encoded 32-byte address)');
11100
- /**
11101
- * Schema for validating Solana transaction hashes.
11102
- *
11103
- * This schema validates that a string is a properly formatted Solana transaction hash:
11104
- * - Must be a valid base58-encoded string
11105
- * - Must be between 86-88 characters long (typical length for base58-encoded 64-byte signatures)
11106
- *
11107
- * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11108
- *
11109
- * @example
11110
- * ```typescript
11111
- * import { solanaTransactionHashSchema } from '@core/adapter'
11112
- *
11113
- * const validTxHash = '5VfYmGBjvQKe3xgLtTQPSMEUdpEVHrJwLK7pKBJWKzYpNBE2g3kJrq7RSe9M8DqzQJ5J2aZPTjHLvd4WgxPpJKS'
11114
- *
11115
- * const result = solanaTransactionHashSchema.safeParse(validTxHash)
11116
- * console.log(result.success) // true
11117
- * ```
11118
- */
11119
- base58StringSchema.refine((value) => value.length >= 86 && value.length <= 88, 'Solana transaction hash must be between 86-88 characters long (base58-encoded 64-byte signature)');
11120
- /**
11121
- * Schema for validating Adapter objects.
11122
- * Checks for the required methods that define an Adapter.
11123
- */
11124
- z.object({
11125
- prepare: z.function(),
11126
- waitForTransaction: z.function(),
11127
- getAddress: z.function(),
11128
- });
11129
-
11130
- /**
11131
- * Validate that the adapter has sufficient token balance for a transaction.
11132
- *
11133
- * This function checks if the adapter's current token balance is greater than or equal
11134
- * to the requested transaction amount. It throws a KitError with code 9001
11135
- * (BALANCE_INSUFFICIENT_TOKEN) if the balance is insufficient, providing detailed
11136
- * information about the shortfall.
11137
- *
11138
- * @param params - The validation parameters containing adapter, amount, token, and token address.
11139
- * @returns A promise that resolves to void if validation passes.
11140
- * @throws {KitError} When the adapter's balance is less than the required amount (code: 9001).
11141
- *
11142
- * @example
11143
- * ```typescript
11144
- * import { validateBalanceForTransaction } from '@core/adapter'
11145
- * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
11146
- * import { isKitError, ERROR_TYPES } from '@core/errors'
11147
- *
11148
- * const adapter = createViemAdapterFromPrivateKey({
11149
- * privateKey: '0x...',
11150
- * chain: 'Ethereum',
11151
- * })
11152
- *
11153
- * try {
11154
- * await validateBalanceForTransaction({
11155
- * adapter,
11156
- * amount: '1000000', // 1 USDC (6 decimals)
11157
- * token: 'USDC',
11158
- * tokenAddress: '0xA0b86a33E6441c8C1c7C16e4c5e3e5b5e4c5e3e5b5e4c5e',
11159
- * operationContext: { chain: 'Ethereum' },
11160
- * })
11161
- * console.log('Balance validation passed')
11162
- * } catch (error) {
11163
- * if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
11164
- * console.error('Insufficient funds:', error.message)
11165
- * }
11166
- * }
11167
- * ```
11168
- */
11169
- const validateBalanceForTransaction = async (params) => {
11170
- const { amount, adapter, token, tokenAddress, operationContext } = params;
11171
- const balancePrepared = await adapter.prepareAction('usdc.balanceOf', {
11172
- walletAddress: operationContext.address,
11173
- }, operationContext);
11174
- const balance = await balancePrepared.execute();
11175
- if (BigInt(balance) < BigInt(amount)) {
11176
- // Extract chain name from operationContext
11177
- const chainName = extractChainInfo(operationContext.chain).name;
11178
- // Create KitError with rich context in trace
11179
- throw createInsufficientTokenBalanceError(chainName, token, {
11180
- balance: balance.toString(),
11181
- amount,
11182
- tokenAddress,
11183
- walletAddress: operationContext.address,
11184
- });
11185
- }
11186
- };
11187
-
12245
+ const hexPattern = /^0x[0-9a-fA-F]+$/;
12246
+ return hexPattern.test(value);
12247
+ }, 'Hex string contains invalid characters. Only hexadecimal characters (0-9, a-f, A-F) are allowed after 0x');
11188
12248
  /**
11189
- * Validate that the adapter has sufficient native token balance for transaction fees.
12249
+ * Schema for validating EVM addresses.
11190
12250
  *
11191
- * This function checks if the adapter's current native token balance (ETH, SOL, etc.)
11192
- * is greater than zero. It throws a KitError with code 9002 (BALANCE_INSUFFICIENT_GAS)
11193
- * if the balance is zero, indicating the wallet cannot pay for transaction fees.
12251
+ * This schema validates that a string is a properly formatted EVM address:
12252
+ * - Must be a valid hex string with '0x' prefix
12253
+ * - Must be exactly 42 characters long (0x + 40 hex characters)
11194
12254
  *
11195
- * @param params - The validation parameters containing adapter and operation context.
11196
- * @returns A promise that resolves to void if validation passes.
11197
- * @throws {KitError} When the adapter's native balance is zero (code: 9002).
12255
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11198
12256
  *
11199
12257
  * @example
11200
12258
  * ```typescript
11201
- * import { validateNativeBalanceForTransaction } from '@core/adapter'
11202
- * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
11203
- * import { isKitError, ERROR_TYPES } from '@core/errors'
12259
+ * import { evmAddressSchema } from '@core/adapter'
11204
12260
  *
11205
- * const adapter = createViemAdapterFromPrivateKey({
11206
- * privateKey: '0x...',
11207
- * chain: 'Ethereum',
11208
- * })
12261
+ * const validAddress = '0x1234567890123456789012345678901234567890'
11209
12262
  *
11210
- * try {
11211
- * await validateNativeBalanceForTransaction({
11212
- * adapter,
11213
- * operationContext: { chain: 'Ethereum' },
11214
- * })
11215
- * console.log('Native balance validation passed')
11216
- * } catch (error) {
11217
- * if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
11218
- * console.error('Insufficient gas funds:', error.message)
11219
- * }
11220
- * }
12263
+ * const result = evmAddressSchema.safeParse(validAddress)
12264
+ * console.log(result.success) // true
11221
12265
  * ```
11222
12266
  */
11223
- const validateNativeBalanceForTransaction = async (params) => {
11224
- const { adapter, operationContext } = params;
11225
- const balancePrepared = await adapter.prepareAction('native.balanceOf', {
11226
- walletAddress: operationContext.address,
11227
- }, operationContext);
11228
- const balance = await balancePrepared.execute();
11229
- if (BigInt(balance) === 0n) {
11230
- // Extract chain name from operationContext
11231
- const chainName = extractChainInfo(operationContext.chain).name;
11232
- // Create KitError with rich context in trace
11233
- throw createInsufficientGasError(chainName, {
11234
- balance: '0',
11235
- walletAddress: operationContext.address,
11236
- });
11237
- }
11238
- };
11239
-
12267
+ hexStringSchema.refine((value) => value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)');
11240
12268
  /**
11241
- * Permit signature standards for gasless token approvals.
12269
+ * Schema for validating transaction hashes.
11242
12270
  *
11243
- * Defines the permit types that can be used to approve token spending
11244
- * without requiring a separate approval transaction.
12271
+ * This schema validates that a string is a properly formatted transaction hash:
12272
+ * - Must be a valid hex string with '0x' prefix
12273
+ * - Must be exactly 66 characters long (0x + 64 hex characters)
11245
12274
  *
11246
- * @remarks
11247
- * - NONE: No permit, tokens must be pre-approved via separate transaction
11248
- * - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
11249
- */
11250
- var PermitType;
11251
- (function (PermitType) {
11252
- /** No permit required - tokens must be pre-approved */
11253
- PermitType[PermitType["NONE"] = 0] = "NONE";
11254
- /** EIP-2612 standard permit */
11255
- PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
11256
- })(PermitType || (PermitType = {}));
11257
-
11258
- /**
11259
- * CCTP bridge step names that can occur in the bridging flow.
12275
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11260
12276
  *
11261
- * This object provides type safety for step names and represents all possible
11262
- * steps that can be executed during a CCTP bridge operation. Using const assertions
11263
- * makes this tree-shakable and follows modern TypeScript best practices.
11264
- */
11265
- const CCTPv2StepName = {
11266
- approve: 'approve',
11267
- burn: 'burn',
11268
- fetchAttestation: 'fetchAttestation',
11269
- mint: 'mint',
11270
- reAttest: 'reAttest',
11271
- };
11272
- /**
11273
- * Conditional step transition rules for CCTP bridge flow.
12277
+ * @example
12278
+ * ```typescript
12279
+ * import { evmTransactionHashSchema } from '@core/adapter'
11274
12280
  *
11275
- * Rules are evaluated in order - the first matching condition determines the next step.
11276
- * This approach supports flexible flow logic and makes it easy to extend with new patterns.
12281
+ * const validTxHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
12282
+ *
12283
+ * const result = evmTransactionHashSchema.safeParse(validTxHash)
12284
+ * console.log(result.success) // true
12285
+ * ```
11277
12286
  */
11278
- const STEP_TRANSITION_RULES = {
11279
- // Starting state - no steps executed yet
11280
- '': [
11281
- {
11282
- condition: () => true,
11283
- nextStep: CCTPv2StepName.approve,
11284
- reason: 'Start with approval step',
11285
- isActionable: true,
11286
- },
11287
- ],
11288
- // After Approve step
11289
- [CCTPv2StepName.approve]: [
11290
- {
11291
- condition: (ctx) => ctx.lastStep?.state === 'success',
11292
- nextStep: CCTPv2StepName.burn,
11293
- reason: 'Approval successful, proceed to burn',
11294
- isActionable: true,
11295
- },
11296
- {
11297
- condition: (ctx) => ctx.lastStep?.state === 'error',
11298
- nextStep: CCTPv2StepName.approve,
11299
- reason: 'Retry failed approval',
11300
- isActionable: true,
11301
- },
11302
- {
11303
- condition: (ctx) => ctx.lastStep?.state === 'noop',
11304
- nextStep: CCTPv2StepName.burn,
11305
- reason: 'No approval needed, proceed to burn',
11306
- isActionable: true,
11307
- },
11308
- {
11309
- condition: (ctx) => ctx.lastStep?.state === 'pending',
11310
- nextStep: CCTPv2StepName.approve,
11311
- reason: 'Continue pending approval',
11312
- isActionable: false, // Waiting for pending transaction
11313
- },
11314
- ],
11315
- // After Burn step
11316
- [CCTPv2StepName.burn]: [
11317
- {
11318
- condition: (ctx) => ctx.lastStep?.state === 'success',
11319
- nextStep: CCTPv2StepName.fetchAttestation,
11320
- reason: 'Burn successful, fetch attestation',
11321
- isActionable: true,
11322
- },
11323
- {
11324
- condition: (ctx) => ctx.lastStep?.state === 'error',
11325
- nextStep: CCTPv2StepName.burn,
11326
- reason: 'Retry failed burn',
11327
- isActionable: true,
11328
- },
11329
- {
11330
- condition: (ctx) => ctx.lastStep?.state === 'pending',
11331
- nextStep: CCTPv2StepName.burn,
11332
- reason: 'Continue pending burn',
11333
- isActionable: false, // Waiting for pending transaction
11334
- },
11335
- ],
11336
- // After FetchAttestation step
11337
- [CCTPv2StepName.fetchAttestation]: [
11338
- {
11339
- condition: (ctx) => ctx.lastStep?.state === 'success',
11340
- nextStep: CCTPv2StepName.mint,
11341
- reason: 'Attestation fetched, proceed to mint',
11342
- isActionable: true,
11343
- },
11344
- {
11345
- condition: (ctx) => ctx.lastStep?.state === 'error',
11346
- nextStep: CCTPv2StepName.fetchAttestation,
11347
- reason: 'Retry fetching attestation',
11348
- isActionable: true,
11349
- },
11350
- {
11351
- condition: (ctx) => ctx.lastStep?.state === 'pending',
11352
- nextStep: CCTPv2StepName.fetchAttestation,
11353
- reason: 'Continue pending attestation fetch',
11354
- isActionable: false, // Waiting for attestation to be ready
11355
- },
11356
- ],
11357
- // After Mint step
11358
- [CCTPv2StepName.mint]: [
11359
- {
11360
- condition: (ctx) => ctx.lastStep?.state === 'success',
11361
- nextStep: null,
11362
- reason: 'Bridge completed successfully',
11363
- isActionable: false, // Nothing more to do
11364
- },
11365
- {
11366
- condition: (ctx) => ctx.lastStep?.state === 'error',
11367
- nextStep: CCTPv2StepName.mint,
11368
- reason: 'Retry failed mint',
11369
- isActionable: true,
11370
- },
11371
- {
11372
- condition: (ctx) => ctx.lastStep?.state === 'pending',
11373
- nextStep: CCTPv2StepName.mint,
11374
- reason: 'Continue pending mint',
11375
- isActionable: false, // Waiting for pending transaction
11376
- },
11377
- ],
11378
- // After ReAttest step
11379
- [CCTPv2StepName.reAttest]: [
11380
- {
11381
- condition: (ctx) => ctx.lastStep?.state === 'success',
11382
- nextStep: CCTPv2StepName.mint,
11383
- reason: 'Re-attestation successful, proceed to mint',
11384
- isActionable: true,
11385
- },
11386
- {
11387
- condition: (ctx) => ctx.lastStep?.state === 'error',
11388
- nextStep: CCTPv2StepName.mint,
11389
- reason: 'Re-attestation failed, retry mint to re-initiate recovery',
11390
- isActionable: true,
11391
- },
11392
- {
11393
- condition: (ctx) => ctx.lastStep?.state === 'pending',
11394
- nextStep: CCTPv2StepName.mint,
11395
- reason: 'Re-attestation pending, retry mint to re-initiate recovery',
11396
- isActionable: true,
11397
- },
11398
- ],
11399
- };
12287
+ hexStringSchema.refine((value) => value.length === 66, 'Transaction hash must be exactly 66 characters long (0x + 64 hex characters)');
11400
12288
  /**
11401
- * Analyze bridge steps to determine retry feasibility and continuation point.
11402
- *
11403
- * This function examines the current state of bridge steps to determine the optimal
11404
- * continuation strategy. It uses a rule-based approach that makes it easy to extend
11405
- * with new flow patterns and step types in the future.
11406
- *
11407
- * The current analysis supports the standard CCTP flow:
11408
- * **Traditional flow**: Approve → Burn → FetchAttestation → Mint
11409
- *
11410
- * Key features:
11411
- * - Rule-based transitions: Easy to extend with new step types and logic
11412
- * - Context-aware decisions: Considers execution history and step states
11413
- * - Actionable logic: Distinguishes between steps requiring user action vs waiting
11414
- * - Terminal states: Properly handles completion and non-actionable states
11415
- *
11416
- * @param bridgeResult - The bridge result containing step execution history.
11417
- * @returns Analysis result with continuation step and actionability information.
11418
- * @throws Error when bridgeResult is invalid or contains no steps array.
12289
+ * Schema for validating base58-encoded strings.
11419
12290
  *
11420
- * @example
11421
- * ```typescript
11422
- * import { analyzeSteps } from './analyzeSteps'
12291
+ * This schema validates that a string:
12292
+ * - Is a string type
12293
+ * - Is not empty after trimming
12294
+ * - Contains only valid base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z)
12295
+ * - Does not contain commonly confused characters (0, O, I, l)
11423
12296
  *
11424
- * // Failed approval step (requires user action)
11425
- * const bridgeResult = {
11426
- * steps: [
11427
- * { name: 'Approve', state: 'error', errorMessage: 'User rejected' }
11428
- * ]
11429
- * }
12297
+ * @remarks
12298
+ * This schema does not validate length, making it suitable for various base58-encoded data
12299
+ * like Solana addresses, transaction signatures, and other base58-encoded data.
11430
12300
  *
11431
- * const analysis = analyzeSteps(bridgeResult)
11432
- * // Result: { continuationStep: 'Approve', isRetryable: true,
11433
- * // reason: 'Retry failed approval' }
11434
- * ```
12301
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11435
12302
  *
11436
12303
  * @example
11437
12304
  * ```typescript
11438
- * // Pending transaction (requires waiting, not actionable)
11439
- * const bridgeResult = {
11440
- * steps: [
11441
- * { name: 'Approve', state: 'pending' }
11442
- * ]
11443
- * }
12305
+ * import { base58StringSchema } from '@core/adapter'
11444
12306
  *
11445
- * const analysis = analyzeSteps(bridgeResult)
11446
- * // Result: { continuationStep: 'Approve', isRetryable: false,
11447
- * // reason: 'Continue pending approval' }
12307
+ * const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
12308
+ * const validTxHash = '3Jf8k2L5mN9pQ7rS1tV4wX6yZ8aB2cD4eF5gH7iJ9kL1mN3oP5qR7sT9uV1wX3yZ5'
12309
+ *
12310
+ * const addressResult = base58StringSchema.safeParse(validAddress)
12311
+ * const txHashResult = base58StringSchema.safeParse(validTxHash)
12312
+ * console.log(addressResult.success) // true
12313
+ * console.log(txHashResult.success) // true
11448
12314
  * ```
12315
+ */
12316
+ const base58StringSchema = z
12317
+ .string()
12318
+ .min(1, 'Base58 string is required')
12319
+ .refine((value) => value.trim().length > 0, 'Base58 string cannot be empty')
12320
+ .refine((value) => {
12321
+ // Base58 alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
12322
+ // Excludes: 0, O, I, l to avoid confusion
12323
+ const base58Pattern = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/;
12324
+ return base58Pattern.test(value);
12325
+ }, 'Base58 string contains invalid characters. Only base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z) are allowed');
12326
+ /**
12327
+ * Schema for validating Solana addresses.
12328
+ *
12329
+ * This schema validates that a string is a properly formatted Solana address:
12330
+ * - Must be a valid base58-encoded string
12331
+ * - Must be between 32-44 characters long (typical length for base58-encoded 32-byte addresses)
12332
+ *
12333
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11449
12334
  *
11450
12335
  * @example
11451
12336
  * ```typescript
11452
- * // Completed bridge (nothing to do)
11453
- * const bridgeResult = {
11454
- * steps: [
11455
- * { name: 'Approve', state: 'success' },
11456
- * { name: 'Burn', state: 'success' },
11457
- * { name: 'FetchAttestation', state: 'success' },
11458
- * { name: 'Mint', state: 'success' }
11459
- * ]
11460
- * }
12337
+ * import { solanaAddressSchema } from '@core/adapter'
11461
12338
  *
11462
- * const analysis = analyzeSteps(bridgeResult)
11463
- * // Result: { continuationStep: null, isRetryable: false,
11464
- * // reason: 'Bridge completed successfully' }
11465
- * ```
11466
- */
11467
- const analyzeSteps = (bridgeResult) => {
11468
- // Input validation
11469
- if (!bridgeResult || !Array.isArray(bridgeResult.steps)) {
11470
- throw new Error('Invalid bridgeResult: must contain a steps array');
11471
- }
11472
- const { steps } = bridgeResult;
11473
- // Build execution context from step history
11474
- const context = buildFlowContext(steps);
11475
- // Determine continuation logic using rule engine
11476
- const continuation = determineContinuationFromRules(context);
11477
- return {
11478
- continuationStep: continuation.nextStep,
11479
- isActionable: continuation.isActionable,
11480
- completedSteps: Array.from(context.completedSteps),
11481
- failedSteps: Array.from(context.failedSteps),
11482
- reason: continuation.reason,
11483
- };
11484
- };
11485
- /**
11486
- * Build flow context from the execution history.
12339
+ * const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
11487
12340
  *
11488
- * @param steps - Array of executed bridge steps.
11489
- * @returns Flow context with execution state and history.
12341
+ * const result = solanaAddressSchema.safeParse(validAddress)
12342
+ * console.log(result.success) // true
12343
+ * ```
11490
12344
  */
11491
- function buildFlowContext(steps) {
11492
- const completedSteps = new Set();
11493
- const failedSteps = new Set();
11494
- let lastStep;
11495
- // Process step history to build context
11496
- for (const step of steps) {
11497
- if (step.state === 'success' || step.state === 'noop') {
11498
- completedSteps.add(step.name);
11499
- }
11500
- else if (step.state === 'error') {
11501
- failedSteps.add(step.name);
11502
- }
11503
- // Track the last step for continuation logic
11504
- lastStep = {
11505
- name: step.name,
11506
- state: step.state,
11507
- };
11508
- }
11509
- return {
11510
- completedSteps,
11511
- failedSteps,
11512
- ...(lastStep && { lastStep }),
11513
- };
11514
- }
12345
+ base58StringSchema.refine((value) => value.length >= 32 && value.length <= 44, 'Solana address must be between 32-44 characters long (base58-encoded 32-byte address)');
11515
12346
  /**
11516
- * Determine continuation step using the rule engine.
12347
+ * Schema for validating Solana transaction hashes.
11517
12348
  *
11518
- * @param context - The flow context with execution history.
11519
- * @returns Continuation decision with next step and actionability information.
11520
- */
11521
- function determineContinuationFromRules(context) {
11522
- const lastStepName = context.lastStep?.name;
11523
- // Handle initial state when no steps have been executed
11524
- if (lastStepName === undefined) {
11525
- const rules = STEP_TRANSITION_RULES[''];
11526
- const matchingRule = rules?.find((rule) => rule.condition(context));
11527
- if (!matchingRule) {
11528
- return {
11529
- nextStep: null,
11530
- isActionable: false,
11531
- reason: 'No initial state rule found',
11532
- };
11533
- }
11534
- return {
11535
- nextStep: matchingRule.nextStep,
11536
- isActionable: matchingRule.isActionable,
11537
- reason: matchingRule.reason,
11538
- };
11539
- }
11540
- // A step with an empty name is ambiguous and should be treated as an unrecoverable state.
11541
- if (lastStepName === '') {
11542
- return {
11543
- nextStep: null,
11544
- isActionable: false,
11545
- reason: 'No transition rules defined for step with empty name',
11546
- };
11547
- }
11548
- const rules = STEP_TRANSITION_RULES[lastStepName];
11549
- if (!rules) {
11550
- return {
11551
- nextStep: null,
11552
- isActionable: false,
11553
- reason: `No transition rules defined for step: ${lastStepName}`,
11554
- };
11555
- }
11556
- // Find the first matching rule
11557
- const matchingRule = rules.find((rule) => rule.condition(context));
11558
- if (!matchingRule) {
11559
- return {
11560
- nextStep: null,
11561
- isActionable: false,
11562
- reason: `No matching transition rule for current context`,
11563
- };
11564
- }
11565
- return {
11566
- nextStep: matchingRule.nextStep,
11567
- isActionable: matchingRule.isActionable,
11568
- reason: matchingRule.reason,
11569
- };
11570
- }
11571
-
11572
- /**
11573
- * Find a step by name in the bridge result.
12349
+ * This schema validates that a string is a properly formatted Solana transaction hash:
12350
+ * - Must be a valid base58-encoded string
12351
+ * - Must be between 86-88 characters long (typical length for base58-encoded 64-byte signatures)
11574
12352
  *
11575
- * @param result - The bridge result to search.
11576
- * @param stepName - The name of the step to find.
11577
- * @returns The step if found, undefined otherwise.
12353
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11578
12354
  *
11579
12355
  * @example
11580
12356
  * ```typescript
11581
- * import { findStepByName } from './findStep'
12357
+ * import { solanaTransactionHashSchema } from '@core/adapter'
11582
12358
  *
11583
- * const burnStep = findStepByName(result, 'burn')
11584
- * if (burnStep) {
11585
- * console.log('Burn tx:', burnStep.txHash)
11586
- * }
12359
+ * const validTxHash = '5VfYmGBjvQKe3xgLtTQPSMEUdpEVHrJwLK7pKBJWKzYpNBE2g3kJrq7RSe9M8DqzQJ5J2aZPTjHLvd4WgxPpJKS'
12360
+ *
12361
+ * const result = solanaTransactionHashSchema.safeParse(validTxHash)
12362
+ * console.log(result.success) // true
11587
12363
  * ```
11588
12364
  */
11589
- function findStepByName(result, stepName) {
11590
- return result.steps.find((step) => step.name === stepName);
11591
- }
12365
+ base58StringSchema.refine((value) => value.length >= 86 && value.length <= 88, 'Solana transaction hash must be between 86-88 characters long (base58-encoded 64-byte signature)');
11592
12366
  /**
11593
- * Find a pending step by name and return it with its index.
12367
+ * Schema for validating Adapter objects.
12368
+ * Checks for the required methods that define an Adapter.
12369
+ */
12370
+ z.object({
12371
+ prepare: z.function(),
12372
+ waitForTransaction: z.function(),
12373
+ getAddress: z.function(),
12374
+ });
12375
+
12376
+ /**
12377
+ * Validate that the adapter has sufficient token balance for a transaction.
11594
12378
  *
11595
- * Searches for a step that matches both the step name and has a pending state.
12379
+ * This function checks if the adapter's current token balance is greater than or equal
12380
+ * to the requested transaction amount. It throws a KitError with code 9001
12381
+ * (BALANCE_INSUFFICIENT_TOKEN) if the balance is insufficient, providing detailed
12382
+ * information about the shortfall.
11596
12383
  *
11597
- * @param result - The bridge result containing steps to search through.
11598
- * @param stepName - The step name to find (e.g., 'burn', 'mint', 'fetchAttestation').
11599
- * @returns An object containing the step and its index in the steps array.
11600
- * @throws KitError if the specified pending step is not found.
12384
+ * @param params - The validation parameters containing adapter, amount, token, and token address.
12385
+ * @returns A promise that resolves to void if validation passes.
12386
+ * @throws {KitError} When the adapter's balance is less than the required amount (code: 9001).
11601
12387
  *
11602
12388
  * @example
11603
12389
  * ```typescript
11604
- * import { findPendingStep } from './findStep'
12390
+ * import { validateBalanceForTransaction } from '@core/adapter'
12391
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
12392
+ * import { isKitError, ERROR_TYPES } from '@core/errors'
11605
12393
  *
11606
- * const { step, index } = findPendingStep(result, 'burn')
11607
- * console.log('Pending step:', step.name, 'at index:', index)
12394
+ * const adapter = createViemAdapterFromPrivateKey({
12395
+ * privateKey: '0x...',
12396
+ * chain: 'Ethereum',
12397
+ * })
12398
+ *
12399
+ * try {
12400
+ * await validateBalanceForTransaction({
12401
+ * adapter,
12402
+ * amount: '1000000', // 1 USDC (6 decimals)
12403
+ * token: 'USDC',
12404
+ * tokenAddress: '0xA0b86a33E6441c8C1c7C16e4c5e3e5b5e4c5e3e5b5e4c5e',
12405
+ * operationContext: { chain: 'Ethereum' },
12406
+ * })
12407
+ * console.log('Balance validation passed')
12408
+ * } catch (error) {
12409
+ * if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
12410
+ * console.error('Insufficient funds:', error.message)
12411
+ * }
12412
+ * }
11608
12413
  * ```
11609
12414
  */
11610
- function findPendingStep(result, stepName) {
11611
- const index = result.steps.findIndex((step) => step.name === stepName && step.state === 'pending');
11612
- if (index === -1) {
11613
- throw new KitError({
11614
- ...InputError.VALIDATION_FAILED,
11615
- recoverability: 'FATAL',
11616
- message: `Pending step "${stepName}" not found in result`,
11617
- });
11618
- }
11619
- const step = result.steps[index];
11620
- if (!step) {
11621
- throw new KitError({
11622
- ...InputError.VALIDATION_FAILED,
11623
- recoverability: 'FATAL',
11624
- message: 'Pending step is undefined',
12415
+ const validateBalanceForTransaction = async (params) => {
12416
+ const { amount, adapter, token, tokenAddress, operationContext } = params;
12417
+ const balancePrepared = await adapter.prepareAction('usdc.balanceOf', {
12418
+ walletAddress: operationContext.address,
12419
+ }, operationContext);
12420
+ const balance = await balancePrepared.execute();
12421
+ if (BigInt(balance) < BigInt(amount)) {
12422
+ // Extract chain name from operationContext
12423
+ const chainName = extractChainInfo(operationContext.chain).name;
12424
+ // Create KitError with rich context in trace
12425
+ throw createInsufficientTokenBalanceError(chainName, token, {
12426
+ balance: balance.toString(),
12427
+ amount,
12428
+ tokenAddress,
12429
+ walletAddress: operationContext.address,
11625
12430
  });
11626
12431
  }
11627
- return { step, index };
11628
- }
12432
+ };
12433
+
11629
12434
  /**
11630
- * Get the burn transaction hash from bridge result.
12435
+ * Validate that the adapter has sufficient native token balance for transaction fees.
11631
12436
  *
11632
- * @param result - The bridge result.
11633
- * @returns The burn transaction hash, or undefined if not found.
12437
+ * This function checks if the adapter's current native token balance (ETH, SOL, etc.)
12438
+ * is greater than zero. It throws a KitError with code 9002 (BALANCE_INSUFFICIENT_GAS)
12439
+ * if the balance is zero, indicating the wallet cannot pay for transaction fees.
12440
+ *
12441
+ * @param params - The validation parameters containing adapter and operation context.
12442
+ * @returns A promise that resolves to void if validation passes.
12443
+ * @throws {KitError} When the adapter's native balance is zero (code: 9002).
11634
12444
  *
11635
12445
  * @example
11636
12446
  * ```typescript
11637
- * import { getBurnTxHash } from './findStep'
12447
+ * import { validateNativeBalanceForTransaction } from '@core/adapter'
12448
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
12449
+ * import { isKitError, ERROR_TYPES } from '@core/errors'
11638
12450
  *
11639
- * const burnTxHash = getBurnTxHash(result)
11640
- * if (burnTxHash) {
11641
- * console.log('Burn tx hash:', burnTxHash)
12451
+ * const adapter = createViemAdapterFromPrivateKey({
12452
+ * privateKey: '0x...',
12453
+ * chain: 'Ethereum',
12454
+ * })
12455
+ *
12456
+ * try {
12457
+ * await validateNativeBalanceForTransaction({
12458
+ * adapter,
12459
+ * operationContext: { chain: 'Ethereum' },
12460
+ * })
12461
+ * console.log('Native balance validation passed')
12462
+ * } catch (error) {
12463
+ * if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
12464
+ * console.error('Insufficient gas funds:', error.message)
12465
+ * }
11642
12466
  * }
11643
12467
  * ```
11644
12468
  */
11645
- function getBurnTxHash(result) {
11646
- return findStepByName(result, CCTPv2StepName.burn)?.txHash;
11647
- }
12469
+ const validateNativeBalanceForTransaction = async (params) => {
12470
+ const { adapter, operationContext } = params;
12471
+ const balancePrepared = await adapter.prepareAction('native.balanceOf', {
12472
+ walletAddress: operationContext.address,
12473
+ }, operationContext);
12474
+ const balance = await balancePrepared.execute();
12475
+ if (BigInt(balance) === 0n) {
12476
+ const { chain } = operationContext;
12477
+ const chainName = extractChainInfo(chain).name;
12478
+ const nativeSymbol = typeof chain === 'object' && chain !== null && 'nativeCurrency' in chain
12479
+ ? chain.nativeCurrency.symbol
12480
+ : undefined;
12481
+ throw createInsufficientGasError(chainName, nativeSymbol, {
12482
+ balance: '0',
12483
+ walletAddress: operationContext.address,
12484
+ });
12485
+ }
12486
+ };
12487
+
11648
12488
  /**
11649
- * Get the attestation data from bridge result.
11650
- *
11651
- * @param result - The bridge result.
11652
- * @returns The attestation data, or undefined if not found.
12489
+ * Permit signature standards for gasless token approvals.
11653
12490
  *
11654
- * @example
11655
- * ```typescript
11656
- * import { getAttestationData } from './findStep'
12491
+ * Defines the permit types that can be used to approve token spending
12492
+ * without requiring a separate approval transaction.
11657
12493
  *
11658
- * const attestation = getAttestationData(result)
11659
- * if (attestation) {
11660
- * console.log('Attestation:', attestation.message)
11661
- * }
11662
- * ```
12494
+ * @remarks
12495
+ * - NONE: No permit, tokens must be pre-approved via separate transaction
12496
+ * - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
11663
12497
  */
11664
- function getAttestationData(result) {
11665
- // Prefer reAttest data (most recent attestation after expiry)
11666
- const reAttestStep = findStepByName(result, CCTPv2StepName.reAttest);
11667
- if (reAttestStep?.state === 'success' && reAttestStep.data) {
11668
- return reAttestStep.data;
11669
- }
11670
- // Fall back to fetchAttestation step
11671
- const fetchStep = findStepByName(result, CCTPv2StepName.fetchAttestation);
11672
- return fetchStep?.data;
11673
- }
12498
+ var PermitType;
12499
+ (function (PermitType) {
12500
+ /** No permit required - tokens must be pre-approved */
12501
+ PermitType[PermitType["NONE"] = 0] = "NONE";
12502
+ /** EIP-2612 standard permit */
12503
+ PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
12504
+ })(PermitType || (PermitType = {}));
11674
12505
 
11675
12506
  /**
11676
12507
  * Determine if a step executes on the source chain.
@@ -11723,169 +12554,6 @@ function getStepAdapterAndChain(step, context, result) {
11723
12554
  };
11724
12555
  }
11725
12556
 
11726
- /**
11727
- * Check if the analysis indicates a non-actionable pending state.
11728
- *
11729
- * A pending state is non-actionable when there's a continuation step but
11730
- * the analysis marks it as not actionable, typically because we need to
11731
- * wait for an ongoing operation to complete.
11732
- *
11733
- * @param analysis - The step analysis result from analyzeSteps.
11734
- * @param result - The bridge result to check for pending steps.
11735
- * @returns True if there is a pending step that we should wait for.
11736
- *
11737
- * @example
11738
- * ```typescript
11739
- * import { hasPendingState } from './stepUtils'
11740
- * import { analyzeSteps } from '../analyzeSteps'
11741
- *
11742
- * const analysis = analyzeSteps(bridgeResult)
11743
- * if (hasPendingState(analysis, bridgeResult)) {
11744
- * // Wait for the pending operation to complete
11745
- * }
11746
- * ```
11747
- */
11748
- function hasPendingState(analysis, result) {
11749
- // Check if there's a continuation step that's marked as non-actionable
11750
- if (analysis.continuationStep === null || analysis.isActionable) {
11751
- return false;
11752
- }
11753
- // Verify that the continuation step actually exists and is in pending state
11754
- const pendingStep = result.steps.find((step) => step.name === analysis.continuationStep && step.state === 'pending');
11755
- return pendingStep !== undefined;
11756
- }
11757
- /**
11758
- * Check if the step is the last one in the execution flow.
11759
- *
11760
- * @param step - The step object to check.
11761
- * @param stepNames - The ordered list of step names in the execution flow.
11762
- * @returns True if this is the last step in the flow.
11763
- *
11764
- * @example
11765
- * ```typescript
11766
- * import { isLastStep } from './stepUtils'
11767
- *
11768
- * const stepNames = ['approve', 'burn', 'fetchAttestation', 'mint']
11769
- * isLastStep({ name: 'mint' }, stepNames) // true
11770
- * isLastStep({ name: 'burn' }, stepNames) // false
11771
- * ```
11772
- */
11773
- function isLastStep(step, stepNames) {
11774
- const stepIndex = stepNames.indexOf(step.name);
11775
- return stepIndex === -1 || stepIndex >= stepNames.length - 1;
11776
- }
11777
- /**
11778
- * Wait for a pending transaction to complete.
11779
- *
11780
- * Poll the adapter until the transaction is confirmed on-chain and return
11781
- * the updated step with success or error state based on the receipt.
11782
- *
11783
- * @param pendingStep - The full step object containing the transaction hash.
11784
- * @param adapter - The adapter to use for waiting.
11785
- * @param chain - The chain where the transaction was submitted.
11786
- * @returns The updated step object with success or error state.
11787
- *
11788
- * @throws KitError when the pending step has no transaction hash.
11789
- *
11790
- * @example
11791
- * ```typescript
11792
- * import { waitForPendingTransaction } from './bridgeStepUtils'
11793
- *
11794
- * const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
11795
- * const updatedStep = await waitForPendingTransaction(pendingStep, adapter, chain)
11796
- * // updatedStep.state is now 'success' or 'error'
11797
- * ```
11798
- */
11799
- async function waitForPendingTransaction(pendingStep, adapter, chain) {
11800
- if (!pendingStep.txHash) {
11801
- throw new KitError({
11802
- ...InputError.VALIDATION_FAILED,
11803
- recoverability: 'FATAL',
11804
- message: `Cannot wait for pending ${pendingStep.name}: no transaction hash available`,
11805
- });
11806
- }
11807
- const txHash = pendingStep.txHash;
11808
- const txReceipt = await retryAsync(async () => adapter.waitForTransaction(txHash, undefined, chain), {
11809
- isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
11810
- });
11811
- // Check if transaction was confirmed on-chain
11812
- if (!txReceipt.blockNumber) {
11813
- return {
11814
- ...pendingStep,
11815
- state: 'error',
11816
- errorMessage: txReceipt.status === 'reverted'
11817
- ? `Transaction ${pendingStep.txHash} was reverted`
11818
- : 'Transaction was not confirmed on-chain',
11819
- data: txReceipt,
11820
- };
11821
- }
11822
- return {
11823
- ...pendingStep,
11824
- state: 'success',
11825
- data: txReceipt,
11826
- };
11827
- }
11828
- /**
11829
- * Wait for a pending step to complete.
11830
- *
11831
- * For transaction steps: waits for the transaction to be confirmed.
11832
- * For attestation: re-executes the attestation fetch.
11833
- *
11834
- * @typeParam TFromAdapterCapabilities - The capabilities of the source adapter.
11835
- * @typeParam TToAdapterCapabilities - The capabilities of the destination adapter.
11836
- * @param pendingStep - The full step object (with name, state, txHash, data, etc.) to resolve.
11837
- * @param adapter - The adapter to use.
11838
- * @param chain - The chain where the step is executing.
11839
- * @param context - The retry context.
11840
- * @param result - The bridge result.
11841
- * @param provider - The CCTP v2 bridging provider.
11842
- * @returns The resolved step object with updated state.
11843
- *
11844
- * @throws KitError when fetching attestation but burn transaction hash is not found.
11845
- *
11846
- * @example
11847
- * ```typescript
11848
- * import { waitForStepToComplete } from './bridgeStepUtils'
11849
- *
11850
- * const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
11851
- * const updatedStep = await waitForStepToComplete(
11852
- * pendingStep,
11853
- * adapter,
11854
- * chain,
11855
- * context,
11856
- * result,
11857
- * provider,
11858
- * )
11859
- * // updatedStep.state is now 'success' or 'error'
11860
- * ```
11861
- */
11862
- async function waitForStepToComplete(pendingStep, adapter, chain, context, result, provider) {
11863
- if (pendingStep.name === CCTPv2StepName.fetchAttestation) {
11864
- // For attestation, re-run the fetch (it has built-in polling)
11865
- const burnTxHash = getBurnTxHash(result);
11866
- if (!burnTxHash) {
11867
- throw new KitError({
11868
- ...InputError.VALIDATION_FAILED,
11869
- recoverability: 'FATAL',
11870
- message: 'Cannot fetch attestation: burn transaction hash not found',
11871
- });
11872
- }
11873
- const sourceAddress = result.source.address;
11874
- const attestation = await provider.fetchAttestation({
11875
- chain: result.source.chain,
11876
- adapter: context.from,
11877
- address: sourceAddress,
11878
- }, burnTxHash);
11879
- return {
11880
- ...pendingStep,
11881
- state: 'success',
11882
- data: attestation,
11883
- };
11884
- }
11885
- // For transaction steps, wait for the transaction to complete
11886
- return waitForPendingTransaction(pendingStep, adapter, chain);
11887
- }
11888
-
11889
12557
  /**
11890
12558
  * Executes a re-attestation operation to obtain a fresh attestation for an expired message.
11891
12559
  *