@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.
- package/README.md +12 -9
- package/dist/api/index.d.ts +7 -3
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +124 -13
- 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/aptos/index.d.ts +4 -6
- package/dist/aptos/index.d.ts.map +1 -1
- package/dist/aptos/index.js +0 -5
- package/dist/aptos/index.js.map +1 -1
- package/dist/aptos/logs.d.ts +2 -2
- package/dist/aptos/logs.d.ts.map +1 -1
- package/dist/chain.d.ts +104 -16
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +97 -9
- 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 +5 -5
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +5 -5
- 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/OnRamp_2_0.d.ts +1 -1
- package/dist/evm/abi/OnRamp_2_0.js +1 -1
- package/dist/evm/abi/OnRamp_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 +11 -7
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +139 -46
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/logs.d.ts +1 -1
- package/dist/evm/logs.js +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/evm/offchain.d.ts +1 -14
- package/dist/evm/offchain.d.ts.map +1 -1
- package/dist/evm/offchain.js +1 -133
- package/dist/evm/offchain.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 +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- 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/offchain.d.ts +23 -6
- package/dist/offchain.d.ts.map +1 -1
- package/dist/offchain.js +92 -17
- package/dist/offchain.js.map +1 -1
- package/dist/requests.d.ts.map +1 -1
- package/dist/requests.js +0 -1
- package/dist/requests.js.map +1 -1
- 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/idl/1.6.0/BASE_TOKEN_POOL.d.ts +1 -1
- package/dist/solana/idl/1.6.0/BASE_TOKEN_POOL.js +1 -1
- package/dist/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.d.ts +1 -1
- package/dist/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.js +1 -1
- package/dist/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.d.ts +1 -1
- package/dist/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.js +1 -1
- package/dist/solana/idl/1.6.0/CCIP_COMMON.d.ts +16 -1
- package/dist/solana/idl/1.6.0/CCIP_COMMON.d.ts.map +1 -1
- package/dist/solana/idl/1.6.0/CCIP_COMMON.js +16 -1
- package/dist/solana/idl/1.6.0/CCIP_COMMON.js.map +1 -1
- package/dist/solana/idl/1.6.0/CCIP_OFFRAMP.d.ts +1 -1
- package/dist/solana/idl/1.6.0/CCIP_OFFRAMP.js +1 -1
- package/dist/solana/idl/1.6.0/CCIP_ROUTER.d.ts +1 -1
- package/dist/solana/idl/1.6.0/CCIP_ROUTER.js +1 -1
- package/dist/solana/index.d.ts +25 -27
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +16 -7
- package/dist/solana/index.js.map +1 -1
- package/dist/solana/offchain.d.ts +1 -13
- package/dist/solana/offchain.d.ts.map +1 -1
- package/dist/solana/offchain.js +1 -66
- package/dist/solana/offchain.js.map +1 -1
- package/dist/solana/utils.d.ts +4 -4
- package/dist/solana/utils.d.ts.map +1 -1
- package/dist/solana/utils.js +1 -1
- package/dist/solana/utils.js.map +1 -1
- package/dist/sui/index.d.ts +4 -6
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +14 -6
- package/dist/sui/index.js.map +1 -1
- package/dist/ton/exec.d.ts +2 -2
- package/dist/ton/exec.d.ts.map +1 -1
- package/dist/ton/exec.js +1 -1
- package/dist/ton/exec.js.map +1 -1
- package/dist/ton/index.d.ts +5 -6
- package/dist/ton/index.d.ts.map +1 -1
- package/dist/ton/index.js +3 -5
- package/dist/ton/index.js.map +1 -1
- package/dist/ton/types.d.ts +3 -5
- package/dist/ton/types.d.ts.map +1 -1
- package/dist/ton/types.js.map +1 -1
- package/dist/types.d.ts +10 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +11 -7
- package/src/api/index.ts +145 -17
- package/src/api/types.ts +43 -0
- package/src/aptos/index.ts +4 -11
- package/src/aptos/logs.ts +2 -2
- package/src/chain.ts +148 -23
- package/src/errors/codes.ts +2 -1
- package/src/errors/index.ts +4 -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/OnRamp_2_0.ts +1 -1
- package/src/evm/abi/TokenPool_2_0.ts +1636 -0
- package/src/evm/const.ts +2 -0
- package/src/evm/index.ts +234 -85
- package/src/evm/logs.ts +1 -1
- package/src/evm/messages.ts +3 -285
- package/src/evm/offchain.ts +2 -191
- package/src/gas.ts +27 -19
- package/src/index.ts +10 -2
- package/src/messages.ts +278 -0
- package/src/offchain.ts +125 -28
- package/src/requests.ts +2 -3
- package/src/solana/cleanup.ts +2 -2
- package/src/solana/exec.ts +1 -1
- package/src/solana/idl/1.6.0/BASE_TOKEN_POOL.ts +2 -2
- package/src/solana/idl/1.6.0/BURN_MINT_TOKEN_POOL.ts +2 -2
- package/src/solana/idl/1.6.0/CCIP_CCTP_TOKEN_POOL.ts +2 -2
- package/src/solana/idl/1.6.0/CCIP_COMMON.ts +32 -2
- package/src/solana/idl/1.6.0/CCIP_OFFRAMP.ts +2 -2
- package/src/solana/idl/1.6.0/CCIP_ROUTER.ts +2 -2
- package/src/solana/index.ts +27 -17
- package/src/solana/offchain.ts +3 -100
- package/src/solana/utils.ts +8 -5
- package/src/sui/index.ts +22 -12
- package/src/ton/exec.ts +3 -6
- package/src/ton/index.ts +15 -16
- package/src/ton/types.ts +3 -6
- package/src/types.ts +13 -10
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/offchain.ts
CHANGED
|
@@ -1,56 +1,79 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
|
12
|
-
testnet: 'https://iris-api-sandbox.circle.com
|
|
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
|
-
| {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
32
|
-
* https://developers.circle.com/
|
|
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
|
|
33
|
+
* @param opts - CCTPv2 options
|
|
35
34
|
* @param networkType - network type (mainnet or testnet)
|
|
36
|
-
* @returns USDC/CCTP attestation
|
|
35
|
+
* @returns USDC/CCTP attestation and message
|
|
37
36
|
*/
|
|
38
37
|
export async function getUsdcAttestation(
|
|
39
|
-
|
|
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
|
|
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(
|
|
51
|
+
const res = await fetch(
|
|
52
|
+
`${circleApiBaseUrl}/v2/messages/${sourceDomain}?transactionHash=${txHash}`,
|
|
53
|
+
)
|
|
47
54
|
const json = (await res.json()) as CctpAttestationResponse
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
let att
|
|
56
|
+
if ('messages' in json) {
|
|
57
|
+
att = json.messages.find((m) => m.status === 'complete' && m.eventNonce === nonce.toString())
|
|
50
58
|
}
|
|
51
|
-
|
|
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
|
|
183
|
+
const decoded = chain.decodeMessage({ data } as unknown as ChainLog)
|
|
185
184
|
if (decoded) return decoded
|
|
186
185
|
} catch (_) {
|
|
187
186
|
// continue
|
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 (
|
package/src/solana/exec.ts
CHANGED
|
@@ -198,7 +198,7 @@ async function buildLookupTableIxs(
|
|
|
198
198
|
lookupTable: new AddressLookupTableAccount({
|
|
199
199
|
key: altAddr,
|
|
200
200
|
state: {
|
|
201
|
-
deactivationSlot:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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.',
|