@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.
Files changed (100) hide show
  1. package/dist/api/index.d.ts +165 -15
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +236 -61
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/types.d.ts +119 -1
  6. package/dist/api/types.d.ts.map +1 -1
  7. package/dist/chain.d.ts +53 -27
  8. package/dist/chain.d.ts.map +1 -1
  9. package/dist/chain.js +72 -17
  10. package/dist/chain.js.map +1 -1
  11. package/dist/errors/codes.d.ts +1 -0
  12. package/dist/errors/codes.d.ts.map +1 -1
  13. package/dist/errors/codes.js +1 -0
  14. package/dist/errors/codes.js.map +1 -1
  15. package/dist/errors/index.d.ts +1 -1
  16. package/dist/errors/index.d.ts.map +1 -1
  17. package/dist/errors/index.js +1 -1
  18. package/dist/errors/index.js.map +1 -1
  19. package/dist/errors/recovery.d.ts.map +1 -1
  20. package/dist/errors/recovery.js +1 -0
  21. package/dist/errors/recovery.js.map +1 -1
  22. package/dist/errors/specialized.d.ts +21 -0
  23. package/dist/errors/specialized.d.ts.map +1 -1
  24. package/dist/errors/specialized.js +31 -1
  25. package/dist/errors/specialized.js.map +1 -1
  26. package/dist/evm/abi/OffRamp_2_0.d.ts +18 -17
  27. package/dist/evm/abi/OffRamp_2_0.d.ts.map +1 -1
  28. package/dist/evm/abi/OffRamp_2_0.js +19 -21
  29. package/dist/evm/abi/OffRamp_2_0.js.map +1 -1
  30. package/dist/evm/abi/TokenPool_2_0.d.ts +0 -4
  31. package/dist/evm/abi/TokenPool_2_0.d.ts.map +1 -1
  32. package/dist/evm/abi/TokenPool_2_0.js +0 -1
  33. package/dist/evm/abi/TokenPool_2_0.js.map +1 -1
  34. package/dist/evm/gas.d.ts +14 -4
  35. package/dist/evm/gas.d.ts.map +1 -1
  36. package/dist/evm/gas.js +7 -6
  37. package/dist/evm/gas.js.map +1 -1
  38. package/dist/evm/index.d.ts +39 -8
  39. package/dist/evm/index.d.ts.map +1 -1
  40. package/dist/evm/index.js +114 -28
  41. package/dist/evm/index.js.map +1 -1
  42. package/dist/evm/types.d.ts +1 -1
  43. package/dist/evm/types.d.ts.map +1 -1
  44. package/dist/extra-args.d.ts +18 -8
  45. package/dist/extra-args.d.ts.map +1 -1
  46. package/dist/extra-args.js +6 -6
  47. package/dist/extra-args.js.map +1 -1
  48. package/dist/gas.d.ts +1 -1
  49. package/dist/gas.d.ts.map +1 -1
  50. package/dist/gas.js +7 -2
  51. package/dist/gas.js.map +1 -1
  52. package/dist/index.d.ts +2 -2
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/offchain.d.ts.map +1 -1
  56. package/dist/offchain.js +6 -6
  57. package/dist/offchain.js.map +1 -1
  58. package/dist/requests.d.ts +11 -5
  59. package/dist/requests.d.ts.map +1 -1
  60. package/dist/requests.js +6 -7
  61. package/dist/requests.js.map +1 -1
  62. package/dist/solana/index.d.ts +2 -2
  63. package/dist/solana/index.d.ts.map +1 -1
  64. package/dist/solana/index.js +1 -1
  65. package/dist/solana/index.js.map +1 -1
  66. package/dist/solana/utils.js +2 -2
  67. package/dist/solana/utils.js.map +1 -1
  68. package/dist/sui/index.d.ts.map +1 -1
  69. package/dist/sui/index.js +1 -1
  70. package/dist/sui/index.js.map +1 -1
  71. package/dist/ton/index.d.ts.map +1 -1
  72. package/dist/ton/index.js +34 -26
  73. package/dist/ton/index.js.map +1 -1
  74. package/dist/utils.d.ts +10 -4
  75. package/dist/utils.d.ts.map +1 -1
  76. package/dist/utils.js +10 -4
  77. package/dist/utils.js.map +1 -1
  78. package/package.json +9 -9
  79. package/src/api/index.ts +271 -59
  80. package/src/api/types.ts +126 -1
  81. package/src/chain.ts +121 -43
  82. package/src/errors/codes.ts +1 -0
  83. package/src/errors/index.ts +1 -0
  84. package/src/errors/recovery.ts +2 -0
  85. package/src/errors/specialized.ts +33 -1
  86. package/src/evm/abi/OffRamp_2_0.ts +19 -21
  87. package/src/evm/abi/TokenPool_2_0.ts +0 -1
  88. package/src/evm/gas.ts +18 -20
  89. package/src/evm/index.ts +141 -28
  90. package/src/evm/types.ts +1 -1
  91. package/src/extra-args.ts +18 -8
  92. package/src/gas.ts +8 -3
  93. package/src/index.ts +4 -0
  94. package/src/offchain.ts +6 -8
  95. package/src/requests.ts +19 -12
  96. package/src/solana/index.ts +3 -1
  97. package/src/solana/utils.ts +2 -2
  98. package/src/sui/index.ts +3 -1
  99. package/src/ton/index.ts +47 -26
  100. 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 = OffRamp_2_0_ABI
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
- type.includes('OnRamp') ? interfaces.OnRamp_v1_6 : interfaces.OffRamp_v1_6,
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<typeof OnRamp_1_6_ABI | typeof OffRamp_1_6_ABI>
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
- * {@inheritDoc Chain.generateUnsignedSendMessage}
1007
- * @returns Array containing 0 or more unsigned token approvals txs (if needed at the time of
1008
- * generation), followed by a ccipSend TransactionRequest
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(opts.gasLimit ?? 0),
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(opts.gasLimit ?? 0),
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(opts.gasLimit ?? 0)
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(opts.gasLimit ?? 0),
1243
+ receiverExecutionGasLimit: BigInt(gasLimit ?? 0),
1221
1244
  tokenGasOverrides: input.message.tokenAmounts.map(() =>
1222
- BigInt(opts.tokensGasLimit ?? opts.gasLimit ?? 0),
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(opts.gasLimit ?? 0),
1298
+ receiverExecutionGasLimit: BigInt(gasLimit ?? 0),
1276
1299
  tokenGasOverrides: input.message.tokenAmounts.map(() =>
1277
- BigInt(opts.tokensGasLimit ?? opts.gasLimit ?? 0),
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 gasLimit = await this.provider.estimateGas(manualExecTx)
1314
+ let txGasLimit = await this.provider.estimateGas(manualExecTx)
1292
1315
  if (
1293
1316
  'gasLimit' in input.message &&
1294
1317
  input.message.gasLimit &&
1295
- gasLimit < input.message.gasLimit + 100000n
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
- gasLimit = BigInt(input.message.gasLimit) + 200000n
1299
- else if ('gasLimit' in input.message && !input.message.gasLimit && gasLimit < 240000n)
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
- gasLimit = 240000n
1302
- manualExecTx.gasLimit = 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
- /** {@inheritDoc Chain.getTokenPoolConfig} */
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
- /** {@inheritDoc Chain.getTokenPoolRemotes} */
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
- opts.offRamp,
1734
- opts.message.sourceChainSelector,
1846
+ opts_.offRamp,
1847
+ opts_.message.sourceChainSelector,
1735
1848
  )
1736
- return estimateExecGas({ provider: this.provider, router: destRouter, ...opts })
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 required. */
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 type of all supported extra arguments formats.
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..v1.7 tokenAmounts)
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(source, dest.network.chainSelector, onRamp, ta)
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, getBytes, toNumber } from 'ethers'
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 (dataLength(extraData) !== 64) return
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
- if (
143
- dataLength(extraData) === 32 &&
144
- getBytes(extraData, 'extraData').filter(Boolean).length > 20 // looks like a hash
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 source - Source chain instance
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: Chain,
484
- destChainSelector: bigint,
485
- onRamp: string,
486
- sourceTokenAmount: S,
487
- ): Promise<
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
@@ -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((k) => !SVM_EXTRA_ARGS_FIELDS.has(k))
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',
@@ -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((k) => !SUI_EXTRA_ARGS_FIELDS.has(k))
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 { type BytesLike, hexlify, isBytesLike, isHexString, toBeArray, toBeHex } from 'ethers'
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
- const cell = beginCell()
706
- .storeUint(Number(EVMExtraArgsV2Tag), 32) // magic tag
707
- .storeUint(args.gasLimit, 256) // gasLimit
708
- .storeBit(args.allowOutOfOrderExecution) // bool
709
- .endCell()
710
-
711
- // Return full BOC including headers
712
- return '0x' + cell.toBoc().toString('hex')
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
- const data = bytesToBuffer(extraArgs)
733
-
734
- try {
735
- // Parse BOC format to extract cell data
736
- const cell = Cell.fromBoc(data)[0]!
737
- const slice = cell.beginParse()
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
- // Load and verify magic tag to ensure correct extra args type
740
- const magicTag = slice.loadUint(32)
741
- if (magicTag !== Number(EVMExtraArgsV2Tag)) return undefined
766
+ const allowOutOfOrderExecution = bits[0] === '1'
742
767
 
743
- return {
744
- _tag: 'EVMExtraArgsV2',
745
- gasLimit: slice.loadUintBig(256),
746
- allowOutOfOrderExecution: slice.loadBit(),
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