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