@chainlink/ccip-sdk 1.3.0 → 1.4.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 (93) hide show
  1. package/dist/api/index.d.ts +17 -6
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +17 -6
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/aptos/index.d.ts +7 -1
  6. package/dist/aptos/index.d.ts.map +1 -1
  7. package/dist/aptos/index.js +20 -0
  8. package/dist/aptos/index.js.map +1 -1
  9. package/dist/chain.d.ts +64 -6
  10. package/dist/chain.d.ts.map +1 -1
  11. package/dist/chain.js +37 -2
  12. package/dist/chain.js.map +1 -1
  13. package/dist/errors/recovery.js +1 -1
  14. package/dist/errors/specialized.d.ts +17 -3
  15. package/dist/errors/specialized.d.ts.map +1 -1
  16. package/dist/errors/specialized.js +17 -3
  17. package/dist/errors/specialized.js.map +1 -1
  18. package/dist/evm/const.d.ts.map +1 -1
  19. package/dist/evm/const.js +3 -0
  20. package/dist/evm/const.js.map +1 -1
  21. package/dist/evm/errors.d.ts.map +1 -1
  22. package/dist/evm/errors.js +20 -5
  23. package/dist/evm/errors.js.map +1 -1
  24. package/dist/evm/index.d.ts +11 -2
  25. package/dist/evm/index.d.ts.map +1 -1
  26. package/dist/evm/index.js +65 -14
  27. package/dist/evm/index.js.map +1 -1
  28. package/dist/gas.d.ts +2 -1
  29. package/dist/gas.d.ts.map +1 -1
  30. package/dist/gas.js +2 -1
  31. package/dist/gas.js.map +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/offchain.d.ts +1 -0
  36. package/dist/offchain.d.ts.map +1 -1
  37. package/dist/offchain.js +1 -0
  38. package/dist/offchain.js.map +1 -1
  39. package/dist/requests.d.ts +6 -5
  40. package/dist/requests.d.ts.map +1 -1
  41. package/dist/requests.js +6 -5
  42. package/dist/requests.js.map +1 -1
  43. package/dist/solana/idl/1.6.0/FEE_QUOTER.d.ts +1719 -0
  44. package/dist/solana/idl/1.6.0/FEE_QUOTER.d.ts.map +1 -0
  45. package/dist/solana/idl/1.6.0/FEE_QUOTER.js +1719 -0
  46. package/dist/solana/idl/1.6.0/FEE_QUOTER.js.map +1 -0
  47. package/dist/solana/index.d.ts +17 -1
  48. package/dist/solana/index.d.ts.map +1 -1
  49. package/dist/solana/index.js +37 -9
  50. package/dist/solana/index.js.map +1 -1
  51. package/dist/solana/utils.d.ts +6 -3
  52. package/dist/solana/utils.d.ts.map +1 -1
  53. package/dist/solana/utils.js +17 -16
  54. package/dist/solana/utils.js.map +1 -1
  55. package/dist/sui/index.d.ts +1 -1
  56. package/dist/sui/index.d.ts.map +1 -1
  57. package/dist/sui/index.js +1 -1
  58. package/dist/sui/index.js.map +1 -1
  59. package/dist/ton/index.d.ts +11 -9
  60. package/dist/ton/index.d.ts.map +1 -1
  61. package/dist/ton/index.js +11 -9
  62. package/dist/ton/index.js.map +1 -1
  63. package/dist/ton/send.d.ts +7 -0
  64. package/dist/ton/send.d.ts.map +1 -1
  65. package/dist/ton/send.js +7 -0
  66. package/dist/ton/send.js.map +1 -1
  67. package/dist/types.d.ts +1 -1
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/utils.d.ts +9 -2
  70. package/dist/utils.d.ts.map +1 -1
  71. package/dist/utils.js +16 -5
  72. package/dist/utils.js.map +1 -1
  73. package/package.json +7 -7
  74. package/src/api/index.ts +17 -6
  75. package/src/aptos/index.ts +31 -0
  76. package/src/chain.ts +67 -7
  77. package/src/errors/recovery.ts +1 -1
  78. package/src/errors/specialized.ts +17 -3
  79. package/src/evm/const.ts +3 -0
  80. package/src/evm/errors.ts +22 -5
  81. package/src/evm/index.ts +102 -13
  82. package/src/gas.ts +2 -1
  83. package/src/index.ts +1 -0
  84. package/src/offchain.ts +1 -0
  85. package/src/requests.ts +6 -5
  86. package/src/solana/idl/1.6.0/FEE_QUOTER.ts +3440 -0
  87. package/src/solana/index.ts +55 -10
  88. package/src/solana/utils.ts +29 -19
  89. package/src/sui/index.ts +1 -1
  90. package/src/ton/index.ts +11 -9
  91. package/src/ton/send.ts +7 -0
  92. package/src/types.ts +1 -1
  93. package/src/utils.ts +19 -6
@@ -35,6 +35,7 @@ import {
35
35
  type LogFilter,
36
36
  type TokenInfo,
37
37
  type TokenPoolRemote,
38
+ type TokenPrice,
38
39
  type TokenTransferFeeOpts,
39
40
  Chain,
40
41
  } from '../chain.ts'
@@ -112,6 +113,7 @@ import { IDL as BURN_MINT_TOKEN_POOL } from './idl/1.6.0/BURN_MINT_TOKEN_POOL.ts
112
113
  import { IDL as CCIP_CCTP_TOKEN_POOL } from './idl/1.6.0/CCIP_CCTP_TOKEN_POOL.ts'
113
114
  import { IDL as CCIP_OFFRAMP_IDL } from './idl/1.6.0/CCIP_OFFRAMP.ts'
114
115
  import { IDL as CCIP_ROUTER_IDL } from './idl/1.6.0/CCIP_ROUTER.ts'
116
+ import { IDL as FEE_QUOTER_IDL } from './idl/1.6.0/FEE_QUOTER.ts'
115
117
  import { getTransactionsForAddress } from './logs.ts'
116
118
  import { generateUnsignedCcipSend, getFee } from './send.ts'
117
119
  import { type CCIPMessage_V1_6_Solana, type UnsignedSolanaTx, isWallet } from './types.ts'
@@ -1567,6 +1569,43 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
1567
1569
  )
1568
1570
  }
1569
1571
 
1572
+ /** {@inheritDoc Chain.getTokenPrice} */
1573
+ override async getTokenPrice(opts: {
1574
+ router: string
1575
+ token: string
1576
+ timestamp?: number
1577
+ }): Promise<TokenPrice> {
1578
+ if (opts.timestamp != null) {
1579
+ this.logger.warn(
1580
+ 'getTokenPrice: timestamp parameter not yet supported on Solana, returning latest price',
1581
+ )
1582
+ }
1583
+ const { feeQuoter } = await this._getRouterConfig(opts.router)
1584
+
1585
+ // Resolve native SOL to wrapped SOL (NATIVE_MINT)
1586
+ const tokenMint =
1587
+ !opts.token || opts.token === PublicKey.default.toBase58()
1588
+ ? NATIVE_MINT
1589
+ : new PublicKey(opts.token)
1590
+
1591
+ const feeQuoterProgram = new Program(FEE_QUOTER_IDL, feeQuoter, {
1592
+ connection: this.connection,
1593
+ })
1594
+
1595
+ const [billingTokenConfigPda] = PublicKey.findProgramAddressSync(
1596
+ [Buffer.from('fee_billing_token_config'), tokenMint.toBuffer()],
1597
+ feeQuoter,
1598
+ )
1599
+
1600
+ const [billingTokenConfigWrapper, { decimals }] = await Promise.all([
1601
+ feeQuoterProgram.account.billingTokenConfigWrapper.fetch(billingTokenConfigPda),
1602
+ this.getTokenInfo(tokenMint.toBase58()),
1603
+ ])
1604
+
1605
+ const usdPerToken = billingTokenConfigWrapper.config.usdPerToken
1606
+ return { price: Number(toBigInt(Buffer.from(usdPerToken.value))) * 10 ** (decimals - 36) }
1607
+ }
1608
+
1570
1609
  /**
1571
1610
  * Gets the router configuration from the Config PDA.
1572
1611
  * @param router - Router program address.
@@ -1588,9 +1627,19 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
1588
1627
  * Returns a copy of a message, populating missing fields like `extraArgs` with defaults.
1589
1628
  * It's expected to return a message suitable at least for basic token transfers.
1590
1629
  *
1630
+ * @remarks
1631
+ * Solana-specific receiver/tokenReceiver handling:
1632
+ * - Explicit `tokenReceiver` in extraArgs: both `receiver` and `tokenReceiver` are kept as provided.
1633
+ * - Tokens but no explicit `tokenReceiver`: `receiver` is set to `PublicKey.default` and
1634
+ * `tokenReceiver` is set to `message.receiver`.
1635
+ * - No tokens: `tokenReceiver` is set to `PublicKey.default` and `receiver` is `message.receiver`.
1636
+ *
1637
+ * Accepts `gasLimit` as an alias for `computeUnits` in extraArgs.
1638
+ *
1591
1639
  * @param message - AnyMessage (from source), containing at least `receiver`
1592
1640
  * @returns A message suitable for `sendMessage` to this destination chain family
1593
1641
  * @throws {@link CCIPArgumentInvalidError} if tokenReceiver missing when sending tokens with data
1642
+ * @throws {@link CCIPArgumentInvalidError} if extraArgs contains unknown fields for SVMExtraArgsV1
1594
1643
  */
1595
1644
  static override buildMessageForDest(
1596
1645
  message: Parameters<ChainStatic['buildMessageForDest']>[0],
@@ -1645,15 +1694,12 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
1645
1694
  message.extraArgs.allowOutOfOrderExecution != null
1646
1695
  ? message.extraArgs.allowOutOfOrderExecution
1647
1696
  : true
1648
- const tokenReceiver =
1649
- message.extraArgs &&
1650
- 'tokenReceiver' in message.extraArgs &&
1651
- message.extraArgs.tokenReceiver != null &&
1652
- typeof message.extraArgs.tokenReceiver === 'string'
1653
- ? message.extraArgs.tokenReceiver
1697
+ const [tokenReceiver, receiver] =
1698
+ message.extraArgs && 'tokenReceiver' in message.extraArgs && !!message.extraArgs.tokenReceiver
1699
+ ? [message.extraArgs.tokenReceiver, message.receiver] // explicit tokenReceiver, keep both
1654
1700
  : message.tokenAmounts?.length
1655
- ? this.getAddress(message.receiver)
1656
- : PublicKey.default.toBase58()
1701
+ ? [this.getAddress(message.receiver), PublicKey.default.toBase58()] // if sending tokens without tokenReceiver, set receiver to default and tokenReceiver to message.receiver
1702
+ : [PublicKey.default.toBase58(), message.receiver] // otherwise, tokenReceiver is default and receiver is message.receiver
1657
1703
  const accounts =
1658
1704
  message.extraArgs && 'accounts' in message.extraArgs && message.extraArgs.accounts != null
1659
1705
  ? message.extraArgs.accounts
@@ -1675,9 +1721,8 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
1675
1721
 
1676
1722
  return {
1677
1723
  ...message,
1724
+ receiver,
1678
1725
  extraArgs,
1679
- // if tokenReceiver, then message.receiver can (must?) be default
1680
- ...(!!message.tokenAmounts?.length && { receiver: PublicKey.default.toBase58() }),
1681
1726
  }
1682
1727
  }
1683
1728
  }
@@ -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 { bigIntReplacer, getDataBytes, sleep } from '../utils.ts'
30
+ import { bigIntReplacer, getDataBytes, isBase64, 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'
@@ -211,13 +211,13 @@ export function parseSolanaLogs(logs: readonly string[]): ParsedLog[] {
211
211
 
212
212
  /**
213
213
  * Extracts error information from Solana transaction logs.
214
- * @param logs_ - Raw log strings or parsed log objects.
214
+ * @param logs_ - Raw log strings or parsed log objects (may include `tx` field with error info).
215
215
  * @returns Parsed error info with program and error details.
216
216
  */
217
217
  export function getErrorFromLogs(
218
218
  logs_:
219
219
  | readonly string[]
220
- | readonly Pick<ChainLog, 'address' | 'index' | 'data' | 'topics'>[]
220
+ | readonly Pick<ChainLog, 'address' | 'index' | 'data' | 'topics' | 'tx'>[]
221
221
  | null,
222
222
  ): { program: string; [k: string]: string } | undefined {
223
223
  if (!logs_?.length) return
@@ -234,12 +234,13 @@ export function getErrorFromLogs(
234
234
  !acc.length || (l.address === acc[0]!.address && !l.topics.length) ? [l, ...acc] : acc,
235
235
  [] as Pick<ChainLog, 'address' | 'index' | 'data'>[],
236
236
  )
237
+ .filter(({ data }) => !isBase64(data))
237
238
  .map(({ data }) => data as string)
238
239
  .reduceRight(
239
240
  (acc, l) =>
240
241
  l.endsWith(':') && acc.length
241
242
  ? [`${l} ${acc[0]}`, ...acc.slice(1)]
242
- : l.split(': ').length > 1 && l.split('. ').length > 1
243
+ : l.indexOf(': ') >= 0 && l.indexOf('. ') >= 0
243
244
  ? [...l.replace(/\.$/, '').split('. '), ...acc]
244
245
  : [l, ...acc],
245
246
  [] as string[],
@@ -261,22 +262,28 @@ export function getErrorFromLogs(
261
262
  return l
262
263
  }
263
264
  })
264
- if (lastProgramLogs.every((l) => l.indexOf(': ') >= 0)) {
265
- return {
266
- program: lastLog.address,
267
- ...Object.fromEntries(
268
- lastProgramLogs.map((l) => [
269
- l.substring(0, l.indexOf(': ')),
270
- l.substring(l.indexOf(': ') + 2),
271
- ]),
272
- ),
273
- }
265
+
266
+ const res: { program: string; [k: string]: string } = {
267
+ program: lastLog.address,
268
+ }
269
+ if (lastProgramLogs.every((l) => l.indexOf(': ') >= 0 || l.indexOf(' in ') >= 0)) {
270
+ Object.assign(
271
+ res,
272
+ Object.fromEntries(lastProgramLogs.map((l) => l.split(/: | in /, 2) as [string, string])),
273
+ )
274
274
  } else {
275
- return {
276
- program: lastLog.address,
277
- error: lastProgramLogs.join('\n'),
278
- }
275
+ res['error'] = lastProgramLogs.join('\n')
279
276
  }
277
+ if (!!logs[0] && 'tx' in logs[0] && !!logs[0].tx?.error)
278
+ Object.assign(
279
+ res,
280
+ Object.fromEntries(
281
+ Object.entries(logs[0].tx.error as Record<string, [number, string]>).map(
282
+ ([k, [i, e]]) => [`${k}[${i}]`, e] as const,
283
+ ),
284
+ ),
285
+ )
286
+ return res
280
287
  }
281
288
 
282
289
  /**
@@ -390,6 +397,7 @@ export function simulationProvider(
390
397
  * - lookupTables - lookupTables to be used for main instruction
391
398
  * @param computeUnits - max computeUnits limit to be used for main instruction
392
399
  * @returns - signature of successful transaction including main instruction
400
+ * @throws {@link CCIPSolanaComputeUnitsExceededError} if simulation exceeds compute units limit
393
401
  */
394
402
  export async function simulateAndSendTxs(
395
403
  ctx: { connection: Connection } & WithLogger,
@@ -458,7 +466,9 @@ export async function simulateAndSendTxs(
458
466
  }
459
467
 
460
468
  /**
461
- * Convert TokenPool's rate limit to RateLimiterState object
469
+ * Convert TokenPool's rate limit to RateLimiterState object.
470
+ * @param input - On-chain rate limiter bucket from the TokenPool IDL.
471
+ * @returns RateLimiterState with capacity, rate, and current tokens, or null if disabled.
462
472
  */
463
473
  export function convertRateLimiter(
464
474
  input: IdlTypes<typeof BASE_TOKEN_POOL_IDL>['BaseChain']['inboundRateLimit'],
package/src/sui/index.ts CHANGED
@@ -68,7 +68,6 @@ const DEFAULT_GAS_LIMIT = 1000000n
68
68
 
69
69
  /**
70
70
  * Sui chain implementation supporting Sui networks.
71
- * Note: This implementation is currently a placeholder.
72
71
  */
73
72
  export class SuiChain extends Chain<typeof ChainFamily.Sui> {
74
73
  static {
@@ -814,6 +813,7 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
814
813
  *
815
814
  * @param message - AnyMessage (from source), containing at least `receiver`
816
815
  * @returns A message suitable for `sendMessage` to this destination chain family
816
+ * @throws {@link CCIPArgumentInvalidError} if extraArgs contains unknown fields for SuiExtraArgsV1
817
817
  */
818
818
  static override buildMessageForDest(
819
819
  message: Parameters<ChainStatic['buildMessageForDest']>[0],
package/src/ton/index.ts CHANGED
@@ -184,6 +184,9 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
184
184
 
185
185
  /**
186
186
  * Detect client network and instantiate a TONChain instance.
187
+ * @param client - TonClient instance connected to the TON network.
188
+ * @param ctx - Optional chain context with logger, API client, and fetch function.
189
+ * @returns TONChain instance configured for the detected network (mainnet or testnet).
187
190
  */
188
191
  static async fromClient(
189
192
  client: TonClient,
@@ -560,10 +563,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
560
563
  }
561
564
  }
562
565
 
563
- /**
564
- * {@inheritDoc Chain.getBalance}
565
- * @throws {@link CCIPNotImplementedError} always (not implemented for TON)
566
- */
566
+ /** {@inheritDoc Chain.getBalance} */
567
567
  async getBalance(opts: GetBalanceOpts): Promise<bigint> {
568
568
  const { holder, token } = opts
569
569
  const holderAddress = Address.parse(holder)
@@ -606,7 +606,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
606
606
  /**
607
607
  * Decodes a CCIP message from a TON log event.
608
608
  * @param log - Log with data field.
609
- * @returns Decoded CCIPMessage or undefined if not valid.
609
+ * @returns Decoded CCIPMessage, or undefined if the data is not a valid CCIP message (parse errors are caught and silently return undefined).
610
610
  */
611
611
  static decodeMessage({
612
612
  data,
@@ -711,6 +711,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
711
711
  *
712
712
  * @param args - Extra arguments containing gas limit and execution flags
713
713
  * @returns Hex string of BOC-encoded extra args (0x-prefixed)
714
+ * @throws {@link CCIPExtraArgsInvalidError} if args contains fields other than `gasLimit` and `allowOutOfOrderExecution`
714
715
  */
715
716
  static encodeExtraArgs(args: ExtraArgs): string {
716
717
  const cell = encodeExtraArgsCell(args)
@@ -718,15 +719,16 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
718
719
  }
719
720
 
720
721
  /**
721
- * Decodes BOC-encoded extra arguments from TON messages.
722
- * Parses the BOC format and extracts extra args, validating the magic tag
723
- * to ensure correct type. Returns undefined if parsing fails or tag doesn't match.
722
+ * Decodes extra arguments from TON messages.
723
+ * Handles both raw bit-packed data (starts with EVMExtraArgsV2 tag directly)
724
+ * and BOC-wrapped data (starts with TON BOC magic `0xb5ee9c72`, unwrapped first).
725
+ * Returns undefined if parsing fails or the tag doesn't match.
724
726
  *
725
727
  * Currently only supports EVMExtraArgsV2 (GenericExtraArgsV2) encoding since TON
726
728
  * lanes are only connected to EVM chains. When new lanes are planned to be added,
727
729
  * this should be extended to support them (eg. Solana and SVMExtraArgsV1)
728
730
  *
729
- * @param extraArgs - BOC-encoded extra args as hex string or bytes
731
+ * @param extraArgs - Extra args as hex string or bytes (raw bit-packed or BOC-wrapped)
730
732
  * @returns Decoded EVMExtraArgsV2 (GenericExtraArgsV2) object or undefined if invalid
731
733
  */
732
734
  static decodeExtraArgs(
package/src/ton/send.ts CHANGED
@@ -54,6 +54,7 @@ function encodeTokenAmounts(
54
54
  * - allowOutOfOrderExecution: 1 bit
55
55
  * @param extraArgs - Extra arguments for CCIP message
56
56
  * @returns Cell encoding the extra arguments
57
+ * @throws {@link CCIPExtraArgsInvalidError} if `extraArgs` contains fields other than `gasLimit` and `allowOutOfOrderExecution`
57
58
  */
58
59
  export function encodeExtraArgsCell(extraArgs: ExtraArgs): Cell {
59
60
  if (
@@ -78,6 +79,12 @@ export function encodeExtraArgsCell(extraArgs: ExtraArgs): Cell {
78
79
  * Builds the Router ccipSend message cell.
79
80
  *
80
81
  * Relies on TL-B structure (Router_CCIPSend) from chainlink-ton repo.
82
+ *
83
+ * @param destChainSelector - Destination chain selector
84
+ * @param message - CCIP message containing receiver, data, tokenAmounts, and extraArgs
85
+ * @param feeTokenAddress - Fee token jetton address, or null for native TON
86
+ * @param queryId - TON query ID for the message (default: 0)
87
+ * @returns Cell containing the encoded Router ccipSend message
81
88
  */
82
89
  export function buildCcipSendCell(
83
90
  destChainSelector: bigint,
package/src/types.ts CHANGED
@@ -470,7 +470,7 @@ export type AnyMessage = {
470
470
  * }
471
471
  * ```
472
472
  */
473
- export type MessageInput = Partial<AnyMessage> & {
473
+ export type MessageInput = Partial<Omit<AnyMessage, 'extraArgs'>> & {
474
474
  receiver: AnyMessage['receiver']
475
475
  extraArgs?: Partial<ExtraArgs>
476
476
  fee?: bigint
package/src/utils.ts CHANGED
@@ -608,7 +608,10 @@ export async function withRetry<T>(
608
608
  /**
609
609
  * Parses a typeAndVersion string into its components.
610
610
  * @param typeAndVersion - String in format "TypeName vX.Y.Z".
611
- * @returns Tuple of [type, version, original, suffix?].
611
+ * @returns Tuple of `[normalizedType, normalizedVersion, original, suffix?]` where
612
+ * `normalizedType` has kebab-to-PascalCase, `CCIP` uppercasing, and ramp casing applied
613
+ * (e.g., `"ccip-offramp"` becomes `"CCIPOffRamp"`), and `normalizedVersion` has the patch
614
+ * component forced to `.0` for core contracts (OnRamp, OffRamp, Router).
612
615
  * @throws {@link CCIPTypeVersionInvalidError} if string format is invalid
613
616
  */
614
617
  export function parseTypeAndVersion(
@@ -616,9 +619,8 @@ export function parseTypeAndVersion(
616
619
  ): Awaited<ReturnType<Chain['typeAndVersion']>> {
617
620
  const match = typeAndVersion.match(/^(\w.+\S)\s+v?(\d+\.\d+(?:\.\d+)?)([^\d.].*)?$/)
618
621
  if (!match) throw new CCIPTypeVersionInvalidError(typeAndVersion)
619
- const [, typeRaw, version] = match
620
622
  // some string normalization
621
- const type = typeRaw!
623
+ const type = match[1]!
622
624
  .replaceAll(/-(\w)/g, (_, w: string) => w.toUpperCase()) // kebabToPascal
623
625
  .replace(/ccip/gi, 'CCIP')
624
626
  .replace(
@@ -626,8 +628,15 @@ export function parseTypeAndVersion(
626
628
  (_, o: string, n: string, ramp: string) =>
627
629
  `${o.toUpperCase()}${n.toLowerCase()}${ramp.charAt(0).toUpperCase()}${ramp.slice(1).toLowerCase()}`,
628
630
  ) // ccipOfframp -> CCIPOffRamp
629
- if (!match[3]) return [type, version!, typeAndVersion]
630
- else return [type, version!, typeAndVersion, match[3]]
631
+ .replace('router', 'Router') // ccip-router -> CCIPRouter
632
+
633
+ let version = match[2]!
634
+ // for core contracts, always use patch `.0`, to match CCIPVersion
635
+ if (type.match(/((o(n|ff)ramp)|router)\b/gi))
636
+ version = version.replace(/^(\d+\.\d+)(?:\.\d+)?$/, '$1.0')
637
+
638
+ if (!match[3]) return [type, version, typeAndVersion]
639
+ else return [type, version, typeAndVersion, match[3]]
631
640
  }
632
641
 
633
642
  /* eslint-disable jsdoc/require-jsdoc */
@@ -822,7 +831,11 @@ export function createRateLimitedFetch(
822
831
  *
823
832
  * @param url - URL to fetch
824
833
  * @param operation - Operation name for error context
825
- * @param opts - Optional timeout, abort signal, fetch function, and extra RequestInit fields
834
+ * @param opts - Optional configuration:
835
+ * - `timeoutMs` — request timeout in milliseconds (default: 30000).
836
+ * - `signal` — an external `AbortSignal` to cancel the request.
837
+ * - `fetch` — custom fetch function (defaults to `globalThis.fetch`).
838
+ * - `init` — additional `RequestInit` fields merged into the fetch call.
826
839
  * @returns Promise resolving to Response
827
840
  * @throws CCIPTimeoutError if request times out
828
841
  * @throws CCIPAbortError if request is aborted via signal