@chainlink/ccip-sdk 1.0.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.
- package/dist/api/index.d.ts +4 -4
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +110 -11
- package/dist/api/index.js.map +1 -1
- package/dist/api/types.d.ts +34 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/chain.d.ts +93 -4
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +78 -8
- package/dist/chain.js.map +1 -1
- package/dist/errors/codes.d.ts +1 -1
- package/dist/errors/codes.d.ts.map +1 -1
- package/dist/errors/codes.js +2 -1
- package/dist/errors/codes.js.map +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +2 -2
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/recovery.js +1 -1
- package/dist/errors/recovery.js.map +1 -1
- package/dist/errors/specialized.d.ts +22 -19
- package/dist/errors/specialized.d.ts.map +1 -1
- package/dist/errors/specialized.js +30 -25
- package/dist/errors/specialized.js.map +1 -1
- package/dist/evm/abi/OffRamp_2_0.d.ts +24 -12
- package/dist/evm/abi/OffRamp_2_0.d.ts.map +1 -1
- package/dist/evm/abi/OffRamp_2_0.js +16 -8
- package/dist/evm/abi/OffRamp_2_0.js.map +1 -1
- package/dist/evm/abi/TokenPool_2_0.d.ts +1552 -0
- package/dist/evm/abi/TokenPool_2_0.d.ts.map +1 -0
- package/dist/evm/abi/TokenPool_2_0.js +1637 -0
- package/dist/evm/abi/TokenPool_2_0.js.map +1 -0
- package/dist/evm/const.d.ts +1 -0
- package/dist/evm/const.d.ts.map +1 -1
- package/dist/evm/const.js +2 -0
- package/dist/evm/const.js.map +1 -1
- package/dist/evm/index.d.ts +10 -4
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +138 -41
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/messages.d.ts +2 -33
- package/dist/evm/messages.d.ts.map +1 -1
- package/dist/evm/messages.js +0 -210
- package/dist/evm/messages.js.map +1 -1
- package/dist/gas.d.ts +4 -0
- package/dist/gas.d.ts.map +1 -1
- package/dist/gas.js +27 -21
- package/dist/gas.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/messages.d.ts +34 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +211 -0
- package/dist/messages.js.map +1 -0
- package/dist/solana/cleanup.js +2 -2
- package/dist/solana/cleanup.js.map +1 -1
- package/dist/solana/exec.js +1 -1
- package/dist/solana/exec.js.map +1 -1
- package/dist/solana/index.d.ts +19 -19
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +14 -0
- package/dist/solana/index.js.map +1 -1
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +14 -1
- package/dist/sui/index.js.map +1 -1
- package/dist/ton/index.d.ts.map +1 -1
- package/dist/ton/index.js +3 -1
- package/dist/ton/index.js.map +1 -1
- package/package.json +5 -5
- package/src/api/index.ts +126 -11
- package/src/api/types.ts +43 -0
- package/src/chain.ts +131 -11
- package/src/errors/codes.ts +2 -1
- package/src/errors/index.ts +1 -1
- package/src/errors/recovery.ts +1 -1
- package/src/errors/specialized.ts +35 -30
- package/src/evm/abi/OffRamp_2_0.ts +16 -8
- package/src/evm/abi/TokenPool_2_0.ts +1636 -0
- package/src/evm/const.ts +2 -0
- package/src/evm/index.ts +230 -75
- package/src/evm/messages.ts +3 -285
- package/src/gas.ts +27 -19
- package/src/index.ts +2 -1
- package/src/messages.ts +278 -0
- package/src/solana/cleanup.ts +2 -2
- package/src/solana/exec.ts +1 -1
- package/src/solana/index.ts +17 -0
- package/src/sui/index.ts +17 -0
- package/src/ton/index.ts +5 -1
package/src/evm/messages.ts
CHANGED
|
@@ -3,25 +3,16 @@ import type {
|
|
|
3
3
|
AbiParametersToPrimitiveTypes,
|
|
4
4
|
ExtractAbiEvent,
|
|
5
5
|
} from 'abitype'
|
|
6
|
-
import {
|
|
7
|
-
type Addressable,
|
|
8
|
-
type BytesLike,
|
|
9
|
-
type Result,
|
|
10
|
-
dataSlice,
|
|
11
|
-
hexlify,
|
|
12
|
-
toBigInt,
|
|
13
|
-
toNumber,
|
|
14
|
-
} from 'ethers'
|
|
6
|
+
import type { Addressable, Result } from 'ethers'
|
|
15
7
|
import type { Simplify } from 'type-fest'
|
|
16
8
|
|
|
17
|
-
import { CCIPMessageDecodeError } from '../errors/index.ts'
|
|
18
9
|
import type { EVMExtraArgsV2 } from '../extra-args.ts'
|
|
19
|
-
import type { CCIPVersion,
|
|
20
|
-
import { decodeAddress, getDataBytes, networkInfo } from '../utils.ts'
|
|
10
|
+
import type { CCIPVersion, MergeArrayElements } from '../types.ts'
|
|
21
11
|
import type EVM2EVMOnRamp_1_5_ABI from './abi/OnRamp_1_5.ts'
|
|
22
12
|
import type OnRamp_1_6_ABI from './abi/OnRamp_1_6.ts'
|
|
23
13
|
import type OnRamp_2_0_ABI from './abi/OnRamp_2_0.ts'
|
|
24
14
|
import { defaultAbiCoder } from './const.ts'
|
|
15
|
+
import type { MessageV1 } from '../messages.ts'
|
|
25
16
|
|
|
26
17
|
/** Utility type that cleans up address types to just `string`. */
|
|
27
18
|
export type CleanAddressable<T> = T extends string | Addressable
|
|
@@ -32,279 +23,6 @@ export type CleanAddressable<T> = T extends string | Addressable
|
|
|
32
23
|
? { readonly [K in keyof T]: CleanAddressable<T[K]> }
|
|
33
24
|
: T
|
|
34
25
|
|
|
35
|
-
/** Token transfer in MessageV1 format. */
|
|
36
|
-
export type TokenTransferV1 = {
|
|
37
|
-
amount: bigint
|
|
38
|
-
sourcePoolAddress: string
|
|
39
|
-
sourceTokenAddress: string
|
|
40
|
-
destTokenAddress: string
|
|
41
|
-
tokenReceiver: string
|
|
42
|
-
extraData: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** MessageV1 struct matching the Solidity MessageV1Codec format. */
|
|
46
|
-
export type MessageV1 = {
|
|
47
|
-
sourceChainSelector: bigint
|
|
48
|
-
destChainSelector: bigint
|
|
49
|
-
messageNumber: bigint
|
|
50
|
-
executionGasLimit: number
|
|
51
|
-
ccipReceiveGasLimit: number
|
|
52
|
-
finality: number
|
|
53
|
-
ccvAndExecutorHash: string
|
|
54
|
-
onRampAddress: string
|
|
55
|
-
offRampAddress: string
|
|
56
|
-
sender: string
|
|
57
|
-
receiver: string
|
|
58
|
-
destBlob: string
|
|
59
|
-
tokenTransfer: readonly TokenTransferV1[]
|
|
60
|
-
data: string
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Decodes a TokenTransferV1 from bytes.
|
|
65
|
-
* @param encoded - The encoded bytes.
|
|
66
|
-
* @param offset - The starting offset.
|
|
67
|
-
* @param sourceFamily - The source chain family for source addresses.
|
|
68
|
-
* @param destFamily - The destination chain family for dest addresses.
|
|
69
|
-
* @returns The decoded token transfer and the new offset.
|
|
70
|
-
*/
|
|
71
|
-
function decodeTokenTransferV1(
|
|
72
|
-
encoded: Uint8Array,
|
|
73
|
-
offset: number,
|
|
74
|
-
sourceFamily: ChainFamily,
|
|
75
|
-
destFamily: ChainFamily,
|
|
76
|
-
): { tokenTransfer: TokenTransferV1; newOffset: number } {
|
|
77
|
-
// version (1 byte)
|
|
78
|
-
if (offset >= encoded.length) throw new CCIPMessageDecodeError('TOKEN_TRANSFER_VERSION')
|
|
79
|
-
const version = encoded[offset++]!
|
|
80
|
-
if (version !== 1) throw new CCIPMessageDecodeError(`Invalid encoding version: ${version}`)
|
|
81
|
-
|
|
82
|
-
// amount (32 bytes)
|
|
83
|
-
if (offset + 32 > encoded.length) throw new CCIPMessageDecodeError('TOKEN_TRANSFER_AMOUNT')
|
|
84
|
-
const amount = toBigInt(dataSlice(encoded, offset, offset + 32))
|
|
85
|
-
offset += 32
|
|
86
|
-
|
|
87
|
-
// sourcePoolAddressLength and sourcePoolAddress
|
|
88
|
-
if (offset >= encoded.length) {
|
|
89
|
-
throw new CCIPMessageDecodeError('TOKEN_TRANSFER_SOURCE_POOL_LENGTH')
|
|
90
|
-
}
|
|
91
|
-
const sourcePoolAddressLength = encoded[offset++]!
|
|
92
|
-
if (offset + sourcePoolAddressLength > encoded.length) {
|
|
93
|
-
throw new CCIPMessageDecodeError('TOKEN_TRANSFER_SOURCE_POOL_CONTENT')
|
|
94
|
-
}
|
|
95
|
-
const sourcePoolAddress = decodeAddress(
|
|
96
|
-
dataSlice(encoded, offset, offset + sourcePoolAddressLength),
|
|
97
|
-
sourceFamily,
|
|
98
|
-
)
|
|
99
|
-
offset += sourcePoolAddressLength
|
|
100
|
-
|
|
101
|
-
// sourceTokenAddressLength and sourceTokenAddress
|
|
102
|
-
if (offset >= encoded.length) {
|
|
103
|
-
throw new CCIPMessageDecodeError('TOKEN_TRANSFER_SOURCE_TOKEN_LENGTH')
|
|
104
|
-
}
|
|
105
|
-
const sourceTokenAddressLength = encoded[offset++]!
|
|
106
|
-
if (offset + sourceTokenAddressLength > encoded.length) {
|
|
107
|
-
throw new CCIPMessageDecodeError('TOKEN_TRANSFER_SOURCE_TOKEN_CONTENT')
|
|
108
|
-
}
|
|
109
|
-
const sourceTokenAddress = decodeAddress(
|
|
110
|
-
dataSlice(encoded, offset, offset + sourceTokenAddressLength),
|
|
111
|
-
sourceFamily,
|
|
112
|
-
)
|
|
113
|
-
offset += sourceTokenAddressLength
|
|
114
|
-
|
|
115
|
-
// destTokenAddressLength and destTokenAddress
|
|
116
|
-
if (offset >= encoded.length) {
|
|
117
|
-
throw new CCIPMessageDecodeError('TOKEN_TRANSFER_DEST_TOKEN_LENGTH')
|
|
118
|
-
}
|
|
119
|
-
const destTokenAddressLength = encoded[offset++]!
|
|
120
|
-
if (offset + destTokenAddressLength > encoded.length) {
|
|
121
|
-
throw new CCIPMessageDecodeError('TOKEN_TRANSFER_DEST_TOKEN_CONTENT')
|
|
122
|
-
}
|
|
123
|
-
const destTokenAddress = decodeAddress(
|
|
124
|
-
dataSlice(encoded, offset, offset + destTokenAddressLength),
|
|
125
|
-
destFamily,
|
|
126
|
-
)
|
|
127
|
-
offset += destTokenAddressLength
|
|
128
|
-
|
|
129
|
-
// tokenReceiverLength and tokenReceiver
|
|
130
|
-
if (offset >= encoded.length) {
|
|
131
|
-
throw new CCIPMessageDecodeError('TOKEN_TRANSFER_TOKEN_RECEIVER_LENGTH')
|
|
132
|
-
}
|
|
133
|
-
const tokenReceiverLength = encoded[offset++]!
|
|
134
|
-
if (offset + tokenReceiverLength > encoded.length) {
|
|
135
|
-
throw new CCIPMessageDecodeError('TOKEN_TRANSFER_TOKEN_RECEIVER_CONTENT')
|
|
136
|
-
}
|
|
137
|
-
const tokenReceiver = decodeAddress(
|
|
138
|
-
dataSlice(encoded, offset, offset + tokenReceiverLength),
|
|
139
|
-
destFamily,
|
|
140
|
-
)
|
|
141
|
-
offset += tokenReceiverLength
|
|
142
|
-
|
|
143
|
-
// extraDataLength and extraData
|
|
144
|
-
if (offset + 2 > encoded.length) {
|
|
145
|
-
throw new CCIPMessageDecodeError('TOKEN_TRANSFER_EXTRA_DATA_LENGTH')
|
|
146
|
-
}
|
|
147
|
-
const extraDataLength = toNumber(dataSlice(encoded, offset, offset + 2))
|
|
148
|
-
offset += 2
|
|
149
|
-
if (offset + extraDataLength > encoded.length) {
|
|
150
|
-
throw new CCIPMessageDecodeError('TOKEN_TRANSFER_EXTRA_DATA_CONTENT')
|
|
151
|
-
}
|
|
152
|
-
const extraData = hexlify(dataSlice(encoded, offset, offset + extraDataLength))
|
|
153
|
-
offset += extraDataLength
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
tokenTransfer: {
|
|
157
|
-
amount,
|
|
158
|
-
sourcePoolAddress,
|
|
159
|
-
sourceTokenAddress,
|
|
160
|
-
destTokenAddress,
|
|
161
|
-
tokenReceiver,
|
|
162
|
-
extraData,
|
|
163
|
-
},
|
|
164
|
-
newOffset: offset,
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Decodes a MessageV1 from bytes following the v1 protocol format.
|
|
170
|
-
* @param encodedMessage - The encoded message bytes to decode.
|
|
171
|
-
* @returns The decoded MessageV1 struct.
|
|
172
|
-
*/
|
|
173
|
-
export function decodeMessageV1(encodedMessage: BytesLike): MessageV1 {
|
|
174
|
-
const MESSAGE_V1_BASE_SIZE = 77
|
|
175
|
-
const encoded = getDataBytes(encodedMessage)
|
|
176
|
-
|
|
177
|
-
if (encoded.length < MESSAGE_V1_BASE_SIZE) throw new CCIPMessageDecodeError('MESSAGE_MIN_SIZE')
|
|
178
|
-
|
|
179
|
-
const version = encoded[0]!
|
|
180
|
-
if (version !== 1) throw new CCIPMessageDecodeError(`Invalid encoding version: ${version}`)
|
|
181
|
-
|
|
182
|
-
// sourceChainSelector (8 bytes, big endian)
|
|
183
|
-
const sourceChainSelector = toBigInt(dataSlice(encoded, 1, 9))
|
|
184
|
-
|
|
185
|
-
// destChainSelector (8 bytes, big endian)
|
|
186
|
-
const destChainSelector = toBigInt(dataSlice(encoded, 9, 17))
|
|
187
|
-
|
|
188
|
-
// Get chain families for address decoding
|
|
189
|
-
const sourceNetworkInfo = networkInfo(sourceChainSelector)
|
|
190
|
-
const destNetworkInfo = networkInfo(destChainSelector)
|
|
191
|
-
const sourceFamily = sourceNetworkInfo.family
|
|
192
|
-
const destFamily = destNetworkInfo.family
|
|
193
|
-
|
|
194
|
-
// messageNumber (8 bytes, big endian)
|
|
195
|
-
const messageNumber = toBigInt(dataSlice(encoded, 17, 25))
|
|
196
|
-
|
|
197
|
-
// executionGasLimit (4 bytes, big endian)
|
|
198
|
-
const executionGasLimit = toNumber(dataSlice(encoded, 25, 29))
|
|
199
|
-
|
|
200
|
-
// ccipReceiveGasLimit (4 bytes, big endian)
|
|
201
|
-
const ccipReceiveGasLimit = toNumber(dataSlice(encoded, 29, 33))
|
|
202
|
-
|
|
203
|
-
// finality (2 bytes, big endian)
|
|
204
|
-
const finality = toNumber(dataSlice(encoded, 33, 35))
|
|
205
|
-
|
|
206
|
-
// ccvAndExecutorHash (32 bytes)
|
|
207
|
-
const ccvAndExecutorHash = hexlify(dataSlice(encoded, 35, 67))
|
|
208
|
-
|
|
209
|
-
// onRampAddressLength and onRampAddress
|
|
210
|
-
let offset = 67
|
|
211
|
-
if (offset >= encoded.length) throw new CCIPMessageDecodeError('MESSAGE_ONRAMP_ADDRESS_LENGTH')
|
|
212
|
-
const onRampAddressLength = encoded[offset++]!
|
|
213
|
-
if (offset + onRampAddressLength > encoded.length) {
|
|
214
|
-
throw new CCIPMessageDecodeError('MESSAGE_ONRAMP_ADDRESS_CONTENT')
|
|
215
|
-
}
|
|
216
|
-
const onRampAddress = decodeAddress(
|
|
217
|
-
dataSlice(encoded, offset, offset + onRampAddressLength),
|
|
218
|
-
sourceFamily,
|
|
219
|
-
)
|
|
220
|
-
offset += onRampAddressLength
|
|
221
|
-
|
|
222
|
-
// offRampAddressLength and offRampAddress
|
|
223
|
-
if (offset >= encoded.length) throw new CCIPMessageDecodeError('MESSAGE_OFFRAMP_ADDRESS_LENGTH')
|
|
224
|
-
const offRampAddressLength = encoded[offset++]!
|
|
225
|
-
if (offset + offRampAddressLength > encoded.length) {
|
|
226
|
-
throw new CCIPMessageDecodeError('MESSAGE_OFFRAMP_ADDRESS_CONTENT')
|
|
227
|
-
}
|
|
228
|
-
const offRampAddress = decodeAddress(
|
|
229
|
-
dataSlice(encoded, offset, offset + offRampAddressLength),
|
|
230
|
-
destFamily,
|
|
231
|
-
)
|
|
232
|
-
offset += offRampAddressLength
|
|
233
|
-
|
|
234
|
-
// senderLength and sender
|
|
235
|
-
if (offset >= encoded.length) throw new CCIPMessageDecodeError('MESSAGE_SENDER_LENGTH')
|
|
236
|
-
const senderLength = encoded[offset++]!
|
|
237
|
-
if (offset + senderLength > encoded.length) {
|
|
238
|
-
throw new CCIPMessageDecodeError('MESSAGE_SENDER_CONTENT')
|
|
239
|
-
}
|
|
240
|
-
const sender = decodeAddress(dataSlice(encoded, offset, offset + senderLength), sourceFamily)
|
|
241
|
-
offset += senderLength
|
|
242
|
-
|
|
243
|
-
// receiverLength and receiver
|
|
244
|
-
if (offset >= encoded.length) throw new CCIPMessageDecodeError('MESSAGE_RECEIVER_LENGTH')
|
|
245
|
-
const receiverLength = encoded[offset++]!
|
|
246
|
-
if (offset + receiverLength > encoded.length) {
|
|
247
|
-
throw new CCIPMessageDecodeError('MESSAGE_RECEIVER_CONTENT')
|
|
248
|
-
}
|
|
249
|
-
const receiver = decodeAddress(dataSlice(encoded, offset, offset + receiverLength), destFamily)
|
|
250
|
-
offset += receiverLength
|
|
251
|
-
|
|
252
|
-
// destBlobLength and destBlob
|
|
253
|
-
if (offset + 2 > encoded.length) throw new CCIPMessageDecodeError('MESSAGE_DEST_BLOB_LENGTH')
|
|
254
|
-
const destBlobLength = toNumber(dataSlice(encoded, offset, offset + 2))
|
|
255
|
-
offset += 2
|
|
256
|
-
if (offset + destBlobLength > encoded.length) {
|
|
257
|
-
throw new CCIPMessageDecodeError('MESSAGE_DEST_BLOB_CONTENT')
|
|
258
|
-
}
|
|
259
|
-
const destBlob = hexlify(dataSlice(encoded, offset, offset + destBlobLength))
|
|
260
|
-
offset += destBlobLength
|
|
261
|
-
|
|
262
|
-
// tokenTransferLength and tokenTransfer
|
|
263
|
-
if (offset + 2 > encoded.length) throw new CCIPMessageDecodeError('MESSAGE_TOKEN_TRANSFER_LENGTH')
|
|
264
|
-
const tokenTransferLength = toNumber(dataSlice(encoded, offset, offset + 2))
|
|
265
|
-
offset += 2
|
|
266
|
-
|
|
267
|
-
// Decode token transfer, which is either 0 or 1
|
|
268
|
-
const tokenTransfer: TokenTransferV1[] = []
|
|
269
|
-
if (tokenTransferLength > 0) {
|
|
270
|
-
const expectedEnd = offset + tokenTransferLength
|
|
271
|
-
const result = decodeTokenTransferV1(encoded, offset, sourceFamily, destFamily)
|
|
272
|
-
tokenTransfer.push(result.tokenTransfer)
|
|
273
|
-
offset = result.newOffset
|
|
274
|
-
if (offset !== expectedEnd) throw new CCIPMessageDecodeError('MESSAGE_TOKEN_TRANSFER_CONTENT')
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// dataLength and data
|
|
278
|
-
if (offset + 2 > encoded.length) throw new CCIPMessageDecodeError('MESSAGE_DATA_LENGTH')
|
|
279
|
-
const dataLength = toNumber(dataSlice(encoded, offset, offset + 2))
|
|
280
|
-
offset += 2
|
|
281
|
-
if (offset + dataLength > encoded.length) {
|
|
282
|
-
throw new CCIPMessageDecodeError('MESSAGE_DATA_CONTENT')
|
|
283
|
-
}
|
|
284
|
-
const data = hexlify(dataSlice(encoded, offset, offset + dataLength))
|
|
285
|
-
offset += dataLength
|
|
286
|
-
|
|
287
|
-
// Ensure we've consumed all bytes
|
|
288
|
-
if (offset !== encoded.length) throw new CCIPMessageDecodeError('MESSAGE_FINAL_OFFSET')
|
|
289
|
-
|
|
290
|
-
return {
|
|
291
|
-
sourceChainSelector,
|
|
292
|
-
destChainSelector,
|
|
293
|
-
messageNumber,
|
|
294
|
-
executionGasLimit,
|
|
295
|
-
ccipReceiveGasLimit,
|
|
296
|
-
finality,
|
|
297
|
-
ccvAndExecutorHash,
|
|
298
|
-
onRampAddress,
|
|
299
|
-
offRampAddress,
|
|
300
|
-
sender,
|
|
301
|
-
receiver,
|
|
302
|
-
destBlob,
|
|
303
|
-
tokenTransfer,
|
|
304
|
-
data,
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
26
|
// v1.2-v1.5 Message ()
|
|
309
27
|
type EVM2AnyMessageRequested = CleanAddressable<
|
|
310
28
|
AbiParametersToPrimitiveTypes<
|
package/src/gas.ts
CHANGED
|
@@ -20,6 +20,10 @@ export type EstimateMessageInput = {
|
|
|
20
20
|
messageId?: string
|
|
21
21
|
/** optional sender: zero address will be used if omitted */
|
|
22
22
|
sender?: string
|
|
23
|
+
/** optional onRampAddress */
|
|
24
|
+
onRampAddress?: string
|
|
25
|
+
/** optional offRampAddress */
|
|
26
|
+
offRampAddress?: string
|
|
23
27
|
/** optional data: zero bytes will be used if omitted */
|
|
24
28
|
data?: BytesLike
|
|
25
29
|
/**
|
|
@@ -89,26 +93,29 @@ export async function estimateReceiveExecution({
|
|
|
89
93
|
if (!dest.estimateReceiveExecution)
|
|
90
94
|
throw new CCIPMethodUnsupportedError(dest.constructor.name, 'estimateReceiveExecution')
|
|
91
95
|
|
|
92
|
-
let onRamp, offRamp: string
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
onRamp = await source.getOnRampForRouter(routerOrRamp, dest.network.chainSelector)
|
|
97
|
-
else onRamp = routerOrRamp
|
|
98
|
-
offRamp = await discoverOffRamp(source, dest, onRamp, source)
|
|
99
|
-
} catch (sourceErr) {
|
|
96
|
+
let onRamp: string, offRamp: string
|
|
97
|
+
if (message.onRampAddress) onRamp = message.onRampAddress
|
|
98
|
+
if (message.offRampAddress) offRamp = message.offRampAddress
|
|
99
|
+
if (!onRamp! || !offRamp!)
|
|
100
100
|
try {
|
|
101
|
-
const tnv = await
|
|
102
|
-
if (!tnv[0].includes('
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
const tnv = await source.typeAndVersion(routerOrRamp)
|
|
102
|
+
if (!tnv[0].includes('OnRamp'))
|
|
103
|
+
onRamp = await source.getOnRampForRouter(routerOrRamp, dest.network.chainSelector)
|
|
104
|
+
else onRamp = routerOrRamp
|
|
105
|
+
offRamp = await discoverOffRamp(source, dest, onRamp, source)
|
|
106
|
+
} catch (sourceErr) {
|
|
107
|
+
try {
|
|
108
|
+
const tnv = await dest.typeAndVersion(routerOrRamp)
|
|
109
|
+
if (!tnv[0].includes('OffRamp'))
|
|
110
|
+
throw new CCIPContractTypeInvalidError(routerOrRamp, tnv[2], ['OffRamp'])
|
|
111
|
+
offRamp = routerOrRamp
|
|
112
|
+
const onRamps = await dest.getOnRampsForOffRamp(offRamp, source.network.chainSelector)
|
|
113
|
+
if (!onRamps.length) throw new CCIPOnRampRequiredError()
|
|
114
|
+
onRamp = onRamps[onRamps.length - 1]!
|
|
115
|
+
} catch {
|
|
116
|
+
throw sourceErr // re-throw original error
|
|
117
|
+
}
|
|
110
118
|
}
|
|
111
|
-
}
|
|
112
119
|
|
|
113
120
|
const destTokenAmounts = await Promise.all(
|
|
114
121
|
(message.tokenAmounts ?? []).map(async (ta) => {
|
|
@@ -127,7 +134,8 @@ export async function estimateReceiveExecution({
|
|
|
127
134
|
dest.getTokenInfo(tokenAmount.destTokenAddress),
|
|
128
135
|
])
|
|
129
136
|
const destAmount =
|
|
130
|
-
(tokenAmount.amount *
|
|
137
|
+
(tokenAmount.amount * BigInt(10) ** BigInt(destDecimals)) /
|
|
138
|
+
BigInt(10) ** BigInt(sourceDecimals)
|
|
131
139
|
if (destAmount === 0n)
|
|
132
140
|
throw new CCIPTokenDecimalsInsufficientError(
|
|
133
141
|
tokenAmount.destTokenAddress,
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export type {
|
|
|
27
27
|
ChainGetter,
|
|
28
28
|
ChainStatic,
|
|
29
29
|
GetBalanceOpts,
|
|
30
|
+
LaneFeatures,
|
|
30
31
|
LogFilter,
|
|
31
32
|
RateLimiterState,
|
|
32
33
|
RegistryTokenConfig,
|
|
@@ -34,7 +35,7 @@ export type {
|
|
|
34
35
|
TokenPoolConfig,
|
|
35
36
|
TokenPoolRemote,
|
|
36
37
|
} from './chain.ts'
|
|
37
|
-
export { DEFAULT_API_RETRY_CONFIG } from './chain.ts'
|
|
38
|
+
export { DEFAULT_API_RETRY_CONFIG, LaneFeature } from './chain.ts'
|
|
38
39
|
export { calculateManualExecProof, discoverOffRamp } from './execution.ts'
|
|
39
40
|
export {
|
|
40
41
|
type EVMExtraArgsV1,
|
package/src/messages.ts
ADDED
|
@@ -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/solana/cleanup.ts
CHANGED
|
@@ -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 <
|
|
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 <
|
|
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 (
|