@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.mjs
CHANGED
|
@@ -88,6 +88,8 @@ var Blockchain;
|
|
|
88
88
|
Blockchain["Noble_Testnet"] = "Noble_Testnet";
|
|
89
89
|
Blockchain["Optimism"] = "Optimism";
|
|
90
90
|
Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
91
|
+
Blockchain["Pharos"] = "Pharos";
|
|
92
|
+
Blockchain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
91
93
|
Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
|
|
92
94
|
Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
|
|
93
95
|
Blockchain["Plume"] = "Plume";
|
|
@@ -296,6 +298,7 @@ var BridgeChain;
|
|
|
296
298
|
BridgeChain["Monad"] = "Monad";
|
|
297
299
|
BridgeChain["Morph"] = "Morph";
|
|
298
300
|
BridgeChain["Optimism"] = "Optimism";
|
|
301
|
+
BridgeChain["Pharos"] = "Pharos";
|
|
299
302
|
BridgeChain["Plume"] = "Plume";
|
|
300
303
|
BridgeChain["Polygon"] = "Polygon";
|
|
301
304
|
BridgeChain["Sei"] = "Sei";
|
|
@@ -318,6 +321,7 @@ var BridgeChain;
|
|
|
318
321
|
BridgeChain["Monad_Testnet"] = "Monad_Testnet";
|
|
319
322
|
BridgeChain["Morph_Testnet"] = "Morph_Testnet";
|
|
320
323
|
BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
324
|
+
BridgeChain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
321
325
|
BridgeChain["Plume_Testnet"] = "Plume_Testnet";
|
|
322
326
|
BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
323
327
|
BridgeChain["Sei_Testnet"] = "Sei_Testnet";
|
|
@@ -328,6 +332,57 @@ var BridgeChain;
|
|
|
328
332
|
BridgeChain["XDC_Apothem"] = "XDC_Apothem";
|
|
329
333
|
})(BridgeChain || (BridgeChain = {}));
|
|
330
334
|
// -----------------------------------------------------------------------------
|
|
335
|
+
// Unified Balance Chain Enum (Gateway V1 Supported Chains)
|
|
336
|
+
// -----------------------------------------------------------------------------
|
|
337
|
+
/**
|
|
338
|
+
* Enumeration of blockchains that support Gateway V1 operations
|
|
339
|
+
* (deposit, spend, balance, delegate, removeFund).
|
|
340
|
+
*
|
|
341
|
+
* Derived from the full {@link Blockchain} enum but filtered to only
|
|
342
|
+
* include chains with active Gateway V1 contract support. When new chains
|
|
343
|
+
* gain Gateway V1 support, they are added to this enum.
|
|
344
|
+
*
|
|
345
|
+
* @enum
|
|
346
|
+
* @category Enums
|
|
347
|
+
*
|
|
348
|
+
* @remarks
|
|
349
|
+
* - This enum is the **canonical source** of Gateway-supported chains.
|
|
350
|
+
* - Use this enum (or its string literals) in unified-balance-kit calls
|
|
351
|
+
* for type safety.
|
|
352
|
+
*
|
|
353
|
+
* @see {@link Blockchain} for the complete list of all known blockchains.
|
|
354
|
+
* @see {@link UnifiedBalanceChainIdentifier} for the type that accepts these values.
|
|
355
|
+
*/
|
|
356
|
+
var UnifiedBalanceChain;
|
|
357
|
+
(function (UnifiedBalanceChain) {
|
|
358
|
+
// Mainnet chains with Gateway V1 support
|
|
359
|
+
UnifiedBalanceChain["Arbitrum"] = "Arbitrum";
|
|
360
|
+
UnifiedBalanceChain["Avalanche"] = "Avalanche";
|
|
361
|
+
UnifiedBalanceChain["Base"] = "Base";
|
|
362
|
+
UnifiedBalanceChain["Ethereum"] = "Ethereum";
|
|
363
|
+
UnifiedBalanceChain["HyperEVM"] = "HyperEVM";
|
|
364
|
+
UnifiedBalanceChain["Optimism"] = "Optimism";
|
|
365
|
+
UnifiedBalanceChain["Polygon"] = "Polygon";
|
|
366
|
+
UnifiedBalanceChain["Sei"] = "Sei";
|
|
367
|
+
UnifiedBalanceChain["Solana"] = "Solana";
|
|
368
|
+
UnifiedBalanceChain["Sonic"] = "Sonic";
|
|
369
|
+
UnifiedBalanceChain["Unichain"] = "Unichain";
|
|
370
|
+
UnifiedBalanceChain["World_Chain"] = "World_Chain";
|
|
371
|
+
// Testnet chains with Gateway V1 support
|
|
372
|
+
UnifiedBalanceChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
|
|
373
|
+
UnifiedBalanceChain["Arc_Testnet"] = "Arc_Testnet";
|
|
374
|
+
UnifiedBalanceChain["Avalanche_Fuji"] = "Avalanche_Fuji";
|
|
375
|
+
UnifiedBalanceChain["Base_Sepolia"] = "Base_Sepolia";
|
|
376
|
+
UnifiedBalanceChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
|
|
377
|
+
UnifiedBalanceChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
|
|
378
|
+
UnifiedBalanceChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
379
|
+
UnifiedBalanceChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
380
|
+
UnifiedBalanceChain["Sei_Testnet"] = "Sei_Testnet";
|
|
381
|
+
UnifiedBalanceChain["Solana_Devnet"] = "Solana_Devnet";
|
|
382
|
+
UnifiedBalanceChain["Sonic_Testnet"] = "Sonic_Testnet";
|
|
383
|
+
UnifiedBalanceChain["Unichain_Sepolia"] = "Unichain_Sepolia";
|
|
384
|
+
UnifiedBalanceChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
385
|
+
})(UnifiedBalanceChain || (UnifiedBalanceChain = {}));
|
|
331
386
|
// Earn Chain Enum
|
|
332
387
|
// -----------------------------------------------------------------------------
|
|
333
388
|
/**
|
|
@@ -612,6 +667,12 @@ const SWAP_TOKEN_REGISTRY = {
|
|
|
612
667
|
category: 'wrapped',
|
|
613
668
|
description: 'Wrapped Polygon',
|
|
614
669
|
},
|
|
670
|
+
CIRBTC: {
|
|
671
|
+
symbol: 'CIRBTC',
|
|
672
|
+
decimals: 8,
|
|
673
|
+
category: 'wrapped',
|
|
674
|
+
description: 'Circle Bitcoin',
|
|
675
|
+
},
|
|
615
676
|
};
|
|
616
677
|
/**
|
|
617
678
|
* Special NATIVE token constant for swap operations.
|
|
@@ -662,6 +723,62 @@ const ADAPTER_CONTRACT_EVM_MAINNET = '0x7FB8c7260b63934d8da38aF902f87ae6e284a845
|
|
|
662
723
|
* integrations (e.g., Arc Testnet).
|
|
663
724
|
*/
|
|
664
725
|
const ADAPTER_CONTRACT_EVM_TESTNET = '0xBBD70b01a1CAbc96d5b7b129Ae1AAabdf50dd40b';
|
|
726
|
+
/**
|
|
727
|
+
* The GatewayWallet contract address for EVM mainnet networks.
|
|
728
|
+
*
|
|
729
|
+
* This contract manages wallet operations for Gateway transactions
|
|
730
|
+
* on mainnet environments across EVM-compatible chains.
|
|
731
|
+
*/
|
|
732
|
+
const GATEWAY_WALLET_EVM_MAINNET = '0x77777777Dcc4d5A8B6E418Fd04D8997ef11000eE';
|
|
733
|
+
/**
|
|
734
|
+
* The GatewayMinter contract address for EVM mainnet networks.
|
|
735
|
+
*
|
|
736
|
+
* This contract handles minting operations for Gateway transactions
|
|
737
|
+
* on mainnet environments across EVM-compatible chains.
|
|
738
|
+
*/
|
|
739
|
+
const GATEWAY_MINTER_EVM_MAINNET = '0x2222222d7164433c4C09B0b0D809a9b52C04C205';
|
|
740
|
+
/**
|
|
741
|
+
* The GatewayWallet contract address for EVM testnet networks.
|
|
742
|
+
*
|
|
743
|
+
* This contract manages wallet operations for Gateway transactions
|
|
744
|
+
* on testnet environments across EVM-compatible chains.
|
|
745
|
+
*/
|
|
746
|
+
const GATEWAY_WALLET_EVM_TESTNET = '0x0077777d7EBA4688BDeF3E311b846F25870A19B9';
|
|
747
|
+
/**
|
|
748
|
+
* The GatewayMinter contract address for EVM testnet networks.
|
|
749
|
+
*
|
|
750
|
+
* This contract handles minting operations for Gateway transactions
|
|
751
|
+
* on testnet environments across EVM-compatible chains.
|
|
752
|
+
*/
|
|
753
|
+
const GATEWAY_MINTER_EVM_TESTNET = '0x0022222ABE238Cc2C7Bb1f21003F0a260052475B';
|
|
754
|
+
/**
|
|
755
|
+
* The GatewayWallet program address for Solana mainnet.
|
|
756
|
+
*
|
|
757
|
+
* This program manages wallet operations for Gateway transactions
|
|
758
|
+
* on Solana mainnet.
|
|
759
|
+
*/
|
|
760
|
+
const GATEWAY_WALLET_SOLANA_MAINNET = 'GATEwy4YxeiEbRJLwB6dXgg7q61e6zBPrMzYj5h1pRXQ';
|
|
761
|
+
/**
|
|
762
|
+
* The GatewayMinter program address for Solana mainnet.
|
|
763
|
+
*
|
|
764
|
+
* This program handles minting operations for Gateway transactions
|
|
765
|
+
* on Solana mainnet.
|
|
766
|
+
*/
|
|
767
|
+
const GATEWAY_MINTER_SOLANA_MAINNET = 'GATEm5SoBJiSw1v2Pz1iPBgUYkXzCUJ27XSXhDfSyzVZ';
|
|
768
|
+
/**
|
|
769
|
+
* The GatewayWallet program address for Solana devnet.
|
|
770
|
+
*
|
|
771
|
+
* This program manages wallet operations for Gateway transactions
|
|
772
|
+
* on Solana devnet.
|
|
773
|
+
*/
|
|
774
|
+
const GATEWAY_WALLET_SOLANA_DEVNET = 'GATEwdfmYNELfp5wDmmR6noSr2vHnAfBPMm2PvCzX5vu';
|
|
775
|
+
/**
|
|
776
|
+
* The GatewayMinter program address for Solana devnet.
|
|
777
|
+
*
|
|
778
|
+
* This program handles minting operations for Gateway transactions
|
|
779
|
+
* on Solana devnet.
|
|
780
|
+
*/
|
|
781
|
+
const GATEWAY_MINTER_SOLANA_DEVNET = 'GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr';
|
|
665
782
|
|
|
666
783
|
/**
|
|
667
784
|
* Arc Testnet chain definition
|
|
@@ -712,6 +829,19 @@ const ArcTestnet = defineChain({
|
|
|
712
829
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
713
830
|
adapter: ADAPTER_CONTRACT_EVM_TESTNET,
|
|
714
831
|
},
|
|
832
|
+
gateway: {
|
|
833
|
+
domain: 26,
|
|
834
|
+
contracts: {
|
|
835
|
+
v1: {
|
|
836
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
837
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
838
|
+
},
|
|
839
|
+
},
|
|
840
|
+
forwarderSupported: {
|
|
841
|
+
source: true,
|
|
842
|
+
destination: true,
|
|
843
|
+
},
|
|
844
|
+
},
|
|
715
845
|
});
|
|
716
846
|
|
|
717
847
|
/**
|
|
@@ -762,6 +892,19 @@ const Arbitrum = defineChain({
|
|
|
762
892
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
763
893
|
adapter: ADAPTER_CONTRACT_EVM_MAINNET,
|
|
764
894
|
},
|
|
895
|
+
gateway: {
|
|
896
|
+
domain: 3,
|
|
897
|
+
contracts: {
|
|
898
|
+
v1: {
|
|
899
|
+
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
900
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
forwarderSupported: {
|
|
904
|
+
source: true,
|
|
905
|
+
destination: true,
|
|
906
|
+
},
|
|
907
|
+
},
|
|
765
908
|
});
|
|
766
909
|
|
|
767
910
|
/**
|
|
@@ -811,6 +954,19 @@ const ArbitrumSepolia = defineChain({
|
|
|
811
954
|
kitContracts: {
|
|
812
955
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
813
956
|
},
|
|
957
|
+
gateway: {
|
|
958
|
+
domain: 3,
|
|
959
|
+
contracts: {
|
|
960
|
+
v1: {
|
|
961
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
962
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
963
|
+
},
|
|
964
|
+
},
|
|
965
|
+
forwarderSupported: {
|
|
966
|
+
source: true,
|
|
967
|
+
destination: true,
|
|
968
|
+
},
|
|
969
|
+
},
|
|
814
970
|
});
|
|
815
971
|
|
|
816
972
|
/**
|
|
@@ -861,6 +1017,19 @@ const Avalanche = defineChain({
|
|
|
861
1017
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
862
1018
|
adapter: ADAPTER_CONTRACT_EVM_MAINNET,
|
|
863
1019
|
},
|
|
1020
|
+
gateway: {
|
|
1021
|
+
domain: 1,
|
|
1022
|
+
contracts: {
|
|
1023
|
+
v1: {
|
|
1024
|
+
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
1025
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
1026
|
+
},
|
|
1027
|
+
},
|
|
1028
|
+
forwarderSupported: {
|
|
1029
|
+
source: true,
|
|
1030
|
+
destination: true,
|
|
1031
|
+
},
|
|
1032
|
+
},
|
|
864
1033
|
});
|
|
865
1034
|
|
|
866
1035
|
/**
|
|
@@ -910,6 +1079,19 @@ const AvalancheFuji = defineChain({
|
|
|
910
1079
|
kitContracts: {
|
|
911
1080
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
912
1081
|
},
|
|
1082
|
+
gateway: {
|
|
1083
|
+
domain: 1,
|
|
1084
|
+
contracts: {
|
|
1085
|
+
v1: {
|
|
1086
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
1087
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
1088
|
+
},
|
|
1089
|
+
},
|
|
1090
|
+
forwarderSupported: {
|
|
1091
|
+
source: true,
|
|
1092
|
+
destination: true,
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
913
1095
|
});
|
|
914
1096
|
|
|
915
1097
|
/**
|
|
@@ -960,6 +1142,19 @@ const Base = defineChain({
|
|
|
960
1142
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
961
1143
|
adapter: ADAPTER_CONTRACT_EVM_MAINNET,
|
|
962
1144
|
},
|
|
1145
|
+
gateway: {
|
|
1146
|
+
domain: 6,
|
|
1147
|
+
contracts: {
|
|
1148
|
+
v1: {
|
|
1149
|
+
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
1150
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
1151
|
+
},
|
|
1152
|
+
},
|
|
1153
|
+
forwarderSupported: {
|
|
1154
|
+
source: true,
|
|
1155
|
+
destination: true,
|
|
1156
|
+
},
|
|
1157
|
+
},
|
|
963
1158
|
});
|
|
964
1159
|
|
|
965
1160
|
/**
|
|
@@ -1009,6 +1204,19 @@ const BaseSepolia = defineChain({
|
|
|
1009
1204
|
kitContracts: {
|
|
1010
1205
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
1011
1206
|
},
|
|
1207
|
+
gateway: {
|
|
1208
|
+
domain: 6,
|
|
1209
|
+
contracts: {
|
|
1210
|
+
v1: {
|
|
1211
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
1212
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
1213
|
+
},
|
|
1214
|
+
},
|
|
1215
|
+
forwarderSupported: {
|
|
1216
|
+
source: true,
|
|
1217
|
+
destination: true,
|
|
1218
|
+
},
|
|
1219
|
+
},
|
|
1012
1220
|
});
|
|
1013
1221
|
|
|
1014
1222
|
/**
|
|
@@ -1253,7 +1461,10 @@ const Ethereum = defineChain({
|
|
|
1253
1461
|
chainId: 1,
|
|
1254
1462
|
isTestnet: false,
|
|
1255
1463
|
explorerUrl: 'https://etherscan.io/tx/{hash}',
|
|
1256
|
-
rpcEndpoints: [
|
|
1464
|
+
rpcEndpoints: [
|
|
1465
|
+
'https://ethereum-rpc.publicnode.com',
|
|
1466
|
+
'https://ethereum.publicnode.com',
|
|
1467
|
+
],
|
|
1257
1468
|
eurcAddress: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
|
|
1258
1469
|
usdcAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
|
1259
1470
|
usdtAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7',
|
|
@@ -1283,6 +1494,19 @@ const Ethereum = defineChain({
|
|
|
1283
1494
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
1284
1495
|
adapter: ADAPTER_CONTRACT_EVM_MAINNET,
|
|
1285
1496
|
},
|
|
1497
|
+
gateway: {
|
|
1498
|
+
domain: 0,
|
|
1499
|
+
contracts: {
|
|
1500
|
+
v1: {
|
|
1501
|
+
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
1502
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
1503
|
+
},
|
|
1504
|
+
},
|
|
1505
|
+
forwarderSupported: {
|
|
1506
|
+
source: true,
|
|
1507
|
+
destination: true,
|
|
1508
|
+
},
|
|
1509
|
+
},
|
|
1286
1510
|
});
|
|
1287
1511
|
|
|
1288
1512
|
/**
|
|
@@ -1303,7 +1527,7 @@ const EthereumSepolia = defineChain({
|
|
|
1303
1527
|
chainId: 11155111,
|
|
1304
1528
|
isTestnet: true,
|
|
1305
1529
|
explorerUrl: 'https://sepolia.etherscan.io/tx/{hash}',
|
|
1306
|
-
rpcEndpoints: ['https://sepolia.
|
|
1530
|
+
rpcEndpoints: ['https://ethereum-sepolia-rpc.publicnode.com'],
|
|
1307
1531
|
eurcAddress: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
|
|
1308
1532
|
usdcAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
|
|
1309
1533
|
usdtAddress: null,
|
|
@@ -1332,6 +1556,19 @@ const EthereumSepolia = defineChain({
|
|
|
1332
1556
|
kitContracts: {
|
|
1333
1557
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
1334
1558
|
},
|
|
1559
|
+
gateway: {
|
|
1560
|
+
domain: 0,
|
|
1561
|
+
contracts: {
|
|
1562
|
+
v1: {
|
|
1563
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
1564
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
1565
|
+
},
|
|
1566
|
+
},
|
|
1567
|
+
forwarderSupported: {
|
|
1568
|
+
source: true,
|
|
1569
|
+
destination: true,
|
|
1570
|
+
},
|
|
1571
|
+
},
|
|
1335
1572
|
});
|
|
1336
1573
|
|
|
1337
1574
|
/**
|
|
@@ -1426,6 +1663,19 @@ const HyperEVM = defineChain({
|
|
|
1426
1663
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
1427
1664
|
adapter: ADAPTER_CONTRACT_EVM_MAINNET,
|
|
1428
1665
|
},
|
|
1666
|
+
gateway: {
|
|
1667
|
+
domain: 19,
|
|
1668
|
+
contracts: {
|
|
1669
|
+
v1: {
|
|
1670
|
+
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
1671
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
1672
|
+
},
|
|
1673
|
+
},
|
|
1674
|
+
forwarderSupported: {
|
|
1675
|
+
source: true,
|
|
1676
|
+
destination: true,
|
|
1677
|
+
},
|
|
1678
|
+
},
|
|
1429
1679
|
});
|
|
1430
1680
|
|
|
1431
1681
|
/**
|
|
@@ -1470,6 +1720,19 @@ const HyperEVMTestnet = defineChain({
|
|
|
1470
1720
|
kitContracts: {
|
|
1471
1721
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
1472
1722
|
},
|
|
1723
|
+
gateway: {
|
|
1724
|
+
domain: 19,
|
|
1725
|
+
contracts: {
|
|
1726
|
+
v1: {
|
|
1727
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
1728
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
1729
|
+
},
|
|
1730
|
+
},
|
|
1731
|
+
forwarderSupported: {
|
|
1732
|
+
source: true,
|
|
1733
|
+
destination: true,
|
|
1734
|
+
},
|
|
1735
|
+
},
|
|
1473
1736
|
});
|
|
1474
1737
|
|
|
1475
1738
|
/**
|
|
@@ -2004,6 +2267,19 @@ const Optimism = defineChain({
|
|
|
2004
2267
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2005
2268
|
adapter: ADAPTER_CONTRACT_EVM_MAINNET,
|
|
2006
2269
|
},
|
|
2270
|
+
gateway: {
|
|
2271
|
+
domain: 2,
|
|
2272
|
+
contracts: {
|
|
2273
|
+
v1: {
|
|
2274
|
+
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
2275
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
2276
|
+
},
|
|
2277
|
+
},
|
|
2278
|
+
forwarderSupported: {
|
|
2279
|
+
source: true,
|
|
2280
|
+
destination: true,
|
|
2281
|
+
},
|
|
2282
|
+
},
|
|
2007
2283
|
});
|
|
2008
2284
|
|
|
2009
2285
|
/**
|
|
@@ -2053,6 +2329,109 @@ const OptimismSepolia = defineChain({
|
|
|
2053
2329
|
kitContracts: {
|
|
2054
2330
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2055
2331
|
},
|
|
2332
|
+
gateway: {
|
|
2333
|
+
domain: 2,
|
|
2334
|
+
contracts: {
|
|
2335
|
+
v1: {
|
|
2336
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
2337
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
2338
|
+
},
|
|
2339
|
+
},
|
|
2340
|
+
forwarderSupported: {
|
|
2341
|
+
source: true,
|
|
2342
|
+
destination: true,
|
|
2343
|
+
},
|
|
2344
|
+
},
|
|
2345
|
+
});
|
|
2346
|
+
|
|
2347
|
+
/**
|
|
2348
|
+
* Pharos Mainnet chain definition
|
|
2349
|
+
* @remarks
|
|
2350
|
+
* This represents the official production network for the Pharos blockchain.
|
|
2351
|
+
* Pharos is a modular, full-stack parallel Layer 1 blockchain with
|
|
2352
|
+
* sub-second finality and EVM compatibility.
|
|
2353
|
+
*/
|
|
2354
|
+
const Pharos = defineChain({
|
|
2355
|
+
type: 'evm',
|
|
2356
|
+
chain: Blockchain.Pharos,
|
|
2357
|
+
name: 'Pharos',
|
|
2358
|
+
title: 'Pharos Mainnet',
|
|
2359
|
+
nativeCurrency: {
|
|
2360
|
+
name: 'Pharos',
|
|
2361
|
+
symbol: 'PHAROS',
|
|
2362
|
+
decimals: 18,
|
|
2363
|
+
},
|
|
2364
|
+
chainId: 1672,
|
|
2365
|
+
isTestnet: false,
|
|
2366
|
+
explorerUrl: 'https://pharos.socialscan.io/tx/{hash}',
|
|
2367
|
+
rpcEndpoints: ['https://rpc.pharos.xyz'],
|
|
2368
|
+
eurcAddress: null,
|
|
2369
|
+
usdcAddress: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
|
|
2370
|
+
usdtAddress: null,
|
|
2371
|
+
cctp: {
|
|
2372
|
+
domain: 31,
|
|
2373
|
+
contracts: {
|
|
2374
|
+
v2: {
|
|
2375
|
+
type: 'split',
|
|
2376
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
2377
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
2378
|
+
confirmations: 1,
|
|
2379
|
+
fastConfirmations: 1,
|
|
2380
|
+
},
|
|
2381
|
+
},
|
|
2382
|
+
forwarderSupported: {
|
|
2383
|
+
source: false,
|
|
2384
|
+
destination: false,
|
|
2385
|
+
},
|
|
2386
|
+
},
|
|
2387
|
+
kitContracts: {
|
|
2388
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2389
|
+
},
|
|
2390
|
+
});
|
|
2391
|
+
|
|
2392
|
+
/**
|
|
2393
|
+
* Pharos Atlantic Testnet chain definition
|
|
2394
|
+
* @remarks
|
|
2395
|
+
* This represents the official test network for the Pharos blockchain.
|
|
2396
|
+
* Pharos is a modular, full-stack parallel Layer 1 blockchain with
|
|
2397
|
+
* sub-second finality and EVM compatibility.
|
|
2398
|
+
*/
|
|
2399
|
+
const PharosTestnet = defineChain({
|
|
2400
|
+
type: 'evm',
|
|
2401
|
+
chain: Blockchain.Pharos_Testnet,
|
|
2402
|
+
name: 'Pharos Atlantic',
|
|
2403
|
+
title: 'Pharos Atlantic Testnet',
|
|
2404
|
+
nativeCurrency: {
|
|
2405
|
+
name: 'Pharos',
|
|
2406
|
+
symbol: 'PHAROS',
|
|
2407
|
+
decimals: 18,
|
|
2408
|
+
},
|
|
2409
|
+
chainId: 688689,
|
|
2410
|
+
isTestnet: true,
|
|
2411
|
+
explorerUrl: 'https://atlantic.pharosscan.xyz/tx/{hash}',
|
|
2412
|
+
rpcEndpoints: ['https://atlantic.dplabs-internal.com'],
|
|
2413
|
+
eurcAddress: null,
|
|
2414
|
+
usdcAddress: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
|
|
2415
|
+
usdtAddress: null,
|
|
2416
|
+
cctp: {
|
|
2417
|
+
domain: 31,
|
|
2418
|
+
contracts: {
|
|
2419
|
+
v2: {
|
|
2420
|
+
type: 'split',
|
|
2421
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
2422
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
2423
|
+
confirmations: 1,
|
|
2424
|
+
fastConfirmations: 1,
|
|
2425
|
+
},
|
|
2426
|
+
},
|
|
2427
|
+
forwarderSupported: {
|
|
2428
|
+
source: false,
|
|
2429
|
+
destination: false,
|
|
2430
|
+
},
|
|
2431
|
+
},
|
|
2432
|
+
kitContracts: {
|
|
2433
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2434
|
+
},
|
|
2056
2435
|
});
|
|
2057
2436
|
|
|
2058
2437
|
/**
|
|
@@ -2241,6 +2620,19 @@ const Polygon = defineChain({
|
|
|
2241
2620
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2242
2621
|
adapter: ADAPTER_CONTRACT_EVM_MAINNET,
|
|
2243
2622
|
},
|
|
2623
|
+
gateway: {
|
|
2624
|
+
domain: 7,
|
|
2625
|
+
contracts: {
|
|
2626
|
+
v1: {
|
|
2627
|
+
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
2628
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
2629
|
+
},
|
|
2630
|
+
},
|
|
2631
|
+
forwarderSupported: {
|
|
2632
|
+
source: true,
|
|
2633
|
+
destination: true,
|
|
2634
|
+
},
|
|
2635
|
+
},
|
|
2244
2636
|
});
|
|
2245
2637
|
|
|
2246
2638
|
/**
|
|
@@ -2290,6 +2682,19 @@ const PolygonAmoy = defineChain({
|
|
|
2290
2682
|
kitContracts: {
|
|
2291
2683
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2292
2684
|
},
|
|
2685
|
+
gateway: {
|
|
2686
|
+
domain: 7,
|
|
2687
|
+
contracts: {
|
|
2688
|
+
v1: {
|
|
2689
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
2690
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
2691
|
+
},
|
|
2692
|
+
},
|
|
2693
|
+
forwarderSupported: {
|
|
2694
|
+
source: true,
|
|
2695
|
+
destination: true,
|
|
2696
|
+
},
|
|
2697
|
+
},
|
|
2293
2698
|
});
|
|
2294
2699
|
|
|
2295
2700
|
/**
|
|
@@ -2311,7 +2716,7 @@ const Sei = defineChain({
|
|
|
2311
2716
|
},
|
|
2312
2717
|
chainId: 1329,
|
|
2313
2718
|
isTestnet: false,
|
|
2314
|
-
explorerUrl: 'https://
|
|
2719
|
+
explorerUrl: 'https://seiscan.io/tx/{hash}',
|
|
2315
2720
|
rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
|
|
2316
2721
|
eurcAddress: null,
|
|
2317
2722
|
usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
|
|
@@ -2336,13 +2741,26 @@ const Sei = defineChain({
|
|
|
2336
2741
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2337
2742
|
adapter: ADAPTER_CONTRACT_EVM_MAINNET,
|
|
2338
2743
|
},
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2744
|
+
gateway: {
|
|
2745
|
+
domain: 16,
|
|
2746
|
+
contracts: {
|
|
2747
|
+
v1: {
|
|
2748
|
+
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
2749
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
2750
|
+
},
|
|
2751
|
+
},
|
|
2752
|
+
forwarderSupported: {
|
|
2753
|
+
source: true,
|
|
2754
|
+
destination: true,
|
|
2755
|
+
},
|
|
2756
|
+
},
|
|
2757
|
+
});
|
|
2758
|
+
|
|
2759
|
+
/**
|
|
2760
|
+
* Sei Testnet chain definition
|
|
2761
|
+
* @remarks
|
|
2762
|
+
* This represents the official testnet for the Sei blockchain.
|
|
2763
|
+
* Used for development and testing purposes before deploying to mainnet.
|
|
2346
2764
|
*/
|
|
2347
2765
|
const SeiTestnet = defineChain({
|
|
2348
2766
|
type: 'evm',
|
|
@@ -2356,7 +2774,7 @@ const SeiTestnet = defineChain({
|
|
|
2356
2774
|
},
|
|
2357
2775
|
chainId: 1328,
|
|
2358
2776
|
isTestnet: true,
|
|
2359
|
-
explorerUrl: 'https://
|
|
2777
|
+
explorerUrl: 'https://testnet.seiscan.io/tx/{hash}',
|
|
2360
2778
|
rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
|
|
2361
2779
|
eurcAddress: null,
|
|
2362
2780
|
usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
|
|
@@ -2380,6 +2798,19 @@ const SeiTestnet = defineChain({
|
|
|
2380
2798
|
kitContracts: {
|
|
2381
2799
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2382
2800
|
},
|
|
2801
|
+
gateway: {
|
|
2802
|
+
domain: 16,
|
|
2803
|
+
contracts: {
|
|
2804
|
+
v1: {
|
|
2805
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
2806
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
2807
|
+
},
|
|
2808
|
+
},
|
|
2809
|
+
forwarderSupported: {
|
|
2810
|
+
source: true,
|
|
2811
|
+
destination: true,
|
|
2812
|
+
},
|
|
2813
|
+
},
|
|
2383
2814
|
});
|
|
2384
2815
|
|
|
2385
2816
|
/**
|
|
@@ -2424,6 +2855,19 @@ const Sonic = defineChain({
|
|
|
2424
2855
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2425
2856
|
adapter: ADAPTER_CONTRACT_EVM_MAINNET,
|
|
2426
2857
|
},
|
|
2858
|
+
gateway: {
|
|
2859
|
+
domain: 13,
|
|
2860
|
+
contracts: {
|
|
2861
|
+
v1: {
|
|
2862
|
+
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
2863
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
2864
|
+
},
|
|
2865
|
+
},
|
|
2866
|
+
forwarderSupported: {
|
|
2867
|
+
source: true,
|
|
2868
|
+
destination: true,
|
|
2869
|
+
},
|
|
2870
|
+
},
|
|
2427
2871
|
});
|
|
2428
2872
|
|
|
2429
2873
|
/**
|
|
@@ -2467,6 +2911,19 @@ const SonicTestnet = defineChain({
|
|
|
2467
2911
|
kitContracts: {
|
|
2468
2912
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2469
2913
|
},
|
|
2914
|
+
gateway: {
|
|
2915
|
+
domain: 13,
|
|
2916
|
+
contracts: {
|
|
2917
|
+
v1: {
|
|
2918
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
2919
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
2920
|
+
},
|
|
2921
|
+
},
|
|
2922
|
+
forwarderSupported: {
|
|
2923
|
+
source: true,
|
|
2924
|
+
destination: true,
|
|
2925
|
+
},
|
|
2926
|
+
},
|
|
2470
2927
|
});
|
|
2471
2928
|
|
|
2472
2929
|
/**
|
|
@@ -2515,6 +2972,19 @@ const Solana = defineChain({
|
|
|
2515
2972
|
kitContracts: {
|
|
2516
2973
|
bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
|
|
2517
2974
|
},
|
|
2975
|
+
gateway: {
|
|
2976
|
+
domain: 5,
|
|
2977
|
+
contracts: {
|
|
2978
|
+
v1: {
|
|
2979
|
+
wallet: GATEWAY_WALLET_SOLANA_MAINNET,
|
|
2980
|
+
minter: GATEWAY_MINTER_SOLANA_MAINNET,
|
|
2981
|
+
},
|
|
2982
|
+
},
|
|
2983
|
+
forwarderSupported: {
|
|
2984
|
+
source: true,
|
|
2985
|
+
destination: false,
|
|
2986
|
+
},
|
|
2987
|
+
},
|
|
2518
2988
|
});
|
|
2519
2989
|
|
|
2520
2990
|
/**
|
|
@@ -2563,6 +3033,19 @@ const SolanaDevnet = defineChain({
|
|
|
2563
3033
|
bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
|
|
2564
3034
|
},
|
|
2565
3035
|
rpcEndpoints: ['https://api.devnet.solana.com'],
|
|
3036
|
+
gateway: {
|
|
3037
|
+
domain: 5,
|
|
3038
|
+
contracts: {
|
|
3039
|
+
v1: {
|
|
3040
|
+
wallet: GATEWAY_WALLET_SOLANA_DEVNET,
|
|
3041
|
+
minter: GATEWAY_MINTER_SOLANA_DEVNET,
|
|
3042
|
+
},
|
|
3043
|
+
},
|
|
3044
|
+
forwarderSupported: {
|
|
3045
|
+
source: true,
|
|
3046
|
+
destination: false,
|
|
3047
|
+
},
|
|
3048
|
+
},
|
|
2566
3049
|
});
|
|
2567
3050
|
|
|
2568
3051
|
/**
|
|
@@ -2737,6 +3220,19 @@ const Unichain = defineChain({
|
|
|
2737
3220
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2738
3221
|
adapter: ADAPTER_CONTRACT_EVM_MAINNET,
|
|
2739
3222
|
},
|
|
3223
|
+
gateway: {
|
|
3224
|
+
domain: 10,
|
|
3225
|
+
contracts: {
|
|
3226
|
+
v1: {
|
|
3227
|
+
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
3228
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
3229
|
+
},
|
|
3230
|
+
},
|
|
3231
|
+
forwarderSupported: {
|
|
3232
|
+
source: true,
|
|
3233
|
+
destination: true,
|
|
3234
|
+
},
|
|
3235
|
+
},
|
|
2740
3236
|
});
|
|
2741
3237
|
|
|
2742
3238
|
/**
|
|
@@ -2786,6 +3282,19 @@ const UnichainSepolia = defineChain({
|
|
|
2786
3282
|
kitContracts: {
|
|
2787
3283
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2788
3284
|
},
|
|
3285
|
+
gateway: {
|
|
3286
|
+
domain: 10,
|
|
3287
|
+
contracts: {
|
|
3288
|
+
v1: {
|
|
3289
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
3290
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
3291
|
+
},
|
|
3292
|
+
},
|
|
3293
|
+
forwarderSupported: {
|
|
3294
|
+
source: true,
|
|
3295
|
+
destination: true,
|
|
3296
|
+
},
|
|
3297
|
+
},
|
|
2789
3298
|
});
|
|
2790
3299
|
|
|
2791
3300
|
/**
|
|
@@ -2830,6 +3339,19 @@ const WorldChain = defineChain({
|
|
|
2830
3339
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2831
3340
|
adapter: ADAPTER_CONTRACT_EVM_MAINNET,
|
|
2832
3341
|
},
|
|
3342
|
+
gateway: {
|
|
3343
|
+
domain: 14,
|
|
3344
|
+
contracts: {
|
|
3345
|
+
v1: {
|
|
3346
|
+
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
3347
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
3348
|
+
},
|
|
3349
|
+
},
|
|
3350
|
+
forwarderSupported: {
|
|
3351
|
+
source: true,
|
|
3352
|
+
destination: true,
|
|
3353
|
+
},
|
|
3354
|
+
},
|
|
2833
3355
|
});
|
|
2834
3356
|
|
|
2835
3357
|
/**
|
|
@@ -2876,6 +3398,19 @@ const WorldChainSepolia = defineChain({
|
|
|
2876
3398
|
kitContracts: {
|
|
2877
3399
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2878
3400
|
},
|
|
3401
|
+
gateway: {
|
|
3402
|
+
domain: 14,
|
|
3403
|
+
contracts: {
|
|
3404
|
+
v1: {
|
|
3405
|
+
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
3406
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
3407
|
+
},
|
|
3408
|
+
},
|
|
3409
|
+
forwarderSupported: {
|
|
3410
|
+
source: true,
|
|
3411
|
+
destination: true,
|
|
3412
|
+
},
|
|
3413
|
+
},
|
|
2879
3414
|
});
|
|
2880
3415
|
|
|
2881
3416
|
/**
|
|
@@ -3056,6 +3591,8 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
3056
3591
|
NobleTestnet: NobleTestnet,
|
|
3057
3592
|
Optimism: Optimism,
|
|
3058
3593
|
OptimismSepolia: OptimismSepolia,
|
|
3594
|
+
Pharos: Pharos,
|
|
3595
|
+
PharosTestnet: PharosTestnet,
|
|
3059
3596
|
Plume: Plume,
|
|
3060
3597
|
PlumeTestnet: PlumeTestnet,
|
|
3061
3598
|
PolkadotAssetHub: PolkadotAssetHub,
|
|
@@ -3155,6 +3692,87 @@ function hasCustomContractSupport(chain, contractType) {
|
|
|
3155
3692
|
return (typeof contractAddress === 'string' && contractAddress.trim().length > 0);
|
|
3156
3693
|
}
|
|
3157
3694
|
|
|
3695
|
+
/**
|
|
3696
|
+
* Zod schema for validating Gateway v1 contract addresses.
|
|
3697
|
+
*
|
|
3698
|
+
* @example
|
|
3699
|
+
* ```typescript
|
|
3700
|
+
* gatewayV1ContractsSchema.parse({
|
|
3701
|
+
* wallet: '0x1234567890abcdef1234567890abcdef12345678',
|
|
3702
|
+
* minter: '0xabcdef1234567890abcdef1234567890abcdef12'
|
|
3703
|
+
* })
|
|
3704
|
+
* ```
|
|
3705
|
+
*/
|
|
3706
|
+
const gatewayV1ContractsSchema = z
|
|
3707
|
+
.object({
|
|
3708
|
+
wallet: z
|
|
3709
|
+
.string({
|
|
3710
|
+
required_error: 'Gateway wallet address is required. Please provide a valid contract address.',
|
|
3711
|
+
invalid_type_error: 'Gateway wallet address must be a string.',
|
|
3712
|
+
})
|
|
3713
|
+
.min(1, 'Gateway wallet address cannot be empty.'),
|
|
3714
|
+
minter: z
|
|
3715
|
+
.string({
|
|
3716
|
+
required_error: 'Gateway minter address is required. Please provide a valid contract address.',
|
|
3717
|
+
invalid_type_error: 'Gateway minter address must be a string.',
|
|
3718
|
+
})
|
|
3719
|
+
.min(1, 'Gateway minter address cannot be empty.'),
|
|
3720
|
+
})
|
|
3721
|
+
.strict(); // Reject any additional properties not defined in the schema
|
|
3722
|
+
/**
|
|
3723
|
+
* Zod schema for validating the versioned Gateway contracts map.
|
|
3724
|
+
*
|
|
3725
|
+
* @description Mirrors the {@link GatewayContracts} type: a partial map of
|
|
3726
|
+
* protocol versions to their contract addresses, following the same pattern
|
|
3727
|
+
* as {@link CCTPContracts}.
|
|
3728
|
+
*
|
|
3729
|
+
* @example
|
|
3730
|
+
* ```typescript
|
|
3731
|
+
* gatewayContractsSchema.parse({
|
|
3732
|
+
* v1: {
|
|
3733
|
+
* wallet: '0x1234567890abcdef1234567890abcdef12345678',
|
|
3734
|
+
* minter: '0xabcdef1234567890abcdef1234567890abcdef12'
|
|
3735
|
+
* }
|
|
3736
|
+
* })
|
|
3737
|
+
* ```
|
|
3738
|
+
*/
|
|
3739
|
+
const gatewayContractsSchema = z
|
|
3740
|
+
.object({
|
|
3741
|
+
v1: gatewayV1ContractsSchema.optional(),
|
|
3742
|
+
})
|
|
3743
|
+
.strict(); // Reject any additional properties not defined in the schema
|
|
3744
|
+
/**
|
|
3745
|
+
* Zod schema for validating the full Gateway configuration.
|
|
3746
|
+
*
|
|
3747
|
+
* @description Mirrors the {@link GatewayConfig} type: a domain number plus
|
|
3748
|
+
* a versioned contracts map, following the same pattern as {@link CCTPConfig}.
|
|
3749
|
+
*
|
|
3750
|
+
* @example
|
|
3751
|
+
* ```typescript
|
|
3752
|
+
* gatewayConfigSchema.parse({
|
|
3753
|
+
* domain: 6,
|
|
3754
|
+
* contracts: {
|
|
3755
|
+
* v1: {
|
|
3756
|
+
* wallet: '0x1234567890abcdef1234567890abcdef12345678',
|
|
3757
|
+
* minter: '0xabcdef1234567890abcdef1234567890abcdef12'
|
|
3758
|
+
* }
|
|
3759
|
+
* }
|
|
3760
|
+
* })
|
|
3761
|
+
* ```
|
|
3762
|
+
*/
|
|
3763
|
+
const gatewayConfigSchema = z
|
|
3764
|
+
.object({
|
|
3765
|
+
domain: z.number({
|
|
3766
|
+
required_error: 'Gateway domain is required. Please provide a valid domain number.',
|
|
3767
|
+
invalid_type_error: 'Gateway domain must be a number.',
|
|
3768
|
+
}),
|
|
3769
|
+
contracts: gatewayContractsSchema,
|
|
3770
|
+
forwarderSupported: z.object({
|
|
3771
|
+
source: z.boolean(),
|
|
3772
|
+
destination: z.boolean(),
|
|
3773
|
+
}),
|
|
3774
|
+
})
|
|
3775
|
+
.strict(); // Reject any additional properties not defined in the schema
|
|
3158
3776
|
/**
|
|
3159
3777
|
* Base schema for common chain definition properties.
|
|
3160
3778
|
* This contains all properties shared between EVM and non-EVM chains.
|
|
@@ -3193,6 +3811,7 @@ const baseChainDefinitionSchema = z.object({
|
|
|
3193
3811
|
adapter: z.string().optional(),
|
|
3194
3812
|
})
|
|
3195
3813
|
.optional(),
|
|
3814
|
+
gateway: gatewayConfigSchema.optional(),
|
|
3196
3815
|
});
|
|
3197
3816
|
/**
|
|
3198
3817
|
* Zod schema for validating EVM chain definitions specifically.
|
|
@@ -3401,6 +4020,42 @@ z.union([
|
|
|
3401
4020
|
`Supported chains: ${Object.values(EarnChain).join(', ')}`,
|
|
3402
4021
|
})),
|
|
3403
4022
|
]);
|
|
4023
|
+
/**
|
|
4024
|
+
* Zod schema for validating unified balance chain identifiers.
|
|
4025
|
+
*
|
|
4026
|
+
* This schema validates that the provided chain is supported for unified balance operations.
|
|
4027
|
+
* It accepts either a UnifiedBalanceChain enum value, a string matching a UnifiedBalanceChain value,
|
|
4028
|
+
* or a ChainDefinition for a supported chain.
|
|
4029
|
+
*
|
|
4030
|
+
* Use this schema when validating chain parameters for unified balance operations to ensure
|
|
4031
|
+
* only Gateway V1-supported chains are accepted at runtime.
|
|
4032
|
+
*
|
|
4033
|
+
* @example
|
|
4034
|
+
* ```typescript
|
|
4035
|
+
* import { unifiedBalanceChainIdentifierSchema } from '@core/chains/validation'
|
|
4036
|
+
* import { UnifiedBalanceChain, Chains } from '@core/chains'
|
|
4037
|
+
*
|
|
4038
|
+
* // Valid - UnifiedBalanceChain enum value
|
|
4039
|
+
* unifiedBalanceChainIdentifierSchema.parse(UnifiedBalanceChain.Ethereum)
|
|
4040
|
+
*
|
|
4041
|
+
* // Valid - string literal
|
|
4042
|
+
* unifiedBalanceChainIdentifierSchema.parse('Ethereum')
|
|
4043
|
+
*
|
|
4044
|
+
* // Invalid - Algorand is not in UnifiedBalanceChain (throws ZodError)
|
|
4045
|
+
* unifiedBalanceChainIdentifierSchema.parse('Algorand')
|
|
4046
|
+
* ```
|
|
4047
|
+
*
|
|
4048
|
+
* @see {@link UnifiedBalanceChain} for the enum of supported chains.
|
|
4049
|
+
*/
|
|
4050
|
+
const supportedUnifiedBalanceChains = Object.keys(UnifiedBalanceChain).join(', ');
|
|
4051
|
+
z.union([
|
|
4052
|
+
z.string().refine((val) => val in UnifiedBalanceChain, (val) => ({
|
|
4053
|
+
message: `Chain "${val}" is not supported for unified balance operations. Supported chains: ${supportedUnifiedBalanceChains}.`,
|
|
4054
|
+
})),
|
|
4055
|
+
chainDefinitionSchema$2.refine((chainDef) => chainDef.chain in UnifiedBalanceChain, (chainDef) => ({
|
|
4056
|
+
message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for unified balance operations. Supported chains: ${supportedUnifiedBalanceChains}.`,
|
|
4057
|
+
})),
|
|
4058
|
+
]);
|
|
3404
4059
|
|
|
3405
4060
|
/**
|
|
3406
4061
|
* @packageDocumentation
|
|
@@ -3709,6 +4364,23 @@ class BridgingProvider {
|
|
|
3709
4364
|
}
|
|
3710
4365
|
}
|
|
3711
4366
|
|
|
4367
|
+
/**
|
|
4368
|
+
* Check whether the current runtime is Node.js.
|
|
4369
|
+
*
|
|
4370
|
+
* @returns `true` when running in Node.js, `false` otherwise.
|
|
4371
|
+
*
|
|
4372
|
+
* @example
|
|
4373
|
+
* ```typescript
|
|
4374
|
+
* import { isNodeEnvironment } from '@core/utils'
|
|
4375
|
+
*
|
|
4376
|
+
* if (isNodeEnvironment()) {
|
|
4377
|
+
* console.log('Running in Node.js')
|
|
4378
|
+
* }
|
|
4379
|
+
* ```
|
|
4380
|
+
*/
|
|
4381
|
+
const isNodeEnvironment = () => typeof process !== 'undefined' &&
|
|
4382
|
+
typeof process.versions === 'object' &&
|
|
4383
|
+
typeof process.versions.node === 'string';
|
|
3712
4384
|
/**
|
|
3713
4385
|
* Detect the runtime environment and return a shortened identifier.
|
|
3714
4386
|
*
|
|
@@ -3716,9 +4388,7 @@ class BridgingProvider {
|
|
|
3716
4388
|
*/
|
|
3717
4389
|
const getRuntime = () => {
|
|
3718
4390
|
// Node.js environment
|
|
3719
|
-
if (
|
|
3720
|
-
typeof process.versions === 'object' &&
|
|
3721
|
-
typeof process.versions.node === 'string') {
|
|
4391
|
+
if (isNodeEnvironment()) {
|
|
3722
4392
|
// Shorten to major version only
|
|
3723
4393
|
const majorVersion = process.versions.node.split('.')[0] ?? 'unknown';
|
|
3724
4394
|
return `node/${majorVersion}`;
|
|
@@ -4427,6 +5097,9 @@ const NetworkError = {
|
|
|
4427
5097
|
name: 'NETWORK_CONNECTION_FAILED',
|
|
4428
5098
|
type: 'NETWORK',
|
|
4429
5099
|
},
|
|
5100
|
+
/** Network request timeout */
|
|
5101
|
+
TIMEOUT: {
|
|
5102
|
+
code: 3002},
|
|
4430
5103
|
/** Circle relayer failed to process the forwarding/mint transaction */
|
|
4431
5104
|
RELAYER_FORWARD_FAILED: {
|
|
4432
5105
|
code: 3003,
|
|
@@ -4707,6 +5380,7 @@ function createInsufficientTokenBalanceError(chain, token, trace) {
|
|
|
4707
5380
|
* as it requires user intervention to add gas funds.
|
|
4708
5381
|
*
|
|
4709
5382
|
* @param chain - The blockchain network where the gas check failed
|
|
5383
|
+
* @param nativeToken - Native token symbol (e.g. 'ETH', 'SOL', 'AVAX') for a specific message, defaults to 'native token'
|
|
4710
5384
|
* @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
|
|
4711
5385
|
* @returns KitError with insufficient gas details
|
|
4712
5386
|
*
|
|
@@ -4715,25 +5389,25 @@ function createInsufficientTokenBalanceError(chain, token, trace) {
|
|
|
4715
5389
|
* import { createInsufficientGasError } from '@core/errors'
|
|
4716
5390
|
*
|
|
4717
5391
|
* throw createInsufficientGasError('Ethereum')
|
|
4718
|
-
* // Message: "Insufficient
|
|
5392
|
+
* // Message: "Insufficient native token on Ethereum to cover gas fees"
|
|
5393
|
+
*
|
|
5394
|
+
* throw createInsufficientGasError('Ethereum', 'ETH')
|
|
5395
|
+
* // Message: "Insufficient ETH on Ethereum to cover gas fees"
|
|
4719
5396
|
* ```
|
|
4720
5397
|
*
|
|
4721
5398
|
* @example
|
|
4722
5399
|
* ```typescript
|
|
4723
|
-
*
|
|
4724
|
-
* throw createInsufficientGasError('Ethereum', {
|
|
5400
|
+
* throw createInsufficientGasError('Ethereum', 'ETH', {
|
|
4725
5401
|
* rawError: error,
|
|
4726
|
-
* gasRequired: '21000',
|
|
4727
|
-
* gasAvailable: '10000',
|
|
4728
5402
|
* walletAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
|
|
4729
5403
|
* })
|
|
4730
5404
|
* ```
|
|
4731
5405
|
*/
|
|
4732
|
-
function createInsufficientGasError(chain, trace) {
|
|
5406
|
+
function createInsufficientGasError(chain, nativeToken = 'native token', trace) {
|
|
4733
5407
|
return new KitError({
|
|
4734
5408
|
...BalanceError.INSUFFICIENT_GAS,
|
|
4735
5409
|
recoverability: 'FATAL',
|
|
4736
|
-
message: `Insufficient
|
|
5410
|
+
message: `Insufficient ${nativeToken} on ${chain} to cover gas fees`,
|
|
4737
5411
|
cause: {
|
|
4738
5412
|
trace: {
|
|
4739
5413
|
...trace,
|
|
@@ -5099,7 +5773,7 @@ const SLIPPAGE_REVERT_PATTERN = /slippage|lower than the minimum required|less t
|
|
|
5099
5773
|
*
|
|
5100
5774
|
* @internal
|
|
5101
5775
|
*/
|
|
5102
|
-
function
|
|
5776
|
+
function parseRevertError(msg, error, context) {
|
|
5103
5777
|
const reason = extractRevertReason(msg, error) ?? 'Transaction reverted';
|
|
5104
5778
|
if (SLIPPAGE_REVERT_PATTERN.test(reason)) {
|
|
5105
5779
|
return new KitError({
|
|
@@ -5188,17 +5862,34 @@ function handleRevertError(msg, error, context) {
|
|
|
5188
5862
|
function parseBlockchainError(error, context) {
|
|
5189
5863
|
const msg = extractMessage(error);
|
|
5190
5864
|
const token = context.token ?? 'token';
|
|
5191
|
-
// Pattern
|
|
5865
|
+
// Pattern 0: Insufficient native gas token errors
|
|
5866
|
+
// Must run BEFORE the generic balance pattern because RPC messages like
|
|
5867
|
+
// "insufficient funds for gas * price + value" and "insufficient funds
|
|
5868
|
+
// for intrinsic transaction cost" contain "insufficient funds" which
|
|
5869
|
+
// would otherwise match the token balance pattern below.
|
|
5870
|
+
if (/insufficient funds for (gas|intrinsic transaction cost)|sender doesn't have enough funds to send tx/i.test(msg)) {
|
|
5871
|
+
return createInsufficientGasError(context.chain, undefined, {
|
|
5872
|
+
rawError: error,
|
|
5873
|
+
});
|
|
5874
|
+
}
|
|
5875
|
+
// Pattern 1: Insufficient token balance errors
|
|
5192
5876
|
// Matches balance-related errors from ERC20 contracts, native transfers, and Solana programs
|
|
5193
5877
|
if (/transfer amount exceeds balance|insufficient (balance|funds)|burn amount exceeded/i.test(msg)) {
|
|
5194
5878
|
return createInsufficientTokenBalanceError(context.chain, token, {
|
|
5195
5879
|
rawError: error,
|
|
5196
5880
|
});
|
|
5197
5881
|
}
|
|
5882
|
+
// Pattern 1b: Gateway-specific contract reverts
|
|
5883
|
+
// Matched before the generic simulation/revert pattern so the error
|
|
5884
|
+
// messages are actionable rather than opaque hex selectors.
|
|
5885
|
+
const gatewayError = parseGatewayContractError(msg, context, error);
|
|
5886
|
+
if (gatewayError !== null) {
|
|
5887
|
+
return gatewayError;
|
|
5888
|
+
}
|
|
5198
5889
|
// Pattern 2: Simulation and execution reverts
|
|
5199
5890
|
// Matches contract revert errors and simulation failures
|
|
5200
5891
|
if (/execution reverted|simulation failed|transaction reverted|transaction failed/i.test(msg)) {
|
|
5201
|
-
return
|
|
5892
|
+
return parseRevertError(msg, error, context);
|
|
5202
5893
|
}
|
|
5203
5894
|
// Pattern 3: Gas-related errors
|
|
5204
5895
|
// Matches gas estimation failures and gas exhaustion
|
|
@@ -5213,10 +5904,6 @@ function parseBlockchainError(error, context) {
|
|
|
5213
5904
|
const { txHash, explorerUrl } = normalizeTransactionDetails(context.txHash, context.explorerUrl);
|
|
5214
5905
|
return createOutOfGasError(context.chain, { rawError: error }, txHash, explorerUrl);
|
|
5215
5906
|
}
|
|
5216
|
-
// Insufficient funds for gas
|
|
5217
|
-
if (/insufficient funds for gas/i.test(msg)) {
|
|
5218
|
-
return createInsufficientGasError(context.chain, { rawError: error });
|
|
5219
|
-
}
|
|
5220
5907
|
// Pattern 4: Network connectivity errors
|
|
5221
5908
|
// Matches connection failures, DNS errors, and timeouts
|
|
5222
5909
|
if (/connection (refused|failed)|network|timeout|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
|
|
@@ -5468,6 +6155,59 @@ function extractRevertReason(msg, error) {
|
|
|
5468
6155
|
}
|
|
5469
6156
|
return null;
|
|
5470
6157
|
}
|
|
6158
|
+
/**
|
|
6159
|
+
* Parses Gateway smart-contract revert selectors into specific KitError types.
|
|
6160
|
+
*
|
|
6161
|
+
* Returns `null` when the message does not match any known Gateway revert,
|
|
6162
|
+
* allowing the caller to fall through to generic patterns.
|
|
6163
|
+
*
|
|
6164
|
+
* @param msg - The extracted error message string.
|
|
6165
|
+
* @param context - Parse error context (chain, token, operation).
|
|
6166
|
+
* @param error - The original raw error for the trace payload.
|
|
6167
|
+
* @returns A KitError if a Gateway pattern matched, otherwise `null`.
|
|
6168
|
+
*
|
|
6169
|
+
* @internal Called by {@link parseBlockchainError} — not re-exported from
|
|
6170
|
+
* the `@core/errors` barrel.
|
|
6171
|
+
*
|
|
6172
|
+
* @example
|
|
6173
|
+
* ```typescript
|
|
6174
|
+
* // Internal usage within parseBlockchainError:
|
|
6175
|
+
* import { parseGatewayContractError } from './parseBlockchainError'
|
|
6176
|
+
*
|
|
6177
|
+
* const gatewayError = parseGatewayContractError(
|
|
6178
|
+
* 'execution reverted: WithdrawalValueExceedsAvailableBalance',
|
|
6179
|
+
* { chain: 'Ethereum', token: 'USDC', operation: 'withdraw' },
|
|
6180
|
+
* new Error('execution reverted'),
|
|
6181
|
+
* )
|
|
6182
|
+
* if (gatewayError !== null) {
|
|
6183
|
+
* // KitError with INSUFFICIENT_TOKEN balance details
|
|
6184
|
+
* console.log(gatewayError.message)
|
|
6185
|
+
* }
|
|
6186
|
+
*
|
|
6187
|
+
* // Returns null for unrecognised reverts (caller falls through to generic parsing)
|
|
6188
|
+
* const unknown = parseGatewayContractError(
|
|
6189
|
+
* 'execution reverted: SomeUnknownError()',
|
|
6190
|
+
* { chain: 'Base', token: 'USDC', operation: 'deposit' },
|
|
6191
|
+
* new Error('execution reverted'),
|
|
6192
|
+
* )
|
|
6193
|
+
* console.log(unknown) // null
|
|
6194
|
+
* ```
|
|
6195
|
+
*/
|
|
6196
|
+
function parseGatewayContractError(msg, context, error) {
|
|
6197
|
+
const token = context.token ?? 'token';
|
|
6198
|
+
if (/WithdrawalValueExceedsAvailableBalance|InsufficientDepositBalance/i.test(msg)) {
|
|
6199
|
+
return createInsufficientTokenBalanceError(context.chain, token, {
|
|
6200
|
+
rawError: error,
|
|
6201
|
+
});
|
|
6202
|
+
}
|
|
6203
|
+
if (/WithdrawalNotYetAvailable|WithdrawalDelayNotElapsed/i.test(msg)) {
|
|
6204
|
+
return createTransactionRevertedError(context.chain, 'Withdrawal is not yet available — the withdrawal delay has not elapsed', { rawError: error });
|
|
6205
|
+
}
|
|
6206
|
+
if (/NoWithdrawingBalance/i.test(msg)) {
|
|
6207
|
+
return createTransactionRevertedError(context.chain, 'No pending withdrawal balance — call initiateWithdrawal() first', { rawError: error });
|
|
6208
|
+
}
|
|
6209
|
+
return null;
|
|
6210
|
+
}
|
|
5471
6211
|
|
|
5472
6212
|
/**
|
|
5473
6213
|
* Type guard to check if an error is a KitError instance.
|
|
@@ -6082,12 +6822,12 @@ function validate(value, schema, context) {
|
|
|
6082
6822
|
}
|
|
6083
6823
|
|
|
6084
6824
|
/**
|
|
6085
|
-
*
|
|
6086
|
-
*
|
|
6087
|
-
*
|
|
6825
|
+
* Module-level WeakMap for tracking which (schema, validator) combinations
|
|
6826
|
+
* have already processed a given object. This avoids mutating user-supplied
|
|
6827
|
+
* input objects while ensuring re-validation occurs when schemas change.
|
|
6088
6828
|
* @internal
|
|
6089
6829
|
*/
|
|
6090
|
-
const
|
|
6830
|
+
const validationStateMap = new WeakMap();
|
|
6091
6831
|
/**
|
|
6092
6832
|
* Validates data against a Zod schema with state tracking and enhanced error reporting.
|
|
6093
6833
|
*
|
|
@@ -6104,11 +6844,12 @@ const VALIDATION_STATE = Symbol('validationState');
|
|
|
6104
6844
|
*
|
|
6105
6845
|
* @example
|
|
6106
6846
|
* ```typescript
|
|
6107
|
-
* const
|
|
6847
|
+
* const VALIDATOR = Symbol('bridgeValidator')
|
|
6848
|
+
* validateWithStateTracking(params, BridgeParamsSchema, 'bridge parameters', VALIDATOR)
|
|
6849
|
+
* // params is now narrowed to the schema's output type
|
|
6108
6850
|
* ```
|
|
6109
6851
|
*/
|
|
6110
6852
|
function validateWithStateTracking(value, schema, context, validatorName) {
|
|
6111
|
-
// Skip validation for null or undefined values
|
|
6112
6853
|
if (value === null) {
|
|
6113
6854
|
throw new KitError({
|
|
6114
6855
|
...InputError.VALIDATION_FAILED,
|
|
@@ -6133,7 +6874,6 @@ function validateWithStateTracking(value, schema, context, validatorName) {
|
|
|
6133
6874
|
},
|
|
6134
6875
|
});
|
|
6135
6876
|
}
|
|
6136
|
-
// Ensure value is an object that can hold validation state
|
|
6137
6877
|
if (typeof value !== 'object') {
|
|
6138
6878
|
throw new KitError({
|
|
6139
6879
|
...InputError.VALIDATION_FAILED,
|
|
@@ -6146,18 +6886,28 @@ function validateWithStateTracking(value, schema, context, validatorName) {
|
|
|
6146
6886
|
},
|
|
6147
6887
|
});
|
|
6148
6888
|
}
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
//
|
|
6153
|
-
if
|
|
6889
|
+
const state = validationStateMap.get(value) ?? {
|
|
6890
|
+
validatedSchemasByValidator: new Map(),
|
|
6891
|
+
};
|
|
6892
|
+
// Get the set of schemas that have already validated this object
|
|
6893
|
+
// for the given validator. Create a new WeakSet if this is first time.
|
|
6894
|
+
const validatedSchemas = state.validatedSchemasByValidator.get(validatorName) ?? new WeakSet();
|
|
6895
|
+
if (!(validatedSchemas instanceof WeakSet)) {
|
|
6896
|
+
throw new KitError({
|
|
6897
|
+
...InputError.VALIDATION_FAILED,
|
|
6898
|
+
recoverability: 'FATAL',
|
|
6899
|
+
message: 'Invalid validation state: expected WeakSet',
|
|
6900
|
+
});
|
|
6901
|
+
}
|
|
6902
|
+
// Check if this exact schema instance has already validated this object
|
|
6903
|
+
if (validatedSchemas.has(schema)) {
|
|
6154
6904
|
return;
|
|
6155
6905
|
}
|
|
6156
|
-
// Delegate to the validate function for actual validation (now throws KitError)
|
|
6157
6906
|
validate(value, schema, context);
|
|
6158
|
-
//
|
|
6159
|
-
|
|
6160
|
-
|
|
6907
|
+
// Record that this schema has validated the object for this validator
|
|
6908
|
+
validatedSchemas.add(schema);
|
|
6909
|
+
state.validatedSchemasByValidator.set(validatorName, validatedSchemas);
|
|
6910
|
+
validationStateMap.set(value, state);
|
|
6161
6911
|
}
|
|
6162
6912
|
|
|
6163
6913
|
/**
|
|
@@ -6533,6 +7283,7 @@ const USDC = {
|
|
|
6533
7283
|
[Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
|
|
6534
7284
|
[Blockchain.Noble]: 'uusdc',
|
|
6535
7285
|
[Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
|
|
7286
|
+
[Blockchain.Pharos]: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
|
|
6536
7287
|
[Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
|
|
6537
7288
|
[Blockchain.Polkadot_Asset_Hub]: '1337',
|
|
6538
7289
|
[Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
|
|
@@ -6564,6 +7315,7 @@ const USDC = {
|
|
|
6564
7315
|
[Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
|
|
6565
7316
|
[Blockchain.Noble_Testnet]: 'uusdc',
|
|
6566
7317
|
[Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
|
|
7318
|
+
[Blockchain.Pharos_Testnet]: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
|
|
6567
7319
|
[Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
|
|
6568
7320
|
[Blockchain.Polkadot_Westmint]: '31337',
|
|
6569
7321
|
[Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -6844,6 +7596,32 @@ const MON = {
|
|
|
6844
7596
|
},
|
|
6845
7597
|
};
|
|
6846
7598
|
|
|
7599
|
+
/**
|
|
7600
|
+
* cirBTC (Circle Bitcoin) token definition with addresses and metadata.
|
|
7601
|
+
*
|
|
7602
|
+
* @remarks
|
|
7603
|
+
* Built-in cirBTC definition for the TokenRegistry. Currently deployed
|
|
7604
|
+
* on Arc Testnet.
|
|
7605
|
+
*
|
|
7606
|
+
* @example
|
|
7607
|
+
* ```typescript
|
|
7608
|
+
* import { CIRBTC } from '@core/tokens'
|
|
7609
|
+
* import { Blockchain } from '@core/chains'
|
|
7610
|
+
*
|
|
7611
|
+
* console.log(CIRBTC.symbol) // 'cirBTC'
|
|
7612
|
+
* console.log(CIRBTC.decimals) // 8
|
|
7613
|
+
* console.log(CIRBTC.locators[Blockchain.Arc_Testnet])
|
|
7614
|
+
* // '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF'
|
|
7615
|
+
* ```
|
|
7616
|
+
*/
|
|
7617
|
+
const CIRBTC = {
|
|
7618
|
+
symbol: 'cirBTC',
|
|
7619
|
+
decimals: 8,
|
|
7620
|
+
locators: {
|
|
7621
|
+
[Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF',
|
|
7622
|
+
},
|
|
7623
|
+
};
|
|
7624
|
+
|
|
6847
7625
|
// Re-export for consumers
|
|
6848
7626
|
/**
|
|
6849
7627
|
* All default token definitions.
|
|
@@ -6852,7 +7630,7 @@ const MON = {
|
|
|
6852
7630
|
* These tokens are automatically included in the TokenRegistry when created
|
|
6853
7631
|
* without explicit defaults. Extensions can override these definitions.
|
|
6854
7632
|
* Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
|
|
6855
|
-
* WPOL, ETH, POL, PLUME, and
|
|
7633
|
+
* WPOL, ETH, POL, PLUME, MON, and cirBTC.
|
|
6856
7634
|
*
|
|
6857
7635
|
* @example
|
|
6858
7636
|
* ```typescript
|
|
@@ -6883,6 +7661,7 @@ const DEFAULT_TOKENS = [
|
|
|
6883
7661
|
POL,
|
|
6884
7662
|
PLUME,
|
|
6885
7663
|
MON,
|
|
7664
|
+
CIRBTC,
|
|
6886
7665
|
];
|
|
6887
7666
|
/**
|
|
6888
7667
|
* Uppercased token symbols approved for swap fee collection.
|
|
@@ -9006,34 +9785,633 @@ async function assertCCTPv2AttestationParams(attestation, params) {
|
|
|
9006
9785
|
}
|
|
9007
9786
|
|
|
9008
9787
|
/**
|
|
9009
|
-
*
|
|
9010
|
-
*
|
|
9011
|
-
* This function takes a prepared chain request (containing transaction data) and executes
|
|
9012
|
-
* it using the appropriate adapter. It handles the execution details and formats
|
|
9013
|
-
* the result as a standardized bridge step with transaction details and explorer URLs.
|
|
9014
|
-
*
|
|
9015
|
-
* @param params - The execution parameters containing:
|
|
9016
|
-
* - `name`: The name of the step
|
|
9017
|
-
* - `request`: The prepared chain request containing transaction data
|
|
9018
|
-
* - `adapter`: The adapter that will execute the transaction
|
|
9019
|
-
* - `confirmations`: The number of confirmations to wait for (defaults to 1)
|
|
9020
|
-
* - `timeout`: The timeout for the request in milliseconds
|
|
9021
|
-
* @returns The bridge step with the transaction details and explorer URL
|
|
9022
|
-
* @throws If the transaction execution fails
|
|
9788
|
+
* CCTP bridge step names that can occur in the bridging flow.
|
|
9023
9789
|
*
|
|
9024
|
-
*
|
|
9025
|
-
*
|
|
9026
|
-
*
|
|
9027
|
-
* name: 'approve',
|
|
9028
|
-
* request: preparedRequest,
|
|
9029
|
-
* adapter: adapter,
|
|
9030
|
-
* confirmations: 2,
|
|
9031
|
-
* timeout: 30000
|
|
9032
|
-
* })
|
|
9033
|
-
* console.log('Transaction hash:', step.txHash)
|
|
9034
|
-
* ```
|
|
9790
|
+
* This object provides type safety for step names and represents all possible
|
|
9791
|
+
* steps that can be executed during a CCTP bridge operation. Using const assertions
|
|
9792
|
+
* makes this tree-shakable and follows modern TypeScript best practices.
|
|
9035
9793
|
*/
|
|
9036
|
-
|
|
9794
|
+
const CCTPv2StepName = {
|
|
9795
|
+
approve: 'approve',
|
|
9796
|
+
burn: 'burn',
|
|
9797
|
+
fetchAttestation: 'fetchAttestation',
|
|
9798
|
+
mint: 'mint',
|
|
9799
|
+
reAttest: 'reAttest',
|
|
9800
|
+
};
|
|
9801
|
+
/**
|
|
9802
|
+
* Conditional step transition rules for CCTP bridge flow.
|
|
9803
|
+
*
|
|
9804
|
+
* Rules are evaluated in order - the first matching condition determines the next step.
|
|
9805
|
+
* This approach supports flexible flow logic and makes it easy to extend with new patterns.
|
|
9806
|
+
*/
|
|
9807
|
+
const STEP_TRANSITION_RULES = {
|
|
9808
|
+
// Starting state - no steps executed yet
|
|
9809
|
+
'': [
|
|
9810
|
+
{
|
|
9811
|
+
condition: () => true,
|
|
9812
|
+
nextStep: CCTPv2StepName.approve,
|
|
9813
|
+
reason: 'Start with approval step',
|
|
9814
|
+
isActionable: true,
|
|
9815
|
+
},
|
|
9816
|
+
],
|
|
9817
|
+
// After Approve step
|
|
9818
|
+
[CCTPv2StepName.approve]: [
|
|
9819
|
+
{
|
|
9820
|
+
condition: (ctx) => ctx.lastStep?.state === 'success',
|
|
9821
|
+
nextStep: CCTPv2StepName.burn,
|
|
9822
|
+
reason: 'Approval successful, proceed to burn',
|
|
9823
|
+
isActionable: true,
|
|
9824
|
+
},
|
|
9825
|
+
{
|
|
9826
|
+
condition: (ctx) => ctx.lastStep?.state === 'error',
|
|
9827
|
+
nextStep: CCTPv2StepName.approve,
|
|
9828
|
+
reason: 'Retry failed approval',
|
|
9829
|
+
isActionable: true,
|
|
9830
|
+
},
|
|
9831
|
+
{
|
|
9832
|
+
condition: (ctx) => ctx.lastStep?.state === 'noop',
|
|
9833
|
+
nextStep: CCTPv2StepName.burn,
|
|
9834
|
+
reason: 'No approval needed, proceed to burn',
|
|
9835
|
+
isActionable: true,
|
|
9836
|
+
},
|
|
9837
|
+
{
|
|
9838
|
+
condition: (ctx) => ctx.lastStep?.state === 'pending',
|
|
9839
|
+
nextStep: CCTPv2StepName.approve,
|
|
9840
|
+
reason: 'Continue pending approval',
|
|
9841
|
+
isActionable: false, // Waiting for pending transaction
|
|
9842
|
+
},
|
|
9843
|
+
],
|
|
9844
|
+
// After Burn step
|
|
9845
|
+
[CCTPv2StepName.burn]: [
|
|
9846
|
+
{
|
|
9847
|
+
condition: (ctx) => ctx.lastStep?.state === 'success',
|
|
9848
|
+
nextStep: CCTPv2StepName.fetchAttestation,
|
|
9849
|
+
reason: 'Burn successful, fetch attestation',
|
|
9850
|
+
isActionable: true,
|
|
9851
|
+
},
|
|
9852
|
+
{
|
|
9853
|
+
condition: (ctx) => ctx.lastStep?.state === 'error',
|
|
9854
|
+
nextStep: CCTPv2StepName.burn,
|
|
9855
|
+
reason: 'Retry failed burn',
|
|
9856
|
+
isActionable: true,
|
|
9857
|
+
},
|
|
9858
|
+
{
|
|
9859
|
+
condition: (ctx) => ctx.lastStep?.state === 'pending',
|
|
9860
|
+
nextStep: CCTPv2StepName.burn,
|
|
9861
|
+
reason: 'Continue pending burn',
|
|
9862
|
+
isActionable: false, // Waiting for pending transaction
|
|
9863
|
+
},
|
|
9864
|
+
],
|
|
9865
|
+
// After FetchAttestation step
|
|
9866
|
+
[CCTPv2StepName.fetchAttestation]: [
|
|
9867
|
+
{
|
|
9868
|
+
condition: (ctx) => ctx.lastStep?.state === 'success',
|
|
9869
|
+
nextStep: CCTPv2StepName.mint,
|
|
9870
|
+
reason: 'Attestation fetched, proceed to mint',
|
|
9871
|
+
isActionable: true,
|
|
9872
|
+
},
|
|
9873
|
+
{
|
|
9874
|
+
condition: (ctx) => ctx.lastStep?.state === 'error',
|
|
9875
|
+
nextStep: CCTPv2StepName.fetchAttestation,
|
|
9876
|
+
reason: 'Retry fetching attestation',
|
|
9877
|
+
isActionable: true,
|
|
9878
|
+
},
|
|
9879
|
+
{
|
|
9880
|
+
condition: (ctx) => ctx.lastStep?.state === 'pending',
|
|
9881
|
+
nextStep: CCTPv2StepName.fetchAttestation,
|
|
9882
|
+
reason: 'Continue pending attestation fetch',
|
|
9883
|
+
isActionable: false, // Waiting for attestation to be ready
|
|
9884
|
+
},
|
|
9885
|
+
],
|
|
9886
|
+
// After Mint step
|
|
9887
|
+
[CCTPv2StepName.mint]: [
|
|
9888
|
+
{
|
|
9889
|
+
condition: (ctx) => ctx.lastStep?.state === 'success',
|
|
9890
|
+
nextStep: null,
|
|
9891
|
+
reason: 'Bridge completed successfully',
|
|
9892
|
+
isActionable: false, // Nothing more to do
|
|
9893
|
+
},
|
|
9894
|
+
{
|
|
9895
|
+
condition: (ctx) => ctx.lastStep?.state === 'error',
|
|
9896
|
+
nextStep: CCTPv2StepName.mint,
|
|
9897
|
+
reason: 'Retry failed mint',
|
|
9898
|
+
isActionable: true,
|
|
9899
|
+
},
|
|
9900
|
+
{
|
|
9901
|
+
condition: (ctx) => ctx.lastStep?.state === 'pending',
|
|
9902
|
+
nextStep: CCTPv2StepName.mint,
|
|
9903
|
+
reason: 'Continue pending mint',
|
|
9904
|
+
isActionable: false, // Waiting for pending transaction
|
|
9905
|
+
},
|
|
9906
|
+
],
|
|
9907
|
+
// After ReAttest step
|
|
9908
|
+
[CCTPv2StepName.reAttest]: [
|
|
9909
|
+
{
|
|
9910
|
+
condition: (ctx) => ctx.lastStep?.state === 'success',
|
|
9911
|
+
nextStep: CCTPv2StepName.mint,
|
|
9912
|
+
reason: 'Re-attestation successful, proceed to mint',
|
|
9913
|
+
isActionable: true,
|
|
9914
|
+
},
|
|
9915
|
+
{
|
|
9916
|
+
condition: (ctx) => ctx.lastStep?.state === 'error',
|
|
9917
|
+
nextStep: CCTPv2StepName.mint,
|
|
9918
|
+
reason: 'Re-attestation failed, retry mint to re-initiate recovery',
|
|
9919
|
+
isActionable: true,
|
|
9920
|
+
},
|
|
9921
|
+
{
|
|
9922
|
+
condition: (ctx) => ctx.lastStep?.state === 'pending',
|
|
9923
|
+
nextStep: CCTPv2StepName.mint,
|
|
9924
|
+
reason: 'Re-attestation pending, retry mint to re-initiate recovery',
|
|
9925
|
+
isActionable: true,
|
|
9926
|
+
},
|
|
9927
|
+
],
|
|
9928
|
+
};
|
|
9929
|
+
/**
|
|
9930
|
+
* Analyze bridge steps to determine retry feasibility and continuation point.
|
|
9931
|
+
*
|
|
9932
|
+
* This function examines the current state of bridge steps to determine the optimal
|
|
9933
|
+
* continuation strategy. It uses a rule-based approach that makes it easy to extend
|
|
9934
|
+
* with new flow patterns and step types in the future.
|
|
9935
|
+
*
|
|
9936
|
+
* The current analysis supports the standard CCTP flow:
|
|
9937
|
+
* **Traditional flow**: Approve → Burn → FetchAttestation → Mint
|
|
9938
|
+
*
|
|
9939
|
+
* Key features:
|
|
9940
|
+
* - Rule-based transitions: Easy to extend with new step types and logic
|
|
9941
|
+
* - Context-aware decisions: Considers execution history and step states
|
|
9942
|
+
* - Actionable logic: Distinguishes between steps requiring user action vs waiting
|
|
9943
|
+
* - Terminal states: Properly handles completion and non-actionable states
|
|
9944
|
+
*
|
|
9945
|
+
* @param bridgeResult - The bridge result containing step execution history.
|
|
9946
|
+
* @returns Analysis result with continuation step and actionability information.
|
|
9947
|
+
* @throws Error when bridgeResult is invalid or contains no steps array.
|
|
9948
|
+
*
|
|
9949
|
+
* @example
|
|
9950
|
+
* ```typescript
|
|
9951
|
+
* import { analyzeSteps } from './analyzeSteps'
|
|
9952
|
+
*
|
|
9953
|
+
* // Failed approval step (requires user action)
|
|
9954
|
+
* const bridgeResult = {
|
|
9955
|
+
* steps: [
|
|
9956
|
+
* { name: 'Approve', state: 'error', errorMessage: 'User rejected' }
|
|
9957
|
+
* ]
|
|
9958
|
+
* }
|
|
9959
|
+
*
|
|
9960
|
+
* const analysis = analyzeSteps(bridgeResult)
|
|
9961
|
+
* // Result: { continuationStep: 'Approve', isRetryable: true,
|
|
9962
|
+
* // reason: 'Retry failed approval' }
|
|
9963
|
+
* ```
|
|
9964
|
+
*
|
|
9965
|
+
* @example
|
|
9966
|
+
* ```typescript
|
|
9967
|
+
* // Pending transaction (requires waiting, not actionable)
|
|
9968
|
+
* const bridgeResult = {
|
|
9969
|
+
* steps: [
|
|
9970
|
+
* { name: 'Approve', state: 'pending' }
|
|
9971
|
+
* ]
|
|
9972
|
+
* }
|
|
9973
|
+
*
|
|
9974
|
+
* const analysis = analyzeSteps(bridgeResult)
|
|
9975
|
+
* // Result: { continuationStep: 'Approve', isRetryable: false,
|
|
9976
|
+
* // reason: 'Continue pending approval' }
|
|
9977
|
+
* ```
|
|
9978
|
+
*
|
|
9979
|
+
* @example
|
|
9980
|
+
* ```typescript
|
|
9981
|
+
* // Completed bridge (nothing to do)
|
|
9982
|
+
* const bridgeResult = {
|
|
9983
|
+
* steps: [
|
|
9984
|
+
* { name: 'Approve', state: 'success' },
|
|
9985
|
+
* { name: 'Burn', state: 'success' },
|
|
9986
|
+
* { name: 'FetchAttestation', state: 'success' },
|
|
9987
|
+
* { name: 'Mint', state: 'success' }
|
|
9988
|
+
* ]
|
|
9989
|
+
* }
|
|
9990
|
+
*
|
|
9991
|
+
* const analysis = analyzeSteps(bridgeResult)
|
|
9992
|
+
* // Result: { continuationStep: null, isRetryable: false,
|
|
9993
|
+
* // reason: 'Bridge completed successfully' }
|
|
9994
|
+
* ```
|
|
9995
|
+
*/
|
|
9996
|
+
const analyzeSteps = (bridgeResult) => {
|
|
9997
|
+
// Input validation
|
|
9998
|
+
if (!bridgeResult || !Array.isArray(bridgeResult.steps)) {
|
|
9999
|
+
throw new Error('Invalid bridgeResult: must contain a steps array');
|
|
10000
|
+
}
|
|
10001
|
+
const { steps } = bridgeResult;
|
|
10002
|
+
// Build execution context from step history
|
|
10003
|
+
const context = buildFlowContext(steps);
|
|
10004
|
+
// Determine continuation logic using rule engine
|
|
10005
|
+
const continuation = determineContinuationFromRules(context);
|
|
10006
|
+
return {
|
|
10007
|
+
continuationStep: continuation.nextStep,
|
|
10008
|
+
isActionable: continuation.isActionable,
|
|
10009
|
+
completedSteps: Array.from(context.completedSteps),
|
|
10010
|
+
failedSteps: Array.from(context.failedSteps),
|
|
10011
|
+
reason: continuation.reason,
|
|
10012
|
+
};
|
|
10013
|
+
};
|
|
10014
|
+
/**
|
|
10015
|
+
* Build flow context from the execution history.
|
|
10016
|
+
*
|
|
10017
|
+
* @param steps - Array of executed bridge steps.
|
|
10018
|
+
* @returns Flow context with execution state and history.
|
|
10019
|
+
*/
|
|
10020
|
+
function buildFlowContext(steps) {
|
|
10021
|
+
const completedSteps = new Set();
|
|
10022
|
+
const failedSteps = new Set();
|
|
10023
|
+
let lastStep;
|
|
10024
|
+
// Process step history to build context
|
|
10025
|
+
for (const step of steps) {
|
|
10026
|
+
if (step.state === 'success' || step.state === 'noop') {
|
|
10027
|
+
completedSteps.add(step.name);
|
|
10028
|
+
}
|
|
10029
|
+
else if (step.state === 'error') {
|
|
10030
|
+
failedSteps.add(step.name);
|
|
10031
|
+
}
|
|
10032
|
+
// Track the last step for continuation logic
|
|
10033
|
+
lastStep = {
|
|
10034
|
+
name: step.name,
|
|
10035
|
+
state: step.state,
|
|
10036
|
+
};
|
|
10037
|
+
}
|
|
10038
|
+
return {
|
|
10039
|
+
completedSteps,
|
|
10040
|
+
failedSteps,
|
|
10041
|
+
...(lastStep && { lastStep }),
|
|
10042
|
+
};
|
|
10043
|
+
}
|
|
10044
|
+
/**
|
|
10045
|
+
* Determine continuation step using the rule engine.
|
|
10046
|
+
*
|
|
10047
|
+
* @param context - The flow context with execution history.
|
|
10048
|
+
* @returns Continuation decision with next step and actionability information.
|
|
10049
|
+
*/
|
|
10050
|
+
function determineContinuationFromRules(context) {
|
|
10051
|
+
const lastStepName = context.lastStep?.name;
|
|
10052
|
+
// Handle initial state when no steps have been executed
|
|
10053
|
+
if (lastStepName === undefined) {
|
|
10054
|
+
const rules = STEP_TRANSITION_RULES[''];
|
|
10055
|
+
const matchingRule = rules?.find((rule) => rule.condition(context));
|
|
10056
|
+
if (!matchingRule) {
|
|
10057
|
+
return {
|
|
10058
|
+
nextStep: null,
|
|
10059
|
+
isActionable: false,
|
|
10060
|
+
reason: 'No initial state rule found',
|
|
10061
|
+
};
|
|
10062
|
+
}
|
|
10063
|
+
return {
|
|
10064
|
+
nextStep: matchingRule.nextStep,
|
|
10065
|
+
isActionable: matchingRule.isActionable,
|
|
10066
|
+
reason: matchingRule.reason,
|
|
10067
|
+
};
|
|
10068
|
+
}
|
|
10069
|
+
// A step with an empty name is ambiguous and should be treated as an unrecoverable state.
|
|
10070
|
+
if (lastStepName === '') {
|
|
10071
|
+
return {
|
|
10072
|
+
nextStep: null,
|
|
10073
|
+
isActionable: false,
|
|
10074
|
+
reason: 'No transition rules defined for step with empty name',
|
|
10075
|
+
};
|
|
10076
|
+
}
|
|
10077
|
+
const rules = STEP_TRANSITION_RULES[lastStepName];
|
|
10078
|
+
if (!rules) {
|
|
10079
|
+
return {
|
|
10080
|
+
nextStep: null,
|
|
10081
|
+
isActionable: false,
|
|
10082
|
+
reason: `No transition rules defined for step: ${lastStepName}`,
|
|
10083
|
+
};
|
|
10084
|
+
}
|
|
10085
|
+
// Find the first matching rule
|
|
10086
|
+
const matchingRule = rules.find((rule) => rule.condition(context));
|
|
10087
|
+
if (!matchingRule) {
|
|
10088
|
+
return {
|
|
10089
|
+
nextStep: null,
|
|
10090
|
+
isActionable: false,
|
|
10091
|
+
reason: `No matching transition rule for current context`,
|
|
10092
|
+
};
|
|
10093
|
+
}
|
|
10094
|
+
return {
|
|
10095
|
+
nextStep: matchingRule.nextStep,
|
|
10096
|
+
isActionable: matchingRule.isActionable,
|
|
10097
|
+
reason: matchingRule.reason,
|
|
10098
|
+
};
|
|
10099
|
+
}
|
|
10100
|
+
|
|
10101
|
+
/**
|
|
10102
|
+
* Find a step by name in the bridge result.
|
|
10103
|
+
*
|
|
10104
|
+
* @param result - The bridge result to search.
|
|
10105
|
+
* @param stepName - The name of the step to find.
|
|
10106
|
+
* @returns The step if found, undefined otherwise.
|
|
10107
|
+
*
|
|
10108
|
+
* @example
|
|
10109
|
+
* ```typescript
|
|
10110
|
+
* import { findStepByName } from './findStep'
|
|
10111
|
+
*
|
|
10112
|
+
* const burnStep = findStepByName(result, 'burn')
|
|
10113
|
+
* if (burnStep) {
|
|
10114
|
+
* console.log('Burn tx:', burnStep.txHash)
|
|
10115
|
+
* }
|
|
10116
|
+
* ```
|
|
10117
|
+
*/
|
|
10118
|
+
function findStepByName(result, stepName) {
|
|
10119
|
+
return result.steps.find((step) => step.name === stepName);
|
|
10120
|
+
}
|
|
10121
|
+
/**
|
|
10122
|
+
* Find a pending step by name and return it with its index.
|
|
10123
|
+
*
|
|
10124
|
+
* Searches for a step that matches both the step name and has a pending state.
|
|
10125
|
+
*
|
|
10126
|
+
* @param result - The bridge result containing steps to search through.
|
|
10127
|
+
* @param stepName - The step name to find (e.g., 'burn', 'mint', 'fetchAttestation').
|
|
10128
|
+
* @returns An object containing the step and its index in the steps array.
|
|
10129
|
+
* @throws KitError if the specified pending step is not found.
|
|
10130
|
+
*
|
|
10131
|
+
* @example
|
|
10132
|
+
* ```typescript
|
|
10133
|
+
* import { findPendingStep } from './findStep'
|
|
10134
|
+
*
|
|
10135
|
+
* const { step, index } = findPendingStep(result, 'burn')
|
|
10136
|
+
* console.log('Pending step:', step.name, 'at index:', index)
|
|
10137
|
+
* ```
|
|
10138
|
+
*/
|
|
10139
|
+
function findPendingStep(result, stepName) {
|
|
10140
|
+
const index = result.steps.findIndex((step) => step.name === stepName && step.state === 'pending');
|
|
10141
|
+
if (index === -1) {
|
|
10142
|
+
throw new KitError({
|
|
10143
|
+
...InputError.VALIDATION_FAILED,
|
|
10144
|
+
recoverability: 'FATAL',
|
|
10145
|
+
message: `Pending step "${stepName}" not found in result`,
|
|
10146
|
+
});
|
|
10147
|
+
}
|
|
10148
|
+
const step = result.steps[index];
|
|
10149
|
+
if (!step) {
|
|
10150
|
+
throw new KitError({
|
|
10151
|
+
...InputError.VALIDATION_FAILED,
|
|
10152
|
+
recoverability: 'FATAL',
|
|
10153
|
+
message: 'Pending step is undefined',
|
|
10154
|
+
});
|
|
10155
|
+
}
|
|
10156
|
+
return { step, index };
|
|
10157
|
+
}
|
|
10158
|
+
/**
|
|
10159
|
+
* Get the burn transaction hash from bridge result.
|
|
10160
|
+
*
|
|
10161
|
+
* @param result - The bridge result.
|
|
10162
|
+
* @returns The burn transaction hash, or undefined if not found.
|
|
10163
|
+
*
|
|
10164
|
+
* @example
|
|
10165
|
+
* ```typescript
|
|
10166
|
+
* import { getBurnTxHash } from './findStep'
|
|
10167
|
+
*
|
|
10168
|
+
* const burnTxHash = getBurnTxHash(result)
|
|
10169
|
+
* if (burnTxHash) {
|
|
10170
|
+
* console.log('Burn tx hash:', burnTxHash)
|
|
10171
|
+
* }
|
|
10172
|
+
* ```
|
|
10173
|
+
*/
|
|
10174
|
+
function getBurnTxHash(result) {
|
|
10175
|
+
return findStepByName(result, CCTPv2StepName.burn)?.txHash;
|
|
10176
|
+
}
|
|
10177
|
+
/**
|
|
10178
|
+
* Get the attestation data from bridge result.
|
|
10179
|
+
*
|
|
10180
|
+
* @param result - The bridge result.
|
|
10181
|
+
* @returns The attestation data, or undefined if not found.
|
|
10182
|
+
*
|
|
10183
|
+
* @example
|
|
10184
|
+
* ```typescript
|
|
10185
|
+
* import { getAttestationData } from './findStep'
|
|
10186
|
+
*
|
|
10187
|
+
* const attestation = getAttestationData(result)
|
|
10188
|
+
* if (attestation) {
|
|
10189
|
+
* console.log('Attestation:', attestation.message)
|
|
10190
|
+
* }
|
|
10191
|
+
* ```
|
|
10192
|
+
*/
|
|
10193
|
+
function getAttestationData(result) {
|
|
10194
|
+
// Prefer reAttest data (most recent attestation after expiry)
|
|
10195
|
+
const reAttestStep = findStepByName(result, CCTPv2StepName.reAttest);
|
|
10196
|
+
if (reAttestStep?.state === 'success' && reAttestStep.data) {
|
|
10197
|
+
return reAttestStep.data;
|
|
10198
|
+
}
|
|
10199
|
+
// Fall back to fetchAttestation step
|
|
10200
|
+
const fetchStep = findStepByName(result, CCTPv2StepName.fetchAttestation);
|
|
10201
|
+
return fetchStep?.data;
|
|
10202
|
+
}
|
|
10203
|
+
|
|
10204
|
+
/**
|
|
10205
|
+
* Check if the analysis indicates a non-actionable pending state.
|
|
10206
|
+
*
|
|
10207
|
+
* A pending state is non-actionable when there's a continuation step but
|
|
10208
|
+
* the analysis marks it as not actionable, typically because we need to
|
|
10209
|
+
* wait for an ongoing operation to complete.
|
|
10210
|
+
*
|
|
10211
|
+
* @param analysis - The step analysis result from analyzeSteps.
|
|
10212
|
+
* @param result - The bridge result to check for pending steps.
|
|
10213
|
+
* @returns True if there is a pending step that we should wait for.
|
|
10214
|
+
*
|
|
10215
|
+
* @example
|
|
10216
|
+
* ```typescript
|
|
10217
|
+
* import { hasPendingState } from './stepUtils'
|
|
10218
|
+
* import { analyzeSteps } from '../analyzeSteps'
|
|
10219
|
+
*
|
|
10220
|
+
* const analysis = analyzeSteps(bridgeResult)
|
|
10221
|
+
* if (hasPendingState(analysis, bridgeResult)) {
|
|
10222
|
+
* // Wait for the pending operation to complete
|
|
10223
|
+
* }
|
|
10224
|
+
* ```
|
|
10225
|
+
*/
|
|
10226
|
+
/**
|
|
10227
|
+
* Evaluate a transaction receipt and return the corresponding step state
|
|
10228
|
+
* and error message. Centralises the success/revert/unconfirmed logic so
|
|
10229
|
+
* every call-site behaves identically.
|
|
10230
|
+
*
|
|
10231
|
+
* @param receipt - The transaction receipt containing status and block info.
|
|
10232
|
+
* @param txHash - The transaction hash used in error messages.
|
|
10233
|
+
* @returns An object with `state` and an optional `errorMessage`.
|
|
10234
|
+
*
|
|
10235
|
+
* @example
|
|
10236
|
+
* ```typescript
|
|
10237
|
+
* const outcome = evaluateTransactionOutcome(receipt, '0xabc...')
|
|
10238
|
+
* step.state = outcome.state
|
|
10239
|
+
* if (outcome.errorMessage) step.errorMessage = outcome.errorMessage
|
|
10240
|
+
* ```
|
|
10241
|
+
*/
|
|
10242
|
+
function evaluateTransactionOutcome(receipt, txHash) {
|
|
10243
|
+
if (receipt.status === 'success' && receipt.blockNumber) {
|
|
10244
|
+
return { state: 'success' };
|
|
10245
|
+
}
|
|
10246
|
+
return {
|
|
10247
|
+
state: 'error',
|
|
10248
|
+
errorMessage: receipt.status === 'reverted'
|
|
10249
|
+
? `Transaction ${txHash} was reverted`
|
|
10250
|
+
: 'Transaction was not confirmed on-chain',
|
|
10251
|
+
};
|
|
10252
|
+
}
|
|
10253
|
+
function hasPendingState(analysis, result) {
|
|
10254
|
+
// Check if there's a continuation step that's marked as non-actionable
|
|
10255
|
+
if (analysis.continuationStep === null || analysis.isActionable) {
|
|
10256
|
+
return false;
|
|
10257
|
+
}
|
|
10258
|
+
// Verify that the continuation step actually exists and is in pending state
|
|
10259
|
+
const pendingStep = result.steps.find((step) => step.name === analysis.continuationStep && step.state === 'pending');
|
|
10260
|
+
return pendingStep !== undefined;
|
|
10261
|
+
}
|
|
10262
|
+
/**
|
|
10263
|
+
* Check if the step is the last one in the execution flow.
|
|
10264
|
+
*
|
|
10265
|
+
* @param step - The step object to check.
|
|
10266
|
+
* @param stepNames - The ordered list of step names in the execution flow.
|
|
10267
|
+
* @returns True if this is the last step in the flow.
|
|
10268
|
+
*
|
|
10269
|
+
* @example
|
|
10270
|
+
* ```typescript
|
|
10271
|
+
* import { isLastStep } from './stepUtils'
|
|
10272
|
+
*
|
|
10273
|
+
* const stepNames = ['approve', 'burn', 'fetchAttestation', 'mint']
|
|
10274
|
+
* isLastStep({ name: 'mint' }, stepNames) // true
|
|
10275
|
+
* isLastStep({ name: 'burn' }, stepNames) // false
|
|
10276
|
+
* ```
|
|
10277
|
+
*/
|
|
10278
|
+
function isLastStep(step, stepNames) {
|
|
10279
|
+
const stepIndex = stepNames.indexOf(step.name);
|
|
10280
|
+
return stepIndex === -1 || stepIndex >= stepNames.length - 1;
|
|
10281
|
+
}
|
|
10282
|
+
/**
|
|
10283
|
+
* Wait for a pending transaction to complete.
|
|
10284
|
+
*
|
|
10285
|
+
* Poll the adapter until the transaction is confirmed on-chain and return
|
|
10286
|
+
* the updated step with success or error state based on the receipt.
|
|
10287
|
+
*
|
|
10288
|
+
* @param pendingStep - The full step object containing the transaction hash.
|
|
10289
|
+
* @param adapter - The adapter to use for waiting.
|
|
10290
|
+
* @param chain - The chain where the transaction was submitted.
|
|
10291
|
+
* @returns The updated step object with success or error state.
|
|
10292
|
+
*
|
|
10293
|
+
* @throws KitError when the pending step has no transaction hash.
|
|
10294
|
+
*
|
|
10295
|
+
* @example
|
|
10296
|
+
* ```typescript
|
|
10297
|
+
* import { waitForPendingTransaction } from './bridgeStepUtils'
|
|
10298
|
+
*
|
|
10299
|
+
* const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
|
|
10300
|
+
* const updatedStep = await waitForPendingTransaction(pendingStep, adapter, chain)
|
|
10301
|
+
* // updatedStep.state is now 'success' or 'error'
|
|
10302
|
+
* ```
|
|
10303
|
+
*/
|
|
10304
|
+
async function waitForPendingTransaction(pendingStep, adapter, chain) {
|
|
10305
|
+
if (!pendingStep.txHash) {
|
|
10306
|
+
throw new KitError({
|
|
10307
|
+
...InputError.VALIDATION_FAILED,
|
|
10308
|
+
recoverability: 'FATAL',
|
|
10309
|
+
message: `Cannot wait for pending ${pendingStep.name}: no transaction hash available`,
|
|
10310
|
+
});
|
|
10311
|
+
}
|
|
10312
|
+
const txHash = pendingStep.txHash;
|
|
10313
|
+
const txReceipt = await retryAsync(async () => adapter.waitForTransaction(txHash, undefined, chain), {
|
|
10314
|
+
isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
|
|
10315
|
+
});
|
|
10316
|
+
const outcome = evaluateTransactionOutcome(txReceipt, txHash);
|
|
10317
|
+
return {
|
|
10318
|
+
...pendingStep,
|
|
10319
|
+
state: outcome.state,
|
|
10320
|
+
data: txReceipt,
|
|
10321
|
+
explorerUrl: buildExplorerUrl(chain, txHash),
|
|
10322
|
+
...(outcome.errorMessage ? { errorMessage: outcome.errorMessage } : {}),
|
|
10323
|
+
};
|
|
10324
|
+
}
|
|
10325
|
+
/**
|
|
10326
|
+
* Wait for a pending step to complete.
|
|
10327
|
+
*
|
|
10328
|
+
* For transaction steps: waits for the transaction to be confirmed.
|
|
10329
|
+
* For attestation: re-executes the attestation fetch.
|
|
10330
|
+
*
|
|
10331
|
+
* @typeParam TFromAdapterCapabilities - The capabilities of the source adapter.
|
|
10332
|
+
* @typeParam TToAdapterCapabilities - The capabilities of the destination adapter.
|
|
10333
|
+
* @param pendingStep - The full step object (with name, state, txHash, data, etc.) to resolve.
|
|
10334
|
+
* @param adapter - The adapter to use.
|
|
10335
|
+
* @param chain - The chain where the step is executing.
|
|
10336
|
+
* @param context - The retry context.
|
|
10337
|
+
* @param result - The bridge result.
|
|
10338
|
+
* @param provider - The CCTP v2 bridging provider.
|
|
10339
|
+
* @returns The resolved step object with updated state.
|
|
10340
|
+
*
|
|
10341
|
+
* @throws KitError when fetching attestation but burn transaction hash is not found.
|
|
10342
|
+
*
|
|
10343
|
+
* @example
|
|
10344
|
+
* ```typescript
|
|
10345
|
+
* import { waitForStepToComplete } from './bridgeStepUtils'
|
|
10346
|
+
*
|
|
10347
|
+
* const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
|
|
10348
|
+
* const updatedStep = await waitForStepToComplete(
|
|
10349
|
+
* pendingStep,
|
|
10350
|
+
* adapter,
|
|
10351
|
+
* chain,
|
|
10352
|
+
* context,
|
|
10353
|
+
* result,
|
|
10354
|
+
* provider,
|
|
10355
|
+
* )
|
|
10356
|
+
* // updatedStep.state is now 'success' or 'error'
|
|
10357
|
+
* ```
|
|
10358
|
+
*/
|
|
10359
|
+
async function waitForStepToComplete(pendingStep, adapter, chain, context, result, provider) {
|
|
10360
|
+
if (pendingStep.name === CCTPv2StepName.fetchAttestation) {
|
|
10361
|
+
// For attestation, re-run the fetch (it has built-in polling)
|
|
10362
|
+
const burnTxHash = getBurnTxHash(result);
|
|
10363
|
+
if (!burnTxHash) {
|
|
10364
|
+
throw new KitError({
|
|
10365
|
+
...InputError.VALIDATION_FAILED,
|
|
10366
|
+
recoverability: 'FATAL',
|
|
10367
|
+
message: 'Cannot fetch attestation: burn transaction hash not found',
|
|
10368
|
+
});
|
|
10369
|
+
}
|
|
10370
|
+
const sourceAddress = result.source.address;
|
|
10371
|
+
const attestation = await provider.fetchAttestation({
|
|
10372
|
+
chain: result.source.chain,
|
|
10373
|
+
adapter: context.from,
|
|
10374
|
+
address: sourceAddress,
|
|
10375
|
+
}, burnTxHash);
|
|
10376
|
+
return {
|
|
10377
|
+
...pendingStep,
|
|
10378
|
+
state: 'success',
|
|
10379
|
+
data: attestation,
|
|
10380
|
+
};
|
|
10381
|
+
}
|
|
10382
|
+
// For transaction steps, wait for the transaction to complete
|
|
10383
|
+
return waitForPendingTransaction(pendingStep, adapter, chain);
|
|
10384
|
+
}
|
|
10385
|
+
|
|
10386
|
+
/**
|
|
10387
|
+
* Executes a prepared chain request and returns the result as a bridge step.
|
|
10388
|
+
*
|
|
10389
|
+
* This function takes a prepared chain request (containing transaction data) and executes
|
|
10390
|
+
* it using the appropriate adapter. It handles the execution details and formats
|
|
10391
|
+
* the result as a standardized bridge step with transaction details and explorer URLs.
|
|
10392
|
+
*
|
|
10393
|
+
* @param params - The execution parameters containing:
|
|
10394
|
+
* - `name`: The name of the step
|
|
10395
|
+
* - `request`: The prepared chain request containing transaction data
|
|
10396
|
+
* - `adapter`: The adapter that will execute the transaction
|
|
10397
|
+
* - `confirmations`: The number of confirmations to wait for (defaults to 1)
|
|
10398
|
+
* - `timeout`: The timeout for the request in milliseconds
|
|
10399
|
+
* @returns The bridge step with the transaction details and explorer URL
|
|
10400
|
+
* @throws If the transaction execution fails
|
|
10401
|
+
*
|
|
10402
|
+
* @example
|
|
10403
|
+
* ```typescript
|
|
10404
|
+
* const step = await executePreparedChainRequest({
|
|
10405
|
+
* name: 'approve',
|
|
10406
|
+
* request: preparedRequest,
|
|
10407
|
+
* adapter: adapter,
|
|
10408
|
+
* confirmations: 2,
|
|
10409
|
+
* timeout: 30000
|
|
10410
|
+
* })
|
|
10411
|
+
* console.log('Transaction hash:', step.txHash)
|
|
10412
|
+
* ```
|
|
10413
|
+
*/
|
|
10414
|
+
async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, }) {
|
|
9037
10415
|
const step = { name, state: 'pending' };
|
|
9038
10416
|
try {
|
|
9039
10417
|
/**
|
|
@@ -9053,17 +10431,25 @@ async function executePreparedChainRequest({ name, request, adapter, chain, conf
|
|
|
9053
10431
|
retryOptions.deadlineMs = Date.now() + timeout;
|
|
9054
10432
|
}
|
|
9055
10433
|
const transaction = await retryAsync(async () => adapter.waitForTransaction(txHash, { confirmations, timeout }, chain), retryOptions);
|
|
9056
|
-
|
|
10434
|
+
const outcome = evaluateTransactionOutcome(transaction, txHash);
|
|
10435
|
+
step.state = outcome.state;
|
|
9057
10436
|
step.data = transaction;
|
|
9058
10437
|
// Generate explorer URL for the step
|
|
9059
10438
|
step.explorerUrl = buildExplorerUrl(chain, txHash);
|
|
9060
|
-
if (
|
|
9061
|
-
step.errorMessage =
|
|
10439
|
+
if (outcome.errorMessage) {
|
|
10440
|
+
step.errorMessage = outcome.errorMessage;
|
|
10441
|
+
// Transaction was mined but reverted on-chain.
|
|
10442
|
+
step.errorCategory = 'chain_revert';
|
|
9062
10443
|
}
|
|
9063
10444
|
}
|
|
9064
10445
|
catch (err) {
|
|
9065
10446
|
step.state = 'error';
|
|
9066
10447
|
step.error = err;
|
|
10448
|
+
// Sequential path does not yet attempt fine-grained classification of
|
|
10449
|
+
// pre-submission errors (user_rejected, capability errors, etc.). Mark
|
|
10450
|
+
// as `unknown` so consumers can at least detect the category is
|
|
10451
|
+
// populated uniformly across batched and sequential flows.
|
|
10452
|
+
step.errorCategory = 'unknown';
|
|
9067
10453
|
// Optionally parse for common blockchain error formats
|
|
9068
10454
|
if (err instanceof Error) {
|
|
9069
10455
|
step.errorMessage = err.message;
|
|
@@ -9072,7 +10458,7 @@ async function executePreparedChainRequest({ name, request, adapter, chain, conf
|
|
|
9072
10458
|
step.errorMessage = String(err.message);
|
|
9073
10459
|
}
|
|
9074
10460
|
else {
|
|
9075
|
-
step.errorMessage =
|
|
10461
|
+
step.errorMessage = `Unknown error occurred during ${name} step.`;
|
|
9076
10462
|
}
|
|
9077
10463
|
}
|
|
9078
10464
|
return step;
|
|
@@ -10612,16 +11998,70 @@ async function executeBatchedApproveAndBurn(params, provider) {
|
|
|
10612
11998
|
const batchResult = await adapter.batchExecute([approveCallData, burnCallData], chain);
|
|
10613
11999
|
const approveReceipt = batchResult.receipts[0];
|
|
10614
12000
|
const burnReceipt = batchResult.receipts[1];
|
|
10615
|
-
const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain);
|
|
10616
|
-
const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain);
|
|
12001
|
+
const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
|
|
12002
|
+
const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
|
|
10617
12003
|
if (burnStep.state !== 'error' && !burnStep.txHash) {
|
|
10618
12004
|
burnStep.state = 'error';
|
|
10619
12005
|
burnStep.errorMessage =
|
|
10620
12006
|
'Batched burn step completed but no transaction hash was returned.';
|
|
12007
|
+
burnStep.errorCategory = 'unknown';
|
|
10621
12008
|
}
|
|
10622
12009
|
const context = { burnTxHash: burnStep.txHash ?? '' };
|
|
10623
12010
|
return { approveStep, burnStep, context };
|
|
10624
12011
|
}
|
|
12012
|
+
/**
|
|
12013
|
+
* Derive a {@link BridgeStepErrorCategory} for a missing receipt.
|
|
12014
|
+
*
|
|
12015
|
+
* Combines the EIP-5792 `statusCode` (when present) with the underlying
|
|
12016
|
+
* polling error (when set) to produce the most specific category available.
|
|
12017
|
+
* Falls back to `'unknown'` when neither signal is conclusive.
|
|
12018
|
+
*
|
|
12019
|
+
* @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
|
|
12020
|
+
* @param batchError - The polling error from `batchExecute`, if any.
|
|
12021
|
+
* @returns The derived error category for a missing-receipt step.
|
|
12022
|
+
*
|
|
12023
|
+
* @internal
|
|
12024
|
+
*/
|
|
12025
|
+
function categorizeMissingReceipt(statusCode, batchError) {
|
|
12026
|
+
if (statusCode === 400)
|
|
12027
|
+
return 'failed_offchain';
|
|
12028
|
+
if (statusCode === 500)
|
|
12029
|
+
return 'reverted_onchain';
|
|
12030
|
+
if (statusCode === 600)
|
|
12031
|
+
return 'partial_reverted';
|
|
12032
|
+
if (batchError instanceof KitError &&
|
|
12033
|
+
batchError.code === NetworkError.TIMEOUT.code) {
|
|
12034
|
+
return 'polling_timeout';
|
|
12035
|
+
}
|
|
12036
|
+
return 'unknown';
|
|
12037
|
+
}
|
|
12038
|
+
/**
|
|
12039
|
+
* Derive a {@link BridgeStepErrorCategory} for a receipt that was returned
|
|
12040
|
+
* but whose per-call `status` is not `'success'`.
|
|
12041
|
+
*
|
|
12042
|
+
* A numeric EIP-5792 `statusCode` of 500 or 600 tells us the whole batch
|
|
12043
|
+
* reverted on-chain (completely or partially); otherwise the receipt
|
|
12044
|
+
* itself signalled a revert without a distinguishing code, so classify
|
|
12045
|
+
* as a plain on-chain revert.
|
|
12046
|
+
*
|
|
12047
|
+
* @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
|
|
12048
|
+
* @returns The derived error category for a failed-receipt step.
|
|
12049
|
+
*
|
|
12050
|
+
* @internal
|
|
12051
|
+
*/
|
|
12052
|
+
function categorizeFailedReceipt(statusCode) {
|
|
12053
|
+
// Mirror categorizeMissingReceipt: if the wallet's terminal status is
|
|
12054
|
+
// 400 ("batch not included onchain"), that judgement is authoritative
|
|
12055
|
+
// even when a non-success receipt is attached. Without this, a wrapped
|
|
12056
|
+
// 400 receipt would fall through to `chain_revert` and mislead UX.
|
|
12057
|
+
if (statusCode === 400)
|
|
12058
|
+
return 'failed_offchain';
|
|
12059
|
+
if (statusCode === 600)
|
|
12060
|
+
return 'partial_reverted';
|
|
12061
|
+
if (statusCode === 500)
|
|
12062
|
+
return 'reverted_onchain';
|
|
12063
|
+
return 'chain_revert';
|
|
12064
|
+
}
|
|
10625
12065
|
/**
|
|
10626
12066
|
* Build a {@link BridgeStep} from a single receipt within a batch.
|
|
10627
12067
|
*
|
|
@@ -10636,11 +12076,17 @@ async function executeBatchedApproveAndBurn(params, provider) {
|
|
|
10636
12076
|
* @param batchId - Wallet-assigned batch identifier.
|
|
10637
12077
|
* @param adapter - The batch-capable adapter (used for confirmation).
|
|
10638
12078
|
* @param chain - The EVM chain the batch was executed on.
|
|
12079
|
+
* @param statusCode - Optional EIP-5792 `statusCode` for the batch.
|
|
12080
|
+
* Used to classify the step's error category when the receipt is
|
|
12081
|
+
* missing or failed.
|
|
12082
|
+
* @param batchError - Optional polling error from `batchExecute`.
|
|
12083
|
+
* Preserved on the step so callers can inspect underlying timeouts
|
|
12084
|
+
* or RPC failures.
|
|
10639
12085
|
* @returns A fully-populated bridge step with state, tx hash and explorer URL.
|
|
10640
12086
|
*
|
|
10641
12087
|
* @internal
|
|
10642
12088
|
*/
|
|
10643
|
-
async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
12089
|
+
async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCode, batchError) {
|
|
10644
12090
|
const step = {
|
|
10645
12091
|
name,
|
|
10646
12092
|
state: 'pending',
|
|
@@ -10650,6 +12096,10 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
10650
12096
|
if (!receipt) {
|
|
10651
12097
|
step.state = 'error';
|
|
10652
12098
|
step.errorMessage = `No receipt returned for ${name} in batch ${batchId}.`;
|
|
12099
|
+
step.errorCategory = categorizeMissingReceipt(statusCode, batchError);
|
|
12100
|
+
if (batchError !== undefined) {
|
|
12101
|
+
step.error = batchError;
|
|
12102
|
+
}
|
|
10653
12103
|
return step;
|
|
10654
12104
|
}
|
|
10655
12105
|
step.txHash = receipt.txHash;
|
|
@@ -10659,11 +12109,13 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
10659
12109
|
if (receipt.status !== 'success') {
|
|
10660
12110
|
step.state = 'error';
|
|
10661
12111
|
step.errorMessage = `${name} call failed within batch ${batchId}.`;
|
|
12112
|
+
step.errorCategory = categorizeFailedReceipt(statusCode);
|
|
10662
12113
|
return step;
|
|
10663
12114
|
}
|
|
10664
12115
|
if (!receipt.txHash) {
|
|
10665
12116
|
step.state = 'error';
|
|
10666
12117
|
step.errorMessage = `${name} succeeded in batch but returned an empty transaction hash.`;
|
|
12118
|
+
step.errorCategory = 'unknown';
|
|
10667
12119
|
return step;
|
|
10668
12120
|
}
|
|
10669
12121
|
try {
|
|
@@ -10673,10 +12125,12 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
10673
12125
|
txHash: receipt.txHash,
|
|
10674
12126
|
})),
|
|
10675
12127
|
});
|
|
10676
|
-
|
|
12128
|
+
const outcome = evaluateTransactionOutcome(transaction, receipt.txHash);
|
|
12129
|
+
step.state = outcome.state;
|
|
10677
12130
|
step.data = transaction;
|
|
10678
|
-
if (
|
|
10679
|
-
step.errorMessage =
|
|
12131
|
+
if (outcome.errorMessage) {
|
|
12132
|
+
step.errorMessage = outcome.errorMessage;
|
|
12133
|
+
step.errorCategory = 'chain_revert';
|
|
10680
12134
|
}
|
|
10681
12135
|
}
|
|
10682
12136
|
catch (err) {
|
|
@@ -10684,11 +12138,12 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
|
10684
12138
|
step.error = err;
|
|
10685
12139
|
step.errorMessage =
|
|
10686
12140
|
err instanceof Error ? err.message : 'Unknown error during confirmation.';
|
|
12141
|
+
step.errorCategory = 'unknown';
|
|
10687
12142
|
}
|
|
10688
12143
|
return step;
|
|
10689
12144
|
}
|
|
10690
12145
|
|
|
10691
|
-
var version = "1.
|
|
12146
|
+
var version = "1.7.0";
|
|
10692
12147
|
var pkg = {
|
|
10693
12148
|
version: version};
|
|
10694
12149
|
|
|
@@ -10764,6 +12219,7 @@ async function executeBatchedPath(params, provider, result, invocation) {
|
|
|
10764
12219
|
errorMessage: error_ instanceof Error
|
|
10765
12220
|
? error_.message
|
|
10766
12221
|
: 'Batched approve + burn failed.',
|
|
12222
|
+
errorCategory: classifyPreSubmissionError(error_),
|
|
10767
12223
|
});
|
|
10768
12224
|
return undefined;
|
|
10769
12225
|
}
|
|
@@ -10778,6 +12234,89 @@ function ensureStepErrorMessage(name, step) {
|
|
|
10778
12234
|
step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
|
|
10779
12235
|
}
|
|
10780
12236
|
}
|
|
12237
|
+
/**
|
|
12238
|
+
* Coerce a raw JSON-RPC `code` to a number.
|
|
12239
|
+
*
|
|
12240
|
+
* Some wallet SDKs serialize the JSON-RPC `code` as a string ("4001")
|
|
12241
|
+
* after round-tripping through JSON; accept both shapes so strict `===`
|
|
12242
|
+
* comparisons downstream still classify 5720/5730/5740 correctly — those
|
|
12243
|
+
* codes have no message-pattern fallback.
|
|
12244
|
+
*
|
|
12245
|
+
* @param rawCode - The raw `code` extracted from the error object.
|
|
12246
|
+
* @returns The numeric code, or `undefined` if the value cannot be parsed.
|
|
12247
|
+
*
|
|
12248
|
+
* @internal
|
|
12249
|
+
*/
|
|
12250
|
+
function coerceRpcCode(rawCode) {
|
|
12251
|
+
if (typeof rawCode === 'number') {
|
|
12252
|
+
return rawCode;
|
|
12253
|
+
}
|
|
12254
|
+
if (typeof rawCode === 'string') {
|
|
12255
|
+
return Number.parseInt(rawCode, 10);
|
|
12256
|
+
}
|
|
12257
|
+
return undefined;
|
|
12258
|
+
}
|
|
12259
|
+
/**
|
|
12260
|
+
* Classify a pre-submission error thrown during `wallet_sendCalls`.
|
|
12261
|
+
*
|
|
12262
|
+
* Inspect the error's JSON-RPC `code` (falling back to message pattern
|
|
12263
|
+
* matching for wrapper errors like viem's `ChainMismatchError`) and map
|
|
12264
|
+
* it to a {@link BridgeStepErrorCategory}. This lets downstream consumers
|
|
12265
|
+
* distinguish user rejections, wallet capability gaps, and unknown
|
|
12266
|
+
* failures without parsing error messages.
|
|
12267
|
+
*
|
|
12268
|
+
* @remarks
|
|
12269
|
+
* Does NOT alter control flow — the SDK continues to surface a
|
|
12270
|
+
* `state: 'error'` step. Auto-fallback to sequential execution is
|
|
12271
|
+
* intentionally out of scope for this helper.
|
|
12272
|
+
*
|
|
12273
|
+
* @param err - The error thrown by `wallet_sendCalls`.
|
|
12274
|
+
* @returns The derived error category, or `'unknown'` if no match.
|
|
12275
|
+
*
|
|
12276
|
+
* @internal
|
|
12277
|
+
*/
|
|
12278
|
+
function classifyPreSubmissionError(err) {
|
|
12279
|
+
// Cross-realm-safe duck typing: `instanceof Error` returns false for
|
|
12280
|
+
// errors thrown in a different JavaScript realm (e.g., a wallet
|
|
12281
|
+
// provider running inside an iframe, which is common with WalletConnect
|
|
12282
|
+
// and the Coinbase Wallet SDK).
|
|
12283
|
+
if (typeof err !== 'object' || err === null || !('message' in err)) {
|
|
12284
|
+
return 'unknown';
|
|
12285
|
+
}
|
|
12286
|
+
const code = coerceRpcCode(err.code);
|
|
12287
|
+
const message = String(err.message);
|
|
12288
|
+
// Numeric JSON-RPC codes are authoritative; check them before falling
|
|
12289
|
+
// back to message-pattern matching. Order matters: an error carrying
|
|
12290
|
+
// `code === 5750` with a message like "user rejected the upgrade"
|
|
12291
|
+
// is a capability problem, not a plain user rejection.
|
|
12292
|
+
if (code === 4001) {
|
|
12293
|
+
return 'user_rejected';
|
|
12294
|
+
}
|
|
12295
|
+
if (code === 5700 || code === 5710 || code === 5750) {
|
|
12296
|
+
return 'atomic_unsupported';
|
|
12297
|
+
}
|
|
12298
|
+
if (code === 5720) {
|
|
12299
|
+
return 'duplicate_batch_id';
|
|
12300
|
+
}
|
|
12301
|
+
if (code === 5730) {
|
|
12302
|
+
return 'unknown_bundle';
|
|
12303
|
+
}
|
|
12304
|
+
if (code === 5740) {
|
|
12305
|
+
return 'batch_too_large';
|
|
12306
|
+
}
|
|
12307
|
+
// Fall back to message patterns when no specific code is available —
|
|
12308
|
+
// viem (and other wrapper layers) sometimes strip the numeric code
|
|
12309
|
+
// while preserving the original wallet message in `Details:`.
|
|
12310
|
+
if (/EIP-7702 not supported/i.test(message) ||
|
|
12311
|
+
/does not support the requested chain/i.test(message) ||
|
|
12312
|
+
/rejected the upgrade/i.test(message)) {
|
|
12313
|
+
return 'atomic_unsupported';
|
|
12314
|
+
}
|
|
12315
|
+
if (/user rejected/i.test(message)) {
|
|
12316
|
+
return 'user_rejected';
|
|
12317
|
+
}
|
|
12318
|
+
return 'unknown';
|
|
12319
|
+
}
|
|
10781
12320
|
/**
|
|
10782
12321
|
* Execute a cross-chain USDC bridge using the CCTP v2 protocol.
|
|
10783
12322
|
*
|
|
@@ -10996,681 +12535,266 @@ const hexStringSchema = z
|
|
|
10996
12535
|
.refine((value) => value.trim().length > 0, 'Hex string cannot be empty')
|
|
10997
12536
|
.refine((value) => value.startsWith('0x'), 'Hex string must start with 0x prefix')
|
|
10998
12537
|
.refine((value) => {
|
|
10999
|
-
const hexPattern = /^0x[0-9a-fA-F]+$/;
|
|
11000
|
-
return hexPattern.test(value);
|
|
11001
|
-
}, 'Hex string contains invalid characters. Only hexadecimal characters (0-9, a-f, A-F) are allowed after 0x');
|
|
11002
|
-
/**
|
|
11003
|
-
* Schema for validating EVM addresses.
|
|
11004
|
-
*
|
|
11005
|
-
* This schema validates that a string is a properly formatted EVM address:
|
|
11006
|
-
* - Must be a valid hex string with '0x' prefix
|
|
11007
|
-
* - Must be exactly 42 characters long (0x + 40 hex characters)
|
|
11008
|
-
*
|
|
11009
|
-
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
11010
|
-
*
|
|
11011
|
-
* @example
|
|
11012
|
-
* ```typescript
|
|
11013
|
-
* import { evmAddressSchema } from '@core/adapter'
|
|
11014
|
-
*
|
|
11015
|
-
* const validAddress = '0x1234567890123456789012345678901234567890'
|
|
11016
|
-
*
|
|
11017
|
-
* const result = evmAddressSchema.safeParse(validAddress)
|
|
11018
|
-
* console.log(result.success) // true
|
|
11019
|
-
* ```
|
|
11020
|
-
*/
|
|
11021
|
-
hexStringSchema.refine((value) => value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)');
|
|
11022
|
-
/**
|
|
11023
|
-
* Schema for validating transaction hashes.
|
|
11024
|
-
*
|
|
11025
|
-
* This schema validates that a string is a properly formatted transaction hash:
|
|
11026
|
-
* - Must be a valid hex string with '0x' prefix
|
|
11027
|
-
* - Must be exactly 66 characters long (0x + 64 hex characters)
|
|
11028
|
-
*
|
|
11029
|
-
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
11030
|
-
*
|
|
11031
|
-
* @example
|
|
11032
|
-
* ```typescript
|
|
11033
|
-
* import { evmTransactionHashSchema } from '@core/adapter'
|
|
11034
|
-
*
|
|
11035
|
-
* const validTxHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
|
|
11036
|
-
*
|
|
11037
|
-
* const result = evmTransactionHashSchema.safeParse(validTxHash)
|
|
11038
|
-
* console.log(result.success) // true
|
|
11039
|
-
* ```
|
|
11040
|
-
*/
|
|
11041
|
-
hexStringSchema.refine((value) => value.length === 66, 'Transaction hash must be exactly 66 characters long (0x + 64 hex characters)');
|
|
11042
|
-
/**
|
|
11043
|
-
* Schema for validating base58-encoded strings.
|
|
11044
|
-
*
|
|
11045
|
-
* This schema validates that a string:
|
|
11046
|
-
* - Is a string type
|
|
11047
|
-
* - Is not empty after trimming
|
|
11048
|
-
* - Contains only valid base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z)
|
|
11049
|
-
* - Does not contain commonly confused characters (0, O, I, l)
|
|
11050
|
-
*
|
|
11051
|
-
* @remarks
|
|
11052
|
-
* This schema does not validate length, making it suitable for various base58-encoded data
|
|
11053
|
-
* like Solana addresses, transaction signatures, and other base58-encoded data.
|
|
11054
|
-
*
|
|
11055
|
-
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
11056
|
-
*
|
|
11057
|
-
* @example
|
|
11058
|
-
* ```typescript
|
|
11059
|
-
* import { base58StringSchema } from '@core/adapter'
|
|
11060
|
-
*
|
|
11061
|
-
* const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
|
|
11062
|
-
* const validTxHash = '3Jf8k2L5mN9pQ7rS1tV4wX6yZ8aB2cD4eF5gH7iJ9kL1mN3oP5qR7sT9uV1wX3yZ5'
|
|
11063
|
-
*
|
|
11064
|
-
* const addressResult = base58StringSchema.safeParse(validAddress)
|
|
11065
|
-
* const txHashResult = base58StringSchema.safeParse(validTxHash)
|
|
11066
|
-
* console.log(addressResult.success) // true
|
|
11067
|
-
* console.log(txHashResult.success) // true
|
|
11068
|
-
* ```
|
|
11069
|
-
*/
|
|
11070
|
-
const base58StringSchema = z
|
|
11071
|
-
.string()
|
|
11072
|
-
.min(1, 'Base58 string is required')
|
|
11073
|
-
.refine((value) => value.trim().length > 0, 'Base58 string cannot be empty')
|
|
11074
|
-
.refine((value) => {
|
|
11075
|
-
// Base58 alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
|
|
11076
|
-
// Excludes: 0, O, I, l to avoid confusion
|
|
11077
|
-
const base58Pattern = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/;
|
|
11078
|
-
return base58Pattern.test(value);
|
|
11079
|
-
}, 'Base58 string contains invalid characters. Only base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z) are allowed');
|
|
11080
|
-
/**
|
|
11081
|
-
* Schema for validating Solana addresses.
|
|
11082
|
-
*
|
|
11083
|
-
* This schema validates that a string is a properly formatted Solana address:
|
|
11084
|
-
* - Must be a valid base58-encoded string
|
|
11085
|
-
* - Must be between 32-44 characters long (typical length for base58-encoded 32-byte addresses)
|
|
11086
|
-
*
|
|
11087
|
-
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
11088
|
-
*
|
|
11089
|
-
* @example
|
|
11090
|
-
* ```typescript
|
|
11091
|
-
* import { solanaAddressSchema } from '@core/adapter'
|
|
11092
|
-
*
|
|
11093
|
-
* const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
|
|
11094
|
-
*
|
|
11095
|
-
* const result = solanaAddressSchema.safeParse(validAddress)
|
|
11096
|
-
* console.log(result.success) // true
|
|
11097
|
-
* ```
|
|
11098
|
-
*/
|
|
11099
|
-
base58StringSchema.refine((value) => value.length >= 32 && value.length <= 44, 'Solana address must be between 32-44 characters long (base58-encoded 32-byte address)');
|
|
11100
|
-
/**
|
|
11101
|
-
* Schema for validating Solana transaction hashes.
|
|
11102
|
-
*
|
|
11103
|
-
* This schema validates that a string is a properly formatted Solana transaction hash:
|
|
11104
|
-
* - Must be a valid base58-encoded string
|
|
11105
|
-
* - Must be between 86-88 characters long (typical length for base58-encoded 64-byte signatures)
|
|
11106
|
-
*
|
|
11107
|
-
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
11108
|
-
*
|
|
11109
|
-
* @example
|
|
11110
|
-
* ```typescript
|
|
11111
|
-
* import { solanaTransactionHashSchema } from '@core/adapter'
|
|
11112
|
-
*
|
|
11113
|
-
* const validTxHash = '5VfYmGBjvQKe3xgLtTQPSMEUdpEVHrJwLK7pKBJWKzYpNBE2g3kJrq7RSe9M8DqzQJ5J2aZPTjHLvd4WgxPpJKS'
|
|
11114
|
-
*
|
|
11115
|
-
* const result = solanaTransactionHashSchema.safeParse(validTxHash)
|
|
11116
|
-
* console.log(result.success) // true
|
|
11117
|
-
* ```
|
|
11118
|
-
*/
|
|
11119
|
-
base58StringSchema.refine((value) => value.length >= 86 && value.length <= 88, 'Solana transaction hash must be between 86-88 characters long (base58-encoded 64-byte signature)');
|
|
11120
|
-
/**
|
|
11121
|
-
* Schema for validating Adapter objects.
|
|
11122
|
-
* Checks for the required methods that define an Adapter.
|
|
11123
|
-
*/
|
|
11124
|
-
z.object({
|
|
11125
|
-
prepare: z.function(),
|
|
11126
|
-
waitForTransaction: z.function(),
|
|
11127
|
-
getAddress: z.function(),
|
|
11128
|
-
});
|
|
11129
|
-
|
|
11130
|
-
/**
|
|
11131
|
-
* Validate that the adapter has sufficient token balance for a transaction.
|
|
11132
|
-
*
|
|
11133
|
-
* This function checks if the adapter's current token balance is greater than or equal
|
|
11134
|
-
* to the requested transaction amount. It throws a KitError with code 9001
|
|
11135
|
-
* (BALANCE_INSUFFICIENT_TOKEN) if the balance is insufficient, providing detailed
|
|
11136
|
-
* information about the shortfall.
|
|
11137
|
-
*
|
|
11138
|
-
* @param params - The validation parameters containing adapter, amount, token, and token address.
|
|
11139
|
-
* @returns A promise that resolves to void if validation passes.
|
|
11140
|
-
* @throws {KitError} When the adapter's balance is less than the required amount (code: 9001).
|
|
11141
|
-
*
|
|
11142
|
-
* @example
|
|
11143
|
-
* ```typescript
|
|
11144
|
-
* import { validateBalanceForTransaction } from '@core/adapter'
|
|
11145
|
-
* import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
|
|
11146
|
-
* import { isKitError, ERROR_TYPES } from '@core/errors'
|
|
11147
|
-
*
|
|
11148
|
-
* const adapter = createViemAdapterFromPrivateKey({
|
|
11149
|
-
* privateKey: '0x...',
|
|
11150
|
-
* chain: 'Ethereum',
|
|
11151
|
-
* })
|
|
11152
|
-
*
|
|
11153
|
-
* try {
|
|
11154
|
-
* await validateBalanceForTransaction({
|
|
11155
|
-
* adapter,
|
|
11156
|
-
* amount: '1000000', // 1 USDC (6 decimals)
|
|
11157
|
-
* token: 'USDC',
|
|
11158
|
-
* tokenAddress: '0xA0b86a33E6441c8C1c7C16e4c5e3e5b5e4c5e3e5b5e4c5e',
|
|
11159
|
-
* operationContext: { chain: 'Ethereum' },
|
|
11160
|
-
* })
|
|
11161
|
-
* console.log('Balance validation passed')
|
|
11162
|
-
* } catch (error) {
|
|
11163
|
-
* if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
|
|
11164
|
-
* console.error('Insufficient funds:', error.message)
|
|
11165
|
-
* }
|
|
11166
|
-
* }
|
|
11167
|
-
* ```
|
|
11168
|
-
*/
|
|
11169
|
-
const validateBalanceForTransaction = async (params) => {
|
|
11170
|
-
const { amount, adapter, token, tokenAddress, operationContext } = params;
|
|
11171
|
-
const balancePrepared = await adapter.prepareAction('usdc.balanceOf', {
|
|
11172
|
-
walletAddress: operationContext.address,
|
|
11173
|
-
}, operationContext);
|
|
11174
|
-
const balance = await balancePrepared.execute();
|
|
11175
|
-
if (BigInt(balance) < BigInt(amount)) {
|
|
11176
|
-
// Extract chain name from operationContext
|
|
11177
|
-
const chainName = extractChainInfo(operationContext.chain).name;
|
|
11178
|
-
// Create KitError with rich context in trace
|
|
11179
|
-
throw createInsufficientTokenBalanceError(chainName, token, {
|
|
11180
|
-
balance: balance.toString(),
|
|
11181
|
-
amount,
|
|
11182
|
-
tokenAddress,
|
|
11183
|
-
walletAddress: operationContext.address,
|
|
11184
|
-
});
|
|
11185
|
-
}
|
|
11186
|
-
};
|
|
11187
|
-
|
|
12538
|
+
const hexPattern = /^0x[0-9a-fA-F]+$/;
|
|
12539
|
+
return hexPattern.test(value);
|
|
12540
|
+
}, 'Hex string contains invalid characters. Only hexadecimal characters (0-9, a-f, A-F) are allowed after 0x');
|
|
11188
12541
|
/**
|
|
11189
|
-
*
|
|
12542
|
+
* Schema for validating EVM addresses.
|
|
11190
12543
|
*
|
|
11191
|
-
* This
|
|
11192
|
-
*
|
|
11193
|
-
*
|
|
12544
|
+
* This schema validates that a string is a properly formatted EVM address:
|
|
12545
|
+
* - Must be a valid hex string with '0x' prefix
|
|
12546
|
+
* - Must be exactly 42 characters long (0x + 40 hex characters)
|
|
11194
12547
|
*
|
|
11195
|
-
* @
|
|
11196
|
-
* @returns A promise that resolves to void if validation passes.
|
|
11197
|
-
* @throws {KitError} When the adapter's native balance is zero (code: 9002).
|
|
12548
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
11198
12549
|
*
|
|
11199
12550
|
* @example
|
|
11200
12551
|
* ```typescript
|
|
11201
|
-
* import {
|
|
11202
|
-
* import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
|
|
11203
|
-
* import { isKitError, ERROR_TYPES } from '@core/errors'
|
|
12552
|
+
* import { evmAddressSchema } from '@core/adapter'
|
|
11204
12553
|
*
|
|
11205
|
-
* const
|
|
11206
|
-
* privateKey: '0x...',
|
|
11207
|
-
* chain: 'Ethereum',
|
|
11208
|
-
* })
|
|
12554
|
+
* const validAddress = '0x1234567890123456789012345678901234567890'
|
|
11209
12555
|
*
|
|
11210
|
-
*
|
|
11211
|
-
*
|
|
11212
|
-
* adapter,
|
|
11213
|
-
* operationContext: { chain: 'Ethereum' },
|
|
11214
|
-
* })
|
|
11215
|
-
* console.log('Native balance validation passed')
|
|
11216
|
-
* } catch (error) {
|
|
11217
|
-
* if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
|
|
11218
|
-
* console.error('Insufficient gas funds:', error.message)
|
|
11219
|
-
* }
|
|
11220
|
-
* }
|
|
12556
|
+
* const result = evmAddressSchema.safeParse(validAddress)
|
|
12557
|
+
* console.log(result.success) // true
|
|
11221
12558
|
* ```
|
|
11222
12559
|
*/
|
|
11223
|
-
|
|
11224
|
-
const { adapter, operationContext } = params;
|
|
11225
|
-
const balancePrepared = await adapter.prepareAction('native.balanceOf', {
|
|
11226
|
-
walletAddress: operationContext.address,
|
|
11227
|
-
}, operationContext);
|
|
11228
|
-
const balance = await balancePrepared.execute();
|
|
11229
|
-
if (BigInt(balance) === 0n) {
|
|
11230
|
-
// Extract chain name from operationContext
|
|
11231
|
-
const chainName = extractChainInfo(operationContext.chain).name;
|
|
11232
|
-
// Create KitError with rich context in trace
|
|
11233
|
-
throw createInsufficientGasError(chainName, {
|
|
11234
|
-
balance: '0',
|
|
11235
|
-
walletAddress: operationContext.address,
|
|
11236
|
-
});
|
|
11237
|
-
}
|
|
11238
|
-
};
|
|
11239
|
-
|
|
12560
|
+
hexStringSchema.refine((value) => value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)');
|
|
11240
12561
|
/**
|
|
11241
|
-
*
|
|
12562
|
+
* Schema for validating transaction hashes.
|
|
11242
12563
|
*
|
|
11243
|
-
*
|
|
11244
|
-
*
|
|
12564
|
+
* This schema validates that a string is a properly formatted transaction hash:
|
|
12565
|
+
* - Must be a valid hex string with '0x' prefix
|
|
12566
|
+
* - Must be exactly 66 characters long (0x + 64 hex characters)
|
|
11245
12567
|
*
|
|
11246
|
-
* @
|
|
11247
|
-
* - NONE: No permit, tokens must be pre-approved via separate transaction
|
|
11248
|
-
* - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
|
|
11249
|
-
*/
|
|
11250
|
-
var PermitType;
|
|
11251
|
-
(function (PermitType) {
|
|
11252
|
-
/** No permit required - tokens must be pre-approved */
|
|
11253
|
-
PermitType[PermitType["NONE"] = 0] = "NONE";
|
|
11254
|
-
/** EIP-2612 standard permit */
|
|
11255
|
-
PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
|
|
11256
|
-
})(PermitType || (PermitType = {}));
|
|
11257
|
-
|
|
11258
|
-
/**
|
|
11259
|
-
* CCTP bridge step names that can occur in the bridging flow.
|
|
12568
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
11260
12569
|
*
|
|
11261
|
-
*
|
|
11262
|
-
*
|
|
11263
|
-
*
|
|
11264
|
-
*/
|
|
11265
|
-
const CCTPv2StepName = {
|
|
11266
|
-
approve: 'approve',
|
|
11267
|
-
burn: 'burn',
|
|
11268
|
-
fetchAttestation: 'fetchAttestation',
|
|
11269
|
-
mint: 'mint',
|
|
11270
|
-
reAttest: 'reAttest',
|
|
11271
|
-
};
|
|
11272
|
-
/**
|
|
11273
|
-
* Conditional step transition rules for CCTP bridge flow.
|
|
12570
|
+
* @example
|
|
12571
|
+
* ```typescript
|
|
12572
|
+
* import { evmTransactionHashSchema } from '@core/adapter'
|
|
11274
12573
|
*
|
|
11275
|
-
*
|
|
11276
|
-
*
|
|
12574
|
+
* const validTxHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
|
|
12575
|
+
*
|
|
12576
|
+
* const result = evmTransactionHashSchema.safeParse(validTxHash)
|
|
12577
|
+
* console.log(result.success) // true
|
|
12578
|
+
* ```
|
|
11277
12579
|
*/
|
|
11278
|
-
|
|
11279
|
-
// Starting state - no steps executed yet
|
|
11280
|
-
'': [
|
|
11281
|
-
{
|
|
11282
|
-
condition: () => true,
|
|
11283
|
-
nextStep: CCTPv2StepName.approve,
|
|
11284
|
-
reason: 'Start with approval step',
|
|
11285
|
-
isActionable: true,
|
|
11286
|
-
},
|
|
11287
|
-
],
|
|
11288
|
-
// After Approve step
|
|
11289
|
-
[CCTPv2StepName.approve]: [
|
|
11290
|
-
{
|
|
11291
|
-
condition: (ctx) => ctx.lastStep?.state === 'success',
|
|
11292
|
-
nextStep: CCTPv2StepName.burn,
|
|
11293
|
-
reason: 'Approval successful, proceed to burn',
|
|
11294
|
-
isActionable: true,
|
|
11295
|
-
},
|
|
11296
|
-
{
|
|
11297
|
-
condition: (ctx) => ctx.lastStep?.state === 'error',
|
|
11298
|
-
nextStep: CCTPv2StepName.approve,
|
|
11299
|
-
reason: 'Retry failed approval',
|
|
11300
|
-
isActionable: true,
|
|
11301
|
-
},
|
|
11302
|
-
{
|
|
11303
|
-
condition: (ctx) => ctx.lastStep?.state === 'noop',
|
|
11304
|
-
nextStep: CCTPv2StepName.burn,
|
|
11305
|
-
reason: 'No approval needed, proceed to burn',
|
|
11306
|
-
isActionable: true,
|
|
11307
|
-
},
|
|
11308
|
-
{
|
|
11309
|
-
condition: (ctx) => ctx.lastStep?.state === 'pending',
|
|
11310
|
-
nextStep: CCTPv2StepName.approve,
|
|
11311
|
-
reason: 'Continue pending approval',
|
|
11312
|
-
isActionable: false, // Waiting for pending transaction
|
|
11313
|
-
},
|
|
11314
|
-
],
|
|
11315
|
-
// After Burn step
|
|
11316
|
-
[CCTPv2StepName.burn]: [
|
|
11317
|
-
{
|
|
11318
|
-
condition: (ctx) => ctx.lastStep?.state === 'success',
|
|
11319
|
-
nextStep: CCTPv2StepName.fetchAttestation,
|
|
11320
|
-
reason: 'Burn successful, fetch attestation',
|
|
11321
|
-
isActionable: true,
|
|
11322
|
-
},
|
|
11323
|
-
{
|
|
11324
|
-
condition: (ctx) => ctx.lastStep?.state === 'error',
|
|
11325
|
-
nextStep: CCTPv2StepName.burn,
|
|
11326
|
-
reason: 'Retry failed burn',
|
|
11327
|
-
isActionable: true,
|
|
11328
|
-
},
|
|
11329
|
-
{
|
|
11330
|
-
condition: (ctx) => ctx.lastStep?.state === 'pending',
|
|
11331
|
-
nextStep: CCTPv2StepName.burn,
|
|
11332
|
-
reason: 'Continue pending burn',
|
|
11333
|
-
isActionable: false, // Waiting for pending transaction
|
|
11334
|
-
},
|
|
11335
|
-
],
|
|
11336
|
-
// After FetchAttestation step
|
|
11337
|
-
[CCTPv2StepName.fetchAttestation]: [
|
|
11338
|
-
{
|
|
11339
|
-
condition: (ctx) => ctx.lastStep?.state === 'success',
|
|
11340
|
-
nextStep: CCTPv2StepName.mint,
|
|
11341
|
-
reason: 'Attestation fetched, proceed to mint',
|
|
11342
|
-
isActionable: true,
|
|
11343
|
-
},
|
|
11344
|
-
{
|
|
11345
|
-
condition: (ctx) => ctx.lastStep?.state === 'error',
|
|
11346
|
-
nextStep: CCTPv2StepName.fetchAttestation,
|
|
11347
|
-
reason: 'Retry fetching attestation',
|
|
11348
|
-
isActionable: true,
|
|
11349
|
-
},
|
|
11350
|
-
{
|
|
11351
|
-
condition: (ctx) => ctx.lastStep?.state === 'pending',
|
|
11352
|
-
nextStep: CCTPv2StepName.fetchAttestation,
|
|
11353
|
-
reason: 'Continue pending attestation fetch',
|
|
11354
|
-
isActionable: false, // Waiting for attestation to be ready
|
|
11355
|
-
},
|
|
11356
|
-
],
|
|
11357
|
-
// After Mint step
|
|
11358
|
-
[CCTPv2StepName.mint]: [
|
|
11359
|
-
{
|
|
11360
|
-
condition: (ctx) => ctx.lastStep?.state === 'success',
|
|
11361
|
-
nextStep: null,
|
|
11362
|
-
reason: 'Bridge completed successfully',
|
|
11363
|
-
isActionable: false, // Nothing more to do
|
|
11364
|
-
},
|
|
11365
|
-
{
|
|
11366
|
-
condition: (ctx) => ctx.lastStep?.state === 'error',
|
|
11367
|
-
nextStep: CCTPv2StepName.mint,
|
|
11368
|
-
reason: 'Retry failed mint',
|
|
11369
|
-
isActionable: true,
|
|
11370
|
-
},
|
|
11371
|
-
{
|
|
11372
|
-
condition: (ctx) => ctx.lastStep?.state === 'pending',
|
|
11373
|
-
nextStep: CCTPv2StepName.mint,
|
|
11374
|
-
reason: 'Continue pending mint',
|
|
11375
|
-
isActionable: false, // Waiting for pending transaction
|
|
11376
|
-
},
|
|
11377
|
-
],
|
|
11378
|
-
// After ReAttest step
|
|
11379
|
-
[CCTPv2StepName.reAttest]: [
|
|
11380
|
-
{
|
|
11381
|
-
condition: (ctx) => ctx.lastStep?.state === 'success',
|
|
11382
|
-
nextStep: CCTPv2StepName.mint,
|
|
11383
|
-
reason: 'Re-attestation successful, proceed to mint',
|
|
11384
|
-
isActionable: true,
|
|
11385
|
-
},
|
|
11386
|
-
{
|
|
11387
|
-
condition: (ctx) => ctx.lastStep?.state === 'error',
|
|
11388
|
-
nextStep: CCTPv2StepName.mint,
|
|
11389
|
-
reason: 'Re-attestation failed, retry mint to re-initiate recovery',
|
|
11390
|
-
isActionable: true,
|
|
11391
|
-
},
|
|
11392
|
-
{
|
|
11393
|
-
condition: (ctx) => ctx.lastStep?.state === 'pending',
|
|
11394
|
-
nextStep: CCTPv2StepName.mint,
|
|
11395
|
-
reason: 'Re-attestation pending, retry mint to re-initiate recovery',
|
|
11396
|
-
isActionable: true,
|
|
11397
|
-
},
|
|
11398
|
-
],
|
|
11399
|
-
};
|
|
12580
|
+
hexStringSchema.refine((value) => value.length === 66, 'Transaction hash must be exactly 66 characters long (0x + 64 hex characters)');
|
|
11400
12581
|
/**
|
|
11401
|
-
*
|
|
11402
|
-
*
|
|
11403
|
-
* This function examines the current state of bridge steps to determine the optimal
|
|
11404
|
-
* continuation strategy. It uses a rule-based approach that makes it easy to extend
|
|
11405
|
-
* with new flow patterns and step types in the future.
|
|
11406
|
-
*
|
|
11407
|
-
* The current analysis supports the standard CCTP flow:
|
|
11408
|
-
* **Traditional flow**: Approve → Burn → FetchAttestation → Mint
|
|
11409
|
-
*
|
|
11410
|
-
* Key features:
|
|
11411
|
-
* - Rule-based transitions: Easy to extend with new step types and logic
|
|
11412
|
-
* - Context-aware decisions: Considers execution history and step states
|
|
11413
|
-
* - Actionable logic: Distinguishes between steps requiring user action vs waiting
|
|
11414
|
-
* - Terminal states: Properly handles completion and non-actionable states
|
|
11415
|
-
*
|
|
11416
|
-
* @param bridgeResult - The bridge result containing step execution history.
|
|
11417
|
-
* @returns Analysis result with continuation step and actionability information.
|
|
11418
|
-
* @throws Error when bridgeResult is invalid or contains no steps array.
|
|
12582
|
+
* Schema for validating base58-encoded strings.
|
|
11419
12583
|
*
|
|
11420
|
-
*
|
|
11421
|
-
*
|
|
11422
|
-
*
|
|
12584
|
+
* This schema validates that a string:
|
|
12585
|
+
* - Is a string type
|
|
12586
|
+
* - Is not empty after trimming
|
|
12587
|
+
* - Contains only valid base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z)
|
|
12588
|
+
* - Does not contain commonly confused characters (0, O, I, l)
|
|
11423
12589
|
*
|
|
11424
|
-
*
|
|
11425
|
-
*
|
|
11426
|
-
*
|
|
11427
|
-
* { name: 'Approve', state: 'error', errorMessage: 'User rejected' }
|
|
11428
|
-
* ]
|
|
11429
|
-
* }
|
|
12590
|
+
* @remarks
|
|
12591
|
+
* This schema does not validate length, making it suitable for various base58-encoded data
|
|
12592
|
+
* like Solana addresses, transaction signatures, and other base58-encoded data.
|
|
11430
12593
|
*
|
|
11431
|
-
*
|
|
11432
|
-
* // Result: { continuationStep: 'Approve', isRetryable: true,
|
|
11433
|
-
* // reason: 'Retry failed approval' }
|
|
11434
|
-
* ```
|
|
12594
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
11435
12595
|
*
|
|
11436
12596
|
* @example
|
|
11437
12597
|
* ```typescript
|
|
11438
|
-
*
|
|
11439
|
-
* const bridgeResult = {
|
|
11440
|
-
* steps: [
|
|
11441
|
-
* { name: 'Approve', state: 'pending' }
|
|
11442
|
-
* ]
|
|
11443
|
-
* }
|
|
12598
|
+
* import { base58StringSchema } from '@core/adapter'
|
|
11444
12599
|
*
|
|
11445
|
-
* const
|
|
11446
|
-
*
|
|
11447
|
-
*
|
|
12600
|
+
* const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
|
|
12601
|
+
* const validTxHash = '3Jf8k2L5mN9pQ7rS1tV4wX6yZ8aB2cD4eF5gH7iJ9kL1mN3oP5qR7sT9uV1wX3yZ5'
|
|
12602
|
+
*
|
|
12603
|
+
* const addressResult = base58StringSchema.safeParse(validAddress)
|
|
12604
|
+
* const txHashResult = base58StringSchema.safeParse(validTxHash)
|
|
12605
|
+
* console.log(addressResult.success) // true
|
|
12606
|
+
* console.log(txHashResult.success) // true
|
|
11448
12607
|
* ```
|
|
12608
|
+
*/
|
|
12609
|
+
const base58StringSchema = z
|
|
12610
|
+
.string()
|
|
12611
|
+
.min(1, 'Base58 string is required')
|
|
12612
|
+
.refine((value) => value.trim().length > 0, 'Base58 string cannot be empty')
|
|
12613
|
+
.refine((value) => {
|
|
12614
|
+
// Base58 alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
|
|
12615
|
+
// Excludes: 0, O, I, l to avoid confusion
|
|
12616
|
+
const base58Pattern = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/;
|
|
12617
|
+
return base58Pattern.test(value);
|
|
12618
|
+
}, 'Base58 string contains invalid characters. Only base58 characters (1-9, A-H, J-N, P-Z, a-k, m-z) are allowed');
|
|
12619
|
+
/**
|
|
12620
|
+
* Schema for validating Solana addresses.
|
|
12621
|
+
*
|
|
12622
|
+
* This schema validates that a string is a properly formatted Solana address:
|
|
12623
|
+
* - Must be a valid base58-encoded string
|
|
12624
|
+
* - Must be between 32-44 characters long (typical length for base58-encoded 32-byte addresses)
|
|
12625
|
+
*
|
|
12626
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
11449
12627
|
*
|
|
11450
12628
|
* @example
|
|
11451
12629
|
* ```typescript
|
|
11452
|
-
*
|
|
11453
|
-
* const bridgeResult = {
|
|
11454
|
-
* steps: [
|
|
11455
|
-
* { name: 'Approve', state: 'success' },
|
|
11456
|
-
* { name: 'Burn', state: 'success' },
|
|
11457
|
-
* { name: 'FetchAttestation', state: 'success' },
|
|
11458
|
-
* { name: 'Mint', state: 'success' }
|
|
11459
|
-
* ]
|
|
11460
|
-
* }
|
|
12630
|
+
* import { solanaAddressSchema } from '@core/adapter'
|
|
11461
12631
|
*
|
|
11462
|
-
* const
|
|
11463
|
-
* // Result: { continuationStep: null, isRetryable: false,
|
|
11464
|
-
* // reason: 'Bridge completed successfully' }
|
|
11465
|
-
* ```
|
|
11466
|
-
*/
|
|
11467
|
-
const analyzeSteps = (bridgeResult) => {
|
|
11468
|
-
// Input validation
|
|
11469
|
-
if (!bridgeResult || !Array.isArray(bridgeResult.steps)) {
|
|
11470
|
-
throw new Error('Invalid bridgeResult: must contain a steps array');
|
|
11471
|
-
}
|
|
11472
|
-
const { steps } = bridgeResult;
|
|
11473
|
-
// Build execution context from step history
|
|
11474
|
-
const context = buildFlowContext(steps);
|
|
11475
|
-
// Determine continuation logic using rule engine
|
|
11476
|
-
const continuation = determineContinuationFromRules(context);
|
|
11477
|
-
return {
|
|
11478
|
-
continuationStep: continuation.nextStep,
|
|
11479
|
-
isActionable: continuation.isActionable,
|
|
11480
|
-
completedSteps: Array.from(context.completedSteps),
|
|
11481
|
-
failedSteps: Array.from(context.failedSteps),
|
|
11482
|
-
reason: continuation.reason,
|
|
11483
|
-
};
|
|
11484
|
-
};
|
|
11485
|
-
/**
|
|
11486
|
-
* Build flow context from the execution history.
|
|
12632
|
+
* const validAddress = 'DhzPkKCLJGHBZbs1AzmK2tRNLZkV8J3yWF3LuWMuKJpN'
|
|
11487
12633
|
*
|
|
11488
|
-
*
|
|
11489
|
-
*
|
|
12634
|
+
* const result = solanaAddressSchema.safeParse(validAddress)
|
|
12635
|
+
* console.log(result.success) // true
|
|
12636
|
+
* ```
|
|
11490
12637
|
*/
|
|
11491
|
-
|
|
11492
|
-
const completedSteps = new Set();
|
|
11493
|
-
const failedSteps = new Set();
|
|
11494
|
-
let lastStep;
|
|
11495
|
-
// Process step history to build context
|
|
11496
|
-
for (const step of steps) {
|
|
11497
|
-
if (step.state === 'success' || step.state === 'noop') {
|
|
11498
|
-
completedSteps.add(step.name);
|
|
11499
|
-
}
|
|
11500
|
-
else if (step.state === 'error') {
|
|
11501
|
-
failedSteps.add(step.name);
|
|
11502
|
-
}
|
|
11503
|
-
// Track the last step for continuation logic
|
|
11504
|
-
lastStep = {
|
|
11505
|
-
name: step.name,
|
|
11506
|
-
state: step.state,
|
|
11507
|
-
};
|
|
11508
|
-
}
|
|
11509
|
-
return {
|
|
11510
|
-
completedSteps,
|
|
11511
|
-
failedSteps,
|
|
11512
|
-
...(lastStep && { lastStep }),
|
|
11513
|
-
};
|
|
11514
|
-
}
|
|
12638
|
+
base58StringSchema.refine((value) => value.length >= 32 && value.length <= 44, 'Solana address must be between 32-44 characters long (base58-encoded 32-byte address)');
|
|
11515
12639
|
/**
|
|
11516
|
-
*
|
|
12640
|
+
* Schema for validating Solana transaction hashes.
|
|
11517
12641
|
*
|
|
11518
|
-
*
|
|
11519
|
-
*
|
|
11520
|
-
|
|
11521
|
-
function determineContinuationFromRules(context) {
|
|
11522
|
-
const lastStepName = context.lastStep?.name;
|
|
11523
|
-
// Handle initial state when no steps have been executed
|
|
11524
|
-
if (lastStepName === undefined) {
|
|
11525
|
-
const rules = STEP_TRANSITION_RULES[''];
|
|
11526
|
-
const matchingRule = rules?.find((rule) => rule.condition(context));
|
|
11527
|
-
if (!matchingRule) {
|
|
11528
|
-
return {
|
|
11529
|
-
nextStep: null,
|
|
11530
|
-
isActionable: false,
|
|
11531
|
-
reason: 'No initial state rule found',
|
|
11532
|
-
};
|
|
11533
|
-
}
|
|
11534
|
-
return {
|
|
11535
|
-
nextStep: matchingRule.nextStep,
|
|
11536
|
-
isActionable: matchingRule.isActionable,
|
|
11537
|
-
reason: matchingRule.reason,
|
|
11538
|
-
};
|
|
11539
|
-
}
|
|
11540
|
-
// A step with an empty name is ambiguous and should be treated as an unrecoverable state.
|
|
11541
|
-
if (lastStepName === '') {
|
|
11542
|
-
return {
|
|
11543
|
-
nextStep: null,
|
|
11544
|
-
isActionable: false,
|
|
11545
|
-
reason: 'No transition rules defined for step with empty name',
|
|
11546
|
-
};
|
|
11547
|
-
}
|
|
11548
|
-
const rules = STEP_TRANSITION_RULES[lastStepName];
|
|
11549
|
-
if (!rules) {
|
|
11550
|
-
return {
|
|
11551
|
-
nextStep: null,
|
|
11552
|
-
isActionable: false,
|
|
11553
|
-
reason: `No transition rules defined for step: ${lastStepName}`,
|
|
11554
|
-
};
|
|
11555
|
-
}
|
|
11556
|
-
// Find the first matching rule
|
|
11557
|
-
const matchingRule = rules.find((rule) => rule.condition(context));
|
|
11558
|
-
if (!matchingRule) {
|
|
11559
|
-
return {
|
|
11560
|
-
nextStep: null,
|
|
11561
|
-
isActionable: false,
|
|
11562
|
-
reason: `No matching transition rule for current context`,
|
|
11563
|
-
};
|
|
11564
|
-
}
|
|
11565
|
-
return {
|
|
11566
|
-
nextStep: matchingRule.nextStep,
|
|
11567
|
-
isActionable: matchingRule.isActionable,
|
|
11568
|
-
reason: matchingRule.reason,
|
|
11569
|
-
};
|
|
11570
|
-
}
|
|
11571
|
-
|
|
11572
|
-
/**
|
|
11573
|
-
* Find a step by name in the bridge result.
|
|
12642
|
+
* This schema validates that a string is a properly formatted Solana transaction hash:
|
|
12643
|
+
* - Must be a valid base58-encoded string
|
|
12644
|
+
* - Must be between 86-88 characters long (typical length for base58-encoded 64-byte signatures)
|
|
11574
12645
|
*
|
|
11575
|
-
* @
|
|
11576
|
-
* @param stepName - The name of the step to find.
|
|
11577
|
-
* @returns The step if found, undefined otherwise.
|
|
12646
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
11578
12647
|
*
|
|
11579
12648
|
* @example
|
|
11580
12649
|
* ```typescript
|
|
11581
|
-
* import {
|
|
12650
|
+
* import { solanaTransactionHashSchema } from '@core/adapter'
|
|
11582
12651
|
*
|
|
11583
|
-
* const
|
|
11584
|
-
*
|
|
11585
|
-
*
|
|
11586
|
-
*
|
|
12652
|
+
* const validTxHash = '5VfYmGBjvQKe3xgLtTQPSMEUdpEVHrJwLK7pKBJWKzYpNBE2g3kJrq7RSe9M8DqzQJ5J2aZPTjHLvd4WgxPpJKS'
|
|
12653
|
+
*
|
|
12654
|
+
* const result = solanaTransactionHashSchema.safeParse(validTxHash)
|
|
12655
|
+
* console.log(result.success) // true
|
|
11587
12656
|
* ```
|
|
11588
12657
|
*/
|
|
11589
|
-
|
|
11590
|
-
return result.steps.find((step) => step.name === stepName);
|
|
11591
|
-
}
|
|
12658
|
+
base58StringSchema.refine((value) => value.length >= 86 && value.length <= 88, 'Solana transaction hash must be between 86-88 characters long (base58-encoded 64-byte signature)');
|
|
11592
12659
|
/**
|
|
11593
|
-
*
|
|
12660
|
+
* Schema for validating Adapter objects.
|
|
12661
|
+
* Checks for the required methods that define an Adapter.
|
|
12662
|
+
*/
|
|
12663
|
+
z.object({
|
|
12664
|
+
prepare: z.function(),
|
|
12665
|
+
waitForTransaction: z.function(),
|
|
12666
|
+
getAddress: z.function(),
|
|
12667
|
+
});
|
|
12668
|
+
|
|
12669
|
+
/**
|
|
12670
|
+
* Validate that the adapter has sufficient token balance for a transaction.
|
|
11594
12671
|
*
|
|
11595
|
-
*
|
|
12672
|
+
* This function checks if the adapter's current token balance is greater than or equal
|
|
12673
|
+
* to the requested transaction amount. It throws a KitError with code 9001
|
|
12674
|
+
* (BALANCE_INSUFFICIENT_TOKEN) if the balance is insufficient, providing detailed
|
|
12675
|
+
* information about the shortfall.
|
|
11596
12676
|
*
|
|
11597
|
-
* @param
|
|
11598
|
-
* @
|
|
11599
|
-
* @
|
|
11600
|
-
* @throws KitError if the specified pending step is not found.
|
|
12677
|
+
* @param params - The validation parameters containing adapter, amount, token, and token address.
|
|
12678
|
+
* @returns A promise that resolves to void if validation passes.
|
|
12679
|
+
* @throws {KitError} When the adapter's balance is less than the required amount (code: 9001).
|
|
11601
12680
|
*
|
|
11602
12681
|
* @example
|
|
11603
12682
|
* ```typescript
|
|
11604
|
-
* import {
|
|
12683
|
+
* import { validateBalanceForTransaction } from '@core/adapter'
|
|
12684
|
+
* import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
|
|
12685
|
+
* import { isKitError, ERROR_TYPES } from '@core/errors'
|
|
11605
12686
|
*
|
|
11606
|
-
* const
|
|
11607
|
-
*
|
|
12687
|
+
* const adapter = createViemAdapterFromPrivateKey({
|
|
12688
|
+
* privateKey: '0x...',
|
|
12689
|
+
* chain: 'Ethereum',
|
|
12690
|
+
* })
|
|
12691
|
+
*
|
|
12692
|
+
* try {
|
|
12693
|
+
* await validateBalanceForTransaction({
|
|
12694
|
+
* adapter,
|
|
12695
|
+
* amount: '1000000', // 1 USDC (6 decimals)
|
|
12696
|
+
* token: 'USDC',
|
|
12697
|
+
* tokenAddress: '0xA0b86a33E6441c8C1c7C16e4c5e3e5b5e4c5e3e5b5e4c5e',
|
|
12698
|
+
* operationContext: { chain: 'Ethereum' },
|
|
12699
|
+
* })
|
|
12700
|
+
* console.log('Balance validation passed')
|
|
12701
|
+
* } catch (error) {
|
|
12702
|
+
* if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
|
|
12703
|
+
* console.error('Insufficient funds:', error.message)
|
|
12704
|
+
* }
|
|
12705
|
+
* }
|
|
11608
12706
|
* ```
|
|
11609
12707
|
*/
|
|
11610
|
-
|
|
11611
|
-
const
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
12708
|
+
const validateBalanceForTransaction = async (params) => {
|
|
12709
|
+
const { amount, adapter, token, tokenAddress, operationContext } = params;
|
|
12710
|
+
const balancePrepared = await adapter.prepareAction('usdc.balanceOf', {
|
|
12711
|
+
walletAddress: operationContext.address,
|
|
12712
|
+
}, operationContext);
|
|
12713
|
+
const balance = await balancePrepared.execute();
|
|
12714
|
+
if (BigInt(balance) < BigInt(amount)) {
|
|
12715
|
+
// Extract chain name from operationContext
|
|
12716
|
+
const chainName = extractChainInfo(operationContext.chain).name;
|
|
12717
|
+
// Create KitError with rich context in trace
|
|
12718
|
+
throw createInsufficientTokenBalanceError(chainName, token, {
|
|
12719
|
+
balance: balance.toString(),
|
|
12720
|
+
amount,
|
|
12721
|
+
tokenAddress,
|
|
12722
|
+
walletAddress: operationContext.address,
|
|
11625
12723
|
});
|
|
11626
12724
|
}
|
|
11627
|
-
|
|
11628
|
-
|
|
12725
|
+
};
|
|
12726
|
+
|
|
11629
12727
|
/**
|
|
11630
|
-
*
|
|
12728
|
+
* Validate that the adapter has sufficient native token balance for transaction fees.
|
|
11631
12729
|
*
|
|
11632
|
-
*
|
|
11633
|
-
*
|
|
12730
|
+
* This function checks if the adapter's current native token balance (ETH, SOL, etc.)
|
|
12731
|
+
* is greater than zero. It throws a KitError with code 9002 (BALANCE_INSUFFICIENT_GAS)
|
|
12732
|
+
* if the balance is zero, indicating the wallet cannot pay for transaction fees.
|
|
12733
|
+
*
|
|
12734
|
+
* @param params - The validation parameters containing adapter and operation context.
|
|
12735
|
+
* @returns A promise that resolves to void if validation passes.
|
|
12736
|
+
* @throws {KitError} When the adapter's native balance is zero (code: 9002).
|
|
11634
12737
|
*
|
|
11635
12738
|
* @example
|
|
11636
12739
|
* ```typescript
|
|
11637
|
-
* import {
|
|
12740
|
+
* import { validateNativeBalanceForTransaction } from '@core/adapter'
|
|
12741
|
+
* import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
|
|
12742
|
+
* import { isKitError, ERROR_TYPES } from '@core/errors'
|
|
11638
12743
|
*
|
|
11639
|
-
* const
|
|
11640
|
-
*
|
|
11641
|
-
*
|
|
12744
|
+
* const adapter = createViemAdapterFromPrivateKey({
|
|
12745
|
+
* privateKey: '0x...',
|
|
12746
|
+
* chain: 'Ethereum',
|
|
12747
|
+
* })
|
|
12748
|
+
*
|
|
12749
|
+
* try {
|
|
12750
|
+
* await validateNativeBalanceForTransaction({
|
|
12751
|
+
* adapter,
|
|
12752
|
+
* operationContext: { chain: 'Ethereum' },
|
|
12753
|
+
* })
|
|
12754
|
+
* console.log('Native balance validation passed')
|
|
12755
|
+
* } catch (error) {
|
|
12756
|
+
* if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
|
|
12757
|
+
* console.error('Insufficient gas funds:', error.message)
|
|
12758
|
+
* }
|
|
11642
12759
|
* }
|
|
11643
12760
|
* ```
|
|
11644
12761
|
*/
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
12762
|
+
const validateNativeBalanceForTransaction = async (params) => {
|
|
12763
|
+
const { adapter, operationContext } = params;
|
|
12764
|
+
const balancePrepared = await adapter.prepareAction('native.balanceOf', {
|
|
12765
|
+
walletAddress: operationContext.address,
|
|
12766
|
+
}, operationContext);
|
|
12767
|
+
const balance = await balancePrepared.execute();
|
|
12768
|
+
if (BigInt(balance) === 0n) {
|
|
12769
|
+
const { chain } = operationContext;
|
|
12770
|
+
const chainName = extractChainInfo(chain).name;
|
|
12771
|
+
const nativeSymbol = typeof chain === 'object' && chain !== null && 'nativeCurrency' in chain
|
|
12772
|
+
? chain.nativeCurrency.symbol
|
|
12773
|
+
: undefined;
|
|
12774
|
+
throw createInsufficientGasError(chainName, nativeSymbol, {
|
|
12775
|
+
balance: '0',
|
|
12776
|
+
walletAddress: operationContext.address,
|
|
12777
|
+
});
|
|
12778
|
+
}
|
|
12779
|
+
};
|
|
12780
|
+
|
|
11648
12781
|
/**
|
|
11649
|
-
*
|
|
11650
|
-
*
|
|
11651
|
-
* @param result - The bridge result.
|
|
11652
|
-
* @returns The attestation data, or undefined if not found.
|
|
12782
|
+
* Permit signature standards for gasless token approvals.
|
|
11653
12783
|
*
|
|
11654
|
-
*
|
|
11655
|
-
*
|
|
11656
|
-
* import { getAttestationData } from './findStep'
|
|
12784
|
+
* Defines the permit types that can be used to approve token spending
|
|
12785
|
+
* without requiring a separate approval transaction.
|
|
11657
12786
|
*
|
|
11658
|
-
*
|
|
11659
|
-
*
|
|
11660
|
-
*
|
|
11661
|
-
* }
|
|
11662
|
-
* ```
|
|
12787
|
+
* @remarks
|
|
12788
|
+
* - NONE: No permit, tokens must be pre-approved via separate transaction
|
|
12789
|
+
* - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
|
|
11663
12790
|
*/
|
|
11664
|
-
|
|
11665
|
-
|
|
11666
|
-
|
|
11667
|
-
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11671
|
-
const fetchStep = findStepByName(result, CCTPv2StepName.fetchAttestation);
|
|
11672
|
-
return fetchStep?.data;
|
|
11673
|
-
}
|
|
12791
|
+
var PermitType;
|
|
12792
|
+
(function (PermitType) {
|
|
12793
|
+
/** No permit required - tokens must be pre-approved */
|
|
12794
|
+
PermitType[PermitType["NONE"] = 0] = "NONE";
|
|
12795
|
+
/** EIP-2612 standard permit */
|
|
12796
|
+
PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
|
|
12797
|
+
})(PermitType || (PermitType = {}));
|
|
11674
12798
|
|
|
11675
12799
|
/**
|
|
11676
12800
|
* Determine if a step executes on the source chain.
|
|
@@ -11723,169 +12847,6 @@ function getStepAdapterAndChain(step, context, result) {
|
|
|
11723
12847
|
};
|
|
11724
12848
|
}
|
|
11725
12849
|
|
|
11726
|
-
/**
|
|
11727
|
-
* Check if the analysis indicates a non-actionable pending state.
|
|
11728
|
-
*
|
|
11729
|
-
* A pending state is non-actionable when there's a continuation step but
|
|
11730
|
-
* the analysis marks it as not actionable, typically because we need to
|
|
11731
|
-
* wait for an ongoing operation to complete.
|
|
11732
|
-
*
|
|
11733
|
-
* @param analysis - The step analysis result from analyzeSteps.
|
|
11734
|
-
* @param result - The bridge result to check for pending steps.
|
|
11735
|
-
* @returns True if there is a pending step that we should wait for.
|
|
11736
|
-
*
|
|
11737
|
-
* @example
|
|
11738
|
-
* ```typescript
|
|
11739
|
-
* import { hasPendingState } from './stepUtils'
|
|
11740
|
-
* import { analyzeSteps } from '../analyzeSteps'
|
|
11741
|
-
*
|
|
11742
|
-
* const analysis = analyzeSteps(bridgeResult)
|
|
11743
|
-
* if (hasPendingState(analysis, bridgeResult)) {
|
|
11744
|
-
* // Wait for the pending operation to complete
|
|
11745
|
-
* }
|
|
11746
|
-
* ```
|
|
11747
|
-
*/
|
|
11748
|
-
function hasPendingState(analysis, result) {
|
|
11749
|
-
// Check if there's a continuation step that's marked as non-actionable
|
|
11750
|
-
if (analysis.continuationStep === null || analysis.isActionable) {
|
|
11751
|
-
return false;
|
|
11752
|
-
}
|
|
11753
|
-
// Verify that the continuation step actually exists and is in pending state
|
|
11754
|
-
const pendingStep = result.steps.find((step) => step.name === analysis.continuationStep && step.state === 'pending');
|
|
11755
|
-
return pendingStep !== undefined;
|
|
11756
|
-
}
|
|
11757
|
-
/**
|
|
11758
|
-
* Check if the step is the last one in the execution flow.
|
|
11759
|
-
*
|
|
11760
|
-
* @param step - The step object to check.
|
|
11761
|
-
* @param stepNames - The ordered list of step names in the execution flow.
|
|
11762
|
-
* @returns True if this is the last step in the flow.
|
|
11763
|
-
*
|
|
11764
|
-
* @example
|
|
11765
|
-
* ```typescript
|
|
11766
|
-
* import { isLastStep } from './stepUtils'
|
|
11767
|
-
*
|
|
11768
|
-
* const stepNames = ['approve', 'burn', 'fetchAttestation', 'mint']
|
|
11769
|
-
* isLastStep({ name: 'mint' }, stepNames) // true
|
|
11770
|
-
* isLastStep({ name: 'burn' }, stepNames) // false
|
|
11771
|
-
* ```
|
|
11772
|
-
*/
|
|
11773
|
-
function isLastStep(step, stepNames) {
|
|
11774
|
-
const stepIndex = stepNames.indexOf(step.name);
|
|
11775
|
-
return stepIndex === -1 || stepIndex >= stepNames.length - 1;
|
|
11776
|
-
}
|
|
11777
|
-
/**
|
|
11778
|
-
* Wait for a pending transaction to complete.
|
|
11779
|
-
*
|
|
11780
|
-
* Poll the adapter until the transaction is confirmed on-chain and return
|
|
11781
|
-
* the updated step with success or error state based on the receipt.
|
|
11782
|
-
*
|
|
11783
|
-
* @param pendingStep - The full step object containing the transaction hash.
|
|
11784
|
-
* @param adapter - The adapter to use for waiting.
|
|
11785
|
-
* @param chain - The chain where the transaction was submitted.
|
|
11786
|
-
* @returns The updated step object with success or error state.
|
|
11787
|
-
*
|
|
11788
|
-
* @throws KitError when the pending step has no transaction hash.
|
|
11789
|
-
*
|
|
11790
|
-
* @example
|
|
11791
|
-
* ```typescript
|
|
11792
|
-
* import { waitForPendingTransaction } from './bridgeStepUtils'
|
|
11793
|
-
*
|
|
11794
|
-
* const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
|
|
11795
|
-
* const updatedStep = await waitForPendingTransaction(pendingStep, adapter, chain)
|
|
11796
|
-
* // updatedStep.state is now 'success' or 'error'
|
|
11797
|
-
* ```
|
|
11798
|
-
*/
|
|
11799
|
-
async function waitForPendingTransaction(pendingStep, adapter, chain) {
|
|
11800
|
-
if (!pendingStep.txHash) {
|
|
11801
|
-
throw new KitError({
|
|
11802
|
-
...InputError.VALIDATION_FAILED,
|
|
11803
|
-
recoverability: 'FATAL',
|
|
11804
|
-
message: `Cannot wait for pending ${pendingStep.name}: no transaction hash available`,
|
|
11805
|
-
});
|
|
11806
|
-
}
|
|
11807
|
-
const txHash = pendingStep.txHash;
|
|
11808
|
-
const txReceipt = await retryAsync(async () => adapter.waitForTransaction(txHash, undefined, chain), {
|
|
11809
|
-
isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
|
|
11810
|
-
});
|
|
11811
|
-
// Check if transaction was confirmed on-chain
|
|
11812
|
-
if (!txReceipt.blockNumber) {
|
|
11813
|
-
return {
|
|
11814
|
-
...pendingStep,
|
|
11815
|
-
state: 'error',
|
|
11816
|
-
errorMessage: txReceipt.status === 'reverted'
|
|
11817
|
-
? `Transaction ${pendingStep.txHash} was reverted`
|
|
11818
|
-
: 'Transaction was not confirmed on-chain',
|
|
11819
|
-
data: txReceipt,
|
|
11820
|
-
};
|
|
11821
|
-
}
|
|
11822
|
-
return {
|
|
11823
|
-
...pendingStep,
|
|
11824
|
-
state: 'success',
|
|
11825
|
-
data: txReceipt,
|
|
11826
|
-
};
|
|
11827
|
-
}
|
|
11828
|
-
/**
|
|
11829
|
-
* Wait for a pending step to complete.
|
|
11830
|
-
*
|
|
11831
|
-
* For transaction steps: waits for the transaction to be confirmed.
|
|
11832
|
-
* For attestation: re-executes the attestation fetch.
|
|
11833
|
-
*
|
|
11834
|
-
* @typeParam TFromAdapterCapabilities - The capabilities of the source adapter.
|
|
11835
|
-
* @typeParam TToAdapterCapabilities - The capabilities of the destination adapter.
|
|
11836
|
-
* @param pendingStep - The full step object (with name, state, txHash, data, etc.) to resolve.
|
|
11837
|
-
* @param adapter - The adapter to use.
|
|
11838
|
-
* @param chain - The chain where the step is executing.
|
|
11839
|
-
* @param context - The retry context.
|
|
11840
|
-
* @param result - The bridge result.
|
|
11841
|
-
* @param provider - The CCTP v2 bridging provider.
|
|
11842
|
-
* @returns The resolved step object with updated state.
|
|
11843
|
-
*
|
|
11844
|
-
* @throws KitError when fetching attestation but burn transaction hash is not found.
|
|
11845
|
-
*
|
|
11846
|
-
* @example
|
|
11847
|
-
* ```typescript
|
|
11848
|
-
* import { waitForStepToComplete } from './bridgeStepUtils'
|
|
11849
|
-
*
|
|
11850
|
-
* const pendingStep = { name: 'burn', state: 'pending', txHash: '0x123...' }
|
|
11851
|
-
* const updatedStep = await waitForStepToComplete(
|
|
11852
|
-
* pendingStep,
|
|
11853
|
-
* adapter,
|
|
11854
|
-
* chain,
|
|
11855
|
-
* context,
|
|
11856
|
-
* result,
|
|
11857
|
-
* provider,
|
|
11858
|
-
* )
|
|
11859
|
-
* // updatedStep.state is now 'success' or 'error'
|
|
11860
|
-
* ```
|
|
11861
|
-
*/
|
|
11862
|
-
async function waitForStepToComplete(pendingStep, adapter, chain, context, result, provider) {
|
|
11863
|
-
if (pendingStep.name === CCTPv2StepName.fetchAttestation) {
|
|
11864
|
-
// For attestation, re-run the fetch (it has built-in polling)
|
|
11865
|
-
const burnTxHash = getBurnTxHash(result);
|
|
11866
|
-
if (!burnTxHash) {
|
|
11867
|
-
throw new KitError({
|
|
11868
|
-
...InputError.VALIDATION_FAILED,
|
|
11869
|
-
recoverability: 'FATAL',
|
|
11870
|
-
message: 'Cannot fetch attestation: burn transaction hash not found',
|
|
11871
|
-
});
|
|
11872
|
-
}
|
|
11873
|
-
const sourceAddress = result.source.address;
|
|
11874
|
-
const attestation = await provider.fetchAttestation({
|
|
11875
|
-
chain: result.source.chain,
|
|
11876
|
-
adapter: context.from,
|
|
11877
|
-
address: sourceAddress,
|
|
11878
|
-
}, burnTxHash);
|
|
11879
|
-
return {
|
|
11880
|
-
...pendingStep,
|
|
11881
|
-
state: 'success',
|
|
11882
|
-
data: attestation,
|
|
11883
|
-
};
|
|
11884
|
-
}
|
|
11885
|
-
// For transaction steps, wait for the transaction to complete
|
|
11886
|
-
return waitForPendingTransaction(pendingStep, adapter, chain);
|
|
11887
|
-
}
|
|
11888
|
-
|
|
11889
12850
|
/**
|
|
11890
12851
|
* Executes a re-attestation operation to obtain a fresh attestation for an expired message.
|
|
11891
12852
|
*
|