@chainlink/ccip-sdk 0.94.0 → 0.96.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 (170) hide show
  1. package/README.md +2 -2
  2. package/dist/all-chains.d.ts +23 -0
  3. package/dist/all-chains.d.ts.map +1 -0
  4. package/dist/all-chains.js +24 -0
  5. package/dist/all-chains.js.map +1 -0
  6. package/dist/api/index.d.ts +86 -7
  7. package/dist/api/index.d.ts.map +1 -1
  8. package/dist/api/index.js +270 -10
  9. package/dist/api/index.js.map +1 -1
  10. package/dist/api/types.d.ts +134 -13
  11. package/dist/api/types.d.ts.map +1 -1
  12. package/dist/aptos/index.d.ts +38 -17
  13. package/dist/aptos/index.d.ts.map +1 -1
  14. package/dist/aptos/index.js +91 -61
  15. package/dist/aptos/index.js.map +1 -1
  16. package/dist/aptos/logs.js +3 -3
  17. package/dist/aptos/logs.js.map +1 -1
  18. package/dist/chain.d.ts +300 -42
  19. package/dist/chain.d.ts.map +1 -1
  20. package/dist/chain.js +160 -9
  21. package/dist/chain.js.map +1 -1
  22. package/dist/errors/codes.d.ts +9 -3
  23. package/dist/errors/codes.d.ts.map +1 -1
  24. package/dist/errors/codes.js +10 -3
  25. package/dist/errors/codes.js.map +1 -1
  26. package/dist/errors/index.d.ts +8 -8
  27. package/dist/errors/index.d.ts.map +1 -1
  28. package/dist/errors/index.js +8 -8
  29. package/dist/errors/index.js.map +1 -1
  30. package/dist/errors/recovery.d.ts.map +1 -1
  31. package/dist/errors/recovery.js +10 -4
  32. package/dist/errors/recovery.js.map +1 -1
  33. package/dist/errors/specialized.d.ts +62 -21
  34. package/dist/errors/specialized.d.ts.map +1 -1
  35. package/dist/errors/specialized.js +128 -41
  36. package/dist/errors/specialized.js.map +1 -1
  37. package/dist/evm/extra-args.d.ts +25 -0
  38. package/dist/evm/extra-args.d.ts.map +1 -0
  39. package/dist/evm/extra-args.js +328 -0
  40. package/dist/evm/extra-args.js.map +1 -0
  41. package/dist/evm/gas.d.ts +14 -0
  42. package/dist/evm/gas.d.ts.map +1 -0
  43. package/dist/evm/gas.js +92 -0
  44. package/dist/evm/gas.js.map +1 -0
  45. package/dist/evm/index.d.ts +76 -32
  46. package/dist/evm/index.d.ts.map +1 -1
  47. package/dist/evm/index.js +94 -104
  48. package/dist/evm/index.js.map +1 -1
  49. package/dist/evm/offchain.d.ts.map +1 -1
  50. package/dist/evm/offchain.js +8 -8
  51. package/dist/evm/offchain.js.map +1 -1
  52. package/dist/execution.d.ts.map +1 -1
  53. package/dist/execution.js +24 -3
  54. package/dist/execution.js.map +1 -1
  55. package/dist/extra-args.d.ts +103 -4
  56. package/dist/extra-args.d.ts.map +1 -1
  57. package/dist/extra-args.js +28 -3
  58. package/dist/extra-args.js.map +1 -1
  59. package/dist/gas.d.ts +46 -19
  60. package/dist/gas.d.ts.map +1 -1
  61. package/dist/gas.js +56 -68
  62. package/dist/gas.js.map +1 -1
  63. package/dist/index.d.ts +18 -15
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +14 -13
  66. package/dist/index.js.map +1 -1
  67. package/dist/offchain.d.ts +5 -4
  68. package/dist/offchain.d.ts.map +1 -1
  69. package/dist/offchain.js +7 -6
  70. package/dist/offchain.js.map +1 -1
  71. package/dist/requests.d.ts +30 -20
  72. package/dist/requests.d.ts.map +1 -1
  73. package/dist/requests.js +86 -56
  74. package/dist/requests.js.map +1 -1
  75. package/dist/selectors.d.ts +2 -1
  76. package/dist/selectors.d.ts.map +1 -1
  77. package/dist/selectors.js +625 -278
  78. package/dist/selectors.js.map +1 -1
  79. package/dist/solana/exec.d.ts.map +1 -1
  80. package/dist/solana/exec.js +2 -1
  81. package/dist/solana/exec.js.map +1 -1
  82. package/dist/solana/index.d.ts +73 -22
  83. package/dist/solana/index.d.ts.map +1 -1
  84. package/dist/solana/index.js +91 -28
  85. package/dist/solana/index.js.map +1 -1
  86. package/dist/solana/offchain.js +2 -2
  87. package/dist/solana/offchain.js.map +1 -1
  88. package/dist/solana/send.d.ts.map +1 -1
  89. package/dist/solana/send.js +6 -9
  90. package/dist/solana/send.js.map +1 -1
  91. package/dist/solana/utils.d.ts +29 -1
  92. package/dist/solana/utils.d.ts.map +1 -1
  93. package/dist/solana/utils.js +39 -1
  94. package/dist/solana/utils.js.map +1 -1
  95. package/dist/sui/discovery.d.ts +7 -4
  96. package/dist/sui/discovery.d.ts.map +1 -1
  97. package/dist/sui/discovery.js +66 -19
  98. package/dist/sui/discovery.js.map +1 -1
  99. package/dist/sui/events.d.ts +23 -12
  100. package/dist/sui/events.d.ts.map +1 -1
  101. package/dist/sui/events.js +267 -128
  102. package/dist/sui/events.js.map +1 -1
  103. package/dist/sui/index.d.ts +57 -41
  104. package/dist/sui/index.d.ts.map +1 -1
  105. package/dist/sui/index.js +286 -159
  106. package/dist/sui/index.js.map +1 -1
  107. package/dist/sui/objects.d.ts +14 -4
  108. package/dist/sui/objects.d.ts.map +1 -1
  109. package/dist/sui/objects.js +61 -68
  110. package/dist/sui/objects.js.map +1 -1
  111. package/dist/sui/types.d.ts +33 -0
  112. package/dist/sui/types.d.ts.map +1 -1
  113. package/dist/sui/types.js.map +1 -1
  114. package/dist/ton/index.d.ts +67 -21
  115. package/dist/ton/index.d.ts.map +1 -1
  116. package/dist/ton/index.js +159 -30
  117. package/dist/ton/index.js.map +1 -1
  118. package/dist/ton/send.d.ts +52 -0
  119. package/dist/ton/send.d.ts.map +1 -0
  120. package/dist/ton/send.js +166 -0
  121. package/dist/ton/send.js.map +1 -0
  122. package/dist/ton/utils.d.ts +3 -3
  123. package/dist/ton/utils.d.ts.map +1 -1
  124. package/dist/ton/utils.js +6 -5
  125. package/dist/ton/utils.js.map +1 -1
  126. package/dist/types.d.ts +126 -9
  127. package/dist/types.d.ts.map +1 -1
  128. package/dist/types.js +19 -5
  129. package/dist/types.js.map +1 -1
  130. package/dist/utils.d.ts +67 -4
  131. package/dist/utils.d.ts.map +1 -1
  132. package/dist/utils.js +126 -17
  133. package/dist/utils.js.map +1 -1
  134. package/package.json +14 -9
  135. package/src/all-chains.ts +26 -0
  136. package/src/api/index.ts +348 -13
  137. package/src/api/types.ts +160 -13
  138. package/src/aptos/index.ts +98 -76
  139. package/src/aptos/logs.ts +3 -3
  140. package/src/chain.ts +408 -51
  141. package/src/errors/codes.ts +10 -3
  142. package/src/errors/index.ts +8 -5
  143. package/src/errors/recovery.ts +18 -5
  144. package/src/errors/specialized.ts +168 -49
  145. package/src/evm/extra-args.ts +377 -0
  146. package/src/evm/gas.ts +150 -0
  147. package/src/evm/index.ts +123 -155
  148. package/src/evm/offchain.ts +15 -9
  149. package/src/execution.ts +26 -3
  150. package/src/extra-args.ts +108 -4
  151. package/src/gas.ts +101 -115
  152. package/src/index.ts +27 -14
  153. package/src/offchain.ts +12 -6
  154. package/src/requests.ts +117 -67
  155. package/src/selectors.ts +632 -280
  156. package/src/solana/exec.ts +3 -1
  157. package/src/solana/index.ts +97 -37
  158. package/src/solana/offchain.ts +2 -2
  159. package/src/solana/send.ts +5 -23
  160. package/src/solana/utils.ts +66 -0
  161. package/src/sui/discovery.ts +92 -31
  162. package/src/sui/events.ts +346 -239
  163. package/src/sui/index.ts +365 -212
  164. package/src/sui/objects.ts +74 -98
  165. package/src/sui/types.ts +35 -0
  166. package/src/ton/index.ts +199 -35
  167. package/src/ton/send.ts +222 -0
  168. package/src/ton/utils.ts +7 -6
  169. package/src/types.ts +128 -9
  170. package/src/utils.ts +169 -21
@@ -0,0 +1,377 @@
1
+ import {
2
+ type BytesLike,
3
+ Result,
4
+ concat,
5
+ dataSlice,
6
+ encodeBase58,
7
+ getAddress,
8
+ hexlify,
9
+ toBeHex,
10
+ toBigInt,
11
+ toNumber,
12
+ zeroPadValue,
13
+ } from 'ethers'
14
+
15
+ import {
16
+ type EVMExtraArgsV1,
17
+ type EVMExtraArgsV2,
18
+ type ExtraArgs,
19
+ type GenericExtraArgsV3,
20
+ type SVMExtraArgsV1,
21
+ type SuiExtraArgsV1,
22
+ EVMExtraArgsV1Tag,
23
+ EVMExtraArgsV2Tag,
24
+ GenericExtraArgsV3Tag,
25
+ SVMExtraArgsV1Tag,
26
+ SuiExtraArgsV1Tag,
27
+ } from '../extra-args.ts'
28
+ import { getAddressBytes, getDataBytes } from '../utils.ts'
29
+ import { DEFAULT_GAS_LIMIT, defaultAbiCoder } from './const.ts'
30
+
31
+ // ABI type strings for extra args encoding
32
+ const EVMExtraArgsV1ABI = 'tuple(uint256 gasLimit)'
33
+ const EVMExtraArgsV2ABI = 'tuple(uint256 gasLimit, bool allowOutOfOrderExecution)'
34
+ const SVMExtraArgsV1ABI =
35
+ 'tuple(uint32 computeUnits, uint64 accountIsWritableBitmap, bool allowOutOfOrderExecution, bytes32 tokenReceiver, bytes32[] accounts)'
36
+ const SuiExtraArgsV1ABI =
37
+ 'tuple(uint256 gasLimit, bool allowOutOfOrderExecution, bytes32 tokenReceiver, bytes32[] receiverObjectIds)'
38
+
39
+ /**
40
+ * Converts an ethers Result to a plain object.
41
+ * @internal
42
+ */
43
+ function resultToObject<T>(o: T): T {
44
+ if (o instanceof Promise) return o.then(resultToObject) as T
45
+ if (!(o instanceof Result)) return o
46
+ if (o.length === 0) return o.toArray() as T
47
+ try {
48
+ const obj = o.toObject()
49
+ if (!Object.keys(obj).every((k) => /^_+\d*$/.test(k)))
50
+ return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, resultToObject(v)])) as T
51
+ } catch (_) {
52
+ // fallthrough
53
+ }
54
+ return o.toArray().map(resultToObject) as T
55
+ }
56
+
57
+ /**
58
+ * Encodes GenericExtraArgsV3 using tightly packed binary format.
59
+ *
60
+ * Binary format:
61
+ * - tag (4 bytes): 0x302326cb
62
+ * - gasLimit (4 bytes): uint32 big-endian
63
+ * - blockConfirmations (2 bytes): uint16 big-endian
64
+ * - ccvsLength (1 byte): uint8
65
+ * - For each CCV:
66
+ * - ccvAddressLength (1 byte): 0 or 20
67
+ * - ccvAddress (0 or 20 bytes)
68
+ * - ccvArgsLength (2 bytes): uint16 big-endian
69
+ * - ccvArgs (variable)
70
+ * - executorLength (1 byte): 0 or 20
71
+ * - executor (0 or 20 bytes)
72
+ * - executorArgsLength (2 bytes): uint16 big-endian
73
+ * - executorArgs (variable)
74
+ * - tokenReceiverLength (1 byte): uint8
75
+ * - tokenReceiver (variable)
76
+ * - tokenArgsLength (2 bytes): uint16 big-endian
77
+ * - tokenArgs (variable)
78
+ */
79
+ function encodeExtraArgsV3(args: GenericExtraArgsV3): string {
80
+ const parts: Uint8Array[] = []
81
+
82
+ // Tag (4 bytes)
83
+ parts.push(getDataBytes(GenericExtraArgsV3Tag))
84
+
85
+ // gasLimit (4 bytes, uint32 big-endian)
86
+ parts.push(getDataBytes(toBeHex(args.gasLimit, 4)))
87
+
88
+ // blockConfirmations (2 bytes, uint16 big-endian)
89
+ parts.push(getDataBytes(toBeHex(args.blockConfirmations, 2)))
90
+
91
+ // ccvsLength (1 byte)
92
+ parts.push(new Uint8Array([args.ccvs.length]))
93
+
94
+ // For each CCV
95
+ for (let i = 0; i < args.ccvs.length; i++) {
96
+ const ccvAddress = args.ccvs[i]!
97
+ const ccvArgsBytes = getDataBytes(args.ccvArgs[i] ?? '0x')
98
+
99
+ if (ccvAddress && ccvAddress !== '' && ccvAddress !== '0x') {
100
+ // ccvAddressLength = 20
101
+ parts.push(new Uint8Array([20]))
102
+ // ccvAddress (20 bytes)
103
+ parts.push(getDataBytes(ccvAddress))
104
+ } else {
105
+ // ccvAddressLength = 0
106
+ parts.push(new Uint8Array([0]))
107
+ }
108
+
109
+ // ccvArgsLength (2 bytes, uint16 big-endian)
110
+ parts.push(getDataBytes(toBeHex(ccvArgsBytes.length, 2)))
111
+
112
+ // ccvArgs (variable)
113
+ if (ccvArgsBytes.length > 0) {
114
+ parts.push(ccvArgsBytes)
115
+ }
116
+ }
117
+
118
+ // executorLength (1 byte)
119
+ if (args.executor && args.executor !== '' && args.executor !== '0x') {
120
+ parts.push(new Uint8Array([20]))
121
+ parts.push(getDataBytes(args.executor))
122
+ } else {
123
+ parts.push(new Uint8Array([0]))
124
+ }
125
+
126
+ // Convert BytesLike fields to Uint8Array
127
+ const executorArgsBytes = getDataBytes(args.executorArgs)
128
+ const tokenReceiverBytes = getDataBytes(args.tokenReceiver)
129
+ const tokenArgsBytes = getDataBytes(args.tokenArgs)
130
+
131
+ // executorArgsLength (2 bytes, uint16 big-endian)
132
+ parts.push(getDataBytes(toBeHex(executorArgsBytes.length, 2)))
133
+
134
+ // executorArgs (variable)
135
+ if (executorArgsBytes.length > 0) {
136
+ parts.push(executorArgsBytes)
137
+ }
138
+
139
+ // tokenReceiverLength (1 byte)
140
+ parts.push(new Uint8Array([tokenReceiverBytes.length]))
141
+
142
+ // tokenReceiver (variable)
143
+ if (tokenReceiverBytes.length > 0) {
144
+ parts.push(tokenReceiverBytes)
145
+ }
146
+
147
+ // tokenArgsLength (2 bytes, uint16 big-endian)
148
+ parts.push(getDataBytes(toBeHex(tokenArgsBytes.length, 2)))
149
+
150
+ // tokenArgs (variable)
151
+ if (tokenArgsBytes.length > 0) {
152
+ parts.push(tokenArgsBytes)
153
+ }
154
+
155
+ return hexlify(concat(parts))
156
+ }
157
+
158
+ /**
159
+ * Decodes GenericExtraArgsV3 from tightly packed binary format.
160
+ * @param data - Bytes to decode (without the tag prefix).
161
+ * @returns Decoded GenericExtraArgsV3 or undefined if parsing fails.
162
+ */
163
+ function decodeExtraArgsV3(data: Uint8Array): GenericExtraArgsV3 | undefined {
164
+ let offset = 0
165
+
166
+ // gasLimit (4 bytes, uint32 big-endian)
167
+ if (offset + 4 > data.length) return undefined
168
+ const gasLimit = toBigInt(data.subarray(offset, offset + 4))
169
+ offset += 4
170
+
171
+ // blockConfirmations (2 bytes, uint16 big-endian)
172
+ if (offset + 2 > data.length) return undefined
173
+ const blockConfirmations = toNumber(data.subarray(offset, offset + 2))
174
+ offset += 2
175
+
176
+ // ccvsLength (1 byte)
177
+ if (offset + 1 > data.length) return undefined
178
+ const ccvsLength = data[offset]!
179
+ offset += 1
180
+
181
+ const ccvs: string[] = []
182
+ const ccvArgs: string[] = []
183
+
184
+ // For each CCV
185
+ for (let i = 0; i < ccvsLength; i++) {
186
+ // ccvAddressLength (1 byte)
187
+ if (offset + 1 > data.length) return undefined
188
+ const ccvAddrLen = data[offset]!
189
+ offset += 1
190
+
191
+ // ccvAddress (0 or 20 bytes)
192
+ if (ccvAddrLen === 20) {
193
+ if (offset + 20 > data.length) return undefined
194
+ ccvs.push(getAddress(hexlify(data.slice(offset, offset + 20))))
195
+ offset += 20
196
+ } else if (ccvAddrLen === 0) {
197
+ ccvs.push('')
198
+ } else {
199
+ return undefined // Invalid address length
200
+ }
201
+
202
+ // ccvArgsLength (2 bytes, uint16 big-endian)
203
+ if (offset + 2 > data.length) return undefined
204
+ const ccvArgsLen = toNumber(data.subarray(offset, offset + 2))
205
+ offset += 2
206
+
207
+ // ccvArgs (variable)
208
+ if (offset + ccvArgsLen > data.length) return undefined
209
+ ccvArgs.push(hexlify(data.slice(offset, offset + ccvArgsLen)))
210
+ offset += ccvArgsLen
211
+ }
212
+
213
+ // executorLength (1 byte)
214
+ if (offset + 1 > data.length) return undefined
215
+ const executorLen = data[offset]!
216
+ offset += 1
217
+
218
+ // executor (0 or 20 bytes)
219
+ let executor = ''
220
+ if (executorLen === 20) {
221
+ if (offset + 20 > data.length) return undefined
222
+ executor = getAddress(hexlify(data.slice(offset, offset + 20)))
223
+ offset += 20
224
+ } else if (executorLen !== 0) {
225
+ return undefined // Invalid executor length
226
+ }
227
+
228
+ // executorArgsLength (2 bytes, uint16 big-endian)
229
+ if (offset + 2 > data.length) return undefined
230
+ const executorArgsLen = toNumber(data.subarray(offset, offset + 2))
231
+ offset += 2
232
+
233
+ // executorArgs (variable)
234
+ if (offset + executorArgsLen > data.length) return undefined
235
+ const executorArgs = hexlify(data.slice(offset, offset + executorArgsLen))
236
+ offset += executorArgsLen
237
+
238
+ // tokenReceiverLength (1 byte)
239
+ if (offset + 1 > data.length) return undefined
240
+ const tokenReceiverLen = data[offset]!
241
+ offset += 1
242
+
243
+ // tokenReceiver (variable)
244
+ if (offset + tokenReceiverLen > data.length) return undefined
245
+ const tokenReceiverBytes = data.slice(offset, offset + tokenReceiverLen)
246
+ offset += tokenReceiverLen
247
+
248
+ // Convert tokenReceiver bytes to string
249
+ let tokenReceiver: string
250
+ if (tokenReceiverLen === 0) {
251
+ tokenReceiver = ''
252
+ } else if (tokenReceiverLen === 20) {
253
+ // 20 bytes = EVM address, return checksummed
254
+ tokenReceiver = getAddress(hexlify(tokenReceiverBytes))
255
+ } else {
256
+ // Other lengths: return as hex string
257
+ tokenReceiver = hexlify(tokenReceiverBytes)
258
+ }
259
+
260
+ // tokenArgsLength (2 bytes, uint16 big-endian)
261
+ if (offset + 2 > data.length) return undefined
262
+ const tokenArgsLen = toNumber(data.subarray(offset, offset + 2))
263
+ offset += 2
264
+
265
+ // tokenArgs (variable)
266
+ if (offset + tokenArgsLen > data.length) return undefined
267
+ const tokenArgs = hexlify(data.slice(offset, offset + tokenArgsLen))
268
+ offset += tokenArgsLen
269
+
270
+ return {
271
+ gasLimit,
272
+ blockConfirmations,
273
+ ccvs,
274
+ ccvArgs,
275
+ executor,
276
+ executorArgs,
277
+ tokenReceiver,
278
+ tokenArgs,
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Decodes extra arguments from a CCIP message.
284
+ * @param extraArgs - Encoded extra arguments bytes.
285
+ * @returns Decoded extra arguments with tag, or undefined if unknown format.
286
+ */
287
+ export function decodeExtraArgs(
288
+ extraArgs: BytesLike,
289
+ ):
290
+ | (EVMExtraArgsV1 & { _tag: 'EVMExtraArgsV1' })
291
+ | (EVMExtraArgsV2 & { _tag: 'EVMExtraArgsV2' })
292
+ | (GenericExtraArgsV3 & { _tag: 'GenericExtraArgsV3' })
293
+ | (SVMExtraArgsV1 & { _tag: 'SVMExtraArgsV1' })
294
+ | (SuiExtraArgsV1 & { _tag: 'SuiExtraArgsV1' })
295
+ | undefined {
296
+ const data = getDataBytes(extraArgs),
297
+ tag = dataSlice(data, 0, 4)
298
+ switch (tag) {
299
+ case EVMExtraArgsV1Tag: {
300
+ const args = defaultAbiCoder.decode([EVMExtraArgsV1ABI], dataSlice(data, 4))
301
+ return { ...(resultToObject(args[0]) as EVMExtraArgsV1), _tag: 'EVMExtraArgsV1' }
302
+ }
303
+ case EVMExtraArgsV2Tag: {
304
+ const args = defaultAbiCoder.decode([EVMExtraArgsV2ABI], dataSlice(data, 4))
305
+ return { ...(resultToObject(args[0]) as EVMExtraArgsV2), _tag: 'EVMExtraArgsV2' }
306
+ }
307
+ case GenericExtraArgsV3Tag: {
308
+ const parsed = decodeExtraArgsV3(data.slice(4))
309
+ if (!parsed) return undefined
310
+ return { ...parsed, _tag: 'GenericExtraArgsV3' }
311
+ }
312
+ case SVMExtraArgsV1Tag: {
313
+ const args = defaultAbiCoder.decode([SVMExtraArgsV1ABI], dataSlice(data, 4))
314
+ const parsed = resultToObject(args[0]) as SVMExtraArgsV1
315
+ parsed.tokenReceiver = encodeBase58(parsed.tokenReceiver)
316
+ parsed.accounts = parsed.accounts.map((a: string) => encodeBase58(a))
317
+ return { ...parsed, _tag: 'SVMExtraArgsV1' }
318
+ }
319
+ case SuiExtraArgsV1Tag: {
320
+ const args = defaultAbiCoder.decode([SuiExtraArgsV1ABI], dataSlice(data, 4))
321
+ const parsed = resultToObject(args[0]) as SuiExtraArgsV1
322
+ return {
323
+ ...parsed,
324
+ _tag: 'SuiExtraArgsV1',
325
+ }
326
+ }
327
+ default:
328
+ return undefined
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Encodes extra arguments for a CCIP message.
334
+ * @param args - Extra arguments to encode.
335
+ * @returns Encoded extra arguments as hex string.
336
+ */
337
+ export function encodeExtraArgs(args: ExtraArgs | undefined): string {
338
+ if (!args) return '0x'
339
+ if ('blockConfirmations' in args) {
340
+ // GenericExtraArgsV3 - tightly packed binary encoding
341
+ return encodeExtraArgsV3(args)
342
+ } else if ('computeUnits' in args) {
343
+ return concat([
344
+ SVMExtraArgsV1Tag,
345
+ defaultAbiCoder.encode(
346
+ [SVMExtraArgsV1ABI],
347
+ [
348
+ {
349
+ ...args,
350
+ tokenReceiver: getAddressBytes(args.tokenReceiver),
351
+ accounts: args.accounts.map((a) => getAddressBytes(a)),
352
+ },
353
+ ],
354
+ ),
355
+ ])
356
+ } else if ('receiverObjectIds' in args) {
357
+ return concat([
358
+ SuiExtraArgsV1Tag,
359
+ defaultAbiCoder.encode(
360
+ [SuiExtraArgsV1ABI],
361
+ [
362
+ {
363
+ ...args,
364
+ tokenReceiver: zeroPadValue(getAddressBytes(args.tokenReceiver), 32),
365
+ receiverObjectIds: args.receiverObjectIds.map((a) => getDataBytes(a)),
366
+ },
367
+ ],
368
+ ),
369
+ ])
370
+ } else if ('allowOutOfOrderExecution' in args) {
371
+ if ((args as Partial<typeof args>).gasLimit == null) args.gasLimit = DEFAULT_GAS_LIMIT
372
+ return concat([EVMExtraArgsV2Tag, defaultAbiCoder.encode([EVMExtraArgsV2ABI], [args])])
373
+ } else if ((args as Partial<typeof args>).gasLimit != null) {
374
+ return concat([EVMExtraArgsV1Tag, defaultAbiCoder.encode([EVMExtraArgsV1ABI], [args])])
375
+ }
376
+ return '0x'
377
+ }
package/src/evm/gas.ts ADDED
@@ -0,0 +1,150 @@
1
+ import {
2
+ type JsonRpcApiProvider,
3
+ Contract,
4
+ FunctionFragment,
5
+ concat,
6
+ getAddress,
7
+ getNumber,
8
+ hexlify,
9
+ randomBytes,
10
+ solidityPackedKeccak256,
11
+ toBeHex,
12
+ zeroPadValue,
13
+ } from 'ethers'
14
+ import type { TypedContract } from 'ethers-abitype'
15
+ import { memoize } from 'micro-memoize'
16
+
17
+ import type { Chain } from '../chain.ts'
18
+ import TokenABI from './abi/BurnMintERC677Token.ts'
19
+ import RouterABI from './abi/Router.ts'
20
+ import { defaultAbiCoder, interfaces } from './const.ts'
21
+ import { getAddressBytes, getDataBytes } from '../utils.ts'
22
+
23
+ const ccipReceive = FunctionFragment.from({
24
+ type: 'function',
25
+ name: 'ccipReceive',
26
+ stateMutability: 'nonpayable',
27
+ inputs: RouterABI.find((v) => v.type === 'function' && v.name === 'routeMessage')!.inputs.slice(
28
+ 0,
29
+ 1,
30
+ ),
31
+ outputs: [],
32
+ })
33
+ type Any2EVMMessage = Parameters<TypedContract<typeof RouterABI>['routeMessage']>[0]
34
+
35
+ /**
36
+ * Finds suitable token balance slot by simulating a fake transfer between 2 non-existent accounts,
37
+ * with state overrides for the holders' balance, which reverts if override slot is wrong
38
+ */
39
+ const findBalancesSlot = memoize(
40
+ async function findBalancesSlot_(
41
+ token: string,
42
+ provider: JsonRpcApiProvider,
43
+ holder: string = getAddress(hexlify(randomBytes(20))),
44
+ recipient: string = getAddress(hexlify(randomBytes(20))),
45
+ ): Promise<number> {
46
+ const contract = new Contract(token, interfaces.Token, provider) as unknown as TypedContract<
47
+ typeof TokenABI
48
+ >
49
+ const fakeAmount = (await contract.totalSupply()) + 1n
50
+ const calldata = interfaces.Token.encodeFunctionData('transfer', [recipient, fakeAmount])
51
+
52
+ let firstErr
53
+ // try range(0..15), but start with most probable 0 (common ERC20) and 9 (USDC)
54
+ for (const slot of [0, 9, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15]) {
55
+ try {
56
+ await provider.send('eth_estimateGas', [
57
+ { from: holder, to: token, data: calldata },
58
+ 'latest',
59
+ {
60
+ [token]: {
61
+ stateDiff: {
62
+ [solidityPackedKeccak256(['uint256', 'uint256'], [holder, slot])]: toBeHex(
63
+ fakeAmount,
64
+ 32,
65
+ ),
66
+ },
67
+ },
68
+ },
69
+ ])
70
+ return slot // if didn't reject
71
+ } catch (err) {
72
+ firstErr ??= err
73
+ }
74
+ }
75
+ throw firstErr as Error
76
+ },
77
+ { maxArgs: 1 },
78
+ )
79
+
80
+ type EstimateExecGasOpts = Pick<
81
+ Parameters<NonNullable<Chain['estimateReceiveExecution']>>[0],
82
+ 'message' | 'receiver'
83
+ > & {
84
+ /* */
85
+ provider: JsonRpcApiProvider
86
+ router: string
87
+ }
88
+
89
+ /**
90
+ * Estimate gasLimit needed to execute a request on a receiver contract.
91
+ * @param opts - Options for estimation: provider, destRouter, receiver address and message
92
+ * @returns Estimated gasLimit
93
+ */
94
+ export async function estimateExecGas({
95
+ provider,
96
+ router,
97
+ receiver,
98
+ message,
99
+ }: EstimateExecGasOpts) {
100
+ // we need to override the state, increasing receiver's balance for each token, to simulate the
101
+ // state after tokens were transferred by the offRamp just before calling `ccipReceive`
102
+ const destAmounts: Record<string, bigint> = {}
103
+ const stateOverrides: Record<string, { stateDiff: Record<string, string> }> = {}
104
+ for (const { token, amount } of message.destTokenAmounts ?? []) {
105
+ if (!(token in destAmounts)) {
106
+ const tokenContract = new Contract(token, TokenABI, provider) as unknown as TypedContract<
107
+ typeof TokenABI
108
+ >
109
+ const currentBalance = await tokenContract.balanceOf(receiver)
110
+ destAmounts[token] = currentBalance
111
+ }
112
+ destAmounts[token]! += amount
113
+ const balancesSlot = await findBalancesSlot(token, provider, receiver, router)
114
+ stateOverrides[token] = {
115
+ stateDiff: {
116
+ [solidityPackedKeccak256(['uint256', 'uint256'], [receiver, balancesSlot])]: toBeHex(
117
+ destAmounts[token]!,
118
+ 32,
119
+ ),
120
+ },
121
+ }
122
+ }
123
+
124
+ const receiverMsg: Any2EVMMessage = {
125
+ ...message,
126
+ destTokenAmounts: message.destTokenAmounts ?? [],
127
+ sender: zeroPadValue(getAddressBytes(message.sender ?? '0x'), 32),
128
+ data: hexlify(getDataBytes(message.data || '0x')),
129
+ sourceChainSelector: message.sourceChainSelector,
130
+ }
131
+ const calldata = concat([
132
+ ccipReceive.selector,
133
+ defaultAbiCoder.encode(ccipReceive.inputs, [receiverMsg]),
134
+ ])
135
+
136
+ return (
137
+ getNumber(
138
+ (await provider.send('eth_estimateGas', [
139
+ {
140
+ from: router,
141
+ to: receiver,
142
+ data: calldata,
143
+ },
144
+ 'latest',
145
+ ...(Object.keys(stateOverrides).length ? [stateOverrides] : []),
146
+ ])) as string,
147
+ ) -
148
+ (21_000 - 700) // 21k is the base gas cost for a transaction, 700 is the gas cost of the call
149
+ )
150
+ }