@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.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import '@ethersproject/bytes';
|
|
|
21
21
|
import '@ethersproject/address';
|
|
22
22
|
import 'bs58';
|
|
23
23
|
import { formatUnits as formatUnits$1, parseUnits as parseUnits$1 } from '@ethersproject/units';
|
|
24
|
+
import pino from 'pino';
|
|
24
25
|
import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2';
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -120,6 +121,8 @@ const ERROR_TYPES = {
|
|
|
120
121
|
RPC: 'RPC',
|
|
121
122
|
/** Internet connectivity, DNS resolution, connection issues */
|
|
122
123
|
NETWORK: 'NETWORK',
|
|
124
|
+
/** Catch-all for unrecognized errors (code 0) */
|
|
125
|
+
UNKNOWN: 'UNKNOWN',
|
|
123
126
|
};
|
|
124
127
|
/**
|
|
125
128
|
* Array of valid error type values for validation.
|
|
@@ -133,6 +136,8 @@ const ERROR_TYPE_ARRAY = [...ERROR_TYPE_VALUES];
|
|
|
133
136
|
/**
|
|
134
137
|
* Error code ranges for validation.
|
|
135
138
|
* Single source of truth for valid error code ranges.
|
|
139
|
+
*
|
|
140
|
+
* Note: Code 0 is special - it's the UNKNOWN catch-all error.
|
|
136
141
|
*/
|
|
137
142
|
const ERROR_CODE_RANGES = [
|
|
138
143
|
{ min: 1000, max: 1999, type: 'INPUT' },
|
|
@@ -141,6 +146,8 @@ const ERROR_CODE_RANGES = [
|
|
|
141
146
|
{ min: 5000, max: 5999, type: 'ONCHAIN' },
|
|
142
147
|
{ min: 9000, max: 9999, type: 'BALANCE' },
|
|
143
148
|
];
|
|
149
|
+
/** Special code for UNKNOWN errors */
|
|
150
|
+
const UNKNOWN_ERROR_CODE = 0;
|
|
144
151
|
/**
|
|
145
152
|
* Zod schema for validating ErrorDetails objects.
|
|
146
153
|
*
|
|
@@ -179,6 +186,7 @@ const ERROR_CODE_RANGES = [
|
|
|
179
186
|
const errorDetailsSchema = z.object({
|
|
180
187
|
/**
|
|
181
188
|
* Numeric identifier following standardized ranges:
|
|
189
|
+
* - 0: UNKNOWN - Catch-all for unrecognized errors
|
|
182
190
|
* - 1000-1999: INPUT errors - Parameter validation
|
|
183
191
|
* - 3000-3999: NETWORK errors - Connectivity issues
|
|
184
192
|
* - 4000-4999: RPC errors - Provider issues, gas estimation
|
|
@@ -188,8 +196,9 @@ const errorDetailsSchema = z.object({
|
|
|
188
196
|
code: z
|
|
189
197
|
.number()
|
|
190
198
|
.int('Error code must be an integer')
|
|
191
|
-
.refine((code) =>
|
|
192
|
-
|
|
199
|
+
.refine((code) => code === UNKNOWN_ERROR_CODE ||
|
|
200
|
+
ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
|
|
201
|
+
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)',
|
|
193
202
|
}),
|
|
194
203
|
/** Human-readable ID (e.g., "INPUT_NETWORK_MISMATCH", "BALANCE_INSUFFICIENT_TOKEN") */
|
|
195
204
|
name: z
|
|
@@ -199,7 +208,7 @@ const errorDetailsSchema = z.object({
|
|
|
199
208
|
/** Error category indicating where the error originated */
|
|
200
209
|
type: z.enum(ERROR_TYPE_ARRAY, {
|
|
201
210
|
errorMap: () => ({
|
|
202
|
-
message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK',
|
|
211
|
+
message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK, UNKNOWN',
|
|
203
212
|
}),
|
|
204
213
|
}),
|
|
205
214
|
/** Error handling strategy */
|
|
@@ -416,6 +425,7 @@ class KitError extends Error {
|
|
|
416
425
|
/**
|
|
417
426
|
* Standardized error code ranges for consistent categorization:
|
|
418
427
|
*
|
|
428
|
+
* - 0: UNKNOWN - Catch-all for unrecognized errors
|
|
419
429
|
* - 1000-1999: INPUT errors - Parameter validation, input format errors
|
|
420
430
|
* - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
|
|
421
431
|
* - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
|
|
@@ -644,6 +654,18 @@ const NetworkError = {
|
|
|
644
654
|
name: 'NETWORK_TIMEOUT',
|
|
645
655
|
type: 'NETWORK',
|
|
646
656
|
},
|
|
657
|
+
/** Circle relayer failed to process the forwarding/mint transaction */
|
|
658
|
+
RELAYER_FORWARD_FAILED: {
|
|
659
|
+
code: 3003,
|
|
660
|
+
name: 'NETWORK_RELAYER_FORWARD_FAILED',
|
|
661
|
+
type: 'NETWORK',
|
|
662
|
+
},
|
|
663
|
+
/** Relayer mint is pending - waiting for confirmation */
|
|
664
|
+
RELAYER_PENDING: {
|
|
665
|
+
code: 3004,
|
|
666
|
+
name: 'NETWORK_RELAYER_PENDING',
|
|
667
|
+
type: 'NETWORK',
|
|
668
|
+
},
|
|
647
669
|
};
|
|
648
670
|
|
|
649
671
|
/**
|
|
@@ -964,6 +986,8 @@ var Blockchain;
|
|
|
964
986
|
Blockchain["Ink_Testnet"] = "Ink_Testnet";
|
|
965
987
|
Blockchain["Linea"] = "Linea";
|
|
966
988
|
Blockchain["Linea_Sepolia"] = "Linea_Sepolia";
|
|
989
|
+
Blockchain["Monad"] = "Monad";
|
|
990
|
+
Blockchain["Monad_Testnet"] = "Monad_Testnet";
|
|
967
991
|
Blockchain["NEAR"] = "NEAR";
|
|
968
992
|
Blockchain["NEAR_Testnet"] = "NEAR_Testnet";
|
|
969
993
|
Blockchain["Noble"] = "Noble";
|
|
@@ -1055,6 +1079,7 @@ var BridgeChain;
|
|
|
1055
1079
|
BridgeChain["HyperEVM"] = "HyperEVM";
|
|
1056
1080
|
BridgeChain["Ink"] = "Ink";
|
|
1057
1081
|
BridgeChain["Linea"] = "Linea";
|
|
1082
|
+
BridgeChain["Monad"] = "Monad";
|
|
1058
1083
|
BridgeChain["Optimism"] = "Optimism";
|
|
1059
1084
|
BridgeChain["Plume"] = "Plume";
|
|
1060
1085
|
BridgeChain["Polygon"] = "Polygon";
|
|
@@ -1074,6 +1099,7 @@ var BridgeChain;
|
|
|
1074
1099
|
BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
|
|
1075
1100
|
BridgeChain["Ink_Testnet"] = "Ink_Testnet";
|
|
1076
1101
|
BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
|
|
1102
|
+
BridgeChain["Monad_Testnet"] = "Monad_Testnet";
|
|
1077
1103
|
BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
1078
1104
|
BridgeChain["Plume_Testnet"] = "Plume_Testnet";
|
|
1079
1105
|
BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
@@ -1200,6 +1226,10 @@ const Aptos = defineChain({
|
|
|
1200
1226
|
confirmations: 1,
|
|
1201
1227
|
},
|
|
1202
1228
|
},
|
|
1229
|
+
forwarderSupported: {
|
|
1230
|
+
source: false,
|
|
1231
|
+
destination: false,
|
|
1232
|
+
},
|
|
1203
1233
|
},
|
|
1204
1234
|
});
|
|
1205
1235
|
|
|
@@ -1233,6 +1263,10 @@ const AptosTestnet = defineChain({
|
|
|
1233
1263
|
confirmations: 1,
|
|
1234
1264
|
},
|
|
1235
1265
|
},
|
|
1266
|
+
forwarderSupported: {
|
|
1267
|
+
source: false,
|
|
1268
|
+
destination: false,
|
|
1269
|
+
},
|
|
1236
1270
|
},
|
|
1237
1271
|
});
|
|
1238
1272
|
|
|
@@ -1292,6 +1326,10 @@ const ArcTestnet = defineChain({
|
|
|
1292
1326
|
fastConfirmations: 1,
|
|
1293
1327
|
},
|
|
1294
1328
|
},
|
|
1329
|
+
forwarderSupported: {
|
|
1330
|
+
source: true,
|
|
1331
|
+
destination: true,
|
|
1332
|
+
},
|
|
1295
1333
|
},
|
|
1296
1334
|
kitContracts: {
|
|
1297
1335
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1336,6 +1374,10 @@ const Arbitrum = defineChain({
|
|
|
1336
1374
|
fastConfirmations: 1,
|
|
1337
1375
|
},
|
|
1338
1376
|
},
|
|
1377
|
+
forwarderSupported: {
|
|
1378
|
+
source: true,
|
|
1379
|
+
destination: true,
|
|
1380
|
+
},
|
|
1339
1381
|
},
|
|
1340
1382
|
kitContracts: {
|
|
1341
1383
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1380,6 +1422,10 @@ const ArbitrumSepolia = defineChain({
|
|
|
1380
1422
|
fastConfirmations: 1,
|
|
1381
1423
|
},
|
|
1382
1424
|
},
|
|
1425
|
+
forwarderSupported: {
|
|
1426
|
+
source: true,
|
|
1427
|
+
destination: true,
|
|
1428
|
+
},
|
|
1383
1429
|
},
|
|
1384
1430
|
kitContracts: {
|
|
1385
1431
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1424,6 +1470,10 @@ const Avalanche = defineChain({
|
|
|
1424
1470
|
fastConfirmations: 1,
|
|
1425
1471
|
},
|
|
1426
1472
|
},
|
|
1473
|
+
forwarderSupported: {
|
|
1474
|
+
source: true,
|
|
1475
|
+
destination: true,
|
|
1476
|
+
},
|
|
1427
1477
|
},
|
|
1428
1478
|
kitContracts: {
|
|
1429
1479
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1467,6 +1517,10 @@ const AvalancheFuji = defineChain({
|
|
|
1467
1517
|
fastConfirmations: 1,
|
|
1468
1518
|
},
|
|
1469
1519
|
},
|
|
1520
|
+
forwarderSupported: {
|
|
1521
|
+
source: true,
|
|
1522
|
+
destination: true,
|
|
1523
|
+
},
|
|
1470
1524
|
},
|
|
1471
1525
|
rpcEndpoints: ['https://api.avax-test.network/ext/bc/C/rpc'],
|
|
1472
1526
|
kitContracts: {
|
|
@@ -1512,6 +1566,10 @@ const Base = defineChain({
|
|
|
1512
1566
|
fastConfirmations: 1,
|
|
1513
1567
|
},
|
|
1514
1568
|
},
|
|
1569
|
+
forwarderSupported: {
|
|
1570
|
+
source: true,
|
|
1571
|
+
destination: true,
|
|
1572
|
+
},
|
|
1515
1573
|
},
|
|
1516
1574
|
kitContracts: {
|
|
1517
1575
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1556,6 +1614,10 @@ const BaseSepolia = defineChain({
|
|
|
1556
1614
|
fastConfirmations: 1,
|
|
1557
1615
|
},
|
|
1558
1616
|
},
|
|
1617
|
+
forwarderSupported: {
|
|
1618
|
+
source: true,
|
|
1619
|
+
destination: true,
|
|
1620
|
+
},
|
|
1559
1621
|
},
|
|
1560
1622
|
kitContracts: {
|
|
1561
1623
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1642,6 +1704,10 @@ const Codex = defineChain({
|
|
|
1642
1704
|
fastConfirmations: 1,
|
|
1643
1705
|
},
|
|
1644
1706
|
},
|
|
1707
|
+
forwarderSupported: {
|
|
1708
|
+
source: true,
|
|
1709
|
+
destination: false,
|
|
1710
|
+
},
|
|
1645
1711
|
},
|
|
1646
1712
|
kitContracts: {
|
|
1647
1713
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1680,6 +1746,10 @@ const CodexTestnet = defineChain({
|
|
|
1680
1746
|
fastConfirmations: 1,
|
|
1681
1747
|
},
|
|
1682
1748
|
},
|
|
1749
|
+
forwarderSupported: {
|
|
1750
|
+
source: true,
|
|
1751
|
+
destination: false,
|
|
1752
|
+
},
|
|
1683
1753
|
},
|
|
1684
1754
|
kitContracts: {
|
|
1685
1755
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1724,6 +1794,10 @@ const Ethereum = defineChain({
|
|
|
1724
1794
|
fastConfirmations: 2,
|
|
1725
1795
|
},
|
|
1726
1796
|
},
|
|
1797
|
+
forwarderSupported: {
|
|
1798
|
+
source: true,
|
|
1799
|
+
destination: true,
|
|
1800
|
+
},
|
|
1727
1801
|
},
|
|
1728
1802
|
kitContracts: {
|
|
1729
1803
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1768,6 +1842,10 @@ const EthereumSepolia = defineChain({
|
|
|
1768
1842
|
fastConfirmations: 2,
|
|
1769
1843
|
},
|
|
1770
1844
|
},
|
|
1845
|
+
forwarderSupported: {
|
|
1846
|
+
source: true,
|
|
1847
|
+
destination: true,
|
|
1848
|
+
},
|
|
1771
1849
|
},
|
|
1772
1850
|
kitContracts: {
|
|
1773
1851
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1854,6 +1932,10 @@ const HyperEVM = defineChain({
|
|
|
1854
1932
|
fastConfirmations: 1,
|
|
1855
1933
|
},
|
|
1856
1934
|
},
|
|
1935
|
+
forwarderSupported: {
|
|
1936
|
+
source: true,
|
|
1937
|
+
destination: true,
|
|
1938
|
+
},
|
|
1857
1939
|
},
|
|
1858
1940
|
kitContracts: {
|
|
1859
1941
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1893,6 +1975,10 @@ const HyperEVMTestnet = defineChain({
|
|
|
1893
1975
|
fastConfirmations: 1,
|
|
1894
1976
|
},
|
|
1895
1977
|
},
|
|
1978
|
+
forwarderSupported: {
|
|
1979
|
+
source: true,
|
|
1980
|
+
destination: true,
|
|
1981
|
+
},
|
|
1896
1982
|
},
|
|
1897
1983
|
kitContracts: {
|
|
1898
1984
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1936,6 +2022,10 @@ const Ink = defineChain({
|
|
|
1936
2022
|
fastConfirmations: 1,
|
|
1937
2023
|
},
|
|
1938
2024
|
},
|
|
2025
|
+
forwarderSupported: {
|
|
2026
|
+
source: true,
|
|
2027
|
+
destination: true,
|
|
2028
|
+
},
|
|
1939
2029
|
},
|
|
1940
2030
|
kitContracts: {
|
|
1941
2031
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1978,6 +2068,10 @@ const InkTestnet = defineChain({
|
|
|
1978
2068
|
fastConfirmations: 1,
|
|
1979
2069
|
},
|
|
1980
2070
|
},
|
|
2071
|
+
forwarderSupported: {
|
|
2072
|
+
source: true,
|
|
2073
|
+
destination: true,
|
|
2074
|
+
},
|
|
1981
2075
|
},
|
|
1982
2076
|
kitContracts: {
|
|
1983
2077
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2016,6 +2110,10 @@ const Linea = defineChain({
|
|
|
2016
2110
|
fastConfirmations: 1,
|
|
2017
2111
|
},
|
|
2018
2112
|
},
|
|
2113
|
+
forwarderSupported: {
|
|
2114
|
+
source: true,
|
|
2115
|
+
destination: true,
|
|
2116
|
+
},
|
|
2019
2117
|
},
|
|
2020
2118
|
kitContracts: {
|
|
2021
2119
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2054,6 +2152,98 @@ const LineaSepolia = defineChain({
|
|
|
2054
2152
|
fastConfirmations: 1,
|
|
2055
2153
|
},
|
|
2056
2154
|
},
|
|
2155
|
+
forwarderSupported: {
|
|
2156
|
+
source: true,
|
|
2157
|
+
destination: true,
|
|
2158
|
+
},
|
|
2159
|
+
},
|
|
2160
|
+
kitContracts: {
|
|
2161
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2162
|
+
},
|
|
2163
|
+
});
|
|
2164
|
+
|
|
2165
|
+
/**
|
|
2166
|
+
* Monad Mainnet chain definition
|
|
2167
|
+
* @remarks
|
|
2168
|
+
* This represents the official production network for the Monad blockchain.
|
|
2169
|
+
* Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
|
|
2170
|
+
* over 10,000 TPS, sub-second finality, and near-zero gas fees.
|
|
2171
|
+
*/
|
|
2172
|
+
const Monad = defineChain({
|
|
2173
|
+
type: 'evm',
|
|
2174
|
+
chain: Blockchain.Monad,
|
|
2175
|
+
name: 'Monad',
|
|
2176
|
+
title: 'Monad Mainnet',
|
|
2177
|
+
nativeCurrency: {
|
|
2178
|
+
name: 'Monad',
|
|
2179
|
+
symbol: 'MON',
|
|
2180
|
+
decimals: 18,
|
|
2181
|
+
},
|
|
2182
|
+
chainId: 143,
|
|
2183
|
+
isTestnet: false,
|
|
2184
|
+
explorerUrl: 'https://monadscan.com/tx/{hash}',
|
|
2185
|
+
rpcEndpoints: ['https://rpc.monad.xyz'],
|
|
2186
|
+
eurcAddress: null,
|
|
2187
|
+
usdcAddress: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
|
|
2188
|
+
cctp: {
|
|
2189
|
+
domain: 15,
|
|
2190
|
+
contracts: {
|
|
2191
|
+
v2: {
|
|
2192
|
+
type: 'split',
|
|
2193
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
2194
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
2195
|
+
confirmations: 1,
|
|
2196
|
+
fastConfirmations: 1,
|
|
2197
|
+
},
|
|
2198
|
+
},
|
|
2199
|
+
forwarderSupported: {
|
|
2200
|
+
source: true,
|
|
2201
|
+
destination: true,
|
|
2202
|
+
},
|
|
2203
|
+
},
|
|
2204
|
+
kitContracts: {
|
|
2205
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2206
|
+
},
|
|
2207
|
+
});
|
|
2208
|
+
|
|
2209
|
+
/**
|
|
2210
|
+
* Monad Testnet chain definition
|
|
2211
|
+
* @remarks
|
|
2212
|
+
* This represents the official test network for the Monad blockchain.
|
|
2213
|
+
* Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
|
|
2214
|
+
* over 10,000 TPS, sub-second finality, and near-zero gas fees.
|
|
2215
|
+
*/
|
|
2216
|
+
const MonadTestnet = defineChain({
|
|
2217
|
+
type: 'evm',
|
|
2218
|
+
chain: Blockchain.Monad_Testnet,
|
|
2219
|
+
name: 'Monad Testnet',
|
|
2220
|
+
title: 'Monad Testnet',
|
|
2221
|
+
nativeCurrency: {
|
|
2222
|
+
name: 'Monad',
|
|
2223
|
+
symbol: 'MON',
|
|
2224
|
+
decimals: 18,
|
|
2225
|
+
},
|
|
2226
|
+
chainId: 10143,
|
|
2227
|
+
isTestnet: true,
|
|
2228
|
+
explorerUrl: 'https://testnet.monadscan.com/tx/{hash}',
|
|
2229
|
+
rpcEndpoints: ['https://testnet-rpc.monad.xyz'],
|
|
2230
|
+
eurcAddress: null,
|
|
2231
|
+
usdcAddress: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
|
|
2232
|
+
cctp: {
|
|
2233
|
+
domain: 15,
|
|
2234
|
+
contracts: {
|
|
2235
|
+
v2: {
|
|
2236
|
+
type: 'split',
|
|
2237
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
2238
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
2239
|
+
confirmations: 1,
|
|
2240
|
+
fastConfirmations: 1,
|
|
2241
|
+
},
|
|
2242
|
+
},
|
|
2243
|
+
forwarderSupported: {
|
|
2244
|
+
source: true,
|
|
2245
|
+
destination: true,
|
|
2246
|
+
},
|
|
2057
2247
|
},
|
|
2058
2248
|
kitContracts: {
|
|
2059
2249
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2135,6 +2325,10 @@ const Noble = defineChain({
|
|
|
2135
2325
|
confirmations: 1,
|
|
2136
2326
|
},
|
|
2137
2327
|
},
|
|
2328
|
+
forwarderSupported: {
|
|
2329
|
+
source: false,
|
|
2330
|
+
destination: false,
|
|
2331
|
+
},
|
|
2138
2332
|
},
|
|
2139
2333
|
});
|
|
2140
2334
|
|
|
@@ -2167,6 +2361,10 @@ const NobleTestnet = defineChain({
|
|
|
2167
2361
|
confirmations: 1,
|
|
2168
2362
|
},
|
|
2169
2363
|
},
|
|
2364
|
+
forwarderSupported: {
|
|
2365
|
+
source: false,
|
|
2366
|
+
destination: false,
|
|
2367
|
+
},
|
|
2170
2368
|
},
|
|
2171
2369
|
});
|
|
2172
2370
|
|
|
@@ -2208,6 +2406,10 @@ const Optimism = defineChain({
|
|
|
2208
2406
|
fastConfirmations: 1,
|
|
2209
2407
|
},
|
|
2210
2408
|
},
|
|
2409
|
+
forwarderSupported: {
|
|
2410
|
+
source: true,
|
|
2411
|
+
destination: true,
|
|
2412
|
+
},
|
|
2211
2413
|
},
|
|
2212
2414
|
kitContracts: {
|
|
2213
2415
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2252,6 +2454,10 @@ const OptimismSepolia = defineChain({
|
|
|
2252
2454
|
fastConfirmations: 1,
|
|
2253
2455
|
},
|
|
2254
2456
|
},
|
|
2457
|
+
forwarderSupported: {
|
|
2458
|
+
source: true,
|
|
2459
|
+
destination: true,
|
|
2460
|
+
},
|
|
2255
2461
|
},
|
|
2256
2462
|
kitContracts: {
|
|
2257
2463
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2292,6 +2498,10 @@ const Plume = defineChain({
|
|
|
2292
2498
|
fastConfirmations: 1,
|
|
2293
2499
|
},
|
|
2294
2500
|
},
|
|
2501
|
+
forwarderSupported: {
|
|
2502
|
+
source: true,
|
|
2503
|
+
destination: false,
|
|
2504
|
+
},
|
|
2295
2505
|
},
|
|
2296
2506
|
kitContracts: {
|
|
2297
2507
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2331,6 +2541,10 @@ const PlumeTestnet = defineChain({
|
|
|
2331
2541
|
fastConfirmations: 1,
|
|
2332
2542
|
},
|
|
2333
2543
|
},
|
|
2544
|
+
forwarderSupported: {
|
|
2545
|
+
source: true,
|
|
2546
|
+
destination: false,
|
|
2547
|
+
},
|
|
2334
2548
|
},
|
|
2335
2549
|
kitContracts: {
|
|
2336
2550
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2421,6 +2635,10 @@ const Polygon = defineChain({
|
|
|
2421
2635
|
fastConfirmations: 13,
|
|
2422
2636
|
},
|
|
2423
2637
|
},
|
|
2638
|
+
forwarderSupported: {
|
|
2639
|
+
source: true,
|
|
2640
|
+
destination: true,
|
|
2641
|
+
},
|
|
2424
2642
|
},
|
|
2425
2643
|
kitContracts: {
|
|
2426
2644
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2465,6 +2683,10 @@ const PolygonAmoy = defineChain({
|
|
|
2465
2683
|
fastConfirmations: 13,
|
|
2466
2684
|
},
|
|
2467
2685
|
},
|
|
2686
|
+
forwarderSupported: {
|
|
2687
|
+
source: true,
|
|
2688
|
+
destination: true,
|
|
2689
|
+
},
|
|
2468
2690
|
},
|
|
2469
2691
|
kitContracts: {
|
|
2470
2692
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2505,6 +2727,10 @@ const Sei = defineChain({
|
|
|
2505
2727
|
fastConfirmations: 1,
|
|
2506
2728
|
},
|
|
2507
2729
|
},
|
|
2730
|
+
forwarderSupported: {
|
|
2731
|
+
source: true,
|
|
2732
|
+
destination: true,
|
|
2733
|
+
},
|
|
2508
2734
|
},
|
|
2509
2735
|
kitContracts: {
|
|
2510
2736
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2544,6 +2770,10 @@ const SeiTestnet = defineChain({
|
|
|
2544
2770
|
fastConfirmations: 1,
|
|
2545
2771
|
},
|
|
2546
2772
|
},
|
|
2773
|
+
forwarderSupported: {
|
|
2774
|
+
source: true,
|
|
2775
|
+
destination: true,
|
|
2776
|
+
},
|
|
2547
2777
|
},
|
|
2548
2778
|
kitContracts: {
|
|
2549
2779
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2582,6 +2812,10 @@ const Sonic = defineChain({
|
|
|
2582
2812
|
fastConfirmations: 1,
|
|
2583
2813
|
},
|
|
2584
2814
|
},
|
|
2815
|
+
forwarderSupported: {
|
|
2816
|
+
source: true,
|
|
2817
|
+
destination: true,
|
|
2818
|
+
},
|
|
2585
2819
|
},
|
|
2586
2820
|
kitContracts: {
|
|
2587
2821
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2620,6 +2854,10 @@ const SonicTestnet = defineChain({
|
|
|
2620
2854
|
fastConfirmations: 1,
|
|
2621
2855
|
},
|
|
2622
2856
|
},
|
|
2857
|
+
forwarderSupported: {
|
|
2858
|
+
source: true,
|
|
2859
|
+
destination: true,
|
|
2860
|
+
},
|
|
2623
2861
|
},
|
|
2624
2862
|
kitContracts: {
|
|
2625
2863
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2663,6 +2901,10 @@ const Solana = defineChain({
|
|
|
2663
2901
|
fastConfirmations: 3,
|
|
2664
2902
|
},
|
|
2665
2903
|
},
|
|
2904
|
+
forwarderSupported: {
|
|
2905
|
+
source: true,
|
|
2906
|
+
destination: false,
|
|
2907
|
+
},
|
|
2666
2908
|
},
|
|
2667
2909
|
kitContracts: {
|
|
2668
2910
|
bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
|
|
@@ -2705,6 +2947,10 @@ const SolanaDevnet = defineChain({
|
|
|
2705
2947
|
fastConfirmations: 3,
|
|
2706
2948
|
},
|
|
2707
2949
|
},
|
|
2950
|
+
forwarderSupported: {
|
|
2951
|
+
source: true,
|
|
2952
|
+
destination: false,
|
|
2953
|
+
},
|
|
2708
2954
|
},
|
|
2709
2955
|
kitContracts: {
|
|
2710
2956
|
bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
|
|
@@ -2788,6 +3034,10 @@ const Sui = defineChain({
|
|
|
2788
3034
|
confirmations: 1,
|
|
2789
3035
|
},
|
|
2790
3036
|
},
|
|
3037
|
+
forwarderSupported: {
|
|
3038
|
+
source: false,
|
|
3039
|
+
destination: false,
|
|
3040
|
+
},
|
|
2791
3041
|
},
|
|
2792
3042
|
});
|
|
2793
3043
|
|
|
@@ -2821,6 +3071,10 @@ const SuiTestnet = defineChain({
|
|
|
2821
3071
|
confirmations: 1,
|
|
2822
3072
|
},
|
|
2823
3073
|
},
|
|
3074
|
+
forwarderSupported: {
|
|
3075
|
+
source: false,
|
|
3076
|
+
destination: false,
|
|
3077
|
+
},
|
|
2824
3078
|
},
|
|
2825
3079
|
});
|
|
2826
3080
|
|
|
@@ -2842,7 +3096,7 @@ const Unichain = defineChain({
|
|
|
2842
3096
|
chainId: 130,
|
|
2843
3097
|
isTestnet: false,
|
|
2844
3098
|
explorerUrl: 'https://unichain.blockscout.com/tx/{hash}',
|
|
2845
|
-
rpcEndpoints: ['https://
|
|
3099
|
+
rpcEndpoints: ['https://mainnet.unichain.org'],
|
|
2846
3100
|
eurcAddress: null,
|
|
2847
3101
|
usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
|
|
2848
3102
|
cctp: {
|
|
@@ -2862,6 +3116,10 @@ const Unichain = defineChain({
|
|
|
2862
3116
|
fastConfirmations: 1,
|
|
2863
3117
|
},
|
|
2864
3118
|
},
|
|
3119
|
+
forwarderSupported: {
|
|
3120
|
+
source: true,
|
|
3121
|
+
destination: true,
|
|
3122
|
+
},
|
|
2865
3123
|
},
|
|
2866
3124
|
kitContracts: {
|
|
2867
3125
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2906,6 +3164,10 @@ const UnichainSepolia = defineChain({
|
|
|
2906
3164
|
fastConfirmations: 1,
|
|
2907
3165
|
},
|
|
2908
3166
|
},
|
|
3167
|
+
forwarderSupported: {
|
|
3168
|
+
source: true,
|
|
3169
|
+
destination: true,
|
|
3170
|
+
},
|
|
2909
3171
|
},
|
|
2910
3172
|
kitContracts: {
|
|
2911
3173
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2932,7 +3194,7 @@ const WorldChain = defineChain({
|
|
|
2932
3194
|
explorerUrl: 'https://worldscan.org/tx/{hash}',
|
|
2933
3195
|
rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
|
|
2934
3196
|
eurcAddress: null,
|
|
2935
|
-
usdcAddress: '
|
|
3197
|
+
usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
|
|
2936
3198
|
cctp: {
|
|
2937
3199
|
domain: 14,
|
|
2938
3200
|
contracts: {
|
|
@@ -2944,6 +3206,10 @@ const WorldChain = defineChain({
|
|
|
2944
3206
|
fastConfirmations: 1,
|
|
2945
3207
|
},
|
|
2946
3208
|
},
|
|
3209
|
+
forwarderSupported: {
|
|
3210
|
+
source: true,
|
|
3211
|
+
destination: true,
|
|
3212
|
+
},
|
|
2947
3213
|
},
|
|
2948
3214
|
kitContracts: {
|
|
2949
3215
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2985,6 +3251,10 @@ const WorldChainSepolia = defineChain({
|
|
|
2985
3251
|
fastConfirmations: 1,
|
|
2986
3252
|
},
|
|
2987
3253
|
},
|
|
3254
|
+
forwarderSupported: {
|
|
3255
|
+
source: true,
|
|
3256
|
+
destination: true,
|
|
3257
|
+
},
|
|
2988
3258
|
},
|
|
2989
3259
|
kitContracts: {
|
|
2990
3260
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -3011,7 +3281,7 @@ const XDC = defineChain({
|
|
|
3011
3281
|
chainId: 50,
|
|
3012
3282
|
isTestnet: false,
|
|
3013
3283
|
explorerUrl: 'https://xdcscan.io/tx/{hash}',
|
|
3014
|
-
rpcEndpoints: ['https://erpc.xinfin.network'],
|
|
3284
|
+
rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
|
|
3015
3285
|
eurcAddress: null,
|
|
3016
3286
|
usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
|
|
3017
3287
|
cctp: {
|
|
@@ -3025,6 +3295,10 @@ const XDC = defineChain({
|
|
|
3025
3295
|
fastConfirmations: 3,
|
|
3026
3296
|
},
|
|
3027
3297
|
},
|
|
3298
|
+
forwarderSupported: {
|
|
3299
|
+
source: true,
|
|
3300
|
+
destination: false,
|
|
3301
|
+
},
|
|
3028
3302
|
},
|
|
3029
3303
|
kitContracts: {
|
|
3030
3304
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -3063,6 +3337,10 @@ const XDCApothem = defineChain({
|
|
|
3063
3337
|
fastConfirmations: 1,
|
|
3064
3338
|
},
|
|
3065
3339
|
},
|
|
3340
|
+
forwarderSupported: {
|
|
3341
|
+
source: true,
|
|
3342
|
+
destination: false,
|
|
3343
|
+
},
|
|
3066
3344
|
},
|
|
3067
3345
|
kitContracts: {
|
|
3068
3346
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -3144,6 +3422,8 @@ var Blockchains = /*#__PURE__*/Object.freeze({
|
|
|
3144
3422
|
InkTestnet: InkTestnet,
|
|
3145
3423
|
Linea: Linea,
|
|
3146
3424
|
LineaSepolia: LineaSepolia,
|
|
3425
|
+
Monad: Monad,
|
|
3426
|
+
MonadTestnet: MonadTestnet,
|
|
3147
3427
|
NEAR: NEAR,
|
|
3148
3428
|
NEARTestnet: NEARTestnet,
|
|
3149
3429
|
Noble: Noble,
|
|
@@ -3297,7 +3577,7 @@ const nonEvmChainDefinitionSchema = baseChainDefinitionSchema
|
|
|
3297
3577
|
* })
|
|
3298
3578
|
* ```
|
|
3299
3579
|
*/
|
|
3300
|
-
const chainDefinitionSchema$
|
|
3580
|
+
const chainDefinitionSchema$2 = z.discriminatedUnion('type', [
|
|
3301
3581
|
evmChainDefinitionSchema,
|
|
3302
3582
|
nonEvmChainDefinitionSchema,
|
|
3303
3583
|
]);
|
|
@@ -3322,7 +3602,7 @@ z.union([
|
|
|
3322
3602
|
.string()
|
|
3323
3603
|
.refine((val) => val in Blockchain, 'Must be a valid Blockchain enum value as string'),
|
|
3324
3604
|
z.nativeEnum(Blockchain),
|
|
3325
|
-
chainDefinitionSchema$
|
|
3605
|
+
chainDefinitionSchema$2,
|
|
3326
3606
|
]);
|
|
3327
3607
|
/**
|
|
3328
3608
|
* Zod schema for validating bridge chain identifiers.
|
|
@@ -3358,7 +3638,7 @@ const bridgeChainIdentifierSchema = z.union([
|
|
|
3358
3638
|
z.string().refine((val) => val in BridgeChain, (val) => ({
|
|
3359
3639
|
message: `Chain "${val}" is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
|
|
3360
3640
|
})),
|
|
3361
|
-
chainDefinitionSchema$
|
|
3641
|
+
chainDefinitionSchema$2.refine((chainDef) => chainDef.chain in BridgeChain, (chainDef) => ({
|
|
3362
3642
|
message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
|
|
3363
3643
|
})),
|
|
3364
3644
|
]);
|
|
@@ -3555,14 +3835,41 @@ function isFatalError(error) {
|
|
|
3555
3835
|
return isKitError(error) && error.recoverability === 'FATAL';
|
|
3556
3836
|
}
|
|
3557
3837
|
/**
|
|
3558
|
-
*
|
|
3838
|
+
* Error codes that are considered retryable by default.
|
|
3839
|
+
*
|
|
3840
|
+
* @remarks
|
|
3841
|
+
* These are typically transient errors that may succeed on retry:
|
|
3842
|
+
* - Network connectivity issues (3001, 3002)
|
|
3843
|
+
* - Provider unavailability (4001, 4002)
|
|
3844
|
+
* - RPC nonce errors (4003)
|
|
3845
|
+
*/
|
|
3846
|
+
const DEFAULT_RETRYABLE_ERROR_CODES = [
|
|
3847
|
+
// Network errors
|
|
3848
|
+
3001, // NETWORK_CONNECTION_FAILED
|
|
3849
|
+
3002, // NETWORK_TIMEOUT
|
|
3850
|
+
// Provider errors
|
|
3851
|
+
4001, // PROVIDER_UNAVAILABLE
|
|
3852
|
+
4002, // PROVIDER_TIMEOUT
|
|
3853
|
+
4003, // RPC_NONCE_ERROR
|
|
3854
|
+
];
|
|
3855
|
+
/**
|
|
3856
|
+
* Checks if an error is retryable.
|
|
3857
|
+
*
|
|
3858
|
+
* @remarks
|
|
3859
|
+
* Check order for KitError instances:
|
|
3860
|
+
* 1. If `recoverability === 'RETRYABLE'`, return `true` immediately (priority check).
|
|
3861
|
+
* 2. Otherwise, check if `error.code` is in `DEFAULT_RETRYABLE_ERROR_CODES` (fallback check).
|
|
3862
|
+
* 3. Non-KitError instances always return `false`.
|
|
3863
|
+
*
|
|
3864
|
+
* This two-tier approach allows both explicit recoverability control and
|
|
3865
|
+
* backward-compatible code-based retry logic.
|
|
3559
3866
|
*
|
|
3560
3867
|
* RETRYABLE errors indicate transient failures that may succeed on
|
|
3561
3868
|
* subsequent attempts, such as network timeouts or temporary service
|
|
3562
3869
|
* unavailability. These errors are safe to retry after a delay.
|
|
3563
3870
|
*
|
|
3564
3871
|
* @param error - Unknown error to check
|
|
3565
|
-
* @returns True if error is
|
|
3872
|
+
* @returns True if error is retryable
|
|
3566
3873
|
*
|
|
3567
3874
|
* @example
|
|
3568
3875
|
* ```typescript
|
|
@@ -3577,9 +3884,51 @@ function isFatalError(error) {
|
|
|
3577
3884
|
* }
|
|
3578
3885
|
* }
|
|
3579
3886
|
* ```
|
|
3887
|
+
*
|
|
3888
|
+
* @example
|
|
3889
|
+
* ```typescript
|
|
3890
|
+
* import { isRetryableError, createNetworkConnectionError, KitError } from '@core/errors'
|
|
3891
|
+
*
|
|
3892
|
+
* // KitError with RETRYABLE recoverability (priority check)
|
|
3893
|
+
* const error1 = createNetworkConnectionError('Ethereum')
|
|
3894
|
+
* isRetryableError(error1) // true
|
|
3895
|
+
*
|
|
3896
|
+
* // KitError with default retryable code (fallback check)
|
|
3897
|
+
* const error2 = new KitError({
|
|
3898
|
+
* code: 3002, // NETWORK_TIMEOUT - in DEFAULT_RETRYABLE_ERROR_CODES
|
|
3899
|
+
* name: 'NETWORK_TIMEOUT',
|
|
3900
|
+
* type: 'NETWORK',
|
|
3901
|
+
* recoverability: 'FATAL', // Not RETRYABLE
|
|
3902
|
+
* message: 'Timeout',
|
|
3903
|
+
* })
|
|
3904
|
+
* isRetryableError(error2) // true (code 3002 is in default list)
|
|
3905
|
+
*
|
|
3906
|
+
* // KitError with non-retryable code and FATAL recoverability
|
|
3907
|
+
* const error3 = new KitError({
|
|
3908
|
+
* code: 1001,
|
|
3909
|
+
* name: 'INVALID_INPUT',
|
|
3910
|
+
* type: 'INPUT',
|
|
3911
|
+
* recoverability: 'FATAL',
|
|
3912
|
+
* message: 'Invalid input',
|
|
3913
|
+
* })
|
|
3914
|
+
* isRetryableError(error3) // false
|
|
3915
|
+
*
|
|
3916
|
+
* // Non-KitError
|
|
3917
|
+
* const error4 = new Error('Standard error')
|
|
3918
|
+
* isRetryableError(error4) // false
|
|
3919
|
+
* ```
|
|
3580
3920
|
*/
|
|
3581
3921
|
function isRetryableError(error) {
|
|
3582
|
-
|
|
3922
|
+
// Use proper type guard to check if it's a KitError
|
|
3923
|
+
if (isKitError(error)) {
|
|
3924
|
+
// Priority check: explicit recoverability
|
|
3925
|
+
if (error.recoverability === 'RETRYABLE') {
|
|
3926
|
+
return true;
|
|
3927
|
+
}
|
|
3928
|
+
// Fallback check: error code against default retryable codes
|
|
3929
|
+
return DEFAULT_RETRYABLE_ERROR_CODES.includes(error.code);
|
|
3930
|
+
}
|
|
3931
|
+
return false;
|
|
3583
3932
|
}
|
|
3584
3933
|
/**
|
|
3585
3934
|
* Type guard to check if error is KitError with INPUT type.
|
|
@@ -3776,6 +4125,62 @@ function getErrorMessage(error) {
|
|
|
3776
4125
|
function getErrorCode(error) {
|
|
3777
4126
|
return isKitError(error) ? error.code : null;
|
|
3778
4127
|
}
|
|
4128
|
+
/**
|
|
4129
|
+
* Extract structured error information for logging and events.
|
|
4130
|
+
*
|
|
4131
|
+
* @remarks
|
|
4132
|
+
* Safely extracts error information from any thrown value, handling:
|
|
4133
|
+
* - KitError objects (message preserved - safe to log)
|
|
4134
|
+
* - Standard Error objects (message redacted for security)
|
|
4135
|
+
* - Plain objects with name/message/code (message redacted)
|
|
4136
|
+
* - Primitive values (logged as-is since they contain no structured data)
|
|
4137
|
+
*
|
|
4138
|
+
* For security, only KitError messages are included. Other error messages are
|
|
4139
|
+
* redacted to prevent logging sensitive data like tokens or PII.
|
|
4140
|
+
*
|
|
4141
|
+
* @param error - The error to extract info from.
|
|
4142
|
+
* @returns Structured error information.
|
|
4143
|
+
*
|
|
4144
|
+
* @example
|
|
4145
|
+
* ```typescript
|
|
4146
|
+
* import { extractErrorInfo } from '@core/errors'
|
|
4147
|
+
*
|
|
4148
|
+
* // Standard Error - message is redacted for security
|
|
4149
|
+
* const info1 = extractErrorInfo(new Error('Something went wrong'))
|
|
4150
|
+
* // { name: 'Error', message: 'An error occurred. See error name and code for details.' }
|
|
4151
|
+
*
|
|
4152
|
+
* // KitError - message is preserved (safe type)
|
|
4153
|
+
* const error = createNetworkConnectionError('Ethereum')
|
|
4154
|
+
* const info2 = extractErrorInfo(error)
|
|
4155
|
+
* // { name: 'NETWORK_CONNECTION_FAILED', message: 'Network connection failed for Ethereum', code: 3001 }
|
|
4156
|
+
*
|
|
4157
|
+
* // Primitive value - logged as-is
|
|
4158
|
+
* const info3 = extractErrorInfo('string error')
|
|
4159
|
+
* // { name: 'UnknownError', message: 'string error' }
|
|
4160
|
+
* ```
|
|
4161
|
+
*/
|
|
4162
|
+
function extractErrorInfo(error) {
|
|
4163
|
+
if (error === null || typeof error !== 'object') {
|
|
4164
|
+
return {
|
|
4165
|
+
name: 'UnknownError',
|
|
4166
|
+
message: String(error),
|
|
4167
|
+
};
|
|
4168
|
+
}
|
|
4169
|
+
const err = error;
|
|
4170
|
+
// Only preserve messages for KitError instances (safe type)
|
|
4171
|
+
// For other errors, redact message for security
|
|
4172
|
+
const errorMessage = isKitError(error)
|
|
4173
|
+
? getErrorMessage(error)
|
|
4174
|
+
: 'An error occurred. See error name and code for details.';
|
|
4175
|
+
const info = {
|
|
4176
|
+
name: err.name ?? 'UnknownError',
|
|
4177
|
+
message: errorMessage,
|
|
4178
|
+
};
|
|
4179
|
+
if (typeof err.code === 'number') {
|
|
4180
|
+
info.code = err.code;
|
|
4181
|
+
}
|
|
4182
|
+
return info;
|
|
4183
|
+
}
|
|
3779
4184
|
|
|
3780
4185
|
/**
|
|
3781
4186
|
* Validates if an address format is correct for the specified chain.
|
|
@@ -4320,7 +4725,7 @@ function validateWithStateTracking(value, schema, context, validatorName) {
|
|
|
4320
4725
|
* Zod schema for validating chain definition objects used in buildExplorerUrl.
|
|
4321
4726
|
* This schema ensures the chain definition has the required properties for URL generation.
|
|
4322
4727
|
*/
|
|
4323
|
-
const chainDefinitionSchema = z.object({
|
|
4728
|
+
const chainDefinitionSchema$1 = z.object({
|
|
4324
4729
|
name: z
|
|
4325
4730
|
.string({
|
|
4326
4731
|
required_error: 'Chain name is required',
|
|
@@ -4344,15 +4749,14 @@ const transactionHashSchema = z
|
|
|
4344
4749
|
required_error: 'Transaction hash is required',
|
|
4345
4750
|
invalid_type_error: 'Transaction hash must be a string',
|
|
4346
4751
|
})
|
|
4347
|
-
.
|
|
4348
|
-
.
|
|
4349
|
-
.refine((hash) => hash.length > 0, 'Transaction hash must not be empty or whitespace-only');
|
|
4752
|
+
.transform((hash) => hash.trim())
|
|
4753
|
+
.refine((hash) => hash.length > 0, 'Transaction hash cannot be empty');
|
|
4350
4754
|
/**
|
|
4351
4755
|
* Zod schema for validating buildExplorerUrl function parameters.
|
|
4352
4756
|
* This schema validates both the chain definition and transaction hash together.
|
|
4353
4757
|
*/
|
|
4354
4758
|
z.object({
|
|
4355
|
-
chainDef: chainDefinitionSchema,
|
|
4759
|
+
chainDef: chainDefinitionSchema$1,
|
|
4356
4760
|
txHash: transactionHashSchema,
|
|
4357
4761
|
});
|
|
4358
4762
|
/**
|
|
@@ -4363,6 +4767,69 @@ z
|
|
|
4363
4767
|
.string()
|
|
4364
4768
|
.url('Generated explorer URL is invalid');
|
|
4365
4769
|
|
|
4770
|
+
/**
|
|
4771
|
+
* Parses and validates data against a Zod schema, returning the transformed result.
|
|
4772
|
+
*
|
|
4773
|
+
* Unlike `validate` and `validateOrThrow` which use type assertions (`asserts value is T`),
|
|
4774
|
+
* this function **returns the parsed data** from Zod. This is important when your schema
|
|
4775
|
+
* includes transformations like defaults, coercion, or custom transforms.
|
|
4776
|
+
*
|
|
4777
|
+
* @typeParam T - The expected output type (caller must specify).
|
|
4778
|
+
* @param value - The value to parse and validate.
|
|
4779
|
+
* @param schema - The Zod schema to validate against.
|
|
4780
|
+
* @param context - Context string to include in error messages (e.g., 'user input', 'config').
|
|
4781
|
+
* @returns The parsed and transformed data of type T.
|
|
4782
|
+
* @throws KitError with INPUT_VALIDATION_FAILED code (1098) if validation fails.
|
|
4783
|
+
*
|
|
4784
|
+
* @remarks
|
|
4785
|
+
* This function uses `ZodTypeAny` for the schema parameter to avoid TypeScript's
|
|
4786
|
+
* "excessively deep type instantiation" error in generic contexts. The caller
|
|
4787
|
+
* must provide the expected type `T` explicitly.
|
|
4788
|
+
*
|
|
4789
|
+
* Use this instead of `validate`/`validateOrThrow` when:
|
|
4790
|
+
* - Your schema has `.default()` values
|
|
4791
|
+
* - Your schema uses `.coerce` (e.g., `z.coerce.number()`)
|
|
4792
|
+
* - Your schema has `.transform()` functions
|
|
4793
|
+
* - You need the actual parsed output, not just type narrowing
|
|
4794
|
+
*
|
|
4795
|
+
* @example
|
|
4796
|
+
* ```typescript
|
|
4797
|
+
* import { parseOrThrow } from '@core/utils'
|
|
4798
|
+
* import { z } from 'zod'
|
|
4799
|
+
*
|
|
4800
|
+
* const userSchema = z.object({
|
|
4801
|
+
* name: z.string().default('Anonymous'),
|
|
4802
|
+
* age: z.coerce.number(), // Transforms "25" → 25
|
|
4803
|
+
* })
|
|
4804
|
+
*
|
|
4805
|
+
* type User = z.infer<typeof userSchema>
|
|
4806
|
+
*
|
|
4807
|
+
* // Explicit type parameter
|
|
4808
|
+
* const user = parseOrThrow<User>({ age: '25' }, userSchema, 'user data')
|
|
4809
|
+
* console.log(user) // { name: 'Anonymous', age: 25 }
|
|
4810
|
+
* ```
|
|
4811
|
+
*
|
|
4812
|
+
* @example
|
|
4813
|
+
* ```typescript
|
|
4814
|
+
* // Usage in generic adapter functions (no deep type instantiation)
|
|
4815
|
+
* function validateInput<TInput>(
|
|
4816
|
+
* input: unknown,
|
|
4817
|
+
* schema: z.ZodTypeAny,
|
|
4818
|
+
* name: string,
|
|
4819
|
+
* ): TInput {
|
|
4820
|
+
* return parseOrThrow<TInput>(input, schema, `${name} input`)
|
|
4821
|
+
* }
|
|
4822
|
+
* ```
|
|
4823
|
+
*/
|
|
4824
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- Intentional: T is caller-provided to avoid deep type instantiation from schema inference
|
|
4825
|
+
function parseOrThrow(value, schema, context) {
|
|
4826
|
+
const result = schema.safeParse(value);
|
|
4827
|
+
if (!result.success) {
|
|
4828
|
+
throw createValidationErrorFromZod(result.error, context);
|
|
4829
|
+
}
|
|
4830
|
+
return result.data;
|
|
4831
|
+
}
|
|
4832
|
+
|
|
4366
4833
|
/**
|
|
4367
4834
|
* A type-safe event emitter for managing action-based event subscriptions.
|
|
4368
4835
|
*
|
|
@@ -4637,7 +5104,7 @@ const parseAmount = (params) => {
|
|
|
4637
5104
|
};
|
|
4638
5105
|
|
|
4639
5106
|
var name = "@circle-fin/bridge-kit";
|
|
4640
|
-
var version = "1.
|
|
5107
|
+
var version = "1.6.0";
|
|
4641
5108
|
var pkg = {
|
|
4642
5109
|
name: name,
|
|
4643
5110
|
version: version};
|
|
@@ -5063,7 +5530,7 @@ const createDecimalStringValidator = (options) => (schema) => {
|
|
|
5063
5530
|
* console.log(result.success) // true
|
|
5064
5531
|
* ```
|
|
5065
5532
|
*/
|
|
5066
|
-
z.object({
|
|
5533
|
+
const chainDefinitionSchema = z.object({
|
|
5067
5534
|
name: z.string().min(1, 'Chain name is required'),
|
|
5068
5535
|
type: z.string().min(1, 'Chain type is required'),
|
|
5069
5536
|
});
|
|
@@ -5110,6 +5577,53 @@ const walletContextSchema = z.object({
|
|
|
5110
5577
|
isTestnet: z.boolean(),
|
|
5111
5578
|
}),
|
|
5112
5579
|
});
|
|
5580
|
+
/**
|
|
5581
|
+
* Schema for validating destination wallet contexts.
|
|
5582
|
+
* Extends walletContextSchema with optional useForwarder flag.
|
|
5583
|
+
*
|
|
5584
|
+
* @throws KitError if validation fails
|
|
5585
|
+
*/
|
|
5586
|
+
const destinationContextSchema = walletContextSchema.extend({
|
|
5587
|
+
useForwarder: z.boolean().optional(),
|
|
5588
|
+
recipientAddress: z.string().optional(),
|
|
5589
|
+
});
|
|
5590
|
+
/**
|
|
5591
|
+
* Schema for validating forwarder-only destination contexts.
|
|
5592
|
+
* Used when useForwarder is true and no adapter is provided.
|
|
5593
|
+
* Requires chain definition and recipientAddress.
|
|
5594
|
+
*
|
|
5595
|
+
* Validates that recipientAddress format is correct for the destination chain
|
|
5596
|
+
* (EVM hex address or Solana base58 address).
|
|
5597
|
+
*
|
|
5598
|
+
* @throws KitError if validation fails
|
|
5599
|
+
*/
|
|
5600
|
+
const forwarderOnlyDestinationSchema = z
|
|
5601
|
+
.object({
|
|
5602
|
+
chain: chainDefinitionSchema.extend({
|
|
5603
|
+
isTestnet: z.boolean(),
|
|
5604
|
+
}),
|
|
5605
|
+
recipientAddress: z
|
|
5606
|
+
.string()
|
|
5607
|
+
.min(1, 'Recipient address is required for forwarder-only destination'),
|
|
5608
|
+
useForwarder: z.literal(true),
|
|
5609
|
+
})
|
|
5610
|
+
.superRefine((data, ctx) => {
|
|
5611
|
+
// Pass chain name as string - isValidAddressForChain will use name-based matching
|
|
5612
|
+
if (!isValidAddressForChain(data.recipientAddress, data.chain.name)) {
|
|
5613
|
+
ctx.addIssue({
|
|
5614
|
+
code: z.ZodIssueCode.custom,
|
|
5615
|
+
message: `Invalid recipient address format for chain ${data.chain.name}`,
|
|
5616
|
+
path: ['recipientAddress'],
|
|
5617
|
+
});
|
|
5618
|
+
}
|
|
5619
|
+
});
|
|
5620
|
+
/**
|
|
5621
|
+
* Schema for validating any destination - either with adapter or forwarder-only.
|
|
5622
|
+
*/
|
|
5623
|
+
const bridgeDestinationSchema$1 = z.union([
|
|
5624
|
+
destinationContextSchema,
|
|
5625
|
+
forwarderOnlyDestinationSchema,
|
|
5626
|
+
]);
|
|
5113
5627
|
/**
|
|
5114
5628
|
* Schema for validating a custom fee configuration.
|
|
5115
5629
|
* Validates the simplified CustomFee interface which includes:
|
|
@@ -5207,7 +5721,7 @@ z.object({
|
|
|
5207
5721
|
maxDecimals: 6,
|
|
5208
5722
|
})(z.string())),
|
|
5209
5723
|
source: walletContextSchema,
|
|
5210
|
-
destination:
|
|
5724
|
+
destination: bridgeDestinationSchema$1,
|
|
5211
5725
|
token: z.literal('USDC'),
|
|
5212
5726
|
config: z.object({
|
|
5213
5727
|
transferSpeed: z.nativeEnum(TransferSpeed).optional(),
|
|
@@ -5224,6 +5738,28 @@ z.object({
|
|
|
5224
5738
|
}),
|
|
5225
5739
|
});
|
|
5226
5740
|
|
|
5741
|
+
/**
|
|
5742
|
+
* Creates a Zod superRefine validator for recipient address format validation.
|
|
5743
|
+
* Validates that the address format matches the expected format for the chain type.
|
|
5744
|
+
*
|
|
5745
|
+
* @returns A superRefine function that validates recipientAddress against chain type
|
|
5746
|
+
*/
|
|
5747
|
+
function createRecipientAddressValidator() {
|
|
5748
|
+
return (data, ctx) => {
|
|
5749
|
+
const chain = data.chain;
|
|
5750
|
+
if (chain === null) {
|
|
5751
|
+
return;
|
|
5752
|
+
}
|
|
5753
|
+
if (!isValidAddressForChain(data.recipientAddress, chain)) {
|
|
5754
|
+
const chainInfo = extractChainInfo(chain);
|
|
5755
|
+
ctx.addIssue({
|
|
5756
|
+
code: z.ZodIssueCode.custom,
|
|
5757
|
+
path: ['recipientAddress'],
|
|
5758
|
+
message: `Invalid address format for ${String(chainInfo.name)}. Expected ${chainInfo.expectedAddressFormat}, but received: ${data.recipientAddress}`,
|
|
5759
|
+
});
|
|
5760
|
+
}
|
|
5761
|
+
};
|
|
5762
|
+
}
|
|
5227
5763
|
/**
|
|
5228
5764
|
* Schema for validating AdapterContext for bridge operations.
|
|
5229
5765
|
* Must always contain both adapter and chain explicitly.
|
|
@@ -5243,32 +5779,52 @@ const adapterContextSchema = z.object({
|
|
|
5243
5779
|
const bridgeDestinationWithAddressSchema = adapterContextSchema
|
|
5244
5780
|
.extend({
|
|
5245
5781
|
recipientAddress: z.string().min(1, 'Recipient address is required'),
|
|
5782
|
+
useForwarder: z.boolean().optional(),
|
|
5246
5783
|
})
|
|
5247
|
-
.superRefine((
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
ctx.addIssue({
|
|
5255
|
-
code: z.ZodIssueCode.custom,
|
|
5256
|
-
path: ['recipientAddress'],
|
|
5257
|
-
message: `Invalid address format for ${String(chainInfo.name)}. Expected ${chainInfo.expectedAddressFormat}, but received: ${data.recipientAddress}`,
|
|
5258
|
-
});
|
|
5259
|
-
}
|
|
5784
|
+
.superRefine(createRecipientAddressValidator());
|
|
5785
|
+
/**
|
|
5786
|
+
* Schema for validating AdapterContext with optional useForwarder.
|
|
5787
|
+
* Extends adapterContextSchema with the useForwarder flag.
|
|
5788
|
+
*/
|
|
5789
|
+
const adapterContextWithForwarderSchema = adapterContextSchema.extend({
|
|
5790
|
+
useForwarder: z.boolean().optional(),
|
|
5260
5791
|
});
|
|
5792
|
+
/**
|
|
5793
|
+
* Schema for validating ForwarderDestination objects.
|
|
5794
|
+
* Used when useForwarder is true and no adapter is provided.
|
|
5795
|
+
* Requires chain, recipientAddress, and useForwarder: true.
|
|
5796
|
+
*
|
|
5797
|
+
* When using this destination type:
|
|
5798
|
+
* - The mint step completes when the IRIS API confirms forwardState === 'CONFIRMED'
|
|
5799
|
+
* - No on-chain transaction confirmation is performed (no adapter available)
|
|
5800
|
+
* - The mint step's data field will be undefined (no transaction receipt)
|
|
5801
|
+
*/
|
|
5802
|
+
const forwarderDestinationSchema = z
|
|
5803
|
+
.object({
|
|
5804
|
+
chain: bridgeChainIdentifierSchema,
|
|
5805
|
+
recipientAddress: z.string().min(1, 'Recipient address is required'),
|
|
5806
|
+
useForwarder: z.literal(true),
|
|
5807
|
+
})
|
|
5808
|
+
.strict()
|
|
5809
|
+
.superRefine(createRecipientAddressValidator());
|
|
5261
5810
|
/**
|
|
5262
5811
|
* Schema for validating BridgeDestination union type.
|
|
5263
|
-
*
|
|
5812
|
+
* Supports three destination configurations:
|
|
5813
|
+
* - BridgeDestinationWithAddress (adapter with explicit recipient)
|
|
5814
|
+
* - ForwarderDestination (no adapter, requires useForwarder: true and recipientAddress)
|
|
5815
|
+
* - AdapterContext with optional useForwarder (adapter for default recipient)
|
|
5264
5816
|
*
|
|
5265
|
-
*
|
|
5266
|
-
*
|
|
5267
|
-
*
|
|
5817
|
+
* When using ForwarderDestination (no adapter):
|
|
5818
|
+
* - The mint step completes when the IRIS API confirms forwardState === 'CONFIRMED'
|
|
5819
|
+
* - No on-chain transaction confirmation is performed
|
|
5820
|
+
*
|
|
5821
|
+
* The order matters: we check the more specific schemas first.
|
|
5822
|
+
* This ensures that objects with specific fields are matched correctly.
|
|
5268
5823
|
*/
|
|
5269
5824
|
const bridgeDestinationSchema = z.union([
|
|
5270
5825
|
bridgeDestinationWithAddressSchema,
|
|
5271
|
-
|
|
5826
|
+
forwarderDestinationSchema,
|
|
5827
|
+
adapterContextWithForwarderSchema.strict(),
|
|
5272
5828
|
]);
|
|
5273
5829
|
/**
|
|
5274
5830
|
* Schema for validating bridge parameters with chain identifiers.
|
|
@@ -5342,45 +5898,1297 @@ const bridgeParamsWithChainIdentifierSchema = z.object({
|
|
|
5342
5898
|
});
|
|
5343
5899
|
|
|
5344
5900
|
/**
|
|
5345
|
-
*
|
|
5901
|
+
* Default clock implementation using `Date.now()`.
|
|
5346
5902
|
*
|
|
5347
|
-
*
|
|
5348
|
-
*
|
|
5349
|
-
*
|
|
5350
|
-
* @param ctx - The bridge destination containing the chain identifier
|
|
5351
|
-
* @returns The resolved chain definition
|
|
5352
|
-
* @throws If the chain definition cannot be resolved
|
|
5903
|
+
* @remarks
|
|
5904
|
+
* Use this in production code. For testing, inject a mock clock
|
|
5905
|
+
* that returns controlled timestamps.
|
|
5353
5906
|
*
|
|
5354
5907
|
* @example
|
|
5355
5908
|
* ```typescript
|
|
5356
|
-
* import {
|
|
5909
|
+
* import { defaultClock } from '@core/runtime'
|
|
5357
5910
|
*
|
|
5358
|
-
*
|
|
5359
|
-
*
|
|
5360
|
-
*
|
|
5361
|
-
* chain: 'Ethereum'
|
|
5362
|
-
* })
|
|
5363
|
-
*
|
|
5364
|
-
* // BridgeDestinationWithAddress
|
|
5365
|
-
* const chain2 = resolveChainDefinition({
|
|
5366
|
-
* adapter: mockAdapter,
|
|
5367
|
-
* chain: 'Base',
|
|
5368
|
-
* recipientAddress: '0x123...'
|
|
5369
|
-
* })
|
|
5911
|
+
* const start = defaultClock.now()
|
|
5912
|
+
* // ... do work ...
|
|
5913
|
+
* const elapsed = defaultClock.since(start)
|
|
5370
5914
|
* ```
|
|
5371
5915
|
*/
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5916
|
+
const defaultClock = {
|
|
5917
|
+
now: () => Date.now(),
|
|
5918
|
+
since: (start) => Date.now() - start,
|
|
5919
|
+
};
|
|
5920
|
+
|
|
5375
5921
|
/**
|
|
5376
|
-
*
|
|
5922
|
+
* ID generation utilities for distributed tracing.
|
|
5377
5923
|
*
|
|
5378
|
-
*
|
|
5379
|
-
*
|
|
5924
|
+
* @remarks
|
|
5925
|
+
* | Function | Format | Use Case |
|
|
5926
|
+
* |----------|--------|----------|
|
|
5927
|
+
* | {@link createTraceId} | 32-char hex | Standard traceId (OpenTelemetry) |
|
|
5928
|
+
* | {@link createShortOpId} | 8-char hex | Standard opId |
|
|
5929
|
+
* | {@link createOpId} | `op-{ts}-{rand}` | Timestamped operation IDs |
|
|
5930
|
+
* | {@link createUuidV4} | RFC 4122 UUID | External system compatibility |
|
|
5931
|
+
* | {@link createId} | `{prefix}-{ts}-{rand}` | Custom prefixed IDs |
|
|
5380
5932
|
*
|
|
5381
|
-
*
|
|
5382
|
-
|
|
5383
|
-
|
|
5933
|
+
* @packageDocumentation
|
|
5934
|
+
*/
|
|
5935
|
+
// ============================================================================
|
|
5936
|
+
// Crypto Utilities (Internal)
|
|
5937
|
+
// ============================================================================
|
|
5938
|
+
/** @internal */
|
|
5939
|
+
function hasGetRandomValues() {
|
|
5940
|
+
return (typeof crypto !== 'undefined' &&
|
|
5941
|
+
typeof crypto.getRandomValues === 'function');
|
|
5942
|
+
}
|
|
5943
|
+
/**
|
|
5944
|
+
* Create a W3C/OpenTelemetry-compatible trace ID.
|
|
5945
|
+
*
|
|
5946
|
+
* @returns 32-character lowercase hex string (128-bit).
|
|
5947
|
+
*
|
|
5948
|
+
* @remarks
|
|
5949
|
+
* **Standard function for generating `traceId` values.** Compatible with
|
|
5950
|
+
* OpenTelemetry, Jaeger, Zipkin, and AWS X-Ray.
|
|
5951
|
+
*
|
|
5952
|
+
* @example
|
|
5953
|
+
* ```typescript
|
|
5954
|
+
* const traceId = createTraceId() // "a1b2c3d4e5f6789012345678abcdef00"
|
|
5955
|
+
* ```
|
|
5956
|
+
*/
|
|
5957
|
+
function createTraceId() {
|
|
5958
|
+
const bytes = new Uint8Array(16);
|
|
5959
|
+
if (hasGetRandomValues()) {
|
|
5960
|
+
crypto.getRandomValues(bytes);
|
|
5961
|
+
}
|
|
5962
|
+
else {
|
|
5963
|
+
for (let i = 0; i < 16; i++) {
|
|
5964
|
+
bytes[i] = Math.floor(Math.random() * 256); // NOSONAR:
|
|
5965
|
+
}
|
|
5966
|
+
}
|
|
5967
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
5968
|
+
}
|
|
5969
|
+
|
|
5970
|
+
/**
|
|
5971
|
+
* Clean tags by removing keys with undefined values.
|
|
5972
|
+
*
|
|
5973
|
+
* @param tags - Tags object, may contain undefined values. Accepts `undefined` or `null`.
|
|
5974
|
+
* @returns A new object with undefined values removed, or empty object if input is nullish.
|
|
5975
|
+
*
|
|
5976
|
+
* @remarks
|
|
5977
|
+
* This helper ensures tags are safe for serialization and logging.
|
|
5978
|
+
* Similar to the internal `cleanUndefined` in the logger module, but
|
|
5979
|
+
* typed specifically for {@link Tags}.
|
|
5980
|
+
*
|
|
5981
|
+
* @example
|
|
5982
|
+
* ```typescript
|
|
5983
|
+
* import { cleanTags } from '@core/runtime'
|
|
5984
|
+
*
|
|
5985
|
+
* const tags = { chain: 'Ethereum', amount: 100, extra: undefined }
|
|
5986
|
+
* const cleaned = cleanTags(tags)
|
|
5987
|
+
* // { chain: 'Ethereum', amount: 100 }
|
|
5988
|
+
*
|
|
5989
|
+
* const empty = cleanTags(undefined)
|
|
5990
|
+
* // {}
|
|
5991
|
+
* ```
|
|
5992
|
+
*/
|
|
5993
|
+
function cleanTags(tags) {
|
|
5994
|
+
if (tags == null)
|
|
5995
|
+
return {};
|
|
5996
|
+
return Object.fromEntries(Object.entries(tags).filter(([, value]) => value !== undefined));
|
|
5997
|
+
}
|
|
5998
|
+
|
|
5999
|
+
/**
|
|
6000
|
+
* Check if a pattern is valid.
|
|
6001
|
+
*
|
|
6002
|
+
* @remarks
|
|
6003
|
+
* Invalid patterns:
|
|
6004
|
+
* - Empty segments (e.g., `a..b`, `.a`, `a.`)
|
|
6005
|
+
* - `**` not at end (e.g., `a.**.b`)
|
|
6006
|
+
*
|
|
6007
|
+
* @param pattern - The pattern to validate.
|
|
6008
|
+
* @returns True if valid, false otherwise.
|
|
6009
|
+
*/
|
|
6010
|
+
function isValidPattern(pattern) {
|
|
6011
|
+
// Empty pattern is valid (matches empty topic)
|
|
6012
|
+
if (pattern === '')
|
|
6013
|
+
return true;
|
|
6014
|
+
const segments = pattern.split('.');
|
|
6015
|
+
// Check for empty segments
|
|
6016
|
+
if (segments.includes(''))
|
|
6017
|
+
return false;
|
|
6018
|
+
// Check that ** only appears at the end
|
|
6019
|
+
const doubleStarIndex = segments.indexOf('**');
|
|
6020
|
+
if (doubleStarIndex !== -1 && doubleStarIndex !== segments.length - 1) {
|
|
6021
|
+
return false;
|
|
6022
|
+
}
|
|
6023
|
+
return true;
|
|
6024
|
+
}
|
|
6025
|
+
/**
|
|
6026
|
+
* Convert a pattern to a regular expression.
|
|
6027
|
+
*
|
|
6028
|
+
* @param pattern - The pattern to convert.
|
|
6029
|
+
* @returns A RegExp that matches topics according to the pattern.
|
|
6030
|
+
*/
|
|
6031
|
+
function patternToRegex(pattern) {
|
|
6032
|
+
const segments = pattern.split('.');
|
|
6033
|
+
// Handle ** at end specially to match zero or more segments
|
|
6034
|
+
const lastSegment = segments.at(-1);
|
|
6035
|
+
if (lastSegment === '**') {
|
|
6036
|
+
// Everything before ** must match, then optionally more segments
|
|
6037
|
+
const prefix = segments
|
|
6038
|
+
.slice(0, -1)
|
|
6039
|
+
.map((seg) => {
|
|
6040
|
+
if (seg === '*')
|
|
6041
|
+
return '[^.]+';
|
|
6042
|
+
return seg.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
|
|
6043
|
+
})
|
|
6044
|
+
.join(String.raw `\.`);
|
|
6045
|
+
// ** matches zero or more segments:
|
|
6046
|
+
// - If prefix is empty (**), match anything
|
|
6047
|
+
// - Otherwise, match prefix, then optionally (dot + more content)
|
|
6048
|
+
if (prefix === '') {
|
|
6049
|
+
return /^.*$/;
|
|
6050
|
+
}
|
|
6051
|
+
return new RegExp(String.raw `^${prefix}(?:\..*)?$`);
|
|
6052
|
+
}
|
|
6053
|
+
// No ** - just convert segments normally
|
|
6054
|
+
const escaped = segments
|
|
6055
|
+
.map((segment) => {
|
|
6056
|
+
if (segment === '*')
|
|
6057
|
+
return '[^.]+';
|
|
6058
|
+
return segment.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
|
|
6059
|
+
})
|
|
6060
|
+
.join(String.raw `\.`);
|
|
6061
|
+
return new RegExp(String.raw `^${escaped}$`);
|
|
6062
|
+
}
|
|
6063
|
+
/**
|
|
6064
|
+
* Execute an event handler with error isolation.
|
|
6065
|
+
*
|
|
6066
|
+
* @param handler - The handler function to execute.
|
|
6067
|
+
* @param event - The event to pass to the handler.
|
|
6068
|
+
* @param logger - Optional logger for error reporting.
|
|
6069
|
+
*
|
|
6070
|
+
* @remarks
|
|
6071
|
+
* - Synchronous errors are caught and logged
|
|
6072
|
+
* - Promise rejections are caught without awaiting
|
|
6073
|
+
* - Handler errors never propagate to caller
|
|
6074
|
+
*/
|
|
6075
|
+
function executeHandler(handler, event, logger) {
|
|
6076
|
+
try {
|
|
6077
|
+
const result = handler(event);
|
|
6078
|
+
// Handle promise rejection without awaiting
|
|
6079
|
+
if (result instanceof Promise) {
|
|
6080
|
+
result.catch((err) => {
|
|
6081
|
+
logger?.error('Event handler promise rejected', {
|
|
6082
|
+
event: event.name,
|
|
6083
|
+
error: extractErrorInfo(err),
|
|
6084
|
+
});
|
|
6085
|
+
});
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
catch (err) {
|
|
6089
|
+
// Isolate handler errors
|
|
6090
|
+
logger?.error('Event handler threw', {
|
|
6091
|
+
event: event.name,
|
|
6092
|
+
error: extractErrorInfo(err),
|
|
6093
|
+
});
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
/**
|
|
6097
|
+
* Create an event bus.
|
|
6098
|
+
*
|
|
6099
|
+
* @param opts - Options for the event bus.
|
|
6100
|
+
* @returns An EventBus instance.
|
|
6101
|
+
*
|
|
6102
|
+
* @example
|
|
6103
|
+
* ```typescript
|
|
6104
|
+
* import { createEventBus } from '@core/runtime'
|
|
6105
|
+
*
|
|
6106
|
+
* const bus = createEventBus({ logger })
|
|
6107
|
+
*
|
|
6108
|
+
* // Subscribe to events
|
|
6109
|
+
* const unsubscribe = bus.on('tx.*', (event) => {
|
|
6110
|
+
* console.log('Transaction event:', event.name)
|
|
6111
|
+
* })
|
|
6112
|
+
*
|
|
6113
|
+
* // Emit events
|
|
6114
|
+
* bus.emit({ name: 'tx.sent', data: { txHash: '0x123' } })
|
|
6115
|
+
*
|
|
6116
|
+
* // Unsubscribe
|
|
6117
|
+
* unsubscribe()
|
|
6118
|
+
* ```
|
|
6119
|
+
*/
|
|
6120
|
+
function createEventBus(opts) {
|
|
6121
|
+
const logger = opts?.logger;
|
|
6122
|
+
const subscriptions = new Set();
|
|
6123
|
+
function createBus(baseTags) {
|
|
6124
|
+
return {
|
|
6125
|
+
emit(event) {
|
|
6126
|
+
// Invalid topic names silently don't match any subscriptions
|
|
6127
|
+
if (!isValidPattern(event.name))
|
|
6128
|
+
return;
|
|
6129
|
+
const mergedTags = Object.keys(baseTags).length > 0 || event.tags
|
|
6130
|
+
? { ...baseTags, ...cleanTags(event.tags) }
|
|
6131
|
+
: undefined;
|
|
6132
|
+
const finalEvent = mergedTags === undefined ? event : { ...event, tags: mergedTags };
|
|
6133
|
+
// Notify all matching subscribers
|
|
6134
|
+
for (const sub of subscriptions) {
|
|
6135
|
+
// Match-all subscription
|
|
6136
|
+
if (sub.pattern === null) {
|
|
6137
|
+
executeHandler(sub.handler, finalEvent, logger);
|
|
6138
|
+
continue;
|
|
6139
|
+
}
|
|
6140
|
+
// Patterned subscription: invalid pattern => regex=null => never match
|
|
6141
|
+
if (sub.regex === null)
|
|
6142
|
+
continue;
|
|
6143
|
+
if (!sub.regex.test(event.name))
|
|
6144
|
+
continue;
|
|
6145
|
+
executeHandler(sub.handler, finalEvent, logger);
|
|
6146
|
+
}
|
|
6147
|
+
},
|
|
6148
|
+
child(tags) {
|
|
6149
|
+
// Merge and clean tags, don't mutate parent
|
|
6150
|
+
const childTags = { ...baseTags, ...cleanTags(tags) };
|
|
6151
|
+
return createBus(childTags);
|
|
6152
|
+
},
|
|
6153
|
+
on(patternOrHandler, handler) {
|
|
6154
|
+
let sub;
|
|
6155
|
+
if (typeof patternOrHandler === 'function') {
|
|
6156
|
+
// on(handler) - subscribe to all
|
|
6157
|
+
sub = { pattern: null, handler: patternOrHandler, regex: null };
|
|
6158
|
+
}
|
|
6159
|
+
else {
|
|
6160
|
+
// on(pattern, handler)
|
|
6161
|
+
if (typeof handler !== 'function') {
|
|
6162
|
+
throw new TypeError(`EventBus.on("${patternOrHandler}") expected a function handler`);
|
|
6163
|
+
}
|
|
6164
|
+
const regex = isValidPattern(patternOrHandler)
|
|
6165
|
+
? patternToRegex(patternOrHandler)
|
|
6166
|
+
: null;
|
|
6167
|
+
sub = {
|
|
6168
|
+
pattern: patternOrHandler,
|
|
6169
|
+
handler,
|
|
6170
|
+
regex,
|
|
6171
|
+
};
|
|
6172
|
+
}
|
|
6173
|
+
subscriptions.add(sub);
|
|
6174
|
+
// Return unsubscribe function (idempotent)
|
|
6175
|
+
let unsubscribed = false;
|
|
6176
|
+
return () => {
|
|
6177
|
+
if (!unsubscribed) {
|
|
6178
|
+
subscriptions.delete(sub);
|
|
6179
|
+
unsubscribed = true;
|
|
6180
|
+
}
|
|
6181
|
+
};
|
|
6182
|
+
},
|
|
6183
|
+
};
|
|
6184
|
+
}
|
|
6185
|
+
return createBus({});
|
|
6186
|
+
}
|
|
6187
|
+
|
|
6188
|
+
/** No-op counter that discards all increments. */
|
|
6189
|
+
const noopCounter = {
|
|
6190
|
+
inc: () => {
|
|
6191
|
+
// Intentionally empty - discards increment
|
|
6192
|
+
},
|
|
6193
|
+
};
|
|
6194
|
+
/** No-op histogram that discards all observations. */
|
|
6195
|
+
const noopHistogram = {
|
|
6196
|
+
observe: () => {
|
|
6197
|
+
// Intentionally empty - discards observation
|
|
6198
|
+
},
|
|
6199
|
+
};
|
|
6200
|
+
/** No-op timer that returns an empty stop function. */
|
|
6201
|
+
const noopTimer = {
|
|
6202
|
+
start: () => () => {
|
|
6203
|
+
// Intentionally empty - discards timing
|
|
6204
|
+
},
|
|
6205
|
+
};
|
|
6206
|
+
/**
|
|
6207
|
+
* Create a no-op metrics instance.
|
|
6208
|
+
*
|
|
6209
|
+
* @returns A Metrics instance that discards all operations.
|
|
6210
|
+
*/
|
|
6211
|
+
function createNoopMetrics() {
|
|
6212
|
+
// Self-referential instance - child() returns same object to avoid allocations
|
|
6213
|
+
const instance = {
|
|
6214
|
+
counter: () => noopCounter,
|
|
6215
|
+
histogram: () => noopHistogram,
|
|
6216
|
+
timer: () => noopTimer,
|
|
6217
|
+
child: () => instance,
|
|
6218
|
+
};
|
|
6219
|
+
return instance;
|
|
6220
|
+
}
|
|
6221
|
+
/**
|
|
6222
|
+
* A no-op metrics implementation that discards all operations.
|
|
6223
|
+
*
|
|
6224
|
+
* @remarks
|
|
6225
|
+
* Use this when metrics collection is disabled or not configured.
|
|
6226
|
+
* All metric operations are safe to call and return immediately.
|
|
6227
|
+
* The `child()` method returns the same instance to avoid allocations.
|
|
6228
|
+
*
|
|
6229
|
+
* @example
|
|
6230
|
+
* ```typescript
|
|
6231
|
+
* import { noopMetrics } from '@core/runtime'
|
|
6232
|
+
*
|
|
6233
|
+
* // Use when metrics are disabled
|
|
6234
|
+
* const metrics = config.metricsEnabled
|
|
6235
|
+
* ? createDatadogMetrics(config)
|
|
6236
|
+
* : noopMetrics
|
|
6237
|
+
*
|
|
6238
|
+
* // All operations are safe to call
|
|
6239
|
+
* metrics.counter('requests').inc()
|
|
6240
|
+
* const stop = metrics.timer('query').start()
|
|
6241
|
+
* stop()
|
|
6242
|
+
* ```
|
|
6243
|
+
*/
|
|
6244
|
+
const noopMetrics = createNoopMetrics();
|
|
6245
|
+
|
|
6246
|
+
/**
|
|
6247
|
+
* Define a schema for runtime logger interfaces.
|
|
6248
|
+
*
|
|
6249
|
+
* @remarks
|
|
6250
|
+
* Validate that a runtime logger provides the minimal methods expected by the SDK.
|
|
6251
|
+
*
|
|
6252
|
+
* @example
|
|
6253
|
+
* ```typescript
|
|
6254
|
+
* import { loggerSchema } from '@core/runtime'
|
|
6255
|
+
*
|
|
6256
|
+
* const logger = {
|
|
6257
|
+
* debug: () => undefined,
|
|
6258
|
+
* info: () => undefined,
|
|
6259
|
+
* warn: () => undefined,
|
|
6260
|
+
* error: () => undefined,
|
|
6261
|
+
* child: () => logger,
|
|
6262
|
+
* }
|
|
6263
|
+
*
|
|
6264
|
+
* loggerSchema.parse(logger)
|
|
6265
|
+
* ```
|
|
6266
|
+
*/
|
|
6267
|
+
const loggerSchema = z.custom((value) => {
|
|
6268
|
+
if (value === null || typeof value !== 'object') {
|
|
6269
|
+
return false;
|
|
6270
|
+
}
|
|
6271
|
+
const record = value;
|
|
6272
|
+
return (typeof record['debug'] === 'function' &&
|
|
6273
|
+
typeof record['info'] === 'function' &&
|
|
6274
|
+
typeof record['warn'] === 'function' &&
|
|
6275
|
+
typeof record['error'] === 'function' &&
|
|
6276
|
+
typeof record['child'] === 'function');
|
|
6277
|
+
}, {
|
|
6278
|
+
message: 'Invalid logger',
|
|
6279
|
+
});
|
|
6280
|
+
|
|
6281
|
+
/**
|
|
6282
|
+
* Define a schema for runtime metrics interfaces.
|
|
6283
|
+
*
|
|
6284
|
+
* @remarks
|
|
6285
|
+
* Validate that a metrics implementation exposes the minimal API expected by the runtime.
|
|
6286
|
+
*
|
|
6287
|
+
* @example
|
|
6288
|
+
* ```typescript
|
|
6289
|
+
* import { metricsSchema } from '@core/runtime'
|
|
6290
|
+
*
|
|
6291
|
+
* const metrics = {
|
|
6292
|
+
* counter: () => ({ inc: () => undefined }),
|
|
6293
|
+
* histogram: () => ({ observe: () => undefined }),
|
|
6294
|
+
* timer: () => ({ start: () => () => undefined }),
|
|
6295
|
+
* child: () => metrics,
|
|
6296
|
+
* }
|
|
6297
|
+
*
|
|
6298
|
+
* metricsSchema.parse(metrics)
|
|
6299
|
+
* ```
|
|
6300
|
+
*/
|
|
6301
|
+
const metricsSchema = z.custom((value) => {
|
|
6302
|
+
if (value === null || typeof value !== 'object') {
|
|
6303
|
+
return false;
|
|
6304
|
+
}
|
|
6305
|
+
const record = value;
|
|
6306
|
+
return (typeof record['counter'] === 'function' &&
|
|
6307
|
+
typeof record['histogram'] === 'function' &&
|
|
6308
|
+
typeof record['timer'] === 'function' &&
|
|
6309
|
+
typeof record['child'] === 'function');
|
|
6310
|
+
}, {
|
|
6311
|
+
message: 'Invalid metrics',
|
|
6312
|
+
});
|
|
6313
|
+
|
|
6314
|
+
/**
|
|
6315
|
+
* Omit undefined values from an object.
|
|
6316
|
+
*
|
|
6317
|
+
* @param obj - The object to process.
|
|
6318
|
+
* @returns A new object with undefined values removed.
|
|
6319
|
+
*
|
|
6320
|
+
* @internal
|
|
6321
|
+
* @remarks
|
|
6322
|
+
* Used by both production and mock loggers to ensure consistent behavior.
|
|
6323
|
+
* This prevents undefined values from being serialized in log output,
|
|
6324
|
+
* which can cause issues with some log transports.
|
|
6325
|
+
*/
|
|
6326
|
+
function omitUndefined(obj) {
|
|
6327
|
+
const result = {};
|
|
6328
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
6329
|
+
if (value !== undefined) {
|
|
6330
|
+
result[key] = value;
|
|
6331
|
+
}
|
|
6332
|
+
}
|
|
6333
|
+
return result;
|
|
6334
|
+
}
|
|
6335
|
+
|
|
6336
|
+
/**
|
|
6337
|
+
* Default redaction paths for web3/blockchain SDKs.
|
|
6338
|
+
*
|
|
6339
|
+
* @remarks
|
|
6340
|
+
* These paths target common sensitive fields in blockchain applications.
|
|
6341
|
+
* All user fields are nested under `context`, so paths start with `context.`.
|
|
6342
|
+
* Wildcard `*` matches any key at that level.
|
|
6343
|
+
*/
|
|
6344
|
+
const DEFAULT_REDACT_PATHS = [
|
|
6345
|
+
// Generic Credentials
|
|
6346
|
+
'context.password',
|
|
6347
|
+
'context.passphrase',
|
|
6348
|
+
'context.secret',
|
|
6349
|
+
'context.token',
|
|
6350
|
+
'context.*.password',
|
|
6351
|
+
'context.*.passphrase',
|
|
6352
|
+
'context.*.secret',
|
|
6353
|
+
'context.*.token',
|
|
6354
|
+
// API Keys & Auth Tokens
|
|
6355
|
+
'context.apiKey',
|
|
6356
|
+
'context.apiSecret',
|
|
6357
|
+
'context.accessToken',
|
|
6358
|
+
'context.refreshToken',
|
|
6359
|
+
'context.jwt',
|
|
6360
|
+
'context.bearerToken',
|
|
6361
|
+
'context.sessionId',
|
|
6362
|
+
'context.authorization',
|
|
6363
|
+
'context.cookie',
|
|
6364
|
+
'context.*.apiKey',
|
|
6365
|
+
'context.*.apiSecret',
|
|
6366
|
+
'context.*.accessToken',
|
|
6367
|
+
'context.*.refreshToken',
|
|
6368
|
+
'context.*.jwt',
|
|
6369
|
+
'context.*.bearerToken',
|
|
6370
|
+
'context.*.sessionId',
|
|
6371
|
+
'context.*.authorization',
|
|
6372
|
+
'context.*.cookie',
|
|
6373
|
+
// Web3 / Crypto Keys
|
|
6374
|
+
'context.privateKey',
|
|
6375
|
+
'context.secretKey',
|
|
6376
|
+
'context.signingKey',
|
|
6377
|
+
'context.encryptionKey',
|
|
6378
|
+
'context.*.privateKey',
|
|
6379
|
+
'context.*.secretKey',
|
|
6380
|
+
'context.*.signingKey',
|
|
6381
|
+
'context.*.encryptionKey',
|
|
6382
|
+
// Web3 / Crypto Mnemonics and Seeds
|
|
6383
|
+
'context.mnemonic',
|
|
6384
|
+
'context.seed',
|
|
6385
|
+
'context.seedPhrase',
|
|
6386
|
+
'context.*.mnemonic',
|
|
6387
|
+
'context.*.seed',
|
|
6388
|
+
'context.*.seedPhrase',
|
|
6389
|
+
// OTP / Verification Codes
|
|
6390
|
+
'context.otp',
|
|
6391
|
+
'context.verificationCode',
|
|
6392
|
+
'context.*.otp',
|
|
6393
|
+
'context.*.verificationCode',
|
|
6394
|
+
// Payment Information
|
|
6395
|
+
'context.cardNumber',
|
|
6396
|
+
'context.cvv',
|
|
6397
|
+
'context.accountNumber',
|
|
6398
|
+
'context.*.cardNumber',
|
|
6399
|
+
'context.*.cvv',
|
|
6400
|
+
'context.*.accountNumber',
|
|
6401
|
+
];
|
|
6402
|
+
/**
|
|
6403
|
+
* Wrap user fields under `context` to prevent collision with pino internals.
|
|
6404
|
+
*
|
|
6405
|
+
* @param fields - User-provided log fields.
|
|
6406
|
+
* @returns Object with fields nested under `context`, or undefined if empty.
|
|
6407
|
+
*
|
|
6408
|
+
* @remarks
|
|
6409
|
+
* This function handles edge cases by returning undefined for null, undefined,
|
|
6410
|
+
* or empty objects to avoid unnecessary wrapping in log output.
|
|
6411
|
+
* Undefined values are cleaned before wrapping.
|
|
6412
|
+
*/
|
|
6413
|
+
function wrapInContext(fields) {
|
|
6414
|
+
if (!fields)
|
|
6415
|
+
return undefined;
|
|
6416
|
+
// Clean undefined values for consistency and transport compatibility
|
|
6417
|
+
const cleaned = omitUndefined(fields);
|
|
6418
|
+
// Handle edge case: all values were undefined, resulting in empty object
|
|
6419
|
+
const keys = Object.keys(cleaned);
|
|
6420
|
+
if (keys.length === 0)
|
|
6421
|
+
return undefined;
|
|
6422
|
+
return { context: cleaned };
|
|
6423
|
+
}
|
|
6424
|
+
/**
|
|
6425
|
+
* Wrap a pino instance to conform to our Logger interface.
|
|
6426
|
+
*
|
|
6427
|
+
* @param pinoInstance - The pino logger instance to wrap.
|
|
6428
|
+
* @returns A Logger instance conforming to our stable interface.
|
|
6429
|
+
*/
|
|
6430
|
+
function wrapPino(pinoInstance) {
|
|
6431
|
+
return {
|
|
6432
|
+
debug(message, fields) {
|
|
6433
|
+
const wrapped = wrapInContext(fields);
|
|
6434
|
+
if (wrapped) {
|
|
6435
|
+
pinoInstance.debug(wrapped, message);
|
|
6436
|
+
}
|
|
6437
|
+
else {
|
|
6438
|
+
pinoInstance.debug(message);
|
|
6439
|
+
}
|
|
6440
|
+
},
|
|
6441
|
+
info(message, fields) {
|
|
6442
|
+
const wrapped = wrapInContext(fields);
|
|
6443
|
+
if (wrapped) {
|
|
6444
|
+
pinoInstance.info(wrapped, message);
|
|
6445
|
+
}
|
|
6446
|
+
else {
|
|
6447
|
+
pinoInstance.info(message);
|
|
6448
|
+
}
|
|
6449
|
+
},
|
|
6450
|
+
warn(message, fields) {
|
|
6451
|
+
const wrapped = wrapInContext(fields);
|
|
6452
|
+
if (wrapped) {
|
|
6453
|
+
pinoInstance.warn(wrapped, message);
|
|
6454
|
+
}
|
|
6455
|
+
else {
|
|
6456
|
+
pinoInstance.warn(message);
|
|
6457
|
+
}
|
|
6458
|
+
},
|
|
6459
|
+
error(message, fields) {
|
|
6460
|
+
const wrapped = wrapInContext(fields);
|
|
6461
|
+
if (wrapped) {
|
|
6462
|
+
pinoInstance.error(wrapped, message);
|
|
6463
|
+
}
|
|
6464
|
+
else {
|
|
6465
|
+
pinoInstance.error(message);
|
|
6466
|
+
}
|
|
6467
|
+
},
|
|
6468
|
+
child(tags) {
|
|
6469
|
+
// Child bindings stay flat (not wrapped) - they're part of logger's base context
|
|
6470
|
+
const cleaned = omitUndefined(tags);
|
|
6471
|
+
return wrapPino(pinoInstance.child(cleaned));
|
|
6472
|
+
},
|
|
6473
|
+
};
|
|
6474
|
+
}
|
|
6475
|
+
/**
|
|
6476
|
+
* Build pino redact configuration from our simplified options.
|
|
6477
|
+
*
|
|
6478
|
+
* @param redact - The redact configuration option.
|
|
6479
|
+
* @returns Pino-compatible redact configuration or undefined.
|
|
6480
|
+
*/
|
|
6481
|
+
function buildRedactConfig(redact) {
|
|
6482
|
+
// Explicitly disabled
|
|
6483
|
+
if (redact === false) {
|
|
6484
|
+
return undefined;
|
|
6485
|
+
}
|
|
6486
|
+
// Custom paths provided
|
|
6487
|
+
if (Array.isArray(redact)) {
|
|
6488
|
+
return redact.length > 0
|
|
6489
|
+
? { paths: redact, censor: '[REDACTED]' }
|
|
6490
|
+
: undefined;
|
|
6491
|
+
}
|
|
6492
|
+
// Default: use web3 sensible defaults
|
|
6493
|
+
return {
|
|
6494
|
+
paths: [...DEFAULT_REDACT_PATHS],
|
|
6495
|
+
censor: '[REDACTED]',
|
|
6496
|
+
};
|
|
6497
|
+
}
|
|
6498
|
+
/**
|
|
6499
|
+
* Create a logger backed by pino.
|
|
6500
|
+
*
|
|
6501
|
+
* @param options - Logger options (optional).
|
|
6502
|
+
* @param stream - Destination stream (optional).
|
|
6503
|
+
* @returns A Logger instance.
|
|
6504
|
+
* @throws Error if invalid pino options are provided.
|
|
6505
|
+
*
|
|
6506
|
+
* @remarks
|
|
6507
|
+
* This is a thin wrapper around pino that exposes our stable Logger interface.
|
|
6508
|
+
* Pino handles all transport concerns: JSON, pretty printing, file, remote, browser, etc.
|
|
6509
|
+
*
|
|
6510
|
+
* **Security**: By default, sensitive web3 fields (privateKey, mnemonic, apiKey, etc.)
|
|
6511
|
+
* are automatically redacted from log output. Use `redact: false` to disable.
|
|
6512
|
+
*
|
|
6513
|
+
* @example
|
|
6514
|
+
* ```typescript
|
|
6515
|
+
* import { createLogger } from '@core/runtime'
|
|
6516
|
+
*
|
|
6517
|
+
* // Default: web3 sensitive fields are redacted
|
|
6518
|
+
* const logger = createLogger({ level: 'info' })
|
|
6519
|
+
* logger.info('Signing', { privateKey: '0x123...' })
|
|
6520
|
+
* // Output: { context: { privateKey: '[REDACTED]' }, msg: 'Signing' }
|
|
6521
|
+
*
|
|
6522
|
+
* // Disable redaction (use with caution)
|
|
6523
|
+
* const unsafeLogger = createLogger({ level: 'debug', redact: false })
|
|
6524
|
+
*
|
|
6525
|
+
* // Custom redaction paths
|
|
6526
|
+
* const customLogger = createLogger({
|
|
6527
|
+
* level: 'info',
|
|
6528
|
+
* redact: ['context.mySecret', 'context.*.credentials']
|
|
6529
|
+
* })
|
|
6530
|
+
*
|
|
6531
|
+
* // Pretty output for development
|
|
6532
|
+
* const devLogger = createLogger({
|
|
6533
|
+
* level: 'debug',
|
|
6534
|
+
* transport: { target: 'pino-pretty' }
|
|
6535
|
+
* })
|
|
6536
|
+
*
|
|
6537
|
+
* // Browser logger
|
|
6538
|
+
* const browserLogger = createLogger({
|
|
6539
|
+
* browser: { asObject: true }
|
|
6540
|
+
* })
|
|
6541
|
+
* ```
|
|
6542
|
+
*/
|
|
6543
|
+
function createLogger(options, stream) {
|
|
6544
|
+
const { redact, ...pinoOptions } = {};
|
|
6545
|
+
// Build redaction config
|
|
6546
|
+
const redactConfig = buildRedactConfig(redact);
|
|
6547
|
+
// Build final pino options, only include redact if defined
|
|
6548
|
+
const finalOptions = redactConfig
|
|
6549
|
+
? { ...pinoOptions, redact: redactConfig }
|
|
6550
|
+
: pinoOptions;
|
|
6551
|
+
const pinoInstance = pino(finalOptions);
|
|
6552
|
+
return wrapPino(pinoInstance);
|
|
6553
|
+
}
|
|
6554
|
+
|
|
6555
|
+
/**
|
|
6556
|
+
* Factory for creating Runtime instances with defaults.
|
|
6557
|
+
*
|
|
6558
|
+
* @packageDocumentation
|
|
6559
|
+
*/
|
|
6560
|
+
// ============================================================================
|
|
6561
|
+
// Validation Schema
|
|
6562
|
+
// ============================================================================
|
|
6563
|
+
/**
|
|
6564
|
+
* Schema for validating {@link RuntimeOptions}.
|
|
6565
|
+
*
|
|
6566
|
+
* @remarks
|
|
6567
|
+
* Used internally by {@link createRuntime} to validate options from JS consumers.
|
|
6568
|
+
* Exported for advanced use cases where manual validation is needed.
|
|
6569
|
+
*/
|
|
6570
|
+
const runtimeOptionsSchema = z
|
|
6571
|
+
.object({
|
|
6572
|
+
logger: loggerSchema.optional(),
|
|
6573
|
+
metrics: metricsSchema.optional(),
|
|
6574
|
+
})
|
|
6575
|
+
.passthrough();
|
|
6576
|
+
// ============================================================================
|
|
6577
|
+
// Factory
|
|
6578
|
+
// ============================================================================
|
|
6579
|
+
/**
|
|
6580
|
+
* Create a complete Runtime with sensible defaults.
|
|
6581
|
+
*
|
|
6582
|
+
* @param options - Optional configuration to override logger and metrics.
|
|
6583
|
+
* @returns A frozen, immutable Runtime with all services guaranteed present.
|
|
6584
|
+
* @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
|
|
6585
|
+
*
|
|
6586
|
+
* @remarks
|
|
6587
|
+
* Creates a fully-configured runtime by merging provided options with defaults.
|
|
6588
|
+
* The returned runtime is frozen to enforce immutability.
|
|
6589
|
+
*
|
|
6590
|
+
* | Service | Default | Configurable |
|
|
6591
|
+
* |---------|---------|--------------|
|
|
6592
|
+
* | `logger` | pino logger (info level) | Yes |
|
|
6593
|
+
* | `metrics` | No-op metrics | Yes |
|
|
6594
|
+
* | `events` | Internal event bus | No |
|
|
6595
|
+
* | `clock` | `Date.now()` | No |
|
|
6596
|
+
*
|
|
6597
|
+
* **Why only logger and metrics?**
|
|
6598
|
+
*
|
|
6599
|
+
* - **Logger/Metrics**: Integration points with your infrastructure
|
|
6600
|
+
* - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
|
|
6601
|
+
* - **Clock**: Testing concern - use mock factories for tests
|
|
6602
|
+
*
|
|
6603
|
+
* @example
|
|
6604
|
+
* ```typescript
|
|
6605
|
+
* import { createRuntime, createLogger } from '@core/runtime'
|
|
6606
|
+
*
|
|
6607
|
+
* // Use all defaults
|
|
6608
|
+
* const runtime = createRuntime()
|
|
6609
|
+
*
|
|
6610
|
+
* // Custom logger
|
|
6611
|
+
* const runtime = createRuntime({
|
|
6612
|
+
* logger: createLogger({ level: 'debug' }),
|
|
6613
|
+
* })
|
|
6614
|
+
*
|
|
6615
|
+
* // Custom metrics (e.g., Prometheus)
|
|
6616
|
+
* const runtime = createRuntime({
|
|
6617
|
+
* metrics: myPrometheusMetrics,
|
|
6618
|
+
* })
|
|
6619
|
+
*
|
|
6620
|
+
* // Subscribe to events (don't replace the bus)
|
|
6621
|
+
* runtime.events.on('operation.*', (event) => {
|
|
6622
|
+
* console.log('Event:', event.name)
|
|
6623
|
+
* })
|
|
6624
|
+
* ```
|
|
6625
|
+
*/
|
|
6626
|
+
function createRuntime(options) {
|
|
6627
|
+
// Validate options for JS consumers
|
|
6628
|
+
if (options != null) {
|
|
6629
|
+
parseOrThrow(options, runtimeOptionsSchema, 'runtime options');
|
|
6630
|
+
}
|
|
6631
|
+
// Resolve logger first (events may need it)
|
|
6632
|
+
const logger = options?.logger ?? createLogger();
|
|
6633
|
+
// Internal services - not configurable
|
|
6634
|
+
const events = createEventBus({ logger });
|
|
6635
|
+
const clock = defaultClock;
|
|
6636
|
+
// Resolve metrics
|
|
6637
|
+
const metrics = options?.metrics ?? noopMetrics;
|
|
6638
|
+
return Object.freeze({ logger, events, metrics, clock });
|
|
6639
|
+
}
|
|
6640
|
+
|
|
6641
|
+
/**
|
|
6642
|
+
* Invocation context resolution - resolves the **WHO/HOW** of an operation.
|
|
6643
|
+
*
|
|
6644
|
+
* @packageDocumentation
|
|
6645
|
+
*/
|
|
6646
|
+
// ============================================================================
|
|
6647
|
+
// Validation Schemas
|
|
6648
|
+
// ============================================================================
|
|
6649
|
+
/**
|
|
6650
|
+
* Schema for validating Caller.
|
|
6651
|
+
*/
|
|
6652
|
+
const callerSchema = z.object({
|
|
6653
|
+
type: z.string(),
|
|
6654
|
+
name: z.string(),
|
|
6655
|
+
version: z.string().optional(),
|
|
6656
|
+
});
|
|
6657
|
+
/**
|
|
6658
|
+
* Schema for validating InvocationMeta input.
|
|
6659
|
+
*/
|
|
6660
|
+
const invocationMetaSchema = z
|
|
6661
|
+
.object({
|
|
6662
|
+
traceId: z.string().optional(),
|
|
6663
|
+
runtime: z.object({}).passthrough().optional(),
|
|
6664
|
+
tokens: z.object({}).passthrough().optional(),
|
|
6665
|
+
callers: z.array(callerSchema).optional(),
|
|
6666
|
+
})
|
|
6667
|
+
.strict();
|
|
6668
|
+
// ============================================================================
|
|
6669
|
+
// Invocation Context Resolution
|
|
6670
|
+
// ============================================================================
|
|
6671
|
+
/**
|
|
6672
|
+
* Resolve invocation metadata to invocation context.
|
|
6673
|
+
*
|
|
6674
|
+
* @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
|
|
6675
|
+
* @param defaults - Default runtime and tokens to use if not overridden.
|
|
6676
|
+
* @returns Frozen, immutable invocation context with guaranteed values.
|
|
6677
|
+
* @throws KitError when meta contains invalid properties.
|
|
6678
|
+
*
|
|
6679
|
+
* @remarks
|
|
6680
|
+
* Resolves the **WHO** called and **HOW** to observe:
|
|
6681
|
+
* - TraceId: Uses provided value or generates new one
|
|
6682
|
+
* - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
|
|
6683
|
+
* - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
|
|
6684
|
+
* - Callers: Uses provided array or empty array
|
|
6685
|
+
*
|
|
6686
|
+
* The returned context is frozen to enforce immutability.
|
|
6687
|
+
*
|
|
6688
|
+
* @example
|
|
6689
|
+
* ```typescript
|
|
6690
|
+
* import { resolveInvocationContext, createRuntime } from '@core/runtime'
|
|
6691
|
+
* import { createTokenRegistry } from '@core/tokens'
|
|
6692
|
+
*
|
|
6693
|
+
* const defaults = {
|
|
6694
|
+
* runtime: createRuntime(),
|
|
6695
|
+
* tokens: createTokenRegistry(),
|
|
6696
|
+
* }
|
|
6697
|
+
*
|
|
6698
|
+
* // Minimal - just using defaults
|
|
6699
|
+
* const ctx = resolveInvocationContext(undefined, defaults)
|
|
6700
|
+
*
|
|
6701
|
+
* // With trace ID and caller info
|
|
6702
|
+
* const ctx = resolveInvocationContext(
|
|
6703
|
+
* {
|
|
6704
|
+
* traceId: 'abc-123',
|
|
6705
|
+
* callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
|
|
6706
|
+
* },
|
|
6707
|
+
* defaults
|
|
6708
|
+
* )
|
|
6709
|
+
*
|
|
6710
|
+
* // With runtime override (complete replacement)
|
|
6711
|
+
* const ctx = resolveInvocationContext(
|
|
6712
|
+
* { runtime: createRuntime({ logger: myLogger }) },
|
|
6713
|
+
* defaults
|
|
6714
|
+
* )
|
|
6715
|
+
* ```
|
|
6716
|
+
*/
|
|
6717
|
+
function resolveInvocationContext(meta, defaults) {
|
|
6718
|
+
// Validate meta input if provided
|
|
6719
|
+
if (meta !== undefined) {
|
|
6720
|
+
const result = invocationMetaSchema.safeParse(meta);
|
|
6721
|
+
if (!result.success) {
|
|
6722
|
+
throw createValidationFailedError$1('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
// Generate trace ID if not provided
|
|
6726
|
+
const traceId = meta?.traceId ?? createTraceId();
|
|
6727
|
+
// Use meta overrides or fall back to defaults
|
|
6728
|
+
const runtime = meta?.runtime ?? defaults.runtime;
|
|
6729
|
+
const tokens = meta?.tokens ?? defaults.tokens;
|
|
6730
|
+
const callers = meta?.callers ?? [];
|
|
6731
|
+
return Object.freeze({ traceId, runtime, tokens, callers });
|
|
6732
|
+
}
|
|
6733
|
+
|
|
6734
|
+
/**
|
|
6735
|
+
* Extend an invocation context by appending a caller to its call chain.
|
|
6736
|
+
*
|
|
6737
|
+
* @param context - The existing invocation context to extend.
|
|
6738
|
+
* @param caller - The caller to append to the call chain.
|
|
6739
|
+
* @returns A new frozen invocation context with the caller appended.
|
|
6740
|
+
*
|
|
6741
|
+
* @remarks
|
|
6742
|
+
* This function creates a new immutable context with the caller appended
|
|
6743
|
+
* to the `callers` array while preserving all other context properties
|
|
6744
|
+
* (traceId, runtime, tokens).
|
|
6745
|
+
*
|
|
6746
|
+
* The returned context is frozen to enforce immutability.
|
|
6747
|
+
*
|
|
6748
|
+
* @example
|
|
6749
|
+
* ```typescript
|
|
6750
|
+
* import { extendInvocationContext } from '@core/runtime'
|
|
6751
|
+
*
|
|
6752
|
+
* const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
|
|
6753
|
+
* const extended = extendInvocationContext(existingContext, caller)
|
|
6754
|
+
* // extended.callers === [...existingContext.callers, caller]
|
|
6755
|
+
* ```
|
|
6756
|
+
*/
|
|
6757
|
+
function extendInvocationContext(context, caller) {
|
|
6758
|
+
return Object.freeze({
|
|
6759
|
+
traceId: context.traceId,
|
|
6760
|
+
runtime: context.runtime,
|
|
6761
|
+
tokens: context.tokens,
|
|
6762
|
+
callers: [...context.callers, caller],
|
|
6763
|
+
});
|
|
6764
|
+
}
|
|
6765
|
+
|
|
6766
|
+
// Clock - expose defaultClock for backward compatibility
|
|
6767
|
+
/** Clock validation schema (backward compatibility). */
|
|
6768
|
+
z.custom((val) => val !== null &&
|
|
6769
|
+
typeof val === 'object' &&
|
|
6770
|
+
'now' in val &&
|
|
6771
|
+
typeof val['now'] === 'function');
|
|
6772
|
+
/** EventBus validation schema (backward compatibility). */
|
|
6773
|
+
z.custom((val) => val !== null &&
|
|
6774
|
+
typeof val === 'object' &&
|
|
6775
|
+
'emit' in val &&
|
|
6776
|
+
typeof val['emit'] === 'function');
|
|
6777
|
+
/** Runtime validation schema (backward compatibility). */
|
|
6778
|
+
z
|
|
6779
|
+
.object({
|
|
6780
|
+
logger: z.any().optional(),
|
|
6781
|
+
events: z.any().optional(),
|
|
6782
|
+
metrics: z.any().optional(),
|
|
6783
|
+
clock: z.any().optional(),
|
|
6784
|
+
})
|
|
6785
|
+
.passthrough();
|
|
6786
|
+
|
|
6787
|
+
/**
|
|
6788
|
+
* Create a structured error for token resolution failures.
|
|
6789
|
+
*
|
|
6790
|
+
* @remarks
|
|
6791
|
+
* Returns a `KitError` with `INPUT_INVALID_TOKEN` code (1006).
|
|
6792
|
+
* The error trace contains the selector and chain context for debugging.
|
|
6793
|
+
*
|
|
6794
|
+
* @param message - Human-readable error description.
|
|
6795
|
+
* @param selector - The token selector that failed to resolve.
|
|
6796
|
+
* @param chainId - The chain being resolved for (optional).
|
|
6797
|
+
* @param cause - The underlying error, if any (optional).
|
|
6798
|
+
* @returns A KitError with INPUT type and FATAL recoverability.
|
|
6799
|
+
*
|
|
6800
|
+
* @example
|
|
6801
|
+
* ```typescript
|
|
6802
|
+
* throw createTokenResolutionError(
|
|
6803
|
+
* 'Unknown token symbol: FAKE',
|
|
6804
|
+
* 'FAKE',
|
|
6805
|
+
* 'Ethereum'
|
|
6806
|
+
* )
|
|
6807
|
+
* ```
|
|
6808
|
+
*/
|
|
6809
|
+
function createTokenResolutionError(message, selector, chainId, cause) {
|
|
6810
|
+
const trace = {
|
|
6811
|
+
selector,
|
|
6812
|
+
...(chainId === undefined ? {} : { chainId }),
|
|
6813
|
+
...({} ),
|
|
6814
|
+
};
|
|
6815
|
+
return new KitError({
|
|
6816
|
+
...InputError.INVALID_TOKEN,
|
|
6817
|
+
recoverability: 'FATAL',
|
|
6818
|
+
message,
|
|
6819
|
+
cause: { trace },
|
|
6820
|
+
});
|
|
6821
|
+
}
|
|
6822
|
+
|
|
6823
|
+
/**
|
|
6824
|
+
* USDC token definition with addresses and metadata.
|
|
6825
|
+
*
|
|
6826
|
+
* @remarks
|
|
6827
|
+
* This is the built-in USDC definition used by the TokenRegistry.
|
|
6828
|
+
* Includes all known USDC addresses across supported chains.
|
|
6829
|
+
*
|
|
6830
|
+
* Keys use the `Blockchain` enum for type safety. Both enum values
|
|
6831
|
+
* and string literals are supported:
|
|
6832
|
+
* - `Blockchain.Ethereum` or `'Ethereum'`
|
|
6833
|
+
*
|
|
6834
|
+
* @example
|
|
6835
|
+
* ```typescript
|
|
6836
|
+
* import { USDC } from '@core/tokens'
|
|
6837
|
+
* import { Blockchain } from '@core/chains'
|
|
6838
|
+
*
|
|
6839
|
+
* console.log(USDC.symbol) // 'USDC'
|
|
6840
|
+
* console.log(USDC.decimals) // 6
|
|
6841
|
+
* console.log(USDC.locators[Blockchain.Ethereum])
|
|
6842
|
+
* // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
|
|
6843
|
+
* ```
|
|
6844
|
+
*/
|
|
6845
|
+
const USDC = {
|
|
6846
|
+
symbol: 'USDC',
|
|
6847
|
+
decimals: 6,
|
|
6848
|
+
locators: {
|
|
6849
|
+
// =========================================================================
|
|
6850
|
+
// Mainnets (alphabetically sorted)
|
|
6851
|
+
// =========================================================================
|
|
6852
|
+
[Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
|
|
6853
|
+
[Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
|
|
6854
|
+
[Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
|
|
6855
|
+
[Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
|
|
6856
|
+
[Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
|
|
6857
|
+
[Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
|
6858
|
+
[Blockchain.Hedera]: '0.0.456858',
|
|
6859
|
+
[Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
|
|
6860
|
+
[Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
|
|
6861
|
+
[Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
|
|
6862
|
+
[Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
|
|
6863
|
+
[Blockchain.Noble]: 'uusdc',
|
|
6864
|
+
[Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
|
|
6865
|
+
[Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
|
|
6866
|
+
[Blockchain.Polkadot_Asset_Hub]: '1337',
|
|
6867
|
+
[Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
|
|
6868
|
+
[Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
|
|
6869
|
+
[Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
|
6870
|
+
[Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
|
|
6871
|
+
[Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
|
|
6872
|
+
[Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
|
|
6873
|
+
[Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
|
|
6874
|
+
[Blockchain.World_Chain]: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
|
|
6875
|
+
[Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
|
|
6876
|
+
[Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
|
|
6877
|
+
// =========================================================================
|
|
6878
|
+
// Testnets (alphabetically sorted)
|
|
6879
|
+
// =========================================================================
|
|
6880
|
+
[Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
|
|
6881
|
+
[Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
|
|
6882
|
+
[Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
6883
|
+
[Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
|
|
6884
|
+
[Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
|
|
6885
|
+
[Blockchain.Hedera_Testnet]: '0.0.429274',
|
|
6886
|
+
[Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
|
|
6887
|
+
[Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
|
|
6888
|
+
[Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
|
|
6889
|
+
[Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
|
|
6890
|
+
[Blockchain.Noble_Testnet]: 'uusdc',
|
|
6891
|
+
[Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
|
|
6892
|
+
[Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
|
|
6893
|
+
[Blockchain.Polkadot_Westmint]: '31337',
|
|
6894
|
+
[Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
6895
|
+
[Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
|
|
6896
|
+
[Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
|
|
6897
|
+
[Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
|
|
6898
|
+
[Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
|
|
6899
|
+
[Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
|
|
6900
|
+
[Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
|
|
6901
|
+
[Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
|
|
6902
|
+
[Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
|
|
6903
|
+
[Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
|
|
6904
|
+
},
|
|
6905
|
+
};
|
|
6906
|
+
|
|
6907
|
+
// Re-export for consumers
|
|
6908
|
+
/**
|
|
6909
|
+
* All default token definitions.
|
|
6910
|
+
*
|
|
6911
|
+
* @remarks
|
|
6912
|
+
* These tokens are automatically included in the TokenRegistry when created
|
|
6913
|
+
* without explicit defaults. Extensions can override these definitions.
|
|
6914
|
+
*
|
|
6915
|
+
* @example
|
|
6916
|
+
* ```typescript
|
|
6917
|
+
* import { createTokenRegistry } from '@core/tokens'
|
|
6918
|
+
*
|
|
6919
|
+
* // Registry uses these by default
|
|
6920
|
+
* const registry = createTokenRegistry()
|
|
6921
|
+
*
|
|
6922
|
+
* // Add custom tokens (built-ins are still included)
|
|
6923
|
+
* const customRegistry = createTokenRegistry({
|
|
6924
|
+
* tokens: [myCustomToken],
|
|
6925
|
+
* })
|
|
6926
|
+
* ```
|
|
6927
|
+
*/
|
|
6928
|
+
const DEFAULT_TOKENS = [USDC];
|
|
6929
|
+
|
|
6930
|
+
/**
|
|
6931
|
+
* Check if a selector is a raw token selector (object form).
|
|
6932
|
+
*
|
|
6933
|
+
* @param selector - The token selector to check.
|
|
6934
|
+
* @returns True if the selector is a raw token selector.
|
|
6935
|
+
*/
|
|
6936
|
+
function isRawSelector(selector) {
|
|
6937
|
+
return typeof selector === 'object' && 'locator' in selector;
|
|
6938
|
+
}
|
|
6939
|
+
/**
|
|
6940
|
+
* Normalize a symbol to uppercase for case-insensitive lookup.
|
|
6941
|
+
*
|
|
6942
|
+
* @param symbol - The symbol to normalize.
|
|
6943
|
+
* @returns The normalized (uppercase) symbol.
|
|
6944
|
+
*/
|
|
6945
|
+
function normalizeSymbol(symbol) {
|
|
6946
|
+
return symbol.toUpperCase();
|
|
6947
|
+
}
|
|
6948
|
+
/**
|
|
6949
|
+
* Create a token registry with built-in tokens and optional extensions.
|
|
6950
|
+
*
|
|
6951
|
+
* @remarks
|
|
6952
|
+
* The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
|
|
6953
|
+
* Custom tokens are merged on top - use this to add new tokens or override
|
|
6954
|
+
* built-in definitions.
|
|
6955
|
+
*
|
|
6956
|
+
* @param options - Configuration options for the registry.
|
|
6957
|
+
* @returns A token registry instance.
|
|
6958
|
+
*
|
|
6959
|
+
* @example
|
|
6960
|
+
* ```typescript
|
|
6961
|
+
* import { createTokenRegistry } from '@core/tokens'
|
|
6962
|
+
*
|
|
6963
|
+
* // Create registry with built-in tokens (USDC, etc.)
|
|
6964
|
+
* const registry = createTokenRegistry()
|
|
6965
|
+
*
|
|
6966
|
+
* // Resolve USDC on Ethereum
|
|
6967
|
+
* const usdc = registry.resolve('USDC', 'Ethereum')
|
|
6968
|
+
* console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
|
|
6969
|
+
* console.log(usdc.decimals) // 6
|
|
6970
|
+
* ```
|
|
6971
|
+
*
|
|
6972
|
+
* @example
|
|
6973
|
+
* ```typescript
|
|
6974
|
+
* // Add custom tokens (built-ins are still included)
|
|
6975
|
+
* const myToken: TokenDefinition = {
|
|
6976
|
+
* symbol: 'MY',
|
|
6977
|
+
* decimals: 18,
|
|
6978
|
+
* locators: { Ethereum: '0x...' },
|
|
6979
|
+
* }
|
|
6980
|
+
*
|
|
6981
|
+
* const registry = createTokenRegistry({ tokens: [myToken] })
|
|
6982
|
+
* registry.resolve('USDC', 'Ethereum') // Still works!
|
|
6983
|
+
* registry.resolve('MY', 'Ethereum') // Also works
|
|
6984
|
+
* ```
|
|
6985
|
+
*
|
|
6986
|
+
* @example
|
|
6987
|
+
* ```typescript
|
|
6988
|
+
* // Override a built-in token
|
|
6989
|
+
* const customUsdc: TokenDefinition = {
|
|
6990
|
+
* symbol: 'USDC',
|
|
6991
|
+
* decimals: 6,
|
|
6992
|
+
* locators: { MyChain: '0xCustomAddress' },
|
|
6993
|
+
* }
|
|
6994
|
+
*
|
|
6995
|
+
* const registry = createTokenRegistry({ tokens: [customUsdc] })
|
|
6996
|
+
* // Now USDC resolves to customUsdc definition
|
|
6997
|
+
* ```
|
|
6998
|
+
*
|
|
6999
|
+
* @example
|
|
7000
|
+
* ```typescript
|
|
7001
|
+
* // Resolve arbitrary tokens by raw locator
|
|
7002
|
+
* const registry = createTokenRegistry()
|
|
7003
|
+
* const token = registry.resolve(
|
|
7004
|
+
* { locator: '0x1234...', decimals: 18 },
|
|
7005
|
+
* 'Ethereum'
|
|
7006
|
+
* )
|
|
7007
|
+
* ```
|
|
7008
|
+
*/
|
|
7009
|
+
function createTokenRegistry(options = {}) {
|
|
7010
|
+
const { tokens = [], requireDecimals = false } = options;
|
|
7011
|
+
// Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
|
|
7012
|
+
const tokenMap = new Map();
|
|
7013
|
+
// Add built-in tokens first
|
|
7014
|
+
for (const def of DEFAULT_TOKENS) {
|
|
7015
|
+
tokenMap.set(normalizeSymbol(def.symbol), def);
|
|
7016
|
+
}
|
|
7017
|
+
// Custom tokens override built-ins
|
|
7018
|
+
for (const def of tokens) {
|
|
7019
|
+
tokenMap.set(normalizeSymbol(def.symbol), def);
|
|
7020
|
+
}
|
|
7021
|
+
/**
|
|
7022
|
+
* Resolve a symbol selector to token information.
|
|
7023
|
+
*/
|
|
7024
|
+
function resolveSymbol(symbol, chainId) {
|
|
7025
|
+
const normalizedSymbol = normalizeSymbol(symbol);
|
|
7026
|
+
const definition = tokenMap.get(normalizedSymbol);
|
|
7027
|
+
if (definition === undefined) {
|
|
7028
|
+
throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
|
|
7029
|
+
}
|
|
7030
|
+
const locator = definition.locators[chainId];
|
|
7031
|
+
if (locator === undefined || locator.trim() === '') {
|
|
7032
|
+
throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
|
|
7033
|
+
}
|
|
7034
|
+
return {
|
|
7035
|
+
symbol: definition.symbol,
|
|
7036
|
+
decimals: definition.decimals,
|
|
7037
|
+
locator,
|
|
7038
|
+
};
|
|
7039
|
+
}
|
|
7040
|
+
/**
|
|
7041
|
+
* Resolve a raw selector to token information.
|
|
7042
|
+
*/
|
|
7043
|
+
function resolveRaw(selector, chainId) {
|
|
7044
|
+
const { locator, decimals } = selector;
|
|
7045
|
+
// Validate locator
|
|
7046
|
+
if (!locator || typeof locator !== 'string') {
|
|
7047
|
+
throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
|
|
7048
|
+
}
|
|
7049
|
+
// Decimals are always required for raw selectors
|
|
7050
|
+
if (decimals === undefined) {
|
|
7051
|
+
const message = requireDecimals
|
|
7052
|
+
? 'Decimals required for raw token selector (requireDecimals: true)'
|
|
7053
|
+
: 'Decimals required for raw token selector. Provide { locator, decimals }.';
|
|
7054
|
+
throw createTokenResolutionError(message, selector, chainId);
|
|
7055
|
+
}
|
|
7056
|
+
// Validate decimals
|
|
7057
|
+
if (typeof decimals !== 'number' ||
|
|
7058
|
+
decimals < 0 ||
|
|
7059
|
+
!Number.isInteger(decimals)) {
|
|
7060
|
+
throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
|
|
7061
|
+
}
|
|
7062
|
+
return {
|
|
7063
|
+
decimals,
|
|
7064
|
+
locator,
|
|
7065
|
+
};
|
|
7066
|
+
}
|
|
7067
|
+
return {
|
|
7068
|
+
resolve(selector, chainId) {
|
|
7069
|
+
// Runtime validation of inputs - these checks are for JS consumers
|
|
7070
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
7071
|
+
if (selector === null || selector === undefined) {
|
|
7072
|
+
throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
|
|
7073
|
+
}
|
|
7074
|
+
if (chainId === '' || typeof chainId !== 'string') {
|
|
7075
|
+
throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
|
|
7076
|
+
}
|
|
7077
|
+
// Dispatch based on selector type
|
|
7078
|
+
if (isRawSelector(selector)) {
|
|
7079
|
+
return resolveRaw(selector, chainId);
|
|
7080
|
+
}
|
|
7081
|
+
if (typeof selector === 'string') {
|
|
7082
|
+
return resolveSymbol(selector, chainId);
|
|
7083
|
+
}
|
|
7084
|
+
throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
|
|
7085
|
+
},
|
|
7086
|
+
get(symbol) {
|
|
7087
|
+
if (!symbol || typeof symbol !== 'string') {
|
|
7088
|
+
return undefined;
|
|
7089
|
+
}
|
|
7090
|
+
return tokenMap.get(normalizeSymbol(symbol));
|
|
7091
|
+
},
|
|
7092
|
+
has(symbol) {
|
|
7093
|
+
if (!symbol || typeof symbol !== 'string') {
|
|
7094
|
+
return false;
|
|
7095
|
+
}
|
|
7096
|
+
return tokenMap.has(normalizeSymbol(symbol));
|
|
7097
|
+
},
|
|
7098
|
+
symbols() {
|
|
7099
|
+
return Array.from(tokenMap.values()).map((def) => def.symbol);
|
|
7100
|
+
},
|
|
7101
|
+
entries() {
|
|
7102
|
+
return Array.from(tokenMap.values());
|
|
7103
|
+
},
|
|
7104
|
+
};
|
|
7105
|
+
}
|
|
7106
|
+
|
|
7107
|
+
/**
|
|
7108
|
+
* Define a schema for token registry interfaces.
|
|
7109
|
+
*
|
|
7110
|
+
* @remarks
|
|
7111
|
+
* Validate that a registry exposes the `resolve()` API used by adapters.
|
|
7112
|
+
*
|
|
7113
|
+
* @example
|
|
7114
|
+
* ```typescript
|
|
7115
|
+
* import { tokenRegistrySchema } from '@core/tokens'
|
|
7116
|
+
*
|
|
7117
|
+
* const registry = {
|
|
7118
|
+
* resolve: () => ({ locator: '0x0', decimals: 6 }),
|
|
7119
|
+
* }
|
|
7120
|
+
*
|
|
7121
|
+
* tokenRegistrySchema.parse(registry)
|
|
7122
|
+
* ```
|
|
7123
|
+
*/
|
|
7124
|
+
z.custom((value) => {
|
|
7125
|
+
if (value === null || typeof value !== 'object') {
|
|
7126
|
+
return false;
|
|
7127
|
+
}
|
|
7128
|
+
const record = value;
|
|
7129
|
+
return (typeof record['resolve'] === 'function' &&
|
|
7130
|
+
typeof record['get'] === 'function' &&
|
|
7131
|
+
typeof record['has'] === 'function' &&
|
|
7132
|
+
typeof record['symbols'] === 'function' &&
|
|
7133
|
+
typeof record['entries'] === 'function');
|
|
7134
|
+
}, {
|
|
7135
|
+
message: 'Invalid token registry',
|
|
7136
|
+
});
|
|
7137
|
+
|
|
7138
|
+
/**
|
|
7139
|
+
* Type guard to check if the destination is a forwarder-only destination.
|
|
7140
|
+
*
|
|
7141
|
+
* Forwarder-only destinations have `useForwarder: true` and no adapter.
|
|
7142
|
+
* They require a `recipientAddress` to be specified.
|
|
7143
|
+
*
|
|
7144
|
+
* @param dest - The bridge destination to check
|
|
7145
|
+
* @returns True if this is a forwarder-only destination without adapter
|
|
7146
|
+
*/
|
|
7147
|
+
function isForwarderOnlyDestination(dest) {
|
|
7148
|
+
return (dest.useForwarder === true &&
|
|
7149
|
+
!('adapter' in dest) &&
|
|
7150
|
+
'recipientAddress' in dest);
|
|
7151
|
+
}
|
|
7152
|
+
/**
|
|
7153
|
+
* Resolves a chain identifier to a chain definition.
|
|
7154
|
+
*
|
|
7155
|
+
* Both AdapterContext and BridgeDestinationWithAddress have the chain property
|
|
7156
|
+
* at the top level, so we can directly access it from either type.
|
|
7157
|
+
*
|
|
7158
|
+
* @param ctx - The bridge destination containing the chain identifier
|
|
7159
|
+
* @returns The resolved chain definition
|
|
7160
|
+
* @throws If the chain definition cannot be resolved
|
|
7161
|
+
*
|
|
7162
|
+
* @example
|
|
7163
|
+
* ```typescript
|
|
7164
|
+
* import { Blockchain } from '@core/chains'
|
|
7165
|
+
*
|
|
7166
|
+
* // AdapterContext
|
|
7167
|
+
* const chain1 = resolveChainDefinition({
|
|
7168
|
+
* adapter: mockAdapter,
|
|
7169
|
+
* chain: 'Ethereum'
|
|
7170
|
+
* })
|
|
7171
|
+
*
|
|
7172
|
+
* // BridgeDestinationWithAddress
|
|
7173
|
+
* const chain2 = resolveChainDefinition({
|
|
7174
|
+
* adapter: mockAdapter,
|
|
7175
|
+
* chain: 'Base',
|
|
7176
|
+
* recipientAddress: '0x123...'
|
|
7177
|
+
* })
|
|
7178
|
+
* ```
|
|
7179
|
+
*/
|
|
7180
|
+
function resolveChainDefinition(ctx) {
|
|
7181
|
+
return resolveChainIdentifier(ctx.chain);
|
|
7182
|
+
}
|
|
7183
|
+
/**
|
|
7184
|
+
* Resolves the signer's address from a bridge destination.
|
|
7185
|
+
*
|
|
7186
|
+
* This function resolves the address that will be used for transaction signing,
|
|
7187
|
+
* ignoring any `recipientAddress` field which is handled separately.
|
|
7188
|
+
*
|
|
7189
|
+
* It handles two cases:
|
|
7190
|
+
* - Developer-controlled adapters - returns the explicit address from context
|
|
7191
|
+
* - User-controlled adapters - calls getAddress() on the adapter
|
|
5384
7192
|
*
|
|
5385
7193
|
* @param ctx - The bridge destination to resolve the address from
|
|
5386
7194
|
* @returns The resolved signer address string
|
|
@@ -5451,6 +7259,46 @@ function resolveAmount(params) {
|
|
|
5451
7259
|
}
|
|
5452
7260
|
return params.amount;
|
|
5453
7261
|
}
|
|
7262
|
+
/**
|
|
7263
|
+
* Resolves the invocation context for a bridge operation.
|
|
7264
|
+
*
|
|
7265
|
+
* Takes optional invocation metadata and resolves it into a full
|
|
7266
|
+
* InvocationContext with BridgeKit caller information appended.
|
|
7267
|
+
*
|
|
7268
|
+
* @param invocationMeta - Optional invocation metadata for tracing and correlation
|
|
7269
|
+
* @returns An InvocationContext with traceId, runtime, tokens, and caller chain
|
|
7270
|
+
*
|
|
7271
|
+
* @example
|
|
7272
|
+
* ```typescript
|
|
7273
|
+
* // With user-provided invocation metadata
|
|
7274
|
+
* const invocation = resolveBridgeInvocation({
|
|
7275
|
+
* traceId: 'my-custom-trace-id',
|
|
7276
|
+
* callers: [{ type: 'app', name: 'MyDApp' }],
|
|
7277
|
+
* })
|
|
7278
|
+
* // invocation.traceId === 'my-custom-trace-id'
|
|
7279
|
+
* // invocation.callers === [{ type: 'app', name: 'MyDApp' }, { type: 'kit', name: 'BridgeKit' }]
|
|
7280
|
+
*
|
|
7281
|
+
* // Without invocation metadata (auto-generated traceId)
|
|
7282
|
+
* const invocation2 = resolveBridgeInvocation()
|
|
7283
|
+
* // invocation2.traceId === <auto-generated OpenTelemetry-compatible trace ID>
|
|
7284
|
+
* // invocation2.callers === [{ type: 'kit', name: 'BridgeKit' }]
|
|
7285
|
+
* ```
|
|
7286
|
+
*/
|
|
7287
|
+
function resolveBridgeInvocation(invocationMeta) {
|
|
7288
|
+
const bridgeKitCaller = {
|
|
7289
|
+
type: 'kit',
|
|
7290
|
+
name: 'BridgeKit',
|
|
7291
|
+
version: pkg.version,
|
|
7292
|
+
};
|
|
7293
|
+
// Create default runtime and tokens for invocation context resolution
|
|
7294
|
+
const defaults = {
|
|
7295
|
+
runtime: createRuntime(),
|
|
7296
|
+
tokens: createTokenRegistry(),
|
|
7297
|
+
};
|
|
7298
|
+
// Resolve invocation metadata to full context, then extend with BridgeKit caller
|
|
7299
|
+
const baseContext = resolveInvocationContext(invocationMeta, defaults);
|
|
7300
|
+
return extendInvocationContext(baseContext, bridgeKitCaller);
|
|
7301
|
+
}
|
|
5454
7302
|
/**
|
|
5455
7303
|
* Resolves and normalizes bridge configuration for the provider.
|
|
5456
7304
|
*
|
|
@@ -5519,6 +7367,7 @@ function resolveConfig(params) {
|
|
|
5519
7367
|
* - Amount formatting
|
|
5520
7368
|
*
|
|
5521
7369
|
* @param params - The bridge parameters containing source/destination contexts, amount, and token
|
|
7370
|
+
* @param invocationMeta - Optional invocation metadata for tracing and correlation
|
|
5522
7371
|
* @returns Promise resolving to normalized bridge parameters for provider consumption
|
|
5523
7372
|
* @throws \{Error\} If parameters cannot be resolved (invalid chains, etc.)
|
|
5524
7373
|
*
|
|
@@ -5538,22 +7387,32 @@ function resolveConfig(params) {
|
|
|
5538
7387
|
* ```
|
|
5539
7388
|
*/
|
|
5540
7389
|
async function resolveBridgeParams(params) {
|
|
5541
|
-
|
|
5542
|
-
const
|
|
7390
|
+
// Resolve chains
|
|
7391
|
+
const fromChain = resolveChainIdentifier(params.from.chain);
|
|
7392
|
+
const toChain = resolveChainIdentifier(params.to.chain);
|
|
7393
|
+
// Check if this is a forwarder-only destination (no adapter)
|
|
7394
|
+
const isForwarderOnly = isForwarderOnlyDestination(params.to);
|
|
5543
7395
|
// Validate adapter chain support after resolution
|
|
5544
|
-
// This ensures adapters support the resolved chains before proceeding
|
|
5545
7396
|
params.from.adapter.validateChainSupport(fromChain);
|
|
5546
|
-
|
|
7397
|
+
// Only validate destination adapter if it exists
|
|
7398
|
+
if (!isForwarderOnly && 'adapter' in params.to) {
|
|
7399
|
+
params.to.adapter.validateChainSupport(toChain);
|
|
7400
|
+
}
|
|
7401
|
+
// For forwarder-only destinations, use recipientAddress directly
|
|
7402
|
+
// For other destinations, resolve address from adapter
|
|
5547
7403
|
const [fromAddress, toAddress] = await Promise.all([
|
|
5548
7404
|
resolveAddress(params.from),
|
|
5549
|
-
|
|
7405
|
+
isForwarderOnly
|
|
7406
|
+
? Promise.resolve(params.to.recipientAddress)
|
|
7407
|
+
: resolveAddress(params.to),
|
|
5550
7408
|
]);
|
|
5551
7409
|
const token = params.token ?? 'USDC';
|
|
5552
|
-
// Extract adapters - now always from explicit contexts
|
|
5553
|
-
const fromAdapter = params.from.adapter;
|
|
5554
|
-
const toAdapter = params.to.adapter;
|
|
5555
7410
|
// Extract recipientAddress from params.to if it exists
|
|
5556
7411
|
const recipientAddress = 'recipientAddress' in params.to ? params.to.recipientAddress : undefined;
|
|
7412
|
+
// Extract useForwarder from params.to if it exists
|
|
7413
|
+
const useForwarder = 'useForwarder' in params.to ? params.to.useForwarder : undefined;
|
|
7414
|
+
// Resolve invocation metadata to full InvocationContext with BridgeKit caller
|
|
7415
|
+
const resolvedInvocation = resolveBridgeInvocation(params.invocationMeta);
|
|
5557
7416
|
return {
|
|
5558
7417
|
amount: resolveAmount({
|
|
5559
7418
|
...params,
|
|
@@ -5563,16 +7422,21 @@ async function resolveBridgeParams(params) {
|
|
|
5563
7422
|
config: resolveConfig({
|
|
5564
7423
|
...params}),
|
|
5565
7424
|
source: {
|
|
5566
|
-
adapter:
|
|
7425
|
+
adapter: params.from.adapter,
|
|
5567
7426
|
chain: fromChain,
|
|
5568
7427
|
address: fromAddress,
|
|
5569
7428
|
},
|
|
5570
7429
|
destination: {
|
|
5571
|
-
adapter
|
|
7430
|
+
// Only include adapter if it exists (not forwarder-only)
|
|
7431
|
+
...(!isForwarderOnly &&
|
|
7432
|
+
'adapter' in params.to && { adapter: params.to.adapter }),
|
|
5572
7433
|
chain: toChain,
|
|
5573
7434
|
address: toAddress,
|
|
5574
7435
|
...(recipientAddress !== undefined && { recipientAddress }),
|
|
7436
|
+
...(useForwarder !== undefined && { useForwarder }),
|
|
5575
7437
|
},
|
|
7438
|
+
// Pass resolved InvocationContext as invocationMeta (superset is compatible)
|
|
7439
|
+
invocationMeta: resolvedInvocation,
|
|
5576
7440
|
};
|
|
5577
7441
|
}
|
|
5578
7442
|
|
|
@@ -5650,6 +7514,36 @@ const formatBridgeResult = (result, formatDirection) => {
|
|
|
5650
7514
|
};
|
|
5651
7515
|
};
|
|
5652
7516
|
|
|
7517
|
+
/**
|
|
7518
|
+
* BridgeKit caller component for retry and estimate operations.
|
|
7519
|
+
*/
|
|
7520
|
+
const BRIDGE_KIT_CALLER = {
|
|
7521
|
+
type: 'kit',
|
|
7522
|
+
name: 'BridgeKit',
|
|
7523
|
+
version: pkg.version,
|
|
7524
|
+
};
|
|
7525
|
+
/**
|
|
7526
|
+
* Merge BridgeKit's caller into the invocation metadata for retry operations.
|
|
7527
|
+
*
|
|
7528
|
+
* Prepends the BridgeKit caller to the callers array.
|
|
7529
|
+
*
|
|
7530
|
+
* @param invocationMeta - Optional invocation metadata provided by caller.
|
|
7531
|
+
* @returns Merged invocation metadata with BridgeKit caller prepended.
|
|
7532
|
+
*
|
|
7533
|
+
* @internal
|
|
7534
|
+
*/
|
|
7535
|
+
function mergeRetryInvocationMeta(invocationMeta) {
|
|
7536
|
+
// Prepend BridgeKit caller to existing callers array
|
|
7537
|
+
const existingCallers = invocationMeta?.callers ?? [];
|
|
7538
|
+
return invocationMeta
|
|
7539
|
+
? {
|
|
7540
|
+
...invocationMeta,
|
|
7541
|
+
callers: [BRIDGE_KIT_CALLER, ...existingCallers],
|
|
7542
|
+
}
|
|
7543
|
+
: {
|
|
7544
|
+
callers: [BRIDGE_KIT_CALLER, ...existingCallers],
|
|
7545
|
+
};
|
|
7546
|
+
}
|
|
5653
7547
|
/**
|
|
5654
7548
|
* Route cross-chain USDC bridging through Circle's Cross-Chain Transfer Protocol v2 (CCTPv2).
|
|
5655
7549
|
*
|
|
@@ -5746,7 +7640,7 @@ class BridgeKit {
|
|
|
5746
7640
|
* - CCTPv2 support for the chain pair
|
|
5747
7641
|
* - Transfer configuration options
|
|
5748
7642
|
*
|
|
5749
|
-
* @param params - The transfer parameters containing source, destination, amount, and
|
|
7643
|
+
* @param params - The transfer parameters containing source, destination, amount, token, and optional invocation metadata
|
|
5750
7644
|
* @returns Promise resolving to the transfer result with transaction details and steps
|
|
5751
7645
|
* @throws {KitError} When any parameter validation fails.
|
|
5752
7646
|
* @throws {Error} When CCTPv2 does not support the specified route.
|
|
@@ -5763,18 +7657,24 @@ class BridgeKit {
|
|
|
5763
7657
|
* privateKey: process.env.PRIVATE_KEY,
|
|
5764
7658
|
* })
|
|
5765
7659
|
*
|
|
7660
|
+
* // Basic usage
|
|
5766
7661
|
* const result = await kit.bridge({
|
|
5767
|
-
* from: {
|
|
5768
|
-
*
|
|
5769
|
-
* chain: 'Ethereum'
|
|
5770
|
-
* },
|
|
5771
|
-
* to: {
|
|
5772
|
-
* adapter,
|
|
5773
|
-
* chain: 'Base'
|
|
5774
|
-
* },
|
|
7662
|
+
* from: { adapter, chain: 'Ethereum' },
|
|
7663
|
+
* to: { adapter, chain: 'Base' },
|
|
5775
7664
|
* amount: '100.50'
|
|
5776
7665
|
* })
|
|
5777
7666
|
*
|
|
7667
|
+
* // With custom invocation metadata
|
|
7668
|
+
* const result = await kit.bridge({
|
|
7669
|
+
* from: { adapter, chain: 'Ethereum' },
|
|
7670
|
+
* to: { adapter, chain: 'Base' },
|
|
7671
|
+
* amount: '100.50',
|
|
7672
|
+
* invocationMeta: {
|
|
7673
|
+
* traceId: 'custom-trace-id',
|
|
7674
|
+
* callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
|
|
7675
|
+
* },
|
|
7676
|
+
* })
|
|
7677
|
+
*
|
|
5778
7678
|
* // Handle result
|
|
5779
7679
|
* if (result.state === 'success') {
|
|
5780
7680
|
* console.log('Bridge completed!')
|
|
@@ -5820,6 +7720,8 @@ class BridgeKit {
|
|
|
5820
7720
|
* @param context - The retry context containing fresh adapter instances for both
|
|
5821
7721
|
* source and destination chains. These adapters should be properly
|
|
5822
7722
|
* configured with current network connections and signing capabilities.
|
|
7723
|
+
* @param invocationMeta - Optional invocation metadata for tracing and correlation.
|
|
7724
|
+
* If not provided, uses the traceId from the original result.
|
|
5823
7725
|
* @returns A promise that resolves to the updated bridge result after retry execution.
|
|
5824
7726
|
* The result will contain the complete step history including both original
|
|
5825
7727
|
* and retry attempts.
|
|
@@ -5851,31 +7753,35 @@ class BridgeKit {
|
|
|
5851
7753
|
* // ... other properties
|
|
5852
7754
|
* }
|
|
5853
7755
|
*
|
|
7756
|
+
* // Basic retry (uses traceId from original result)
|
|
7757
|
+
* const retryResult = await kit.retry(failedResult, {
|
|
7758
|
+
* from: sourceAdapter,
|
|
7759
|
+
* to: destAdapter
|
|
7760
|
+
* })
|
|
5854
7761
|
*
|
|
5855
|
-
*
|
|
5856
|
-
*
|
|
5857
|
-
*
|
|
5858
|
-
*
|
|
5859
|
-
*
|
|
5860
|
-
*
|
|
5861
|
-
*
|
|
5862
|
-
*
|
|
5863
|
-
*
|
|
5864
|
-
* console.error('Retry failed:', error.message)
|
|
5865
|
-
* // Handle retry failure (may require manual intervention)
|
|
5866
|
-
* }
|
|
7762
|
+
* // Retry with custom invocation metadata
|
|
7763
|
+
* const retryResult = await kit.retry(
|
|
7764
|
+
* failedResult,
|
|
7765
|
+
* { from: sourceAdapter, to: destAdapter },
|
|
7766
|
+
* {
|
|
7767
|
+
* traceId: 'custom-trace-id',
|
|
7768
|
+
* callers: [{ type: 'app', name: 'MyApp' }],
|
|
7769
|
+
* }
|
|
7770
|
+
* )
|
|
5867
7771
|
* ```
|
|
5868
7772
|
*/
|
|
5869
|
-
async retry(result, context) {
|
|
7773
|
+
async retry(result, context, invocationMeta) {
|
|
5870
7774
|
const provider = this.providers.find((p) => p.name === result.provider);
|
|
5871
7775
|
if (!provider) {
|
|
5872
7776
|
throw new Error(`Provider ${result.provider} not found`);
|
|
5873
7777
|
}
|
|
7778
|
+
// Merge BridgeKit caller into invocation metadata for retry operation
|
|
7779
|
+
const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
|
|
5874
7780
|
// Format the bridge result into bigint string values for internal use
|
|
5875
7781
|
const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
|
|
5876
7782
|
// Execute the retry using the provider
|
|
5877
7783
|
// Format the bridge result into human-readable string values for the user
|
|
5878
|
-
return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context), 'to-human-readable');
|
|
7784
|
+
return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context, mergedMeta), 'to-human-readable');
|
|
5879
7785
|
}
|
|
5880
7786
|
/**
|
|
5881
7787
|
* Estimate the cost and fees for a cross-chain USDC bridge operation.
|
|
@@ -5883,13 +7789,15 @@ class BridgeKit {
|
|
|
5883
7789
|
* This method calculates the expected gas fees and protocol costs for bridging
|
|
5884
7790
|
* without actually executing the transaction. It performs the same validation
|
|
5885
7791
|
* as the bridge method but stops before execution.
|
|
5886
|
-
*
|
|
7792
|
+
*
|
|
7793
|
+
* @param params - The bridge parameters for cost estimation, including optional invocation metadata
|
|
5887
7794
|
* @returns Promise resolving to detailed cost breakdown including gas estimates
|
|
5888
7795
|
* @throws {KitError} When the parameters are invalid.
|
|
5889
7796
|
* @throws {UnsupportedRouteError} When the route is not supported.
|
|
5890
7797
|
*
|
|
5891
7798
|
* @example
|
|
5892
7799
|
* ```typescript
|
|
7800
|
+
* // Basic usage
|
|
5893
7801
|
* const estimate = await kit.estimate({
|
|
5894
7802
|
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
5895
7803
|
* to: { adapter: adapter, chain: 'Base' },
|
|
@@ -5897,6 +7805,18 @@ class BridgeKit {
|
|
|
5897
7805
|
* token: 'USDC'
|
|
5898
7806
|
* })
|
|
5899
7807
|
* console.log('Estimated cost:', estimate.totalCost)
|
|
7808
|
+
*
|
|
7809
|
+
* // With custom invocation metadata
|
|
7810
|
+
* const estimate = await kit.estimate({
|
|
7811
|
+
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
7812
|
+
* to: { adapter: adapter, chain: 'Base' },
|
|
7813
|
+
* amount: '10.50',
|
|
7814
|
+
* token: 'USDC',
|
|
7815
|
+
* invocationMeta: {
|
|
7816
|
+
* traceId: 'custom-trace-id',
|
|
7817
|
+
* callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
|
|
7818
|
+
* },
|
|
7819
|
+
* })
|
|
5900
7820
|
* ```
|
|
5901
7821
|
*/
|
|
5902
7822
|
async estimate(params) {
|
|
@@ -5948,6 +7868,9 @@ class BridgeKit {
|
|
|
5948
7868
|
* // Get only EVM mainnet chains
|
|
5949
7869
|
* const evmMainnets = kit.getSupportedChains({ chainType: 'evm', isTestnet: false })
|
|
5950
7870
|
*
|
|
7871
|
+
* // Get only chains that support forwarding
|
|
7872
|
+
* const forwarderChains = kit.getSupportedChains({ forwarderSupported: true })
|
|
7873
|
+
*
|
|
5951
7874
|
* console.log('Supported chains:')
|
|
5952
7875
|
* allChains.forEach(chain => {
|
|
5953
7876
|
* console.log(`- ${chain.name} (${chain.type})`)
|
|
@@ -5981,6 +7904,18 @@ class BridgeKit {
|
|
|
5981
7904
|
if (options?.isTestnet !== undefined) {
|
|
5982
7905
|
chains = chains.filter((chain) => chain.isTestnet === options.isTestnet);
|
|
5983
7906
|
}
|
|
7907
|
+
// Apply forwarder support filter if provided
|
|
7908
|
+
if (options?.forwarderSupported !== undefined) {
|
|
7909
|
+
chains = chains.filter((chain) => {
|
|
7910
|
+
const fs = chain.cctp?.forwarderSupported;
|
|
7911
|
+
if (!fs) {
|
|
7912
|
+
return !options.forwarderSupported;
|
|
7913
|
+
}
|
|
7914
|
+
return options.forwarderSupported
|
|
7915
|
+
? fs.source || fs.destination
|
|
7916
|
+
: !fs.source && !fs.destination;
|
|
7917
|
+
});
|
|
7918
|
+
}
|
|
5984
7919
|
return chains;
|
|
5985
7920
|
}
|
|
5986
7921
|
/**
|
|
@@ -6168,5 +8103,5 @@ class BridgeKit {
|
|
|
6168
8103
|
// Auto-register this kit for user agent tracking
|
|
6169
8104
|
registerKit(`${pkg.name}/${pkg.version}`);
|
|
6170
8105
|
|
|
6171
|
-
export { BalanceError, Blockchain, BridgeChain, BridgeKit, InputError, KitError, NetworkError, OnchainError, RpcError, TransferSpeed, bridgeParamsWithChainIdentifierSchema, getErrorCode, getErrorMessage, isBalanceError, isFatalError, isInputError, isKitError, isNetworkError, isOnchainError, isRetryableError, isRpcError, resolveChainIdentifier, setExternalPrefix };
|
|
8106
|
+
export { BalanceError, Blockchain, BridgeChain, BridgeKit, InputError, KitError, NetworkError, OnchainError, RpcError, TransferSpeed, bridgeParamsWithChainIdentifierSchema, createRuntime, createTokenRegistry, createTraceId, extendInvocationContext, getErrorCode, getErrorMessage, isBalanceError, isFatalError, isInputError, isKitError, isNetworkError, isOnchainError, isRetryableError, isRpcError, resolveChainIdentifier, resolveInvocationContext, setExternalPrefix };
|
|
6172
8107
|
//# sourceMappingURL=index.mjs.map
|