@circle-fin/bridge-kit 1.4.0 → 1.6.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 +36 -0
- package/QUICKSTART.md +2 -2
- package/README.md +110 -13
- package/chains.cjs +261 -3
- package/chains.d.ts +236 -4
- package/chains.mjs +260 -4
- package/index.cjs +2043 -99
- package/index.d.ts +4148 -2106
- package/index.mjs +2037 -102
- package/package.json +4 -3
package/index.cjs
CHANGED
|
@@ -23,8 +23,13 @@ require('@ethersproject/bytes');
|
|
|
23
23
|
require('@ethersproject/address');
|
|
24
24
|
require('bs58');
|
|
25
25
|
var units = require('@ethersproject/units');
|
|
26
|
+
var pino = require('pino');
|
|
26
27
|
var providerCctpV2 = require('@circle-fin/provider-cctp-v2');
|
|
27
28
|
|
|
29
|
+
function _interopDefault (e) { return e && e.__esModule ? e.default : e; }
|
|
30
|
+
|
|
31
|
+
var pino__default = /*#__PURE__*/_interopDefault(pino);
|
|
32
|
+
|
|
28
33
|
/**
|
|
29
34
|
* Detect the runtime environment and return a shortened identifier.
|
|
30
35
|
*
|
|
@@ -122,6 +127,8 @@ const ERROR_TYPES = {
|
|
|
122
127
|
RPC: 'RPC',
|
|
123
128
|
/** Internet connectivity, DNS resolution, connection issues */
|
|
124
129
|
NETWORK: 'NETWORK',
|
|
130
|
+
/** Catch-all for unrecognized errors (code 0) */
|
|
131
|
+
UNKNOWN: 'UNKNOWN',
|
|
125
132
|
};
|
|
126
133
|
/**
|
|
127
134
|
* Array of valid error type values for validation.
|
|
@@ -135,6 +142,8 @@ const ERROR_TYPE_ARRAY = [...ERROR_TYPE_VALUES];
|
|
|
135
142
|
/**
|
|
136
143
|
* Error code ranges for validation.
|
|
137
144
|
* Single source of truth for valid error code ranges.
|
|
145
|
+
*
|
|
146
|
+
* Note: Code 0 is special - it's the UNKNOWN catch-all error.
|
|
138
147
|
*/
|
|
139
148
|
const ERROR_CODE_RANGES = [
|
|
140
149
|
{ min: 1000, max: 1999, type: 'INPUT' },
|
|
@@ -143,6 +152,8 @@ const ERROR_CODE_RANGES = [
|
|
|
143
152
|
{ min: 5000, max: 5999, type: 'ONCHAIN' },
|
|
144
153
|
{ min: 9000, max: 9999, type: 'BALANCE' },
|
|
145
154
|
];
|
|
155
|
+
/** Special code for UNKNOWN errors */
|
|
156
|
+
const UNKNOWN_ERROR_CODE = 0;
|
|
146
157
|
/**
|
|
147
158
|
* Zod schema for validating ErrorDetails objects.
|
|
148
159
|
*
|
|
@@ -181,6 +192,7 @@ const ERROR_CODE_RANGES = [
|
|
|
181
192
|
const errorDetailsSchema = zod.z.object({
|
|
182
193
|
/**
|
|
183
194
|
* Numeric identifier following standardized ranges:
|
|
195
|
+
* - 0: UNKNOWN - Catch-all for unrecognized errors
|
|
184
196
|
* - 1000-1999: INPUT errors - Parameter validation
|
|
185
197
|
* - 3000-3999: NETWORK errors - Connectivity issues
|
|
186
198
|
* - 4000-4999: RPC errors - Provider issues, gas estimation
|
|
@@ -190,8 +202,9 @@ const errorDetailsSchema = zod.z.object({
|
|
|
190
202
|
code: zod.z
|
|
191
203
|
.number()
|
|
192
204
|
.int('Error code must be an integer')
|
|
193
|
-
.refine((code) =>
|
|
194
|
-
|
|
205
|
+
.refine((code) => code === UNKNOWN_ERROR_CODE ||
|
|
206
|
+
ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
|
|
207
|
+
message: 'Error code must be 0 (UNKNOWN) or in valid ranges: 1000-1999 (INPUT), 3000-3999 (NETWORK), 4000-4999 (RPC), 5000-5999 (ONCHAIN), 9000-9999 (BALANCE)',
|
|
195
208
|
}),
|
|
196
209
|
/** Human-readable ID (e.g., "INPUT_NETWORK_MISMATCH", "BALANCE_INSUFFICIENT_TOKEN") */
|
|
197
210
|
name: zod.z
|
|
@@ -201,7 +214,7 @@ const errorDetailsSchema = zod.z.object({
|
|
|
201
214
|
/** Error category indicating where the error originated */
|
|
202
215
|
type: zod.z.enum(ERROR_TYPE_ARRAY, {
|
|
203
216
|
errorMap: () => ({
|
|
204
|
-
message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK',
|
|
217
|
+
message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK, UNKNOWN',
|
|
205
218
|
}),
|
|
206
219
|
}),
|
|
207
220
|
/** Error handling strategy */
|
|
@@ -418,6 +431,7 @@ class KitError extends Error {
|
|
|
418
431
|
/**
|
|
419
432
|
* Standardized error code ranges for consistent categorization:
|
|
420
433
|
*
|
|
434
|
+
* - 0: UNKNOWN - Catch-all for unrecognized errors
|
|
421
435
|
* - 1000-1999: INPUT errors - Parameter validation, input format errors
|
|
422
436
|
* - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
|
|
423
437
|
* - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
|
|
@@ -646,6 +660,18 @@ const NetworkError = {
|
|
|
646
660
|
name: 'NETWORK_TIMEOUT',
|
|
647
661
|
type: 'NETWORK',
|
|
648
662
|
},
|
|
663
|
+
/** Circle relayer failed to process the forwarding/mint transaction */
|
|
664
|
+
RELAYER_FORWARD_FAILED: {
|
|
665
|
+
code: 3003,
|
|
666
|
+
name: 'NETWORK_RELAYER_FORWARD_FAILED',
|
|
667
|
+
type: 'NETWORK',
|
|
668
|
+
},
|
|
669
|
+
/** Relayer mint is pending - waiting for confirmation */
|
|
670
|
+
RELAYER_PENDING: {
|
|
671
|
+
code: 3004,
|
|
672
|
+
name: 'NETWORK_RELAYER_PENDING',
|
|
673
|
+
type: 'NETWORK',
|
|
674
|
+
},
|
|
649
675
|
};
|
|
650
676
|
|
|
651
677
|
/**
|
|
@@ -966,6 +992,8 @@ exports.Blockchain = void 0;
|
|
|
966
992
|
Blockchain["Ink_Testnet"] = "Ink_Testnet";
|
|
967
993
|
Blockchain["Linea"] = "Linea";
|
|
968
994
|
Blockchain["Linea_Sepolia"] = "Linea_Sepolia";
|
|
995
|
+
Blockchain["Monad"] = "Monad";
|
|
996
|
+
Blockchain["Monad_Testnet"] = "Monad_Testnet";
|
|
969
997
|
Blockchain["NEAR"] = "NEAR";
|
|
970
998
|
Blockchain["NEAR_Testnet"] = "NEAR_Testnet";
|
|
971
999
|
Blockchain["Noble"] = "Noble";
|
|
@@ -1057,6 +1085,7 @@ exports.BridgeChain = void 0;
|
|
|
1057
1085
|
BridgeChain["HyperEVM"] = "HyperEVM";
|
|
1058
1086
|
BridgeChain["Ink"] = "Ink";
|
|
1059
1087
|
BridgeChain["Linea"] = "Linea";
|
|
1088
|
+
BridgeChain["Monad"] = "Monad";
|
|
1060
1089
|
BridgeChain["Optimism"] = "Optimism";
|
|
1061
1090
|
BridgeChain["Plume"] = "Plume";
|
|
1062
1091
|
BridgeChain["Polygon"] = "Polygon";
|
|
@@ -1076,6 +1105,7 @@ exports.BridgeChain = void 0;
|
|
|
1076
1105
|
BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
|
|
1077
1106
|
BridgeChain["Ink_Testnet"] = "Ink_Testnet";
|
|
1078
1107
|
BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
|
|
1108
|
+
BridgeChain["Monad_Testnet"] = "Monad_Testnet";
|
|
1079
1109
|
BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
1080
1110
|
BridgeChain["Plume_Testnet"] = "Plume_Testnet";
|
|
1081
1111
|
BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
@@ -1202,6 +1232,10 @@ const Aptos = defineChain({
|
|
|
1202
1232
|
confirmations: 1,
|
|
1203
1233
|
},
|
|
1204
1234
|
},
|
|
1235
|
+
forwarderSupported: {
|
|
1236
|
+
source: false,
|
|
1237
|
+
destination: false,
|
|
1238
|
+
},
|
|
1205
1239
|
},
|
|
1206
1240
|
});
|
|
1207
1241
|
|
|
@@ -1235,6 +1269,10 @@ const AptosTestnet = defineChain({
|
|
|
1235
1269
|
confirmations: 1,
|
|
1236
1270
|
},
|
|
1237
1271
|
},
|
|
1272
|
+
forwarderSupported: {
|
|
1273
|
+
source: false,
|
|
1274
|
+
destination: false,
|
|
1275
|
+
},
|
|
1238
1276
|
},
|
|
1239
1277
|
});
|
|
1240
1278
|
|
|
@@ -1294,6 +1332,10 @@ const ArcTestnet = defineChain({
|
|
|
1294
1332
|
fastConfirmations: 1,
|
|
1295
1333
|
},
|
|
1296
1334
|
},
|
|
1335
|
+
forwarderSupported: {
|
|
1336
|
+
source: true,
|
|
1337
|
+
destination: true,
|
|
1338
|
+
},
|
|
1297
1339
|
},
|
|
1298
1340
|
kitContracts: {
|
|
1299
1341
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1338,6 +1380,10 @@ const Arbitrum = defineChain({
|
|
|
1338
1380
|
fastConfirmations: 1,
|
|
1339
1381
|
},
|
|
1340
1382
|
},
|
|
1383
|
+
forwarderSupported: {
|
|
1384
|
+
source: true,
|
|
1385
|
+
destination: true,
|
|
1386
|
+
},
|
|
1341
1387
|
},
|
|
1342
1388
|
kitContracts: {
|
|
1343
1389
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1382,6 +1428,10 @@ const ArbitrumSepolia = defineChain({
|
|
|
1382
1428
|
fastConfirmations: 1,
|
|
1383
1429
|
},
|
|
1384
1430
|
},
|
|
1431
|
+
forwarderSupported: {
|
|
1432
|
+
source: true,
|
|
1433
|
+
destination: true,
|
|
1434
|
+
},
|
|
1385
1435
|
},
|
|
1386
1436
|
kitContracts: {
|
|
1387
1437
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1426,6 +1476,10 @@ const Avalanche = defineChain({
|
|
|
1426
1476
|
fastConfirmations: 1,
|
|
1427
1477
|
},
|
|
1428
1478
|
},
|
|
1479
|
+
forwarderSupported: {
|
|
1480
|
+
source: true,
|
|
1481
|
+
destination: true,
|
|
1482
|
+
},
|
|
1429
1483
|
},
|
|
1430
1484
|
kitContracts: {
|
|
1431
1485
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1469,6 +1523,10 @@ const AvalancheFuji = defineChain({
|
|
|
1469
1523
|
fastConfirmations: 1,
|
|
1470
1524
|
},
|
|
1471
1525
|
},
|
|
1526
|
+
forwarderSupported: {
|
|
1527
|
+
source: true,
|
|
1528
|
+
destination: true,
|
|
1529
|
+
},
|
|
1472
1530
|
},
|
|
1473
1531
|
rpcEndpoints: ['https://api.avax-test.network/ext/bc/C/rpc'],
|
|
1474
1532
|
kitContracts: {
|
|
@@ -1514,6 +1572,10 @@ const Base = defineChain({
|
|
|
1514
1572
|
fastConfirmations: 1,
|
|
1515
1573
|
},
|
|
1516
1574
|
},
|
|
1575
|
+
forwarderSupported: {
|
|
1576
|
+
source: true,
|
|
1577
|
+
destination: true,
|
|
1578
|
+
},
|
|
1517
1579
|
},
|
|
1518
1580
|
kitContracts: {
|
|
1519
1581
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1558,6 +1620,10 @@ const BaseSepolia = defineChain({
|
|
|
1558
1620
|
fastConfirmations: 1,
|
|
1559
1621
|
},
|
|
1560
1622
|
},
|
|
1623
|
+
forwarderSupported: {
|
|
1624
|
+
source: true,
|
|
1625
|
+
destination: true,
|
|
1626
|
+
},
|
|
1561
1627
|
},
|
|
1562
1628
|
kitContracts: {
|
|
1563
1629
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1644,6 +1710,10 @@ const Codex = defineChain({
|
|
|
1644
1710
|
fastConfirmations: 1,
|
|
1645
1711
|
},
|
|
1646
1712
|
},
|
|
1713
|
+
forwarderSupported: {
|
|
1714
|
+
source: true,
|
|
1715
|
+
destination: false,
|
|
1716
|
+
},
|
|
1647
1717
|
},
|
|
1648
1718
|
kitContracts: {
|
|
1649
1719
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1682,6 +1752,10 @@ const CodexTestnet = defineChain({
|
|
|
1682
1752
|
fastConfirmations: 1,
|
|
1683
1753
|
},
|
|
1684
1754
|
},
|
|
1755
|
+
forwarderSupported: {
|
|
1756
|
+
source: true,
|
|
1757
|
+
destination: false,
|
|
1758
|
+
},
|
|
1685
1759
|
},
|
|
1686
1760
|
kitContracts: {
|
|
1687
1761
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1726,6 +1800,10 @@ const Ethereum = defineChain({
|
|
|
1726
1800
|
fastConfirmations: 2,
|
|
1727
1801
|
},
|
|
1728
1802
|
},
|
|
1803
|
+
forwarderSupported: {
|
|
1804
|
+
source: true,
|
|
1805
|
+
destination: true,
|
|
1806
|
+
},
|
|
1729
1807
|
},
|
|
1730
1808
|
kitContracts: {
|
|
1731
1809
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1770,6 +1848,10 @@ const EthereumSepolia = defineChain({
|
|
|
1770
1848
|
fastConfirmations: 2,
|
|
1771
1849
|
},
|
|
1772
1850
|
},
|
|
1851
|
+
forwarderSupported: {
|
|
1852
|
+
source: true,
|
|
1853
|
+
destination: true,
|
|
1854
|
+
},
|
|
1773
1855
|
},
|
|
1774
1856
|
kitContracts: {
|
|
1775
1857
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1856,6 +1938,10 @@ const HyperEVM = defineChain({
|
|
|
1856
1938
|
fastConfirmations: 1,
|
|
1857
1939
|
},
|
|
1858
1940
|
},
|
|
1941
|
+
forwarderSupported: {
|
|
1942
|
+
source: true,
|
|
1943
|
+
destination: true,
|
|
1944
|
+
},
|
|
1859
1945
|
},
|
|
1860
1946
|
kitContracts: {
|
|
1861
1947
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1895,6 +1981,10 @@ const HyperEVMTestnet = defineChain({
|
|
|
1895
1981
|
fastConfirmations: 1,
|
|
1896
1982
|
},
|
|
1897
1983
|
},
|
|
1984
|
+
forwarderSupported: {
|
|
1985
|
+
source: true,
|
|
1986
|
+
destination: true,
|
|
1987
|
+
},
|
|
1898
1988
|
},
|
|
1899
1989
|
kitContracts: {
|
|
1900
1990
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1938,6 +2028,10 @@ const Ink = defineChain({
|
|
|
1938
2028
|
fastConfirmations: 1,
|
|
1939
2029
|
},
|
|
1940
2030
|
},
|
|
2031
|
+
forwarderSupported: {
|
|
2032
|
+
source: true,
|
|
2033
|
+
destination: true,
|
|
2034
|
+
},
|
|
1941
2035
|
},
|
|
1942
2036
|
kitContracts: {
|
|
1943
2037
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1980,6 +2074,10 @@ const InkTestnet = defineChain({
|
|
|
1980
2074
|
fastConfirmations: 1,
|
|
1981
2075
|
},
|
|
1982
2076
|
},
|
|
2077
|
+
forwarderSupported: {
|
|
2078
|
+
source: true,
|
|
2079
|
+
destination: true,
|
|
2080
|
+
},
|
|
1983
2081
|
},
|
|
1984
2082
|
kitContracts: {
|
|
1985
2083
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2018,6 +2116,10 @@ const Linea = defineChain({
|
|
|
2018
2116
|
fastConfirmations: 1,
|
|
2019
2117
|
},
|
|
2020
2118
|
},
|
|
2119
|
+
forwarderSupported: {
|
|
2120
|
+
source: true,
|
|
2121
|
+
destination: true,
|
|
2122
|
+
},
|
|
2021
2123
|
},
|
|
2022
2124
|
kitContracts: {
|
|
2023
2125
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2056,6 +2158,98 @@ const LineaSepolia = defineChain({
|
|
|
2056
2158
|
fastConfirmations: 1,
|
|
2057
2159
|
},
|
|
2058
2160
|
},
|
|
2161
|
+
forwarderSupported: {
|
|
2162
|
+
source: true,
|
|
2163
|
+
destination: true,
|
|
2164
|
+
},
|
|
2165
|
+
},
|
|
2166
|
+
kitContracts: {
|
|
2167
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2168
|
+
},
|
|
2169
|
+
});
|
|
2170
|
+
|
|
2171
|
+
/**
|
|
2172
|
+
* Monad Mainnet chain definition
|
|
2173
|
+
* @remarks
|
|
2174
|
+
* This represents the official production network for the Monad blockchain.
|
|
2175
|
+
* Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
|
|
2176
|
+
* over 10,000 TPS, sub-second finality, and near-zero gas fees.
|
|
2177
|
+
*/
|
|
2178
|
+
const Monad = defineChain({
|
|
2179
|
+
type: 'evm',
|
|
2180
|
+
chain: exports.Blockchain.Monad,
|
|
2181
|
+
name: 'Monad',
|
|
2182
|
+
title: 'Monad Mainnet',
|
|
2183
|
+
nativeCurrency: {
|
|
2184
|
+
name: 'Monad',
|
|
2185
|
+
symbol: 'MON',
|
|
2186
|
+
decimals: 18,
|
|
2187
|
+
},
|
|
2188
|
+
chainId: 143,
|
|
2189
|
+
isTestnet: false,
|
|
2190
|
+
explorerUrl: 'https://monadscan.com/tx/{hash}',
|
|
2191
|
+
rpcEndpoints: ['https://rpc.monad.xyz'],
|
|
2192
|
+
eurcAddress: null,
|
|
2193
|
+
usdcAddress: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
|
|
2194
|
+
cctp: {
|
|
2195
|
+
domain: 15,
|
|
2196
|
+
contracts: {
|
|
2197
|
+
v2: {
|
|
2198
|
+
type: 'split',
|
|
2199
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
2200
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
2201
|
+
confirmations: 1,
|
|
2202
|
+
fastConfirmations: 1,
|
|
2203
|
+
},
|
|
2204
|
+
},
|
|
2205
|
+
forwarderSupported: {
|
|
2206
|
+
source: true,
|
|
2207
|
+
destination: true,
|
|
2208
|
+
},
|
|
2209
|
+
},
|
|
2210
|
+
kitContracts: {
|
|
2211
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2212
|
+
},
|
|
2213
|
+
});
|
|
2214
|
+
|
|
2215
|
+
/**
|
|
2216
|
+
* Monad Testnet chain definition
|
|
2217
|
+
* @remarks
|
|
2218
|
+
* This represents the official test network for the Monad blockchain.
|
|
2219
|
+
* Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
|
|
2220
|
+
* over 10,000 TPS, sub-second finality, and near-zero gas fees.
|
|
2221
|
+
*/
|
|
2222
|
+
const MonadTestnet = defineChain({
|
|
2223
|
+
type: 'evm',
|
|
2224
|
+
chain: exports.Blockchain.Monad_Testnet,
|
|
2225
|
+
name: 'Monad Testnet',
|
|
2226
|
+
title: 'Monad Testnet',
|
|
2227
|
+
nativeCurrency: {
|
|
2228
|
+
name: 'Monad',
|
|
2229
|
+
symbol: 'MON',
|
|
2230
|
+
decimals: 18,
|
|
2231
|
+
},
|
|
2232
|
+
chainId: 10143,
|
|
2233
|
+
isTestnet: true,
|
|
2234
|
+
explorerUrl: 'https://testnet.monadscan.com/tx/{hash}',
|
|
2235
|
+
rpcEndpoints: ['https://testnet-rpc.monad.xyz'],
|
|
2236
|
+
eurcAddress: null,
|
|
2237
|
+
usdcAddress: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
|
|
2238
|
+
cctp: {
|
|
2239
|
+
domain: 15,
|
|
2240
|
+
contracts: {
|
|
2241
|
+
v2: {
|
|
2242
|
+
type: 'split',
|
|
2243
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
2244
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
2245
|
+
confirmations: 1,
|
|
2246
|
+
fastConfirmations: 1,
|
|
2247
|
+
},
|
|
2248
|
+
},
|
|
2249
|
+
forwarderSupported: {
|
|
2250
|
+
source: true,
|
|
2251
|
+
destination: true,
|
|
2252
|
+
},
|
|
2059
2253
|
},
|
|
2060
2254
|
kitContracts: {
|
|
2061
2255
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2137,6 +2331,10 @@ const Noble = defineChain({
|
|
|
2137
2331
|
confirmations: 1,
|
|
2138
2332
|
},
|
|
2139
2333
|
},
|
|
2334
|
+
forwarderSupported: {
|
|
2335
|
+
source: false,
|
|
2336
|
+
destination: false,
|
|
2337
|
+
},
|
|
2140
2338
|
},
|
|
2141
2339
|
});
|
|
2142
2340
|
|
|
@@ -2169,6 +2367,10 @@ const NobleTestnet = defineChain({
|
|
|
2169
2367
|
confirmations: 1,
|
|
2170
2368
|
},
|
|
2171
2369
|
},
|
|
2370
|
+
forwarderSupported: {
|
|
2371
|
+
source: false,
|
|
2372
|
+
destination: false,
|
|
2373
|
+
},
|
|
2172
2374
|
},
|
|
2173
2375
|
});
|
|
2174
2376
|
|
|
@@ -2210,6 +2412,10 @@ const Optimism = defineChain({
|
|
|
2210
2412
|
fastConfirmations: 1,
|
|
2211
2413
|
},
|
|
2212
2414
|
},
|
|
2415
|
+
forwarderSupported: {
|
|
2416
|
+
source: true,
|
|
2417
|
+
destination: true,
|
|
2418
|
+
},
|
|
2213
2419
|
},
|
|
2214
2420
|
kitContracts: {
|
|
2215
2421
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2254,6 +2460,10 @@ const OptimismSepolia = defineChain({
|
|
|
2254
2460
|
fastConfirmations: 1,
|
|
2255
2461
|
},
|
|
2256
2462
|
},
|
|
2463
|
+
forwarderSupported: {
|
|
2464
|
+
source: true,
|
|
2465
|
+
destination: true,
|
|
2466
|
+
},
|
|
2257
2467
|
},
|
|
2258
2468
|
kitContracts: {
|
|
2259
2469
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2294,6 +2504,10 @@ const Plume = defineChain({
|
|
|
2294
2504
|
fastConfirmations: 1,
|
|
2295
2505
|
},
|
|
2296
2506
|
},
|
|
2507
|
+
forwarderSupported: {
|
|
2508
|
+
source: true,
|
|
2509
|
+
destination: false,
|
|
2510
|
+
},
|
|
2297
2511
|
},
|
|
2298
2512
|
kitContracts: {
|
|
2299
2513
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2333,6 +2547,10 @@ const PlumeTestnet = defineChain({
|
|
|
2333
2547
|
fastConfirmations: 1,
|
|
2334
2548
|
},
|
|
2335
2549
|
},
|
|
2550
|
+
forwarderSupported: {
|
|
2551
|
+
source: true,
|
|
2552
|
+
destination: false,
|
|
2553
|
+
},
|
|
2336
2554
|
},
|
|
2337
2555
|
kitContracts: {
|
|
2338
2556
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2423,6 +2641,10 @@ const Polygon = defineChain({
|
|
|
2423
2641
|
fastConfirmations: 13,
|
|
2424
2642
|
},
|
|
2425
2643
|
},
|
|
2644
|
+
forwarderSupported: {
|
|
2645
|
+
source: true,
|
|
2646
|
+
destination: true,
|
|
2647
|
+
},
|
|
2426
2648
|
},
|
|
2427
2649
|
kitContracts: {
|
|
2428
2650
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2467,6 +2689,10 @@ const PolygonAmoy = defineChain({
|
|
|
2467
2689
|
fastConfirmations: 13,
|
|
2468
2690
|
},
|
|
2469
2691
|
},
|
|
2692
|
+
forwarderSupported: {
|
|
2693
|
+
source: true,
|
|
2694
|
+
destination: true,
|
|
2695
|
+
},
|
|
2470
2696
|
},
|
|
2471
2697
|
kitContracts: {
|
|
2472
2698
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2507,6 +2733,10 @@ const Sei = defineChain({
|
|
|
2507
2733
|
fastConfirmations: 1,
|
|
2508
2734
|
},
|
|
2509
2735
|
},
|
|
2736
|
+
forwarderSupported: {
|
|
2737
|
+
source: true,
|
|
2738
|
+
destination: true,
|
|
2739
|
+
},
|
|
2510
2740
|
},
|
|
2511
2741
|
kitContracts: {
|
|
2512
2742
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2546,6 +2776,10 @@ const SeiTestnet = defineChain({
|
|
|
2546
2776
|
fastConfirmations: 1,
|
|
2547
2777
|
},
|
|
2548
2778
|
},
|
|
2779
|
+
forwarderSupported: {
|
|
2780
|
+
source: true,
|
|
2781
|
+
destination: true,
|
|
2782
|
+
},
|
|
2549
2783
|
},
|
|
2550
2784
|
kitContracts: {
|
|
2551
2785
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2584,6 +2818,10 @@ const Sonic = defineChain({
|
|
|
2584
2818
|
fastConfirmations: 1,
|
|
2585
2819
|
},
|
|
2586
2820
|
},
|
|
2821
|
+
forwarderSupported: {
|
|
2822
|
+
source: true,
|
|
2823
|
+
destination: true,
|
|
2824
|
+
},
|
|
2587
2825
|
},
|
|
2588
2826
|
kitContracts: {
|
|
2589
2827
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2622,6 +2860,10 @@ const SonicTestnet = defineChain({
|
|
|
2622
2860
|
fastConfirmations: 1,
|
|
2623
2861
|
},
|
|
2624
2862
|
},
|
|
2863
|
+
forwarderSupported: {
|
|
2864
|
+
source: true,
|
|
2865
|
+
destination: true,
|
|
2866
|
+
},
|
|
2625
2867
|
},
|
|
2626
2868
|
kitContracts: {
|
|
2627
2869
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2665,6 +2907,10 @@ const Solana = defineChain({
|
|
|
2665
2907
|
fastConfirmations: 3,
|
|
2666
2908
|
},
|
|
2667
2909
|
},
|
|
2910
|
+
forwarderSupported: {
|
|
2911
|
+
source: true,
|
|
2912
|
+
destination: false,
|
|
2913
|
+
},
|
|
2668
2914
|
},
|
|
2669
2915
|
kitContracts: {
|
|
2670
2916
|
bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
|
|
@@ -2707,6 +2953,10 @@ const SolanaDevnet = defineChain({
|
|
|
2707
2953
|
fastConfirmations: 3,
|
|
2708
2954
|
},
|
|
2709
2955
|
},
|
|
2956
|
+
forwarderSupported: {
|
|
2957
|
+
source: true,
|
|
2958
|
+
destination: false,
|
|
2959
|
+
},
|
|
2710
2960
|
},
|
|
2711
2961
|
kitContracts: {
|
|
2712
2962
|
bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
|
|
@@ -2790,6 +3040,10 @@ const Sui = defineChain({
|
|
|
2790
3040
|
confirmations: 1,
|
|
2791
3041
|
},
|
|
2792
3042
|
},
|
|
3043
|
+
forwarderSupported: {
|
|
3044
|
+
source: false,
|
|
3045
|
+
destination: false,
|
|
3046
|
+
},
|
|
2793
3047
|
},
|
|
2794
3048
|
});
|
|
2795
3049
|
|
|
@@ -2823,6 +3077,10 @@ const SuiTestnet = defineChain({
|
|
|
2823
3077
|
confirmations: 1,
|
|
2824
3078
|
},
|
|
2825
3079
|
},
|
|
3080
|
+
forwarderSupported: {
|
|
3081
|
+
source: false,
|
|
3082
|
+
destination: false,
|
|
3083
|
+
},
|
|
2826
3084
|
},
|
|
2827
3085
|
});
|
|
2828
3086
|
|
|
@@ -2844,7 +3102,7 @@ const Unichain = defineChain({
|
|
|
2844
3102
|
chainId: 130,
|
|
2845
3103
|
isTestnet: false,
|
|
2846
3104
|
explorerUrl: 'https://unichain.blockscout.com/tx/{hash}',
|
|
2847
|
-
rpcEndpoints: ['https://
|
|
3105
|
+
rpcEndpoints: ['https://mainnet.unichain.org'],
|
|
2848
3106
|
eurcAddress: null,
|
|
2849
3107
|
usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
|
|
2850
3108
|
cctp: {
|
|
@@ -2864,6 +3122,10 @@ const Unichain = defineChain({
|
|
|
2864
3122
|
fastConfirmations: 1,
|
|
2865
3123
|
},
|
|
2866
3124
|
},
|
|
3125
|
+
forwarderSupported: {
|
|
3126
|
+
source: true,
|
|
3127
|
+
destination: true,
|
|
3128
|
+
},
|
|
2867
3129
|
},
|
|
2868
3130
|
kitContracts: {
|
|
2869
3131
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2908,6 +3170,10 @@ const UnichainSepolia = defineChain({
|
|
|
2908
3170
|
fastConfirmations: 1,
|
|
2909
3171
|
},
|
|
2910
3172
|
},
|
|
3173
|
+
forwarderSupported: {
|
|
3174
|
+
source: true,
|
|
3175
|
+
destination: true,
|
|
3176
|
+
},
|
|
2911
3177
|
},
|
|
2912
3178
|
kitContracts: {
|
|
2913
3179
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2934,7 +3200,7 @@ const WorldChain = defineChain({
|
|
|
2934
3200
|
explorerUrl: 'https://worldscan.org/tx/{hash}',
|
|
2935
3201
|
rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
|
|
2936
3202
|
eurcAddress: null,
|
|
2937
|
-
usdcAddress: '
|
|
3203
|
+
usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
|
|
2938
3204
|
cctp: {
|
|
2939
3205
|
domain: 14,
|
|
2940
3206
|
contracts: {
|
|
@@ -2946,6 +3212,10 @@ const WorldChain = defineChain({
|
|
|
2946
3212
|
fastConfirmations: 1,
|
|
2947
3213
|
},
|
|
2948
3214
|
},
|
|
3215
|
+
forwarderSupported: {
|
|
3216
|
+
source: true,
|
|
3217
|
+
destination: true,
|
|
3218
|
+
},
|
|
2949
3219
|
},
|
|
2950
3220
|
kitContracts: {
|
|
2951
3221
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2987,6 +3257,10 @@ const WorldChainSepolia = defineChain({
|
|
|
2987
3257
|
fastConfirmations: 1,
|
|
2988
3258
|
},
|
|
2989
3259
|
},
|
|
3260
|
+
forwarderSupported: {
|
|
3261
|
+
source: true,
|
|
3262
|
+
destination: true,
|
|
3263
|
+
},
|
|
2990
3264
|
},
|
|
2991
3265
|
kitContracts: {
|
|
2992
3266
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -3013,7 +3287,7 @@ const XDC = defineChain({
|
|
|
3013
3287
|
chainId: 50,
|
|
3014
3288
|
isTestnet: false,
|
|
3015
3289
|
explorerUrl: 'https://xdcscan.io/tx/{hash}',
|
|
3016
|
-
rpcEndpoints: ['https://erpc.xinfin.network'],
|
|
3290
|
+
rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
|
|
3017
3291
|
eurcAddress: null,
|
|
3018
3292
|
usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
|
|
3019
3293
|
cctp: {
|
|
@@ -3027,6 +3301,10 @@ const XDC = defineChain({
|
|
|
3027
3301
|
fastConfirmations: 3,
|
|
3028
3302
|
},
|
|
3029
3303
|
},
|
|
3304
|
+
forwarderSupported: {
|
|
3305
|
+
source: true,
|
|
3306
|
+
destination: false,
|
|
3307
|
+
},
|
|
3030
3308
|
},
|
|
3031
3309
|
kitContracts: {
|
|
3032
3310
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -3065,6 +3343,10 @@ const XDCApothem = defineChain({
|
|
|
3065
3343
|
fastConfirmations: 1,
|
|
3066
3344
|
},
|
|
3067
3345
|
},
|
|
3346
|
+
forwarderSupported: {
|
|
3347
|
+
source: true,
|
|
3348
|
+
destination: false,
|
|
3349
|
+
},
|
|
3068
3350
|
},
|
|
3069
3351
|
kitContracts: {
|
|
3070
3352
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -3146,6 +3428,8 @@ var Blockchains = {
|
|
|
3146
3428
|
InkTestnet: InkTestnet,
|
|
3147
3429
|
Linea: Linea,
|
|
3148
3430
|
LineaSepolia: LineaSepolia,
|
|
3431
|
+
Monad: Monad,
|
|
3432
|
+
MonadTestnet: MonadTestnet,
|
|
3149
3433
|
NEAR: NEAR,
|
|
3150
3434
|
NEARTestnet: NEARTestnet,
|
|
3151
3435
|
Noble: Noble,
|
|
@@ -3299,7 +3583,7 @@ const nonEvmChainDefinitionSchema = baseChainDefinitionSchema
|
|
|
3299
3583
|
* })
|
|
3300
3584
|
* ```
|
|
3301
3585
|
*/
|
|
3302
|
-
const chainDefinitionSchema$
|
|
3586
|
+
const chainDefinitionSchema$2 = zod.z.discriminatedUnion('type', [
|
|
3303
3587
|
evmChainDefinitionSchema,
|
|
3304
3588
|
nonEvmChainDefinitionSchema,
|
|
3305
3589
|
]);
|
|
@@ -3324,7 +3608,7 @@ zod.z.union([
|
|
|
3324
3608
|
.string()
|
|
3325
3609
|
.refine((val) => val in exports.Blockchain, 'Must be a valid Blockchain enum value as string'),
|
|
3326
3610
|
zod.z.nativeEnum(exports.Blockchain),
|
|
3327
|
-
chainDefinitionSchema$
|
|
3611
|
+
chainDefinitionSchema$2,
|
|
3328
3612
|
]);
|
|
3329
3613
|
/**
|
|
3330
3614
|
* Zod schema for validating bridge chain identifiers.
|
|
@@ -3360,7 +3644,7 @@ const bridgeChainIdentifierSchema = zod.z.union([
|
|
|
3360
3644
|
zod.z.string().refine((val) => val in exports.BridgeChain, (val) => ({
|
|
3361
3645
|
message: `Chain "${val}" is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
|
|
3362
3646
|
})),
|
|
3363
|
-
chainDefinitionSchema$
|
|
3647
|
+
chainDefinitionSchema$2.refine((chainDef) => chainDef.chain in exports.BridgeChain, (chainDef) => ({
|
|
3364
3648
|
message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
|
|
3365
3649
|
})),
|
|
3366
3650
|
]);
|
|
@@ -3557,14 +3841,41 @@ function isFatalError(error) {
|
|
|
3557
3841
|
return isKitError(error) && error.recoverability === 'FATAL';
|
|
3558
3842
|
}
|
|
3559
3843
|
/**
|
|
3560
|
-
*
|
|
3844
|
+
* Error codes that are considered retryable by default.
|
|
3845
|
+
*
|
|
3846
|
+
* @remarks
|
|
3847
|
+
* These are typically transient errors that may succeed on retry:
|
|
3848
|
+
* - Network connectivity issues (3001, 3002)
|
|
3849
|
+
* - Provider unavailability (4001, 4002)
|
|
3850
|
+
* - RPC nonce errors (4003)
|
|
3851
|
+
*/
|
|
3852
|
+
const DEFAULT_RETRYABLE_ERROR_CODES = [
|
|
3853
|
+
// Network errors
|
|
3854
|
+
3001, // NETWORK_CONNECTION_FAILED
|
|
3855
|
+
3002, // NETWORK_TIMEOUT
|
|
3856
|
+
// Provider errors
|
|
3857
|
+
4001, // PROVIDER_UNAVAILABLE
|
|
3858
|
+
4002, // PROVIDER_TIMEOUT
|
|
3859
|
+
4003, // RPC_NONCE_ERROR
|
|
3860
|
+
];
|
|
3861
|
+
/**
|
|
3862
|
+
* Checks if an error is retryable.
|
|
3863
|
+
*
|
|
3864
|
+
* @remarks
|
|
3865
|
+
* Check order for KitError instances:
|
|
3866
|
+
* 1. If `recoverability === 'RETRYABLE'`, return `true` immediately (priority check).
|
|
3867
|
+
* 2. Otherwise, check if `error.code` is in `DEFAULT_RETRYABLE_ERROR_CODES` (fallback check).
|
|
3868
|
+
* 3. Non-KitError instances always return `false`.
|
|
3869
|
+
*
|
|
3870
|
+
* This two-tier approach allows both explicit recoverability control and
|
|
3871
|
+
* backward-compatible code-based retry logic.
|
|
3561
3872
|
*
|
|
3562
3873
|
* RETRYABLE errors indicate transient failures that may succeed on
|
|
3563
3874
|
* subsequent attempts, such as network timeouts or temporary service
|
|
3564
3875
|
* unavailability. These errors are safe to retry after a delay.
|
|
3565
3876
|
*
|
|
3566
3877
|
* @param error - Unknown error to check
|
|
3567
|
-
* @returns True if error is
|
|
3878
|
+
* @returns True if error is retryable
|
|
3568
3879
|
*
|
|
3569
3880
|
* @example
|
|
3570
3881
|
* ```typescript
|
|
@@ -3579,9 +3890,51 @@ function isFatalError(error) {
|
|
|
3579
3890
|
* }
|
|
3580
3891
|
* }
|
|
3581
3892
|
* ```
|
|
3893
|
+
*
|
|
3894
|
+
* @example
|
|
3895
|
+
* ```typescript
|
|
3896
|
+
* import { isRetryableError, createNetworkConnectionError, KitError } from '@core/errors'
|
|
3897
|
+
*
|
|
3898
|
+
* // KitError with RETRYABLE recoverability (priority check)
|
|
3899
|
+
* const error1 = createNetworkConnectionError('Ethereum')
|
|
3900
|
+
* isRetryableError(error1) // true
|
|
3901
|
+
*
|
|
3902
|
+
* // KitError with default retryable code (fallback check)
|
|
3903
|
+
* const error2 = new KitError({
|
|
3904
|
+
* code: 3002, // NETWORK_TIMEOUT - in DEFAULT_RETRYABLE_ERROR_CODES
|
|
3905
|
+
* name: 'NETWORK_TIMEOUT',
|
|
3906
|
+
* type: 'NETWORK',
|
|
3907
|
+
* recoverability: 'FATAL', // Not RETRYABLE
|
|
3908
|
+
* message: 'Timeout',
|
|
3909
|
+
* })
|
|
3910
|
+
* isRetryableError(error2) // true (code 3002 is in default list)
|
|
3911
|
+
*
|
|
3912
|
+
* // KitError with non-retryable code and FATAL recoverability
|
|
3913
|
+
* const error3 = new KitError({
|
|
3914
|
+
* code: 1001,
|
|
3915
|
+
* name: 'INVALID_INPUT',
|
|
3916
|
+
* type: 'INPUT',
|
|
3917
|
+
* recoverability: 'FATAL',
|
|
3918
|
+
* message: 'Invalid input',
|
|
3919
|
+
* })
|
|
3920
|
+
* isRetryableError(error3) // false
|
|
3921
|
+
*
|
|
3922
|
+
* // Non-KitError
|
|
3923
|
+
* const error4 = new Error('Standard error')
|
|
3924
|
+
* isRetryableError(error4) // false
|
|
3925
|
+
* ```
|
|
3582
3926
|
*/
|
|
3583
3927
|
function isRetryableError(error) {
|
|
3584
|
-
|
|
3928
|
+
// Use proper type guard to check if it's a KitError
|
|
3929
|
+
if (isKitError(error)) {
|
|
3930
|
+
// Priority check: explicit recoverability
|
|
3931
|
+
if (error.recoverability === 'RETRYABLE') {
|
|
3932
|
+
return true;
|
|
3933
|
+
}
|
|
3934
|
+
// Fallback check: error code against default retryable codes
|
|
3935
|
+
return DEFAULT_RETRYABLE_ERROR_CODES.includes(error.code);
|
|
3936
|
+
}
|
|
3937
|
+
return false;
|
|
3585
3938
|
}
|
|
3586
3939
|
/**
|
|
3587
3940
|
* Type guard to check if error is KitError with INPUT type.
|
|
@@ -3778,6 +4131,62 @@ function getErrorMessage(error) {
|
|
|
3778
4131
|
function getErrorCode(error) {
|
|
3779
4132
|
return isKitError(error) ? error.code : null;
|
|
3780
4133
|
}
|
|
4134
|
+
/**
|
|
4135
|
+
* Extract structured error information for logging and events.
|
|
4136
|
+
*
|
|
4137
|
+
* @remarks
|
|
4138
|
+
* Safely extracts error information from any thrown value, handling:
|
|
4139
|
+
* - KitError objects (message preserved - safe to log)
|
|
4140
|
+
* - Standard Error objects (message redacted for security)
|
|
4141
|
+
* - Plain objects with name/message/code (message redacted)
|
|
4142
|
+
* - Primitive values (logged as-is since they contain no structured data)
|
|
4143
|
+
*
|
|
4144
|
+
* For security, only KitError messages are included. Other error messages are
|
|
4145
|
+
* redacted to prevent logging sensitive data like tokens or PII.
|
|
4146
|
+
*
|
|
4147
|
+
* @param error - The error to extract info from.
|
|
4148
|
+
* @returns Structured error information.
|
|
4149
|
+
*
|
|
4150
|
+
* @example
|
|
4151
|
+
* ```typescript
|
|
4152
|
+
* import { extractErrorInfo } from '@core/errors'
|
|
4153
|
+
*
|
|
4154
|
+
* // Standard Error - message is redacted for security
|
|
4155
|
+
* const info1 = extractErrorInfo(new Error('Something went wrong'))
|
|
4156
|
+
* // { name: 'Error', message: 'An error occurred. See error name and code for details.' }
|
|
4157
|
+
*
|
|
4158
|
+
* // KitError - message is preserved (safe type)
|
|
4159
|
+
* const error = createNetworkConnectionError('Ethereum')
|
|
4160
|
+
* const info2 = extractErrorInfo(error)
|
|
4161
|
+
* // { name: 'NETWORK_CONNECTION_FAILED', message: 'Network connection failed for Ethereum', code: 3001 }
|
|
4162
|
+
*
|
|
4163
|
+
* // Primitive value - logged as-is
|
|
4164
|
+
* const info3 = extractErrorInfo('string error')
|
|
4165
|
+
* // { name: 'UnknownError', message: 'string error' }
|
|
4166
|
+
* ```
|
|
4167
|
+
*/
|
|
4168
|
+
function extractErrorInfo(error) {
|
|
4169
|
+
if (error === null || typeof error !== 'object') {
|
|
4170
|
+
return {
|
|
4171
|
+
name: 'UnknownError',
|
|
4172
|
+
message: String(error),
|
|
4173
|
+
};
|
|
4174
|
+
}
|
|
4175
|
+
const err = error;
|
|
4176
|
+
// Only preserve messages for KitError instances (safe type)
|
|
4177
|
+
// For other errors, redact message for security
|
|
4178
|
+
const errorMessage = isKitError(error)
|
|
4179
|
+
? getErrorMessage(error)
|
|
4180
|
+
: 'An error occurred. See error name and code for details.';
|
|
4181
|
+
const info = {
|
|
4182
|
+
name: err.name ?? 'UnknownError',
|
|
4183
|
+
message: errorMessage,
|
|
4184
|
+
};
|
|
4185
|
+
if (typeof err.code === 'number') {
|
|
4186
|
+
info.code = err.code;
|
|
4187
|
+
}
|
|
4188
|
+
return info;
|
|
4189
|
+
}
|
|
3781
4190
|
|
|
3782
4191
|
/**
|
|
3783
4192
|
* Validates if an address format is correct for the specified chain.
|
|
@@ -4322,7 +4731,7 @@ function validateWithStateTracking(value, schema, context, validatorName) {
|
|
|
4322
4731
|
* Zod schema for validating chain definition objects used in buildExplorerUrl.
|
|
4323
4732
|
* This schema ensures the chain definition has the required properties for URL generation.
|
|
4324
4733
|
*/
|
|
4325
|
-
const chainDefinitionSchema = zod.z.object({
|
|
4734
|
+
const chainDefinitionSchema$1 = zod.z.object({
|
|
4326
4735
|
name: zod.z
|
|
4327
4736
|
.string({
|
|
4328
4737
|
required_error: 'Chain name is required',
|
|
@@ -4346,15 +4755,14 @@ const transactionHashSchema = zod.z
|
|
|
4346
4755
|
required_error: 'Transaction hash is required',
|
|
4347
4756
|
invalid_type_error: 'Transaction hash must be a string',
|
|
4348
4757
|
})
|
|
4349
|
-
.
|
|
4350
|
-
.
|
|
4351
|
-
.refine((hash) => hash.length > 0, 'Transaction hash must not be empty or whitespace-only');
|
|
4758
|
+
.transform((hash) => hash.trim())
|
|
4759
|
+
.refine((hash) => hash.length > 0, 'Transaction hash cannot be empty');
|
|
4352
4760
|
/**
|
|
4353
4761
|
* Zod schema for validating buildExplorerUrl function parameters.
|
|
4354
4762
|
* This schema validates both the chain definition and transaction hash together.
|
|
4355
4763
|
*/
|
|
4356
4764
|
zod.z.object({
|
|
4357
|
-
chainDef: chainDefinitionSchema,
|
|
4765
|
+
chainDef: chainDefinitionSchema$1,
|
|
4358
4766
|
txHash: transactionHashSchema,
|
|
4359
4767
|
});
|
|
4360
4768
|
/**
|
|
@@ -4365,6 +4773,69 @@ zod.z
|
|
|
4365
4773
|
.string()
|
|
4366
4774
|
.url('Generated explorer URL is invalid');
|
|
4367
4775
|
|
|
4776
|
+
/**
|
|
4777
|
+
* Parses and validates data against a Zod schema, returning the transformed result.
|
|
4778
|
+
*
|
|
4779
|
+
* Unlike `validate` and `validateOrThrow` which use type assertions (`asserts value is T`),
|
|
4780
|
+
* this function **returns the parsed data** from Zod. This is important when your schema
|
|
4781
|
+
* includes transformations like defaults, coercion, or custom transforms.
|
|
4782
|
+
*
|
|
4783
|
+
* @typeParam T - The expected output type (caller must specify).
|
|
4784
|
+
* @param value - The value to parse and validate.
|
|
4785
|
+
* @param schema - The Zod schema to validate against.
|
|
4786
|
+
* @param context - Context string to include in error messages (e.g., 'user input', 'config').
|
|
4787
|
+
* @returns The parsed and transformed data of type T.
|
|
4788
|
+
* @throws KitError with INPUT_VALIDATION_FAILED code (1098) if validation fails.
|
|
4789
|
+
*
|
|
4790
|
+
* @remarks
|
|
4791
|
+
* This function uses `ZodTypeAny` for the schema parameter to avoid TypeScript's
|
|
4792
|
+
* "excessively deep type instantiation" error in generic contexts. The caller
|
|
4793
|
+
* must provide the expected type `T` explicitly.
|
|
4794
|
+
*
|
|
4795
|
+
* Use this instead of `validate`/`validateOrThrow` when:
|
|
4796
|
+
* - Your schema has `.default()` values
|
|
4797
|
+
* - Your schema uses `.coerce` (e.g., `z.coerce.number()`)
|
|
4798
|
+
* - Your schema has `.transform()` functions
|
|
4799
|
+
* - You need the actual parsed output, not just type narrowing
|
|
4800
|
+
*
|
|
4801
|
+
* @example
|
|
4802
|
+
* ```typescript
|
|
4803
|
+
* import { parseOrThrow } from '@core/utils'
|
|
4804
|
+
* import { z } from 'zod'
|
|
4805
|
+
*
|
|
4806
|
+
* const userSchema = z.object({
|
|
4807
|
+
* name: z.string().default('Anonymous'),
|
|
4808
|
+
* age: z.coerce.number(), // Transforms "25" → 25
|
|
4809
|
+
* })
|
|
4810
|
+
*
|
|
4811
|
+
* type User = z.infer<typeof userSchema>
|
|
4812
|
+
*
|
|
4813
|
+
* // Explicit type parameter
|
|
4814
|
+
* const user = parseOrThrow<User>({ age: '25' }, userSchema, 'user data')
|
|
4815
|
+
* console.log(user) // { name: 'Anonymous', age: 25 }
|
|
4816
|
+
* ```
|
|
4817
|
+
*
|
|
4818
|
+
* @example
|
|
4819
|
+
* ```typescript
|
|
4820
|
+
* // Usage in generic adapter functions (no deep type instantiation)
|
|
4821
|
+
* function validateInput<TInput>(
|
|
4822
|
+
* input: unknown,
|
|
4823
|
+
* schema: z.ZodTypeAny,
|
|
4824
|
+
* name: string,
|
|
4825
|
+
* ): TInput {
|
|
4826
|
+
* return parseOrThrow<TInput>(input, schema, `${name} input`)
|
|
4827
|
+
* }
|
|
4828
|
+
* ```
|
|
4829
|
+
*/
|
|
4830
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- Intentional: T is caller-provided to avoid deep type instantiation from schema inference
|
|
4831
|
+
function parseOrThrow(value, schema, context) {
|
|
4832
|
+
const result = schema.safeParse(value);
|
|
4833
|
+
if (!result.success) {
|
|
4834
|
+
throw createValidationErrorFromZod(result.error, context);
|
|
4835
|
+
}
|
|
4836
|
+
return result.data;
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4368
4839
|
/**
|
|
4369
4840
|
* A type-safe event emitter for managing action-based event subscriptions.
|
|
4370
4841
|
*
|
|
@@ -4639,7 +5110,7 @@ const parseAmount = (params) => {
|
|
|
4639
5110
|
};
|
|
4640
5111
|
|
|
4641
5112
|
var name = "@circle-fin/bridge-kit";
|
|
4642
|
-
var version = "1.
|
|
5113
|
+
var version = "1.6.0";
|
|
4643
5114
|
var pkg = {
|
|
4644
5115
|
name: name,
|
|
4645
5116
|
version: version};
|
|
@@ -5065,7 +5536,7 @@ const createDecimalStringValidator = (options) => (schema) => {
|
|
|
5065
5536
|
* console.log(result.success) // true
|
|
5066
5537
|
* ```
|
|
5067
5538
|
*/
|
|
5068
|
-
zod.z.object({
|
|
5539
|
+
const chainDefinitionSchema = zod.z.object({
|
|
5069
5540
|
name: zod.z.string().min(1, 'Chain name is required'),
|
|
5070
5541
|
type: zod.z.string().min(1, 'Chain type is required'),
|
|
5071
5542
|
});
|
|
@@ -5112,6 +5583,53 @@ const walletContextSchema = zod.z.object({
|
|
|
5112
5583
|
isTestnet: zod.z.boolean(),
|
|
5113
5584
|
}),
|
|
5114
5585
|
});
|
|
5586
|
+
/**
|
|
5587
|
+
* Schema for validating destination wallet contexts.
|
|
5588
|
+
* Extends walletContextSchema with optional useForwarder flag.
|
|
5589
|
+
*
|
|
5590
|
+
* @throws KitError if validation fails
|
|
5591
|
+
*/
|
|
5592
|
+
const destinationContextSchema = walletContextSchema.extend({
|
|
5593
|
+
useForwarder: zod.z.boolean().optional(),
|
|
5594
|
+
recipientAddress: zod.z.string().optional(),
|
|
5595
|
+
});
|
|
5596
|
+
/**
|
|
5597
|
+
* Schema for validating forwarder-only destination contexts.
|
|
5598
|
+
* Used when useForwarder is true and no adapter is provided.
|
|
5599
|
+
* Requires chain definition and recipientAddress.
|
|
5600
|
+
*
|
|
5601
|
+
* Validates that recipientAddress format is correct for the destination chain
|
|
5602
|
+
* (EVM hex address or Solana base58 address).
|
|
5603
|
+
*
|
|
5604
|
+
* @throws KitError if validation fails
|
|
5605
|
+
*/
|
|
5606
|
+
const forwarderOnlyDestinationSchema = zod.z
|
|
5607
|
+
.object({
|
|
5608
|
+
chain: chainDefinitionSchema.extend({
|
|
5609
|
+
isTestnet: zod.z.boolean(),
|
|
5610
|
+
}),
|
|
5611
|
+
recipientAddress: zod.z
|
|
5612
|
+
.string()
|
|
5613
|
+
.min(1, 'Recipient address is required for forwarder-only destination'),
|
|
5614
|
+
useForwarder: zod.z.literal(true),
|
|
5615
|
+
})
|
|
5616
|
+
.superRefine((data, ctx) => {
|
|
5617
|
+
// Pass chain name as string - isValidAddressForChain will use name-based matching
|
|
5618
|
+
if (!isValidAddressForChain(data.recipientAddress, data.chain.name)) {
|
|
5619
|
+
ctx.addIssue({
|
|
5620
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5621
|
+
message: `Invalid recipient address format for chain ${data.chain.name}`,
|
|
5622
|
+
path: ['recipientAddress'],
|
|
5623
|
+
});
|
|
5624
|
+
}
|
|
5625
|
+
});
|
|
5626
|
+
/**
|
|
5627
|
+
* Schema for validating any destination - either with adapter or forwarder-only.
|
|
5628
|
+
*/
|
|
5629
|
+
const bridgeDestinationSchema$1 = zod.z.union([
|
|
5630
|
+
destinationContextSchema,
|
|
5631
|
+
forwarderOnlyDestinationSchema,
|
|
5632
|
+
]);
|
|
5115
5633
|
/**
|
|
5116
5634
|
* Schema for validating a custom fee configuration.
|
|
5117
5635
|
* Validates the simplified CustomFee interface which includes:
|
|
@@ -5209,7 +5727,7 @@ zod.z.object({
|
|
|
5209
5727
|
maxDecimals: 6,
|
|
5210
5728
|
})(zod.z.string())),
|
|
5211
5729
|
source: walletContextSchema,
|
|
5212
|
-
destination:
|
|
5730
|
+
destination: bridgeDestinationSchema$1,
|
|
5213
5731
|
token: zod.z.literal('USDC'),
|
|
5214
5732
|
config: zod.z.object({
|
|
5215
5733
|
transferSpeed: zod.z.nativeEnum(exports.TransferSpeed).optional(),
|
|
@@ -5226,6 +5744,28 @@ zod.z.object({
|
|
|
5226
5744
|
}),
|
|
5227
5745
|
});
|
|
5228
5746
|
|
|
5747
|
+
/**
|
|
5748
|
+
* Creates a Zod superRefine validator for recipient address format validation.
|
|
5749
|
+
* Validates that the address format matches the expected format for the chain type.
|
|
5750
|
+
*
|
|
5751
|
+
* @returns A superRefine function that validates recipientAddress against chain type
|
|
5752
|
+
*/
|
|
5753
|
+
function createRecipientAddressValidator() {
|
|
5754
|
+
return (data, ctx) => {
|
|
5755
|
+
const chain = data.chain;
|
|
5756
|
+
if (chain === null) {
|
|
5757
|
+
return;
|
|
5758
|
+
}
|
|
5759
|
+
if (!isValidAddressForChain(data.recipientAddress, chain)) {
|
|
5760
|
+
const chainInfo = extractChainInfo(chain);
|
|
5761
|
+
ctx.addIssue({
|
|
5762
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5763
|
+
path: ['recipientAddress'],
|
|
5764
|
+
message: `Invalid address format for ${String(chainInfo.name)}. Expected ${chainInfo.expectedAddressFormat}, but received: ${data.recipientAddress}`,
|
|
5765
|
+
});
|
|
5766
|
+
}
|
|
5767
|
+
};
|
|
5768
|
+
}
|
|
5229
5769
|
/**
|
|
5230
5770
|
* Schema for validating AdapterContext for bridge operations.
|
|
5231
5771
|
* Must always contain both adapter and chain explicitly.
|
|
@@ -5245,32 +5785,52 @@ const adapterContextSchema = zod.z.object({
|
|
|
5245
5785
|
const bridgeDestinationWithAddressSchema = adapterContextSchema
|
|
5246
5786
|
.extend({
|
|
5247
5787
|
recipientAddress: zod.z.string().min(1, 'Recipient address is required'),
|
|
5788
|
+
useForwarder: zod.z.boolean().optional(),
|
|
5248
5789
|
})
|
|
5249
|
-
.superRefine((
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
ctx.addIssue({
|
|
5257
|
-
code: zod.z.ZodIssueCode.custom,
|
|
5258
|
-
path: ['recipientAddress'],
|
|
5259
|
-
message: `Invalid address format for ${String(chainInfo.name)}. Expected ${chainInfo.expectedAddressFormat}, but received: ${data.recipientAddress}`,
|
|
5260
|
-
});
|
|
5261
|
-
}
|
|
5790
|
+
.superRefine(createRecipientAddressValidator());
|
|
5791
|
+
/**
|
|
5792
|
+
* Schema for validating AdapterContext with optional useForwarder.
|
|
5793
|
+
* Extends adapterContextSchema with the useForwarder flag.
|
|
5794
|
+
*/
|
|
5795
|
+
const adapterContextWithForwarderSchema = adapterContextSchema.extend({
|
|
5796
|
+
useForwarder: zod.z.boolean().optional(),
|
|
5262
5797
|
});
|
|
5798
|
+
/**
|
|
5799
|
+
* Schema for validating ForwarderDestination objects.
|
|
5800
|
+
* Used when useForwarder is true and no adapter is provided.
|
|
5801
|
+
* Requires chain, recipientAddress, and useForwarder: true.
|
|
5802
|
+
*
|
|
5803
|
+
* When using this destination type:
|
|
5804
|
+
* - The mint step completes when the IRIS API confirms forwardState === 'CONFIRMED'
|
|
5805
|
+
* - No on-chain transaction confirmation is performed (no adapter available)
|
|
5806
|
+
* - The mint step's data field will be undefined (no transaction receipt)
|
|
5807
|
+
*/
|
|
5808
|
+
const forwarderDestinationSchema = zod.z
|
|
5809
|
+
.object({
|
|
5810
|
+
chain: bridgeChainIdentifierSchema,
|
|
5811
|
+
recipientAddress: zod.z.string().min(1, 'Recipient address is required'),
|
|
5812
|
+
useForwarder: zod.z.literal(true),
|
|
5813
|
+
})
|
|
5814
|
+
.strict()
|
|
5815
|
+
.superRefine(createRecipientAddressValidator());
|
|
5263
5816
|
/**
|
|
5264
5817
|
* Schema for validating BridgeDestination union type.
|
|
5265
|
-
*
|
|
5818
|
+
* Supports three destination configurations:
|
|
5819
|
+
* - BridgeDestinationWithAddress (adapter with explicit recipient)
|
|
5820
|
+
* - ForwarderDestination (no adapter, requires useForwarder: true and recipientAddress)
|
|
5821
|
+
* - AdapterContext with optional useForwarder (adapter for default recipient)
|
|
5266
5822
|
*
|
|
5267
|
-
*
|
|
5268
|
-
*
|
|
5269
|
-
*
|
|
5823
|
+
* When using ForwarderDestination (no adapter):
|
|
5824
|
+
* - The mint step completes when the IRIS API confirms forwardState === 'CONFIRMED'
|
|
5825
|
+
* - No on-chain transaction confirmation is performed
|
|
5826
|
+
*
|
|
5827
|
+
* The order matters: we check the more specific schemas first.
|
|
5828
|
+
* This ensures that objects with specific fields are matched correctly.
|
|
5270
5829
|
*/
|
|
5271
5830
|
const bridgeDestinationSchema = zod.z.union([
|
|
5272
5831
|
bridgeDestinationWithAddressSchema,
|
|
5273
|
-
|
|
5832
|
+
forwarderDestinationSchema,
|
|
5833
|
+
adapterContextWithForwarderSchema.strict(),
|
|
5274
5834
|
]);
|
|
5275
5835
|
/**
|
|
5276
5836
|
* Schema for validating bridge parameters with chain identifiers.
|
|
@@ -5344,43 +5904,1295 @@ const bridgeParamsWithChainIdentifierSchema = zod.z.object({
|
|
|
5344
5904
|
});
|
|
5345
5905
|
|
|
5346
5906
|
/**
|
|
5347
|
-
*
|
|
5348
|
-
*
|
|
5349
|
-
* Both AdapterContext and BridgeDestinationWithAddress have the chain property
|
|
5350
|
-
* at the top level, so we can directly access it from either type.
|
|
5907
|
+
* Default clock implementation using `Date.now()`.
|
|
5351
5908
|
*
|
|
5352
|
-
* @
|
|
5353
|
-
*
|
|
5354
|
-
*
|
|
5909
|
+
* @remarks
|
|
5910
|
+
* Use this in production code. For testing, inject a mock clock
|
|
5911
|
+
* that returns controlled timestamps.
|
|
5355
5912
|
*
|
|
5356
5913
|
* @example
|
|
5357
5914
|
* ```typescript
|
|
5358
|
-
* import {
|
|
5359
|
-
*
|
|
5360
|
-
* // AdapterContext
|
|
5361
|
-
* const chain1 = resolveChainDefinition({
|
|
5362
|
-
* adapter: mockAdapter,
|
|
5363
|
-
* chain: 'Ethereum'
|
|
5364
|
-
* })
|
|
5915
|
+
* import { defaultClock } from '@core/runtime'
|
|
5365
5916
|
*
|
|
5366
|
-
*
|
|
5367
|
-
*
|
|
5368
|
-
*
|
|
5369
|
-
* chain: 'Base',
|
|
5370
|
-
* recipientAddress: '0x123...'
|
|
5371
|
-
* })
|
|
5917
|
+
* const start = defaultClock.now()
|
|
5918
|
+
* // ... do work ...
|
|
5919
|
+
* const elapsed = defaultClock.since(start)
|
|
5372
5920
|
* ```
|
|
5373
5921
|
*/
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5922
|
+
const defaultClock = {
|
|
5923
|
+
now: () => Date.now(),
|
|
5924
|
+
since: (start) => Date.now() - start,
|
|
5925
|
+
};
|
|
5926
|
+
|
|
5377
5927
|
/**
|
|
5378
|
-
*
|
|
5928
|
+
* ID generation utilities for distributed tracing.
|
|
5379
5929
|
*
|
|
5380
|
-
*
|
|
5381
|
-
*
|
|
5930
|
+
* @remarks
|
|
5931
|
+
* | Function | Format | Use Case |
|
|
5932
|
+
* |----------|--------|----------|
|
|
5933
|
+
* | {@link createTraceId} | 32-char hex | Standard traceId (OpenTelemetry) |
|
|
5934
|
+
* | {@link createShortOpId} | 8-char hex | Standard opId |
|
|
5935
|
+
* | {@link createOpId} | `op-{ts}-{rand}` | Timestamped operation IDs |
|
|
5936
|
+
* | {@link createUuidV4} | RFC 4122 UUID | External system compatibility |
|
|
5937
|
+
* | {@link createId} | `{prefix}-{ts}-{rand}` | Custom prefixed IDs |
|
|
5382
5938
|
*
|
|
5383
|
-
*
|
|
5939
|
+
* @packageDocumentation
|
|
5940
|
+
*/
|
|
5941
|
+
// ============================================================================
|
|
5942
|
+
// Crypto Utilities (Internal)
|
|
5943
|
+
// ============================================================================
|
|
5944
|
+
/** @internal */
|
|
5945
|
+
function hasGetRandomValues() {
|
|
5946
|
+
return (typeof crypto !== 'undefined' &&
|
|
5947
|
+
typeof crypto.getRandomValues === 'function');
|
|
5948
|
+
}
|
|
5949
|
+
/**
|
|
5950
|
+
* Create a W3C/OpenTelemetry-compatible trace ID.
|
|
5951
|
+
*
|
|
5952
|
+
* @returns 32-character lowercase hex string (128-bit).
|
|
5953
|
+
*
|
|
5954
|
+
* @remarks
|
|
5955
|
+
* **Standard function for generating `traceId` values.** Compatible with
|
|
5956
|
+
* OpenTelemetry, Jaeger, Zipkin, and AWS X-Ray.
|
|
5957
|
+
*
|
|
5958
|
+
* @example
|
|
5959
|
+
* ```typescript
|
|
5960
|
+
* const traceId = createTraceId() // "a1b2c3d4e5f6789012345678abcdef00"
|
|
5961
|
+
* ```
|
|
5962
|
+
*/
|
|
5963
|
+
function createTraceId() {
|
|
5964
|
+
const bytes = new Uint8Array(16);
|
|
5965
|
+
if (hasGetRandomValues()) {
|
|
5966
|
+
crypto.getRandomValues(bytes);
|
|
5967
|
+
}
|
|
5968
|
+
else {
|
|
5969
|
+
for (let i = 0; i < 16; i++) {
|
|
5970
|
+
bytes[i] = Math.floor(Math.random() * 256); // NOSONAR:
|
|
5971
|
+
}
|
|
5972
|
+
}
|
|
5973
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
5974
|
+
}
|
|
5975
|
+
|
|
5976
|
+
/**
|
|
5977
|
+
* Clean tags by removing keys with undefined values.
|
|
5978
|
+
*
|
|
5979
|
+
* @param tags - Tags object, may contain undefined values. Accepts `undefined` or `null`.
|
|
5980
|
+
* @returns A new object with undefined values removed, or empty object if input is nullish.
|
|
5981
|
+
*
|
|
5982
|
+
* @remarks
|
|
5983
|
+
* This helper ensures tags are safe for serialization and logging.
|
|
5984
|
+
* Similar to the internal `cleanUndefined` in the logger module, but
|
|
5985
|
+
* typed specifically for {@link Tags}.
|
|
5986
|
+
*
|
|
5987
|
+
* @example
|
|
5988
|
+
* ```typescript
|
|
5989
|
+
* import { cleanTags } from '@core/runtime'
|
|
5990
|
+
*
|
|
5991
|
+
* const tags = { chain: 'Ethereum', amount: 100, extra: undefined }
|
|
5992
|
+
* const cleaned = cleanTags(tags)
|
|
5993
|
+
* // { chain: 'Ethereum', amount: 100 }
|
|
5994
|
+
*
|
|
5995
|
+
* const empty = cleanTags(undefined)
|
|
5996
|
+
* // {}
|
|
5997
|
+
* ```
|
|
5998
|
+
*/
|
|
5999
|
+
function cleanTags(tags) {
|
|
6000
|
+
if (tags == null)
|
|
6001
|
+
return {};
|
|
6002
|
+
return Object.fromEntries(Object.entries(tags).filter(([, value]) => value !== undefined));
|
|
6003
|
+
}
|
|
6004
|
+
|
|
6005
|
+
/**
|
|
6006
|
+
* Check if a pattern is valid.
|
|
6007
|
+
*
|
|
6008
|
+
* @remarks
|
|
6009
|
+
* Invalid patterns:
|
|
6010
|
+
* - Empty segments (e.g., `a..b`, `.a`, `a.`)
|
|
6011
|
+
* - `**` not at end (e.g., `a.**.b`)
|
|
6012
|
+
*
|
|
6013
|
+
* @param pattern - The pattern to validate.
|
|
6014
|
+
* @returns True if valid, false otherwise.
|
|
6015
|
+
*/
|
|
6016
|
+
function isValidPattern(pattern) {
|
|
6017
|
+
// Empty pattern is valid (matches empty topic)
|
|
6018
|
+
if (pattern === '')
|
|
6019
|
+
return true;
|
|
6020
|
+
const segments = pattern.split('.');
|
|
6021
|
+
// Check for empty segments
|
|
6022
|
+
if (segments.includes(''))
|
|
6023
|
+
return false;
|
|
6024
|
+
// Check that ** only appears at the end
|
|
6025
|
+
const doubleStarIndex = segments.indexOf('**');
|
|
6026
|
+
if (doubleStarIndex !== -1 && doubleStarIndex !== segments.length - 1) {
|
|
6027
|
+
return false;
|
|
6028
|
+
}
|
|
6029
|
+
return true;
|
|
6030
|
+
}
|
|
6031
|
+
/**
|
|
6032
|
+
* Convert a pattern to a regular expression.
|
|
6033
|
+
*
|
|
6034
|
+
* @param pattern - The pattern to convert.
|
|
6035
|
+
* @returns A RegExp that matches topics according to the pattern.
|
|
6036
|
+
*/
|
|
6037
|
+
function patternToRegex(pattern) {
|
|
6038
|
+
const segments = pattern.split('.');
|
|
6039
|
+
// Handle ** at end specially to match zero or more segments
|
|
6040
|
+
const lastSegment = segments.at(-1);
|
|
6041
|
+
if (lastSegment === '**') {
|
|
6042
|
+
// Everything before ** must match, then optionally more segments
|
|
6043
|
+
const prefix = segments
|
|
6044
|
+
.slice(0, -1)
|
|
6045
|
+
.map((seg) => {
|
|
6046
|
+
if (seg === '*')
|
|
6047
|
+
return '[^.]+';
|
|
6048
|
+
return seg.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
|
|
6049
|
+
})
|
|
6050
|
+
.join(String.raw `\.`);
|
|
6051
|
+
// ** matches zero or more segments:
|
|
6052
|
+
// - If prefix is empty (**), match anything
|
|
6053
|
+
// - Otherwise, match prefix, then optionally (dot + more content)
|
|
6054
|
+
if (prefix === '') {
|
|
6055
|
+
return /^.*$/;
|
|
6056
|
+
}
|
|
6057
|
+
return new RegExp(String.raw `^${prefix}(?:\..*)?$`);
|
|
6058
|
+
}
|
|
6059
|
+
// No ** - just convert segments normally
|
|
6060
|
+
const escaped = segments
|
|
6061
|
+
.map((segment) => {
|
|
6062
|
+
if (segment === '*')
|
|
6063
|
+
return '[^.]+';
|
|
6064
|
+
return segment.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
|
|
6065
|
+
})
|
|
6066
|
+
.join(String.raw `\.`);
|
|
6067
|
+
return new RegExp(String.raw `^${escaped}$`);
|
|
6068
|
+
}
|
|
6069
|
+
/**
|
|
6070
|
+
* Execute an event handler with error isolation.
|
|
6071
|
+
*
|
|
6072
|
+
* @param handler - The handler function to execute.
|
|
6073
|
+
* @param event - The event to pass to the handler.
|
|
6074
|
+
* @param logger - Optional logger for error reporting.
|
|
6075
|
+
*
|
|
6076
|
+
* @remarks
|
|
6077
|
+
* - Synchronous errors are caught and logged
|
|
6078
|
+
* - Promise rejections are caught without awaiting
|
|
6079
|
+
* - Handler errors never propagate to caller
|
|
6080
|
+
*/
|
|
6081
|
+
function executeHandler(handler, event, logger) {
|
|
6082
|
+
try {
|
|
6083
|
+
const result = handler(event);
|
|
6084
|
+
// Handle promise rejection without awaiting
|
|
6085
|
+
if (result instanceof Promise) {
|
|
6086
|
+
result.catch((err) => {
|
|
6087
|
+
logger?.error('Event handler promise rejected', {
|
|
6088
|
+
event: event.name,
|
|
6089
|
+
error: extractErrorInfo(err),
|
|
6090
|
+
});
|
|
6091
|
+
});
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6094
|
+
catch (err) {
|
|
6095
|
+
// Isolate handler errors
|
|
6096
|
+
logger?.error('Event handler threw', {
|
|
6097
|
+
event: event.name,
|
|
6098
|
+
error: extractErrorInfo(err),
|
|
6099
|
+
});
|
|
6100
|
+
}
|
|
6101
|
+
}
|
|
6102
|
+
/**
|
|
6103
|
+
* Create an event bus.
|
|
6104
|
+
*
|
|
6105
|
+
* @param opts - Options for the event bus.
|
|
6106
|
+
* @returns An EventBus instance.
|
|
6107
|
+
*
|
|
6108
|
+
* @example
|
|
6109
|
+
* ```typescript
|
|
6110
|
+
* import { createEventBus } from '@core/runtime'
|
|
6111
|
+
*
|
|
6112
|
+
* const bus = createEventBus({ logger })
|
|
6113
|
+
*
|
|
6114
|
+
* // Subscribe to events
|
|
6115
|
+
* const unsubscribe = bus.on('tx.*', (event) => {
|
|
6116
|
+
* console.log('Transaction event:', event.name)
|
|
6117
|
+
* })
|
|
6118
|
+
*
|
|
6119
|
+
* // Emit events
|
|
6120
|
+
* bus.emit({ name: 'tx.sent', data: { txHash: '0x123' } })
|
|
6121
|
+
*
|
|
6122
|
+
* // Unsubscribe
|
|
6123
|
+
* unsubscribe()
|
|
6124
|
+
* ```
|
|
6125
|
+
*/
|
|
6126
|
+
function createEventBus(opts) {
|
|
6127
|
+
const logger = opts?.logger;
|
|
6128
|
+
const subscriptions = new Set();
|
|
6129
|
+
function createBus(baseTags) {
|
|
6130
|
+
return {
|
|
6131
|
+
emit(event) {
|
|
6132
|
+
// Invalid topic names silently don't match any subscriptions
|
|
6133
|
+
if (!isValidPattern(event.name))
|
|
6134
|
+
return;
|
|
6135
|
+
const mergedTags = Object.keys(baseTags).length > 0 || event.tags
|
|
6136
|
+
? { ...baseTags, ...cleanTags(event.tags) }
|
|
6137
|
+
: undefined;
|
|
6138
|
+
const finalEvent = mergedTags === undefined ? event : { ...event, tags: mergedTags };
|
|
6139
|
+
// Notify all matching subscribers
|
|
6140
|
+
for (const sub of subscriptions) {
|
|
6141
|
+
// Match-all subscription
|
|
6142
|
+
if (sub.pattern === null) {
|
|
6143
|
+
executeHandler(sub.handler, finalEvent, logger);
|
|
6144
|
+
continue;
|
|
6145
|
+
}
|
|
6146
|
+
// Patterned subscription: invalid pattern => regex=null => never match
|
|
6147
|
+
if (sub.regex === null)
|
|
6148
|
+
continue;
|
|
6149
|
+
if (!sub.regex.test(event.name))
|
|
6150
|
+
continue;
|
|
6151
|
+
executeHandler(sub.handler, finalEvent, logger);
|
|
6152
|
+
}
|
|
6153
|
+
},
|
|
6154
|
+
child(tags) {
|
|
6155
|
+
// Merge and clean tags, don't mutate parent
|
|
6156
|
+
const childTags = { ...baseTags, ...cleanTags(tags) };
|
|
6157
|
+
return createBus(childTags);
|
|
6158
|
+
},
|
|
6159
|
+
on(patternOrHandler, handler) {
|
|
6160
|
+
let sub;
|
|
6161
|
+
if (typeof patternOrHandler === 'function') {
|
|
6162
|
+
// on(handler) - subscribe to all
|
|
6163
|
+
sub = { pattern: null, handler: patternOrHandler, regex: null };
|
|
6164
|
+
}
|
|
6165
|
+
else {
|
|
6166
|
+
// on(pattern, handler)
|
|
6167
|
+
if (typeof handler !== 'function') {
|
|
6168
|
+
throw new TypeError(`EventBus.on("${patternOrHandler}") expected a function handler`);
|
|
6169
|
+
}
|
|
6170
|
+
const regex = isValidPattern(patternOrHandler)
|
|
6171
|
+
? patternToRegex(patternOrHandler)
|
|
6172
|
+
: null;
|
|
6173
|
+
sub = {
|
|
6174
|
+
pattern: patternOrHandler,
|
|
6175
|
+
handler,
|
|
6176
|
+
regex,
|
|
6177
|
+
};
|
|
6178
|
+
}
|
|
6179
|
+
subscriptions.add(sub);
|
|
6180
|
+
// Return unsubscribe function (idempotent)
|
|
6181
|
+
let unsubscribed = false;
|
|
6182
|
+
return () => {
|
|
6183
|
+
if (!unsubscribed) {
|
|
6184
|
+
subscriptions.delete(sub);
|
|
6185
|
+
unsubscribed = true;
|
|
6186
|
+
}
|
|
6187
|
+
};
|
|
6188
|
+
},
|
|
6189
|
+
};
|
|
6190
|
+
}
|
|
6191
|
+
return createBus({});
|
|
6192
|
+
}
|
|
6193
|
+
|
|
6194
|
+
/** No-op counter that discards all increments. */
|
|
6195
|
+
const noopCounter = {
|
|
6196
|
+
inc: () => {
|
|
6197
|
+
// Intentionally empty - discards increment
|
|
6198
|
+
},
|
|
6199
|
+
};
|
|
6200
|
+
/** No-op histogram that discards all observations. */
|
|
6201
|
+
const noopHistogram = {
|
|
6202
|
+
observe: () => {
|
|
6203
|
+
// Intentionally empty - discards observation
|
|
6204
|
+
},
|
|
6205
|
+
};
|
|
6206
|
+
/** No-op timer that returns an empty stop function. */
|
|
6207
|
+
const noopTimer = {
|
|
6208
|
+
start: () => () => {
|
|
6209
|
+
// Intentionally empty - discards timing
|
|
6210
|
+
},
|
|
6211
|
+
};
|
|
6212
|
+
/**
|
|
6213
|
+
* Create a no-op metrics instance.
|
|
6214
|
+
*
|
|
6215
|
+
* @returns A Metrics instance that discards all operations.
|
|
6216
|
+
*/
|
|
6217
|
+
function createNoopMetrics() {
|
|
6218
|
+
// Self-referential instance - child() returns same object to avoid allocations
|
|
6219
|
+
const instance = {
|
|
6220
|
+
counter: () => noopCounter,
|
|
6221
|
+
histogram: () => noopHistogram,
|
|
6222
|
+
timer: () => noopTimer,
|
|
6223
|
+
child: () => instance,
|
|
6224
|
+
};
|
|
6225
|
+
return instance;
|
|
6226
|
+
}
|
|
6227
|
+
/**
|
|
6228
|
+
* A no-op metrics implementation that discards all operations.
|
|
6229
|
+
*
|
|
6230
|
+
* @remarks
|
|
6231
|
+
* Use this when metrics collection is disabled or not configured.
|
|
6232
|
+
* All metric operations are safe to call and return immediately.
|
|
6233
|
+
* The `child()` method returns the same instance to avoid allocations.
|
|
6234
|
+
*
|
|
6235
|
+
* @example
|
|
6236
|
+
* ```typescript
|
|
6237
|
+
* import { noopMetrics } from '@core/runtime'
|
|
6238
|
+
*
|
|
6239
|
+
* // Use when metrics are disabled
|
|
6240
|
+
* const metrics = config.metricsEnabled
|
|
6241
|
+
* ? createDatadogMetrics(config)
|
|
6242
|
+
* : noopMetrics
|
|
6243
|
+
*
|
|
6244
|
+
* // All operations are safe to call
|
|
6245
|
+
* metrics.counter('requests').inc()
|
|
6246
|
+
* const stop = metrics.timer('query').start()
|
|
6247
|
+
* stop()
|
|
6248
|
+
* ```
|
|
6249
|
+
*/
|
|
6250
|
+
const noopMetrics = createNoopMetrics();
|
|
6251
|
+
|
|
6252
|
+
/**
|
|
6253
|
+
* Define a schema for runtime logger interfaces.
|
|
6254
|
+
*
|
|
6255
|
+
* @remarks
|
|
6256
|
+
* Validate that a runtime logger provides the minimal methods expected by the SDK.
|
|
6257
|
+
*
|
|
6258
|
+
* @example
|
|
6259
|
+
* ```typescript
|
|
6260
|
+
* import { loggerSchema } from '@core/runtime'
|
|
6261
|
+
*
|
|
6262
|
+
* const logger = {
|
|
6263
|
+
* debug: () => undefined,
|
|
6264
|
+
* info: () => undefined,
|
|
6265
|
+
* warn: () => undefined,
|
|
6266
|
+
* error: () => undefined,
|
|
6267
|
+
* child: () => logger,
|
|
6268
|
+
* }
|
|
6269
|
+
*
|
|
6270
|
+
* loggerSchema.parse(logger)
|
|
6271
|
+
* ```
|
|
6272
|
+
*/
|
|
6273
|
+
const loggerSchema = zod.z.custom((value) => {
|
|
6274
|
+
if (value === null || typeof value !== 'object') {
|
|
6275
|
+
return false;
|
|
6276
|
+
}
|
|
6277
|
+
const record = value;
|
|
6278
|
+
return (typeof record['debug'] === 'function' &&
|
|
6279
|
+
typeof record['info'] === 'function' &&
|
|
6280
|
+
typeof record['warn'] === 'function' &&
|
|
6281
|
+
typeof record['error'] === 'function' &&
|
|
6282
|
+
typeof record['child'] === 'function');
|
|
6283
|
+
}, {
|
|
6284
|
+
message: 'Invalid logger',
|
|
6285
|
+
});
|
|
6286
|
+
|
|
6287
|
+
/**
|
|
6288
|
+
* Define a schema for runtime metrics interfaces.
|
|
6289
|
+
*
|
|
6290
|
+
* @remarks
|
|
6291
|
+
* Validate that a metrics implementation exposes the minimal API expected by the runtime.
|
|
6292
|
+
*
|
|
6293
|
+
* @example
|
|
6294
|
+
* ```typescript
|
|
6295
|
+
* import { metricsSchema } from '@core/runtime'
|
|
6296
|
+
*
|
|
6297
|
+
* const metrics = {
|
|
6298
|
+
* counter: () => ({ inc: () => undefined }),
|
|
6299
|
+
* histogram: () => ({ observe: () => undefined }),
|
|
6300
|
+
* timer: () => ({ start: () => () => undefined }),
|
|
6301
|
+
* child: () => metrics,
|
|
6302
|
+
* }
|
|
6303
|
+
*
|
|
6304
|
+
* metricsSchema.parse(metrics)
|
|
6305
|
+
* ```
|
|
6306
|
+
*/
|
|
6307
|
+
const metricsSchema = zod.z.custom((value) => {
|
|
6308
|
+
if (value === null || typeof value !== 'object') {
|
|
6309
|
+
return false;
|
|
6310
|
+
}
|
|
6311
|
+
const record = value;
|
|
6312
|
+
return (typeof record['counter'] === 'function' &&
|
|
6313
|
+
typeof record['histogram'] === 'function' &&
|
|
6314
|
+
typeof record['timer'] === 'function' &&
|
|
6315
|
+
typeof record['child'] === 'function');
|
|
6316
|
+
}, {
|
|
6317
|
+
message: 'Invalid metrics',
|
|
6318
|
+
});
|
|
6319
|
+
|
|
6320
|
+
/**
|
|
6321
|
+
* Omit undefined values from an object.
|
|
6322
|
+
*
|
|
6323
|
+
* @param obj - The object to process.
|
|
6324
|
+
* @returns A new object with undefined values removed.
|
|
6325
|
+
*
|
|
6326
|
+
* @internal
|
|
6327
|
+
* @remarks
|
|
6328
|
+
* Used by both production and mock loggers to ensure consistent behavior.
|
|
6329
|
+
* This prevents undefined values from being serialized in log output,
|
|
6330
|
+
* which can cause issues with some log transports.
|
|
6331
|
+
*/
|
|
6332
|
+
function omitUndefined(obj) {
|
|
6333
|
+
const result = {};
|
|
6334
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
6335
|
+
if (value !== undefined) {
|
|
6336
|
+
result[key] = value;
|
|
6337
|
+
}
|
|
6338
|
+
}
|
|
6339
|
+
return result;
|
|
6340
|
+
}
|
|
6341
|
+
|
|
6342
|
+
/**
|
|
6343
|
+
* Default redaction paths for web3/blockchain SDKs.
|
|
6344
|
+
*
|
|
6345
|
+
* @remarks
|
|
6346
|
+
* These paths target common sensitive fields in blockchain applications.
|
|
6347
|
+
* All user fields are nested under `context`, so paths start with `context.`.
|
|
6348
|
+
* Wildcard `*` matches any key at that level.
|
|
6349
|
+
*/
|
|
6350
|
+
const DEFAULT_REDACT_PATHS = [
|
|
6351
|
+
// Generic Credentials
|
|
6352
|
+
'context.password',
|
|
6353
|
+
'context.passphrase',
|
|
6354
|
+
'context.secret',
|
|
6355
|
+
'context.token',
|
|
6356
|
+
'context.*.password',
|
|
6357
|
+
'context.*.passphrase',
|
|
6358
|
+
'context.*.secret',
|
|
6359
|
+
'context.*.token',
|
|
6360
|
+
// API Keys & Auth Tokens
|
|
6361
|
+
'context.apiKey',
|
|
6362
|
+
'context.apiSecret',
|
|
6363
|
+
'context.accessToken',
|
|
6364
|
+
'context.refreshToken',
|
|
6365
|
+
'context.jwt',
|
|
6366
|
+
'context.bearerToken',
|
|
6367
|
+
'context.sessionId',
|
|
6368
|
+
'context.authorization',
|
|
6369
|
+
'context.cookie',
|
|
6370
|
+
'context.*.apiKey',
|
|
6371
|
+
'context.*.apiSecret',
|
|
6372
|
+
'context.*.accessToken',
|
|
6373
|
+
'context.*.refreshToken',
|
|
6374
|
+
'context.*.jwt',
|
|
6375
|
+
'context.*.bearerToken',
|
|
6376
|
+
'context.*.sessionId',
|
|
6377
|
+
'context.*.authorization',
|
|
6378
|
+
'context.*.cookie',
|
|
6379
|
+
// Web3 / Crypto Keys
|
|
6380
|
+
'context.privateKey',
|
|
6381
|
+
'context.secretKey',
|
|
6382
|
+
'context.signingKey',
|
|
6383
|
+
'context.encryptionKey',
|
|
6384
|
+
'context.*.privateKey',
|
|
6385
|
+
'context.*.secretKey',
|
|
6386
|
+
'context.*.signingKey',
|
|
6387
|
+
'context.*.encryptionKey',
|
|
6388
|
+
// Web3 / Crypto Mnemonics and Seeds
|
|
6389
|
+
'context.mnemonic',
|
|
6390
|
+
'context.seed',
|
|
6391
|
+
'context.seedPhrase',
|
|
6392
|
+
'context.*.mnemonic',
|
|
6393
|
+
'context.*.seed',
|
|
6394
|
+
'context.*.seedPhrase',
|
|
6395
|
+
// OTP / Verification Codes
|
|
6396
|
+
'context.otp',
|
|
6397
|
+
'context.verificationCode',
|
|
6398
|
+
'context.*.otp',
|
|
6399
|
+
'context.*.verificationCode',
|
|
6400
|
+
// Payment Information
|
|
6401
|
+
'context.cardNumber',
|
|
6402
|
+
'context.cvv',
|
|
6403
|
+
'context.accountNumber',
|
|
6404
|
+
'context.*.cardNumber',
|
|
6405
|
+
'context.*.cvv',
|
|
6406
|
+
'context.*.accountNumber',
|
|
6407
|
+
];
|
|
6408
|
+
/**
|
|
6409
|
+
* Wrap user fields under `context` to prevent collision with pino internals.
|
|
6410
|
+
*
|
|
6411
|
+
* @param fields - User-provided log fields.
|
|
6412
|
+
* @returns Object with fields nested under `context`, or undefined if empty.
|
|
6413
|
+
*
|
|
6414
|
+
* @remarks
|
|
6415
|
+
* This function handles edge cases by returning undefined for null, undefined,
|
|
6416
|
+
* or empty objects to avoid unnecessary wrapping in log output.
|
|
6417
|
+
* Undefined values are cleaned before wrapping.
|
|
6418
|
+
*/
|
|
6419
|
+
function wrapInContext(fields) {
|
|
6420
|
+
if (!fields)
|
|
6421
|
+
return undefined;
|
|
6422
|
+
// Clean undefined values for consistency and transport compatibility
|
|
6423
|
+
const cleaned = omitUndefined(fields);
|
|
6424
|
+
// Handle edge case: all values were undefined, resulting in empty object
|
|
6425
|
+
const keys = Object.keys(cleaned);
|
|
6426
|
+
if (keys.length === 0)
|
|
6427
|
+
return undefined;
|
|
6428
|
+
return { context: cleaned };
|
|
6429
|
+
}
|
|
6430
|
+
/**
|
|
6431
|
+
* Wrap a pino instance to conform to our Logger interface.
|
|
6432
|
+
*
|
|
6433
|
+
* @param pinoInstance - The pino logger instance to wrap.
|
|
6434
|
+
* @returns A Logger instance conforming to our stable interface.
|
|
6435
|
+
*/
|
|
6436
|
+
function wrapPino(pinoInstance) {
|
|
6437
|
+
return {
|
|
6438
|
+
debug(message, fields) {
|
|
6439
|
+
const wrapped = wrapInContext(fields);
|
|
6440
|
+
if (wrapped) {
|
|
6441
|
+
pinoInstance.debug(wrapped, message);
|
|
6442
|
+
}
|
|
6443
|
+
else {
|
|
6444
|
+
pinoInstance.debug(message);
|
|
6445
|
+
}
|
|
6446
|
+
},
|
|
6447
|
+
info(message, fields) {
|
|
6448
|
+
const wrapped = wrapInContext(fields);
|
|
6449
|
+
if (wrapped) {
|
|
6450
|
+
pinoInstance.info(wrapped, message);
|
|
6451
|
+
}
|
|
6452
|
+
else {
|
|
6453
|
+
pinoInstance.info(message);
|
|
6454
|
+
}
|
|
6455
|
+
},
|
|
6456
|
+
warn(message, fields) {
|
|
6457
|
+
const wrapped = wrapInContext(fields);
|
|
6458
|
+
if (wrapped) {
|
|
6459
|
+
pinoInstance.warn(wrapped, message);
|
|
6460
|
+
}
|
|
6461
|
+
else {
|
|
6462
|
+
pinoInstance.warn(message);
|
|
6463
|
+
}
|
|
6464
|
+
},
|
|
6465
|
+
error(message, fields) {
|
|
6466
|
+
const wrapped = wrapInContext(fields);
|
|
6467
|
+
if (wrapped) {
|
|
6468
|
+
pinoInstance.error(wrapped, message);
|
|
6469
|
+
}
|
|
6470
|
+
else {
|
|
6471
|
+
pinoInstance.error(message);
|
|
6472
|
+
}
|
|
6473
|
+
},
|
|
6474
|
+
child(tags) {
|
|
6475
|
+
// Child bindings stay flat (not wrapped) - they're part of logger's base context
|
|
6476
|
+
const cleaned = omitUndefined(tags);
|
|
6477
|
+
return wrapPino(pinoInstance.child(cleaned));
|
|
6478
|
+
},
|
|
6479
|
+
};
|
|
6480
|
+
}
|
|
6481
|
+
/**
|
|
6482
|
+
* Build pino redact configuration from our simplified options.
|
|
6483
|
+
*
|
|
6484
|
+
* @param redact - The redact configuration option.
|
|
6485
|
+
* @returns Pino-compatible redact configuration or undefined.
|
|
6486
|
+
*/
|
|
6487
|
+
function buildRedactConfig(redact) {
|
|
6488
|
+
// Explicitly disabled
|
|
6489
|
+
if (redact === false) {
|
|
6490
|
+
return undefined;
|
|
6491
|
+
}
|
|
6492
|
+
// Custom paths provided
|
|
6493
|
+
if (Array.isArray(redact)) {
|
|
6494
|
+
return redact.length > 0
|
|
6495
|
+
? { paths: redact, censor: '[REDACTED]' }
|
|
6496
|
+
: undefined;
|
|
6497
|
+
}
|
|
6498
|
+
// Default: use web3 sensible defaults
|
|
6499
|
+
return {
|
|
6500
|
+
paths: [...DEFAULT_REDACT_PATHS],
|
|
6501
|
+
censor: '[REDACTED]',
|
|
6502
|
+
};
|
|
6503
|
+
}
|
|
6504
|
+
/**
|
|
6505
|
+
* Create a logger backed by pino.
|
|
6506
|
+
*
|
|
6507
|
+
* @param options - Logger options (optional).
|
|
6508
|
+
* @param stream - Destination stream (optional).
|
|
6509
|
+
* @returns A Logger instance.
|
|
6510
|
+
* @throws Error if invalid pino options are provided.
|
|
6511
|
+
*
|
|
6512
|
+
* @remarks
|
|
6513
|
+
* This is a thin wrapper around pino that exposes our stable Logger interface.
|
|
6514
|
+
* Pino handles all transport concerns: JSON, pretty printing, file, remote, browser, etc.
|
|
6515
|
+
*
|
|
6516
|
+
* **Security**: By default, sensitive web3 fields (privateKey, mnemonic, apiKey, etc.)
|
|
6517
|
+
* are automatically redacted from log output. Use `redact: false` to disable.
|
|
6518
|
+
*
|
|
6519
|
+
* @example
|
|
6520
|
+
* ```typescript
|
|
6521
|
+
* import { createLogger } from '@core/runtime'
|
|
6522
|
+
*
|
|
6523
|
+
* // Default: web3 sensitive fields are redacted
|
|
6524
|
+
* const logger = createLogger({ level: 'info' })
|
|
6525
|
+
* logger.info('Signing', { privateKey: '0x123...' })
|
|
6526
|
+
* // Output: { context: { privateKey: '[REDACTED]' }, msg: 'Signing' }
|
|
6527
|
+
*
|
|
6528
|
+
* // Disable redaction (use with caution)
|
|
6529
|
+
* const unsafeLogger = createLogger({ level: 'debug', redact: false })
|
|
6530
|
+
*
|
|
6531
|
+
* // Custom redaction paths
|
|
6532
|
+
* const customLogger = createLogger({
|
|
6533
|
+
* level: 'info',
|
|
6534
|
+
* redact: ['context.mySecret', 'context.*.credentials']
|
|
6535
|
+
* })
|
|
6536
|
+
*
|
|
6537
|
+
* // Pretty output for development
|
|
6538
|
+
* const devLogger = createLogger({
|
|
6539
|
+
* level: 'debug',
|
|
6540
|
+
* transport: { target: 'pino-pretty' }
|
|
6541
|
+
* })
|
|
6542
|
+
*
|
|
6543
|
+
* // Browser logger
|
|
6544
|
+
* const browserLogger = createLogger({
|
|
6545
|
+
* browser: { asObject: true }
|
|
6546
|
+
* })
|
|
6547
|
+
* ```
|
|
6548
|
+
*/
|
|
6549
|
+
function createLogger(options, stream) {
|
|
6550
|
+
const { redact, ...pinoOptions } = {};
|
|
6551
|
+
// Build redaction config
|
|
6552
|
+
const redactConfig = buildRedactConfig(redact);
|
|
6553
|
+
// Build final pino options, only include redact if defined
|
|
6554
|
+
const finalOptions = redactConfig
|
|
6555
|
+
? { ...pinoOptions, redact: redactConfig }
|
|
6556
|
+
: pinoOptions;
|
|
6557
|
+
const pinoInstance = pino__default(finalOptions);
|
|
6558
|
+
return wrapPino(pinoInstance);
|
|
6559
|
+
}
|
|
6560
|
+
|
|
6561
|
+
/**
|
|
6562
|
+
* Factory for creating Runtime instances with defaults.
|
|
6563
|
+
*
|
|
6564
|
+
* @packageDocumentation
|
|
6565
|
+
*/
|
|
6566
|
+
// ============================================================================
|
|
6567
|
+
// Validation Schema
|
|
6568
|
+
// ============================================================================
|
|
6569
|
+
/**
|
|
6570
|
+
* Schema for validating {@link RuntimeOptions}.
|
|
6571
|
+
*
|
|
6572
|
+
* @remarks
|
|
6573
|
+
* Used internally by {@link createRuntime} to validate options from JS consumers.
|
|
6574
|
+
* Exported for advanced use cases where manual validation is needed.
|
|
6575
|
+
*/
|
|
6576
|
+
const runtimeOptionsSchema = zod.z
|
|
6577
|
+
.object({
|
|
6578
|
+
logger: loggerSchema.optional(),
|
|
6579
|
+
metrics: metricsSchema.optional(),
|
|
6580
|
+
})
|
|
6581
|
+
.passthrough();
|
|
6582
|
+
// ============================================================================
|
|
6583
|
+
// Factory
|
|
6584
|
+
// ============================================================================
|
|
6585
|
+
/**
|
|
6586
|
+
* Create a complete Runtime with sensible defaults.
|
|
6587
|
+
*
|
|
6588
|
+
* @param options - Optional configuration to override logger and metrics.
|
|
6589
|
+
* @returns A frozen, immutable Runtime with all services guaranteed present.
|
|
6590
|
+
* @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
|
|
6591
|
+
*
|
|
6592
|
+
* @remarks
|
|
6593
|
+
* Creates a fully-configured runtime by merging provided options with defaults.
|
|
6594
|
+
* The returned runtime is frozen to enforce immutability.
|
|
6595
|
+
*
|
|
6596
|
+
* | Service | Default | Configurable |
|
|
6597
|
+
* |---------|---------|--------------|
|
|
6598
|
+
* | `logger` | pino logger (info level) | Yes |
|
|
6599
|
+
* | `metrics` | No-op metrics | Yes |
|
|
6600
|
+
* | `events` | Internal event bus | No |
|
|
6601
|
+
* | `clock` | `Date.now()` | No |
|
|
6602
|
+
*
|
|
6603
|
+
* **Why only logger and metrics?**
|
|
6604
|
+
*
|
|
6605
|
+
* - **Logger/Metrics**: Integration points with your infrastructure
|
|
6606
|
+
* - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
|
|
6607
|
+
* - **Clock**: Testing concern - use mock factories for tests
|
|
6608
|
+
*
|
|
6609
|
+
* @example
|
|
6610
|
+
* ```typescript
|
|
6611
|
+
* import { createRuntime, createLogger } from '@core/runtime'
|
|
6612
|
+
*
|
|
6613
|
+
* // Use all defaults
|
|
6614
|
+
* const runtime = createRuntime()
|
|
6615
|
+
*
|
|
6616
|
+
* // Custom logger
|
|
6617
|
+
* const runtime = createRuntime({
|
|
6618
|
+
* logger: createLogger({ level: 'debug' }),
|
|
6619
|
+
* })
|
|
6620
|
+
*
|
|
6621
|
+
* // Custom metrics (e.g., Prometheus)
|
|
6622
|
+
* const runtime = createRuntime({
|
|
6623
|
+
* metrics: myPrometheusMetrics,
|
|
6624
|
+
* })
|
|
6625
|
+
*
|
|
6626
|
+
* // Subscribe to events (don't replace the bus)
|
|
6627
|
+
* runtime.events.on('operation.*', (event) => {
|
|
6628
|
+
* console.log('Event:', event.name)
|
|
6629
|
+
* })
|
|
6630
|
+
* ```
|
|
6631
|
+
*/
|
|
6632
|
+
function createRuntime(options) {
|
|
6633
|
+
// Validate options for JS consumers
|
|
6634
|
+
if (options != null) {
|
|
6635
|
+
parseOrThrow(options, runtimeOptionsSchema, 'runtime options');
|
|
6636
|
+
}
|
|
6637
|
+
// Resolve logger first (events may need it)
|
|
6638
|
+
const logger = options?.logger ?? createLogger();
|
|
6639
|
+
// Internal services - not configurable
|
|
6640
|
+
const events = createEventBus({ logger });
|
|
6641
|
+
const clock = defaultClock;
|
|
6642
|
+
// Resolve metrics
|
|
6643
|
+
const metrics = options?.metrics ?? noopMetrics;
|
|
6644
|
+
return Object.freeze({ logger, events, metrics, clock });
|
|
6645
|
+
}
|
|
6646
|
+
|
|
6647
|
+
/**
|
|
6648
|
+
* Invocation context resolution - resolves the **WHO/HOW** of an operation.
|
|
6649
|
+
*
|
|
6650
|
+
* @packageDocumentation
|
|
6651
|
+
*/
|
|
6652
|
+
// ============================================================================
|
|
6653
|
+
// Validation Schemas
|
|
6654
|
+
// ============================================================================
|
|
6655
|
+
/**
|
|
6656
|
+
* Schema for validating Caller.
|
|
6657
|
+
*/
|
|
6658
|
+
const callerSchema = zod.z.object({
|
|
6659
|
+
type: zod.z.string(),
|
|
6660
|
+
name: zod.z.string(),
|
|
6661
|
+
version: zod.z.string().optional(),
|
|
6662
|
+
});
|
|
6663
|
+
/**
|
|
6664
|
+
* Schema for validating InvocationMeta input.
|
|
6665
|
+
*/
|
|
6666
|
+
const invocationMetaSchema = zod.z
|
|
6667
|
+
.object({
|
|
6668
|
+
traceId: zod.z.string().optional(),
|
|
6669
|
+
runtime: zod.z.object({}).passthrough().optional(),
|
|
6670
|
+
tokens: zod.z.object({}).passthrough().optional(),
|
|
6671
|
+
callers: zod.z.array(callerSchema).optional(),
|
|
6672
|
+
})
|
|
6673
|
+
.strict();
|
|
6674
|
+
// ============================================================================
|
|
6675
|
+
// Invocation Context Resolution
|
|
6676
|
+
// ============================================================================
|
|
6677
|
+
/**
|
|
6678
|
+
* Resolve invocation metadata to invocation context.
|
|
6679
|
+
*
|
|
6680
|
+
* @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
|
|
6681
|
+
* @param defaults - Default runtime and tokens to use if not overridden.
|
|
6682
|
+
* @returns Frozen, immutable invocation context with guaranteed values.
|
|
6683
|
+
* @throws KitError when meta contains invalid properties.
|
|
6684
|
+
*
|
|
6685
|
+
* @remarks
|
|
6686
|
+
* Resolves the **WHO** called and **HOW** to observe:
|
|
6687
|
+
* - TraceId: Uses provided value or generates new one
|
|
6688
|
+
* - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
|
|
6689
|
+
* - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
|
|
6690
|
+
* - Callers: Uses provided array or empty array
|
|
6691
|
+
*
|
|
6692
|
+
* The returned context is frozen to enforce immutability.
|
|
6693
|
+
*
|
|
6694
|
+
* @example
|
|
6695
|
+
* ```typescript
|
|
6696
|
+
* import { resolveInvocationContext, createRuntime } from '@core/runtime'
|
|
6697
|
+
* import { createTokenRegistry } from '@core/tokens'
|
|
6698
|
+
*
|
|
6699
|
+
* const defaults = {
|
|
6700
|
+
* runtime: createRuntime(),
|
|
6701
|
+
* tokens: createTokenRegistry(),
|
|
6702
|
+
* }
|
|
6703
|
+
*
|
|
6704
|
+
* // Minimal - just using defaults
|
|
6705
|
+
* const ctx = resolveInvocationContext(undefined, defaults)
|
|
6706
|
+
*
|
|
6707
|
+
* // With trace ID and caller info
|
|
6708
|
+
* const ctx = resolveInvocationContext(
|
|
6709
|
+
* {
|
|
6710
|
+
* traceId: 'abc-123',
|
|
6711
|
+
* callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
|
|
6712
|
+
* },
|
|
6713
|
+
* defaults
|
|
6714
|
+
* )
|
|
6715
|
+
*
|
|
6716
|
+
* // With runtime override (complete replacement)
|
|
6717
|
+
* const ctx = resolveInvocationContext(
|
|
6718
|
+
* { runtime: createRuntime({ logger: myLogger }) },
|
|
6719
|
+
* defaults
|
|
6720
|
+
* )
|
|
6721
|
+
* ```
|
|
6722
|
+
*/
|
|
6723
|
+
function resolveInvocationContext(meta, defaults) {
|
|
6724
|
+
// Validate meta input if provided
|
|
6725
|
+
if (meta !== undefined) {
|
|
6726
|
+
const result = invocationMetaSchema.safeParse(meta);
|
|
6727
|
+
if (!result.success) {
|
|
6728
|
+
throw createValidationFailedError$1('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
|
|
6729
|
+
}
|
|
6730
|
+
}
|
|
6731
|
+
// Generate trace ID if not provided
|
|
6732
|
+
const traceId = meta?.traceId ?? createTraceId();
|
|
6733
|
+
// Use meta overrides or fall back to defaults
|
|
6734
|
+
const runtime = meta?.runtime ?? defaults.runtime;
|
|
6735
|
+
const tokens = meta?.tokens ?? defaults.tokens;
|
|
6736
|
+
const callers = meta?.callers ?? [];
|
|
6737
|
+
return Object.freeze({ traceId, runtime, tokens, callers });
|
|
6738
|
+
}
|
|
6739
|
+
|
|
6740
|
+
/**
|
|
6741
|
+
* Extend an invocation context by appending a caller to its call chain.
|
|
6742
|
+
*
|
|
6743
|
+
* @param context - The existing invocation context to extend.
|
|
6744
|
+
* @param caller - The caller to append to the call chain.
|
|
6745
|
+
* @returns A new frozen invocation context with the caller appended.
|
|
6746
|
+
*
|
|
6747
|
+
* @remarks
|
|
6748
|
+
* This function creates a new immutable context with the caller appended
|
|
6749
|
+
* to the `callers` array while preserving all other context properties
|
|
6750
|
+
* (traceId, runtime, tokens).
|
|
6751
|
+
*
|
|
6752
|
+
* The returned context is frozen to enforce immutability.
|
|
6753
|
+
*
|
|
6754
|
+
* @example
|
|
6755
|
+
* ```typescript
|
|
6756
|
+
* import { extendInvocationContext } from '@core/runtime'
|
|
6757
|
+
*
|
|
6758
|
+
* const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
|
|
6759
|
+
* const extended = extendInvocationContext(existingContext, caller)
|
|
6760
|
+
* // extended.callers === [...existingContext.callers, caller]
|
|
6761
|
+
* ```
|
|
6762
|
+
*/
|
|
6763
|
+
function extendInvocationContext(context, caller) {
|
|
6764
|
+
return Object.freeze({
|
|
6765
|
+
traceId: context.traceId,
|
|
6766
|
+
runtime: context.runtime,
|
|
6767
|
+
tokens: context.tokens,
|
|
6768
|
+
callers: [...context.callers, caller],
|
|
6769
|
+
});
|
|
6770
|
+
}
|
|
6771
|
+
|
|
6772
|
+
// Clock - expose defaultClock for backward compatibility
|
|
6773
|
+
/** Clock validation schema (backward compatibility). */
|
|
6774
|
+
zod.z.custom((val) => val !== null &&
|
|
6775
|
+
typeof val === 'object' &&
|
|
6776
|
+
'now' in val &&
|
|
6777
|
+
typeof val['now'] === 'function');
|
|
6778
|
+
/** EventBus validation schema (backward compatibility). */
|
|
6779
|
+
zod.z.custom((val) => val !== null &&
|
|
6780
|
+
typeof val === 'object' &&
|
|
6781
|
+
'emit' in val &&
|
|
6782
|
+
typeof val['emit'] === 'function');
|
|
6783
|
+
/** Runtime validation schema (backward compatibility). */
|
|
6784
|
+
zod.z
|
|
6785
|
+
.object({
|
|
6786
|
+
logger: zod.z.any().optional(),
|
|
6787
|
+
events: zod.z.any().optional(),
|
|
6788
|
+
metrics: zod.z.any().optional(),
|
|
6789
|
+
clock: zod.z.any().optional(),
|
|
6790
|
+
})
|
|
6791
|
+
.passthrough();
|
|
6792
|
+
|
|
6793
|
+
/**
|
|
6794
|
+
* Create a structured error for token resolution failures.
|
|
6795
|
+
*
|
|
6796
|
+
* @remarks
|
|
6797
|
+
* Returns a `KitError` with `INPUT_INVALID_TOKEN` code (1006).
|
|
6798
|
+
* The error trace contains the selector and chain context for debugging.
|
|
6799
|
+
*
|
|
6800
|
+
* @param message - Human-readable error description.
|
|
6801
|
+
* @param selector - The token selector that failed to resolve.
|
|
6802
|
+
* @param chainId - The chain being resolved for (optional).
|
|
6803
|
+
* @param cause - The underlying error, if any (optional).
|
|
6804
|
+
* @returns A KitError with INPUT type and FATAL recoverability.
|
|
6805
|
+
*
|
|
6806
|
+
* @example
|
|
6807
|
+
* ```typescript
|
|
6808
|
+
* throw createTokenResolutionError(
|
|
6809
|
+
* 'Unknown token symbol: FAKE',
|
|
6810
|
+
* 'FAKE',
|
|
6811
|
+
* 'Ethereum'
|
|
6812
|
+
* )
|
|
6813
|
+
* ```
|
|
6814
|
+
*/
|
|
6815
|
+
function createTokenResolutionError(message, selector, chainId, cause) {
|
|
6816
|
+
const trace = {
|
|
6817
|
+
selector,
|
|
6818
|
+
...(chainId === undefined ? {} : { chainId }),
|
|
6819
|
+
...({} ),
|
|
6820
|
+
};
|
|
6821
|
+
return new KitError({
|
|
6822
|
+
...InputError.INVALID_TOKEN,
|
|
6823
|
+
recoverability: 'FATAL',
|
|
6824
|
+
message,
|
|
6825
|
+
cause: { trace },
|
|
6826
|
+
});
|
|
6827
|
+
}
|
|
6828
|
+
|
|
6829
|
+
/**
|
|
6830
|
+
* USDC token definition with addresses and metadata.
|
|
6831
|
+
*
|
|
6832
|
+
* @remarks
|
|
6833
|
+
* This is the built-in USDC definition used by the TokenRegistry.
|
|
6834
|
+
* Includes all known USDC addresses across supported chains.
|
|
6835
|
+
*
|
|
6836
|
+
* Keys use the `Blockchain` enum for type safety. Both enum values
|
|
6837
|
+
* and string literals are supported:
|
|
6838
|
+
* - `Blockchain.Ethereum` or `'Ethereum'`
|
|
6839
|
+
*
|
|
6840
|
+
* @example
|
|
6841
|
+
* ```typescript
|
|
6842
|
+
* import { USDC } from '@core/tokens'
|
|
6843
|
+
* import { Blockchain } from '@core/chains'
|
|
6844
|
+
*
|
|
6845
|
+
* console.log(USDC.symbol) // 'USDC'
|
|
6846
|
+
* console.log(USDC.decimals) // 6
|
|
6847
|
+
* console.log(USDC.locators[Blockchain.Ethereum])
|
|
6848
|
+
* // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
|
|
6849
|
+
* ```
|
|
6850
|
+
*/
|
|
6851
|
+
const USDC = {
|
|
6852
|
+
symbol: 'USDC',
|
|
6853
|
+
decimals: 6,
|
|
6854
|
+
locators: {
|
|
6855
|
+
// =========================================================================
|
|
6856
|
+
// Mainnets (alphabetically sorted)
|
|
6857
|
+
// =========================================================================
|
|
6858
|
+
[exports.Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
|
|
6859
|
+
[exports.Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
|
|
6860
|
+
[exports.Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
6861
|
+
[exports.Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
|
|
6862
|
+
[exports.Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
|
|
6863
|
+
[exports.Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
|
6864
|
+
[exports.Blockchain.Hedera]: '0.0.456858',
|
|
6865
|
+
[exports.Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
|
|
6866
|
+
[exports.Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
|
|
6867
|
+
[exports.Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
|
|
6868
|
+
[exports.Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
|
|
6869
|
+
[exports.Blockchain.Noble]: 'uusdc',
|
|
6870
|
+
[exports.Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
|
|
6871
|
+
[exports.Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
|
|
6872
|
+
[exports.Blockchain.Polkadot_Asset_Hub]: '1337',
|
|
6873
|
+
[exports.Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
|
|
6874
|
+
[exports.Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
|
|
6875
|
+
[exports.Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
6876
|
+
[exports.Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
|
|
6877
|
+
[exports.Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
|
|
6878
|
+
[exports.Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
|
|
6879
|
+
[exports.Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
|
|
6880
|
+
[exports.Blockchain.World_Chain]: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
|
|
6881
|
+
[exports.Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
|
|
6882
|
+
[exports.Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
|
|
6883
|
+
// =========================================================================
|
|
6884
|
+
// Testnets (alphabetically sorted)
|
|
6885
|
+
// =========================================================================
|
|
6886
|
+
[exports.Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
|
|
6887
|
+
[exports.Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
|
|
6888
|
+
[exports.Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
6889
|
+
[exports.Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
|
|
6890
|
+
[exports.Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
|
|
6891
|
+
[exports.Blockchain.Hedera_Testnet]: '0.0.429274',
|
|
6892
|
+
[exports.Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
|
|
6893
|
+
[exports.Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
|
|
6894
|
+
[exports.Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
|
|
6895
|
+
[exports.Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
|
|
6896
|
+
[exports.Blockchain.Noble_Testnet]: 'uusdc',
|
|
6897
|
+
[exports.Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
|
|
6898
|
+
[exports.Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
|
|
6899
|
+
[exports.Blockchain.Polkadot_Westmint]: '31337',
|
|
6900
|
+
[exports.Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
6901
|
+
[exports.Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
|
|
6902
|
+
[exports.Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
|
|
6903
|
+
[exports.Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
|
|
6904
|
+
[exports.Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
|
|
6905
|
+
[exports.Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
|
|
6906
|
+
[exports.Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
|
|
6907
|
+
[exports.Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
|
|
6908
|
+
[exports.Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
|
|
6909
|
+
[exports.Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
|
|
6910
|
+
},
|
|
6911
|
+
};
|
|
6912
|
+
|
|
6913
|
+
// Re-export for consumers
|
|
6914
|
+
/**
|
|
6915
|
+
* All default token definitions.
|
|
6916
|
+
*
|
|
6917
|
+
* @remarks
|
|
6918
|
+
* These tokens are automatically included in the TokenRegistry when created
|
|
6919
|
+
* without explicit defaults. Extensions can override these definitions.
|
|
6920
|
+
*
|
|
6921
|
+
* @example
|
|
6922
|
+
* ```typescript
|
|
6923
|
+
* import { createTokenRegistry } from '@core/tokens'
|
|
6924
|
+
*
|
|
6925
|
+
* // Registry uses these by default
|
|
6926
|
+
* const registry = createTokenRegistry()
|
|
6927
|
+
*
|
|
6928
|
+
* // Add custom tokens (built-ins are still included)
|
|
6929
|
+
* const customRegistry = createTokenRegistry({
|
|
6930
|
+
* tokens: [myCustomToken],
|
|
6931
|
+
* })
|
|
6932
|
+
* ```
|
|
6933
|
+
*/
|
|
6934
|
+
const DEFAULT_TOKENS = [USDC];
|
|
6935
|
+
|
|
6936
|
+
/**
|
|
6937
|
+
* Check if a selector is a raw token selector (object form).
|
|
6938
|
+
*
|
|
6939
|
+
* @param selector - The token selector to check.
|
|
6940
|
+
* @returns True if the selector is a raw token selector.
|
|
6941
|
+
*/
|
|
6942
|
+
function isRawSelector(selector) {
|
|
6943
|
+
return typeof selector === 'object' && 'locator' in selector;
|
|
6944
|
+
}
|
|
6945
|
+
/**
|
|
6946
|
+
* Normalize a symbol to uppercase for case-insensitive lookup.
|
|
6947
|
+
*
|
|
6948
|
+
* @param symbol - The symbol to normalize.
|
|
6949
|
+
* @returns The normalized (uppercase) symbol.
|
|
6950
|
+
*/
|
|
6951
|
+
function normalizeSymbol(symbol) {
|
|
6952
|
+
return symbol.toUpperCase();
|
|
6953
|
+
}
|
|
6954
|
+
/**
|
|
6955
|
+
* Create a token registry with built-in tokens and optional extensions.
|
|
6956
|
+
*
|
|
6957
|
+
* @remarks
|
|
6958
|
+
* The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
|
|
6959
|
+
* Custom tokens are merged on top - use this to add new tokens or override
|
|
6960
|
+
* built-in definitions.
|
|
6961
|
+
*
|
|
6962
|
+
* @param options - Configuration options for the registry.
|
|
6963
|
+
* @returns A token registry instance.
|
|
6964
|
+
*
|
|
6965
|
+
* @example
|
|
6966
|
+
* ```typescript
|
|
6967
|
+
* import { createTokenRegistry } from '@core/tokens'
|
|
6968
|
+
*
|
|
6969
|
+
* // Create registry with built-in tokens (USDC, etc.)
|
|
6970
|
+
* const registry = createTokenRegistry()
|
|
6971
|
+
*
|
|
6972
|
+
* // Resolve USDC on Ethereum
|
|
6973
|
+
* const usdc = registry.resolve('USDC', 'Ethereum')
|
|
6974
|
+
* console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
|
|
6975
|
+
* console.log(usdc.decimals) // 6
|
|
6976
|
+
* ```
|
|
6977
|
+
*
|
|
6978
|
+
* @example
|
|
6979
|
+
* ```typescript
|
|
6980
|
+
* // Add custom tokens (built-ins are still included)
|
|
6981
|
+
* const myToken: TokenDefinition = {
|
|
6982
|
+
* symbol: 'MY',
|
|
6983
|
+
* decimals: 18,
|
|
6984
|
+
* locators: { Ethereum: '0x...' },
|
|
6985
|
+
* }
|
|
6986
|
+
*
|
|
6987
|
+
* const registry = createTokenRegistry({ tokens: [myToken] })
|
|
6988
|
+
* registry.resolve('USDC', 'Ethereum') // Still works!
|
|
6989
|
+
* registry.resolve('MY', 'Ethereum') // Also works
|
|
6990
|
+
* ```
|
|
6991
|
+
*
|
|
6992
|
+
* @example
|
|
6993
|
+
* ```typescript
|
|
6994
|
+
* // Override a built-in token
|
|
6995
|
+
* const customUsdc: TokenDefinition = {
|
|
6996
|
+
* symbol: 'USDC',
|
|
6997
|
+
* decimals: 6,
|
|
6998
|
+
* locators: { MyChain: '0xCustomAddress' },
|
|
6999
|
+
* }
|
|
7000
|
+
*
|
|
7001
|
+
* const registry = createTokenRegistry({ tokens: [customUsdc] })
|
|
7002
|
+
* // Now USDC resolves to customUsdc definition
|
|
7003
|
+
* ```
|
|
7004
|
+
*
|
|
7005
|
+
* @example
|
|
7006
|
+
* ```typescript
|
|
7007
|
+
* // Resolve arbitrary tokens by raw locator
|
|
7008
|
+
* const registry = createTokenRegistry()
|
|
7009
|
+
* const token = registry.resolve(
|
|
7010
|
+
* { locator: '0x1234...', decimals: 18 },
|
|
7011
|
+
* 'Ethereum'
|
|
7012
|
+
* )
|
|
7013
|
+
* ```
|
|
7014
|
+
*/
|
|
7015
|
+
function createTokenRegistry(options = {}) {
|
|
7016
|
+
const { tokens = [], requireDecimals = false } = options;
|
|
7017
|
+
// Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
|
|
7018
|
+
const tokenMap = new Map();
|
|
7019
|
+
// Add built-in tokens first
|
|
7020
|
+
for (const def of DEFAULT_TOKENS) {
|
|
7021
|
+
tokenMap.set(normalizeSymbol(def.symbol), def);
|
|
7022
|
+
}
|
|
7023
|
+
// Custom tokens override built-ins
|
|
7024
|
+
for (const def of tokens) {
|
|
7025
|
+
tokenMap.set(normalizeSymbol(def.symbol), def);
|
|
7026
|
+
}
|
|
7027
|
+
/**
|
|
7028
|
+
* Resolve a symbol selector to token information.
|
|
7029
|
+
*/
|
|
7030
|
+
function resolveSymbol(symbol, chainId) {
|
|
7031
|
+
const normalizedSymbol = normalizeSymbol(symbol);
|
|
7032
|
+
const definition = tokenMap.get(normalizedSymbol);
|
|
7033
|
+
if (definition === undefined) {
|
|
7034
|
+
throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
|
|
7035
|
+
}
|
|
7036
|
+
const locator = definition.locators[chainId];
|
|
7037
|
+
if (locator === undefined || locator.trim() === '') {
|
|
7038
|
+
throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
|
|
7039
|
+
}
|
|
7040
|
+
return {
|
|
7041
|
+
symbol: definition.symbol,
|
|
7042
|
+
decimals: definition.decimals,
|
|
7043
|
+
locator,
|
|
7044
|
+
};
|
|
7045
|
+
}
|
|
7046
|
+
/**
|
|
7047
|
+
* Resolve a raw selector to token information.
|
|
7048
|
+
*/
|
|
7049
|
+
function resolveRaw(selector, chainId) {
|
|
7050
|
+
const { locator, decimals } = selector;
|
|
7051
|
+
// Validate locator
|
|
7052
|
+
if (!locator || typeof locator !== 'string') {
|
|
7053
|
+
throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
|
|
7054
|
+
}
|
|
7055
|
+
// Decimals are always required for raw selectors
|
|
7056
|
+
if (decimals === undefined) {
|
|
7057
|
+
const message = requireDecimals
|
|
7058
|
+
? 'Decimals required for raw token selector (requireDecimals: true)'
|
|
7059
|
+
: 'Decimals required for raw token selector. Provide { locator, decimals }.';
|
|
7060
|
+
throw createTokenResolutionError(message, selector, chainId);
|
|
7061
|
+
}
|
|
7062
|
+
// Validate decimals
|
|
7063
|
+
if (typeof decimals !== 'number' ||
|
|
7064
|
+
decimals < 0 ||
|
|
7065
|
+
!Number.isInteger(decimals)) {
|
|
7066
|
+
throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
|
|
7067
|
+
}
|
|
7068
|
+
return {
|
|
7069
|
+
decimals,
|
|
7070
|
+
locator,
|
|
7071
|
+
};
|
|
7072
|
+
}
|
|
7073
|
+
return {
|
|
7074
|
+
resolve(selector, chainId) {
|
|
7075
|
+
// Runtime validation of inputs - these checks are for JS consumers
|
|
7076
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
7077
|
+
if (selector === null || selector === undefined) {
|
|
7078
|
+
throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
|
|
7079
|
+
}
|
|
7080
|
+
if (chainId === '' || typeof chainId !== 'string') {
|
|
7081
|
+
throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
|
|
7082
|
+
}
|
|
7083
|
+
// Dispatch based on selector type
|
|
7084
|
+
if (isRawSelector(selector)) {
|
|
7085
|
+
return resolveRaw(selector, chainId);
|
|
7086
|
+
}
|
|
7087
|
+
if (typeof selector === 'string') {
|
|
7088
|
+
return resolveSymbol(selector, chainId);
|
|
7089
|
+
}
|
|
7090
|
+
throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
|
|
7091
|
+
},
|
|
7092
|
+
get(symbol) {
|
|
7093
|
+
if (!symbol || typeof symbol !== 'string') {
|
|
7094
|
+
return undefined;
|
|
7095
|
+
}
|
|
7096
|
+
return tokenMap.get(normalizeSymbol(symbol));
|
|
7097
|
+
},
|
|
7098
|
+
has(symbol) {
|
|
7099
|
+
if (!symbol || typeof symbol !== 'string') {
|
|
7100
|
+
return false;
|
|
7101
|
+
}
|
|
7102
|
+
return tokenMap.has(normalizeSymbol(symbol));
|
|
7103
|
+
},
|
|
7104
|
+
symbols() {
|
|
7105
|
+
return Array.from(tokenMap.values()).map((def) => def.symbol);
|
|
7106
|
+
},
|
|
7107
|
+
entries() {
|
|
7108
|
+
return Array.from(tokenMap.values());
|
|
7109
|
+
},
|
|
7110
|
+
};
|
|
7111
|
+
}
|
|
7112
|
+
|
|
7113
|
+
/**
|
|
7114
|
+
* Define a schema for token registry interfaces.
|
|
7115
|
+
*
|
|
7116
|
+
* @remarks
|
|
7117
|
+
* Validate that a registry exposes the `resolve()` API used by adapters.
|
|
7118
|
+
*
|
|
7119
|
+
* @example
|
|
7120
|
+
* ```typescript
|
|
7121
|
+
* import { tokenRegistrySchema } from '@core/tokens'
|
|
7122
|
+
*
|
|
7123
|
+
* const registry = {
|
|
7124
|
+
* resolve: () => ({ locator: '0x0', decimals: 6 }),
|
|
7125
|
+
* }
|
|
7126
|
+
*
|
|
7127
|
+
* tokenRegistrySchema.parse(registry)
|
|
7128
|
+
* ```
|
|
7129
|
+
*/
|
|
7130
|
+
zod.z.custom((value) => {
|
|
7131
|
+
if (value === null || typeof value !== 'object') {
|
|
7132
|
+
return false;
|
|
7133
|
+
}
|
|
7134
|
+
const record = value;
|
|
7135
|
+
return (typeof record['resolve'] === 'function' &&
|
|
7136
|
+
typeof record['get'] === 'function' &&
|
|
7137
|
+
typeof record['has'] === 'function' &&
|
|
7138
|
+
typeof record['symbols'] === 'function' &&
|
|
7139
|
+
typeof record['entries'] === 'function');
|
|
7140
|
+
}, {
|
|
7141
|
+
message: 'Invalid token registry',
|
|
7142
|
+
});
|
|
7143
|
+
|
|
7144
|
+
/**
|
|
7145
|
+
* Type guard to check if the destination is a forwarder-only destination.
|
|
7146
|
+
*
|
|
7147
|
+
* Forwarder-only destinations have `useForwarder: true` and no adapter.
|
|
7148
|
+
* They require a `recipientAddress` to be specified.
|
|
7149
|
+
*
|
|
7150
|
+
* @param dest - The bridge destination to check
|
|
7151
|
+
* @returns True if this is a forwarder-only destination without adapter
|
|
7152
|
+
*/
|
|
7153
|
+
function isForwarderOnlyDestination(dest) {
|
|
7154
|
+
return (dest.useForwarder === true &&
|
|
7155
|
+
!('adapter' in dest) &&
|
|
7156
|
+
'recipientAddress' in dest);
|
|
7157
|
+
}
|
|
7158
|
+
/**
|
|
7159
|
+
* Resolves a chain identifier to a chain definition.
|
|
7160
|
+
*
|
|
7161
|
+
* Both AdapterContext and BridgeDestinationWithAddress have the chain property
|
|
7162
|
+
* at the top level, so we can directly access it from either type.
|
|
7163
|
+
*
|
|
7164
|
+
* @param ctx - The bridge destination containing the chain identifier
|
|
7165
|
+
* @returns The resolved chain definition
|
|
7166
|
+
* @throws If the chain definition cannot be resolved
|
|
7167
|
+
*
|
|
7168
|
+
* @example
|
|
7169
|
+
* ```typescript
|
|
7170
|
+
* import { Blockchain } from '@core/chains'
|
|
7171
|
+
*
|
|
7172
|
+
* // AdapterContext
|
|
7173
|
+
* const chain1 = resolveChainDefinition({
|
|
7174
|
+
* adapter: mockAdapter,
|
|
7175
|
+
* chain: 'Ethereum'
|
|
7176
|
+
* })
|
|
7177
|
+
*
|
|
7178
|
+
* // BridgeDestinationWithAddress
|
|
7179
|
+
* const chain2 = resolveChainDefinition({
|
|
7180
|
+
* adapter: mockAdapter,
|
|
7181
|
+
* chain: 'Base',
|
|
7182
|
+
* recipientAddress: '0x123...'
|
|
7183
|
+
* })
|
|
7184
|
+
* ```
|
|
7185
|
+
*/
|
|
7186
|
+
function resolveChainDefinition(ctx) {
|
|
7187
|
+
return resolveChainIdentifier(ctx.chain);
|
|
7188
|
+
}
|
|
7189
|
+
/**
|
|
7190
|
+
* Resolves the signer's address from a bridge destination.
|
|
7191
|
+
*
|
|
7192
|
+
* This function resolves the address that will be used for transaction signing,
|
|
7193
|
+
* ignoring any `recipientAddress` field which is handled separately.
|
|
7194
|
+
*
|
|
7195
|
+
* It handles two cases:
|
|
5384
7196
|
* - Developer-controlled adapters - returns the explicit address from context
|
|
5385
7197
|
* - User-controlled adapters - calls getAddress() on the adapter
|
|
5386
7198
|
*
|
|
@@ -5453,6 +7265,46 @@ function resolveAmount(params) {
|
|
|
5453
7265
|
}
|
|
5454
7266
|
return params.amount;
|
|
5455
7267
|
}
|
|
7268
|
+
/**
|
|
7269
|
+
* Resolves the invocation context for a bridge operation.
|
|
7270
|
+
*
|
|
7271
|
+
* Takes optional invocation metadata and resolves it into a full
|
|
7272
|
+
* InvocationContext with BridgeKit caller information appended.
|
|
7273
|
+
*
|
|
7274
|
+
* @param invocationMeta - Optional invocation metadata for tracing and correlation
|
|
7275
|
+
* @returns An InvocationContext with traceId, runtime, tokens, and caller chain
|
|
7276
|
+
*
|
|
7277
|
+
* @example
|
|
7278
|
+
* ```typescript
|
|
7279
|
+
* // With user-provided invocation metadata
|
|
7280
|
+
* const invocation = resolveBridgeInvocation({
|
|
7281
|
+
* traceId: 'my-custom-trace-id',
|
|
7282
|
+
* callers: [{ type: 'app', name: 'MyDApp' }],
|
|
7283
|
+
* })
|
|
7284
|
+
* // invocation.traceId === 'my-custom-trace-id'
|
|
7285
|
+
* // invocation.callers === [{ type: 'app', name: 'MyDApp' }, { type: 'kit', name: 'BridgeKit' }]
|
|
7286
|
+
*
|
|
7287
|
+
* // Without invocation metadata (auto-generated traceId)
|
|
7288
|
+
* const invocation2 = resolveBridgeInvocation()
|
|
7289
|
+
* // invocation2.traceId === <auto-generated OpenTelemetry-compatible trace ID>
|
|
7290
|
+
* // invocation2.callers === [{ type: 'kit', name: 'BridgeKit' }]
|
|
7291
|
+
* ```
|
|
7292
|
+
*/
|
|
7293
|
+
function resolveBridgeInvocation(invocationMeta) {
|
|
7294
|
+
const bridgeKitCaller = {
|
|
7295
|
+
type: 'kit',
|
|
7296
|
+
name: 'BridgeKit',
|
|
7297
|
+
version: pkg.version,
|
|
7298
|
+
};
|
|
7299
|
+
// Create default runtime and tokens for invocation context resolution
|
|
7300
|
+
const defaults = {
|
|
7301
|
+
runtime: createRuntime(),
|
|
7302
|
+
tokens: createTokenRegistry(),
|
|
7303
|
+
};
|
|
7304
|
+
// Resolve invocation metadata to full context, then extend with BridgeKit caller
|
|
7305
|
+
const baseContext = resolveInvocationContext(invocationMeta, defaults);
|
|
7306
|
+
return extendInvocationContext(baseContext, bridgeKitCaller);
|
|
7307
|
+
}
|
|
5456
7308
|
/**
|
|
5457
7309
|
* Resolves and normalizes bridge configuration for the provider.
|
|
5458
7310
|
*
|
|
@@ -5521,6 +7373,7 @@ function resolveConfig(params) {
|
|
|
5521
7373
|
* - Amount formatting
|
|
5522
7374
|
*
|
|
5523
7375
|
* @param params - The bridge parameters containing source/destination contexts, amount, and token
|
|
7376
|
+
* @param invocationMeta - Optional invocation metadata for tracing and correlation
|
|
5524
7377
|
* @returns Promise resolving to normalized bridge parameters for provider consumption
|
|
5525
7378
|
* @throws \{Error\} If parameters cannot be resolved (invalid chains, etc.)
|
|
5526
7379
|
*
|
|
@@ -5540,22 +7393,32 @@ function resolveConfig(params) {
|
|
|
5540
7393
|
* ```
|
|
5541
7394
|
*/
|
|
5542
7395
|
async function resolveBridgeParams(params) {
|
|
5543
|
-
|
|
5544
|
-
const
|
|
7396
|
+
// Resolve chains
|
|
7397
|
+
const fromChain = resolveChainIdentifier(params.from.chain);
|
|
7398
|
+
const toChain = resolveChainIdentifier(params.to.chain);
|
|
7399
|
+
// Check if this is a forwarder-only destination (no adapter)
|
|
7400
|
+
const isForwarderOnly = isForwarderOnlyDestination(params.to);
|
|
5545
7401
|
// Validate adapter chain support after resolution
|
|
5546
|
-
// This ensures adapters support the resolved chains before proceeding
|
|
5547
7402
|
params.from.adapter.validateChainSupport(fromChain);
|
|
5548
|
-
|
|
7403
|
+
// Only validate destination adapter if it exists
|
|
7404
|
+
if (!isForwarderOnly && 'adapter' in params.to) {
|
|
7405
|
+
params.to.adapter.validateChainSupport(toChain);
|
|
7406
|
+
}
|
|
7407
|
+
// For forwarder-only destinations, use recipientAddress directly
|
|
7408
|
+
// For other destinations, resolve address from adapter
|
|
5549
7409
|
const [fromAddress, toAddress] = await Promise.all([
|
|
5550
7410
|
resolveAddress(params.from),
|
|
5551
|
-
|
|
7411
|
+
isForwarderOnly
|
|
7412
|
+
? Promise.resolve(params.to.recipientAddress)
|
|
7413
|
+
: resolveAddress(params.to),
|
|
5552
7414
|
]);
|
|
5553
7415
|
const token = params.token ?? 'USDC';
|
|
5554
|
-
// Extract adapters - now always from explicit contexts
|
|
5555
|
-
const fromAdapter = params.from.adapter;
|
|
5556
|
-
const toAdapter = params.to.adapter;
|
|
5557
7416
|
// Extract recipientAddress from params.to if it exists
|
|
5558
7417
|
const recipientAddress = 'recipientAddress' in params.to ? params.to.recipientAddress : undefined;
|
|
7418
|
+
// Extract useForwarder from params.to if it exists
|
|
7419
|
+
const useForwarder = 'useForwarder' in params.to ? params.to.useForwarder : undefined;
|
|
7420
|
+
// Resolve invocation metadata to full InvocationContext with BridgeKit caller
|
|
7421
|
+
const resolvedInvocation = resolveBridgeInvocation(params.invocationMeta);
|
|
5559
7422
|
return {
|
|
5560
7423
|
amount: resolveAmount({
|
|
5561
7424
|
...params,
|
|
@@ -5565,16 +7428,21 @@ async function resolveBridgeParams(params) {
|
|
|
5565
7428
|
config: resolveConfig({
|
|
5566
7429
|
...params}),
|
|
5567
7430
|
source: {
|
|
5568
|
-
adapter:
|
|
7431
|
+
adapter: params.from.adapter,
|
|
5569
7432
|
chain: fromChain,
|
|
5570
7433
|
address: fromAddress,
|
|
5571
7434
|
},
|
|
5572
7435
|
destination: {
|
|
5573
|
-
adapter
|
|
7436
|
+
// Only include adapter if it exists (not forwarder-only)
|
|
7437
|
+
...(!isForwarderOnly &&
|
|
7438
|
+
'adapter' in params.to && { adapter: params.to.adapter }),
|
|
5574
7439
|
chain: toChain,
|
|
5575
7440
|
address: toAddress,
|
|
5576
7441
|
...(recipientAddress !== undefined && { recipientAddress }),
|
|
7442
|
+
...(useForwarder !== undefined && { useForwarder }),
|
|
5577
7443
|
},
|
|
7444
|
+
// Pass resolved InvocationContext as invocationMeta (superset is compatible)
|
|
7445
|
+
invocationMeta: resolvedInvocation,
|
|
5578
7446
|
};
|
|
5579
7447
|
}
|
|
5580
7448
|
|
|
@@ -5652,6 +7520,36 @@ const formatBridgeResult = (result, formatDirection) => {
|
|
|
5652
7520
|
};
|
|
5653
7521
|
};
|
|
5654
7522
|
|
|
7523
|
+
/**
|
|
7524
|
+
* BridgeKit caller component for retry and estimate operations.
|
|
7525
|
+
*/
|
|
7526
|
+
const BRIDGE_KIT_CALLER = {
|
|
7527
|
+
type: 'kit',
|
|
7528
|
+
name: 'BridgeKit',
|
|
7529
|
+
version: pkg.version,
|
|
7530
|
+
};
|
|
7531
|
+
/**
|
|
7532
|
+
* Merge BridgeKit's caller into the invocation metadata for retry operations.
|
|
7533
|
+
*
|
|
7534
|
+
* Prepends the BridgeKit caller to the callers array.
|
|
7535
|
+
*
|
|
7536
|
+
* @param invocationMeta - Optional invocation metadata provided by caller.
|
|
7537
|
+
* @returns Merged invocation metadata with BridgeKit caller prepended.
|
|
7538
|
+
*
|
|
7539
|
+
* @internal
|
|
7540
|
+
*/
|
|
7541
|
+
function mergeRetryInvocationMeta(invocationMeta) {
|
|
7542
|
+
// Prepend BridgeKit caller to existing callers array
|
|
7543
|
+
const existingCallers = invocationMeta?.callers ?? [];
|
|
7544
|
+
return invocationMeta
|
|
7545
|
+
? {
|
|
7546
|
+
...invocationMeta,
|
|
7547
|
+
callers: [BRIDGE_KIT_CALLER, ...existingCallers],
|
|
7548
|
+
}
|
|
7549
|
+
: {
|
|
7550
|
+
callers: [BRIDGE_KIT_CALLER, ...existingCallers],
|
|
7551
|
+
};
|
|
7552
|
+
}
|
|
5655
7553
|
/**
|
|
5656
7554
|
* Route cross-chain USDC bridging through Circle's Cross-Chain Transfer Protocol v2 (CCTPv2).
|
|
5657
7555
|
*
|
|
@@ -5748,7 +7646,7 @@ class BridgeKit {
|
|
|
5748
7646
|
* - CCTPv2 support for the chain pair
|
|
5749
7647
|
* - Transfer configuration options
|
|
5750
7648
|
*
|
|
5751
|
-
* @param params - The transfer parameters containing source, destination, amount, and
|
|
7649
|
+
* @param params - The transfer parameters containing source, destination, amount, token, and optional invocation metadata
|
|
5752
7650
|
* @returns Promise resolving to the transfer result with transaction details and steps
|
|
5753
7651
|
* @throws {KitError} When any parameter validation fails.
|
|
5754
7652
|
* @throws {Error} When CCTPv2 does not support the specified route.
|
|
@@ -5765,18 +7663,24 @@ class BridgeKit {
|
|
|
5765
7663
|
* privateKey: process.env.PRIVATE_KEY,
|
|
5766
7664
|
* })
|
|
5767
7665
|
*
|
|
7666
|
+
* // Basic usage
|
|
5768
7667
|
* const result = await kit.bridge({
|
|
5769
|
-
* from: {
|
|
5770
|
-
*
|
|
5771
|
-
* chain: 'Ethereum'
|
|
5772
|
-
* },
|
|
5773
|
-
* to: {
|
|
5774
|
-
* adapter,
|
|
5775
|
-
* chain: 'Base'
|
|
5776
|
-
* },
|
|
7668
|
+
* from: { adapter, chain: 'Ethereum' },
|
|
7669
|
+
* to: { adapter, chain: 'Base' },
|
|
5777
7670
|
* amount: '100.50'
|
|
5778
7671
|
* })
|
|
5779
7672
|
*
|
|
7673
|
+
* // With custom invocation metadata
|
|
7674
|
+
* const result = await kit.bridge({
|
|
7675
|
+
* from: { adapter, chain: 'Ethereum' },
|
|
7676
|
+
* to: { adapter, chain: 'Base' },
|
|
7677
|
+
* amount: '100.50',
|
|
7678
|
+
* invocationMeta: {
|
|
7679
|
+
* traceId: 'custom-trace-id',
|
|
7680
|
+
* callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
|
|
7681
|
+
* },
|
|
7682
|
+
* })
|
|
7683
|
+
*
|
|
5780
7684
|
* // Handle result
|
|
5781
7685
|
* if (result.state === 'success') {
|
|
5782
7686
|
* console.log('Bridge completed!')
|
|
@@ -5822,6 +7726,8 @@ class BridgeKit {
|
|
|
5822
7726
|
* @param context - The retry context containing fresh adapter instances for both
|
|
5823
7727
|
* source and destination chains. These adapters should be properly
|
|
5824
7728
|
* configured with current network connections and signing capabilities.
|
|
7729
|
+
* @param invocationMeta - Optional invocation metadata for tracing and correlation.
|
|
7730
|
+
* If not provided, uses the traceId from the original result.
|
|
5825
7731
|
* @returns A promise that resolves to the updated bridge result after retry execution.
|
|
5826
7732
|
* The result will contain the complete step history including both original
|
|
5827
7733
|
* and retry attempts.
|
|
@@ -5853,31 +7759,35 @@ class BridgeKit {
|
|
|
5853
7759
|
* // ... other properties
|
|
5854
7760
|
* }
|
|
5855
7761
|
*
|
|
7762
|
+
* // Basic retry (uses traceId from original result)
|
|
7763
|
+
* const retryResult = await kit.retry(failedResult, {
|
|
7764
|
+
* from: sourceAdapter,
|
|
7765
|
+
* to: destAdapter
|
|
7766
|
+
* })
|
|
5856
7767
|
*
|
|
5857
|
-
*
|
|
5858
|
-
*
|
|
5859
|
-
*
|
|
5860
|
-
*
|
|
5861
|
-
*
|
|
5862
|
-
*
|
|
5863
|
-
*
|
|
5864
|
-
*
|
|
5865
|
-
*
|
|
5866
|
-
* console.error('Retry failed:', error.message)
|
|
5867
|
-
* // Handle retry failure (may require manual intervention)
|
|
5868
|
-
* }
|
|
7768
|
+
* // Retry with custom invocation metadata
|
|
7769
|
+
* const retryResult = await kit.retry(
|
|
7770
|
+
* failedResult,
|
|
7771
|
+
* { from: sourceAdapter, to: destAdapter },
|
|
7772
|
+
* {
|
|
7773
|
+
* traceId: 'custom-trace-id',
|
|
7774
|
+
* callers: [{ type: 'app', name: 'MyApp' }],
|
|
7775
|
+
* }
|
|
7776
|
+
* )
|
|
5869
7777
|
* ```
|
|
5870
7778
|
*/
|
|
5871
|
-
async retry(result, context) {
|
|
7779
|
+
async retry(result, context, invocationMeta) {
|
|
5872
7780
|
const provider = this.providers.find((p) => p.name === result.provider);
|
|
5873
7781
|
if (!provider) {
|
|
5874
7782
|
throw new Error(`Provider ${result.provider} not found`);
|
|
5875
7783
|
}
|
|
7784
|
+
// Merge BridgeKit caller into invocation metadata for retry operation
|
|
7785
|
+
const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
|
|
5876
7786
|
// Format the bridge result into bigint string values for internal use
|
|
5877
7787
|
const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
|
|
5878
7788
|
// Execute the retry using the provider
|
|
5879
7789
|
// Format the bridge result into human-readable string values for the user
|
|
5880
|
-
return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context), 'to-human-readable');
|
|
7790
|
+
return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context, mergedMeta), 'to-human-readable');
|
|
5881
7791
|
}
|
|
5882
7792
|
/**
|
|
5883
7793
|
* Estimate the cost and fees for a cross-chain USDC bridge operation.
|
|
@@ -5885,13 +7795,15 @@ class BridgeKit {
|
|
|
5885
7795
|
* This method calculates the expected gas fees and protocol costs for bridging
|
|
5886
7796
|
* without actually executing the transaction. It performs the same validation
|
|
5887
7797
|
* as the bridge method but stops before execution.
|
|
5888
|
-
*
|
|
7798
|
+
*
|
|
7799
|
+
* @param params - The bridge parameters for cost estimation, including optional invocation metadata
|
|
5889
7800
|
* @returns Promise resolving to detailed cost breakdown including gas estimates
|
|
5890
7801
|
* @throws {KitError} When the parameters are invalid.
|
|
5891
7802
|
* @throws {UnsupportedRouteError} When the route is not supported.
|
|
5892
7803
|
*
|
|
5893
7804
|
* @example
|
|
5894
7805
|
* ```typescript
|
|
7806
|
+
* // Basic usage
|
|
5895
7807
|
* const estimate = await kit.estimate({
|
|
5896
7808
|
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
5897
7809
|
* to: { adapter: adapter, chain: 'Base' },
|
|
@@ -5899,6 +7811,18 @@ class BridgeKit {
|
|
|
5899
7811
|
* token: 'USDC'
|
|
5900
7812
|
* })
|
|
5901
7813
|
* console.log('Estimated cost:', estimate.totalCost)
|
|
7814
|
+
*
|
|
7815
|
+
* // With custom invocation metadata
|
|
7816
|
+
* const estimate = await kit.estimate({
|
|
7817
|
+
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
7818
|
+
* to: { adapter: adapter, chain: 'Base' },
|
|
7819
|
+
* amount: '10.50',
|
|
7820
|
+
* token: 'USDC',
|
|
7821
|
+
* invocationMeta: {
|
|
7822
|
+
* traceId: 'custom-trace-id',
|
|
7823
|
+
* callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
|
|
7824
|
+
* },
|
|
7825
|
+
* })
|
|
5902
7826
|
* ```
|
|
5903
7827
|
*/
|
|
5904
7828
|
async estimate(params) {
|
|
@@ -5950,6 +7874,9 @@ class BridgeKit {
|
|
|
5950
7874
|
* // Get only EVM mainnet chains
|
|
5951
7875
|
* const evmMainnets = kit.getSupportedChains({ chainType: 'evm', isTestnet: false })
|
|
5952
7876
|
*
|
|
7877
|
+
* // Get only chains that support forwarding
|
|
7878
|
+
* const forwarderChains = kit.getSupportedChains({ forwarderSupported: true })
|
|
7879
|
+
*
|
|
5953
7880
|
* console.log('Supported chains:')
|
|
5954
7881
|
* allChains.forEach(chain => {
|
|
5955
7882
|
* console.log(`- ${chain.name} (${chain.type})`)
|
|
@@ -5983,6 +7910,18 @@ class BridgeKit {
|
|
|
5983
7910
|
if (options?.isTestnet !== undefined) {
|
|
5984
7911
|
chains = chains.filter((chain) => chain.isTestnet === options.isTestnet);
|
|
5985
7912
|
}
|
|
7913
|
+
// Apply forwarder support filter if provided
|
|
7914
|
+
if (options?.forwarderSupported !== undefined) {
|
|
7915
|
+
chains = chains.filter((chain) => {
|
|
7916
|
+
const fs = chain.cctp?.forwarderSupported;
|
|
7917
|
+
if (!fs) {
|
|
7918
|
+
return !options.forwarderSupported;
|
|
7919
|
+
}
|
|
7920
|
+
return options.forwarderSupported
|
|
7921
|
+
? fs.source || fs.destination
|
|
7922
|
+
: !fs.source && !fs.destination;
|
|
7923
|
+
});
|
|
7924
|
+
}
|
|
5986
7925
|
return chains;
|
|
5987
7926
|
}
|
|
5988
7927
|
/**
|
|
@@ -6178,6 +8117,10 @@ exports.NetworkError = NetworkError;
|
|
|
6178
8117
|
exports.OnchainError = OnchainError;
|
|
6179
8118
|
exports.RpcError = RpcError;
|
|
6180
8119
|
exports.bridgeParamsWithChainIdentifierSchema = bridgeParamsWithChainIdentifierSchema;
|
|
8120
|
+
exports.createRuntime = createRuntime;
|
|
8121
|
+
exports.createTokenRegistry = createTokenRegistry;
|
|
8122
|
+
exports.createTraceId = createTraceId;
|
|
8123
|
+
exports.extendInvocationContext = extendInvocationContext;
|
|
6181
8124
|
exports.getErrorCode = getErrorCode;
|
|
6182
8125
|
exports.getErrorMessage = getErrorMessage;
|
|
6183
8126
|
exports.isBalanceError = isBalanceError;
|
|
@@ -6189,5 +8132,6 @@ exports.isOnchainError = isOnchainError;
|
|
|
6189
8132
|
exports.isRetryableError = isRetryableError;
|
|
6190
8133
|
exports.isRpcError = isRpcError;
|
|
6191
8134
|
exports.resolveChainIdentifier = resolveChainIdentifier;
|
|
8135
|
+
exports.resolveInvocationContext = resolveInvocationContext;
|
|
6192
8136
|
exports.setExternalPrefix = setExternalPrefix;
|
|
6193
8137
|
//# sourceMappingURL=index.cjs.map
|