@chainlink/ccip-sdk 1.1.0 → 1.2.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.
Files changed (115) 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/aptos/index.d.ts.map +1 -1
  8. package/dist/aptos/index.js +5 -5
  9. package/dist/aptos/index.js.map +1 -1
  10. package/dist/chain.d.ts +65 -24
  11. package/dist/chain.d.ts.map +1 -1
  12. package/dist/chain.js +84 -11
  13. package/dist/chain.js.map +1 -1
  14. package/dist/errors/codes.d.ts +1 -0
  15. package/dist/errors/codes.d.ts.map +1 -1
  16. package/dist/errors/codes.js +1 -0
  17. package/dist/errors/codes.js.map +1 -1
  18. package/dist/errors/index.d.ts +1 -1
  19. package/dist/errors/index.d.ts.map +1 -1
  20. package/dist/errors/index.js +1 -1
  21. package/dist/errors/index.js.map +1 -1
  22. package/dist/errors/recovery.d.ts.map +1 -1
  23. package/dist/errors/recovery.js +1 -0
  24. package/dist/errors/recovery.js.map +1 -1
  25. package/dist/errors/specialized.d.ts +21 -0
  26. package/dist/errors/specialized.d.ts.map +1 -1
  27. package/dist/errors/specialized.js +31 -1
  28. package/dist/errors/specialized.js.map +1 -1
  29. package/dist/evm/abi/OffRamp_2_0.d.ts +18 -17
  30. package/dist/evm/abi/OffRamp_2_0.d.ts.map +1 -1
  31. package/dist/evm/abi/OffRamp_2_0.js +19 -21
  32. package/dist/evm/abi/OffRamp_2_0.js.map +1 -1
  33. package/dist/evm/abi/TokenPool_2_0.d.ts +0 -4
  34. package/dist/evm/abi/TokenPool_2_0.d.ts.map +1 -1
  35. package/dist/evm/abi/TokenPool_2_0.js +0 -1
  36. package/dist/evm/abi/TokenPool_2_0.js.map +1 -1
  37. package/dist/evm/gas.d.ts +14 -4
  38. package/dist/evm/gas.d.ts.map +1 -1
  39. package/dist/evm/gas.js +7 -6
  40. package/dist/evm/gas.js.map +1 -1
  41. package/dist/evm/index.d.ts +39 -8
  42. package/dist/evm/index.d.ts.map +1 -1
  43. package/dist/evm/index.js +106 -36
  44. package/dist/evm/index.js.map +1 -1
  45. package/dist/extra-args.d.ts +18 -8
  46. package/dist/extra-args.d.ts.map +1 -1
  47. package/dist/extra-args.js +6 -6
  48. package/dist/extra-args.js.map +1 -1
  49. package/dist/gas.d.ts +1 -1
  50. package/dist/gas.d.ts.map +1 -1
  51. package/dist/gas.js +7 -2
  52. package/dist/gas.js.map +1 -1
  53. package/dist/index.d.ts +3 -2
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/requests.d.ts +11 -5
  57. package/dist/requests.d.ts.map +1 -1
  58. package/dist/requests.js +4 -7
  59. package/dist/requests.js.map +1 -1
  60. package/dist/solana/index.d.ts +2 -2
  61. package/dist/solana/index.d.ts.map +1 -1
  62. package/dist/solana/index.js +3 -2
  63. package/dist/solana/index.js.map +1 -1
  64. package/dist/solana/utils.js +2 -2
  65. package/dist/solana/utils.js.map +1 -1
  66. package/dist/sui/exec.d.ts +30 -0
  67. package/dist/sui/exec.d.ts.map +1 -0
  68. package/dist/sui/exec.js +92 -0
  69. package/dist/sui/exec.js.map +1 -0
  70. package/dist/sui/index.d.ts +7 -2
  71. package/dist/sui/index.d.ts.map +1 -1
  72. package/dist/sui/index.js +23 -65
  73. package/dist/sui/index.js.map +1 -1
  74. package/dist/sui/manuallyExec/index.d.ts.map +1 -1
  75. package/dist/sui/manuallyExec/index.js +10 -13
  76. package/dist/sui/manuallyExec/index.js.map +1 -1
  77. package/dist/sui/objects.d.ts.map +1 -1
  78. package/dist/sui/objects.js +4 -2
  79. package/dist/sui/objects.js.map +1 -1
  80. package/dist/sui/types.d.ts +9 -1
  81. package/dist/sui/types.d.ts.map +1 -1
  82. package/dist/sui/types.js.map +1 -1
  83. package/dist/ton/index.d.ts.map +1 -1
  84. package/dist/ton/index.js +34 -26
  85. package/dist/ton/index.js.map +1 -1
  86. package/dist/utils.d.ts +10 -4
  87. package/dist/utils.d.ts.map +1 -1
  88. package/dist/utils.js +10 -4
  89. package/dist/utils.js.map +1 -1
  90. package/package.json +7 -7
  91. package/src/api/index.ts +271 -59
  92. package/src/api/types.ts +126 -1
  93. package/src/aptos/index.ts +7 -9
  94. package/src/chain.ts +136 -38
  95. package/src/errors/codes.ts +1 -0
  96. package/src/errors/index.ts +1 -0
  97. package/src/errors/recovery.ts +2 -0
  98. package/src/errors/specialized.ts +33 -1
  99. package/src/evm/abi/OffRamp_2_0.ts +19 -21
  100. package/src/evm/abi/TokenPool_2_0.ts +0 -1
  101. package/src/evm/gas.ts +18 -20
  102. package/src/evm/index.ts +126 -34
  103. package/src/extra-args.ts +18 -8
  104. package/src/gas.ts +8 -3
  105. package/src/index.ts +5 -0
  106. package/src/requests.ts +18 -12
  107. package/src/solana/index.ts +3 -2
  108. package/src/solana/utils.ts +2 -2
  109. package/src/sui/exec.ts +131 -0
  110. package/src/sui/index.ts +33 -98
  111. package/src/sui/manuallyExec/index.ts +11 -17
  112. package/src/sui/objects.ts +4 -2
  113. package/src/sui/types.ts +10 -1
  114. package/src/ton/index.ts +47 -26
  115. 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'
@@ -37,7 +39,6 @@ import {
37
39
  } from '../chain.ts'
38
40
  import {
39
41
  CCIPAddressInvalidEvmError,
40
- CCIPApiClientNotAvailableError,
41
42
  CCIPBlockNotFoundError,
42
43
  CCIPContractNotRouterError,
43
44
  CCIPContractTypeInvalidError,
@@ -48,6 +49,7 @@ import {
48
49
  CCIPHasherVersionUnsupportedError,
49
50
  CCIPLogDataInvalidError,
50
51
  CCIPSourceChainUnsupportedError,
52
+ CCIPTokenDecimalsInsufficientError,
51
53
  CCIPTokenNotConfiguredError,
52
54
  CCIPTokenPoolChainConfigNotFoundError,
53
55
  CCIPTransactionNotFoundError,
@@ -67,7 +69,6 @@ import {
67
69
  type ChainLog,
68
70
  type ChainTransaction,
69
71
  type CommitReport,
70
- type ExecutionInput,
71
72
  type ExecutionReceipt,
72
73
  type ExecutionState,
73
74
  type Lane,
@@ -694,7 +695,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
694
695
  offRampABI = OffRamp_1_6_ABI
695
696
  // falls through
696
697
  case CCIPVersion.V2_0: {
697
- offRampABI = OffRamp_2_0_ABI
698
+ offRampABI ??= OffRamp_2_0_ABI
698
699
  const contract = new Contract(
699
700
  offRamp,
700
701
  offRampABI,
@@ -1005,9 +1006,16 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1005
1006
  }
1006
1007
 
1007
1008
  /**
1008
- * {@inheritDoc Chain.generateUnsignedSendMessage}
1009
- * @returns Array containing 0 or more unsigned token approvals txs (if needed at the time of
1010
- * generation), followed by a ccipSend TransactionRequest
1009
+ * Generates unsigned EVM transactions for sending a CCIP message.
1010
+ *
1011
+ * @param opts - Send message options with sender address for populating transaction fields.
1012
+ * @returns Unsigned EVM transaction set containing 0 or more token approval txs
1013
+ * (if needed at the time of generation), followed by a ccipSend TransactionRequest.
1014
+ *
1015
+ * @remarks
1016
+ * When a token in `tokenAmounts` has `ZeroAddress` as its address, the corresponding
1017
+ * amount is included as native `value` in the `ccipSend` transaction instead of
1018
+ * going through the ERC-20 approve flow.
1011
1019
  */
1012
1020
  async generateUnsignedSendMessage(
1013
1021
  opts: Parameters<Chain['generateUnsignedSendMessage']>[0],
@@ -1140,13 +1148,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1140
1148
  async generateUnsignedExecute(
1141
1149
  opts: Parameters<Chain['generateUnsignedExecute']>[0],
1142
1150
  ): Promise<UnsignedEVMTx> {
1143
- let input: ExecutionInput, offRamp: string
1144
- if (!('input' in opts)) {
1145
- if (!this.apiClient) throw new CCIPApiClientNotAvailableError()
1146
- ;({ offRamp, ...input } = await this.apiClient.getExecutionInput(opts.messageId))
1147
- } else {
1148
- ;({ offRamp, input } = opts)
1149
- }
1151
+ const { offRamp, input, gasLimit } = await this.resolveExecuteOpts(opts)
1150
1152
  if ('verifications' in input) {
1151
1153
  const contract = new Contract(
1152
1154
  offRamp,
@@ -1174,14 +1176,14 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1174
1176
  messageId,
1175
1177
  input.verifications.map(({ destAddress }) => destAddress),
1176
1178
  input.verifications.map(({ ccvData }) => hexlify(ccvData)),
1177
- BigInt(opts.gasLimit ?? 0),
1179
+ BigInt(gasLimit ?? 0),
1178
1180
  { from: offRamp }, // internal method
1179
1181
  )
1180
1182
  const execTx = await contract.execute.populateTransaction(
1181
1183
  input.encodedMessage,
1182
1184
  input.verifications.map(({ destAddress }) => destAddress),
1183
1185
  input.verifications.map(({ ccvData }) => hexlify(ccvData)),
1184
- BigInt(opts.gasLimit ?? 0),
1186
+ BigInt(gasLimit ?? 0),
1185
1187
  )
1186
1188
  execTx.gasLimit = txGasLimit + 40000n // plus `execute`'s overhead
1187
1189
  return { family: ChainFamily.EVM, transactions: [execTx] }
@@ -1198,7 +1200,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1198
1200
  interfaces.EVM2EVMOffRamp_v1_2,
1199
1201
  this.provider,
1200
1202
  ) as unknown as TypedContract<typeof EVM2EVMOffRamp_1_2_ABI>
1201
- const gasOverride = BigInt(opts.gasLimit ?? 0)
1203
+ const gasOverride = BigInt(gasLimit ?? 0)
1202
1204
  manualExecTx = await contract.manuallyExecute.populateTransaction(
1203
1205
  {
1204
1206
  ...input,
@@ -1225,9 +1227,9 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1225
1227
  },
1226
1228
  [
1227
1229
  {
1228
- receiverExecutionGasLimit: BigInt(opts.gasLimit ?? 0),
1230
+ receiverExecutionGasLimit: BigInt(gasLimit ?? 0),
1229
1231
  tokenGasOverrides: input.message.tokenAmounts.map(() =>
1230
- BigInt(opts.tokensGasLimit ?? opts.gasLimit ?? 0),
1232
+ BigInt(opts.tokensGasLimit ?? gasLimit ?? 0),
1231
1233
  ),
1232
1234
  },
1233
1235
  ],
@@ -1280,9 +1282,9 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1280
1282
  [
1281
1283
  [
1282
1284
  {
1283
- receiverExecutionGasLimit: BigInt(opts.gasLimit ?? 0),
1285
+ receiverExecutionGasLimit: BigInt(gasLimit ?? 0),
1284
1286
  tokenGasOverrides: input.message.tokenAmounts.map(() =>
1285
- BigInt(opts.tokensGasLimit ?? opts.gasLimit ?? 0),
1287
+ BigInt(opts.tokensGasLimit ?? gasLimit ?? 0),
1286
1288
  ),
1287
1289
  },
1288
1290
  ],
@@ -1296,27 +1298,27 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1296
1298
 
1297
1299
  /* Executing a message for the first time has some hard try/catches on-chain
1298
1300
  * so we need to ensure some lower-bounds gasLimits */
1299
- let gasLimit = await this.provider.estimateGas(manualExecTx)
1301
+ let txGasLimit = await this.provider.estimateGas(manualExecTx)
1300
1302
  if (
1301
1303
  'gasLimit' in input.message &&
1302
1304
  input.message.gasLimit &&
1303
- gasLimit < input.message.gasLimit + 100000n
1305
+ txGasLimit < input.message.gasLimit + 100000n
1304
1306
  )
1305
1307
  // if message requested gasLimit, ensure execution more than 100k above requested, otherwise it's clearly a try/catch fail
1306
- gasLimit = BigInt(input.message.gasLimit) + 200000n
1307
- else if ('gasLimit' in input.message && !input.message.gasLimit && gasLimit < 240000n)
1308
+ txGasLimit = BigInt(input.message.gasLimit) + 200000n
1309
+ else if ('gasLimit' in input.message && !input.message.gasLimit && txGasLimit < 240000n)
1308
1310
  // if message didn't request gasLimit, ensure execution gasLimit is above 240k (empiric)
1309
- gasLimit = 240000n
1310
- manualExecTx.gasLimit = gasLimit
1311
+ txGasLimit = 240000n
1312
+ manualExecTx.gasLimit = txGasLimit
1311
1313
 
1312
1314
  return { family: ChainFamily.EVM, transactions: [manualExecTx] }
1313
1315
  }
1314
1316
 
1315
1317
  /**
1316
1318
  * {@inheritDoc Chain.execute}
1317
- * @throws {@link CCIPWalletInvalidError} if wallet is not a valid Signer
1318
- * @throws {@link CCIPExecTxNotConfirmedError} if execution transaction fails to confirm
1319
- * @throws {@link CCIPExecTxRevertedError} if execution transaction reverts
1319
+ * @throws {@link CCIPWalletInvalidError} if wallet is not a valid Signer.
1320
+ * @throws {@link CCIPExecTxNotConfirmedError} if execution transaction fails to confirm.
1321
+ * @throws {@link CCIPExecTxRevertedError} if execution transaction reverts.
1320
1322
  */
1321
1323
  async execute(opts: Parameters<Chain['execute']>[0]) {
1322
1324
  const wallet = opts.wallet
@@ -1407,7 +1409,16 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1407
1409
  }
1408
1410
  }
1409
1411
 
1410
- /** {@inheritDoc Chain.getTokenPoolConfig} */
1412
+ /**
1413
+ * Fetches the token pool configuration for an EVM token pool contract.
1414
+ *
1415
+ * @param tokenPool - Token pool contract address.
1416
+ * @returns Token pool config containing token, router, typeAndVersion, and optionally minBlockConfirmations.
1417
+ *
1418
+ * @remarks
1419
+ * For pools with version \>= 2.0, also returns `minBlockConfirmations` for
1420
+ * Faster-Than-Finality (FTF) support. Pre-2.0 pools omit this field.
1421
+ */
1411
1422
  async getTokenPoolConfig(tokenPool: string): Promise<{
1412
1423
  token: string
1413
1424
  router: string
@@ -1452,7 +1463,22 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1452
1463
  )
1453
1464
  }
1454
1465
 
1455
- /** {@inheritDoc Chain.getTokenPoolRemotes} */
1466
+ /**
1467
+ * Fetches remote chain configurations for an EVM token pool contract.
1468
+ *
1469
+ * @param tokenPool - Token pool address on the current chain.
1470
+ * @param remoteChainSelector - Optional chain selector to filter results to a single destination.
1471
+ * @returns Record mapping chain names to {@link TokenPoolRemote} configs.
1472
+ *
1473
+ * @remarks
1474
+ * Handles 3 pool version branches:
1475
+ * - v1.5: single remote pool via `getRemotePool`, standard rate limiters.
1476
+ * - v1.6: multiple remote pools via `getRemotePools`, standard rate limiters.
1477
+ * - v2.0+: multiple remote pools plus FTF (Faster-Than-Finality) rate limiters
1478
+ * (`customBlockConfirmationsOutboundRateLimiterState` / `customBlockConfirmationsInboundRateLimiterState`).
1479
+ *
1480
+ * @throws {@link CCIPTokenPoolChainConfigNotFoundError} if remote token is not configured for a chain.
1481
+ */
1456
1482
  async getTokenPoolRemotes(
1457
1483
  tokenPool: string,
1458
1484
  remoteChainSelector?: bigint,
@@ -1737,10 +1763,76 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1737
1763
  override async estimateReceiveExecution(
1738
1764
  opts: Parameters<NonNullable<Chain['estimateReceiveExecution']>>[0],
1739
1765
  ): Promise<number> {
1766
+ const convertAmounts = (
1767
+ tokenAmounts?: readonly ((
1768
+ | { token: string }
1769
+ | { destTokenAddress: string; extraData?: string }
1770
+ ) & {
1771
+ amount: bigint
1772
+ })[],
1773
+ ) =>
1774
+ !tokenAmounts
1775
+ ? undefined
1776
+ : Promise.all(
1777
+ tokenAmounts.map(async (ta) => {
1778
+ if (!('destTokenAddress' in ta)) return ta
1779
+ let amount = ta.amount
1780
+ if (isHexString(ta.extraData, 32)) {
1781
+ // extraData is source token decimals in most pools derived from standard TP contracts;
1782
+ // we can identify for it being exactly 32B and being a small integer; otherwise, assume same decimals
1783
+ const sourceDecimals = toBigInt(ta.extraData)
1784
+ if (0 < sourceDecimals && sourceDecimals <= 36) {
1785
+ const { decimals: destDecimals } = await this.getTokenInfo(ta.destTokenAddress)
1786
+ amount =
1787
+ (amount * BigInt(10) ** BigInt(destDecimals)) /
1788
+ BigInt(10) ** BigInt(sourceDecimals)
1789
+ if (amount === 0n)
1790
+ throw new CCIPTokenDecimalsInsufficientError(
1791
+ ta.destTokenAddress,
1792
+ destDecimals,
1793
+ this.network.name,
1794
+ formatUnits(amount, sourceDecimals),
1795
+ )
1796
+ }
1797
+ }
1798
+ return { token: ta.destTokenAddress, amount }
1799
+ }),
1800
+ )
1801
+
1802
+ let opts_
1803
+ if (!('offRamp' in opts)) {
1804
+ const { lane, message, metadata } = await this.getMessageById(opts.messageId)
1805
+
1806
+ const offRamp =
1807
+ ('offRampAddress' in message && message.offRampAddress) ||
1808
+ metadata?.offRamp ||
1809
+ (await this.apiClient!.getExecutionInput(opts.messageId)).offRamp
1810
+
1811
+ opts_ = {
1812
+ offRamp,
1813
+ message: {
1814
+ sourceChainSelector: lane.sourceChainSelector,
1815
+ messageId: message.messageId,
1816
+ receiver: message.receiver,
1817
+ sender: message.sender,
1818
+ data: message.data,
1819
+ destTokenAmounts: await convertAmounts(message.tokenAmounts),
1820
+ },
1821
+ }
1822
+ } else {
1823
+ opts_ = {
1824
+ ...opts,
1825
+ message: {
1826
+ ...opts.message,
1827
+ destTokenAmounts: await convertAmounts(opts.message.destTokenAmounts),
1828
+ },
1829
+ }
1830
+ }
1831
+
1740
1832
  const destRouter = await this.getRouterForOffRamp(
1741
- opts.offRamp,
1742
- opts.message.sourceChainSelector,
1833
+ opts_.offRamp,
1834
+ opts_.message.sourceChainSelector,
1743
1835
  )
1744
- return estimateExecGas({ provider: this.provider, router: destRouter, ...opts })
1836
+ return estimateExecGas({ provider: this.provider, router: destRouter, ...opts_ })
1745
1837
  }
1746
1838
  }
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,
@@ -100,6 +104,7 @@ export type { UnsignedEVMTx } from './evm/index.ts'
100
104
  import { SolanaChain } from './solana/index.ts'
101
105
  export type { UnsignedSolanaTx } from './solana/index.ts'
102
106
  import { SuiChain } from './sui/index.ts'
107
+ export type { UnsignedSuiTx } from './sui/index.ts'
103
108
  import { TONChain } from './ton/index.ts'
104
109
  export type { UnsignedTONTx } from './ton/index.ts'
105
110
  import { ChainFamily, NetworkType } from './types.ts'
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[]))
@@ -456,10 +456,7 @@ export async function* getMessagesForSender(
456
456
  * Resolves token routing by querying the TokenAdminRegistry and TokenPool
457
457
  * to find the corresponding destination chain token.
458
458
  *
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`
459
+ * @param opts - options to convert source to dest token addresses
463
460
  * @returns Extended token amount with `sourcePoolAddress`, `sourceTokenAddress`, and `destTokenAddress`
464
461
  *
465
462
  * @throws {@link CCIPTokenNotInRegistryError} if token is not registered in TokenAdminRegistry
@@ -479,12 +476,21 @@ export async function* getMessagesForSender(
479
476
  * console.log(`Dest token: ${tokenAmount.destTokenAddress}`)
480
477
  * ```
481
478
  */
482
- export async function sourceToDestTokenAddresses<S extends { token: string }>(
483
- source: Chain,
484
- destChainSelector: bigint,
485
- onRamp: string,
486
- sourceTokenAmount: S,
487
- ): Promise<
479
+ export async function sourceToDestTokenAddresses<S extends { token: string }>({
480
+ source,
481
+ onRamp,
482
+ destChainSelector,
483
+ sourceTokenAmount,
484
+ }: {
485
+ /** Source chain instance */
486
+ source: Chain
487
+ /** OnRamp contract address */
488
+ onRamp: string
489
+ /** Destination chain selector */
490
+ destChainSelector: bigint
491
+ /** Token amount object containing `token` and `amount` */
492
+ sourceTokenAmount: S
493
+ }): Promise<
488
494
  S & {
489
495
  sourcePoolAddress: string
490
496
  sourceTokenAddress: string
@@ -1118,9 +1118,10 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
1118
1118
  payer,
1119
1119
  ...opts
1120
1120
  }: Parameters<Chain['generateUnsignedExecute']>[0]): Promise<UnsignedSolanaTx> {
1121
- if (!('input' in opts) || !('message' in opts.input) || !('computeUnits' in opts.input.message))
1121
+ const resolved = await this.resolveExecuteOpts(opts)
1122
+ if (!('message' in resolved.input) || !('computeUnits' in resolved.input.message))
1122
1123
  throw new CCIPExecutionReportChainMismatchError('Solana')
1123
- const { offRamp, input } = opts
1124
+ const { offRamp, input } = resolved
1124
1125
  const execReport_ = input as ExecutionInput<CCIPMessage_V1_6_Solana>
1125
1126
  return generateUnsignedExecuteReport(
1126
1127
  this,
@@ -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
  }
@@ -0,0 +1,131 @@
1
+ import type { SuiClient } from '@mysten/sui/client'
2
+ import type { Keypair } from '@mysten/sui/cryptography'
3
+ import { Transaction } from '@mysten/sui/transactions'
4
+
5
+ import {
6
+ CCIPError,
7
+ CCIPErrorCode,
8
+ CCIPExecTxRevertedError,
9
+ CCIPExecutionReportChainMismatchError,
10
+ } from '../errors/index.ts'
11
+ import { type ExecutionInput, ChainFamily } from '../types.ts'
12
+ import { getCcipStateAddress } from './discovery.ts'
13
+ import {
14
+ type SuiManuallyExecuteInput,
15
+ type TokenConfig,
16
+ buildManualExecutionPTB,
17
+ } from './manuallyExec/index.ts'
18
+ import { fetchTokenConfigs, getObjectRef, getReceiverModule } from './objects.ts'
19
+ import type { CCIPMessage_V1_6_Sui, UnsignedSuiTx } from './types.ts'
20
+
21
+ /**
22
+ * Builds a Sui manual-execution PTB and returns it as an {@link UnsignedSuiTx}.
23
+ *
24
+ * @param client - Sui RPC client.
25
+ * @param offRamp - OffRamp object ID / address.
26
+ * @param input - Execution input (message + proofs).
27
+ * @param opts - Optional overrides such as `gasLimit` and `receiverObjectIds`.
28
+ * @returns Serialized unsigned transaction ready to sign and submit.
29
+ */
30
+ export async function generateUnsignedExecutePTB(
31
+ client: SuiClient,
32
+ offRamp: string,
33
+ input: ExecutionInput<CCIPMessage_V1_6_Sui>,
34
+ opts?: { gasLimit?: number | bigint; receiverObjectIds?: string[] },
35
+ ): Promise<UnsignedSuiTx> {
36
+ if (!('message' in input)) {
37
+ throw new CCIPExecutionReportChainMismatchError('Sui')
38
+ }
39
+
40
+ const ccip = await getCcipStateAddress(offRamp, client)
41
+
42
+ const ccipObjectRef = await getObjectRef(ccip, client)
43
+ const [offrampStateObject, receiverConfig] = await Promise.all([
44
+ getObjectRef(offRamp, client),
45
+ getReceiverModule(client, ccip, ccipObjectRef, input.message.receiver),
46
+ ])
47
+
48
+ let tokenConfigs: TokenConfig[] = []
49
+ if (input.message.tokenAmounts.length !== 0) {
50
+ tokenConfigs = await fetchTokenConfigs(client, ccip, ccipObjectRef, input.message.tokenAmounts)
51
+ }
52
+
53
+ const suiInput: SuiManuallyExecuteInput = {
54
+ executionReport: input,
55
+ offrampAddress: offRamp,
56
+ ccipAddress: ccip,
57
+ ccipObjectRef,
58
+ offrampStateObject,
59
+ receiverConfig,
60
+ tokenConfigs,
61
+ ...(opts?.receiverObjectIds ? { overrideReceiverObjectIds: opts.receiverObjectIds } : {}),
62
+ }
63
+
64
+ const tx = buildManualExecutionPTB(suiInput)
65
+
66
+ if (opts?.gasLimit) {
67
+ tx.setGasBudget(opts.gasLimit)
68
+ }
69
+
70
+ return {
71
+ family: ChainFamily.Sui,
72
+ transactions: [tx.serialize()],
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Signs and executes a pre-built {@link UnsignedSuiTx} using the provided keypair.
78
+ *
79
+ * @param client - Sui RPC client.
80
+ * @param wallet - Keypair used to sign the transaction.
81
+ * @param unsignedTx - The unsigned Sui transaction to execute.
82
+ * @param logger - Optional logger.
83
+ * @returns The finalized transaction digest string.
84
+ */
85
+ export async function signAndExecuteSuiTx(
86
+ client: SuiClient,
87
+ wallet: Keypair,
88
+ unsignedTx: UnsignedSuiTx,
89
+ logger?: { info: (...args: unknown[]) => void },
90
+ ): Promise<string> {
91
+ const tx = Transaction.from(unsignedTx.transactions[0])
92
+
93
+ logger?.info('Executing Sui CCIP execute transaction...')
94
+
95
+ let digest: string
96
+ try {
97
+ const result = await client.signAndExecuteTransaction({
98
+ signer: wallet,
99
+ transaction: tx,
100
+ options: {
101
+ showEffects: true,
102
+ showEvents: true,
103
+ },
104
+ })
105
+
106
+ if (result.effects?.status.status !== 'success') {
107
+ const errorMsg = result.effects?.status.error ?? 'Unknown error'
108
+ throw new CCIPExecTxRevertedError(result.digest, { context: { error: errorMsg } })
109
+ }
110
+
111
+ digest = result.digest
112
+ } catch (e) {
113
+ if (e instanceof CCIPExecTxRevertedError) throw e
114
+ throw new CCIPError(
115
+ CCIPErrorCode.TRANSACTION_NOT_FINALIZED,
116
+ `Failed to send Sui execute transaction: ${(e as Error).message}`,
117
+ )
118
+ }
119
+
120
+ logger?.info(`Waiting for Sui transaction ${digest} to be finalized...`)
121
+
122
+ await client.waitForTransaction({
123
+ digest,
124
+ options: {
125
+ showEffects: true,
126
+ showEvents: true,
127
+ },
128
+ })
129
+
130
+ return digest
131
+ }