@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/chain.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type BytesLike, dataLength } from 'ethers'
1
+ import { type BytesLike, dataLength, keccak256 } from 'ethers'
2
2
  import type { PickDeep, SetOptional } from 'type-fest'
3
3
 
4
4
  import { type LaneLatencyResponse, CCIPAPIClient } from './api/index.ts'
@@ -24,10 +24,12 @@ import type {
24
24
  SuiExtraArgsV1,
25
25
  } from './extra-args.ts'
26
26
  import type { LeafHasher } from './hasher/common.ts'
27
+ import { decodeMessageV1 } from './messages.ts'
27
28
  import { getOffchainTokenData } from './offchain.ts'
28
29
  import { getMessagesInTx } from './requests.ts'
29
30
  import { DEFAULT_GAS_LIMIT } from './shared/constants.ts'
30
31
  import type { UnsignedSolanaTx } from './solana/types.ts'
32
+ import type { UnsignedSuiTx } from './sui/types.ts'
31
33
  import type { UnsignedTONTx } from './ton/types.ts'
32
34
  import {
33
35
  type AnyMessage,
@@ -67,7 +69,7 @@ const V3_FIELDS = new Set([
67
69
  'tokenArgs',
68
70
  ])
69
71
 
70
- /** Throw if any key in extraArgs is not in the allowed set. */
72
+ /** Throw {@link CCIPArgumentInvalidError} if any key in extraArgs is not in the allowed set. */
71
73
  function assertNoUnknownFields(
72
74
  extraArgs: Partial<ExtraArgs>,
73
75
  allowed: Set<string>,
@@ -200,7 +202,7 @@ export type TokenInfo = {
200
202
  */
201
203
  export const LaneFeature = {
202
204
  /**
203
- * Minimum block confirmations for Faster Time to Finality (FTF).
205
+ * Minimum block confirmations for Faster-Than-Finality (FTF).
204
206
  * - **absent**: the lane does not support FTF (pre-v2.0 lane).
205
207
  * - **0**: the lane supports FTF, but it is not enabled for this
206
208
  * token (e.g. the token pool predates FTF, or FTF is configured
@@ -284,6 +286,11 @@ export type RateLimiterState = {
284
286
  * @remarks
285
287
  * Each entry represents the configuration needed to transfer tokens
286
288
  * from the current chain to a specific destination chain.
289
+ *
290
+ * The `customBlockConfirmationsOutboundRateLimiterState` and
291
+ * `customBlockConfirmationsInboundRateLimiterState` fields are present only for
292
+ * TokenPool v2.0+ contracts. These provide separate rate limits applied when
293
+ * Faster-Than-Finality (FTF) custom block confirmations are used.
287
294
  */
288
295
  export type TokenPoolRemote = {
289
296
  /** Address of the remote token on the destination chain. */
@@ -332,7 +339,7 @@ export type TokenPoolConfig = {
332
339
  */
333
340
  typeAndVersion?: string
334
341
  /**
335
- * Min custom block confirmations for Faster Time to Finality (FTF),
342
+ * Min custom block confirmations for Faster-Than-Finality (FTF),
336
343
  * if TokenPool version \>= v2.0.0 and FTF is supported on this lane.
337
344
  * `0` indicates FTF is supported but not enabled for this token; `>0` indicates FTF is enabled
338
345
  * with this many minimum confirmations.
@@ -364,7 +371,7 @@ export type UnsignedTx = {
364
371
  [ChainFamily.Solana]: UnsignedSolanaTx
365
372
  [ChainFamily.Aptos]: UnsignedAptosTx
366
373
  [ChainFamily.TON]: UnsignedTONTx
367
- [ChainFamily.Sui]: never // TODO
374
+ [ChainFamily.Sui]: UnsignedSuiTx
368
375
  [ChainFamily.Unknown]: never
369
376
  }
370
377
 
@@ -395,7 +402,7 @@ export type ExecuteOpts = (
395
402
  | {
396
403
  /**
397
404
  * messageId of message to execute; requires `apiClient`.
398
- * @remarks Currently throws CCIPNotImplementedError - API endpoint pending.
405
+ * The SDK will fetch execution inputs (offRamp, proofs/verifications) from the CCIP API.
399
406
  */
400
407
  messageId: string
401
408
  }
@@ -1011,6 +1018,71 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
1011
1018
  return getOffchainTokenData(request, this)
1012
1019
  }
1013
1020
 
1021
+ /**
1022
+ * Resolves {@link ExecuteOpts} that may contain a `messageId` (API shorthand) into the
1023
+ * canonical `{ offRamp, input }` form required by {@link generateUnsignedExecute}.
1024
+ *
1025
+ * When `opts` already contains `input` the method is a no-op and returns it unchanged.
1026
+ * When `opts` contains only a `messageId` it calls `apiClient.getExecutionInput` and merges
1027
+ * the result back with any extra opts fields (e.g. `gasLimit`).
1028
+ * If opts.gasLimit is undefined and `estimateReceiveExecution` is available, try to estimate gasLimitOverride
1029
+ *
1030
+ * @throws {@link CCIPApiClientNotAvailableError} if `messageId` is provided but no apiClient
1031
+ */
1032
+ protected async resolveExecuteOpts(
1033
+ opts: ExecuteOpts,
1034
+ ): Promise<Extract<ExecuteOpts, { input: unknown }>> {
1035
+ let opts_: Extract<typeof opts, { input: unknown }>
1036
+ if ('input' in opts) {
1037
+ opts_ = opts
1038
+ } else if (!this.apiClient) throw new CCIPApiClientNotAvailableError()
1039
+ else {
1040
+ const { offRamp, ...input } = await this.apiClient.getExecutionInput(opts.messageId)
1041
+ opts_ = { ...opts, offRamp, input }
1042
+ }
1043
+
1044
+ if (
1045
+ opts_.gasLimit == null &&
1046
+ this.estimateReceiveExecution &&
1047
+ (!('message' in opts_.input) ||
1048
+ !opts_.input.message.tokenAmounts.length ||
1049
+ opts_.input.message.tokenAmounts.every((ta) => 'destTokenAddress' in ta))
1050
+ ) {
1051
+ let message
1052
+ if ('message' in opts_.input) {
1053
+ message = {
1054
+ ...opts_.input.message,
1055
+ // pass `tokenAmount` with `destTokenAddress` to estimate
1056
+ destTokenAmounts: opts_.input.message.tokenAmounts,
1057
+ }
1058
+ } else {
1059
+ const decoded = decodeMessageV1(opts_.input.encodedMessage)
1060
+ message = {
1061
+ ...decoded,
1062
+ messageId: keccak256(opts_.input.encodedMessage),
1063
+ destTokenAmounts: decoded.tokenTransfer,
1064
+ }
1065
+ }
1066
+ try {
1067
+ const estimated = await this.estimateReceiveExecution({
1068
+ offRamp: opts_.offRamp,
1069
+ message,
1070
+ })
1071
+ this.logger.debug('Estimated receiver execution:', estimated)
1072
+ if (
1073
+ ('gasLimit' in message && estimated > message.gasLimit) ||
1074
+ ('ccipReceiveGasLimit' in message && estimated > message.ccipReceiveGasLimit)
1075
+ )
1076
+ opts_.gasLimit = estimated
1077
+ } catch (err) {
1078
+ // ignore if receiver fails, let estimation of execute method itself throw if needed
1079
+ this.logger.debug('Failed to auto-estimateReceiveExecution for:', opts, err)
1080
+ }
1081
+ }
1082
+
1083
+ return opts_
1084
+ }
1085
+
1014
1086
  /**
1015
1087
  * Generate unsigned tx to manuallyExecute a message.
1016
1088
  *
@@ -1036,21 +1108,24 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
1036
1108
  /**
1037
1109
  * Execute messages in report in an offRamp.
1038
1110
  *
1039
- * @param opts - {@link ExecuteOpts} with chain-specific wallet to sign and send tx
1040
- * @returns Promise resolving to transaction of the execution
1111
+ * @param opts - {@link ExecuteOpts} with chain-specific wallet to sign and send tx.
1112
+ * @returns Promise resolving to transaction of the execution.
1041
1113
  *
1042
- * @throws {@link CCIPWalletNotSignerError} if wallet is not a valid signer
1043
- * @throws {@link CCIPExecTxRevertedError} if execution transaction reverts
1044
- * @throws {@link CCIPMerkleRootMismatchError} if merkle proof is invalid
1114
+ * @throws {@link CCIPWalletNotSignerError} if wallet is not a valid signer.
1115
+ * @throws {@link CCIPExecTxNotConfirmedError} if execution transaction fails to confirm.
1116
+ * @throws {@link CCIPExecTxRevertedError} if execution transaction reverts.
1117
+ * @throws {@link CCIPMerkleRootMismatchError} if merkle proof is invalid.
1045
1118
  *
1046
- * @example Manual execution of pending message
1119
+ * @example Manual execution using message ID (simplified, requires API)
1120
+ * ```typescript
1121
+ * const receipt = await dest.execute({ messageId: '0x...', wallet })
1122
+ * ```
1123
+ *
1124
+ * @example Manual execution using transaction hash
1047
1125
  * ```typescript
1048
1126
  * const input = await source.getExecutionInput({ request, verifications })
1049
1127
  * const receipt = await dest.execute({ offRamp, input, wallet })
1050
- * console.log(`Executed: ${receipt.log.transactionHash}`)
1051
1128
  * ```
1052
- * @throws {@link CCIPWalletNotSignerError} if wallet cannot sign transactions
1053
- * @throws {@link CCIPExecTxNotConfirmedError} if execution transaction fails to confirm
1054
1129
  */
1055
1130
  abstract execute(
1056
1131
  opts: ExecuteOpts & {
@@ -1098,10 +1173,10 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
1098
1173
  * Uses this chain's selector as the source.
1099
1174
  *
1100
1175
  * @param destChainSelector - Destination CCIP chain selector (bigint)
1176
+ * @param numberOfBlocks - Optional number of block confirmations to use for latency
1177
+ * calculation. When omitted or 0, uses the lane's default finality. When provided
1178
+ * as a positive integer, the API returns latency for that custom finality value.
1101
1179
  * @returns Promise resolving to {@link LaneLatencyResponse} containing:
1102
- * - `lane.sourceNetworkInfo` - Source chain metadata (name, selector, chainId)
1103
- * - `lane.destNetworkInfo` - Destination chain metadata
1104
- * - `lane.routerAddress` - Router contract address on source chain
1105
1180
  * - `totalMs` - Estimated delivery time in milliseconds
1106
1181
  *
1107
1182
  * @throws {@link CCIPApiClientNotAvailableError} if apiClient was disabled (set to `null`)
@@ -1117,19 +1192,31 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
1117
1192
  * try {
1118
1193
  * const latency = await chain.getLaneLatency(4949039107694359620n) // Arbitrum
1119
1194
  * console.log(`Estimated delivery: ${Math.round(latency.totalMs / 60000)} minutes`)
1120
- * console.log(`Router: ${latency.lane.routerAddress}`)
1121
1195
  * } catch (err) {
1122
1196
  * if (err instanceof CCIPHttpError) {
1123
1197
  * console.error(`API error: ${err.context.apiErrorCode}`)
1124
1198
  * }
1125
1199
  * }
1126
1200
  * ```
1201
+ *
1202
+ * @example Get latency with custom block confirmations
1203
+ * ```typescript
1204
+ * const latency = await chain.getLaneLatency(4949039107694359620n, 10)
1205
+ * console.log(`Latency with 10 confirmations: ${Math.round(latency.totalMs / 60000)} minutes`)
1206
+ * ```
1127
1207
  */
1128
- async getLaneLatency(destChainSelector: bigint): Promise<LaneLatencyResponse> {
1208
+ async getLaneLatency(
1209
+ destChainSelector: bigint,
1210
+ numberOfBlocks?: number,
1211
+ ): Promise<LaneLatencyResponse> {
1129
1212
  if (!this.apiClient) {
1130
1213
  throw new CCIPApiClientNotAvailableError()
1131
1214
  }
1132
- return this.apiClient.getLaneLatency(this.network.chainSelector, destChainSelector)
1215
+ return this.apiClient.getLaneLatency(
1216
+ this.network.chainSelector,
1217
+ destChainSelector,
1218
+ numberOfBlocks,
1219
+ )
1133
1220
  }
1134
1221
 
1135
1222
  /**
@@ -1418,8 +1505,15 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
1418
1505
  * Returns a copy of a message, populating missing fields like `extraArgs` with defaults.
1419
1506
  * It's expected to return a message suitable at least for basic token transfers.
1420
1507
  *
1421
- * @param message - AnyMessage (from source), containing at least `receiver`
1422
- * @returns A message suitable for `sendMessage` to this destination chain family
1508
+ * @param message - AnyMessage (from source), containing at least `receiver`.
1509
+ * @returns A message suitable for `sendMessage` to this destination chain family.
1510
+ *
1511
+ * @remarks
1512
+ * V3 (GenericExtraArgsV3) is auto-detected when any V3-only field is present
1513
+ * (e.g. `blockConfirmations`, `ccvs`, `ccvArgs`, `executor`, `executorArgs`,
1514
+ * `tokenReceiver`, `tokenArgs`). Otherwise defaults to V2 (EVMExtraArgsV2).
1515
+ *
1516
+ * @throws {@link CCIPArgumentInvalidError} if extraArgs contains unknown fields for the detected version.
1423
1517
  */
1424
1518
  static buildMessageForDest(
1425
1519
  message: Parameters<ChainStatic['buildMessageForDest']>[0],
@@ -1461,23 +1555,27 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
1461
1555
 
1462
1556
  /**
1463
1557
  * Estimate `ccipReceive` execution cost (gas, computeUnits) for this *dest*
1464
- * @param opts - estimation options
1558
+ * @param opts - estimation options, either `messageId` (for api) or `offRamp`, `message` (with `destTokenAmounts`)
1465
1559
  * @returns estimated execution cost (gas or computeUnits)
1466
1560
  */
1467
- estimateReceiveExecution?(opts: {
1468
- offRamp: string
1469
- receiver: string
1470
- message: {
1471
- sourceChainSelector: bigint
1472
- messageId: string
1473
- sender?: string
1474
- data?: BytesLike
1475
- destTokenAmounts?: readonly {
1476
- token: string
1477
- amount: bigint
1478
- }[]
1479
- }
1480
- }): Promise<number>
1561
+ estimateReceiveExecution?(
1562
+ opts:
1563
+ | {
1564
+ offRamp: string
1565
+ message: {
1566
+ sourceChainSelector: bigint
1567
+ messageId: string
1568
+ receiver: string
1569
+ sender?: string
1570
+ data?: BytesLike
1571
+ destTokenAmounts?: readonly ((
1572
+ | { token: string }
1573
+ | { destTokenAddress: string; extraData?: string }
1574
+ ) & { amount: bigint })[]
1575
+ }
1576
+ }
1577
+ | { messageId: string },
1578
+ ): Promise<number>
1481
1579
  }
1482
1580
 
1483
1581
  /**
@@ -144,6 +144,7 @@ export const CCIPErrorCode = {
144
144
  HTTP_ERROR: 'HTTP_ERROR',
145
145
  RPC_NOT_FOUND: 'RPC_NOT_FOUND',
146
146
  TIMEOUT: 'TIMEOUT',
147
+ ABORT: 'ABORT',
147
148
 
148
149
  // API Client
149
150
  API_CLIENT_NOT_AVAILABLE: 'API_CLIENT_NOT_AVAILABLE',
@@ -156,6 +156,7 @@ export { CCIPBorshMethodUnknownError, CCIPBorshTypeUnknownError } from './specia
156
156
 
157
157
  // Specialized errors - HTTP & Data
158
158
  export {
159
+ CCIPAbortError,
159
160
  CCIPBlockBeforeTimestampNotFoundError,
160
161
  CCIPDataFormatUnsupportedError,
161
162
  CCIPDataParseError,
@@ -170,6 +170,8 @@ export const DEFAULT_RECOVERY_HINTS: Partial<Record<CCIPErrorCode, string>> = {
170
170
  RPC_NOT_FOUND: 'No RPC endpoint found. Configure an RPC URL.',
171
171
  TIMEOUT:
172
172
  'Request timed out. Check network connectivity and try again. Consider increasing timeoutMs if the server is slow.',
173
+ ABORT:
174
+ 'Request was aborted. This is usually intentional (e.g. user cancellation or component unmount).',
173
175
 
174
176
  VIEM_ADAPTER_ERROR:
175
177
  'Check that your viem client has both account and chain defined. For WalletClient, use createWalletClient({ chain, account, ... }).',
@@ -1,6 +1,7 @@
1
1
  import { type CCIPErrorOptions, CCIPError } from './CCIPError.ts'
2
2
  import { CCIPErrorCode } from './codes.ts'
3
3
  import { isTransientHttpStatus } from '../http-status.ts'
4
+ import { bigIntReplacer } from '../utils.ts'
4
5
 
5
6
  // Chain/Network
6
7
 
@@ -166,7 +167,10 @@ export class CCIPMessageInvalidError extends CCIPError {
166
167
  override readonly name = 'CCIPMessageInvalidError'
167
168
  /** Creates a message invalid error. */
168
169
  constructor(data: unknown, options?: CCIPErrorOptions) {
169
- const dataStr = typeof data === 'object' && data !== null ? JSON.stringify(data) : String(data)
170
+ const dataStr =
171
+ typeof data === 'object' && data !== null
172
+ ? JSON.stringify(data, bigIntReplacer)
173
+ : String(data)
170
174
  super(CCIPErrorCode.MESSAGE_INVALID, `Invalid CCIP message format: ${dataStr}`, {
171
175
  ...options,
172
176
  isTransient: false,
@@ -1203,6 +1207,34 @@ export class CCIPTimeoutError extends CCIPError {
1203
1207
  }
1204
1208
  }
1205
1209
 
1210
+ /**
1211
+ * Thrown when a request is aborted via an AbortSignal.
1212
+ *
1213
+ * @example
1214
+ * ```typescript
1215
+ * const controller = new AbortController()
1216
+ * setTimeout(() => controller.abort(), 1000)
1217
+ * try {
1218
+ * await api.searchMessages({ sender: '0x...' }, { signal: controller.signal })
1219
+ * } catch (error) {
1220
+ * if (error instanceof CCIPAbortError) {
1221
+ * console.log(`Request was cancelled: ${error.context.operation}`)
1222
+ * }
1223
+ * }
1224
+ * ```
1225
+ */
1226
+ export class CCIPAbortError extends CCIPError {
1227
+ override readonly name = 'CCIPAbortError'
1228
+ /** Creates an abort error. */
1229
+ constructor(operation: string, options?: CCIPErrorOptions) {
1230
+ super(CCIPErrorCode.ABORT, `Request aborted: ${operation}`, {
1231
+ ...options,
1232
+ isTransient: false,
1233
+ context: { ...options?.context, operation },
1234
+ })
1235
+ }
1236
+ }
1237
+
1206
1238
  /**
1207
1239
  * Thrown for not implemented features.
1208
1240
  *
@@ -222,9 +222,13 @@ export default [
222
222
  name: 'getAllSourceChainConfigs',
223
223
  inputs: [],
224
224
  outputs: [
225
- { name: '', type: 'uint64[]', internalType: 'uint64[]' },
226
225
  {
227
- name: '',
226
+ name: 'sourceChainSelectors',
227
+ type: 'uint64[]',
228
+ internalType: 'uint64[]',
229
+ },
230
+ {
231
+ name: 'sourceChainConfigs',
228
232
  type: 'tuple[]',
229
233
  internalType: 'struct OffRamp.SourceChainConfig[]',
230
234
  components: [
@@ -418,25 +422,6 @@ export default [
418
422
  ],
419
423
  anonymous: false,
420
424
  },
421
- {
422
- type: 'event',
423
- name: 'MaxGasBufferToUpdateStateUpdated',
424
- inputs: [
425
- {
426
- name: 'oldMaxGasBufferToUpdateState',
427
- type: 'uint32',
428
- indexed: false,
429
- internalType: 'uint32',
430
- },
431
- {
432
- name: 'newMaxGasBufferToUpdateState',
433
- type: 'uint32',
434
- indexed: false,
435
- internalType: 'uint32',
436
- },
437
- ],
438
- anonymous: false,
439
- },
440
425
  {
441
426
  type: 'event',
442
427
  name: 'OwnershipTransferRequested',
@@ -611,6 +596,19 @@ export default [
611
596
  name: 'InvalidEncodingVersion',
612
597
  inputs: [{ name: 'version', type: 'uint8', internalType: 'uint8' }],
613
598
  },
599
+ {
600
+ type: 'error',
601
+ name: 'InvalidFinalityForReceiver',
602
+ inputs: [
603
+ { name: 'receiver', type: 'address', internalType: 'address' },
604
+ { name: 'msgBlockDepth', type: 'uint16', internalType: 'uint16' },
605
+ {
606
+ name: 'requiredBlockDepth',
607
+ type: 'uint16',
608
+ internalType: 'uint16',
609
+ },
610
+ ],
611
+ },
614
612
  {
615
613
  type: 'error',
616
614
  name: 'InvalidGasLimitOverride',
@@ -1546,7 +1546,6 @@ export default [
1546
1546
  name: 'InvalidTransferFeeBps',
1547
1547
  inputs: [{ name: 'bps', type: 'uint256', internalType: 'uint256' }],
1548
1548
  },
1549
- { type: 'error', name: 'MismatchedArrayLengths', inputs: [] },
1550
1549
  { type: 'error', name: 'MustBeProposedOwner', inputs: [] },
1551
1550
  {
1552
1551
  type: 'error',
package/src/evm/gas.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type BytesLike,
2
3
  type JsonRpcApiProvider,
3
4
  Contract,
4
5
  FunctionFragment,
@@ -14,7 +15,6 @@ import {
14
15
  import type { TypedContract } from 'ethers-abitype'
15
16
  import { memoize } from 'micro-memoize'
16
17
 
17
- import type { Chain } from '../chain.ts'
18
18
  import TokenABI from './abi/BurnMintERC677Token.ts'
19
19
  import RouterABI from './abi/Router.ts'
20
20
  import { defaultAbiCoder, interfaces } from './const.ts'
@@ -77,13 +77,17 @@ const findBalancesSlot = memoize(
77
77
  { maxArgs: 1 },
78
78
  )
79
79
 
80
- type EstimateExecGasOpts = Pick<
81
- Parameters<NonNullable<Chain['estimateReceiveExecution']>>[0],
82
- 'message' | 'receiver'
83
- > & {
84
- /* */
80
+ type EstimateExecGasOpts = {
85
81
  provider: JsonRpcApiProvider
86
82
  router: string
83
+ message: {
84
+ sourceChainSelector: bigint
85
+ messageId: string
86
+ receiver: string
87
+ sender?: string
88
+ data?: BytesLike
89
+ destTokenAmounts?: readonly { token: string; amount: bigint }[]
90
+ }
87
91
  }
88
92
 
89
93
  /**
@@ -91,12 +95,7 @@ type EstimateExecGasOpts = Pick<
91
95
  * @param opts - Options for estimation: provider, destRouter, receiver address and message
92
96
  * @returns Estimated gasLimit
93
97
  */
94
- export async function estimateExecGas({
95
- provider,
96
- router,
97
- receiver,
98
- message,
99
- }: EstimateExecGasOpts) {
98
+ export async function estimateExecGas({ provider, router, message }: EstimateExecGasOpts) {
100
99
  // we need to override the state, increasing receiver's balance for each token, to simulate the
101
100
  // state after tokens were transferred by the offRamp just before calling `ccipReceive`
102
101
  const destAmounts: Record<string, bigint> = {}
@@ -106,25 +105,24 @@ export async function estimateExecGas({
106
105
  const tokenContract = new Contract(token, TokenABI, provider) as unknown as TypedContract<
107
106
  typeof TokenABI
108
107
  >
109
- const currentBalance = await tokenContract.balanceOf(receiver)
108
+ const currentBalance = await tokenContract.balanceOf(message.receiver)
110
109
  destAmounts[token] = currentBalance
111
110
  }
112
111
  destAmounts[token]! += amount
113
- const balancesSlot = await findBalancesSlot(token, provider, receiver, router)
112
+ const balancesSlot = await findBalancesSlot(token, provider, message.receiver, router)
114
113
  stateOverrides[token] = {
115
114
  stateDiff: {
116
- [solidityPackedKeccak256(['uint256', 'uint256'], [receiver, balancesSlot])]: toBeHex(
117
- destAmounts[token]!,
118
- 32,
119
- ),
115
+ [solidityPackedKeccak256(['uint256', 'uint256'], [message.receiver, balancesSlot])]:
116
+ toBeHex(destAmounts[token]!, 32),
120
117
  },
121
118
  }
122
119
  }
123
120
 
121
+ const senderBytes = getAddressBytes(message.sender ?? '0x')
124
122
  const receiverMsg: Any2EVMMessage = {
125
123
  ...message,
126
124
  destTokenAmounts: message.destTokenAmounts ?? [],
127
- sender: zeroPadValue(getAddressBytes(message.sender ?? '0x'), 32),
125
+ sender: senderBytes.length < 32 ? zeroPadValue(senderBytes, 32) : hexlify(senderBytes),
128
126
  data: hexlify(getDataBytes(message.data || '0x')),
129
127
  sourceChainSelector: message.sourceChainSelector,
130
128
  }
@@ -138,7 +136,7 @@ export async function estimateExecGas({
138
136
  (await provider.send('eth_estimateGas', [
139
137
  {
140
138
  from: router,
141
- to: receiver,
139
+ to: message.receiver,
142
140
  data: calldata,
143
141
  },
144
142
  'latest',