@chainlink/ccip-sdk 0.97.0 → 1.1.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 (166) hide show
  1. package/README.md +12 -9
  2. package/dist/api/index.d.ts +7 -3
  3. package/dist/api/index.d.ts.map +1 -1
  4. package/dist/api/index.js +124 -13
  5. package/dist/api/index.js.map +1 -1
  6. package/dist/api/types.d.ts +34 -0
  7. package/dist/api/types.d.ts.map +1 -1
  8. package/dist/aptos/index.d.ts +4 -6
  9. package/dist/aptos/index.d.ts.map +1 -1
  10. package/dist/aptos/index.js +0 -5
  11. package/dist/aptos/index.js.map +1 -1
  12. package/dist/aptos/logs.d.ts +2 -2
  13. package/dist/aptos/logs.d.ts.map +1 -1
  14. package/dist/chain.d.ts +104 -16
  15. package/dist/chain.d.ts.map +1 -1
  16. package/dist/chain.js +97 -9
  17. package/dist/chain.js.map +1 -1
  18. package/dist/errors/codes.d.ts +1 -1
  19. package/dist/errors/codes.d.ts.map +1 -1
  20. package/dist/errors/codes.js +2 -1
  21. package/dist/errors/codes.js.map +1 -1
  22. package/dist/errors/index.d.ts +5 -5
  23. package/dist/errors/index.d.ts.map +1 -1
  24. package/dist/errors/index.js +5 -5
  25. package/dist/errors/index.js.map +1 -1
  26. package/dist/errors/recovery.js +1 -1
  27. package/dist/errors/recovery.js.map +1 -1
  28. package/dist/errors/specialized.d.ts +22 -19
  29. package/dist/errors/specialized.d.ts.map +1 -1
  30. package/dist/errors/specialized.js +30 -25
  31. package/dist/errors/specialized.js.map +1 -1
  32. package/dist/evm/abi/OffRamp_2_0.d.ts +24 -12
  33. package/dist/evm/abi/OffRamp_2_0.d.ts.map +1 -1
  34. package/dist/evm/abi/OffRamp_2_0.js +16 -8
  35. package/dist/evm/abi/OffRamp_2_0.js.map +1 -1
  36. package/dist/evm/abi/OnRamp_2_0.d.ts +1 -1
  37. package/dist/evm/abi/OnRamp_2_0.js +1 -1
  38. package/dist/evm/abi/OnRamp_2_0.js.map +1 -1
  39. package/dist/evm/abi/TokenPool_2_0.d.ts +1552 -0
  40. package/dist/evm/abi/TokenPool_2_0.d.ts.map +1 -0
  41. package/dist/evm/abi/TokenPool_2_0.js +1637 -0
  42. package/dist/evm/abi/TokenPool_2_0.js.map +1 -0
  43. package/dist/evm/const.d.ts +1 -0
  44. package/dist/evm/const.d.ts.map +1 -1
  45. package/dist/evm/const.js +2 -0
  46. package/dist/evm/const.js.map +1 -1
  47. package/dist/evm/index.d.ts +11 -7
  48. package/dist/evm/index.d.ts.map +1 -1
  49. package/dist/evm/index.js +139 -46
  50. package/dist/evm/index.js.map +1 -1
  51. package/dist/evm/logs.d.ts +1 -1
  52. package/dist/evm/logs.js +1 -1
  53. package/dist/evm/messages.d.ts +2 -33
  54. package/dist/evm/messages.d.ts.map +1 -1
  55. package/dist/evm/messages.js +0 -210
  56. package/dist/evm/messages.js.map +1 -1
  57. package/dist/evm/offchain.d.ts +1 -14
  58. package/dist/evm/offchain.d.ts.map +1 -1
  59. package/dist/evm/offchain.js +1 -133
  60. package/dist/evm/offchain.js.map +1 -1
  61. package/dist/gas.d.ts +4 -0
  62. package/dist/gas.d.ts.map +1 -1
  63. package/dist/gas.js +27 -21
  64. package/dist/gas.js.map +1 -1
  65. package/dist/index.d.ts +5 -4
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +3 -2
  68. package/dist/index.js.map +1 -1
  69. package/dist/messages.d.ts +34 -0
  70. package/dist/messages.d.ts.map +1 -0
  71. package/dist/messages.js +211 -0
  72. package/dist/messages.js.map +1 -0
  73. package/dist/offchain.d.ts +23 -6
  74. package/dist/offchain.d.ts.map +1 -1
  75. package/dist/offchain.js +92 -17
  76. package/dist/offchain.js.map +1 -1
  77. package/dist/requests.d.ts.map +1 -1
  78. package/dist/requests.js +0 -1
  79. package/dist/requests.js.map +1 -1
  80. package/dist/solana/cleanup.js +2 -2
  81. package/dist/solana/cleanup.js.map +1 -1
  82. package/dist/solana/exec.js +1 -1
  83. package/dist/solana/exec.js.map +1 -1
  84. package/dist/solana/idl/1.6.0/BASE_TOKEN_POOL.d.ts +1 -1
  85. package/dist/solana/idl/1.6.0/BASE_TOKEN_POOL.js +1 -1
  86. package/dist/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.d.ts +1 -1
  87. package/dist/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.js +1 -1
  88. package/dist/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.d.ts +1 -1
  89. package/dist/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.js +1 -1
  90. package/dist/solana/idl/1.6.0/CCIP_COMMON.d.ts +16 -1
  91. package/dist/solana/idl/1.6.0/CCIP_COMMON.d.ts.map +1 -1
  92. package/dist/solana/idl/1.6.0/CCIP_COMMON.js +16 -1
  93. package/dist/solana/idl/1.6.0/CCIP_COMMON.js.map +1 -1
  94. package/dist/solana/idl/1.6.0/CCIP_OFFRAMP.d.ts +1 -1
  95. package/dist/solana/idl/1.6.0/CCIP_OFFRAMP.js +1 -1
  96. package/dist/solana/idl/1.6.0/CCIP_ROUTER.d.ts +1 -1
  97. package/dist/solana/idl/1.6.0/CCIP_ROUTER.js +1 -1
  98. package/dist/solana/index.d.ts +25 -27
  99. package/dist/solana/index.d.ts.map +1 -1
  100. package/dist/solana/index.js +16 -7
  101. package/dist/solana/index.js.map +1 -1
  102. package/dist/solana/offchain.d.ts +1 -13
  103. package/dist/solana/offchain.d.ts.map +1 -1
  104. package/dist/solana/offchain.js +1 -66
  105. package/dist/solana/offchain.js.map +1 -1
  106. package/dist/solana/utils.d.ts +4 -4
  107. package/dist/solana/utils.d.ts.map +1 -1
  108. package/dist/solana/utils.js +1 -1
  109. package/dist/solana/utils.js.map +1 -1
  110. package/dist/sui/index.d.ts +4 -6
  111. package/dist/sui/index.d.ts.map +1 -1
  112. package/dist/sui/index.js +14 -6
  113. package/dist/sui/index.js.map +1 -1
  114. package/dist/ton/exec.d.ts +2 -2
  115. package/dist/ton/exec.d.ts.map +1 -1
  116. package/dist/ton/exec.js +1 -1
  117. package/dist/ton/exec.js.map +1 -1
  118. package/dist/ton/index.d.ts +5 -6
  119. package/dist/ton/index.d.ts.map +1 -1
  120. package/dist/ton/index.js +3 -5
  121. package/dist/ton/index.js.map +1 -1
  122. package/dist/ton/types.d.ts +3 -5
  123. package/dist/ton/types.d.ts.map +1 -1
  124. package/dist/ton/types.js.map +1 -1
  125. package/dist/types.d.ts +10 -10
  126. package/dist/types.d.ts.map +1 -1
  127. package/dist/types.js.map +1 -1
  128. package/package.json +11 -7
  129. package/src/api/index.ts +145 -17
  130. package/src/api/types.ts +43 -0
  131. package/src/aptos/index.ts +4 -11
  132. package/src/aptos/logs.ts +2 -2
  133. package/src/chain.ts +148 -23
  134. package/src/errors/codes.ts +2 -1
  135. package/src/errors/index.ts +4 -1
  136. package/src/errors/recovery.ts +1 -1
  137. package/src/errors/specialized.ts +35 -30
  138. package/src/evm/abi/OffRamp_2_0.ts +16 -8
  139. package/src/evm/abi/OnRamp_2_0.ts +1 -1
  140. package/src/evm/abi/TokenPool_2_0.ts +1636 -0
  141. package/src/evm/const.ts +2 -0
  142. package/src/evm/index.ts +234 -85
  143. package/src/evm/logs.ts +1 -1
  144. package/src/evm/messages.ts +3 -285
  145. package/src/evm/offchain.ts +2 -191
  146. package/src/gas.ts +27 -19
  147. package/src/index.ts +10 -2
  148. package/src/messages.ts +278 -0
  149. package/src/offchain.ts +125 -28
  150. package/src/requests.ts +2 -3
  151. package/src/solana/cleanup.ts +2 -2
  152. package/src/solana/exec.ts +1 -1
  153. package/src/solana/idl/1.6.0/BASE_TOKEN_POOL.ts +2 -2
  154. package/src/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.ts +2 -2
  155. package/src/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.ts +2 -2
  156. package/src/solana/idl/1.6.0/CCIP_COMMON.ts +32 -2
  157. package/src/solana/idl/1.6.0/CCIP_OFFRAMP.ts +2 -2
  158. package/src/solana/idl/1.6.0/CCIP_ROUTER.ts +2 -2
  159. package/src/solana/index.ts +27 -17
  160. package/src/solana/offchain.ts +3 -100
  161. package/src/solana/utils.ts +8 -5
  162. package/src/sui/index.ts +22 -12
  163. package/src/ton/exec.ts +3 -6
  164. package/src/ton/index.ts +15 -16
  165. package/src/ton/types.ts +3 -6
  166. package/src/types.ts +13 -10
@@ -0,0 +1,278 @@
1
+ import { type BytesLike, dataSlice, hexlify, toBigInt, toNumber } from 'ethers'
2
+
3
+ import { CCIPMessageDecodeError } from './errors/index.ts'
4
+ import type { ChainFamily } from './types.ts'
5
+ import { decodeAddress, getDataBytes, networkInfo } from './utils.ts'
6
+
7
+ /** Token transfer in MessageV1 format. */
8
+ export type TokenTransferV1 = {
9
+ amount: bigint
10
+ sourcePoolAddress: string
11
+ sourceTokenAddress: string
12
+ destTokenAddress: string
13
+ tokenReceiver: string
14
+ extraData: string
15
+ }
16
+
17
+ /** MessageV1 struct matching the Solidity MessageV1Codec format. */
18
+ export type MessageV1 = {
19
+ sourceChainSelector: bigint
20
+ destChainSelector: bigint
21
+ messageNumber: bigint
22
+ executionGasLimit: number
23
+ ccipReceiveGasLimit: number
24
+ finality: number
25
+ ccvAndExecutorHash: string
26
+ onRampAddress: string
27
+ offRampAddress: string
28
+ sender: string
29
+ receiver: string
30
+ destBlob: string
31
+ tokenTransfer: readonly TokenTransferV1[]
32
+ data: string
33
+ }
34
+
35
+ /**
36
+ * Decodes a TokenTransferV1 from bytes.
37
+ * @param encoded - The encoded bytes.
38
+ * @param offset - The starting offset.
39
+ * @param sourceFamily - The source chain family for source addresses.
40
+ * @param destFamily - The destination chain family for dest addresses.
41
+ * @returns The decoded token transfer and the new offset.
42
+ */
43
+ function decodeTokenTransferV1(
44
+ encoded: Uint8Array,
45
+ offset: number,
46
+ sourceFamily: ChainFamily,
47
+ destFamily: ChainFamily,
48
+ ): { tokenTransfer: TokenTransferV1; newOffset: number } {
49
+ // version (1 byte)
50
+ if (offset >= encoded.length) throw new CCIPMessageDecodeError('TOKEN_TRANSFER_VERSION')
51
+ const version = encoded[offset++]!
52
+ if (version !== 1) throw new CCIPMessageDecodeError(`Invalid encoding version: ${version}`)
53
+
54
+ // amount (32 bytes)
55
+ if (offset + 32 > encoded.length) throw new CCIPMessageDecodeError('TOKEN_TRANSFER_AMOUNT')
56
+ const amount = toBigInt(dataSlice(encoded, offset, offset + 32))
57
+ offset += 32
58
+
59
+ // sourcePoolAddressLength and sourcePoolAddress
60
+ if (offset >= encoded.length) {
61
+ throw new CCIPMessageDecodeError('TOKEN_TRANSFER_SOURCE_POOL_LENGTH')
62
+ }
63
+ const sourcePoolAddressLength = encoded[offset++]!
64
+ if (offset + sourcePoolAddressLength > encoded.length) {
65
+ throw new CCIPMessageDecodeError('TOKEN_TRANSFER_SOURCE_POOL_CONTENT')
66
+ }
67
+ const sourcePoolAddress = decodeAddress(
68
+ dataSlice(encoded, offset, offset + sourcePoolAddressLength),
69
+ sourceFamily,
70
+ )
71
+ offset += sourcePoolAddressLength
72
+
73
+ // sourceTokenAddressLength and sourceTokenAddress
74
+ if (offset >= encoded.length) {
75
+ throw new CCIPMessageDecodeError('TOKEN_TRANSFER_SOURCE_TOKEN_LENGTH')
76
+ }
77
+ const sourceTokenAddressLength = encoded[offset++]!
78
+ if (offset + sourceTokenAddressLength > encoded.length) {
79
+ throw new CCIPMessageDecodeError('TOKEN_TRANSFER_SOURCE_TOKEN_CONTENT')
80
+ }
81
+ const sourceTokenAddress = decodeAddress(
82
+ dataSlice(encoded, offset, offset + sourceTokenAddressLength),
83
+ sourceFamily,
84
+ )
85
+ offset += sourceTokenAddressLength
86
+
87
+ // destTokenAddressLength and destTokenAddress
88
+ if (offset >= encoded.length) {
89
+ throw new CCIPMessageDecodeError('TOKEN_TRANSFER_DEST_TOKEN_LENGTH')
90
+ }
91
+ const destTokenAddressLength = encoded[offset++]!
92
+ if (offset + destTokenAddressLength > encoded.length) {
93
+ throw new CCIPMessageDecodeError('TOKEN_TRANSFER_DEST_TOKEN_CONTENT')
94
+ }
95
+ const destTokenAddress = decodeAddress(
96
+ dataSlice(encoded, offset, offset + destTokenAddressLength),
97
+ destFamily,
98
+ )
99
+ offset += destTokenAddressLength
100
+
101
+ // tokenReceiverLength and tokenReceiver
102
+ if (offset >= encoded.length) {
103
+ throw new CCIPMessageDecodeError('TOKEN_TRANSFER_TOKEN_RECEIVER_LENGTH')
104
+ }
105
+ const tokenReceiverLength = encoded[offset++]!
106
+ if (offset + tokenReceiverLength > encoded.length) {
107
+ throw new CCIPMessageDecodeError('TOKEN_TRANSFER_TOKEN_RECEIVER_CONTENT')
108
+ }
109
+ const tokenReceiver = decodeAddress(
110
+ dataSlice(encoded, offset, offset + tokenReceiverLength),
111
+ destFamily,
112
+ )
113
+ offset += tokenReceiverLength
114
+
115
+ // extraDataLength and extraData
116
+ if (offset + 2 > encoded.length) {
117
+ throw new CCIPMessageDecodeError('TOKEN_TRANSFER_EXTRA_DATA_LENGTH')
118
+ }
119
+ const extraDataLength = toNumber(dataSlice(encoded, offset, offset + 2))
120
+ offset += 2
121
+ if (offset + extraDataLength > encoded.length) {
122
+ throw new CCIPMessageDecodeError('TOKEN_TRANSFER_EXTRA_DATA_CONTENT')
123
+ }
124
+ const extraData = hexlify(dataSlice(encoded, offset, offset + extraDataLength))
125
+ offset += extraDataLength
126
+
127
+ return {
128
+ tokenTransfer: {
129
+ amount,
130
+ sourcePoolAddress,
131
+ sourceTokenAddress,
132
+ destTokenAddress,
133
+ tokenReceiver,
134
+ extraData,
135
+ },
136
+ newOffset: offset,
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Decodes a MessageV1 from bytes following the v1 protocol format.
142
+ * @param encodedMessage - The encoded message bytes to decode.
143
+ * @returns The decoded MessageV1 struct.
144
+ */
145
+ export function decodeMessageV1(encodedMessage: BytesLike): MessageV1 {
146
+ const MESSAGE_V1_BASE_SIZE = 77
147
+ const encoded = getDataBytes(encodedMessage)
148
+
149
+ if (encoded.length < MESSAGE_V1_BASE_SIZE) throw new CCIPMessageDecodeError('MESSAGE_MIN_SIZE')
150
+
151
+ const version = encoded[0]!
152
+ if (version !== 1) throw new CCIPMessageDecodeError(`Invalid encoding version: ${version}`)
153
+
154
+ // sourceChainSelector (8 bytes, big endian)
155
+ const sourceChainSelector = toBigInt(dataSlice(encoded, 1, 9))
156
+
157
+ // destChainSelector (8 bytes, big endian)
158
+ const destChainSelector = toBigInt(dataSlice(encoded, 9, 17))
159
+
160
+ // Get chain families for address decoding
161
+ const sourceNetworkInfo = networkInfo(sourceChainSelector)
162
+ const destNetworkInfo = networkInfo(destChainSelector)
163
+ const sourceFamily = sourceNetworkInfo.family
164
+ const destFamily = destNetworkInfo.family
165
+
166
+ // messageNumber (8 bytes, big endian)
167
+ const messageNumber = toBigInt(dataSlice(encoded, 17, 25))
168
+
169
+ // executionGasLimit (4 bytes, big endian)
170
+ const executionGasLimit = toNumber(dataSlice(encoded, 25, 29))
171
+
172
+ // ccipReceiveGasLimit (4 bytes, big endian)
173
+ const ccipReceiveGasLimit = toNumber(dataSlice(encoded, 29, 33))
174
+
175
+ // finality (2 bytes, big endian)
176
+ const finality = toNumber(dataSlice(encoded, 33, 35))
177
+
178
+ // ccvAndExecutorHash (32 bytes)
179
+ const ccvAndExecutorHash = hexlify(dataSlice(encoded, 35, 67))
180
+
181
+ // onRampAddressLength and onRampAddress
182
+ let offset = 67
183
+ if (offset >= encoded.length) throw new CCIPMessageDecodeError('MESSAGE_ONRAMP_ADDRESS_LENGTH')
184
+ const onRampAddressLength = encoded[offset++]!
185
+ if (offset + onRampAddressLength > encoded.length) {
186
+ throw new CCIPMessageDecodeError('MESSAGE_ONRAMP_ADDRESS_CONTENT')
187
+ }
188
+ const onRampAddress = decodeAddress(
189
+ dataSlice(encoded, offset, offset + onRampAddressLength),
190
+ sourceFamily,
191
+ )
192
+ offset += onRampAddressLength
193
+
194
+ // offRampAddressLength and offRampAddress
195
+ if (offset >= encoded.length) throw new CCIPMessageDecodeError('MESSAGE_OFFRAMP_ADDRESS_LENGTH')
196
+ const offRampAddressLength = encoded[offset++]!
197
+ if (offset + offRampAddressLength > encoded.length) {
198
+ throw new CCIPMessageDecodeError('MESSAGE_OFFRAMP_ADDRESS_CONTENT')
199
+ }
200
+ const offRampAddress = decodeAddress(
201
+ dataSlice(encoded, offset, offset + offRampAddressLength),
202
+ destFamily,
203
+ )
204
+ offset += offRampAddressLength
205
+
206
+ // senderLength and sender
207
+ if (offset >= encoded.length) throw new CCIPMessageDecodeError('MESSAGE_SENDER_LENGTH')
208
+ const senderLength = encoded[offset++]!
209
+ if (offset + senderLength > encoded.length) {
210
+ throw new CCIPMessageDecodeError('MESSAGE_SENDER_CONTENT')
211
+ }
212
+ const sender = decodeAddress(dataSlice(encoded, offset, offset + senderLength), sourceFamily)
213
+ offset += senderLength
214
+
215
+ // receiverLength and receiver
216
+ if (offset >= encoded.length) throw new CCIPMessageDecodeError('MESSAGE_RECEIVER_LENGTH')
217
+ const receiverLength = encoded[offset++]!
218
+ if (offset + receiverLength > encoded.length) {
219
+ throw new CCIPMessageDecodeError('MESSAGE_RECEIVER_CONTENT')
220
+ }
221
+ const receiver = decodeAddress(dataSlice(encoded, offset, offset + receiverLength), destFamily)
222
+ offset += receiverLength
223
+
224
+ // destBlobLength and destBlob
225
+ if (offset + 2 > encoded.length) throw new CCIPMessageDecodeError('MESSAGE_DEST_BLOB_LENGTH')
226
+ const destBlobLength = toNumber(dataSlice(encoded, offset, offset + 2))
227
+ offset += 2
228
+ if (offset + destBlobLength > encoded.length) {
229
+ throw new CCIPMessageDecodeError('MESSAGE_DEST_BLOB_CONTENT')
230
+ }
231
+ const destBlob = hexlify(dataSlice(encoded, offset, offset + destBlobLength))
232
+ offset += destBlobLength
233
+
234
+ // tokenTransferLength and tokenTransfer
235
+ if (offset + 2 > encoded.length) throw new CCIPMessageDecodeError('MESSAGE_TOKEN_TRANSFER_LENGTH')
236
+ const tokenTransferLength = toNumber(dataSlice(encoded, offset, offset + 2))
237
+ offset += 2
238
+
239
+ // Decode token transfer, which is either 0 or 1
240
+ const tokenTransfer: TokenTransferV1[] = []
241
+ if (tokenTransferLength > 0) {
242
+ const expectedEnd = offset + tokenTransferLength
243
+ const result = decodeTokenTransferV1(encoded, offset, sourceFamily, destFamily)
244
+ tokenTransfer.push(result.tokenTransfer)
245
+ offset = result.newOffset
246
+ if (offset !== expectedEnd) throw new CCIPMessageDecodeError('MESSAGE_TOKEN_TRANSFER_CONTENT')
247
+ }
248
+
249
+ // dataLength and data
250
+ if (offset + 2 > encoded.length) throw new CCIPMessageDecodeError('MESSAGE_DATA_LENGTH')
251
+ const dataLength = toNumber(dataSlice(encoded, offset, offset + 2))
252
+ offset += 2
253
+ if (offset + dataLength > encoded.length) {
254
+ throw new CCIPMessageDecodeError('MESSAGE_DATA_CONTENT')
255
+ }
256
+ const data = hexlify(dataSlice(encoded, offset, offset + dataLength))
257
+ offset += dataLength
258
+
259
+ // Ensure we've consumed all bytes
260
+ if (offset !== encoded.length) throw new CCIPMessageDecodeError('MESSAGE_FINAL_OFFSET')
261
+
262
+ return {
263
+ sourceChainSelector,
264
+ destChainSelector,
265
+ messageNumber,
266
+ executionGasLimit,
267
+ ccipReceiveGasLimit,
268
+ finality,
269
+ ccvAndExecutorHash,
270
+ onRampAddress,
271
+ offRampAddress,
272
+ sender,
273
+ receiver,
274
+ destBlob,
275
+ tokenTransfer,
276
+ data,
277
+ }
278
+ }
package/src/offchain.ts CHANGED
@@ -1,56 +1,79 @@
1
- import { keccak256 } from 'ethers'
1
+ import { type BytesLike, dataLength, dataSlice, getBytes, toNumber } from 'ethers'
2
+ import type { PickDeep } from 'type-fest'
2
3
 
3
4
  import {
4
5
  CCIPLbtcAttestationNotApprovedError,
5
6
  CCIPLbtcAttestationNotFoundError,
6
7
  CCIPUsdcAttestationError,
7
8
  } from './errors/index.ts'
8
- import { NetworkType } from './types.ts'
9
+ import { parseSourceTokenData } from './evm/messages.ts'
10
+ import { type CCIPRequest, type OffchainTokenData, type WithLogger, NetworkType } from './types.ts'
11
+ import { networkInfo } from './utils.ts'
9
12
 
10
13
  const CIRCLE_API_URL = {
11
- mainnet: 'https://iris-api.circle.com/v1',
12
- testnet: 'https://iris-api-sandbox.circle.com/v1',
14
+ mainnet: 'https://iris-api.circle.com',
15
+ testnet: 'https://iris-api-sandbox.circle.com',
13
16
  }
14
17
 
15
18
  type CctpAttestationResponse =
16
19
  | { error: 'string' }
17
- | { status: 'pending_confirmations' }
18
- | { status: 'complete'; attestation: string }
19
-
20
- const LOMBARD_API_URL = {
21
- mainnet: 'https://mainnet.prod.lombard.finance',
22
- testnet: 'https://gastald-testnet.prod.lombard.finance',
23
- }
24
-
25
- type LombardAttestation =
26
- | { status: 'NOTARIZATION_STATUS_SESSION_APPROVED'; message_hash: string; attestation: string }
27
- | { status: string; message_hash: string }
28
- type LombardAttestationsResponse = { attestations: Array<LombardAttestation> }
20
+ | {
21
+ messages: {
22
+ status: 'pending_confirmations' | 'complete'
23
+ eventNonce?: string
24
+ attestation: string
25
+ message: string
26
+ }[]
27
+ }
29
28
 
30
29
  /**
31
- * Returns the USDC attestation for a given MessageSent Log
32
- * https://developers.circle.com/stablecoins/reference/getattestation
30
+ * Returns the USDC attestation for a given tokenAmount.extraData and txHash
31
+ * https://developers.circle.com/cctp/quickstarts/transfer-usdc-ethereum-to-arc#3-3-retrieve-attestation
33
32
  *
34
- * @param message - payload of USDC MessageSent(bytes message) event
33
+ * @param opts - CCTPv2 options
35
34
  * @param networkType - network type (mainnet or testnet)
36
- * @returns USDC/CCTP attestation bytes
35
+ * @returns USDC/CCTP attestation and message
37
36
  */
38
37
  export async function getUsdcAttestation(
39
- message: string,
38
+ opts: {
39
+ /** CCTP sourceDomain */
40
+ sourceDomain: number
41
+ /** CCTP burn eventNonce */
42
+ nonce: number
43
+ /** burn txHash, same as CCIP request */
44
+ txHash: string
45
+ },
40
46
  networkType: NetworkType,
41
- ): Promise<string> {
42
- const msgHash = keccak256(message)
43
-
47
+ ): Promise<{ attestation: string; message: string }> {
48
+ const { sourceDomain, nonce, txHash } = opts
44
49
  const circleApiBaseUrl =
45
50
  networkType === NetworkType.Mainnet ? CIRCLE_API_URL.mainnet : CIRCLE_API_URL.testnet
46
- const res = await fetch(`${circleApiBaseUrl}/attestations/${msgHash}`)
51
+ const res = await fetch(
52
+ `${circleApiBaseUrl}/v2/messages/${sourceDomain}?transactionHash=${txHash}`,
53
+ )
47
54
  const json = (await res.json()) as CctpAttestationResponse
48
- if (!('status' in json) || json.status !== 'complete' || !json.attestation) {
49
- throw new CCIPUsdcAttestationError(msgHash, json)
55
+ let att
56
+ if ('messages' in json) {
57
+ att = json.messages.find((m) => m.status === 'complete' && m.eventNonce === nonce.toString())
50
58
  }
51
- return json.attestation
59
+ if (!att?.message) throw new CCIPUsdcAttestationError(txHash, json, { context: opts })
60
+ return att
61
+ }
62
+
63
+ const LOMBARD_API_URL = {
64
+ mainnet: 'https://mainnet.prod.lombard.finance',
65
+ testnet: 'https://gastald-testnet.prod.lombard.finance',
52
66
  }
53
67
 
68
+ type LombardAttestation =
69
+ | {
70
+ status: 'NOTARIZATION_STATUS_SESSION_APPROVED'
71
+ message_hash: string
72
+ attestation: string
73
+ }
74
+ | { status: string; message_hash: string }
75
+ type LombardAttestationsResponse = { attestations: Array<LombardAttestation> }
76
+
54
77
  /**
55
78
  * Returns the LBTC attestation for a given payload hash
56
79
  *
@@ -86,3 +109,77 @@ export async function getLbtcAttestation(
86
109
  }
87
110
  return attestation
88
111
  }
112
+
113
+ /**
114
+ * Fetch CCIPv1 offchain token data for USDC and LBTC tokenAmounts
115
+ * @param request - CCIPRequest containing tx.hash and message
116
+ * @returns Promise resolving to an OffchainTokenData for each tokenAmount
117
+ */
118
+ export async function getOffchainTokenData(
119
+ request: PickDeep<CCIPRequest, 'tx.hash' | `message`>,
120
+ { logger = console }: WithLogger = {},
121
+ ): Promise<OffchainTokenData[]> {
122
+ const { networkType } = networkInfo(request.message.sourceChainSelector)
123
+
124
+ function looksUsdcData(extraData: BytesLike) {
125
+ if (dataLength(extraData) !== 64) return
126
+ // USDCTokenPool's extraData is a packed `SourceTokenDataPayloadV1{uint64 nonce, uint32 sourceDomain}`,
127
+ // which we need to query CCTPv2 (by sourceDomain and txHash) and to filter by nonce among messages,
128
+ // if more than one in tx
129
+ let nonce, sourceDomain
130
+ try {
131
+ // those toNumber conversions throw early in case the bytearray don't look like small numbers
132
+ nonce = toNumber(dataSlice(extraData, 0, 32))
133
+ sourceDomain = toNumber(dataSlice(extraData, 32, 32 + 32))
134
+ return { nonce, sourceDomain } // maybe USDC
135
+ } catch {
136
+ // not USDC
137
+ }
138
+ }
139
+
140
+ function looksLbtcData(extraData: BytesLike) {
141
+ // LBTC returns `message_hash`/`payloadHash` directly as `bytes32 extraData`
142
+ if (
143
+ dataLength(extraData) === 32 &&
144
+ getBytes(extraData, 'extraData').filter(Boolean).length > 20 // looks like a hash
145
+ )
146
+ return true
147
+ }
148
+
149
+ return Promise.all(
150
+ request.message.tokenAmounts.map(async (tokenAmount, i) => {
151
+ let extraData
152
+ if ('extraData' in tokenAmount) {
153
+ extraData = tokenAmount.extraData
154
+ } else if ('sourceTokenData' in request.message) {
155
+ // v1.2..v1.5
156
+ if (dataLength(request.message.sourceTokenData[i]!) === 64) {
157
+ extraData = request.message.sourceTokenData[i]
158
+ } else {
159
+ ;({ extraData } = parseSourceTokenData(request.message.sourceTokenData[i]!))
160
+ }
161
+ }
162
+ if (!extraData) return
163
+ const usdcOpts = looksUsdcData(extraData)
164
+ if (usdcOpts) {
165
+ try {
166
+ const usdcAttestation = await getUsdcAttestation(
167
+ { ...usdcOpts, txHash: request.tx.hash },
168
+ networkType,
169
+ )
170
+ return { _tag: 'usdc', extraData, ...usdcAttestation }
171
+ } catch (err) {
172
+ // maybe not a USDC transfer, or not ready
173
+ logger.warn(`❌ CCTP: Failed to fetch attestation for message:`, request.message, err)
174
+ }
175
+ } else if (looksLbtcData(extraData)) {
176
+ try {
177
+ const lbtcAttestation = await getLbtcAttestation(extraData, networkType)
178
+ return { _tag: 'lbtc', extraData, ...lbtcAttestation }
179
+ } catch (err) {
180
+ logger.warn(`❌ LBTC: Failed to fetch attestation for message:`, extraData, err)
181
+ }
182
+ }
183
+ }),
184
+ )
185
+ }
package/src/requests.ts CHANGED
@@ -19,8 +19,8 @@ import {
19
19
  type CCIPMessage,
20
20
  type CCIPRequest,
21
21
  type CCIPVersion,
22
+ type ChainLog,
22
23
  type ChainTransaction,
23
- type Log_,
24
24
  type MessageInput,
25
25
  ChainFamily,
26
26
  } from './types.ts'
@@ -78,7 +78,6 @@ function decodeJsonMessage(data: Record<string, unknown> | undefined) {
78
78
  data_.sourceNetworkInfo?.chainSelector
79
79
  if (!sourceChainSelector) throw new CCIPMessageInvalidError(data)
80
80
  data_.sourceChainSelector ??= sourceChainSelector
81
- data_.nonce ??= 0n
82
81
  const sourceFamily = networkInfo(sourceChainSelector).family
83
82
 
84
83
  const destChainSelector =
@@ -181,7 +180,7 @@ export function decodeMessage(data: string | Uint8Array | Record<string, unknown
181
180
  // try bytearray decoding on each supported chain
182
181
  for (const chain of Object.values(supportedChains)) {
183
182
  try {
184
- const decoded = chain.decodeMessage({ data } as unknown as Log_)
183
+ const decoded = chain.decodeMessage({ data } as unknown as ChainLog)
185
184
  if (decoded) return decoded
186
185
  } catch (_) {
187
186
  // continue
@@ -93,7 +93,7 @@ export async function cleanUpBuffers(
93
93
  } catch (err) {
94
94
  const info = await connection.getAddressLookupTable(lookupTable)
95
95
  if (!info.value) break
96
- else if (info.value.state.deactivationSlot < 2n ** 63n)
96
+ else if (info.value.state.deactivationSlot < BigInt(2) ** BigInt(63))
97
97
  deactivationSlot = Number(info.value.state.deactivationSlot)
98
98
  logger.warn('Failed to close lookup table', altAddr, err)
99
99
  }
@@ -196,7 +196,7 @@ export async function cleanUpBuffers(
196
196
  'not owned by us, but by',
197
197
  info.value.state.authority?.toBase58(),
198
198
  )
199
- } else if (info.value.state.deactivationSlot < 2n ** 63n) {
199
+ } else if (info.value.state.deactivationSlot < BigInt(2) ** BigInt(63)) {
200
200
  // non-deactivated have deactivationSlot=MAX_UINT64
201
201
  pendingPromises.push(closeAlt(lookupTable, Number(info.value.state.deactivationSlot)))
202
202
  } else if (
@@ -198,7 +198,7 @@ async function buildLookupTableIxs(
198
198
  lookupTable: new AddressLookupTableAccount({
199
199
  key: altAddr,
200
200
  state: {
201
- deactivationSlot: 2n ** 64n - 1n,
201
+ deactivationSlot: BigInt(2) ** BigInt(64) - BigInt(1),
202
202
  lastExtendedSlot: recentSlot,
203
203
  lastExtendedSlotStartIndex: 0,
204
204
  addresses,
@@ -3,7 +3,7 @@
3
3
  // .then((res) => res.text())
4
4
  // .then((text) => text.trim())
5
5
  export type BaseTokenPool = {
6
- version: '1.6.0'
6
+ version: '1.6.1'
7
7
  name: 'base_token_pool'
8
8
  instructions: []
9
9
  types: [
@@ -868,7 +868,7 @@ export type BaseTokenPool = {
868
868
  }
869
869
 
870
870
  export const IDL: BaseTokenPool = {
871
- version: '1.6.0',
871
+ version: '1.6.1',
872
872
  name: 'base_token_pool',
873
873
  instructions: [],
874
874
  types: [
@@ -3,7 +3,7 @@
3
3
  // .then((res) => res.text())
4
4
  // .then((text) => text.trim())
5
5
  export type BurnmintTokenPool = {
6
- version: '1.6.0'
6
+ version: '1.6.1'
7
7
  name: 'burnmint_token_pool'
8
8
  instructions: [
9
9
  {
@@ -951,7 +951,7 @@ export type BurnmintTokenPool = {
951
951
  }
952
952
 
953
953
  export const IDL: BurnmintTokenPool = {
954
- version: '1.6.0',
954
+ version: '1.6.1',
955
955
  name: 'burnmint_token_pool',
956
956
  instructions: [
957
957
  {
@@ -3,7 +3,7 @@
3
3
  // .then((res) => res.text())
4
4
  // .then((text) => text.trim())
5
5
  export type CctpTokenPool = {
6
- version: '1.6.0'
6
+ version: '1.6.1'
7
7
  name: 'cctp_token_pool'
8
8
  instructions: [
9
9
  {
@@ -1376,7 +1376,7 @@ export type CctpTokenPool = {
1376
1376
  }
1377
1377
 
1378
1378
  export const IDL: CctpTokenPool = {
1379
- version: '1.6.0',
1379
+ version: '1.6.1',
1380
1380
  name: 'cctp_token_pool',
1381
1381
  instructions: [
1382
1382
  {
@@ -3,7 +3,7 @@
3
3
  // .then((res) => res.text())
4
4
  // .then((text) => text.trim())
5
5
  export type CcipCommon = {
6
- version: '0.1.1'
6
+ version: '1.6.1'
7
7
  name: 'ccip_common'
8
8
  instructions: []
9
9
  accounts: [
@@ -102,11 +102,26 @@ export type CcipCommon = {
102
102
  name: 'InvalidSVMAddress'
103
103
  msg: 'Invalid SVM address'
104
104
  },
105
+ {
106
+ code: 10011
107
+ name: 'InvalidTVMAddress'
108
+ msg: 'Invalid TVM address'
109
+ },
110
+ {
111
+ code: 10012
112
+ name: 'InvalidAptosAddress'
113
+ msg: 'Invalid Aptos address'
114
+ },
115
+ {
116
+ code: 10013
117
+ name: 'InvalidSuiAddress'
118
+ msg: 'Invalid Sui address'
119
+ },
105
120
  ]
106
121
  }
107
122
 
108
123
  export const IDL: CcipCommon = {
109
- version: '0.1.1',
124
+ version: '1.6.1',
110
125
  name: 'ccip_common',
111
126
  instructions: [],
112
127
  accounts: [
@@ -205,6 +220,21 @@ export const IDL: CcipCommon = {
205
220
  name: 'InvalidSVMAddress',
206
221
  msg: 'Invalid SVM address',
207
222
  },
223
+ {
224
+ code: 10011,
225
+ name: 'InvalidTVMAddress',
226
+ msg: 'Invalid TVM address',
227
+ },
228
+ {
229
+ code: 10012,
230
+ name: 'InvalidAptosAddress',
231
+ msg: 'Invalid Aptos address',
232
+ },
233
+ {
234
+ code: 10013,
235
+ name: 'InvalidSuiAddress',
236
+ msg: 'Invalid Sui address',
237
+ },
208
238
  ],
209
239
  }
210
240
  // generate:end
@@ -3,7 +3,7 @@
3
3
  // .then((res) => res.text())
4
4
  // .then((text) => text.trim())
5
5
  export type CcipOfframp = {
6
- version: '0.1.1'
6
+ version: '1.6.1'
7
7
  name: 'ccip_offramp'
8
8
  constants: [
9
9
  {
@@ -2748,7 +2748,7 @@ export type CcipOfframp = {
2748
2748
  }
2749
2749
 
2750
2750
  export const IDL: CcipOfframp = {
2751
- version: '0.1.1',
2751
+ version: '1.6.1',
2752
2752
  name: 'ccip_offramp',
2753
2753
  constants: [
2754
2754
  {
@@ -8,7 +8,7 @@
8
8
  // else throw new Error('isMut is already true, no need for this workaround anymore');
9
9
  // }))
10
10
  export type CcipRouter = {
11
- version: '0.1.1'
11
+ version: '1.6.1'
12
12
  name: 'ccip_router'
13
13
  docs: [
14
14
  'The `ccip_router` module contains the implementation of the Cross-Chain Interoperability Protocol (CCIP) Router.',
@@ -2339,7 +2339,7 @@ export type CcipRouter = {
2339
2339
  }
2340
2340
 
2341
2341
  export const IDL: CcipRouter = {
2342
- version: '0.1.1',
2342
+ version: '1.6.1',
2343
2343
  name: 'ccip_router',
2344
2344
  docs: [
2345
2345
  'The `ccip_router` module contains the implementation of the Cross-Chain Interoperability Protocol (CCIP) Router.',