@chainlink/ccip-sdk 1.3.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) 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 +65 -7
  10. package/dist/chain.d.ts.map +1 -1
  11. package/dist/chain.js +47 -3
  12. package/dist/chain.js.map +1 -1
  13. package/dist/errors/codes.d.ts +1 -2
  14. package/dist/errors/codes.d.ts.map +1 -1
  15. package/dist/errors/codes.js +1 -2
  16. package/dist/errors/codes.js.map +1 -1
  17. package/dist/errors/index.d.ts +2 -2
  18. package/dist/errors/index.d.ts.map +1 -1
  19. package/dist/errors/index.js +2 -2
  20. package/dist/errors/index.js.map +1 -1
  21. package/dist/errors/recovery.d.ts.map +1 -1
  22. package/dist/errors/recovery.js +2 -3
  23. package/dist/errors/recovery.js.map +1 -1
  24. package/dist/errors/specialized.d.ts +23 -28
  25. package/dist/errors/specialized.d.ts.map +1 -1
  26. package/dist/errors/specialized.js +33 -37
  27. package/dist/errors/specialized.js.map +1 -1
  28. package/dist/evm/const.d.ts.map +1 -1
  29. package/dist/evm/const.js +1 -0
  30. package/dist/evm/const.js.map +1 -1
  31. package/dist/evm/errors.d.ts.map +1 -1
  32. package/dist/evm/errors.js +20 -5
  33. package/dist/evm/errors.js.map +1 -1
  34. package/dist/evm/extra-args.d.ts.map +1 -1
  35. package/dist/evm/extra-args.js +14 -13
  36. package/dist/evm/extra-args.js.map +1 -1
  37. package/dist/evm/index.d.ts +11 -2
  38. package/dist/evm/index.d.ts.map +1 -1
  39. package/dist/evm/index.js +73 -22
  40. package/dist/evm/index.js.map +1 -1
  41. package/dist/gas.d.ts +2 -1
  42. package/dist/gas.d.ts.map +1 -1
  43. package/dist/gas.js +2 -1
  44. package/dist/gas.js.map +1 -1
  45. package/dist/index.d.ts +1 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/offchain.d.ts +1 -0
  49. package/dist/offchain.d.ts.map +1 -1
  50. package/dist/offchain.js +1 -0
  51. package/dist/offchain.js.map +1 -1
  52. package/dist/requests.d.ts +7 -6
  53. package/dist/requests.d.ts.map +1 -1
  54. package/dist/requests.js +7 -8
  55. package/dist/requests.js.map +1 -1
  56. package/dist/solana/idl/1.6.0/FEE_QUOTER.d.ts +1719 -0
  57. package/dist/solana/idl/1.6.0/FEE_QUOTER.d.ts.map +1 -0
  58. package/dist/solana/idl/1.6.0/FEE_QUOTER.js +1719 -0
  59. package/dist/solana/idl/1.6.0/FEE_QUOTER.js.map +1 -0
  60. package/dist/solana/index.d.ts +34 -18
  61. package/dist/solana/index.d.ts.map +1 -1
  62. package/dist/solana/index.js +45 -5
  63. package/dist/solana/index.js.map +1 -1
  64. package/dist/solana/utils.d.ts +5 -2
  65. package/dist/solana/utils.d.ts.map +1 -1
  66. package/dist/solana/utils.js +5 -2
  67. package/dist/solana/utils.js.map +1 -1
  68. package/dist/sui/index.d.ts +1 -1
  69. package/dist/sui/index.d.ts.map +1 -1
  70. package/dist/sui/index.js +14 -14
  71. package/dist/sui/index.js.map +1 -1
  72. package/dist/ton/index.d.ts +11 -9
  73. package/dist/ton/index.d.ts.map +1 -1
  74. package/dist/ton/index.js +24 -26
  75. package/dist/ton/index.js.map +1 -1
  76. package/dist/ton/send.d.ts +7 -0
  77. package/dist/ton/send.d.ts.map +1 -1
  78. package/dist/ton/send.js +7 -0
  79. package/dist/ton/send.js.map +1 -1
  80. package/dist/utils.d.ts +9 -2
  81. package/dist/utils.d.ts.map +1 -1
  82. package/dist/utils.js +10 -3
  83. package/dist/utils.js.map +1 -1
  84. package/package.json +8 -8
  85. package/src/api/index.ts +17 -6
  86. package/src/aptos/index.ts +31 -0
  87. package/src/chain.ts +80 -8
  88. package/src/errors/codes.ts +1 -2
  89. package/src/errors/index.ts +1 -2
  90. package/src/errors/recovery.ts +2 -3
  91. package/src/errors/specialized.ts +35 -38
  92. package/src/evm/const.ts +1 -0
  93. package/src/evm/errors.ts +22 -5
  94. package/src/evm/extra-args.ts +15 -14
  95. package/src/evm/index.ts +110 -21
  96. package/src/gas.ts +2 -1
  97. package/src/index.ts +1 -0
  98. package/src/offchain.ts +1 -0
  99. package/src/requests.ts +8 -8
  100. package/src/solana/idl/1.6.0/FEE_QUOTER.ts +3440 -0
  101. package/src/solana/index.ts +60 -4
  102. package/src/solana/utils.ts +5 -2
  103. package/src/sui/index.ts +17 -15
  104. package/src/ton/index.ts +23 -31
  105. package/src/ton/send.ts +7 -0
  106. package/src/utils.ts +10 -3
@@ -1,7 +1,10 @@
1
+ import type { BytesLike } from 'ethers'
2
+
1
3
  import { type CCIPErrorOptions, CCIPError } from './CCIPError.ts'
2
4
  import { CCIPErrorCode } from './codes.ts'
3
5
  import { isTransientHttpStatus } from '../http-status.ts'
4
- import { bigIntReplacer } from '../utils.ts'
6
+ import type { ChainFamily } from '../types.ts'
7
+ import { bigIntReplacer, getAddressBytes, util } from '../utils.ts'
5
8
 
6
9
  // Chain/Network
7
10
 
@@ -987,7 +990,12 @@ export class CCIPUsdcAttestationError extends CCIPError {
987
990
  */
988
991
  export class CCIPUsdcBurnFeesError extends CCIPError {
989
992
  override readonly name = 'CCIPUsdcBurnFeesError'
990
- /** Creates a USDC burn fees error. */
993
+ /**
994
+ * Creates a USDC burn fees error.
995
+ * @param sourceDomain - CCTP source domain identifier.
996
+ * @param destDomain - CCTP destination domain identifier.
997
+ * @param httpStatus - HTTP status code from the failed request.
998
+ */
991
999
  constructor(sourceDomain: number, destDomain: number, httpStatus: number) {
992
1000
  super(
993
1001
  CCIPErrorCode.USDC_BURN_FEES_FAILED,
@@ -1240,7 +1248,11 @@ export class CCIPTimeoutError extends CCIPError {
1240
1248
  */
1241
1249
  export class CCIPAbortError extends CCIPError {
1242
1250
  override readonly name = 'CCIPAbortError'
1243
- /** Creates an abort error. */
1251
+ /**
1252
+ * Creates an abort error.
1253
+ * @param operation - Name of the operation that was aborted.
1254
+ * @param options - Optional error options.
1255
+ */
1244
1256
  constructor(operation: string, options?: CCIPErrorOptions) {
1245
1257
  super(CCIPErrorCode.ABORT, `Request aborted: ${operation}`, {
1246
1258
  ...options,
@@ -1853,14 +1865,22 @@ export class CCIPMerkleInternalError extends CCIPError {
1853
1865
  * }
1854
1866
  * ```
1855
1867
  */
1856
- export class CCIPAddressInvalidEvmError extends CCIPError {
1857
- override readonly name = 'CCIPAddressInvalidEvmError'
1858
- /** Creates an EVM address invalid error. */
1859
- constructor(address: string, options?: CCIPErrorOptions) {
1860
- super(CCIPErrorCode.ADDRESS_INVALID_EVM, `Invalid EVM address: ${address}`, {
1868
+ export class CCIPAddressInvalidError extends CCIPError {
1869
+ override readonly name = 'CCIPAddressInvalidError'
1870
+ /** Creates an address invalid error for the given chain family. */
1871
+ constructor(address: BytesLike, family: ChainFamily, options?: CCIPErrorOptions) {
1872
+ const len = address.length
1873
+ const type = typeof address === 'object' ? address.constructor.name : typeof address
1874
+ let bytesLen
1875
+ try {
1876
+ bytesLen = getAddressBytes(address).length
1877
+ } catch (err) {
1878
+ bytesLen = (err as Error).message
1879
+ }
1880
+ super(CCIPErrorCode.ADDRESS_INVALID, `Invalid ${family} address: ${util.inspect(address)}`, {
1861
1881
  ...options,
1862
1882
  isTransient: false,
1863
- context: { ...options?.context, address },
1883
+ context: { ...options?.context, address, family, len, type, bytesLen },
1864
1884
  })
1865
1885
  }
1866
1886
  }
@@ -2556,34 +2576,6 @@ export class CCIPAptosLogInvalidError extends CCIPError {
2556
2576
  }
2557
2577
  }
2558
2578
 
2559
- /**
2560
- * Thrown when Aptos address is invalid.
2561
- *
2562
- * @example
2563
- * ```typescript
2564
- * import { CCIPDataFormatUnsupportedError } from '@chainlink/ccip-sdk'
2565
- *
2566
- * try {
2567
- * AptosChain.getAddress('invalid-address')
2568
- * } catch (error) {
2569
- * if (error instanceof CCIPDataFormatUnsupportedError) {
2570
- * console.log(`Invalid address: ${error.message}`)
2571
- * }
2572
- * }
2573
- * ```
2574
- */
2575
- export class CCIPAptosAddressInvalidError extends CCIPError {
2576
- override readonly name = 'CCIPAptosAddressInvalidError'
2577
- /** Creates an Aptos address invalid error. */
2578
- constructor(address: string, options?: CCIPErrorOptions) {
2579
- super(CCIPErrorCode.ADDRESS_INVALID_APTOS, `Invalid aptos address: "${address}"`, {
2580
- ...options,
2581
- isTransient: false,
2582
- context: { ...options?.context, address },
2583
- })
2584
- }
2585
- }
2586
-
2587
2579
  /**
2588
2580
  * Thrown when Aptos can only encode specific extra args types.
2589
2581
  *
@@ -3429,7 +3421,12 @@ export class CCIPApiClientNotAvailableError extends CCIPError {
3429
3421
  */
3430
3422
  export class CCIPUnexpectedPaginationError extends CCIPError {
3431
3423
  override readonly name = 'CCIPUnexpectedPaginationError'
3432
- /** Creates an unexpected pagination error. */
3424
+ /**
3425
+ * Creates an unexpected pagination error.
3426
+ * @param txHash - Source transaction hash.
3427
+ * @param messageCount - Number of messages returned in the first page.
3428
+ * @param options - Optional error options.
3429
+ */
3433
3430
  constructor(txHash: string, messageCount: number, options?: CCIPErrorOptions) {
3434
3431
  super(
3435
3432
  CCIPErrorCode.API_UNEXPECTED_PAGINATION,
package/src/evm/const.ts CHANGED
@@ -33,6 +33,7 @@ const customErrors = [
33
33
  'error NotEnoughGas()',
34
34
  'error InvalidChain(uint64 chainSelector)',
35
35
  'error InvalidAdapter()',
36
+ 'error BlacklistableBlacklistedAccount(address)',
36
37
  ] as const
37
38
 
38
39
  export const VersionedContractABI = parseAbi(['function typeAndVersion() view returns (string)'])
package/src/evm/errors.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  import { defaultAbiCoder, interfaces } from './const.ts'
15
15
  import { decodeExtraArgs } from '../extra-args.ts'
16
16
  import { ChainFamily } from '../types.ts'
17
+ import { networkInfo } from '../utils.ts'
17
18
 
18
19
  /**
19
20
  * Get error data from an error object, if possible
@@ -151,6 +152,16 @@ export function recursiveParseError(
151
152
  )
152
153
  }
153
154
  if (!isBytesLike(data) || [0, 20].includes(dataLength(data))) {
155
+ // include networkName for chainSelectors
156
+ if (key.match(/sel(ector)?$/i) && typeof data === 'bigint') {
157
+ let name
158
+ try {
159
+ ;({ name } = networkInfo(data))
160
+ } catch {
161
+ // ignore
162
+ }
163
+ if (name) return [[key, `${data} [${name}]`]]
164
+ }
154
165
  return [[key, data]]
155
166
  }
156
167
  try {
@@ -166,14 +177,16 @@ export function recursiveParseError(
166
177
  if (!parsed) return [[key, data]]
167
178
  const [fragment, _, args] = parsed
168
179
  const desc = fragment.format('full')
169
- key = desc.split(' ')[0]!
170
- const res = [[key, desc.substring(key.length + 1)]] as ReturnType<typeof recursiveParseError>
180
+ const key_ = j(key, desc.split(' ')[0]!)
181
+ const res = [[key_, desc.substring(desc.indexOf(' ') + 1)]] as ReturnType<
182
+ typeof recursiveParseError
183
+ >
171
184
  if (!args) return res
172
185
  if (['ReceiverError', 'TokenHandlingError'].includes(fragment.name) && args.err === '0x') {
173
- res.push([`${key}.err`, '0x [possibly out-of-gas or abi.decode error]'])
186
+ res.push([`${key_}.err`, '0x [possibly out-of-gas or abi.decode error]'])
174
187
  return res
175
188
  }
176
- res.push(...recursiveParseError('', args))
189
+ res.push(...recursiveParseError(key, args))
177
190
  return res
178
191
  }
179
192
 
@@ -187,7 +200,11 @@ export function parseData(data: unknown): Record<string, unknown> | undefined {
187
200
  if (isHexString(data)) {
188
201
  const parsed = recursiveParseError('', data)
189
202
  if (parsed.length === 1 && parsed[0]![1] === data) return
190
- return Object.fromEntries(parsed)
203
+ // like Object.fromEntries, but on duplicated keys, add a space to first occurrence, to avoid overwriting and keep all values
204
+ return parsed.reduceRight(
205
+ (acc, [k, v]) => ({ ...{ [k && k in acc ? k + ' ' : k]: v }, ...acc }),
206
+ {},
207
+ )
191
208
  }
192
209
  if (typeof data !== 'object') return
193
210
  // ethers tx/simulation/call errors
@@ -5,7 +5,7 @@ import {
5
5
  encodeBase58,
6
6
  getAddress,
7
7
  hexlify,
8
- toBeHex,
8
+ toBeArray,
9
9
  toBigInt,
10
10
  toNumber,
11
11
  zeroPadValue,
@@ -25,7 +25,8 @@ import {
25
25
  SuiExtraArgsV1Tag,
26
26
  } from '../extra-args.ts'
27
27
  import { DEFAULT_GAS_LIMIT } from '../shared/constants.ts'
28
- import { getAddressBytes, getDataBytes } from '../utils.ts'
28
+ import { ChainFamily } from '../types.ts'
29
+ import { decodeAddress, getAddressBytes, getDataBytes } from '../utils.ts'
29
30
  import { defaultAbiCoder } from './const.ts'
30
31
  import { resultToObject } from './types.ts'
31
32
 
@@ -60,16 +61,16 @@ const SuiExtraArgsV1ABI =
60
61
  * - tokenArgs (variable)
61
62
  */
62
63
  function encodeExtraArgsV3(args: GenericExtraArgsV3): string {
63
- const parts: Uint8Array[] = []
64
+ const parts: BytesLike[] = []
64
65
 
65
66
  // Tag (4 bytes)
66
- parts.push(getDataBytes(GenericExtraArgsV3Tag))
67
+ parts.push(GenericExtraArgsV3Tag)
67
68
 
68
69
  // gasLimit (4 bytes, uint32 big-endian)
69
- parts.push(getDataBytes(toBeHex(args.gasLimit, 4)))
70
+ parts.push(toBeArray(args.gasLimit, 4))
70
71
 
71
72
  // blockConfirmations (2 bytes, uint16 big-endian)
72
- parts.push(getDataBytes(toBeHex(args.blockConfirmations, 2)))
73
+ parts.push(toBeArray(args.blockConfirmations, 2))
73
74
 
74
75
  // ccvsLength (1 byte)
75
76
  parts.push(new Uint8Array([args.ccvs.length]))
@@ -83,14 +84,14 @@ function encodeExtraArgsV3(args: GenericExtraArgsV3): string {
83
84
  // ccvAddressLength = 20
84
85
  parts.push(new Uint8Array([20]))
85
86
  // ccvAddress (20 bytes)
86
- parts.push(getDataBytes(ccvAddress))
87
+ parts.push(decodeAddress(ccvAddress, ChainFamily.EVM))
87
88
  } else {
88
89
  // ccvAddressLength = 0
89
90
  parts.push(new Uint8Array([0]))
90
91
  }
91
92
 
92
93
  // ccvArgsLength (2 bytes, uint16 big-endian)
93
- parts.push(getDataBytes(toBeHex(ccvArgsBytes.length, 2)))
94
+ parts.push(toBeArray(ccvArgsBytes.length, 2))
94
95
 
95
96
  // ccvArgs (variable)
96
97
  if (ccvArgsBytes.length > 0) {
@@ -99,26 +100,25 @@ function encodeExtraArgsV3(args: GenericExtraArgsV3): string {
99
100
  }
100
101
 
101
102
  // executorLength (1 byte)
102
- if (args.executor && args.executor !== '' && args.executor !== '0x') {
103
+ if (args.executor && args.executor !== '0x') {
103
104
  parts.push(new Uint8Array([20]))
104
- parts.push(getDataBytes(args.executor))
105
+ parts.push(decodeAddress(args.executor, ChainFamily.EVM))
105
106
  } else {
106
107
  parts.push(new Uint8Array([0]))
107
108
  }
108
109
 
109
110
  // Convert BytesLike fields to Uint8Array
110
111
  const executorArgsBytes = getDataBytes(args.executorArgs)
111
- const tokenReceiverBytes = getDataBytes(args.tokenReceiver)
112
- const tokenArgsBytes = getDataBytes(args.tokenArgs)
113
112
 
114
113
  // executorArgsLength (2 bytes, uint16 big-endian)
115
- parts.push(getDataBytes(toBeHex(executorArgsBytes.length, 2)))
114
+ parts.push(toBeArray(executorArgsBytes.length, 2))
116
115
 
117
116
  // executorArgs (variable)
118
117
  if (executorArgsBytes.length > 0) {
119
118
  parts.push(executorArgsBytes)
120
119
  }
121
120
 
121
+ const tokenReceiverBytes = getAddressBytes(args.tokenReceiver)
122
122
  // tokenReceiverLength (1 byte)
123
123
  parts.push(new Uint8Array([tokenReceiverBytes.length]))
124
124
 
@@ -127,8 +127,9 @@ function encodeExtraArgsV3(args: GenericExtraArgsV3): string {
127
127
  parts.push(tokenReceiverBytes)
128
128
  }
129
129
 
130
+ const tokenArgsBytes = getDataBytes(args.tokenArgs)
130
131
  // tokenArgsLength (2 bytes, uint16 big-endian)
131
- parts.push(getDataBytes(toBeHex(tokenArgsBytes.length, 2)))
132
+ parts.push(toBeArray(tokenArgsBytes.length, 2))
132
133
 
133
134
  // tokenArgs (variable)
134
135
  if (tokenArgsBytes.length > 0) {
package/src/evm/index.ts CHANGED
@@ -34,6 +34,7 @@ import {
34
34
  type LogFilter,
35
35
  type RateLimiterState,
36
36
  type TokenPoolRemote,
37
+ type TokenPrice,
37
38
  type TokenTransferFeeConfig,
38
39
  type TokenTransferFeeOpts,
39
40
  type TotalFeesEstimate,
@@ -41,7 +42,7 @@ import {
41
42
  LaneFeature,
42
43
  } from '../chain.ts'
43
44
  import {
44
- CCIPAddressInvalidEvmError,
45
+ CCIPAddressInvalidError,
45
46
  CCIPBlockNotFoundError,
46
47
  CCIPContractNotRouterError,
47
48
  CCIPContractTypeInvalidError,
@@ -56,7 +57,6 @@ import {
56
57
  CCIPTokenNotConfiguredError,
57
58
  CCIPTokenPoolChainConfigNotFoundError,
58
59
  CCIPTransactionNotFoundError,
59
- CCIPVersionFeatureUnavailableError,
60
60
  CCIPVersionRequiresLaneError,
61
61
  CCIPVersionUnsupportedError,
62
62
  CCIPWalletInvalidError,
@@ -87,6 +87,7 @@ import {
87
87
  decodeOnRampAddress,
88
88
  getAddressBytes,
89
89
  getDataBytes,
90
+ getSomeBlockNumberBefore,
90
91
  networkInfo,
91
92
  parseTypeAndVersion,
92
93
  } from '../utils.ts'
@@ -250,6 +251,10 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
250
251
  this.getFeeTokens = memoize(this.getFeeTokens.bind(this), { async: true, maxArgs: 1 })
251
252
  this.detectUsdcDomains = memoize(this.detectUsdcDomains.bind(this))
252
253
  this.resolveVerifier = memoize(this.resolveVerifier.bind(this))
254
+ this.getFeeQuoterFor = memoize(this.getFeeQuoterFor.bind(this), {
255
+ async: true,
256
+ maxArgs: 1,
257
+ })
253
258
  }
254
259
 
255
260
  /**
@@ -555,16 +560,16 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
555
560
  */
556
561
  static getAddress(bytes: BytesLike): string {
557
562
  if (isHexString(bytes, 20)) return getAddress(bytes)
558
- bytes = getAddressBytes(bytes)
559
- if (bytes.length < 20) throw new CCIPAddressInvalidEvmError(hexlify(bytes))
560
- else if (bytes.length > 20) {
561
- if (bytes.slice(0, bytes.length - 20).every((b) => b === 0)) {
562
- bytes = bytes.slice(-20)
563
+ let bytes_ = getAddressBytes(bytes)
564
+ if (bytes_.length < 20) throw new CCIPAddressInvalidError(bytes, this.family)
565
+ else if (bytes_.length > 20) {
566
+ if (bytes_.slice(0, bytes_.length - 20).every((b) => b === 0)) {
567
+ bytes_ = bytes_.slice(-20)
563
568
  } else {
564
- throw new CCIPAddressInvalidEvmError(hexlify(bytes))
569
+ throw new CCIPAddressInvalidError(hexlify(bytes_), this.family)
565
570
  }
566
571
  }
567
- return getAddress(hexlify(bytes))
572
+ return getAddress(hexlify(bytes_))
568
573
  }
569
574
 
570
575
  /**
@@ -970,17 +975,26 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
970
975
  * @throws {@link CCIPVersionFeatureUnavailableError} if contract version is below v1.6
971
976
  */
972
977
  async getFeeQuoterFor(address: string): Promise<string> {
973
- let [type, version, typeAndVersion] = await this.typeAndVersion(address)
974
- if (type === 'FeeQuoter') {
978
+ const [type, version, typeAndVersion] = await this.typeAndVersion(address)
979
+ if (type === 'FeeQuoter' || type === 'PriceRegistry') {
975
980
  return address
976
981
  } else if (type === 'Router') {
977
- address = await this._getSomeOnRampFor(address)
978
- ;[type, version, typeAndVersion] = await this.typeAndVersion(address)
982
+ return this.getFeeQuoterFor(await this._getSomeOnRampFor(address)) // use cache
979
983
  } else if (!type.includes('Ramp')) {
980
984
  throw new CCIPContractNotRouterError(address, typeAndVersion)
981
985
  }
982
- if (version < CCIPVersion.V1_6)
983
- throw new CCIPVersionFeatureUnavailableError('feeQuoter', version, 'v1.6')
986
+
987
+ if (version < CCIPVersion.V1_6) {
988
+ const contract = new Contract(
989
+ address,
990
+ version === CCIPVersion.V1_2
991
+ ? interfaces.EVM2EVMOnRamp_v1_2
992
+ : interfaces.EVM2EVMOnRamp_v1_5,
993
+ this.provider,
994
+ ) as unknown as TypedContract<typeof EVM2EVMOnRamp_1_2_ABI | typeof EVM2EVMOnRamp_1_5_ABI>
995
+ const { priceRegistry } = await contract.getDynamicConfig()
996
+ return priceRegistry as string
997
+ }
984
998
 
985
999
  const isOnRamp = type.includes('OnRamp')
986
1000
  const contract = new Contract(
@@ -1017,7 +1031,10 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1017
1031
  this.provider,
1018
1032
  ) as unknown as TypedContract<typeof Router_ABI>
1019
1033
  return contract.getFee(destChainSelector, {
1020
- receiver: zeroPadValue(getAddressBytes(populatedMessage.receiver), 32),
1034
+ receiver: (() => {
1035
+ const receiverBytes = getAddressBytes(populatedMessage.receiver)
1036
+ return receiverBytes.length <= 32 ? zeroPadValue(receiverBytes, 32) : hexlify(receiverBytes)
1037
+ })(),
1021
1038
  data: hexlify(populatedMessage.data ?? '0x'),
1022
1039
  tokenAmounts: populatedMessage.tokenAmounts ?? [],
1023
1040
  feeToken: populatedMessage.feeToken ?? ZeroAddress,
@@ -1133,6 +1150,52 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1133
1150
  return undefined
1134
1151
  }
1135
1152
 
1153
+ /** {@inheritDoc Chain.getTokenPrice} */
1154
+ override async getTokenPrice(opts: {
1155
+ router: string
1156
+ token: string
1157
+ timestamp?: number
1158
+ }): Promise<TokenPrice> {
1159
+ let { token } = opts
1160
+
1161
+ // Resolve native token (ZeroAddress) to wrapped native
1162
+ if (token === ZeroAddress) {
1163
+ token = await this.getNativeTokenForRouter(opts.router)
1164
+ }
1165
+
1166
+ const priceContractAddress = await this.getFeeQuoterFor(opts.router)
1167
+
1168
+ // Both PriceRegistry (v1.2/v1.5) and FeeQuoter (v1.6+) expose
1169
+ // getTokenPrice(address) → { value: uint224, timestamp: uint32 }
1170
+ const contract = new Contract(
1171
+ priceContractAddress,
1172
+ interfaces.FeeQuoter,
1173
+ this.provider,
1174
+ ) as unknown as TypedContract<typeof FeeQuoter_ABI>
1175
+
1176
+ // If timestamp provided, resolve to block number for historical query
1177
+ let blockTag: number | undefined
1178
+ if (opts.timestamp != null) {
1179
+ const { number: latestBlock } = (await this.provider.getBlock('latest'))!
1180
+ blockTag = await getSomeBlockNumberBefore(
1181
+ async (block: number) => (await this.provider.getBlock(block))!.timestamp,
1182
+ latestBlock,
1183
+ opts.timestamp,
1184
+ this,
1185
+ )
1186
+ }
1187
+
1188
+ const [result, { decimals }] = await Promise.all([
1189
+ blockTag != null
1190
+ ? contract.getTokenPrice.staticCall(token, { blockTag })
1191
+ : contract.getTokenPrice(token),
1192
+ this.getTokenInfo(token),
1193
+ ])
1194
+
1195
+ const rawPrice = BigInt(result.value)
1196
+ return { price: Number(rawPrice) * 10 ** (decimals - 36) }
1197
+ }
1198
+
1136
1199
  /** {@inheritDoc Chain.getTotalFeesEstimate} */
1137
1200
  override async getTotalFeesEstimate(
1138
1201
  opts: Parameters<Chain['getTotalFeesEstimate']>[0],
@@ -1152,7 +1215,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1152
1215
  let tokenArgs: string = '0x'
1153
1216
  if (extraArgs && 'blockConfirmations' in extraArgs) {
1154
1217
  blockConfirmations = extraArgs.blockConfirmations as number
1155
- tokenArgs = hexlify(extraArgs.tokenArgs as BytesLike)
1218
+ if (extraArgs.tokenArgs) tokenArgs = hexlify(extraArgs.tokenArgs)
1156
1219
  }
1157
1220
 
1158
1221
  // Skip pool-level fee lookup for pre-v2.0 lanes
@@ -1257,7 +1320,9 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1257
1320
  }
1258
1321
 
1259
1322
  const feeToken = message.feeToken ?? ZeroAddress
1260
- const receiver = zeroPadValue(getAddressBytes(message.receiver), 32)
1323
+ const receiverBytes = getAddressBytes(message.receiver)
1324
+ const receiver =
1325
+ receiverBytes.length <= 32 ? zeroPadValue(receiverBytes, 32) : hexlify(receiverBytes)
1261
1326
  const data = hexlify(message.data ?? '0x')
1262
1327
  const extraArgs = hexlify(
1263
1328
  (this.constructor as typeof EVMChain).encodeExtraArgs(message.extraArgs),
@@ -1639,7 +1704,10 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1639
1704
  * Fetches the token pool configuration for an EVM token pool contract.
1640
1705
  *
1641
1706
  * @param tokenPool - Token pool contract address.
1642
- * @param feeOpts - Optional parameters to also fetch token transfer fee config.
1707
+ * @param feeOpts - Optional parameters to also fetch token transfer fee config:
1708
+ * - `destChainSelector` — destination chain selector.
1709
+ * - `blockConfirmationsRequested` — number of block confirmations (0 = standard, positive = FTF).
1710
+ * - `tokenArgs` — hex-encoded bytes passed to the pool contract.
1643
1711
  * @returns Token pool config containing token, router, typeAndVersion, and optionally
1644
1712
  * minBlockConfirmations and tokenTransferFeeConfig.
1645
1713
  *
@@ -1658,7 +1726,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1658
1726
  minBlockConfirmations?: number
1659
1727
  tokenTransferFeeConfig?: TokenTransferFeeConfig
1660
1728
  }> {
1661
- const [_, version, typeAndVersion] = await this.typeAndVersion(tokenPool)
1729
+ const [type, version, typeAndVersion] = await this.typeAndVersion(tokenPool)
1662
1730
 
1663
1731
  let token, router, minBlockConfirmations, tokenTransferFeeConfig
1664
1732
  if (version < CCIPVersion.V2_0) {
@@ -1670,6 +1738,14 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1670
1738
  token = contract.getToken()
1671
1739
  router = contract.getRouter()
1672
1740
  } else {
1741
+ if (type === 'USDCTokenPoolProxy') {
1742
+ const proxy = new Contract(
1743
+ tokenPool,
1744
+ interfaces.USDCTokenPoolProxy_v2_0,
1745
+ this.provider,
1746
+ ) as unknown as TypedContract<typeof USDCTokenPoolProxy_2_0_ABI>
1747
+ tokenPool = (await proxy.getPools())['cctpV2PoolWithCCV'] as string
1748
+ }
1673
1749
  const contract = new Contract(
1674
1750
  tokenPool,
1675
1751
  interfaces.TokenPool_v2_0,
@@ -1678,6 +1754,11 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1678
1754
  token = contract.getToken()
1679
1755
  router = contract.getDynamicConfig().then(([router]) => router)
1680
1756
  minBlockConfirmations = contract.getMinBlockConfirmations().catch((err) => {
1757
+ this.logger.debug(
1758
+ typeAndVersion,
1759
+ 'threw when fetching minBlockConfirmations, defaulting to 0:',
1760
+ err,
1761
+ )
1681
1762
  if (isError(err, 'CALL_EXCEPTION')) return 0
1682
1763
  throw CCIPError.from(err)
1683
1764
  })
@@ -1750,7 +1831,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1750
1831
  tokenPool: string,
1751
1832
  remoteChainSelector?: bigint,
1752
1833
  ): Promise<Record<string, TokenPoolRemote>> {
1753
- const [_, version] = await this.typeAndVersion(tokenPool)
1834
+ const [type, version] = await this.typeAndVersion(tokenPool)
1754
1835
 
1755
1836
  let supportedChains: Promise<NetworkInfo[]> | undefined
1756
1837
  if (remoteChainSelector) supportedChains = Promise.resolve([networkInfo(remoteChainSelector)])
@@ -1812,6 +1893,14 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
1812
1893
  ),
1813
1894
  )
1814
1895
  } else {
1896
+ if (type === 'USDCTokenPoolProxy') {
1897
+ const proxy = new Contract(
1898
+ tokenPool,
1899
+ interfaces.USDCTokenPoolProxy_v2_0,
1900
+ this.provider,
1901
+ ) as unknown as TypedContract<typeof USDCTokenPoolProxy_2_0_ABI>
1902
+ tokenPool = (await proxy.getPools())['cctpV2PoolWithCCV'] as string
1903
+ }
1815
1904
  const contract = new Contract(
1816
1905
  tokenPool,
1817
1906
  interfaces.TokenPool_v2_0,
package/src/gas.ts CHANGED
@@ -57,11 +57,12 @@ export type EstimateReceiveExecutionOpts = {
57
57
  * Estimate CCIP gasLimit needed to execute a request on a contract receiver.
58
58
  *
59
59
  * @param opts - {@link EstimateReceiveExecutionOpts} for estimation
60
- * @returns Estimated gasLimit
60
+ * @returns Estimated execution gas (base transaction cost subtracted)
61
61
  *
62
62
  * @throws {@link CCIPMethodUnsupportedError} if dest chain doesn't support estimation
63
63
  * @throws {@link CCIPContractTypeInvalidError} if routerOrRamp is not a valid contract type
64
64
  * @throws {@link CCIPTokenDecimalsInsufficientError} if dest token has insufficient decimals
65
+ * @throws {@link CCIPOnRampRequiredError} if no OnRamp found for the given OffRamp and source chain
65
66
  *
66
67
  * @example
67
68
  * ```typescript
package/src/index.ts CHANGED
@@ -37,6 +37,7 @@ export type {
37
37
  TokenInfo,
38
38
  TokenPoolConfig,
39
39
  TokenPoolRemote,
40
+ TokenPrice,
40
41
  TokenTransferFee,
41
42
  TokenTransferFeeConfig,
42
43
  TokenTransferFeeOpts,
package/src/offchain.ts CHANGED
@@ -86,6 +86,7 @@ export const CCTP_FINALITY_STANDARD = 2000
86
86
  * @param destDomain - CCTP destination domain identifier
87
87
  * @param networkType - network type (mainnet or testnet)
88
88
  * @returns Array of fee tiers with finality thresholds and BPS fees
89
+ * @throws {@link CCIPUsdcBurnFeesError} if the HTTP request fails or the response is not a valid array of fee tiers
89
90
  */
90
91
  export async function getUsdcBurnFees(
91
92
  sourceDomain: number,
package/src/requests.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { type BytesLike, hexlify, isBytesLike, toBigInt } from 'ethers'
2
2
  import type { PickDeep } from 'type-fest'
3
3
 
4
- import { type ChainStatic, Chain } from './chain.ts'
4
+ import type { Chain, ChainStatic } from './chain.ts'
5
5
  import {
6
6
  CCIPChainFamilyUnsupportedError,
7
7
  CCIPMessageBatchIncompleteError,
@@ -196,9 +196,8 @@ export function decodeMessage(data: string | Uint8Array | Record<string, unknown
196
196
  * @returns Original message or shallow copy with defaults for required fields
197
197
  */
198
198
  export function buildMessageForDest(message: MessageInput, dest: ChainFamily): AnyMessage {
199
- const chain = supportedChains[dest] ?? Chain
200
199
  if (message.extraArgs && '_tag' in message.extraArgs) delete message.extraArgs._tag
201
- return chain.buildMessageForDest(message)
200
+ return supportedChains[dest]!.buildMessageForDest(message)
202
201
  }
203
202
 
204
203
  /**
@@ -310,6 +309,7 @@ const BLOCK_LOG_WINDOW_SIZE = 5000
310
309
  * @param range - Object containing minSeqNr and maxSeqNr for the batch range.
311
310
  * @param opts - Optional log filtering parameters.
312
311
  * @returns Array of messages in the batch.
312
+ * @throws {@link CCIPMessageBatchIncompleteError} if not all messages in the batch range could be found in source chain logs
313
313
  * @see {@link getVerifications} - Get commit report to determine batch range
314
314
  */
315
315
  export async function getMessagesInBatch<
@@ -409,12 +409,12 @@ export async function getMessagesInBatch<
409
409
  * import { sourceToDestTokenAddresses, EVMChain } from '@chainlink/ccip-sdk'
410
410
  *
411
411
  * const source = await EVMChain.fromUrl('https://rpc.sepolia.org')
412
- * const tokenAmount = await sourceToDestTokenAddresses(
412
+ * const tokenAmount = await sourceToDestTokenAddresses({
413
413
  * source,
414
- * destChainSelector,
415
- * '0xOnRamp...',
416
- * { token: '0xLINK...', amount: 1000000000000000000n }
417
- * )
414
+ * onRamp: '0xOnRamp...',
415
+ * destChainSelector: 14767482510784806043n,
416
+ * sourceTokenAmount: { token: '0xLINK...', amount: 1000000000000000000n },
417
+ * })
418
418
  * console.log(`Pool: ${tokenAmount.sourcePoolAddress}`)
419
419
  * console.log(`Dest token: ${tokenAmount.destTokenAddress}`)
420
420
  * ```