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