@circle-fin/adapter-ethers-v6 1.3.0 → 1.5.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 +46 -0
- package/index.cjs +845 -172
- package/index.d.ts +113 -2
- package/index.mjs +845 -172
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -80,6 +80,8 @@ const ERROR_TYPES = {
|
|
|
80
80
|
RPC: 'RPC',
|
|
81
81
|
/** Internet connectivity, DNS resolution, connection issues */
|
|
82
82
|
NETWORK: 'NETWORK',
|
|
83
|
+
/** Catch-all for unrecognized errors (code 0) */
|
|
84
|
+
UNKNOWN: 'UNKNOWN',
|
|
83
85
|
};
|
|
84
86
|
/**
|
|
85
87
|
* Array of valid error type values for validation.
|
|
@@ -93,6 +95,8 @@ const ERROR_TYPE_ARRAY = [...ERROR_TYPE_VALUES];
|
|
|
93
95
|
/**
|
|
94
96
|
* Error code ranges for validation.
|
|
95
97
|
* Single source of truth for valid error code ranges.
|
|
98
|
+
*
|
|
99
|
+
* Note: Code 0 is special - it's the UNKNOWN catch-all error.
|
|
96
100
|
*/
|
|
97
101
|
const ERROR_CODE_RANGES = [
|
|
98
102
|
{ min: 1000, max: 1999, type: 'INPUT' },
|
|
@@ -101,6 +105,8 @@ const ERROR_CODE_RANGES = [
|
|
|
101
105
|
{ min: 5000, max: 5999, type: 'ONCHAIN' },
|
|
102
106
|
{ min: 9000, max: 9999, type: 'BALANCE' },
|
|
103
107
|
];
|
|
108
|
+
/** Special code for UNKNOWN errors */
|
|
109
|
+
const UNKNOWN_ERROR_CODE = 0;
|
|
104
110
|
/**
|
|
105
111
|
* Zod schema for validating ErrorDetails objects.
|
|
106
112
|
*
|
|
@@ -139,6 +145,7 @@ const ERROR_CODE_RANGES = [
|
|
|
139
145
|
const errorDetailsSchema = z.object({
|
|
140
146
|
/**
|
|
141
147
|
* Numeric identifier following standardized ranges:
|
|
148
|
+
* - 0: UNKNOWN - Catch-all for unrecognized errors
|
|
142
149
|
* - 1000-1999: INPUT errors - Parameter validation
|
|
143
150
|
* - 3000-3999: NETWORK errors - Connectivity issues
|
|
144
151
|
* - 4000-4999: RPC errors - Provider issues, gas estimation
|
|
@@ -148,8 +155,9 @@ const errorDetailsSchema = z.object({
|
|
|
148
155
|
code: z
|
|
149
156
|
.number()
|
|
150
157
|
.int('Error code must be an integer')
|
|
151
|
-
.refine((code) =>
|
|
152
|
-
|
|
158
|
+
.refine((code) => code === UNKNOWN_ERROR_CODE ||
|
|
159
|
+
ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
|
|
160
|
+
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)',
|
|
153
161
|
}),
|
|
154
162
|
/** Human-readable ID (e.g., "INPUT_NETWORK_MISMATCH", "BALANCE_INSUFFICIENT_TOKEN") */
|
|
155
163
|
name: z
|
|
@@ -159,7 +167,7 @@ const errorDetailsSchema = z.object({
|
|
|
159
167
|
/** Error category indicating where the error originated */
|
|
160
168
|
type: z.enum(ERROR_TYPE_ARRAY, {
|
|
161
169
|
errorMap: () => ({
|
|
162
|
-
message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK',
|
|
170
|
+
message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK, UNKNOWN',
|
|
163
171
|
}),
|
|
164
172
|
}),
|
|
165
173
|
/** Error handling strategy */
|
|
@@ -342,6 +350,7 @@ class KitError extends Error {
|
|
|
342
350
|
/**
|
|
343
351
|
* Standardized error code ranges for consistent categorization:
|
|
344
352
|
*
|
|
353
|
+
* - 0: UNKNOWN - Catch-all for unrecognized errors
|
|
345
354
|
* - 1000-1999: INPUT errors - Parameter validation, input format errors
|
|
346
355
|
* - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
|
|
347
356
|
* - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
|
|
@@ -537,6 +546,53 @@ function createInvalidChainError(chain, reason) {
|
|
|
537
546
|
};
|
|
538
547
|
return new KitError(errorDetails);
|
|
539
548
|
}
|
|
549
|
+
/**
|
|
550
|
+
* Creates error for general validation failure.
|
|
551
|
+
*
|
|
552
|
+
* This error is thrown when input validation fails for reasons not covered
|
|
553
|
+
* by more specific error types.
|
|
554
|
+
*
|
|
555
|
+
* @param field - The field that failed validation
|
|
556
|
+
* @param value - The invalid value (can be any type)
|
|
557
|
+
* @param reason - Specific reason why validation failed
|
|
558
|
+
* @returns KitError with validation details
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```typescript
|
|
562
|
+
* import { createValidationFailedError } from '@core/errors'
|
|
563
|
+
*
|
|
564
|
+
* throw createValidationFailedError('recipient', 'invalid@email', 'Must be a valid wallet address')
|
|
565
|
+
* // Message: "Validation failed for 'recipient': 'invalid@email' - Must be a valid wallet address"
|
|
566
|
+
*
|
|
567
|
+
* throw createValidationFailedError('chainId', 999, 'Unsupported chain ID')
|
|
568
|
+
* // Message: "Validation failed for 'chainId': 999 - Unsupported chain ID"
|
|
569
|
+
*
|
|
570
|
+
* throw createValidationFailedError('config', { invalid: true }, 'Missing required properties')
|
|
571
|
+
* // Message: "Validation failed for 'config': [object Object] - Missing required properties"
|
|
572
|
+
* ```
|
|
573
|
+
*/
|
|
574
|
+
function createValidationFailedError(field, value, reason) {
|
|
575
|
+
// Convert value to string for display, handling different types appropriately
|
|
576
|
+
let valueString;
|
|
577
|
+
if (typeof value === 'string') {
|
|
578
|
+
valueString = `'${value}'`;
|
|
579
|
+
}
|
|
580
|
+
else if (typeof value === 'object' && value !== null) {
|
|
581
|
+
valueString = JSON.stringify(value);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
valueString = String(value);
|
|
585
|
+
}
|
|
586
|
+
const errorDetails = {
|
|
587
|
+
...InputError.VALIDATION_FAILED,
|
|
588
|
+
recoverability: 'FATAL',
|
|
589
|
+
message: `Validation failed for '${field}': ${valueString} - ${reason}.`,
|
|
590
|
+
cause: {
|
|
591
|
+
trace: { field, value, reason },
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
return new KitError(errorDetails);
|
|
595
|
+
}
|
|
540
596
|
/**
|
|
541
597
|
* Creates a KitError from a Zod validation error with detailed error information.
|
|
542
598
|
*
|
|
@@ -1163,6 +1219,8 @@ var Blockchain;
|
|
|
1163
1219
|
Blockchain["Ink_Testnet"] = "Ink_Testnet";
|
|
1164
1220
|
Blockchain["Linea"] = "Linea";
|
|
1165
1221
|
Blockchain["Linea_Sepolia"] = "Linea_Sepolia";
|
|
1222
|
+
Blockchain["Monad"] = "Monad";
|
|
1223
|
+
Blockchain["Monad_Testnet"] = "Monad_Testnet";
|
|
1166
1224
|
Blockchain["NEAR"] = "NEAR";
|
|
1167
1225
|
Blockchain["NEAR_Testnet"] = "NEAR_Testnet";
|
|
1168
1226
|
Blockchain["Noble"] = "Noble";
|
|
@@ -1254,6 +1312,7 @@ var BridgeChain;
|
|
|
1254
1312
|
BridgeChain["HyperEVM"] = "HyperEVM";
|
|
1255
1313
|
BridgeChain["Ink"] = "Ink";
|
|
1256
1314
|
BridgeChain["Linea"] = "Linea";
|
|
1315
|
+
BridgeChain["Monad"] = "Monad";
|
|
1257
1316
|
BridgeChain["Optimism"] = "Optimism";
|
|
1258
1317
|
BridgeChain["Plume"] = "Plume";
|
|
1259
1318
|
BridgeChain["Polygon"] = "Polygon";
|
|
@@ -1273,6 +1332,7 @@ var BridgeChain;
|
|
|
1273
1332
|
BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
|
|
1274
1333
|
BridgeChain["Ink_Testnet"] = "Ink_Testnet";
|
|
1275
1334
|
BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
|
|
1335
|
+
BridgeChain["Monad_Testnet"] = "Monad_Testnet";
|
|
1276
1336
|
BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
1277
1337
|
BridgeChain["Plume_Testnet"] = "Plume_Testnet";
|
|
1278
1338
|
BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
@@ -1399,6 +1459,10 @@ const Aptos = defineChain({
|
|
|
1399
1459
|
confirmations: 1,
|
|
1400
1460
|
},
|
|
1401
1461
|
},
|
|
1462
|
+
forwarderSupported: {
|
|
1463
|
+
source: false,
|
|
1464
|
+
destination: false,
|
|
1465
|
+
},
|
|
1402
1466
|
},
|
|
1403
1467
|
});
|
|
1404
1468
|
|
|
@@ -1432,6 +1496,10 @@ const AptosTestnet = defineChain({
|
|
|
1432
1496
|
confirmations: 1,
|
|
1433
1497
|
},
|
|
1434
1498
|
},
|
|
1499
|
+
forwarderSupported: {
|
|
1500
|
+
source: false,
|
|
1501
|
+
destination: false,
|
|
1502
|
+
},
|
|
1435
1503
|
},
|
|
1436
1504
|
});
|
|
1437
1505
|
|
|
@@ -1491,6 +1559,10 @@ const ArcTestnet = defineChain({
|
|
|
1491
1559
|
fastConfirmations: 1,
|
|
1492
1560
|
},
|
|
1493
1561
|
},
|
|
1562
|
+
forwarderSupported: {
|
|
1563
|
+
source: true,
|
|
1564
|
+
destination: true,
|
|
1565
|
+
},
|
|
1494
1566
|
},
|
|
1495
1567
|
kitContracts: {
|
|
1496
1568
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1535,6 +1607,10 @@ const Arbitrum = defineChain({
|
|
|
1535
1607
|
fastConfirmations: 1,
|
|
1536
1608
|
},
|
|
1537
1609
|
},
|
|
1610
|
+
forwarderSupported: {
|
|
1611
|
+
source: true,
|
|
1612
|
+
destination: true,
|
|
1613
|
+
},
|
|
1538
1614
|
},
|
|
1539
1615
|
kitContracts: {
|
|
1540
1616
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1579,6 +1655,10 @@ const ArbitrumSepolia = defineChain({
|
|
|
1579
1655
|
fastConfirmations: 1,
|
|
1580
1656
|
},
|
|
1581
1657
|
},
|
|
1658
|
+
forwarderSupported: {
|
|
1659
|
+
source: true,
|
|
1660
|
+
destination: true,
|
|
1661
|
+
},
|
|
1582
1662
|
},
|
|
1583
1663
|
kitContracts: {
|
|
1584
1664
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1623,6 +1703,10 @@ const Avalanche = defineChain({
|
|
|
1623
1703
|
fastConfirmations: 1,
|
|
1624
1704
|
},
|
|
1625
1705
|
},
|
|
1706
|
+
forwarderSupported: {
|
|
1707
|
+
source: true,
|
|
1708
|
+
destination: true,
|
|
1709
|
+
},
|
|
1626
1710
|
},
|
|
1627
1711
|
kitContracts: {
|
|
1628
1712
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1666,6 +1750,10 @@ const AvalancheFuji = defineChain({
|
|
|
1666
1750
|
fastConfirmations: 1,
|
|
1667
1751
|
},
|
|
1668
1752
|
},
|
|
1753
|
+
forwarderSupported: {
|
|
1754
|
+
source: true,
|
|
1755
|
+
destination: true,
|
|
1756
|
+
},
|
|
1669
1757
|
},
|
|
1670
1758
|
rpcEndpoints: ['https://api.avax-test.network/ext/bc/C/rpc'],
|
|
1671
1759
|
kitContracts: {
|
|
@@ -1711,6 +1799,10 @@ const Base = defineChain({
|
|
|
1711
1799
|
fastConfirmations: 1,
|
|
1712
1800
|
},
|
|
1713
1801
|
},
|
|
1802
|
+
forwarderSupported: {
|
|
1803
|
+
source: true,
|
|
1804
|
+
destination: true,
|
|
1805
|
+
},
|
|
1714
1806
|
},
|
|
1715
1807
|
kitContracts: {
|
|
1716
1808
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1755,6 +1847,10 @@ const BaseSepolia = defineChain({
|
|
|
1755
1847
|
fastConfirmations: 1,
|
|
1756
1848
|
},
|
|
1757
1849
|
},
|
|
1850
|
+
forwarderSupported: {
|
|
1851
|
+
source: true,
|
|
1852
|
+
destination: true,
|
|
1853
|
+
},
|
|
1758
1854
|
},
|
|
1759
1855
|
kitContracts: {
|
|
1760
1856
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1841,6 +1937,10 @@ const Codex = defineChain({
|
|
|
1841
1937
|
fastConfirmations: 1,
|
|
1842
1938
|
},
|
|
1843
1939
|
},
|
|
1940
|
+
forwarderSupported: {
|
|
1941
|
+
source: true,
|
|
1942
|
+
destination: false,
|
|
1943
|
+
},
|
|
1844
1944
|
},
|
|
1845
1945
|
kitContracts: {
|
|
1846
1946
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1879,6 +1979,10 @@ const CodexTestnet = defineChain({
|
|
|
1879
1979
|
fastConfirmations: 1,
|
|
1880
1980
|
},
|
|
1881
1981
|
},
|
|
1982
|
+
forwarderSupported: {
|
|
1983
|
+
source: true,
|
|
1984
|
+
destination: false,
|
|
1985
|
+
},
|
|
1882
1986
|
},
|
|
1883
1987
|
kitContracts: {
|
|
1884
1988
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -1923,6 +2027,10 @@ const Ethereum = defineChain({
|
|
|
1923
2027
|
fastConfirmations: 2,
|
|
1924
2028
|
},
|
|
1925
2029
|
},
|
|
2030
|
+
forwarderSupported: {
|
|
2031
|
+
source: true,
|
|
2032
|
+
destination: true,
|
|
2033
|
+
},
|
|
1926
2034
|
},
|
|
1927
2035
|
kitContracts: {
|
|
1928
2036
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -1967,6 +2075,10 @@ const EthereumSepolia = defineChain({
|
|
|
1967
2075
|
fastConfirmations: 2,
|
|
1968
2076
|
},
|
|
1969
2077
|
},
|
|
2078
|
+
forwarderSupported: {
|
|
2079
|
+
source: true,
|
|
2080
|
+
destination: true,
|
|
2081
|
+
},
|
|
1970
2082
|
},
|
|
1971
2083
|
kitContracts: {
|
|
1972
2084
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2053,6 +2165,10 @@ const HyperEVM = defineChain({
|
|
|
2053
2165
|
fastConfirmations: 1,
|
|
2054
2166
|
},
|
|
2055
2167
|
},
|
|
2168
|
+
forwarderSupported: {
|
|
2169
|
+
source: true,
|
|
2170
|
+
destination: true,
|
|
2171
|
+
},
|
|
2056
2172
|
},
|
|
2057
2173
|
kitContracts: {
|
|
2058
2174
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2092,6 +2208,10 @@ const HyperEVMTestnet = defineChain({
|
|
|
2092
2208
|
fastConfirmations: 1,
|
|
2093
2209
|
},
|
|
2094
2210
|
},
|
|
2211
|
+
forwarderSupported: {
|
|
2212
|
+
source: true,
|
|
2213
|
+
destination: true,
|
|
2214
|
+
},
|
|
2095
2215
|
},
|
|
2096
2216
|
kitContracts: {
|
|
2097
2217
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2135,6 +2255,10 @@ const Ink = defineChain({
|
|
|
2135
2255
|
fastConfirmations: 1,
|
|
2136
2256
|
},
|
|
2137
2257
|
},
|
|
2258
|
+
forwarderSupported: {
|
|
2259
|
+
source: true,
|
|
2260
|
+
destination: true,
|
|
2261
|
+
},
|
|
2138
2262
|
},
|
|
2139
2263
|
kitContracts: {
|
|
2140
2264
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2177,6 +2301,10 @@ const InkTestnet = defineChain({
|
|
|
2177
2301
|
fastConfirmations: 1,
|
|
2178
2302
|
},
|
|
2179
2303
|
},
|
|
2304
|
+
forwarderSupported: {
|
|
2305
|
+
source: true,
|
|
2306
|
+
destination: true,
|
|
2307
|
+
},
|
|
2180
2308
|
},
|
|
2181
2309
|
kitContracts: {
|
|
2182
2310
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2215,6 +2343,10 @@ const Linea = defineChain({
|
|
|
2215
2343
|
fastConfirmations: 1,
|
|
2216
2344
|
},
|
|
2217
2345
|
},
|
|
2346
|
+
forwarderSupported: {
|
|
2347
|
+
source: true,
|
|
2348
|
+
destination: true,
|
|
2349
|
+
},
|
|
2218
2350
|
},
|
|
2219
2351
|
kitContracts: {
|
|
2220
2352
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2253,6 +2385,98 @@ const LineaSepolia = defineChain({
|
|
|
2253
2385
|
fastConfirmations: 1,
|
|
2254
2386
|
},
|
|
2255
2387
|
},
|
|
2388
|
+
forwarderSupported: {
|
|
2389
|
+
source: true,
|
|
2390
|
+
destination: true,
|
|
2391
|
+
},
|
|
2392
|
+
},
|
|
2393
|
+
kitContracts: {
|
|
2394
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
2395
|
+
},
|
|
2396
|
+
});
|
|
2397
|
+
|
|
2398
|
+
/**
|
|
2399
|
+
* Monad Mainnet chain definition
|
|
2400
|
+
* @remarks
|
|
2401
|
+
* This represents the official production network for the Monad blockchain.
|
|
2402
|
+
* Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
|
|
2403
|
+
* over 10,000 TPS, sub-second finality, and near-zero gas fees.
|
|
2404
|
+
*/
|
|
2405
|
+
const Monad = defineChain({
|
|
2406
|
+
type: 'evm',
|
|
2407
|
+
chain: Blockchain.Monad,
|
|
2408
|
+
name: 'Monad',
|
|
2409
|
+
title: 'Monad Mainnet',
|
|
2410
|
+
nativeCurrency: {
|
|
2411
|
+
name: 'Monad',
|
|
2412
|
+
symbol: 'MON',
|
|
2413
|
+
decimals: 18,
|
|
2414
|
+
},
|
|
2415
|
+
chainId: 143,
|
|
2416
|
+
isTestnet: false,
|
|
2417
|
+
explorerUrl: 'https://monadscan.com/tx/{hash}',
|
|
2418
|
+
rpcEndpoints: ['https://rpc.monad.xyz'],
|
|
2419
|
+
eurcAddress: null,
|
|
2420
|
+
usdcAddress: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
|
|
2421
|
+
cctp: {
|
|
2422
|
+
domain: 15,
|
|
2423
|
+
contracts: {
|
|
2424
|
+
v2: {
|
|
2425
|
+
type: 'split',
|
|
2426
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
2427
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
2428
|
+
confirmations: 1,
|
|
2429
|
+
fastConfirmations: 1,
|
|
2430
|
+
},
|
|
2431
|
+
},
|
|
2432
|
+
forwarderSupported: {
|
|
2433
|
+
source: true,
|
|
2434
|
+
destination: true,
|
|
2435
|
+
},
|
|
2436
|
+
},
|
|
2437
|
+
kitContracts: {
|
|
2438
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
2439
|
+
},
|
|
2440
|
+
});
|
|
2441
|
+
|
|
2442
|
+
/**
|
|
2443
|
+
* Monad Testnet chain definition
|
|
2444
|
+
* @remarks
|
|
2445
|
+
* This represents the official test network for the Monad blockchain.
|
|
2446
|
+
* Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
|
|
2447
|
+
* over 10,000 TPS, sub-second finality, and near-zero gas fees.
|
|
2448
|
+
*/
|
|
2449
|
+
const MonadTestnet = defineChain({
|
|
2450
|
+
type: 'evm',
|
|
2451
|
+
chain: Blockchain.Monad_Testnet,
|
|
2452
|
+
name: 'Monad Testnet',
|
|
2453
|
+
title: 'Monad Testnet',
|
|
2454
|
+
nativeCurrency: {
|
|
2455
|
+
name: 'Monad',
|
|
2456
|
+
symbol: 'MON',
|
|
2457
|
+
decimals: 18,
|
|
2458
|
+
},
|
|
2459
|
+
chainId: 10143,
|
|
2460
|
+
isTestnet: true,
|
|
2461
|
+
explorerUrl: 'https://testnet.monadscan.com/tx/{hash}',
|
|
2462
|
+
rpcEndpoints: ['https://testnet-rpc.monad.xyz'],
|
|
2463
|
+
eurcAddress: null,
|
|
2464
|
+
usdcAddress: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
|
|
2465
|
+
cctp: {
|
|
2466
|
+
domain: 15,
|
|
2467
|
+
contracts: {
|
|
2468
|
+
v2: {
|
|
2469
|
+
type: 'split',
|
|
2470
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
2471
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
2472
|
+
confirmations: 1,
|
|
2473
|
+
fastConfirmations: 1,
|
|
2474
|
+
},
|
|
2475
|
+
},
|
|
2476
|
+
forwarderSupported: {
|
|
2477
|
+
source: true,
|
|
2478
|
+
destination: true,
|
|
2479
|
+
},
|
|
2256
2480
|
},
|
|
2257
2481
|
kitContracts: {
|
|
2258
2482
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2334,6 +2558,10 @@ const Noble = defineChain({
|
|
|
2334
2558
|
confirmations: 1,
|
|
2335
2559
|
},
|
|
2336
2560
|
},
|
|
2561
|
+
forwarderSupported: {
|
|
2562
|
+
source: false,
|
|
2563
|
+
destination: false,
|
|
2564
|
+
},
|
|
2337
2565
|
},
|
|
2338
2566
|
});
|
|
2339
2567
|
|
|
@@ -2366,6 +2594,10 @@ const NobleTestnet = defineChain({
|
|
|
2366
2594
|
confirmations: 1,
|
|
2367
2595
|
},
|
|
2368
2596
|
},
|
|
2597
|
+
forwarderSupported: {
|
|
2598
|
+
source: false,
|
|
2599
|
+
destination: false,
|
|
2600
|
+
},
|
|
2369
2601
|
},
|
|
2370
2602
|
});
|
|
2371
2603
|
|
|
@@ -2407,6 +2639,10 @@ const Optimism = defineChain({
|
|
|
2407
2639
|
fastConfirmations: 1,
|
|
2408
2640
|
},
|
|
2409
2641
|
},
|
|
2642
|
+
forwarderSupported: {
|
|
2643
|
+
source: true,
|
|
2644
|
+
destination: true,
|
|
2645
|
+
},
|
|
2410
2646
|
},
|
|
2411
2647
|
kitContracts: {
|
|
2412
2648
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2451,6 +2687,10 @@ const OptimismSepolia = defineChain({
|
|
|
2451
2687
|
fastConfirmations: 1,
|
|
2452
2688
|
},
|
|
2453
2689
|
},
|
|
2690
|
+
forwarderSupported: {
|
|
2691
|
+
source: true,
|
|
2692
|
+
destination: true,
|
|
2693
|
+
},
|
|
2454
2694
|
},
|
|
2455
2695
|
kitContracts: {
|
|
2456
2696
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2491,6 +2731,10 @@ const Plume = defineChain({
|
|
|
2491
2731
|
fastConfirmations: 1,
|
|
2492
2732
|
},
|
|
2493
2733
|
},
|
|
2734
|
+
forwarderSupported: {
|
|
2735
|
+
source: true,
|
|
2736
|
+
destination: false,
|
|
2737
|
+
},
|
|
2494
2738
|
},
|
|
2495
2739
|
kitContracts: {
|
|
2496
2740
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2530,6 +2774,10 @@ const PlumeTestnet = defineChain({
|
|
|
2530
2774
|
fastConfirmations: 1,
|
|
2531
2775
|
},
|
|
2532
2776
|
},
|
|
2777
|
+
forwarderSupported: {
|
|
2778
|
+
source: true,
|
|
2779
|
+
destination: false,
|
|
2780
|
+
},
|
|
2533
2781
|
},
|
|
2534
2782
|
kitContracts: {
|
|
2535
2783
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2620,6 +2868,10 @@ const Polygon = defineChain({
|
|
|
2620
2868
|
fastConfirmations: 13,
|
|
2621
2869
|
},
|
|
2622
2870
|
},
|
|
2871
|
+
forwarderSupported: {
|
|
2872
|
+
source: true,
|
|
2873
|
+
destination: true,
|
|
2874
|
+
},
|
|
2623
2875
|
},
|
|
2624
2876
|
kitContracts: {
|
|
2625
2877
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2664,6 +2916,10 @@ const PolygonAmoy = defineChain({
|
|
|
2664
2916
|
fastConfirmations: 13,
|
|
2665
2917
|
},
|
|
2666
2918
|
},
|
|
2919
|
+
forwarderSupported: {
|
|
2920
|
+
source: true,
|
|
2921
|
+
destination: true,
|
|
2922
|
+
},
|
|
2667
2923
|
},
|
|
2668
2924
|
kitContracts: {
|
|
2669
2925
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2704,6 +2960,10 @@ const Sei = defineChain({
|
|
|
2704
2960
|
fastConfirmations: 1,
|
|
2705
2961
|
},
|
|
2706
2962
|
},
|
|
2963
|
+
forwarderSupported: {
|
|
2964
|
+
source: true,
|
|
2965
|
+
destination: true,
|
|
2966
|
+
},
|
|
2707
2967
|
},
|
|
2708
2968
|
kitContracts: {
|
|
2709
2969
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2743,6 +3003,10 @@ const SeiTestnet = defineChain({
|
|
|
2743
3003
|
fastConfirmations: 1,
|
|
2744
3004
|
},
|
|
2745
3005
|
},
|
|
3006
|
+
forwarderSupported: {
|
|
3007
|
+
source: true,
|
|
3008
|
+
destination: true,
|
|
3009
|
+
},
|
|
2746
3010
|
},
|
|
2747
3011
|
kitContracts: {
|
|
2748
3012
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2781,6 +3045,10 @@ const Sonic = defineChain({
|
|
|
2781
3045
|
fastConfirmations: 1,
|
|
2782
3046
|
},
|
|
2783
3047
|
},
|
|
3048
|
+
forwarderSupported: {
|
|
3049
|
+
source: true,
|
|
3050
|
+
destination: true,
|
|
3051
|
+
},
|
|
2784
3052
|
},
|
|
2785
3053
|
kitContracts: {
|
|
2786
3054
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -2819,6 +3087,10 @@ const SonicTestnet = defineChain({
|
|
|
2819
3087
|
fastConfirmations: 1,
|
|
2820
3088
|
},
|
|
2821
3089
|
},
|
|
3090
|
+
forwarderSupported: {
|
|
3091
|
+
source: true,
|
|
3092
|
+
destination: true,
|
|
3093
|
+
},
|
|
2822
3094
|
},
|
|
2823
3095
|
kitContracts: {
|
|
2824
3096
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -2862,6 +3134,10 @@ const Solana = defineChain({
|
|
|
2862
3134
|
fastConfirmations: 3,
|
|
2863
3135
|
},
|
|
2864
3136
|
},
|
|
3137
|
+
forwarderSupported: {
|
|
3138
|
+
source: true,
|
|
3139
|
+
destination: false,
|
|
3140
|
+
},
|
|
2865
3141
|
},
|
|
2866
3142
|
kitContracts: {
|
|
2867
3143
|
bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
|
|
@@ -2904,6 +3180,10 @@ const SolanaDevnet = defineChain({
|
|
|
2904
3180
|
fastConfirmations: 3,
|
|
2905
3181
|
},
|
|
2906
3182
|
},
|
|
3183
|
+
forwarderSupported: {
|
|
3184
|
+
source: true,
|
|
3185
|
+
destination: false,
|
|
3186
|
+
},
|
|
2907
3187
|
},
|
|
2908
3188
|
kitContracts: {
|
|
2909
3189
|
bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
|
|
@@ -2987,6 +3267,10 @@ const Sui = defineChain({
|
|
|
2987
3267
|
confirmations: 1,
|
|
2988
3268
|
},
|
|
2989
3269
|
},
|
|
3270
|
+
forwarderSupported: {
|
|
3271
|
+
source: false,
|
|
3272
|
+
destination: false,
|
|
3273
|
+
},
|
|
2990
3274
|
},
|
|
2991
3275
|
});
|
|
2992
3276
|
|
|
@@ -3020,6 +3304,10 @@ const SuiTestnet = defineChain({
|
|
|
3020
3304
|
confirmations: 1,
|
|
3021
3305
|
},
|
|
3022
3306
|
},
|
|
3307
|
+
forwarderSupported: {
|
|
3308
|
+
source: false,
|
|
3309
|
+
destination: false,
|
|
3310
|
+
},
|
|
3023
3311
|
},
|
|
3024
3312
|
});
|
|
3025
3313
|
|
|
@@ -3041,7 +3329,7 @@ const Unichain = defineChain({
|
|
|
3041
3329
|
chainId: 130,
|
|
3042
3330
|
isTestnet: false,
|
|
3043
3331
|
explorerUrl: 'https://unichain.blockscout.com/tx/{hash}',
|
|
3044
|
-
rpcEndpoints: ['https://
|
|
3332
|
+
rpcEndpoints: ['https://mainnet.unichain.org'],
|
|
3045
3333
|
eurcAddress: null,
|
|
3046
3334
|
usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
|
|
3047
3335
|
cctp: {
|
|
@@ -3061,6 +3349,10 @@ const Unichain = defineChain({
|
|
|
3061
3349
|
fastConfirmations: 1,
|
|
3062
3350
|
},
|
|
3063
3351
|
},
|
|
3352
|
+
forwarderSupported: {
|
|
3353
|
+
source: true,
|
|
3354
|
+
destination: true,
|
|
3355
|
+
},
|
|
3064
3356
|
},
|
|
3065
3357
|
kitContracts: {
|
|
3066
3358
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -3105,6 +3397,10 @@ const UnichainSepolia = defineChain({
|
|
|
3105
3397
|
fastConfirmations: 1,
|
|
3106
3398
|
},
|
|
3107
3399
|
},
|
|
3400
|
+
forwarderSupported: {
|
|
3401
|
+
source: true,
|
|
3402
|
+
destination: true,
|
|
3403
|
+
},
|
|
3108
3404
|
},
|
|
3109
3405
|
kitContracts: {
|
|
3110
3406
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -3131,7 +3427,7 @@ const WorldChain = defineChain({
|
|
|
3131
3427
|
explorerUrl: 'https://worldscan.org/tx/{hash}',
|
|
3132
3428
|
rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
|
|
3133
3429
|
eurcAddress: null,
|
|
3134
|
-
usdcAddress: '
|
|
3430
|
+
usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
|
|
3135
3431
|
cctp: {
|
|
3136
3432
|
domain: 14,
|
|
3137
3433
|
contracts: {
|
|
@@ -3143,6 +3439,10 @@ const WorldChain = defineChain({
|
|
|
3143
3439
|
fastConfirmations: 1,
|
|
3144
3440
|
},
|
|
3145
3441
|
},
|
|
3442
|
+
forwarderSupported: {
|
|
3443
|
+
source: true,
|
|
3444
|
+
destination: true,
|
|
3445
|
+
},
|
|
3146
3446
|
},
|
|
3147
3447
|
kitContracts: {
|
|
3148
3448
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -3184,6 +3484,10 @@ const WorldChainSepolia = defineChain({
|
|
|
3184
3484
|
fastConfirmations: 1,
|
|
3185
3485
|
},
|
|
3186
3486
|
},
|
|
3487
|
+
forwarderSupported: {
|
|
3488
|
+
source: true,
|
|
3489
|
+
destination: true,
|
|
3490
|
+
},
|
|
3187
3491
|
},
|
|
3188
3492
|
kitContracts: {
|
|
3189
3493
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -3210,7 +3514,7 @@ const XDC = defineChain({
|
|
|
3210
3514
|
chainId: 50,
|
|
3211
3515
|
isTestnet: false,
|
|
3212
3516
|
explorerUrl: 'https://xdcscan.io/tx/{hash}',
|
|
3213
|
-
rpcEndpoints: ['https://erpc.xinfin.network'],
|
|
3517
|
+
rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
|
|
3214
3518
|
eurcAddress: null,
|
|
3215
3519
|
usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
|
|
3216
3520
|
cctp: {
|
|
@@ -3224,6 +3528,10 @@ const XDC = defineChain({
|
|
|
3224
3528
|
fastConfirmations: 3,
|
|
3225
3529
|
},
|
|
3226
3530
|
},
|
|
3531
|
+
forwarderSupported: {
|
|
3532
|
+
source: true,
|
|
3533
|
+
destination: false,
|
|
3534
|
+
},
|
|
3227
3535
|
},
|
|
3228
3536
|
kitContracts: {
|
|
3229
3537
|
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
@@ -3262,6 +3570,10 @@ const XDCApothem = defineChain({
|
|
|
3262
3570
|
fastConfirmations: 1,
|
|
3263
3571
|
},
|
|
3264
3572
|
},
|
|
3573
|
+
forwarderSupported: {
|
|
3574
|
+
source: true,
|
|
3575
|
+
destination: false,
|
|
3576
|
+
},
|
|
3265
3577
|
},
|
|
3266
3578
|
kitContracts: {
|
|
3267
3579
|
bridge: BRIDGE_CONTRACT_EVM_TESTNET,
|
|
@@ -3343,6 +3655,8 @@ var Blockchains = /*#__PURE__*/Object.freeze({
|
|
|
3343
3655
|
InkTestnet: InkTestnet,
|
|
3344
3656
|
Linea: Linea,
|
|
3345
3657
|
LineaSepolia: LineaSepolia,
|
|
3658
|
+
Monad: Monad,
|
|
3659
|
+
MonadTestnet: MonadTestnet,
|
|
3346
3660
|
NEAR: NEAR,
|
|
3347
3661
|
NEARTestnet: NEARTestnet,
|
|
3348
3662
|
Noble: Noble,
|
|
@@ -4371,9 +4685,8 @@ const transactionHashSchema = z
|
|
|
4371
4685
|
required_error: 'Transaction hash is required',
|
|
4372
4686
|
invalid_type_error: 'Transaction hash must be a string',
|
|
4373
4687
|
})
|
|
4374
|
-
.
|
|
4375
|
-
.
|
|
4376
|
-
.refine((hash) => hash.length > 0, 'Transaction hash must not be empty or whitespace-only');
|
|
4688
|
+
.transform((hash) => hash.trim())
|
|
4689
|
+
.refine((hash) => hash.length > 0, 'Transaction hash cannot be empty');
|
|
4377
4690
|
/**
|
|
4378
4691
|
* Zod schema for validating buildExplorerUrl function parameters.
|
|
4379
4692
|
* This schema validates both the chain definition and transaction hash together.
|
|
@@ -8289,28 +8602,79 @@ const bridgeContractAbi = [
|
|
|
8289
8602
|
const ZERO_HASH = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
8290
8603
|
|
|
8291
8604
|
/**
|
|
8292
|
-
*
|
|
8605
|
+
* Prepare an EVM-compatible deposit for burn transaction for CCTP v2.
|
|
8293
8606
|
*
|
|
8294
|
-
* This
|
|
8295
|
-
*
|
|
8296
|
-
* chain
|
|
8297
|
-
* TokenMessengerV2 contract.
|
|
8607
|
+
* This is the shared implementation for both `depositForBurn` and `depositForBurnWithHook` actions.
|
|
8608
|
+
* It creates a prepared chain request for burning USDC tokens on the source EVM chain to initiate
|
|
8609
|
+
* a cross-chain transfer using Circle's CCTP v2 protocol.
|
|
8298
8610
|
*
|
|
8299
|
-
* The function
|
|
8300
|
-
*
|
|
8301
|
-
*
|
|
8611
|
+
* The function automatically selects the appropriate contract method based on whether hookData
|
|
8612
|
+
* is present:
|
|
8613
|
+
* - Without hookData → `depositForBurn`
|
|
8614
|
+
* - With hookData → `depositForBurnWithHook`
|
|
8615
|
+
*
|
|
8616
|
+
* @param params - The action payload (either depositForBurn or depositForBurnWithHook params).
|
|
8617
|
+
* @param adapter - The EVM adapter responsible for chain context and transaction preparation.
|
|
8618
|
+
* @param context - The resolved operation context providing chain and address information.
|
|
8619
|
+
* @returns A promise that resolves to a prepared chain request.
|
|
8620
|
+
* @throws KitError if the `fromChain` is not EVM-compatible.
|
|
8621
|
+
* @throws KitError if `hookData` is provided but is not a valid hex string.
|
|
8622
|
+
*
|
|
8623
|
+
* @internal This function is used internally by depositForBurn and depositForBurnWithHook wrappers.
|
|
8624
|
+
*/
|
|
8625
|
+
const prepareDepositForBurn = async (params, adapter, context) => {
|
|
8626
|
+
if (params.fromChain.type !== 'evm') {
|
|
8627
|
+
throw createInvalidChainError(params.fromChain.name, `Expected EVM chain but received chain type: ${params.fromChain.type}`);
|
|
8628
|
+
}
|
|
8629
|
+
// Detect if hookData is present (depositForBurnWithHook action)
|
|
8630
|
+
const hookData = 'hookData' in params ? params.hookData : undefined;
|
|
8631
|
+
// Build args array - base args are the same for both variants
|
|
8632
|
+
const args = [
|
|
8633
|
+
params.amount,
|
|
8634
|
+
params.toChain.cctp.domain,
|
|
8635
|
+
convertAddress(params.mintRecipient, 'bytes32'),
|
|
8636
|
+
params.fromChain.usdcAddress,
|
|
8637
|
+
params.destinationCaller ?? ZERO_HASH,
|
|
8638
|
+
params.maxFee,
|
|
8639
|
+
params.minFinalityThreshold,
|
|
8640
|
+
];
|
|
8641
|
+
const hasHookData = hookData !== undefined && hookData !== '';
|
|
8642
|
+
// Add hookData as the last argument if present, after validating format
|
|
8643
|
+
if (hasHookData) {
|
|
8644
|
+
const result = hexStringSchema.safeParse(hookData);
|
|
8645
|
+
if (!result.success) {
|
|
8646
|
+
throw createValidationFailedError('hookData', hookData, 'Must be a valid hex string starting with 0x');
|
|
8647
|
+
}
|
|
8648
|
+
args.push(hookData);
|
|
8649
|
+
}
|
|
8650
|
+
// Select function name based on hookData presence
|
|
8651
|
+
const functionName = hasHookData ? 'depositForBurnWithHook' : 'depositForBurn';
|
|
8652
|
+
return adapter.prepare({
|
|
8653
|
+
type: 'evm',
|
|
8654
|
+
abi: tokenMessengerV2Abi,
|
|
8655
|
+
address: resolveCCTPV2ContractAddress(params.fromChain, 'tokenMessenger'),
|
|
8656
|
+
functionName,
|
|
8657
|
+
args,
|
|
8658
|
+
}, context);
|
|
8659
|
+
};
|
|
8660
|
+
|
|
8661
|
+
/**
|
|
8662
|
+
* Prepare an EVM-compatible `depositForBurn` transaction for CCTP v2.
|
|
8663
|
+
*
|
|
8664
|
+
* This function creates a prepared chain request for burning USDC tokens on the source EVM chain
|
|
8665
|
+
* to initiate a cross-chain transfer using Circle's CCTP v2 protocol.
|
|
8302
8666
|
*
|
|
8303
8667
|
* @param params - The action payload containing:
|
|
8304
8668
|
* - `fromChain`: The EVM chain definition where the burn will occur.
|
|
8305
8669
|
* - `toChain`: The destination chain definition (must include CCTP domain).
|
|
8306
|
-
* - `amount`: The amount of USDC to burn (as
|
|
8307
|
-
* - `mintRecipient`: The address
|
|
8670
|
+
* - `amount`: The amount of USDC to burn (as bigint, in the smallest unit).
|
|
8671
|
+
* - `mintRecipient`: The address to receive minted USDC on the destination chain.
|
|
8308
8672
|
* - `maxFee`: The maximum fee to pay for the cross-chain message relay.
|
|
8309
8673
|
* - `minFinalityThreshold`: The minimum finality threshold for the burn event.
|
|
8310
8674
|
* @param adapter - The EVM adapter responsible for chain context and transaction preparation.
|
|
8311
8675
|
* @param context - The resolved operation context providing chain and address information.
|
|
8312
8676
|
* @returns A promise that resolves to a prepared chain request for the `depositForBurn` call.
|
|
8313
|
-
* @throws
|
|
8677
|
+
* @throws KitError if the `fromChain` is not EVM-compatible or if preparation fails.
|
|
8314
8678
|
*
|
|
8315
8679
|
* @example
|
|
8316
8680
|
* ```typescript
|
|
@@ -8318,37 +8682,63 @@ const ZERO_HASH = '0x00000000000000000000000000000000000000000000000000000000000
|
|
|
8318
8682
|
* {
|
|
8319
8683
|
* fromChain,
|
|
8320
8684
|
* toChain,
|
|
8321
|
-
* amount:
|
|
8685
|
+
* amount: 1000000n,
|
|
8322
8686
|
* mintRecipient: '0xabc...',
|
|
8323
8687
|
* maxFee: 0n,
|
|
8324
|
-
* minFinalityThreshold:
|
|
8688
|
+
* minFinalityThreshold: 1000,
|
|
8325
8689
|
* },
|
|
8326
8690
|
* adapter,
|
|
8327
8691
|
* context
|
|
8328
|
-
* )
|
|
8329
|
-
* await prepared.execute()
|
|
8692
|
+
* )
|
|
8693
|
+
* await prepared.execute()
|
|
8330
8694
|
* ```
|
|
8331
8695
|
*/
|
|
8332
8696
|
const depositForBurn = async (params, adapter, context) => {
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8697
|
+
return prepareDepositForBurn(params, adapter, context);
|
|
8698
|
+
};
|
|
8699
|
+
|
|
8700
|
+
/**
|
|
8701
|
+
* Prepare an EVM-compatible `depositForBurnWithHook` transaction for CCTP v2 forwarding.
|
|
8702
|
+
*
|
|
8703
|
+
* This function creates a prepared chain request for burning USDC tokens on the source EVM chain
|
|
8704
|
+
* with hook data that signals to Circle's Orbit relayer that forwarding is requested. The relayer
|
|
8705
|
+
* will automatically fetch the attestation and submit the destination mint transaction.
|
|
8706
|
+
*
|
|
8707
|
+
* @param params - The action payload containing:
|
|
8708
|
+
* - `fromChain`: The EVM chain definition where the burn will occur.
|
|
8709
|
+
* - `toChain`: The destination chain definition (must include CCTP domain).
|
|
8710
|
+
* - `amount`: The amount of USDC to burn (as bigint, in the smallest unit).
|
|
8711
|
+
* - `mintRecipient`: The address to receive minted USDC on the destination chain.
|
|
8712
|
+
* - `maxFee`: The maximum fee to pay (must cover burn fee + forwarding fee).
|
|
8713
|
+
* - `minFinalityThreshold`: The minimum finality threshold for the burn event.
|
|
8714
|
+
* - `hookData`: Hex-encoded hook data for CCTP forwarding.
|
|
8715
|
+
* @param adapter - The EVM adapter responsible for chain context and transaction preparation.
|
|
8716
|
+
* @param context - The resolved operation context providing chain and address information.
|
|
8717
|
+
* @returns A promise that resolves to a prepared chain request for the `depositForBurnWithHook` call.
|
|
8718
|
+
* @throws KitError if the `fromChain` is not EVM-compatible or if preparation fails.
|
|
8719
|
+
*
|
|
8720
|
+
* @example
|
|
8721
|
+
* ```typescript
|
|
8722
|
+
* import { buildForwardingHookData } from '@core/utils'
|
|
8723
|
+
*
|
|
8724
|
+
* const prepared = await depositForBurnWithHook(
|
|
8725
|
+
* {
|
|
8726
|
+
* fromChain,
|
|
8727
|
+
* toChain,
|
|
8728
|
+
* amount: 1000000n,
|
|
8729
|
+
* mintRecipient: '0xabc...',
|
|
8730
|
+
* maxFee: 50000n,
|
|
8731
|
+
* minFinalityThreshold: 1000,
|
|
8732
|
+
* hookData: buildForwardingHookData(),
|
|
8733
|
+
* },
|
|
8734
|
+
* adapter,
|
|
8735
|
+
* context
|
|
8736
|
+
* )
|
|
8737
|
+
* await prepared.execute()
|
|
8738
|
+
* ```
|
|
8739
|
+
*/
|
|
8740
|
+
const depositForBurnWithHook = async (params, adapter, context) => {
|
|
8741
|
+
return prepareDepositForBurn(params, adapter, context);
|
|
8352
8742
|
};
|
|
8353
8743
|
|
|
8354
8744
|
/**
|
|
@@ -8399,8 +8789,317 @@ const receiveMessage = async (params, adapter, context) => {
|
|
|
8399
8789
|
}, context);
|
|
8400
8790
|
};
|
|
8401
8791
|
|
|
8792
|
+
// ============================================================================
|
|
8793
|
+
// Validation Helper Functions
|
|
8794
|
+
// ============================================================================
|
|
8795
|
+
/**
|
|
8796
|
+
* Validate that the source chain is EVM-compatible and supports custom bridge contracts.
|
|
8797
|
+
*
|
|
8798
|
+
* Custom bridge contracts are Circle's kit-specific contracts that provide
|
|
8799
|
+
* additional functionality beyond the standard CCTP TokenMessenger, such as:
|
|
8800
|
+
* - Permit-based gasless approvals (EIP-2612)
|
|
8801
|
+
* - Protocol fee collection
|
|
8802
|
+
* - Hook data for automatic forwarding
|
|
8803
|
+
*
|
|
8804
|
+
* Not all chains have custom bridge contracts deployed. For chains without
|
|
8805
|
+
* custom bridge support, users should use `cctp.v2.depositForBurn` instead,
|
|
8806
|
+
* which interacts directly with Circle's TokenMessenger contract.
|
|
8807
|
+
*
|
|
8808
|
+
* @param params - The custom burn parameters containing chain definitions.
|
|
8809
|
+
* @throws KitError if the source chain is not EVM (e.g., Solana).
|
|
8810
|
+
* @throws KitError if the chain doesn't have a custom bridge contract deployed.
|
|
8811
|
+
*/
|
|
8812
|
+
function validateChainSupport(params) {
|
|
8813
|
+
// Ensure source chain is EVM - Solana uses a different code path
|
|
8814
|
+
if (params.fromChain.type !== 'evm') {
|
|
8815
|
+
throw createInvalidChainError(params.fromChain.name, `Expected EVM chain but received chain type: ${params.fromChain.type}`);
|
|
8816
|
+
}
|
|
8817
|
+
// Check if the chain has a custom bridge contract in its configuration
|
|
8818
|
+
if (!hasCustomContractSupport(params.fromChain, 'bridge')) {
|
|
8819
|
+
throw createInvalidChainError(params.fromChain.name, "Chain does not support custom bridge contracts. Use 'cctp.v2.depositForBurn' instead.");
|
|
8820
|
+
}
|
|
8821
|
+
}
|
|
8822
|
+
/**
|
|
8823
|
+
* Validate the logical consistency of protocol fee parameters.
|
|
8824
|
+
*
|
|
8825
|
+
* The protocol fee mechanism allows integrators to collect a fee on each
|
|
8826
|
+
* cross-chain transfer. When enabled, the fee is deducted from the transfer
|
|
8827
|
+
* amount and sent to the specified fee recipient.
|
|
8828
|
+
*
|
|
8829
|
+
* Business rules:
|
|
8830
|
+
* - protocolFee MUST be non-negative
|
|
8831
|
+
* - If protocolFee is non-zero, feeRecipient MUST be specified (where does the fee go?)
|
|
8832
|
+
* - If feeRecipient is specified, protocolFee MUST be non-zero (why specify recipient for zero fee?)
|
|
8833
|
+
* - When feeRecipient is provided, it must be a valid EVM address
|
|
8834
|
+
*
|
|
8835
|
+
* @param protocolFee - The fee amount (defaults to 0n if not provided).
|
|
8836
|
+
* @param feeRecipient - The address to receive the fee (optional).
|
|
8837
|
+
* @throws KitError if protocolFee is negative.
|
|
8838
|
+
* @throws KitError if protocolFee is set but feeRecipient is missing.
|
|
8839
|
+
* @throws KitError if feeRecipient is set but protocolFee is zero.
|
|
8840
|
+
* @throws KitError if feeRecipient is not a valid EVM address.
|
|
8841
|
+
*/
|
|
8842
|
+
function validateProtocolFeeParams(protocolFee, feeRecipient) {
|
|
8843
|
+
// Validate: fee must be non-negative
|
|
8844
|
+
if (protocolFee < 0n) {
|
|
8845
|
+
throw createValidationFailedError('protocolFee', protocolFee, 'Must be non-negative');
|
|
8846
|
+
}
|
|
8847
|
+
// Validate: fee without recipient makes no sense
|
|
8848
|
+
if (protocolFee > 0n && feeRecipient === undefined) {
|
|
8849
|
+
throw createValidationFailedError('protocolFee', protocolFee, 'Requires feeRecipient. Specify where the fee should be sent');
|
|
8850
|
+
}
|
|
8851
|
+
// Validate: recipient without fee makes no sense
|
|
8852
|
+
if (feeRecipient !== undefined && protocolFee === 0n) {
|
|
8853
|
+
throw createValidationFailedError('feeRecipient', feeRecipient, 'Requires non-zero protocolFee. Specify the fee amount to send');
|
|
8854
|
+
}
|
|
8855
|
+
// Validate the fee recipient address format if provided
|
|
8856
|
+
if (feeRecipient !== undefined) {
|
|
8857
|
+
assertEvmAddress(feeRecipient);
|
|
8858
|
+
}
|
|
8859
|
+
}
|
|
8860
|
+
/**
|
|
8861
|
+
* Validate EIP-2612 permit signature components if provided.
|
|
8862
|
+
*
|
|
8863
|
+
* EIP-2612 permits allow gasless token approvals via off-chain signatures.
|
|
8864
|
+
* Instead of the user calling `approve()` on the USDC contract (which costs gas),
|
|
8865
|
+
* they sign a message off-chain, and the bridge contract calls `permit()` on
|
|
8866
|
+
* their behalf as part of the bridge transaction.
|
|
8867
|
+
*
|
|
8868
|
+
* The permit signature consists of:
|
|
8869
|
+
* - `deadline`: Unix timestamp after which the permit expires
|
|
8870
|
+
* - `v`: ECDSA signature recovery parameter (27 or 28)
|
|
8871
|
+
* - `r`: ECDSA signature component (32 bytes)
|
|
8872
|
+
* - `s`: ECDSA signature component (32 bytes)
|
|
8873
|
+
*
|
|
8874
|
+
* @param permitParams - The permit parameters including deadline and signature.
|
|
8875
|
+
* @throws KitError if the permit deadline has already passed.
|
|
8876
|
+
* @throws KitError if the signature recovery parameter (v) is invalid.
|
|
8877
|
+
*/
|
|
8878
|
+
function validatePermitParams(permitParams) {
|
|
8879
|
+
// Early return if no permit params - caller will use preapproval flow instead
|
|
8880
|
+
if (!permitParams) {
|
|
8881
|
+
return;
|
|
8882
|
+
}
|
|
8883
|
+
// Check that the permit hasn't expired
|
|
8884
|
+
// Deadline is compared against current Unix timestamp (seconds since epoch)
|
|
8885
|
+
const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
|
|
8886
|
+
if (permitParams.deadline <= currentTimestamp) {
|
|
8887
|
+
throw createValidationFailedError('permitParams.deadline', permitParams.deadline, 'Permit deadline has expired');
|
|
8888
|
+
}
|
|
8889
|
+
// Validate ECDSA signature recovery parameter (v).
|
|
8890
|
+
// In the EVM's ECDSA implementation, 'v' is the recovery identifier that
|
|
8891
|
+
// allows the public key to be recovered from the signature. Per EIP-155,
|
|
8892
|
+
// valid values are:
|
|
8893
|
+
// - 27: corresponds to recovery id 0 (even y-coordinate)
|
|
8894
|
+
// - 28: corresponds to recovery id 1 (odd y-coordinate)
|
|
8895
|
+
// These are the only valid values for standard Ethereum signatures.
|
|
8896
|
+
const VALID_RECOVERY_PARAMS = [27, 28];
|
|
8897
|
+
if (!VALID_RECOVERY_PARAMS.includes(permitParams.v)) {
|
|
8898
|
+
throw createValidationFailedError('permitParams.v', permitParams.v, 'Invalid ECDSA recovery parameter. Must be 27 or 28');
|
|
8899
|
+
}
|
|
8900
|
+
}
|
|
8901
|
+
/**
|
|
8902
|
+
* Get and validate the custom bridge contract address from chain definition.
|
|
8903
|
+
*
|
|
8904
|
+
* The bridge contract address is stored in the chain definition under
|
|
8905
|
+
* `kitContracts.bridge`. This function retrieves it and validates that
|
|
8906
|
+
* it's a properly formatted EVM address (0x-prefixed, 40 hex characters).
|
|
8907
|
+
*
|
|
8908
|
+
* @param fromChain - The source EVM chain definition.
|
|
8909
|
+
* @returns The validated bridge contract address.
|
|
8910
|
+
* @throws KitError if no bridge contract address is configured for the chain.
|
|
8911
|
+
* @throws KitError if the address format is invalid.
|
|
8912
|
+
*/
|
|
8913
|
+
function getBridgeAddress(fromChain) {
|
|
8914
|
+
const bridgeAddress = fromChain.kitContracts?.bridge;
|
|
8915
|
+
if (bridgeAddress === undefined) {
|
|
8916
|
+
throw createInvalidChainError(fromChain.name, 'No bridge contract address found in chain configuration');
|
|
8917
|
+
}
|
|
8918
|
+
// Validate address format (throws if invalid)
|
|
8919
|
+
assertEvmAddress(bridgeAddress);
|
|
8920
|
+
return bridgeAddress;
|
|
8921
|
+
}
|
|
8922
|
+
// ============================================================================
|
|
8923
|
+
// Contract Call Builder Functions
|
|
8924
|
+
// ============================================================================
|
|
8925
|
+
/**
|
|
8926
|
+
* Build the BridgeParams struct for the smart contract call.
|
|
8927
|
+
*
|
|
8928
|
+
* Construct the struct from user-provided parameters, applying defaults and
|
|
8929
|
+
* converting addresses to bytes32 format as required by CCTP's cross-chain messaging.
|
|
8930
|
+
*
|
|
8931
|
+
* @param params - The custom burn parameters from the user.
|
|
8932
|
+
* @param protocolFee - The validated protocol fee amount.
|
|
8933
|
+
* @param bridgeAddress - The bridge contract address (used as default fee recipient).
|
|
8934
|
+
* @returns The BridgeParams struct ready for the contract call.
|
|
8935
|
+
*/
|
|
8936
|
+
function buildBridgeParams(params, protocolFee, bridgeAddress) {
|
|
8937
|
+
const fromChain = params.fromChain;
|
|
8938
|
+
return {
|
|
8939
|
+
amount: params.amount,
|
|
8940
|
+
maxFee: params.maxFee,
|
|
8941
|
+
fee: protocolFee,
|
|
8942
|
+
// Convert recipient to bytes32 for cross-chain compatibility
|
|
8943
|
+
mintRecipient: convertAddress(params.mintRecipient, 'bytes32'),
|
|
8944
|
+
// Zero address means any caller can claim on destination
|
|
8945
|
+
destinationCaller: convertAddress(params.destinationCaller ?? ZERO_HASH, 'bytes32'),
|
|
8946
|
+
// USDC address on the source chain
|
|
8947
|
+
burnToken: fromChain.usdcAddress,
|
|
8948
|
+
// Fee recipient defaults to bridge contract if not specified
|
|
8949
|
+
feeRecipient: params.feeRecipient ?? bridgeAddress,
|
|
8950
|
+
// CCTP domain ID (e.g., 0 = Ethereum, 1 = Avalanche, 6 = Base)
|
|
8951
|
+
destinationDomain: params.toChain.cctp.domain,
|
|
8952
|
+
// Convert to bigint for contract compatibility
|
|
8953
|
+
minFinalityThreshold: BigInt(params.minFinalityThreshold),
|
|
8954
|
+
};
|
|
8955
|
+
}
|
|
8956
|
+
/**
|
|
8957
|
+
* Determine which bridge contract function to call.
|
|
8958
|
+
*
|
|
8959
|
+
* The function selection follows a 2x2 matrix based on:
|
|
8960
|
+
* 1. Authorization method: Has the user provided a permit signature?
|
|
8961
|
+
* 2. Forwarding mode: Should the relayer auto-complete the transfer?
|
|
8962
|
+
*
|
|
8963
|
+
* Decision tree:
|
|
8964
|
+
* - Permit + Hook → bridgeWithPermitAndHook
|
|
8965
|
+
* - Permit + No Hook → bridgeWithPermit
|
|
8966
|
+
* - No Permit + Hook → bridgeWithPreapprovalAndHook
|
|
8967
|
+
* - No Permit + No Hook → bridgeWithPreapproval
|
|
8968
|
+
*
|
|
8969
|
+
* @param hasPermit - True if permit params were provided.
|
|
8970
|
+
* @param hasHookData - True if hook data was provided for auto-forwarding.
|
|
8971
|
+
* @returns The name of the bridge contract function to call.
|
|
8972
|
+
*/
|
|
8973
|
+
function selectBridgeFunction(hasPermit, hasHookData) {
|
|
8974
|
+
if (hasPermit) {
|
|
8975
|
+
return hasHookData ? 'bridgeWithPermitAndHook' : 'bridgeWithPermit';
|
|
8976
|
+
}
|
|
8977
|
+
return hasHookData ? 'bridgeWithPreapprovalAndHook' : 'bridgeWithPreapproval';
|
|
8978
|
+
}
|
|
8979
|
+
/**
|
|
8980
|
+
* Build the arguments array for the bridge contract call.
|
|
8981
|
+
*
|
|
8982
|
+
* Different bridge functions expect different argument patterns:
|
|
8983
|
+
* - bridgeWithPreapproval(bridgeParams)
|
|
8984
|
+
* - bridgeWithPermit(bridgeParams, permitParams)
|
|
8985
|
+
* - bridgeWithPreapprovalAndHook(bridgeParams, hookData)
|
|
8986
|
+
* - bridgeWithPermitAndHook(bridgeParams, permitParams, hookData)
|
|
8987
|
+
*
|
|
8988
|
+
* This function constructs the correct args array based on which optional
|
|
8989
|
+
* parameters are present.
|
|
8990
|
+
*
|
|
8991
|
+
* @param bridgeParams - The BridgeParams struct (always first argument).
|
|
8992
|
+
* @param permitParams - Optional EIP-2612 permit signature.
|
|
8993
|
+
* @param hookData - Optional hook data for CCTP forwarding.
|
|
8994
|
+
* @returns The arguments array ready for the contract call.
|
|
8995
|
+
* @throws KitError if hookData is not a valid hex string starting with 0x.
|
|
8996
|
+
*/
|
|
8997
|
+
function buildContractArgs(bridgeParams, permitParams, hookData) {
|
|
8998
|
+
// BridgeParams is always the first argument
|
|
8999
|
+
const args = [bridgeParams];
|
|
9000
|
+
// Add permit params if using permit-based authorization
|
|
9001
|
+
if (permitParams) {
|
|
9002
|
+
args.push({
|
|
9003
|
+
deadline: permitParams.deadline,
|
|
9004
|
+
v: permitParams.v,
|
|
9005
|
+
r: permitParams.r,
|
|
9006
|
+
s: permitParams.s,
|
|
9007
|
+
});
|
|
9008
|
+
}
|
|
9009
|
+
// Add hook data if using auto-forwarding, after validating format
|
|
9010
|
+
// Empty string is treated as "no hook data"
|
|
9011
|
+
if (hookData !== undefined && hookData !== '') {
|
|
9012
|
+
const result = hexStringSchema.safeParse(hookData);
|
|
9013
|
+
if (!result.success) {
|
|
9014
|
+
throw createValidationFailedError('hookData', hookData, 'Must be a valid hex string starting with 0x');
|
|
9015
|
+
}
|
|
9016
|
+
args.push(hookData);
|
|
9017
|
+
}
|
|
9018
|
+
return args;
|
|
9019
|
+
}
|
|
9020
|
+
// ============================================================================
|
|
9021
|
+
// Main Export Function
|
|
9022
|
+
// ============================================================================
|
|
9023
|
+
/**
|
|
9024
|
+
* Prepare an EVM-compatible custom burn transaction for CCTP v2 using a custom bridge contract.
|
|
9025
|
+
*
|
|
9026
|
+
* This is the shared implementation for both `customBurn` and `customBurnWithHook` actions.
|
|
9027
|
+
* It creates a prepared chain request for burning USDC tokens using a custom bridge
|
|
9028
|
+
* contract that supports preapproval, permit-based authorization, and optional hook data
|
|
9029
|
+
* for CCTP forwarding.
|
|
9030
|
+
*
|
|
9031
|
+
* **Authorization Methods**
|
|
9032
|
+
*
|
|
9033
|
+
* **Preapproval**: User has already called `approve()` on the USDC contract to allow
|
|
9034
|
+
* the bridge to spend their tokens. This requires a separate transaction but gives
|
|
9035
|
+
* the user full control over when and how much they approve.
|
|
9036
|
+
*
|
|
9037
|
+
* **Permit**: User signs an EIP-2612 permit message off-chain, and the bridge contract
|
|
9038
|
+
* calls `permit()` on their behalf. This enables gasless approvals in a single transaction.
|
|
9039
|
+
*
|
|
9040
|
+
* **Forwarding Modes**
|
|
9041
|
+
*
|
|
9042
|
+
* **Standard**: User must manually claim the USDC on the destination chain by submitting
|
|
9043
|
+
* the attestation to the destination MessageTransmitter.
|
|
9044
|
+
*
|
|
9045
|
+
* **Hook (Auto-forwarding)**: Circle's Orbit relayer automatically fetches the attestation
|
|
9046
|
+
* and submits the mint transaction on the destination chain. The user pays a higher fee
|
|
9047
|
+
* but doesn't need to interact with the destination chain.
|
|
9048
|
+
*
|
|
9049
|
+
* **Function Selection**
|
|
9050
|
+
*
|
|
9051
|
+
* The function automatically selects the appropriate bridge method based on parameters:
|
|
9052
|
+
* - Without hookData + without permit → `bridgeWithPreapproval`
|
|
9053
|
+
* - Without hookData + with permit → `bridgeWithPermit`
|
|
9054
|
+
* - With hookData + without permit → `bridgeWithPreapprovalAndHook`
|
|
9055
|
+
* - With hookData + with permit → `bridgeWithPermitAndHook`
|
|
9056
|
+
*
|
|
9057
|
+
* @param params - The action payload (either customBurn or customBurnWithHook params).
|
|
9058
|
+
* @param adapter - The EVM adapter responsible for chain context and transaction preparation.
|
|
9059
|
+
* @param context - The resolved operation context providing chain and address information.
|
|
9060
|
+
* @returns A promise that resolves to a prepared chain request.
|
|
9061
|
+
* @throws KitError if the source chain doesn't support custom bridge contracts.
|
|
9062
|
+
* @throws KitError if the source chain is not EVM-compatible.
|
|
9063
|
+
* @throws KitError if permit signature validation fails.
|
|
9064
|
+
* @throws KitError if transaction preparation fails.
|
|
9065
|
+
*
|
|
9066
|
+
* @internal This function is used internally by customBurn and customBurnWithHook wrappers.
|
|
9067
|
+
*/
|
|
9068
|
+
const prepareCustomBurn = async (params, adapter, context) => {
|
|
9069
|
+
// Step 1: Validate that the source chain supports custom bridge contracts
|
|
9070
|
+
validateChainSupport(params);
|
|
9071
|
+
// Step 2: Validate protocol fee configuration
|
|
9072
|
+
// Default protocolFee to 0 if not specified (no fee collection)
|
|
9073
|
+
const protocolFee = params.protocolFee ?? 0n;
|
|
9074
|
+
validateProtocolFeeParams(protocolFee, params.feeRecipient);
|
|
9075
|
+
// Step 3: Validate permit signature if provided
|
|
9076
|
+
// This checks deadline and ECDSA parameters
|
|
9077
|
+
validatePermitParams(params.permitParams);
|
|
9078
|
+
// Step 4: Get the custom bridge contract address from chain config
|
|
9079
|
+
const fromChain = params.fromChain;
|
|
9080
|
+
const bridgeAddress = getBridgeAddress(fromChain);
|
|
9081
|
+
// Step 5: Build the BridgeParams struct for the contract
|
|
9082
|
+
const bridgeParams = buildBridgeParams(params, protocolFee, bridgeAddress);
|
|
9083
|
+
// Step 6: Detect if hookData is present and determine which function to call
|
|
9084
|
+
// Hook data is only present in customBurnWithHook action payload
|
|
9085
|
+
const hookData = 'hookData' in params ? params.hookData : undefined;
|
|
9086
|
+
const hasHookData = hookData !== undefined && hookData !== '';
|
|
9087
|
+
const functionName = selectBridgeFunction(!!params.permitParams, hasHookData);
|
|
9088
|
+
// Step 7: Build the arguments array for the contract call
|
|
9089
|
+
const args = buildContractArgs(bridgeParams, params.permitParams, hookData);
|
|
9090
|
+
// Step 8: Prepare the transaction using the adapter
|
|
9091
|
+
// This creates a PreparedChainRequest that can be executed or simulated
|
|
9092
|
+
return adapter.prepare({
|
|
9093
|
+
type: 'evm',
|
|
9094
|
+
abi: bridgeContractAbi,
|
|
9095
|
+
address: bridgeAddress,
|
|
9096
|
+
functionName,
|
|
9097
|
+
args,
|
|
9098
|
+
}, context);
|
|
9099
|
+
};
|
|
9100
|
+
|
|
8402
9101
|
/**
|
|
8403
|
-
*
|
|
9102
|
+
* Prepare an EVM-compatible `customBurn` transaction for CCTP v2 using a custom bridge contract.
|
|
8404
9103
|
*
|
|
8405
9104
|
* This function creates a prepared chain request for burning USDC tokens using a custom bridge
|
|
8406
9105
|
* contract that supports both preapproval and permit-based token authorization. The custom
|
|
@@ -8410,9 +9109,6 @@ const receiveMessage = async (params, adapter, context) => {
|
|
|
8410
9109
|
* - When `permitParams` is provided → uses `bridgeWithPermit` for EIP-2612 signature-based approval
|
|
8411
9110
|
* - When `permitParams` is not provided → uses `bridgeWithPreapproval` for traditional preapproval
|
|
8412
9111
|
*
|
|
8413
|
-
* The action provides a unified interface for both approval methods, with extra bridge
|
|
8414
|
-
* contract parameters (fee, burnToken, feeRecipient) automatically resolved for ease of use.
|
|
8415
|
-
*
|
|
8416
9112
|
* @param params - The action payload containing:
|
|
8417
9113
|
* - `fromChain`: The EVM chain definition where the burn will occur (must support custom bridge).
|
|
8418
9114
|
* - `toChain`: The destination chain definition (must include CCTP domain).
|
|
@@ -8424,17 +9120,13 @@ const receiveMessage = async (params, adapter, context) => {
|
|
|
8424
9120
|
* - `protocolFee`: Optional protocol fee amount (defaults to 0n for basic usage).
|
|
8425
9121
|
* - `feeRecipient`: Optional fee recipient address (defaults to bridge address).
|
|
8426
9122
|
* - `permitParams`: Optional EIP-2612 permit signature for gasless approval.
|
|
8427
|
-
*
|
|
8428
|
-
* For basic usage, only the core CCTP parameters are needed (same as depositForBurn).
|
|
8429
|
-
* For advanced usage, protocol fee parameters enable custom fee collection.
|
|
8430
|
-
* For gasless transactions, provide permitParams to avoid separate approval transactions.
|
|
8431
9123
|
* @param adapter - The EVM adapter responsible for chain context and transaction preparation.
|
|
8432
9124
|
* @param context - The resolved operation context providing chain and address information.
|
|
8433
9125
|
* @returns A promise that resolves to a prepared chain request for the `customBurn` operation.
|
|
8434
|
-
* @throws
|
|
8435
|
-
* @throws
|
|
8436
|
-
* @throws
|
|
8437
|
-
* @throws
|
|
9126
|
+
* @throws KitError if the source chain doesn't support custom bridge contracts.
|
|
9127
|
+
* @throws KitError if the source chain is not EVM-compatible.
|
|
9128
|
+
* @throws KitError if permit signature validation fails.
|
|
9129
|
+
* @throws KitError if transaction preparation fails.
|
|
8438
9130
|
*
|
|
8439
9131
|
* @example
|
|
8440
9132
|
* ```typescript
|
|
@@ -8442,9 +9134,7 @@ const receiveMessage = async (params, adapter, context) => {
|
|
|
8442
9134
|
*
|
|
8443
9135
|
* // Check if chain supports custom bridge before using
|
|
8444
9136
|
* if (hasCustomContractSupport(sourceChain, 'bridge')) {
|
|
8445
|
-
*
|
|
8446
|
-
* // Basic usage with preapproval (same interface as depositForBurn)
|
|
8447
|
-
* const basicTransfer = await customBurn(
|
|
9137
|
+
* const prepared = await customBurn(
|
|
8448
9138
|
* {
|
|
8449
9139
|
* fromChain: sourceChain,
|
|
8450
9140
|
* toChain: destinationChain,
|
|
@@ -8452,127 +9142,74 @@ const receiveMessage = async (params, adapter, context) => {
|
|
|
8452
9142
|
* mintRecipient: recipientAddress,
|
|
8453
9143
|
* maxFee: BigInt('1000'),
|
|
8454
9144
|
* minFinalityThreshold: 65
|
|
8455
|
-
* // protocolFee and feeRecipient auto-default
|
|
8456
9145
|
* },
|
|
8457
9146
|
* adapter,
|
|
8458
9147
|
* context
|
|
8459
9148
|
* )
|
|
9149
|
+
* await prepared.execute()
|
|
9150
|
+
* }
|
|
9151
|
+
* ```
|
|
9152
|
+
*/
|
|
9153
|
+
const customBurn = async (params, adapter, context) => {
|
|
9154
|
+
return prepareCustomBurn(params, adapter, context);
|
|
9155
|
+
};
|
|
9156
|
+
|
|
9157
|
+
/**
|
|
9158
|
+
* Prepare an EVM-compatible `customBurnWithHook` transaction for CCTP v2 with forwarding.
|
|
8460
9159
|
*
|
|
8461
|
-
*
|
|
8462
|
-
*
|
|
8463
|
-
*
|
|
8464
|
-
*
|
|
8465
|
-
*
|
|
8466
|
-
*
|
|
8467
|
-
*
|
|
8468
|
-
* maxFee: BigInt('1000'),
|
|
8469
|
-
* minFinalityThreshold: 65,
|
|
8470
|
-
* permitParams: {
|
|
8471
|
-
* deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour
|
|
8472
|
-
* v: 27,
|
|
8473
|
-
* r: '0x1234567890abcdef...',
|
|
8474
|
-
* s: '0xfedcba0987654321...'
|
|
8475
|
-
* }
|
|
8476
|
-
* },
|
|
8477
|
-
* adapter,
|
|
8478
|
-
* context
|
|
8479
|
-
* )
|
|
9160
|
+
* This function creates a prepared chain request for burning USDC tokens using a custom bridge
|
|
9161
|
+
* contract with hook data that signals to Circle's Orbit relayer that forwarding is requested.
|
|
9162
|
+
* The relayer will automatically fetch the attestation and submit the destination mint transaction.
|
|
9163
|
+
*
|
|
9164
|
+
* The function automatically selects the appropriate bridge method based on parameters:
|
|
9165
|
+
* - When `permitParams` is provided → uses `bridgeWithPermitAndHook`
|
|
9166
|
+
* - When `permitParams` is not provided → uses `bridgeWithPreapprovalAndHook`
|
|
8480
9167
|
*
|
|
8481
|
-
*
|
|
8482
|
-
*
|
|
9168
|
+
* @param params - The action payload containing:
|
|
9169
|
+
* - `fromChain`: The EVM chain definition where the burn will occur (must support custom bridge).
|
|
9170
|
+
* - `toChain`: The destination chain definition (must include CCTP domain).
|
|
9171
|
+
* - `amount`: The amount of USDC to burn (as bigint, in the smallest unit).
|
|
9172
|
+
* - `mintRecipient`: The address to receive minted USDC on the destination chain.
|
|
9173
|
+
* - `destinationCaller`: Optional address authorized to call receiveMessage (zero hash if any).
|
|
9174
|
+
* - `maxFee`: The maximum fee to pay (must cover burn fee + forwarding fee).
|
|
9175
|
+
* - `minFinalityThreshold`: The minimum finality threshold for the burn event.
|
|
9176
|
+
* - `protocolFee`: Optional protocol fee amount (defaults to 0n for basic usage).
|
|
9177
|
+
* - `feeRecipient`: Optional fee recipient address (defaults to bridge address).
|
|
9178
|
+
* - `permitParams`: Optional EIP-2612 permit signature for gasless approval.
|
|
9179
|
+
* - `hookData`: Hex-encoded hook data for CCTP forwarding.
|
|
9180
|
+
* @param adapter - The EVM adapter responsible for chain context and transaction preparation.
|
|
9181
|
+
* @param context - The resolved operation context providing chain and address information.
|
|
9182
|
+
* @returns A promise that resolves to a prepared chain request for the `customBurnWithHook` operation.
|
|
9183
|
+
* @throws KitError if the source chain doesn't support custom bridge contracts.
|
|
9184
|
+
* @throws KitError if the source chain is not EVM-compatible.
|
|
9185
|
+
* @throws KitError if permit signature validation fails.
|
|
9186
|
+
* @throws KitError if transaction preparation fails.
|
|
9187
|
+
*
|
|
9188
|
+
* @example
|
|
9189
|
+
* ```typescript
|
|
9190
|
+
* import { hasCustomContractSupport } from '@core/chains'
|
|
9191
|
+
* import { buildForwardingHookData } from '@core/utils'
|
|
9192
|
+
*
|
|
9193
|
+
* if (hasCustomContractSupport(sourceChain, 'bridge')) {
|
|
9194
|
+
* const prepared = await customBurnWithHook(
|
|
8483
9195
|
* {
|
|
8484
9196
|
* fromChain: sourceChain,
|
|
8485
9197
|
* toChain: destinationChain,
|
|
8486
9198
|
* amount: BigInt('1000000'),
|
|
8487
9199
|
* mintRecipient: recipientAddress,
|
|
8488
|
-
* maxFee: BigInt('
|
|
9200
|
+
* maxFee: BigInt('50000'),
|
|
8489
9201
|
* minFinalityThreshold: 65,
|
|
8490
|
-
*
|
|
8491
|
-
* feeRecipient: '0xFeeRecipientAddress'
|
|
9202
|
+
* hookData: buildForwardingHookData()
|
|
8492
9203
|
* },
|
|
8493
9204
|
* adapter,
|
|
8494
9205
|
* context
|
|
8495
9206
|
* )
|
|
8496
|
-
*
|
|
8497
|
-
* await basicTransfer.execute()
|
|
9207
|
+
* await prepared.execute()
|
|
8498
9208
|
* }
|
|
8499
9209
|
* ```
|
|
8500
9210
|
*/
|
|
8501
|
-
const
|
|
8502
|
-
|
|
8503
|
-
if (params.fromChain.type !== 'evm') {
|
|
8504
|
-
throw new Error(`Expected fromChain to be EVM chain definition, but received chain type: ${params.fromChain.type}`);
|
|
8505
|
-
}
|
|
8506
|
-
// Validate that the chain supports custom bridge contracts
|
|
8507
|
-
if (!hasCustomContractSupport(params.fromChain, 'bridge')) {
|
|
8508
|
-
throw new Error(`Chain ${params.fromChain.name} does not support custom bridge contracts. Use 'cctp.v2.depositForBurn' instead.`);
|
|
8509
|
-
}
|
|
8510
|
-
// Validate logical consistency: protocolFee and feeRecipient must be used together
|
|
8511
|
-
const protocolFee = params.protocolFee ?? 0n;
|
|
8512
|
-
if (protocolFee > 0n && params.feeRecipient === undefined) {
|
|
8513
|
-
throw new Error('protocolFee requires feeRecipient. Specify where the fee should be sent.');
|
|
8514
|
-
}
|
|
8515
|
-
if (params.feeRecipient !== undefined && protocolFee === 0n) {
|
|
8516
|
-
throw new Error('feeRecipient requires non-zero protocolFee. Specify the fee amount to send.');
|
|
8517
|
-
}
|
|
8518
|
-
// Validate address format for user-provided feeRecipient
|
|
8519
|
-
if (params.feeRecipient !== undefined) {
|
|
8520
|
-
assertEvmAddress(params.feeRecipient);
|
|
8521
|
-
}
|
|
8522
|
-
// Validate permit signature components if provided
|
|
8523
|
-
if (params.permitParams) {
|
|
8524
|
-
const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
|
|
8525
|
-
if (params.permitParams.deadline <= currentTimestamp) {
|
|
8526
|
-
throw new Error('Permit deadline has expired');
|
|
8527
|
-
}
|
|
8528
|
-
if (![27, 28].includes(params.permitParams.v)) {
|
|
8529
|
-
throw new Error('Invalid permit signature recovery parameter (v)');
|
|
8530
|
-
}
|
|
8531
|
-
}
|
|
8532
|
-
// Get the custom bridge contract address from chain definition
|
|
8533
|
-
const bridgeAddress = params.fromChain.kitContracts?.bridge;
|
|
8534
|
-
if (bridgeAddress === undefined) {
|
|
8535
|
-
throw new Error(`No bridge contract address found for chain ${params.fromChain.name}`);
|
|
8536
|
-
}
|
|
8537
|
-
assertEvmAddress(bridgeAddress);
|
|
8538
|
-
// Prepare the bridge parameters struct for the contract call
|
|
8539
|
-
// Use optional parameters with sensible defaults:
|
|
8540
|
-
// - feeRecipient: defaults to bridge address (safe for zero fees)
|
|
8541
|
-
// - burnToken: auto-resolve from chain definition (same as depositForBurn)
|
|
8542
|
-
const destinationCallerAddress = convertAddress(params.destinationCaller ?? ZERO_HASH, 'bytes32');
|
|
8543
|
-
const feeRecipient = params.feeRecipient ?? bridgeAddress;
|
|
8544
|
-
const bridgeParams = {
|
|
8545
|
-
amount: params.amount,
|
|
8546
|
-
maxFee: params.maxFee,
|
|
8547
|
-
fee: protocolFee,
|
|
8548
|
-
mintRecipient: convertAddress(params.mintRecipient, 'bytes32'),
|
|
8549
|
-
destinationCaller: destinationCallerAddress,
|
|
8550
|
-
burnToken: params.fromChain.usdcAddress,
|
|
8551
|
-
feeRecipient,
|
|
8552
|
-
destinationDomain: params.toChain.cctp.domain,
|
|
8553
|
-
minFinalityThreshold: BigInt(params.minFinalityThreshold), // Convert to bigint for contract
|
|
8554
|
-
};
|
|
8555
|
-
// Prepare the custom bridge transaction - select method based on whether permit is provided
|
|
8556
|
-
const args = [bridgeParams];
|
|
8557
|
-
let functionName = 'bridgeWithPreapproval';
|
|
8558
|
-
if (params.permitParams) {
|
|
8559
|
-
// Use bridgeWithPermit when permit signature is provided
|
|
8560
|
-
const permitParams = {
|
|
8561
|
-
deadline: params.permitParams.deadline,
|
|
8562
|
-
v: params.permitParams.v,
|
|
8563
|
-
r: params.permitParams.r,
|
|
8564
|
-
s: params.permitParams.s,
|
|
8565
|
-
};
|
|
8566
|
-
args.push(permitParams);
|
|
8567
|
-
functionName = 'bridgeWithPermit';
|
|
8568
|
-
}
|
|
8569
|
-
return adapter.prepare({
|
|
8570
|
-
type: 'evm',
|
|
8571
|
-
abi: bridgeContractAbi,
|
|
8572
|
-
address: bridgeAddress,
|
|
8573
|
-
functionName,
|
|
8574
|
-
args,
|
|
8575
|
-
}, context);
|
|
9211
|
+
const customBurnWithHook = async (params, adapter, context) => {
|
|
9212
|
+
return prepareCustomBurn(params, adapter, context);
|
|
8576
9213
|
};
|
|
8577
9214
|
|
|
8578
9215
|
/**
|
|
@@ -8935,6 +9572,16 @@ const getHandlers = (adapter) => {
|
|
|
8935
9572
|
'cctp.v2.depositForBurn': async (params, context) => {
|
|
8936
9573
|
return depositForBurn(params, adapter, context);
|
|
8937
9574
|
},
|
|
9575
|
+
/**
|
|
9576
|
+
* Handler for CCTP v2 deposit for burn with hook operations on EVM chains.
|
|
9577
|
+
*
|
|
9578
|
+
* Burns USDC tokens with hook data that signals to Circle's Orbit relayer
|
|
9579
|
+
* that forwarding is requested. The relayer will automatically fetch the
|
|
9580
|
+
* attestation and submit the destination mint transaction.
|
|
9581
|
+
*/
|
|
9582
|
+
'cctp.v2.depositForBurnWithHook': async (params, context) => {
|
|
9583
|
+
return depositForBurnWithHook(params, adapter, context);
|
|
9584
|
+
},
|
|
8938
9585
|
/**
|
|
8939
9586
|
* Handler for CCTP v2 receive message operations on EVM chains.
|
|
8940
9587
|
*
|
|
@@ -8955,6 +9602,17 @@ const getHandlers = (adapter) => {
|
|
|
8955
9602
|
'cctp.v2.customBurn': async (params, context) => {
|
|
8956
9603
|
return customBurn(params, adapter, context);
|
|
8957
9604
|
},
|
|
9605
|
+
/**
|
|
9606
|
+
* Handler for CCTP v2 custom burn with hook operations on EVM chains.
|
|
9607
|
+
*
|
|
9608
|
+
* Burns USDC tokens using a custom bridge contract with hook data that
|
|
9609
|
+
* signals to Circle's Orbit relayer that forwarding is requested.
|
|
9610
|
+
* Combines the custom bridge functionality (preapproval/permit) with
|
|
9611
|
+
* automated attestation and destination mint execution.
|
|
9612
|
+
*/
|
|
9613
|
+
'cctp.v2.customBurnWithHook': async (params, context) => {
|
|
9614
|
+
return customBurnWithHook(params, adapter, context);
|
|
9615
|
+
},
|
|
8958
9616
|
/**
|
|
8959
9617
|
* Handler for native token balance operations on EVM chains.
|
|
8960
9618
|
*
|
|
@@ -11021,16 +11679,31 @@ class EthersAdapter extends EvmAdapter {
|
|
|
11021
11679
|
* Simulates a contract function call using Ethers v6 `.staticCall`.
|
|
11022
11680
|
*/
|
|
11023
11681
|
async simulateFunctionCall(contract, functionName, args, chain) {
|
|
11024
|
-
|
|
11025
|
-
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
}
|
|
11682
|
+
// Retry on allowance errors to handle RPC state propagation delays
|
|
11683
|
+
// (e.g., approve tx confirmed but not yet visible in latest block for staticCall)
|
|
11684
|
+
const MAX_SIMULATION_ATTEMPTS = 3;
|
|
11685
|
+
const SIMULATION_RETRY_DELAY_MS = 1000;
|
|
11686
|
+
for (let attempt = 1; attempt <= MAX_SIMULATION_ATTEMPTS; attempt++) {
|
|
11687
|
+
try {
|
|
11688
|
+
const func = contract.getFunction(functionName);
|
|
11689
|
+
await func.staticCall(...args);
|
|
11690
|
+
return;
|
|
11691
|
+
}
|
|
11692
|
+
catch (err) {
|
|
11693
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
11694
|
+
const lowerCaseError = errorMessage.toLowerCase();
|
|
11695
|
+
const isAllowanceError = lowerCaseError.includes('exceeds allowance') ||
|
|
11696
|
+
lowerCaseError.includes('insufficient allowance');
|
|
11697
|
+
if (isAllowanceError && attempt < MAX_SIMULATION_ATTEMPTS) {
|
|
11698
|
+
await new Promise((resolve) => setTimeout(resolve, SIMULATION_RETRY_DELAY_MS * attempt));
|
|
11699
|
+
continue;
|
|
11700
|
+
}
|
|
11701
|
+
// Wrap simulation errors with structured error format
|
|
11702
|
+
throw parseBlockchainError(err, {
|
|
11703
|
+
chain: chain.name,
|
|
11704
|
+
operation: 'simulation',
|
|
11705
|
+
});
|
|
11706
|
+
}
|
|
11034
11707
|
}
|
|
11035
11708
|
}
|
|
11036
11709
|
/**
|
|
@@ -11938,7 +12611,7 @@ const createAdapterFromPrivateKey = createEthersAdapterFromPrivateKey;
|
|
|
11938
12611
|
* const adapter = await createEthersAdapterFromProvider({
|
|
11939
12612
|
* provider: window.ethereum,
|
|
11940
12613
|
* getProvider: ({ chain }) => new JsonRpcProvider(
|
|
11941
|
-
* `https://eth-mainnet.
|
|
12614
|
+
* `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`,
|
|
11942
12615
|
* { name: chain.name, chainId: chain.chainId }
|
|
11943
12616
|
* )
|
|
11944
12617
|
* })
|
|
@@ -12108,7 +12781,7 @@ const createEthersAdapterFromProvider = async (params) => {
|
|
|
12108
12781
|
* const adapter = await createAdapterFromProvider({
|
|
12109
12782
|
* provider: window.ethereum,
|
|
12110
12783
|
* getProvider: ({ chain }) => new JsonRpcProvider(
|
|
12111
|
-
* `https://eth-mainnet.
|
|
12784
|
+
* `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`,
|
|
12112
12785
|
* { name: chain.name, chainId: chain.chainId }
|
|
12113
12786
|
* )
|
|
12114
12787
|
* })
|