@chainlink/ccip-sdk 0.95.0 → 0.96.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/README.md +2 -2
  2. package/dist/all-chains.d.ts +23 -0
  3. package/dist/all-chains.d.ts.map +1 -0
  4. package/dist/all-chains.js +24 -0
  5. package/dist/all-chains.js.map +1 -0
  6. package/dist/api/index.d.ts +15 -12
  7. package/dist/api/index.d.ts.map +1 -1
  8. package/dist/api/index.js +20 -16
  9. package/dist/api/index.js.map +1 -1
  10. package/dist/api/types.d.ts +25 -29
  11. package/dist/api/types.d.ts.map +1 -1
  12. package/dist/aptos/index.d.ts +33 -8
  13. package/dist/aptos/index.d.ts.map +1 -1
  14. package/dist/aptos/index.js +74 -41
  15. package/dist/aptos/index.js.map +1 -1
  16. package/dist/chain.d.ts +220 -41
  17. package/dist/chain.d.ts.map +1 -1
  18. package/dist/chain.js +105 -15
  19. package/dist/chain.js.map +1 -1
  20. package/dist/errors/codes.d.ts +2 -0
  21. package/dist/errors/codes.d.ts.map +1 -1
  22. package/dist/errors/codes.js +2 -0
  23. package/dist/errors/codes.js.map +1 -1
  24. package/dist/errors/index.d.ts +1 -1
  25. package/dist/errors/index.d.ts.map +1 -1
  26. package/dist/errors/index.js +1 -1
  27. package/dist/errors/index.js.map +1 -1
  28. package/dist/errors/recovery.d.ts.map +1 -1
  29. package/dist/errors/recovery.js +2 -0
  30. package/dist/errors/recovery.js.map +1 -1
  31. package/dist/errors/specialized.d.ts +12 -6
  32. package/dist/errors/specialized.d.ts.map +1 -1
  33. package/dist/errors/specialized.js +19 -7
  34. package/dist/errors/specialized.js.map +1 -1
  35. package/dist/evm/extra-args.d.ts +25 -0
  36. package/dist/evm/extra-args.d.ts.map +1 -0
  37. package/dist/evm/extra-args.js +328 -0
  38. package/dist/evm/extra-args.js.map +1 -0
  39. package/dist/evm/gas.d.ts.map +1 -1
  40. package/dist/evm/gas.js +7 -12
  41. package/dist/evm/gas.js.map +1 -1
  42. package/dist/evm/index.d.ts +70 -24
  43. package/dist/evm/index.d.ts.map +1 -1
  44. package/dist/evm/index.js +72 -91
  45. package/dist/evm/index.js.map +1 -1
  46. package/dist/execution.d.ts.map +1 -1
  47. package/dist/execution.js +16 -2
  48. package/dist/execution.js.map +1 -1
  49. package/dist/extra-args.d.ts +103 -4
  50. package/dist/extra-args.d.ts.map +1 -1
  51. package/dist/extra-args.js +28 -3
  52. package/dist/extra-args.js.map +1 -1
  53. package/dist/gas.d.ts +6 -3
  54. package/dist/gas.d.ts.map +1 -1
  55. package/dist/gas.js +14 -6
  56. package/dist/gas.js.map +1 -1
  57. package/dist/index.d.ts +10 -9
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +8 -8
  60. package/dist/index.js.map +1 -1
  61. package/dist/requests.d.ts +17 -9
  62. package/dist/requests.d.ts.map +1 -1
  63. package/dist/requests.js +17 -9
  64. package/dist/requests.js.map +1 -1
  65. package/dist/selectors.d.ts.map +1 -1
  66. package/dist/selectors.js +12 -0
  67. package/dist/selectors.js.map +1 -1
  68. package/dist/solana/index.d.ts +70 -15
  69. package/dist/solana/index.d.ts.map +1 -1
  70. package/dist/solana/index.js +72 -16
  71. package/dist/solana/index.js.map +1 -1
  72. package/dist/sui/index.d.ts +37 -9
  73. package/dist/sui/index.d.ts.map +1 -1
  74. package/dist/sui/index.js +40 -11
  75. package/dist/sui/index.js.map +1 -1
  76. package/dist/ton/index.d.ts +65 -19
  77. package/dist/ton/index.d.ts.map +1 -1
  78. package/dist/ton/index.js +155 -25
  79. package/dist/ton/index.js.map +1 -1
  80. package/dist/ton/send.d.ts +52 -0
  81. package/dist/ton/send.d.ts.map +1 -0
  82. package/dist/ton/send.js +166 -0
  83. package/dist/ton/send.js.map +1 -0
  84. package/dist/types.d.ts +102 -1
  85. package/dist/types.d.ts.map +1 -1
  86. package/dist/types.js.map +1 -1
  87. package/dist/utils.d.ts +15 -3
  88. package/dist/utils.d.ts.map +1 -1
  89. package/dist/utils.js +19 -6
  90. package/dist/utils.js.map +1 -1
  91. package/package.json +12 -7
  92. package/src/all-chains.ts +26 -0
  93. package/src/api/index.ts +26 -25
  94. package/src/api/types.ts +25 -30
  95. package/src/aptos/index.ts +79 -43
  96. package/src/chain.ts +274 -46
  97. package/src/errors/codes.ts +2 -0
  98. package/src/errors/index.ts +1 -1
  99. package/src/errors/recovery.ts +2 -0
  100. package/src/errors/specialized.ts +24 -7
  101. package/src/evm/extra-args.ts +377 -0
  102. package/src/evm/gas.ts +14 -13
  103. package/src/evm/index.ts +76 -125
  104. package/src/execution.ts +18 -2
  105. package/src/extra-args.ts +108 -4
  106. package/src/gas.ts +16 -9
  107. package/src/index.ts +12 -9
  108. package/src/requests.ts +17 -9
  109. package/src/selectors.ts +12 -0
  110. package/src/solana/index.ts +72 -16
  111. package/src/sui/index.ts +40 -11
  112. package/src/ton/index.ts +192 -27
  113. package/src/ton/send.ts +222 -0
  114. package/src/types.ts +103 -1
  115. package/src/utils.ts +19 -6
package/src/chain.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  CCIPApiClientNotAvailableError,
9
9
  CCIPChainFamilyMismatchError,
10
10
  CCIPExecTxRevertedError,
11
+ CCIPTokenPoolChainConfigNotFoundError,
11
12
  CCIPTransactionNotFinalizedError,
12
13
  } from './errors/index.ts'
13
14
  import { DEFAULT_GAS_LIMIT } from './evm/const.ts'
@@ -16,6 +17,7 @@ import type {
16
17
  EVMExtraArgsV1,
17
18
  EVMExtraArgsV2,
18
19
  ExtraArgs,
20
+ GenericExtraArgsV3,
19
21
  SVMExtraArgsV1,
20
22
  SuiExtraArgsV1,
21
23
  } from './extra-args.ts'
@@ -43,7 +45,24 @@ import {
43
45
  type WithLogger,
44
46
  ExecutionState,
45
47
  } from './types.ts'
46
- import { util, withRetry } from './utils.ts'
48
+ import { networkInfo, util, withRetry } from './utils.ts'
49
+
50
+ /** Field names unique to GenericExtraArgsV3 (not present in V2). */
51
+ const V3_ONLY_FIELDS = [
52
+ 'blockConfirmations',
53
+ 'ccvs',
54
+ 'ccvArgs',
55
+ 'executor',
56
+ 'executorArgs',
57
+ 'tokenReceiver',
58
+ 'tokenArgs',
59
+ ] as const
60
+
61
+ /** Check if extraArgs contains any V3-only fields. */
62
+ function hasV3ExtraArgs(extraArgs: Partial<ExtraArgs> | undefined): boolean {
63
+ if (!extraArgs) return false
64
+ return V3_ONLY_FIELDS.some((field) => field in extraArgs)
65
+ }
47
66
 
48
67
  /**
49
68
  * Context for Chain class initialization.
@@ -71,8 +90,7 @@ export type ChainContext = WithLogger & {
71
90
  /**
72
91
  * CCIP API client instance for lane information queries.
73
92
  *
74
- * - `undefined` (default): Creates CCIPAPIClient with production endpoint
75
- * (https://api.ccip.chain.link)
93
+ * - `undefined` (default): Creates CCIPAPIClient with {@link DEFAULT_API_BASE_URL}
76
94
  * - `CCIPAPIClient`: Uses provided instance (allows custom URL, fetch, etc.)
77
95
  * - `null`: Disables API client entirely (getLaneLatency() will throw)
78
96
  *
@@ -164,24 +182,51 @@ export type GetBalanceOpts = {
164
182
 
165
183
  /**
166
184
  * Rate limiter state for token pool configurations.
167
- * Null if rate limiting is disabled.
185
+ *
186
+ * @remarks
187
+ * - Returns the rate limiter bucket state when rate limiting is **enabled**
188
+ * - Returns `null` when rate limiting is **disabled** (unlimited throughput)
189
+ *
190
+ * @example Handling nullable state
191
+ * ```typescript
192
+ * const remote = await chain.getTokenPoolRemotes(poolAddress)
193
+ * const state = remote['ethereum-mainnet'].inboundRateLimiterState
194
+ *
195
+ * if (state === null) {
196
+ * console.log('Rate limiting disabled - unlimited throughput')
197
+ * } else {
198
+ * console.log(`Capacity: ${state.capacity}, Available: ${state.tokens}`)
199
+ * }
200
+ * ```
168
201
  */
169
202
  export type RateLimiterState = {
170
203
  /** Current token balance in the rate limiter bucket. */
171
204
  tokens: bigint
172
205
  /** Maximum capacity of the rate limiter bucket. */
173
206
  capacity: bigint
174
- /** Rate at which tokens are replenished. */
207
+ /** Rate at which tokens are replenished (tokens per second). */
175
208
  rate: bigint
176
209
  } | null
177
210
 
178
211
  /**
179
- * Remote token pool configuration for a specific chain.
212
+ * Remote token pool configuration for a specific destination chain.
213
+ *
214
+ * @remarks
215
+ * Each entry represents the configuration needed to transfer tokens
216
+ * from the current chain to a specific destination chain.
180
217
  */
181
218
  export type TokenPoolRemote = {
182
219
  /** Address of the remote token on the destination chain. */
183
220
  remoteToken: string
184
- /** Addresses of remote token pools. */
221
+ /**
222
+ * Addresses of remote token pools on the destination chain.
223
+ *
224
+ * @remarks
225
+ * Multiple pools may exist for:
226
+ * - Redundancy (failover if one pool is unavailable)
227
+ * - Capacity aggregation across pools
228
+ * - Version management (different pool implementations)
229
+ */
185
230
  remotePools: string[]
186
231
  /** Inbound rate limiter state for tokens coming into this chain. */
187
232
  inboundRateLimiterState: RateLimiterState
@@ -189,6 +234,43 @@ export type TokenPoolRemote = {
189
234
  outboundRateLimiterState: RateLimiterState
190
235
  }
191
236
 
237
+ /**
238
+ * Token pool configuration returned by {@link Chain.getTokenPoolConfig}.
239
+ *
240
+ * @remarks
241
+ * Contains the core configuration of a token pool including the token it manages,
242
+ * the router it's registered with, and optionally its version identifier.
243
+ */
244
+ export type TokenPoolConfig = {
245
+ /** Address of the token managed by this pool. */
246
+ token: string
247
+ /** Address of the CCIP router this pool is registered with. */
248
+ router: string
249
+ /**
250
+ * Version identifier string (e.g., "BurnMintTokenPool 1.5.1").
251
+ *
252
+ * @remarks
253
+ * May be undefined for older pool implementations that don't expose this method.
254
+ */
255
+ typeAndVersion?: string
256
+ }
257
+
258
+ /**
259
+ * Token configuration from a TokenAdminRegistry, returned by {@link Chain.getRegistryTokenConfig}.
260
+ *
261
+ * @remarks
262
+ * The TokenAdminRegistry tracks which administrator controls each token
263
+ * and which pool is authorized to handle transfers.
264
+ */
265
+ export type RegistryTokenConfig = {
266
+ /** Address of the current administrator for this token. */
267
+ administrator: string
268
+ /** Address of pending administrator (if ownership transfer is in progress). */
269
+ pendingAdministrator?: string
270
+ /** Address of the token pool authorized to handle this token's transfers. */
271
+ tokenPool?: string
272
+ }
273
+
192
274
  /**
193
275
  * Maps chain family to respective unsigned transaction type.
194
276
  */
@@ -202,7 +284,7 @@ export type UnsignedTx = {
202
284
  }
203
285
 
204
286
  /**
205
- * Common options for [[getFee]], [[generateUnsignedSendMessage]] and [[sendMessage]] Chain methods
287
+ * Common options for {@link Chain.getFee}, {@link Chain.generateUnsignedSendMessage} and {@link Chain.sendMessage} methods.
206
288
  */
207
289
  export type SendMessageOpts = {
208
290
  /** Router address on this chain */
@@ -216,7 +298,7 @@ export type SendMessageOpts = {
216
298
  }
217
299
 
218
300
  /**
219
- * Common options for [[generateUnsignedExecuteReport]] and [[executeReport]] Chain methods
301
+ * Common options for {@link Chain.generateUnsignedExecuteReport} and {@link Chain.executeReport} methods.
220
302
  */
221
303
  export type ExecuteReportOpts = {
222
304
  /** address of the OffRamp contract */
@@ -249,6 +331,7 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
249
331
  * Base constructor for Chain class.
250
332
  * @param network - NetworkInfo object for the Chain instance
251
333
  * @param ctx - Optional context with logger and API client configuration
334
+ * @throws {@link CCIPChainFamilyMismatchError} if network family doesn't match the Chain subclass
252
335
  */
253
336
  constructor(network: NetworkInfo, ctx?: ChainContext) {
254
337
  const { logger = console, apiClient, apiRetryConfig } = ctx ?? {}
@@ -287,17 +370,21 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
287
370
  * Fetch the timestamp of a given block
288
371
  * @param block - positive block number, negative finality depth or 'finalized' tag
289
372
  * @returns timestamp of the block, in seconds
373
+ * @throws {@link CCIPBlockNotFoundError} if block does not exist
290
374
  */
291
375
  abstract getBlockTimestamp(block: number | 'finalized'): Promise<number>
292
376
  /**
293
377
  * Fetch a transaction by its hash
294
378
  * @param hash - transaction hash
295
379
  * @returns generic transaction details
380
+ * @throws {@link CCIPTransactionNotFoundError} if transaction not found
296
381
  */
297
382
  abstract getTransaction(hash: string): Promise<ChainTransaction>
298
383
  /**
299
- * Confirm a log tx is finalized or wait for it to be finalized
300
- * Throws if it isn't included (e.g. a reorg)
384
+ * Confirm a log tx is finalized or wait for it to be finalized.
385
+ * @param opts - Options containing the request, finality level, and optional cancel promise
386
+ * @returns true when the transaction is finalized
387
+ * @throws {@link CCIPTransactionNotFinalizedError} if the transaction is not included (e.g., due to a reorg)
301
388
  */
302
389
  async waitFinalized({
303
390
  request: { log, tx },
@@ -359,6 +446,9 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
359
446
  * - `page`: if provided, try to use this page/range for batches
360
447
  * - `watch`: true or cancellation promise, getLogs continuously after initial fetch
361
448
  * @returns An async iterable iterator of logs.
449
+ * @throws {@link CCIPLogsWatchRequiresFinalityError} if watch mode is used without a finality endBlock tag
450
+ * @throws {@link CCIPLogsWatchRequiresStartError} if watch mode is used without startBlock or startTime
451
+ * @throws {@link CCIPLogsAddressRequiredError} if address is required but not provided (chain-specific)
362
452
  */
363
453
  abstract getLogs(opts: LogFilter): AsyncIterableIterator<Log_>
364
454
 
@@ -366,7 +456,8 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
366
456
  * Fetch all CCIP requests in a transaction
367
457
  * @param tx - ChainTransaction or txHash to fetch requests from
368
458
  * @returns CCIP messages in the transaction (at least one)
369
- **/
459
+ * @throws {@link CCIPMessageNotFoundInTxError} if no CCIP messages found in transaction
460
+ */
370
461
  async getMessagesInTx(tx: string | ChainTransaction): Promise<CCIPRequest[]> {
371
462
  const txHash = typeof tx === 'string' ? tx : tx.hash
372
463
  try {
@@ -396,12 +487,31 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
396
487
  }
397
488
 
398
489
  /**
399
- * Fetch a message by ID.
400
- * Default implementation just tries API.
401
- * Children may override to fetch from chain as fallback
402
- * @param messageId - message ID to fetch request for
403
- * @param _opts - onRamp may be required in some implementations, and throw if missing
404
- * @returns CCIPRequest
490
+ * Fetch a CCIP message by its unique message ID.
491
+ *
492
+ * @remarks
493
+ * Uses the CCIP API to retrieve message details. The returned request includes
494
+ * a `metadata` field with API-specific information.
495
+ *
496
+ * @example
497
+ * ```typescript
498
+ * const request = await chain.getMessageById(messageId)
499
+ * console.log(`Sender: ${request.message.sender}`)
500
+ *
501
+ * if (request.metadata) {
502
+ * console.log(`Status: ${request.metadata.status}`)
503
+ * if (request.metadata.deliveryTime) {
504
+ * console.log(`Delivered in ${request.metadata.deliveryTime}ms`)
505
+ * }
506
+ * }
507
+ * ```
508
+ *
509
+ * @param messageId - The unique message ID (0x + 64 hex chars)
510
+ * @param _opts - Optional: `onRamp` hint for non-EVM chains
511
+ * @returns CCIPRequest with `metadata` populated from API
512
+ * @throws {@link CCIPApiClientNotAvailableError} if API disabled
513
+ * @throws {@link CCIPMessageIdNotFoundError} if message not found
514
+ * @throws {@link CCIPHttpError} if API request fails
405
515
  **/
406
516
  async getMessageById(
407
517
  messageId: string,
@@ -420,6 +530,8 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
420
530
  * @param request - CCIPRequest to fetch batch for.
421
531
  * @param commit - CommitReport range (min, max).
422
532
  * @param opts - Optional parameters (e.g., `page` for pagination width).
533
+ * @returns Array of messages in the batch.
534
+ * @throws {@link CCIPMessageBatchIncompleteError} if not all messages in range could be fetched
423
535
  */
424
536
  abstract getMessagesInBatch<
425
537
  R extends PickDeep<
@@ -438,6 +550,7 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
438
550
  * @returns version - parsed version of the contract, e.g. `1.6.0`
439
551
  * @returns typeAndVersion - original (unparsed) typeAndVersion() string
440
552
  * @returns suffix - suffix of the version, if any (e.g. `-dev`)
553
+ * @throws {@link CCIPTypeVersionInvalidError} if contract doesn't have valid typeAndVersion
441
554
  */
442
555
  abstract typeAndVersion(
443
556
  address: string,
@@ -532,12 +645,14 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
532
645
  /**
533
646
  * Fetch TokenAdminRegistry configured in a given OnRamp, Router, etc
534
647
  * Needed to map a source token to its dest counterparts
535
- * @param onRamp - Some contract for which we can fetch a TokenAdminRegistry
648
+ * @param address - Some contract for which we can fetch a TokenAdminRegistry (OnRamp, Router, etc.)
649
+ * @returns TokenAdminRegistry address
536
650
  */
537
651
  abstract getTokenAdminRegistryFor(address: string): Promise<string>
538
652
  /**
539
653
  * Fetch the current fee for a given intended message
540
654
  * @param opts - {@link SendMessageOpts} without approveMax
655
+ * @returns Fee amount in the feeToken's smallest units
541
656
  */
542
657
  abstract getFee(opts: Omit<SendMessageOpts, 'approveMax'>): Promise<bigint>
543
658
  /**
@@ -555,6 +670,7 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
555
670
  * Send a CCIP message through a router using provided wallet.
556
671
  * @param opts - {@link SendMessageOpts} with chain-specific wallet for signing
557
672
  * @returns CCIP request
673
+ * @throws {@link CCIPWalletNotSignerError} if wallet cannot sign transactions
558
674
  *
559
675
  * @example
560
676
  * ```typescript
@@ -620,6 +736,8 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
620
736
  * })
621
737
  * console.log(`Message ID: ${request.message.messageId}`)
622
738
  * ```
739
+ * @throws {@link CCIPWalletNotSignerError} if wallet cannot sign transactions
740
+ * @throws {@link CCIPExecTxNotConfirmedError} if execution transaction fails to confirm
623
741
  */
624
742
  abstract executeReport(
625
743
  opts: ExecuteReportOpts & {
@@ -632,8 +750,9 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
632
750
  * Look for a CommitReport at dest for given CCIP request
633
751
  * May be specialized by some subclasses
634
752
  * @param opts - getCommitReport options
635
- * @returns CCIPCommit info, or reject if none found
636
- **/
753
+ * @returns CCIPCommit info
754
+ * @throws {@link CCIPCommitNotFoundError} if no commit found for the request
755
+ */
637
756
  async getCommitReport({
638
757
  commitStore,
639
758
  request,
@@ -739,6 +858,7 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
739
858
  * @internal
740
859
  * @param tx - transaction hash or transaction object
741
860
  * @returns CCIP execution object
861
+ * @throws {@link CCIPExecTxRevertedError} if no execution receipt found in transaction
742
862
  */
743
863
  async getExecutionReceiptInTx(tx: string | ChainTransaction): Promise<CCIPExecution> {
744
864
  if (typeof tx === 'string') tx = await this.getTransaction(tx)
@@ -761,39 +881,125 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
761
881
  abstract getSupportedTokens(address: string, opts?: { page?: number }): Promise<string[]>
762
882
 
763
883
  /**
764
- * Get TokenConfig for a given token address in a TokenAdminRegistry
765
- * @param address - TokenAdminRegistry contract address
766
- * @param token - Token address
884
+ * Fetch token configuration from a TokenAdminRegistry.
885
+ *
886
+ * @remarks
887
+ * The TokenAdminRegistry is a contract that tracks token administrators and their
888
+ * associated pools. Each token has an administrator who can update pool configurations.
889
+ *
890
+ * @example Query a token's registry configuration
891
+ * ```typescript
892
+ * const config = await chain.getRegistryTokenConfig(registryAddress, tokenAddress)
893
+ * console.log(`Administrator: ${config.administrator}`)
894
+ * if (config.tokenPool) {
895
+ * console.log(`Pool: ${config.tokenPool}`)
896
+ * }
897
+ * ```
898
+ *
899
+ * @param registry - TokenAdminRegistry contract address.
900
+ * @param token - Token address to query.
901
+ * @returns {@link RegistryTokenConfig} containing administrator and pool information.
902
+ * @throws {@link CCIPTokenNotInRegistryError} if token is not registered.
767
903
  */
768
- abstract getRegistryTokenConfig(
769
- registry: string,
770
- token: string,
771
- ): Promise<{
772
- administrator: string
773
- pendingAdministrator?: string
774
- tokenPool?: string
775
- }>
904
+ abstract getRegistryTokenConfig(registry: string, token: string): Promise<RegistryTokenConfig>
776
905
 
777
906
  /**
778
- * Get TokenPool state and configurations
779
- * @param tokenPool - Token pool address
907
+ * Fetch configuration of a token pool.
908
+ *
909
+ * @remarks
910
+ * Returns the core configuration of a token pool including which token it manages
911
+ * and which router it's registered with.
912
+ *
913
+ * @example Query pool configuration
914
+ * ```typescript
915
+ * const config = await chain.getTokenPoolConfig(poolAddress)
916
+ * console.log(`Manages token: ${config.token}`)
917
+ * console.log(`Router: ${config.router}`)
918
+ * if (config.typeAndVersion) {
919
+ * console.log(`Version: ${config.typeAndVersion}`)
920
+ * }
921
+ * ```
922
+ *
923
+ * @param tokenPool - Token pool contract address.
924
+ * @returns {@link TokenPoolConfig} containing token, router, and version info.
780
925
  */
781
- abstract getTokenPoolConfigs(tokenPool: string): Promise<{
782
- token: string
783
- router: string
784
- typeAndVersion?: string
785
- }>
926
+ abstract getTokenPoolConfig(tokenPool: string): Promise<TokenPoolConfig>
786
927
 
787
928
  /**
788
- * Get TokenPool remote configurations.
789
- * @param tokenPool - Token pool address.
790
- * @param remoteChainSelector - If provided, only return remotes for the specified chain (may error if remote not supported).
791
- * @returns Record of network names and remote configurations (remoteToken, remotePools, rateLimitStates).
929
+ * Fetch remote chain configurations for a token pool.
930
+ *
931
+ * @remarks
932
+ * A token pool maintains configurations for each destination chain it supports.
933
+ * The returned Record maps chain names to their respective configurations.
934
+ *
935
+ * @example Get all supported destinations
936
+ * ```typescript
937
+ * const remotes = await chain.getTokenPoolRemotes(poolAddress)
938
+ * // Returns: {
939
+ * // "ethereum-mainnet": { remoteToken: "0x...", remotePools: [...], ... },
940
+ * // "ethereum-mainnet-arbitrum-1": { remoteToken: "0x...", remotePools: [...], ... },
941
+ * // "solana-mainnet": { remoteToken: "...", remotePools: [...], ... }
942
+ * // }
943
+ *
944
+ * // Access a specific chain's config
945
+ * const arbConfig = remotes['ethereum-mainnet']
946
+ * console.log(`Remote token: ${arbConfig.remoteToken}`)
947
+ * ```
948
+ *
949
+ * @example Filter to a specific destination
950
+ * ```typescript
951
+ * import { networkInfo } from '@chainlink/ccip-sdk'
952
+ *
953
+ * const arbitrumSelector = 4949039107694359620n
954
+ * const remotes = await chain.getTokenPoolRemotes(poolAddress, arbitrumSelector)
955
+ * // Returns only: { "arbitrum-mainnet": { ... } }
956
+ *
957
+ * const chainName = networkInfo(arbitrumSelector).name
958
+ * const config = remotes[chainName]
959
+ * ```
960
+ *
961
+ * @param tokenPool - Token pool address on the current chain.
962
+ * @param remoteChainSelector - Optional chain selector to filter results to a single destination.
963
+ * @returns Record where keys are chain names (e.g., "ethereum-mainnet") and values are {@link TokenPoolRemote} configs.
964
+ * @throws {@link CCIPTokenPoolChainConfigNotFoundError} if remoteChainSelector is specified but not configured.
792
965
  */
793
966
  abstract getTokenPoolRemotes(
794
967
  tokenPool: string,
795
968
  remoteChainSelector?: bigint,
796
969
  ): Promise<Record<string, TokenPoolRemote>>
970
+ /**
971
+ * Fetch remote chain configuration for a token pool for a specific destination.
972
+ *
973
+ * @remarks
974
+ * Convenience wrapper around {@link getTokenPoolRemotes} that returns a single
975
+ * configuration instead of a Record. Use this when you need configuration for
976
+ * a specific destination chain.
977
+ *
978
+ * @example
979
+ * ```typescript
980
+ * const arbitrumSelector = 4949039107694359620n
981
+ * const remote = await chain.getTokenPoolRemote(poolAddress, arbitrumSelector)
982
+ * console.log(`Remote token: ${remote.remoteToken}`)
983
+ * console.log(`Remote pools: ${remote.remotePools.join(', ')}`)
984
+ * ```
985
+ *
986
+ * @param tokenPool - Token pool address on the current chain.
987
+ * @param remoteChainSelector - Chain selector of the desired remote chain.
988
+ * @returns TokenPoolRemote config for the specified remote chain.
989
+ * @throws {@link CCIPTokenPoolChainConfigNotFoundError} if no configuration found for the specified remote chain.
990
+ */
991
+ async getTokenPoolRemote(
992
+ tokenPool: string,
993
+ remoteChainSelector: bigint,
994
+ ): Promise<TokenPoolRemote> {
995
+ const remotes = await this.getTokenPoolRemotes(tokenPool, remoteChainSelector)
996
+ const network = networkInfo(remoteChainSelector)
997
+ const remoteConfig = remotes[network.name]
998
+ if (!remoteConfig) {
999
+ throw new CCIPTokenPoolChainConfigNotFoundError(tokenPool, tokenPool, network.name)
1000
+ }
1001
+ return remoteConfig
1002
+ }
797
1003
 
798
1004
  /**
799
1005
  * Fetch list and info of supported feeTokens.
@@ -806,11 +1012,32 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
806
1012
  static buildMessageForDest(
807
1013
  message: Parameters<ChainStatic['buildMessageForDest']>[0],
808
1014
  ): AnyMessage {
809
- // default to GenericExtraArgsV2, aka EVMExtraArgsV2
1015
+ const gasLimit = message.data && dataLength(message.data) ? DEFAULT_GAS_LIMIT : 0n
1016
+
1017
+ // Detect if user wants V3 by checking for any V3-only field
1018
+ if (hasV3ExtraArgs(message.extraArgs)) {
1019
+ // V3 defaults (GenericExtraArgsV3)
1020
+ return {
1021
+ ...message,
1022
+ extraArgs: {
1023
+ gasLimit,
1024
+ blockConfirmations: 0,
1025
+ ccvs: [],
1026
+ ccvArgs: [],
1027
+ executor: '',
1028
+ executorArgs: '0x',
1029
+ tokenReceiver: '',
1030
+ tokenArgs: '0x',
1031
+ ...message.extraArgs,
1032
+ },
1033
+ }
1034
+ }
1035
+
1036
+ // Default to V2 (GenericExtraArgsV2, aka EVMExtraArgsV2)
810
1037
  return {
811
1038
  ...message,
812
1039
  extraArgs: {
813
- gasLimit: message.data && dataLength(message.data) ? DEFAULT_GAS_LIMIT : 0n,
1040
+ gasLimit,
814
1041
  allowOutOfOrderExecution: true,
815
1042
  ...message.extraArgs,
816
1043
  },
@@ -868,6 +1095,7 @@ export type ChainStatic<F extends ChainFamily = ChainFamily> = Function & {
868
1095
  ):
869
1096
  | (EVMExtraArgsV1 & { _tag: 'EVMExtraArgsV1' })
870
1097
  | (EVMExtraArgsV2 & { _tag: 'EVMExtraArgsV2' })
1098
+ | (GenericExtraArgsV3 & { _tag: 'GenericExtraArgsV3' })
871
1099
  | (SVMExtraArgsV1 & { _tag: 'SVMExtraArgsV1' })
872
1100
  | (SuiExtraArgsV1 & { _tag: 'SuiExtraArgsV1' })
873
1101
  | undefined
@@ -880,7 +1108,7 @@ export type ChainStatic<F extends ChainFamily = ChainFamily> = Function & {
880
1108
  */
881
1109
  decodeCommits(log: Pick<Log_, 'data'>, lane?: Lane): CommitReport[] | undefined
882
1110
  /**
883
- * Decode a receipt (ExecutioStateChanged) event
1111
+ * Decode a receipt (ExecutionStateChanged) event
884
1112
  * @param log - Chain generic log
885
1113
  * @returns ExecutionReceipt or undefined if not a recognized receipt
886
1114
  */
@@ -80,6 +80,7 @@ export const CCIPErrorCode = {
80
80
  TOKEN_NOT_IN_REGISTRY: 'TOKEN_NOT_IN_REGISTRY',
81
81
  TOKEN_NOT_CONFIGURED: 'TOKEN_NOT_CONFIGURED',
82
82
  TOKEN_NOT_REGISTERED: 'TOKEN_NOT_REGISTERED',
83
+ TOKEN_REMOTE_NOT_CONFIGURED: 'TOKEN_REMOTE_NOT_CONFIGURED',
83
84
  TOKEN_DECIMALS_INSUFFICIENT: 'TOKEN_DECIMALS_INSUFFICIENT',
84
85
  TOKEN_INVALID_SPL: 'TOKEN_INVALID_SPL',
85
86
  TOKEN_DATA_PARSE_FAILED: 'TOKEN_DATA_PARSE_FAILED',
@@ -153,6 +154,7 @@ export const CCIPErrorCode = {
153
154
 
154
155
  // CLI & Validation
155
156
  ARGUMENT_INVALID: 'ARGUMENT_INVALID',
157
+ INSUFFICIENT_BALANCE: 'INSUFFICIENT_BALANCE',
156
158
 
157
159
  // Internal
158
160
  NOT_IMPLEMENTED: 'NOT_IMPLEMENTED',
@@ -179,7 +179,7 @@ export { CCIPAddressInvalidEvmError } from './specialized.ts'
179
179
  export { CCIPSourceChainUnsupportedError } from './specialized.ts'
180
180
 
181
181
  // Specialized errors - CLI & Validation
182
- export { CCIPArgumentInvalidError } from './specialized.ts'
182
+ export { CCIPArgumentInvalidError, CCIPInsufficientBalanceError } from './specialized.ts'
183
183
 
184
184
  // HTTP Status codes (re-exported from root)
185
185
  export { HttpStatus, isServerError, isTransientHttpStatus } from '../http-status.ts'
@@ -96,6 +96,7 @@ export const DEFAULT_RECOVERY_HINTS: Partial<Record<CCIPErrorCode, string>> = {
96
96
  TOKEN_NOT_IN_REGISTRY: 'Token not found in TokenAdminRegistry.',
97
97
  TOKEN_NOT_CONFIGURED: 'Token is not configured in the registry.',
98
98
  TOKEN_NOT_REGISTERED: 'Token is not registered in the TokenAdminRegistry.',
99
+ TOKEN_REMOTE_NOT_CONFIGURED: 'Remote network is not registered in TokenPool.',
99
100
  TOKEN_DECIMALS_INSUFFICIENT: 'Destination token has insufficient decimals.',
100
101
  TOKEN_INVALID_SPL: 'Invalid SPL token or Token-2022.',
101
102
  TOKEN_DATA_PARSE_FAILED:
@@ -178,6 +179,7 @@ export const DEFAULT_RECOVERY_HINTS: Partial<Record<CCIPErrorCode, string>> = {
178
179
  BORSH_METHOD_UNKNOWN: 'Unknown Borsh method.',
179
180
 
180
181
  ARGUMENT_INVALID: 'Check the command-line argument format and requirements.',
182
+ INSUFFICIENT_BALANCE: 'Fund the wallet to cover the transaction fee.',
181
183
 
182
184
  NOT_IMPLEMENTED: 'This feature is not yet implemented.',
183
185
  UNKNOWN: 'An unknown error occurred. Check the error details.',
@@ -30,10 +30,10 @@ export class CCIPChainFamilyUnsupportedError extends CCIPError {
30
30
  }
31
31
  }
32
32
 
33
- /** Thrown when some method/operation is not supported on a given implementaiton class. */
33
+ /** Thrown when some method/operation is not supported on a given implementation class. */
34
34
  export class CCIPMethodUnsupportedError extends CCIPError {
35
35
  override readonly name = 'CCIPMethodUnsupportedError'
36
- /** Creates a method nsupported error. */
36
+ /** Creates a method unsupported error. */
37
37
  constructor(klass: string, method: string, options?: CCIPErrorOptions) {
38
38
  super(CCIPErrorCode.METHOD_UNSUPPORTED, `Unsupported method in class: ${klass}.${method}`, {
39
39
  ...options,
@@ -776,7 +776,7 @@ export class CCIPLogTopicsNotFoundError extends CCIPError {
776
776
  /** Thrown when trying to `watch` logs but giving a fixed `endBlock` */
777
777
  export class CCIPLogsWatchRequiresFinalityError extends CCIPError {
778
778
  override readonly name = 'CCIPLogsWatchRequiresFinalityError'
779
- /** Creates a block not found error. */
779
+ /** Creates a watch requires finality error. */
780
780
  constructor(endBlock?: number | string, options?: CCIPErrorOptions) {
781
781
  super(
782
782
  CCIPErrorCode.LOGS_WATCH_REQUIRES_FINALITY,
@@ -786,10 +786,10 @@ export class CCIPLogsWatchRequiresFinalityError extends CCIPError {
786
786
  }
787
787
  }
788
788
 
789
- /** Thrown when trying to `watch` logs but giving a fixed `endBlock` */
789
+ /** Thrown when trying to `watch` logs without a start point. */
790
790
  export class CCIPLogsWatchRequiresStartError extends CCIPError {
791
791
  override readonly name = 'CCIPLogsWatchRequiresStartError'
792
- /** Creates a block not found error. */
792
+ /** Creates a watch requires start error. */
793
793
  constructor(options?: CCIPErrorOptions) {
794
794
  super(CCIPErrorCode.LOGS_WATCH_REQUIRES_START, `Watch mode requires startBlock or startTime`, {
795
795
  ...options,
@@ -1255,7 +1255,7 @@ export class CCIPTokenPoolChainConfigNotFoundError extends CCIPError {
1255
1255
  options?: CCIPErrorOptions,
1256
1256
  ) {
1257
1257
  super(
1258
- CCIPErrorCode.TOKEN_NOT_CONFIGURED,
1258
+ CCIPErrorCode.TOKEN_REMOTE_NOT_CONFIGURED,
1259
1259
  `ChainConfig not found at ${address} for tokenPool=${tokenPool} and remoteNetwork=${remoteNetwork}`,
1260
1260
  {
1261
1261
  ...options,
@@ -1515,6 +1515,23 @@ export class CCIPTokenNotFoundError extends CCIPError {
1515
1515
  }
1516
1516
  }
1517
1517
 
1518
+ /** Thrown when account has insufficient balance for operation. */
1519
+ export class CCIPInsufficientBalanceError extends CCIPError {
1520
+ override readonly name = 'CCIPInsufficientBalanceError'
1521
+ /** Creates an insufficient balance error. */
1522
+ constructor(have: string, need: string, symbol: string, options?: CCIPErrorOptions) {
1523
+ super(
1524
+ CCIPErrorCode.INSUFFICIENT_BALANCE,
1525
+ `Insufficient balance: have ${have} ${symbol}, need ${need} ${symbol}`,
1526
+ {
1527
+ ...options,
1528
+ isTransient: false,
1529
+ context: { ...options?.context, have, need, symbol },
1530
+ },
1531
+ )
1532
+ }
1533
+ }
1534
+
1518
1535
  // Solana-specific (additional)
1519
1536
 
1520
1537
  /** Thrown when router config not found at PDA. */
@@ -1679,7 +1696,7 @@ export class CCIPSuiMessageVersionInvalidError extends CCIPError {
1679
1696
  /** Thrown when Sui log data is invalid. */
1680
1697
  export class CCIPSuiLogInvalidError extends CCIPError {
1681
1698
  override readonly name = 'CCIPSuiLogInvalidError'
1682
- /** Creates an Sui log invalid error. */
1699
+ /** Creates a Sui log invalid error. */
1683
1700
  constructor(log: unknown, options?: CCIPErrorOptions) {
1684
1701
  super(CCIPErrorCode.LOG_DATA_INVALID, `Invalid sui log: ${String(log)}`, {
1685
1702
  ...options,