@chainlink/ccip-sdk 1.1.1 → 1.2.1
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/dist/api/index.d.ts +165 -15
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +236 -61
- package/dist/api/index.js.map +1 -1
- package/dist/api/types.d.ts +119 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/chain.d.ts +53 -27
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +72 -17
- package/dist/chain.js.map +1 -1
- package/dist/errors/codes.d.ts +1 -0
- package/dist/errors/codes.d.ts.map +1 -1
- package/dist/errors/codes.js +1 -0
- package/dist/errors/codes.js.map +1 -1
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/recovery.d.ts.map +1 -1
- package/dist/errors/recovery.js +1 -0
- package/dist/errors/recovery.js.map +1 -1
- package/dist/errors/specialized.d.ts +21 -0
- package/dist/errors/specialized.d.ts.map +1 -1
- package/dist/errors/specialized.js +31 -1
- package/dist/errors/specialized.js.map +1 -1
- package/dist/evm/abi/OffRamp_2_0.d.ts +18 -17
- package/dist/evm/abi/OffRamp_2_0.d.ts.map +1 -1
- package/dist/evm/abi/OffRamp_2_0.js +19 -21
- package/dist/evm/abi/OffRamp_2_0.js.map +1 -1
- package/dist/evm/abi/TokenPool_2_0.d.ts +0 -4
- package/dist/evm/abi/TokenPool_2_0.d.ts.map +1 -1
- package/dist/evm/abi/TokenPool_2_0.js +0 -1
- package/dist/evm/abi/TokenPool_2_0.js.map +1 -1
- package/dist/evm/gas.d.ts +14 -4
- package/dist/evm/gas.d.ts.map +1 -1
- package/dist/evm/gas.js +7 -6
- package/dist/evm/gas.js.map +1 -1
- package/dist/evm/index.d.ts +39 -8
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +114 -28
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/types.d.ts +1 -1
- package/dist/evm/types.d.ts.map +1 -1
- package/dist/extra-args.d.ts +18 -8
- package/dist/extra-args.d.ts.map +1 -1
- package/dist/extra-args.js +6 -6
- package/dist/extra-args.js.map +1 -1
- package/dist/gas.d.ts +1 -1
- package/dist/gas.d.ts.map +1 -1
- package/dist/gas.js +7 -2
- package/dist/gas.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/offchain.d.ts.map +1 -1
- package/dist/offchain.js +6 -6
- package/dist/offchain.js.map +1 -1
- package/dist/requests.d.ts +11 -5
- package/dist/requests.d.ts.map +1 -1
- package/dist/requests.js +6 -7
- package/dist/requests.js.map +1 -1
- package/dist/solana/index.d.ts +2 -2
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +1 -1
- package/dist/solana/index.js.map +1 -1
- package/dist/solana/utils.js +2 -2
- package/dist/solana/utils.js.map +1 -1
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +1 -1
- package/dist/sui/index.js.map +1 -1
- package/dist/ton/index.d.ts.map +1 -1
- package/dist/ton/index.js +34 -26
- package/dist/ton/index.js.map +1 -1
- package/dist/utils.d.ts +10 -4
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +10 -4
- package/dist/utils.js.map +1 -1
- package/package.json +9 -9
- package/src/api/index.ts +271 -59
- package/src/api/types.ts +126 -1
- package/src/chain.ts +121 -43
- package/src/errors/codes.ts +1 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/recovery.ts +2 -0
- package/src/errors/specialized.ts +33 -1
- package/src/evm/abi/OffRamp_2_0.ts +19 -21
- package/src/evm/abi/TokenPool_2_0.ts +0 -1
- package/src/evm/gas.ts +18 -20
- package/src/evm/index.ts +141 -28
- package/src/evm/types.ts +1 -1
- package/src/extra-args.ts +18 -8
- package/src/gas.ts +8 -3
- package/src/index.ts +4 -0
- package/src/offchain.ts +6 -8
- package/src/requests.ts +19 -12
- package/src/solana/index.ts +3 -1
- package/src/solana/utils.ts +2 -2
- package/src/sui/index.ts +3 -1
- package/src/ton/index.ts +47 -26
- package/src/utils.ts +10 -4
package/src/evm/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
JsonRpcProvider,
|
|
13
13
|
WebSocketProvider,
|
|
14
14
|
ZeroAddress,
|
|
15
|
+
formatUnits,
|
|
15
16
|
getAddress,
|
|
16
17
|
hexlify,
|
|
17
18
|
isBytesLike,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
isHexString,
|
|
20
21
|
keccak256,
|
|
21
22
|
toBeHex,
|
|
23
|
+
toBigInt,
|
|
22
24
|
zeroPadValue,
|
|
23
25
|
} from 'ethers'
|
|
24
26
|
import type { TypedContract } from 'ethers-abitype'
|
|
@@ -47,6 +49,7 @@ import {
|
|
|
47
49
|
CCIPHasherVersionUnsupportedError,
|
|
48
50
|
CCIPLogDataInvalidError,
|
|
49
51
|
CCIPSourceChainUnsupportedError,
|
|
52
|
+
CCIPTokenDecimalsInsufficientError,
|
|
50
53
|
CCIPTokenNotConfiguredError,
|
|
51
54
|
CCIPTokenPoolChainConfigNotFoundError,
|
|
52
55
|
CCIPTransactionNotFoundError,
|
|
@@ -692,7 +695,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
692
695
|
offRampABI = OffRamp_1_6_ABI
|
|
693
696
|
// falls through
|
|
694
697
|
case CCIPVersion.V2_0: {
|
|
695
|
-
offRampABI
|
|
698
|
+
offRampABI ??= OffRamp_2_0_ABI
|
|
696
699
|
const contract = new Contract(
|
|
697
700
|
offRamp,
|
|
698
701
|
offRampABI,
|
|
@@ -970,11 +973,24 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
970
973
|
if (version < CCIPVersion.V1_6)
|
|
971
974
|
throw new CCIPVersionFeatureUnavailableError('feeQuoter', version, 'v1.6')
|
|
972
975
|
|
|
976
|
+
const isOnRamp = type.includes('OnRamp')
|
|
973
977
|
const contract = new Contract(
|
|
974
978
|
address,
|
|
975
|
-
|
|
979
|
+
version < CCIPVersion.V2_0
|
|
980
|
+
? isOnRamp
|
|
981
|
+
? interfaces.OnRamp_v1_6
|
|
982
|
+
: interfaces.OffRamp_v1_6
|
|
983
|
+
: isOnRamp
|
|
984
|
+
? interfaces.OnRamp_v2_0
|
|
985
|
+
: interfaces.OffRamp_v2_0,
|
|
976
986
|
this.provider,
|
|
977
|
-
) as unknown as TypedContract<
|
|
987
|
+
) as unknown as TypedContract<
|
|
988
|
+
| typeof OnRamp_1_6_ABI
|
|
989
|
+
| typeof OffRamp_1_6_ABI
|
|
990
|
+
| typeof OnRamp_2_0_ABI
|
|
991
|
+
| typeof OffRamp_2_0_ABI
|
|
992
|
+
>
|
|
993
|
+
|
|
978
994
|
const { feeQuoter } = await contract.getDynamicConfig()
|
|
979
995
|
return feeQuoter as string
|
|
980
996
|
}
|
|
@@ -1003,9 +1019,16 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1003
1019
|
}
|
|
1004
1020
|
|
|
1005
1021
|
/**
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1022
|
+
* Generates unsigned EVM transactions for sending a CCIP message.
|
|
1023
|
+
*
|
|
1024
|
+
* @param opts - Send message options with sender address for populating transaction fields.
|
|
1025
|
+
* @returns Unsigned EVM transaction set containing 0 or more token approval txs
|
|
1026
|
+
* (if needed at the time of generation), followed by a ccipSend TransactionRequest.
|
|
1027
|
+
*
|
|
1028
|
+
* @remarks
|
|
1029
|
+
* When a token in `tokenAmounts` has `ZeroAddress` as its address, the corresponding
|
|
1030
|
+
* amount is included as native `value` in the `ccipSend` transaction instead of
|
|
1031
|
+
* going through the ERC-20 approve flow.
|
|
1009
1032
|
*/
|
|
1010
1033
|
async generateUnsignedSendMessage(
|
|
1011
1034
|
opts: Parameters<Chain['generateUnsignedSendMessage']>[0],
|
|
@@ -1138,7 +1161,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1138
1161
|
async generateUnsignedExecute(
|
|
1139
1162
|
opts: Parameters<Chain['generateUnsignedExecute']>[0],
|
|
1140
1163
|
): Promise<UnsignedEVMTx> {
|
|
1141
|
-
const { offRamp, input } = await this.resolveExecuteOpts(opts)
|
|
1164
|
+
const { offRamp, input, gasLimit } = await this.resolveExecuteOpts(opts)
|
|
1142
1165
|
if ('verifications' in input) {
|
|
1143
1166
|
const contract = new Contract(
|
|
1144
1167
|
offRamp,
|
|
@@ -1166,14 +1189,14 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1166
1189
|
messageId,
|
|
1167
1190
|
input.verifications.map(({ destAddress }) => destAddress),
|
|
1168
1191
|
input.verifications.map(({ ccvData }) => hexlify(ccvData)),
|
|
1169
|
-
BigInt(
|
|
1192
|
+
BigInt(gasLimit ?? 0),
|
|
1170
1193
|
{ from: offRamp }, // internal method
|
|
1171
1194
|
)
|
|
1172
1195
|
const execTx = await contract.execute.populateTransaction(
|
|
1173
1196
|
input.encodedMessage,
|
|
1174
1197
|
input.verifications.map(({ destAddress }) => destAddress),
|
|
1175
1198
|
input.verifications.map(({ ccvData }) => hexlify(ccvData)),
|
|
1176
|
-
BigInt(
|
|
1199
|
+
BigInt(gasLimit ?? 0),
|
|
1177
1200
|
)
|
|
1178
1201
|
execTx.gasLimit = txGasLimit + 40000n // plus `execute`'s overhead
|
|
1179
1202
|
return { family: ChainFamily.EVM, transactions: [execTx] }
|
|
@@ -1190,7 +1213,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1190
1213
|
interfaces.EVM2EVMOffRamp_v1_2,
|
|
1191
1214
|
this.provider,
|
|
1192
1215
|
) as unknown as TypedContract<typeof EVM2EVMOffRamp_1_2_ABI>
|
|
1193
|
-
const gasOverride = BigInt(
|
|
1216
|
+
const gasOverride = BigInt(gasLimit ?? 0)
|
|
1194
1217
|
manualExecTx = await contract.manuallyExecute.populateTransaction(
|
|
1195
1218
|
{
|
|
1196
1219
|
...input,
|
|
@@ -1217,9 +1240,9 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1217
1240
|
},
|
|
1218
1241
|
[
|
|
1219
1242
|
{
|
|
1220
|
-
receiverExecutionGasLimit: BigInt(
|
|
1243
|
+
receiverExecutionGasLimit: BigInt(gasLimit ?? 0),
|
|
1221
1244
|
tokenGasOverrides: input.message.tokenAmounts.map(() =>
|
|
1222
|
-
BigInt(opts.tokensGasLimit ??
|
|
1245
|
+
BigInt(opts.tokensGasLimit ?? gasLimit ?? 0),
|
|
1223
1246
|
),
|
|
1224
1247
|
},
|
|
1225
1248
|
],
|
|
@@ -1272,9 +1295,9 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1272
1295
|
[
|
|
1273
1296
|
[
|
|
1274
1297
|
{
|
|
1275
|
-
receiverExecutionGasLimit: BigInt(
|
|
1298
|
+
receiverExecutionGasLimit: BigInt(gasLimit ?? 0),
|
|
1276
1299
|
tokenGasOverrides: input.message.tokenAmounts.map(() =>
|
|
1277
|
-
BigInt(opts.tokensGasLimit ??
|
|
1300
|
+
BigInt(opts.tokensGasLimit ?? gasLimit ?? 0),
|
|
1278
1301
|
),
|
|
1279
1302
|
},
|
|
1280
1303
|
],
|
|
@@ -1288,27 +1311,27 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1288
1311
|
|
|
1289
1312
|
/* Executing a message for the first time has some hard try/catches on-chain
|
|
1290
1313
|
* so we need to ensure some lower-bounds gasLimits */
|
|
1291
|
-
let
|
|
1314
|
+
let txGasLimit = await this.provider.estimateGas(manualExecTx)
|
|
1292
1315
|
if (
|
|
1293
1316
|
'gasLimit' in input.message &&
|
|
1294
1317
|
input.message.gasLimit &&
|
|
1295
|
-
|
|
1318
|
+
txGasLimit < input.message.gasLimit + 100000n
|
|
1296
1319
|
)
|
|
1297
1320
|
// if message requested gasLimit, ensure execution more than 100k above requested, otherwise it's clearly a try/catch fail
|
|
1298
|
-
|
|
1299
|
-
else if ('gasLimit' in input.message && !input.message.gasLimit &&
|
|
1321
|
+
txGasLimit = BigInt(input.message.gasLimit) + 200000n
|
|
1322
|
+
else if ('gasLimit' in input.message && !input.message.gasLimit && txGasLimit < 240000n)
|
|
1300
1323
|
// if message didn't request gasLimit, ensure execution gasLimit is above 240k (empiric)
|
|
1301
|
-
|
|
1302
|
-
manualExecTx.gasLimit =
|
|
1324
|
+
txGasLimit = 240000n
|
|
1325
|
+
manualExecTx.gasLimit = txGasLimit
|
|
1303
1326
|
|
|
1304
1327
|
return { family: ChainFamily.EVM, transactions: [manualExecTx] }
|
|
1305
1328
|
}
|
|
1306
1329
|
|
|
1307
1330
|
/**
|
|
1308
1331
|
* {@inheritDoc Chain.execute}
|
|
1309
|
-
* @throws {@link CCIPWalletInvalidError} if wallet is not a valid Signer
|
|
1310
|
-
* @throws {@link CCIPExecTxNotConfirmedError} if execution transaction fails to confirm
|
|
1311
|
-
* @throws {@link CCIPExecTxRevertedError} if execution transaction reverts
|
|
1332
|
+
* @throws {@link CCIPWalletInvalidError} if wallet is not a valid Signer.
|
|
1333
|
+
* @throws {@link CCIPExecTxNotConfirmedError} if execution transaction fails to confirm.
|
|
1334
|
+
* @throws {@link CCIPExecTxRevertedError} if execution transaction reverts.
|
|
1312
1335
|
*/
|
|
1313
1336
|
async execute(opts: Parameters<Chain['execute']>[0]) {
|
|
1314
1337
|
const wallet = opts.wallet
|
|
@@ -1399,7 +1422,16 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1399
1422
|
}
|
|
1400
1423
|
}
|
|
1401
1424
|
|
|
1402
|
-
/**
|
|
1425
|
+
/**
|
|
1426
|
+
* Fetches the token pool configuration for an EVM token pool contract.
|
|
1427
|
+
*
|
|
1428
|
+
* @param tokenPool - Token pool contract address.
|
|
1429
|
+
* @returns Token pool config containing token, router, typeAndVersion, and optionally minBlockConfirmations.
|
|
1430
|
+
*
|
|
1431
|
+
* @remarks
|
|
1432
|
+
* For pools with version \>= 2.0, also returns `minBlockConfirmations` for
|
|
1433
|
+
* Faster-Than-Finality (FTF) support. Pre-2.0 pools omit this field.
|
|
1434
|
+
*/
|
|
1403
1435
|
async getTokenPoolConfig(tokenPool: string): Promise<{
|
|
1404
1436
|
token: string
|
|
1405
1437
|
router: string
|
|
@@ -1444,7 +1476,22 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1444
1476
|
)
|
|
1445
1477
|
}
|
|
1446
1478
|
|
|
1447
|
-
/**
|
|
1479
|
+
/**
|
|
1480
|
+
* Fetches remote chain configurations for an EVM token pool contract.
|
|
1481
|
+
*
|
|
1482
|
+
* @param tokenPool - Token pool address on the current chain.
|
|
1483
|
+
* @param remoteChainSelector - Optional chain selector to filter results to a single destination.
|
|
1484
|
+
* @returns Record mapping chain names to {@link TokenPoolRemote} configs.
|
|
1485
|
+
*
|
|
1486
|
+
* @remarks
|
|
1487
|
+
* Handles 3 pool version branches:
|
|
1488
|
+
* - v1.5: single remote pool via `getRemotePool`, standard rate limiters.
|
|
1489
|
+
* - v1.6: multiple remote pools via `getRemotePools`, standard rate limiters.
|
|
1490
|
+
* - v2.0+: multiple remote pools plus FTF (Faster-Than-Finality) rate limiters
|
|
1491
|
+
* (`customBlockConfirmationsOutboundRateLimiterState` / `customBlockConfirmationsInboundRateLimiterState`).
|
|
1492
|
+
*
|
|
1493
|
+
* @throws {@link CCIPTokenPoolChainConfigNotFoundError} if remote token is not configured for a chain.
|
|
1494
|
+
*/
|
|
1448
1495
|
async getTokenPoolRemotes(
|
|
1449
1496
|
tokenPool: string,
|
|
1450
1497
|
remoteChainSelector?: bigint,
|
|
@@ -1729,10 +1776,76 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1729
1776
|
override async estimateReceiveExecution(
|
|
1730
1777
|
opts: Parameters<NonNullable<Chain['estimateReceiveExecution']>>[0],
|
|
1731
1778
|
): Promise<number> {
|
|
1779
|
+
const convertAmounts = (
|
|
1780
|
+
tokenAmounts?: readonly ((
|
|
1781
|
+
| { token: string }
|
|
1782
|
+
| { destTokenAddress: string; extraData?: string }
|
|
1783
|
+
) & {
|
|
1784
|
+
amount: bigint
|
|
1785
|
+
})[],
|
|
1786
|
+
) =>
|
|
1787
|
+
!tokenAmounts
|
|
1788
|
+
? undefined
|
|
1789
|
+
: Promise.all(
|
|
1790
|
+
tokenAmounts.map(async (ta) => {
|
|
1791
|
+
if (!('destTokenAddress' in ta)) return ta
|
|
1792
|
+
let amount = ta.amount
|
|
1793
|
+
if (isHexString(ta.extraData, 32)) {
|
|
1794
|
+
// extraData is source token decimals in most pools derived from standard TP contracts;
|
|
1795
|
+
// we can identify for it being exactly 32B and being a small integer; otherwise, assume same decimals
|
|
1796
|
+
const sourceDecimals = toBigInt(ta.extraData)
|
|
1797
|
+
if (0 < sourceDecimals && sourceDecimals <= 36) {
|
|
1798
|
+
const { decimals: destDecimals } = await this.getTokenInfo(ta.destTokenAddress)
|
|
1799
|
+
amount =
|
|
1800
|
+
(amount * BigInt(10) ** BigInt(destDecimals)) /
|
|
1801
|
+
BigInt(10) ** BigInt(sourceDecimals)
|
|
1802
|
+
if (amount === 0n)
|
|
1803
|
+
throw new CCIPTokenDecimalsInsufficientError(
|
|
1804
|
+
ta.destTokenAddress,
|
|
1805
|
+
destDecimals,
|
|
1806
|
+
this.network.name,
|
|
1807
|
+
formatUnits(amount, sourceDecimals),
|
|
1808
|
+
)
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
return { token: ta.destTokenAddress, amount }
|
|
1812
|
+
}),
|
|
1813
|
+
)
|
|
1814
|
+
|
|
1815
|
+
let opts_
|
|
1816
|
+
if (!('offRamp' in opts)) {
|
|
1817
|
+
const { lane, message, metadata } = await this.getMessageById(opts.messageId)
|
|
1818
|
+
|
|
1819
|
+
const offRamp =
|
|
1820
|
+
('offRampAddress' in message && message.offRampAddress) ||
|
|
1821
|
+
metadata?.offRamp ||
|
|
1822
|
+
(await this.apiClient!.getExecutionInput(opts.messageId)).offRamp
|
|
1823
|
+
|
|
1824
|
+
opts_ = {
|
|
1825
|
+
offRamp,
|
|
1826
|
+
message: {
|
|
1827
|
+
sourceChainSelector: lane.sourceChainSelector,
|
|
1828
|
+
messageId: message.messageId,
|
|
1829
|
+
receiver: message.receiver,
|
|
1830
|
+
sender: message.sender,
|
|
1831
|
+
data: message.data,
|
|
1832
|
+
destTokenAmounts: await convertAmounts(message.tokenAmounts),
|
|
1833
|
+
},
|
|
1834
|
+
}
|
|
1835
|
+
} else {
|
|
1836
|
+
opts_ = {
|
|
1837
|
+
...opts,
|
|
1838
|
+
message: {
|
|
1839
|
+
...opts.message,
|
|
1840
|
+
destTokenAmounts: await convertAmounts(opts.message.destTokenAmounts),
|
|
1841
|
+
},
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1732
1845
|
const destRouter = await this.getRouterForOffRamp(
|
|
1733
|
-
|
|
1734
|
-
|
|
1846
|
+
opts_.offRamp,
|
|
1847
|
+
opts_.message.sourceChainSelector,
|
|
1735
1848
|
)
|
|
1736
|
-
return estimateExecGas({ provider: this.provider, router: destRouter, ...
|
|
1849
|
+
return estimateExecGas({ provider: this.provider, router: destRouter, ...opts_ })
|
|
1737
1850
|
}
|
|
1738
1851
|
}
|
package/src/evm/types.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { ChainFamily } from '../types.ts'
|
|
|
7
7
|
*/
|
|
8
8
|
export type UnsignedEVMTx = {
|
|
9
9
|
family: typeof ChainFamily.EVM
|
|
10
|
-
transactions: Pick<TransactionRequest, 'from' | 'to' | 'data' | 'gasLimit'>[]
|
|
10
|
+
transactions: Pick<TransactionRequest, 'from' | 'to' | 'data' | 'gasLimit' | 'value'>[]
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
package/src/extra-args.ts
CHANGED
|
@@ -68,7 +68,7 @@ export type EVMExtraArgsV2 = EVMExtraArgsV1 & {
|
|
|
68
68
|
export type GenericExtraArgsV3 = {
|
|
69
69
|
/** Gas limit for execution on the destination chain (uint32). */
|
|
70
70
|
gasLimit: bigint
|
|
71
|
-
/** Number of block confirmations
|
|
71
|
+
/** Number of source-chain block confirmations to wait before relaying the message. */
|
|
72
72
|
blockConfirmations: number
|
|
73
73
|
/** Cross-chain verifier addresses (EVM addresses). */
|
|
74
74
|
ccvs: string[]
|
|
@@ -132,7 +132,17 @@ export type SuiExtraArgsV1 = EVMExtraArgsV2 & {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
|
-
* Union
|
|
135
|
+
* Union of all supported extra arguments formats for CCIP messages.
|
|
136
|
+
*
|
|
137
|
+
* The SDK auto-detects the correct variant based on the fields provided:
|
|
138
|
+
* - {@link EVMExtraArgsV1} - EVM legacy (gasLimit only)
|
|
139
|
+
* - {@link EVMExtraArgsV2} - EVM with out-of-order execution support
|
|
140
|
+
* - {@link GenericExtraArgsV3} - Generic V3 with minimum block confirmations, cross-chain verifiers, custom executor, and per-token receiver/args
|
|
141
|
+
* - {@link SVMExtraArgsV1} - Solana (compute units, accounts)
|
|
142
|
+
* - {@link SuiExtraArgsV1} - Sui (gas limit, receiver object IDs)
|
|
143
|
+
*
|
|
144
|
+
* @see {@link encodeExtraArgs} - Encode extra arguments for on-chain use.
|
|
145
|
+
* @see {@link decodeExtraArgs} - Decode extra arguments from bytes.
|
|
136
146
|
*/
|
|
137
147
|
export type ExtraArgs =
|
|
138
148
|
| EVMExtraArgsV1
|
|
@@ -146,10 +156,10 @@ export type ExtraArgs =
|
|
|
146
156
|
* The args are *to* a dest network, but are encoded as a message *from* this source chain.
|
|
147
157
|
* E.g. Solana uses Borsh to encode extraArgs in its produced requests, even those targeting EVM.
|
|
148
158
|
*
|
|
149
|
-
* @param args - Extra arguments to encode
|
|
150
|
-
* @param from - Source chain family for encoding format (defaults to EVM)
|
|
151
|
-
* @returns Encoded extra arguments as hex string
|
|
152
|
-
* @throws {@link CCIPChainFamilyUnsupportedError} if chain family not supported
|
|
159
|
+
* @param args - Extra arguments to encode.
|
|
160
|
+
* @param from - Source chain family for encoding format (defaults to EVM).
|
|
161
|
+
* @returns Encoded extra arguments as hex string.
|
|
162
|
+
* @throws {@link CCIPChainFamilyUnsupportedError} if chain family not supported.
|
|
153
163
|
*
|
|
154
164
|
* @example
|
|
155
165
|
* ```typescript
|
|
@@ -175,8 +185,8 @@ export function encodeExtraArgs(args: ExtraArgs, from: ChainFamily = ChainFamily
|
|
|
175
185
|
* @param data - Extra arguments bytearray data.
|
|
176
186
|
* @param from - Optional chain family to narrow decoding attempts.
|
|
177
187
|
* @returns Extra arguments object if found, undefined otherwise.
|
|
178
|
-
* @throws {@link CCIPChainFamilyUnsupportedError} if specified chain family not supported
|
|
179
|
-
* @throws {@link CCIPExtraArgsParseError} if data cannot be parsed as valid extra args
|
|
188
|
+
* @throws {@link CCIPChainFamilyUnsupportedError} if specified chain family not supported.
|
|
189
|
+
* @throws {@link CCIPExtraArgsParseError} if data cannot be parsed as valid extra args.
|
|
180
190
|
*
|
|
181
191
|
* @example
|
|
182
192
|
* ```typescript
|
package/src/gas.ts
CHANGED
|
@@ -28,7 +28,7 @@ export type EstimateMessageInput = {
|
|
|
28
28
|
data?: BytesLike
|
|
29
29
|
/**
|
|
30
30
|
* optional tokenAmounts; `amount` with either source `token` (as in MessageInput) or
|
|
31
|
-
* `{ sourceTokenAddress?, sourcePoolAddress, destTokenAddress }` (as in v1.5..
|
|
31
|
+
* `{ sourceTokenAddress?, sourcePoolAddress, destTokenAddress }` (as in v1.5..v2.0 tokenAmounts)
|
|
32
32
|
* can be provided
|
|
33
33
|
*/
|
|
34
34
|
tokenAmounts?: readonly ({
|
|
@@ -122,7 +122,12 @@ export async function estimateReceiveExecution({
|
|
|
122
122
|
const tokenAmount =
|
|
123
123
|
'destTokenAddress' in ta
|
|
124
124
|
? ta
|
|
125
|
-
: await sourceToDestTokenAddresses(
|
|
125
|
+
: await sourceToDestTokenAddresses({
|
|
126
|
+
source,
|
|
127
|
+
onRamp,
|
|
128
|
+
destChainSelector: dest.network.chainSelector,
|
|
129
|
+
sourceTokenAmount: ta,
|
|
130
|
+
})
|
|
126
131
|
const sourceTokenAddress =
|
|
127
132
|
'token' in ta
|
|
128
133
|
? ta.token
|
|
@@ -147,10 +152,10 @@ export async function estimateReceiveExecution({
|
|
|
147
152
|
}),
|
|
148
153
|
)
|
|
149
154
|
return dest.estimateReceiveExecution({
|
|
150
|
-
receiver: message.receiver,
|
|
151
155
|
offRamp,
|
|
152
156
|
message: {
|
|
153
157
|
messageId: message.messageId ?? hexlify(randomBytes(32)),
|
|
158
|
+
receiver: message.receiver,
|
|
154
159
|
sender: message.sender,
|
|
155
160
|
data: message.data,
|
|
156
161
|
sourceChainSelector: source.network.chainSelector,
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,9 @@ export type {
|
|
|
12
12
|
APIErrorResponse,
|
|
13
13
|
CCIPAPIClientContext,
|
|
14
14
|
LaneLatencyResponse,
|
|
15
|
+
MessageSearchFilters,
|
|
16
|
+
MessageSearchPage,
|
|
17
|
+
MessageSearchResult,
|
|
15
18
|
} from './api/index.ts'
|
|
16
19
|
export {
|
|
17
20
|
CCIPAPIClient,
|
|
@@ -41,6 +44,7 @@ export {
|
|
|
41
44
|
type EVMExtraArgsV1,
|
|
42
45
|
type EVMExtraArgsV2,
|
|
43
46
|
type ExtraArgs,
|
|
47
|
+
type GenericExtraArgsV3,
|
|
44
48
|
type SVMExtraArgsV1,
|
|
45
49
|
type SuiExtraArgsV1,
|
|
46
50
|
decodeExtraArgs,
|
package/src/offchain.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BytesLike, dataLength, dataSlice,
|
|
1
|
+
import { type BytesLike, dataLength, dataSlice, toNumber } from 'ethers'
|
|
2
2
|
import type { PickDeep } from 'type-fest'
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from './errors/index.ts'
|
|
9
9
|
import { parseSourceTokenData } from './evm/messages.ts'
|
|
10
10
|
import { type CCIPRequest, type OffchainTokenData, type WithLogger, NetworkType } from './types.ts'
|
|
11
|
-
import { networkInfo } from './utils.ts'
|
|
11
|
+
import { getDataBytes, networkInfo } from './utils.ts'
|
|
12
12
|
|
|
13
13
|
const CIRCLE_API_URL = {
|
|
14
14
|
mainnet: 'https://iris-api.circle.com',
|
|
@@ -122,7 +122,7 @@ export async function getOffchainTokenData(
|
|
|
122
122
|
const { networkType } = networkInfo(request.message.sourceChainSelector)
|
|
123
123
|
|
|
124
124
|
function looksUsdcData(extraData: BytesLike) {
|
|
125
|
-
if (
|
|
125
|
+
if (getDataBytes(extraData).length !== 64) return
|
|
126
126
|
// USDCTokenPool's extraData is a packed `SourceTokenDataPayloadV1{uint64 nonce, uint32 sourceDomain}`,
|
|
127
127
|
// which we need to query CCTPv2 (by sourceDomain and txHash) and to filter by nonce among messages,
|
|
128
128
|
// if more than one in tx
|
|
@@ -139,11 +139,9 @@ export async function getOffchainTokenData(
|
|
|
139
139
|
|
|
140
140
|
function looksLbtcData(extraData: BytesLike) {
|
|
141
141
|
// LBTC returns `message_hash`/`payloadHash` directly as `bytes32 extraData`
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
)
|
|
146
|
-
return true
|
|
142
|
+
const bytes = getDataBytes(extraData)
|
|
143
|
+
// looks like a hash
|
|
144
|
+
if (bytes.length === 32 && bytes.filter(Boolean).length > 20) return true
|
|
147
145
|
}
|
|
148
146
|
|
|
149
147
|
return Promise.all(
|
package/src/requests.ts
CHANGED
|
@@ -89,9 +89,9 @@ function decodeJsonMessage(data: Record<string, unknown> | undefined) {
|
|
|
89
89
|
k?.match(/(selector|amount|nonce|number|limit|bitmap|juels)$/i)
|
|
90
90
|
? BigInt(v as string | number | bigint)
|
|
91
91
|
: k?.match(/(^dest.*address)|(receiver|offramp|accounts)/i)
|
|
92
|
-
? decodeAddress(v as BytesLike, destFamily)
|
|
92
|
+
? decodeAddress((typeof v === 'bigint' ? v.toString() : v) as BytesLike, destFamily)
|
|
93
93
|
: k?.match(/((source.*address)|sender|issuer|origin|onramp|(feetoken$)|(token.*address$))/i)
|
|
94
|
-
? decodeAddress(v as BytesLike, sourceFamily)
|
|
94
|
+
? decodeAddress((typeof v === 'bigint' ? v.toString() : v) as BytesLike, sourceFamily)
|
|
95
95
|
: v instanceof Uint8Array ||
|
|
96
96
|
(Array.isArray(v) && v.length >= 4 && v.every((e) => typeof e === 'number'))
|
|
97
97
|
? hexlify(getDataBytes(v as readonly number[]))
|
|
@@ -197,6 +197,7 @@ export function decodeMessage(data: string | Uint8Array | Record<string, unknown
|
|
|
197
197
|
*/
|
|
198
198
|
export function buildMessageForDest(message: MessageInput, dest: ChainFamily): AnyMessage {
|
|
199
199
|
const chain = supportedChains[dest] ?? Chain
|
|
200
|
+
if (message.extraArgs && '_tag' in message.extraArgs) delete message.extraArgs._tag
|
|
200
201
|
return chain.buildMessageForDest(message)
|
|
201
202
|
}
|
|
202
203
|
|
|
@@ -456,10 +457,7 @@ export async function* getMessagesForSender(
|
|
|
456
457
|
* Resolves token routing by querying the TokenAdminRegistry and TokenPool
|
|
457
458
|
* to find the corresponding destination chain token.
|
|
458
459
|
*
|
|
459
|
-
* @param
|
|
460
|
-
* @param destChainSelector - Destination chain selector
|
|
461
|
-
* @param onRamp - OnRamp contract address
|
|
462
|
-
* @param sourceTokenAmount - Token amount object containing `token` and `amount`
|
|
460
|
+
* @param opts - options to convert source to dest token addresses
|
|
463
461
|
* @returns Extended token amount with `sourcePoolAddress`, `sourceTokenAddress`, and `destTokenAddress`
|
|
464
462
|
*
|
|
465
463
|
* @throws {@link CCIPTokenNotInRegistryError} if token is not registered in TokenAdminRegistry
|
|
@@ -479,12 +477,21 @@ export async function* getMessagesForSender(
|
|
|
479
477
|
* console.log(`Dest token: ${tokenAmount.destTokenAddress}`)
|
|
480
478
|
* ```
|
|
481
479
|
*/
|
|
482
|
-
export async function sourceToDestTokenAddresses<S extends { token: string }>(
|
|
483
|
-
source
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
sourceTokenAmount
|
|
487
|
-
|
|
480
|
+
export async function sourceToDestTokenAddresses<S extends { token: string }>({
|
|
481
|
+
source,
|
|
482
|
+
onRamp,
|
|
483
|
+
destChainSelector,
|
|
484
|
+
sourceTokenAmount,
|
|
485
|
+
}: {
|
|
486
|
+
/** Source chain instance */
|
|
487
|
+
source: Chain
|
|
488
|
+
/** OnRamp contract address */
|
|
489
|
+
onRamp: string
|
|
490
|
+
/** Destination chain selector */
|
|
491
|
+
destChainSelector: bigint
|
|
492
|
+
/** Token amount object containing `token` and `amount` */
|
|
493
|
+
sourceTokenAmount: S
|
|
494
|
+
}): Promise<
|
|
488
495
|
S & {
|
|
489
496
|
sourcePoolAddress: string
|
|
490
497
|
sourceTokenAddress: string
|
package/src/solana/index.ts
CHANGED
|
@@ -1601,7 +1601,9 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
|
|
|
1601
1601
|
'accountIsWritableBitmap',
|
|
1602
1602
|
])
|
|
1603
1603
|
if (message.extraArgs) {
|
|
1604
|
-
const unknown = Object.keys(message.extraArgs).filter(
|
|
1604
|
+
const unknown = Object.keys(message.extraArgs).filter(
|
|
1605
|
+
(k) => k !== '_tag' && !SVM_EXTRA_ARGS_FIELDS.has(k),
|
|
1606
|
+
)
|
|
1605
1607
|
if (unknown.length)
|
|
1606
1608
|
throw new CCIPArgumentInvalidError(
|
|
1607
1609
|
'extraArgs',
|
package/src/solana/utils.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
CCIPTransactionNotFinalizedError,
|
|
28
28
|
} from '../errors/index.ts'
|
|
29
29
|
import type { ChainLog, WithLogger } from '../types.ts'
|
|
30
|
-
import { getDataBytes, sleep } from '../utils.ts'
|
|
30
|
+
import { bigIntReplacer, getDataBytes, sleep } from '../utils.ts'
|
|
31
31
|
import type { IDL as BASE_TOKEN_POOL_IDL } from './idl/1.6.0/BASE_TOKEN_POOL.ts'
|
|
32
32
|
import type { UnsignedSolanaTx, Wallet } from './types.ts'
|
|
33
33
|
import type { RateLimiterState } from '../chain.ts'
|
|
@@ -347,7 +347,7 @@ export async function simulateTransaction(
|
|
|
347
347
|
throw new SendTransactionError({
|
|
348
348
|
action: 'simulate',
|
|
349
349
|
signature: '',
|
|
350
|
-
transactionMessage: JSON.stringify(result.value.err),
|
|
350
|
+
transactionMessage: JSON.stringify(result.value.err, bigIntReplacer),
|
|
351
351
|
logs: result.value.logs!,
|
|
352
352
|
})
|
|
353
353
|
}
|
package/src/sui/index.ts
CHANGED
|
@@ -826,7 +826,9 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
826
826
|
'accounts', // alias for receiverObjectIds
|
|
827
827
|
])
|
|
828
828
|
if (message.extraArgs) {
|
|
829
|
-
const unknown = Object.keys(message.extraArgs).filter(
|
|
829
|
+
const unknown = Object.keys(message.extraArgs).filter(
|
|
830
|
+
(k) => k !== '_tag' && !SUI_EXTRA_ARGS_FIELDS.has(k),
|
|
831
|
+
)
|
|
830
832
|
if (unknown.length)
|
|
831
833
|
throw new CCIPArgumentInvalidError(
|
|
832
834
|
'extraArgs',
|
package/src/ton/index.ts
CHANGED
|
@@ -3,7 +3,18 @@ import { Buffer } from 'buffer'
|
|
|
3
3
|
import { type Transaction, Address, Cell, beginCell, toNano } from '@ton/core'
|
|
4
4
|
import { TonClient } from '@ton/ton'
|
|
5
5
|
import { type AxiosAdapter, getAdapter } from 'axios'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
type BytesLike,
|
|
8
|
+
concat,
|
|
9
|
+
dataLength,
|
|
10
|
+
dataSlice,
|
|
11
|
+
hexlify,
|
|
12
|
+
isBytesLike,
|
|
13
|
+
isHexString,
|
|
14
|
+
toBeArray,
|
|
15
|
+
toBeHex,
|
|
16
|
+
toBigInt,
|
|
17
|
+
} from 'ethers'
|
|
7
18
|
import { type Memoized, memoize } from 'micro-memoize'
|
|
8
19
|
import type { PickDeep } from 'type-fest'
|
|
9
20
|
|
|
@@ -45,6 +56,7 @@ import {
|
|
|
45
56
|
bytesToBuffer,
|
|
46
57
|
createRateLimitedFetch,
|
|
47
58
|
decodeAddress,
|
|
59
|
+
getDataBytes,
|
|
48
60
|
networkInfo,
|
|
49
61
|
parseTypeAndVersion,
|
|
50
62
|
sleep,
|
|
@@ -64,6 +76,16 @@ function isTvmError(error: unknown): error is Error & { exitCode: number } {
|
|
|
64
76
|
return error instanceof Error && 'exitCode' in error && typeof error.exitCode === 'number'
|
|
65
77
|
}
|
|
66
78
|
|
|
79
|
+
function bitsToBytes(bits: string): Uint8Array {
|
|
80
|
+
return Uint8Array.from(bits.match(/.{1,8}/g)!.map((byte) => parseInt(byte.padEnd(8, '0'), 2)))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function bytesToBits(bytes: Uint8Array): string {
|
|
84
|
+
return Array.from(bytes)
|
|
85
|
+
.map((B) => B.toString(2).padStart(8, '0'))
|
|
86
|
+
.join('')
|
|
87
|
+
}
|
|
88
|
+
|
|
67
89
|
/**
|
|
68
90
|
* TON chain implementation supporting TON networks.
|
|
69
91
|
*
|
|
@@ -702,14 +724,14 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
702
724
|
*/
|
|
703
725
|
static encodeExtraArgs(args: ExtraArgs): string {
|
|
704
726
|
if ('gasLimit' in args && 'allowOutOfOrderExecution' in args) {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
727
|
+
return concat([
|
|
728
|
+
EVMExtraArgsV2Tag,
|
|
729
|
+
bitsToBytes(
|
|
730
|
+
'1' +
|
|
731
|
+
bytesToBits(toBeArray(args.gasLimit, 32)) +
|
|
732
|
+
(args.allowOutOfOrderExecution ? '1' : '0'),
|
|
733
|
+
),
|
|
734
|
+
])
|
|
713
735
|
}
|
|
714
736
|
return '0x'
|
|
715
737
|
}
|
|
@@ -729,25 +751,24 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
729
751
|
static decodeExtraArgs(
|
|
730
752
|
extraArgs: BytesLike,
|
|
731
753
|
): (EVMExtraArgsV2 & { _tag: 'EVMExtraArgsV2' }) | undefined {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
754
|
+
if (dataSlice(extraArgs, 0, 4) !== EVMExtraArgsV2Tag || dataLength(extraArgs) < 5) return
|
|
755
|
+
const data = getDataBytes(extraArgs).subarray(4)
|
|
756
|
+
let bits = bytesToBits(data)
|
|
757
|
+
let gasLimit = 0n
|
|
758
|
+
if (bits[0] === '1') {
|
|
759
|
+
if (bits.length < 1 + 32 * 8) return
|
|
760
|
+
gasLimit = toBigInt(bitsToBytes(bits.substring(1, 1 + 32 * 8)))
|
|
761
|
+
bits = bits.substring(1 + 32 * 8)
|
|
762
|
+
} else {
|
|
763
|
+
bits = bits.substring(1)
|
|
764
|
+
}
|
|
738
765
|
|
|
739
|
-
|
|
740
|
-
const magicTag = slice.loadUint(32)
|
|
741
|
-
if (magicTag !== Number(EVMExtraArgsV2Tag)) return undefined
|
|
766
|
+
const allowOutOfOrderExecution = bits[0] === '1'
|
|
742
767
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
} catch {
|
|
749
|
-
// Return undefined for any parsing errors (invalid BOC, malformed data, etc.)
|
|
750
|
-
return undefined
|
|
768
|
+
return {
|
|
769
|
+
_tag: 'EVMExtraArgsV2',
|
|
770
|
+
gasLimit,
|
|
771
|
+
allowOutOfOrderExecution,
|
|
751
772
|
}
|
|
752
773
|
}
|
|
753
774
|
|