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