@chainlink/ccip-sdk 0.90.2 → 0.91.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 +35 -26
- package/dist/aptos/exec.d.ts +4 -5
- package/dist/aptos/exec.d.ts.map +1 -1
- package/dist/aptos/exec.js +5 -14
- package/dist/aptos/exec.js.map +1 -1
- package/dist/aptos/hasher.d.ts +18 -0
- package/dist/aptos/hasher.d.ts.map +1 -1
- package/dist/aptos/hasher.js +18 -0
- package/dist/aptos/hasher.js.map +1 -1
- package/dist/aptos/index.d.ts +127 -28
- package/dist/aptos/index.d.ts.map +1 -1
- package/dist/aptos/index.js +199 -70
- package/dist/aptos/index.js.map +1 -1
- package/dist/aptos/logs.d.ts +18 -0
- package/dist/aptos/logs.d.ts.map +1 -1
- package/dist/aptos/logs.js +21 -3
- package/dist/aptos/logs.js.map +1 -1
- package/dist/aptos/send.d.ts +22 -5
- package/dist/aptos/send.d.ts.map +1 -1
- package/dist/aptos/send.js +23 -15
- package/dist/aptos/send.js.map +1 -1
- package/dist/aptos/token.d.ts +6 -0
- package/dist/aptos/token.d.ts.map +1 -1
- package/dist/aptos/token.js +6 -0
- package/dist/aptos/token.js.map +1 -1
- package/dist/aptos/types.d.ts +16 -1
- package/dist/aptos/types.d.ts.map +1 -1
- package/dist/aptos/types.js +13 -0
- package/dist/aptos/types.js.map +1 -1
- package/dist/aptos/utils.d.ts +1 -1
- package/dist/aptos/utils.js +1 -1
- package/dist/chain.d.ts +185 -99
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +38 -15
- package/dist/chain.js.map +1 -1
- package/dist/commits.d.ts +4 -10
- package/dist/commits.d.ts.map +1 -1
- package/dist/commits.js +2 -1
- package/dist/commits.js.map +1 -1
- package/dist/evm/const.d.ts +5 -0
- package/dist/evm/const.d.ts.map +1 -1
- package/dist/evm/const.js +5 -0
- package/dist/evm/const.js.map +1 -1
- package/dist/evm/errors.d.ts +5 -0
- package/dist/evm/errors.d.ts.map +1 -1
- package/dist/evm/errors.js +6 -1
- package/dist/evm/errors.js.map +1 -1
- package/dist/evm/hasher.d.ts +16 -2
- package/dist/evm/hasher.d.ts.map +1 -1
- package/dist/evm/hasher.js +17 -3
- package/dist/evm/hasher.js.map +1 -1
- package/dist/evm/index.d.ts +176 -31
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +312 -154
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/logs.d.ts +20 -0
- package/dist/evm/logs.d.ts.map +1 -0
- package/dist/evm/logs.js +194 -0
- package/dist/evm/logs.js.map +1 -0
- package/dist/evm/messages.d.ts +11 -2
- package/dist/evm/messages.d.ts.map +1 -1
- package/dist/evm/messages.js +4 -2
- package/dist/evm/messages.js.map +1 -1
- package/dist/evm/offchain.d.ts +7 -2
- package/dist/evm/offchain.d.ts.map +1 -1
- package/dist/evm/offchain.js +12 -7
- package/dist/evm/offchain.js.map +1 -1
- package/dist/execution.d.ts +19 -62
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +28 -31
- package/dist/execution.js.map +1 -1
- package/dist/extra-args.d.ts +35 -5
- package/dist/extra-args.d.ts.map +1 -1
- package/dist/extra-args.js +10 -5
- package/dist/extra-args.js.map +1 -1
- package/dist/gas.d.ts +6 -8
- package/dist/gas.d.ts.map +1 -1
- package/dist/gas.js +7 -9
- package/dist/gas.js.map +1 -1
- package/dist/hasher/common.d.ts +3 -2
- package/dist/hasher/common.d.ts.map +1 -1
- package/dist/hasher/common.js +2 -2
- package/dist/hasher/common.js.map +1 -1
- package/dist/hasher/hasher.d.ts +8 -2
- package/dist/hasher/hasher.d.ts.map +1 -1
- package/dist/hasher/hasher.js +8 -3
- package/dist/hasher/hasher.js.map +1 -1
- package/dist/hasher/merklemulti.d.ts +11 -9
- package/dist/hasher/merklemulti.d.ts.map +1 -1
- package/dist/hasher/merklemulti.js +17 -16
- package/dist/hasher/merklemulti.js.map +1 -1
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/dist/requests.d.ts +39 -25
- package/dist/requests.d.ts.map +1 -1
- package/dist/requests.js +42 -35
- package/dist/requests.js.map +1 -1
- package/dist/selectors.d.ts +1 -1
- package/dist/solana/cleanup.d.ts +14 -10
- package/dist/solana/cleanup.d.ts.map +1 -1
- package/dist/solana/cleanup.js +35 -33
- package/dist/solana/cleanup.js.map +1 -1
- package/dist/solana/exec.d.ts +19 -11
- package/dist/solana/exec.d.ts.map +1 -1
- package/dist/solana/exec.js +86 -163
- package/dist/solana/exec.js.map +1 -1
- package/dist/solana/hasher.d.ts +7 -2
- package/dist/solana/hasher.d.ts.map +1 -1
- package/dist/solana/hasher.js +7 -2
- package/dist/solana/hasher.js.map +1 -1
- package/dist/solana/index.d.ts +202 -84
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +367 -252
- package/dist/solana/index.js.map +1 -1
- package/dist/solana/offchain.d.ts +8 -18
- package/dist/solana/offchain.d.ts.map +1 -1
- package/dist/solana/offchain.js +29 -83
- package/dist/solana/offchain.js.map +1 -1
- package/dist/solana/patchBorsh.d.ts +5 -1
- package/dist/solana/patchBorsh.d.ts.map +1 -1
- package/dist/solana/patchBorsh.js +57 -46
- package/dist/solana/patchBorsh.js.map +1 -1
- package/dist/solana/send.d.ts +28 -10
- package/dist/solana/send.d.ts.map +1 -1
- package/dist/solana/send.js +44 -77
- package/dist/solana/send.js.map +1 -1
- package/dist/solana/types.d.ts +22 -1
- package/dist/solana/types.d.ts.map +1 -1
- package/dist/solana/types.js +12 -1
- package/dist/solana/types.js.map +1 -1
- package/dist/solana/utils.d.ts +58 -4
- package/dist/solana/utils.d.ts.map +1 -1
- package/dist/solana/utils.js +110 -7
- package/dist/solana/utils.js.map +1 -1
- package/dist/sui/hasher.d.ts +18 -0
- package/dist/sui/hasher.d.ts.map +1 -1
- package/dist/sui/hasher.js +18 -0
- package/dist/sui/hasher.js.map +1 -1
- package/dist/sui/index.d.ts +99 -12
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +108 -19
- package/dist/sui/index.js.map +1 -1
- package/dist/sui/types.d.ts +6 -0
- package/dist/sui/types.d.ts.map +1 -1
- package/dist/sui/types.js +5 -0
- package/dist/sui/types.js.map +1 -1
- package/dist/supported-chains.d.ts +2 -1
- package/dist/supported-chains.d.ts.map +1 -1
- package/dist/supported-chains.js.map +1 -1
- package/dist/types.d.ts +127 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +67 -46
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +143 -21
- package/dist/utils.js.map +1 -1
- package/package.json +13 -9
- package/src/aptos/exec.ts +7 -18
- package/src/aptos/hasher.ts +18 -0
- package/src/aptos/index.ts +288 -110
- package/src/aptos/logs.ts +21 -3
- package/src/aptos/send.ts +25 -22
- package/src/aptos/token.ts +6 -0
- package/src/aptos/types.ts +26 -2
- package/src/aptos/utils.ts +1 -1
- package/src/chain.ts +243 -108
- package/src/commits.ts +6 -7
- package/src/evm/const.ts +5 -0
- package/src/evm/errors.ts +6 -1
- package/src/evm/hasher.ts +20 -4
- package/src/evm/index.ts +416 -214
- package/src/evm/logs.ts +255 -0
- package/src/evm/messages.ts +11 -5
- package/src/evm/offchain.ts +13 -4
- package/src/execution.ts +40 -32
- package/src/extra-args.ts +38 -6
- package/src/gas.ts +7 -9
- package/src/hasher/common.ts +3 -2
- package/src/hasher/hasher.ts +12 -4
- package/src/hasher/merklemulti.ts +17 -16
- package/src/index.ts +29 -23
- package/src/requests.ts +64 -46
- package/src/selectors.ts +1 -1
- package/src/solana/cleanup.ts +49 -34
- package/src/solana/exec.ts +128 -272
- package/src/solana/hasher.ts +13 -4
- package/src/solana/index.ts +483 -356
- package/src/solana/offchain.ts +32 -102
- package/src/solana/patchBorsh.ts +65 -50
- package/src/solana/send.ts +52 -111
- package/src/solana/types.ts +44 -3
- package/src/solana/utils.ts +143 -19
- package/src/sui/hasher.ts +18 -0
- package/src/sui/index.ts +143 -31
- package/src/sui/types.ts +6 -0
- package/src/supported-chains.ts +2 -1
- package/src/types.ts +130 -18
- package/src/utils.ts +168 -26
- package/tsconfig.json +2 -1
package/src/evm/logs.ts
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type JsonRpcApiProvider,
|
|
3
|
+
type Log,
|
|
4
|
+
FetchRequest,
|
|
5
|
+
JsonRpcProvider,
|
|
6
|
+
isHexString,
|
|
7
|
+
} from 'ethers'
|
|
8
|
+
import { memoize } from 'micro-memoize'
|
|
9
|
+
|
|
10
|
+
import type { LogFilter } from '../chain.ts'
|
|
11
|
+
import { blockRangeGenerator, getSomeBlockNumberBefore, util } from '../utils.ts'
|
|
12
|
+
import { getAllFragmentsMatchingEvents } from './const.ts'
|
|
13
|
+
import type { WithLogger } from '../types.ts'
|
|
14
|
+
|
|
15
|
+
const MAX_PARALLEL_JOBS = 24
|
|
16
|
+
const PER_REQUEST_TIMEOUT = 5000
|
|
17
|
+
|
|
18
|
+
const getFallbackRpcsList = memoize(
|
|
19
|
+
async () => {
|
|
20
|
+
const response = await fetch('https://chainlist.org/rpcs.json')
|
|
21
|
+
const data = await response.json()
|
|
22
|
+
return data as {
|
|
23
|
+
chainId: number
|
|
24
|
+
rpc: { url: string }[]
|
|
25
|
+
explorers: { url: string }[]
|
|
26
|
+
}[]
|
|
27
|
+
},
|
|
28
|
+
{ async: true },
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// like Promise.any, but receives Promise factories and spawn a maximum number of them in parallel
|
|
32
|
+
function anyPromiseMax<T>(
|
|
33
|
+
promises: readonly (() => Promise<T>)[],
|
|
34
|
+
maxParallelJobs: number,
|
|
35
|
+
cancel?: Promise<unknown>,
|
|
36
|
+
): Promise<T> {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const errors: unknown[] = new Array(promises.length)
|
|
39
|
+
let index = 0
|
|
40
|
+
let inFlight = 0
|
|
41
|
+
let completed = 0
|
|
42
|
+
|
|
43
|
+
if (promises.length === 0) {
|
|
44
|
+
reject(new AggregateError([], 'All promises were rejected'))
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
let cancelled = false
|
|
48
|
+
void cancel?.finally(() => {
|
|
49
|
+
cancelled = true
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const startNext = () => {
|
|
53
|
+
while (!cancelled && inFlight < maxParallelJobs && index < promises.length) {
|
|
54
|
+
const currentIndex = index++
|
|
55
|
+
inFlight++
|
|
56
|
+
|
|
57
|
+
void promises[currentIndex]()
|
|
58
|
+
.then(resolve)
|
|
59
|
+
.catch((error) => {
|
|
60
|
+
errors[currentIndex] = error
|
|
61
|
+
completed++
|
|
62
|
+
inFlight--
|
|
63
|
+
|
|
64
|
+
if (completed === promises.length) {
|
|
65
|
+
reject(new AggregateError(errors, 'All promises were rejected'))
|
|
66
|
+
} else {
|
|
67
|
+
startNext()
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
startNext()
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// cache
|
|
78
|
+
const archiveRpcs: Record<number, Promise<JsonRpcApiProvider>> = {}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Like provider.getLogs, but from a public list of archive nodes and wide range, races the first to reply
|
|
82
|
+
* @param chainId - The chain ID of the network to query
|
|
83
|
+
* @param filter - Log filter options
|
|
84
|
+
* @param destroy$ - An optional promise that, when resolved, cancels the requests
|
|
85
|
+
* @returns Array of Logs
|
|
86
|
+
*/
|
|
87
|
+
async function getFallbackArchiveLogs(
|
|
88
|
+
chainId: number,
|
|
89
|
+
filter: {
|
|
90
|
+
address: string
|
|
91
|
+
topics: (string | string[] | null)[]
|
|
92
|
+
startBlock?: number
|
|
93
|
+
endBlock?: number | 'latest'
|
|
94
|
+
},
|
|
95
|
+
{ logger = console, destroy$ }: { destroy$?: Promise<unknown> } & WithLogger = {},
|
|
96
|
+
) {
|
|
97
|
+
const provider = archiveRpcs[chainId]
|
|
98
|
+
if (provider != null) {
|
|
99
|
+
return (await provider).getLogs({
|
|
100
|
+
...filter,
|
|
101
|
+
fromBlock: filter.startBlock ?? 1,
|
|
102
|
+
toBlock: filter.endBlock ?? 'latest',
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
let cancel!: (_?: unknown) => void
|
|
106
|
+
let cancel$ = new Promise<unknown>((resolve) => (cancel = resolve))
|
|
107
|
+
if (destroy$) cancel$ = Promise.race([destroy$, cancel$])
|
|
108
|
+
|
|
109
|
+
let winner: string
|
|
110
|
+
const providerLogs$ = getFallbackRpcsList()
|
|
111
|
+
.then((rpcs) => {
|
|
112
|
+
const rpc = rpcs.find(({ chainId: id }) => id === chainId)
|
|
113
|
+
if (!rpc) throw new Error(`No RPC found for chainId=${chainId}`)
|
|
114
|
+
return Array.from(
|
|
115
|
+
new Set(rpc.rpc.map(({ url }) => url).filter((url) => url.match(/^https?:\/\//))),
|
|
116
|
+
)
|
|
117
|
+
})
|
|
118
|
+
.then((urls) =>
|
|
119
|
+
anyPromiseMax(
|
|
120
|
+
urls.map((url) => async () => {
|
|
121
|
+
const fetchReq = new FetchRequest(url)
|
|
122
|
+
fetchReq.timeout = PER_REQUEST_TIMEOUT
|
|
123
|
+
const provider = new JsonRpcProvider(fetchReq, chainId)
|
|
124
|
+
void cancel$.finally(() => {
|
|
125
|
+
if (url === winner) return
|
|
126
|
+
provider.destroy()
|
|
127
|
+
try {
|
|
128
|
+
fetchReq.cancel()
|
|
129
|
+
} catch (_) {
|
|
130
|
+
// ignore
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
return [
|
|
134
|
+
provider,
|
|
135
|
+
await provider
|
|
136
|
+
.getLogs({
|
|
137
|
+
...filter,
|
|
138
|
+
fromBlock: filter.startBlock ?? 1,
|
|
139
|
+
toBlock: filter.endBlock ?? 'latest',
|
|
140
|
+
})
|
|
141
|
+
.then((logs) => {
|
|
142
|
+
if (!logs.length) throw new Error('No logs found')
|
|
143
|
+
logger.debug(
|
|
144
|
+
'getFallbackArchiveLogs raced',
|
|
145
|
+
url,
|
|
146
|
+
'from',
|
|
147
|
+
urls.length,
|
|
148
|
+
'urls, got',
|
|
149
|
+
logs.length,
|
|
150
|
+
'logs for',
|
|
151
|
+
filter,
|
|
152
|
+
)
|
|
153
|
+
winner ??= url
|
|
154
|
+
cancel()
|
|
155
|
+
return logs
|
|
156
|
+
}),
|
|
157
|
+
] as const // return both winner provider and logs
|
|
158
|
+
}),
|
|
159
|
+
MAX_PARALLEL_JOBS,
|
|
160
|
+
cancel$,
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
.finally(cancel)
|
|
164
|
+
archiveRpcs[chainId] = providerLogs$.then(([provider]) => provider) // cache provider
|
|
165
|
+
archiveRpcs[chainId].catch(() => {
|
|
166
|
+
delete archiveRpcs[chainId]
|
|
167
|
+
})
|
|
168
|
+
return providerLogs$.then(([, logs]) => logs) // return logs
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Implements Chain.getLogs for EVM.
|
|
173
|
+
* If !(filter.startBlock|startTime), walks backwards from endBlock, otherwise forward from then.
|
|
174
|
+
* @param filter - Chain LogFilter. The `onlyFallback` option controls pagination behavior:
|
|
175
|
+
* - If undefined (default): paginate main provider only by filter.page
|
|
176
|
+
* - If false: first try whole range with main provider, then fallback to archive provider
|
|
177
|
+
* - If true: don't paginate (throw if can't fetch wide range from either provider)
|
|
178
|
+
* @param ctx - Context object containing provider, logger and destry$ notify promise
|
|
179
|
+
* @returns Async iterator of logs.
|
|
180
|
+
*/
|
|
181
|
+
export async function* getEvmLogs(
|
|
182
|
+
filter: LogFilter & { onlyFallback?: boolean },
|
|
183
|
+
ctx: { provider: JsonRpcApiProvider; destroy$?: Promise<unknown> } & WithLogger,
|
|
184
|
+
): AsyncIterableIterator<Log> {
|
|
185
|
+
const { provider, logger = console } = ctx
|
|
186
|
+
const endBlock = filter.endBlock ?? (await provider.getBlockNumber())
|
|
187
|
+
if (
|
|
188
|
+
filter.topics?.length &&
|
|
189
|
+
filter.topics.every((t: string | string[] | null): t is string => typeof t === 'string')
|
|
190
|
+
) {
|
|
191
|
+
const topics = new Set(
|
|
192
|
+
filter.topics
|
|
193
|
+
.filter(isHexString)
|
|
194
|
+
.concat(Object.keys(getAllFragmentsMatchingEvents(filter.topics)) as `0x${string}`[])
|
|
195
|
+
.flat(),
|
|
196
|
+
)
|
|
197
|
+
if (!topics.size) {
|
|
198
|
+
throw new Error(`Could not find matching topics: ${util.inspect(filter.topics)}`)
|
|
199
|
+
}
|
|
200
|
+
filter.topics = [Array.from(topics)]
|
|
201
|
+
}
|
|
202
|
+
if (filter.startBlock == null && filter.startTime) {
|
|
203
|
+
filter.startBlock = await getSomeBlockNumberBefore(
|
|
204
|
+
async (block: number | 'finalized') => (await provider.getBlock(block))!.timestamp, // cached
|
|
205
|
+
endBlock,
|
|
206
|
+
filter.startTime,
|
|
207
|
+
ctx,
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
if (filter.onlyFallback != null && filter.address && filter.topics?.length) {
|
|
211
|
+
let logs
|
|
212
|
+
try {
|
|
213
|
+
logs = await provider.getLogs({
|
|
214
|
+
...filter,
|
|
215
|
+
fromBlock: filter.startBlock ?? 1,
|
|
216
|
+
toBlock: filter.endBlock ?? 'latest',
|
|
217
|
+
})
|
|
218
|
+
} catch (_) {
|
|
219
|
+
try {
|
|
220
|
+
logs = await getFallbackArchiveLogs(
|
|
221
|
+
Number((await provider.getNetwork()).chainId),
|
|
222
|
+
{
|
|
223
|
+
address: filter.address,
|
|
224
|
+
topics: filter.topics,
|
|
225
|
+
startBlock: filter.startBlock ?? 1,
|
|
226
|
+
endBlock: filter.endBlock ?? 'latest',
|
|
227
|
+
},
|
|
228
|
+
ctx,
|
|
229
|
+
)
|
|
230
|
+
} catch (err) {
|
|
231
|
+
if (filter.onlyFallback === true) throw err
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (logs) {
|
|
235
|
+
if (!filter.startBlock) logs.reverse()
|
|
236
|
+
yield* logs
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// paginate only if filter.onlyFallback is nullish
|
|
241
|
+
for (const blockRange of blockRangeGenerator({ ...filter, endBlock })) {
|
|
242
|
+
logger.debug('evm getLogs:', {
|
|
243
|
+
...blockRange,
|
|
244
|
+
...(filter.address ? { address: filter.address } : {}),
|
|
245
|
+
...(filter.topics?.length ? { topics: filter.topics } : {}),
|
|
246
|
+
})
|
|
247
|
+
const logs = await provider.getLogs({
|
|
248
|
+
...blockRange,
|
|
249
|
+
...(filter.address ? { address: filter.address } : {}),
|
|
250
|
+
...(filter.topics?.length ? { topics: filter.topics } : {}),
|
|
251
|
+
})
|
|
252
|
+
if (!filter.startBlock) logs.reverse()
|
|
253
|
+
yield* logs
|
|
254
|
+
}
|
|
255
|
+
}
|
package/src/evm/messages.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type EVM2EVMOnRamp_1_5_ABI from './abi/OnRamp_1_5.ts'
|
|
|
7
7
|
import type OnRamp_1_6_ABI from './abi/OnRamp_1_6.ts'
|
|
8
8
|
import { defaultAbiCoder } from './const.ts'
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/** Utility type that cleans up address types to just `string`. */
|
|
11
11
|
export type CleanAddressable<T> = T extends string | Addressable
|
|
12
12
|
? string
|
|
13
13
|
: T extends Record<string, unknown>
|
|
@@ -23,7 +23,7 @@ type EVM2AnyMessageRequested = CleanAddressable<
|
|
|
23
23
|
>[0]
|
|
24
24
|
>
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
/** v1.6+ Message Base type (all other destinations share this intersection). */
|
|
27
27
|
export type CCIPMessage_V1_6 = MergeArrayElements<
|
|
28
28
|
CleanAddressable<
|
|
29
29
|
AbiParametersToPrimitiveTypes<
|
|
@@ -33,6 +33,7 @@ export type CCIPMessage_V1_6 = MergeArrayElements<
|
|
|
33
33
|
{ tokenAmounts: readonly SourceTokenData[] }
|
|
34
34
|
>
|
|
35
35
|
|
|
36
|
+
/** CCIP v1.5 EVM message type. */
|
|
36
37
|
export type CCIPMessage_V1_5_EVM = MergeArrayElements<
|
|
37
38
|
EVM2AnyMessageRequested,
|
|
38
39
|
{
|
|
@@ -41,13 +42,15 @@ export type CCIPMessage_V1_5_EVM = MergeArrayElements<
|
|
|
41
42
|
}
|
|
42
43
|
>
|
|
43
44
|
|
|
45
|
+
/** CCIP v1.2 EVM message type. */
|
|
44
46
|
export type CCIPMessage_V1_2_EVM = EVM2AnyMessageRequested & {
|
|
45
47
|
header: Omit<CCIPMessage_V1_6['header'], 'destChainSelector'>
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
/** v1.6 EVM specialization with EVMExtraArgsV2 and tokenAmounts.*.destGasAmount. */
|
|
49
51
|
export type CCIPMessage_V1_6_EVM = CCIPMessage_V1_6 & EVMExtraArgsV2
|
|
50
52
|
|
|
53
|
+
/** Union type for CCIP EVM messages across versions. */
|
|
51
54
|
export type CCIPMessage_EVM<V extends CCIPVersion = CCIPVersion> = V extends typeof CCIPVersion.V1_2
|
|
52
55
|
? CCIPMessage_V1_2_EVM
|
|
53
56
|
: V extends typeof CCIPVersion.V1_5
|
|
@@ -56,6 +59,7 @@ export type CCIPMessage_EVM<V extends CCIPVersion = CCIPVersion> = V extends typ
|
|
|
56
59
|
|
|
57
60
|
const SourceTokenData =
|
|
58
61
|
'tuple(bytes sourcePoolAddress, bytes destTokenAddress, bytes extraData, uint64 destGasAmount)'
|
|
62
|
+
/** Token transfer data in a CCIP message. */
|
|
59
63
|
export type SourceTokenData = {
|
|
60
64
|
sourcePoolAddress: string
|
|
61
65
|
destTokenAddress: string
|
|
@@ -64,8 +68,10 @@ export type SourceTokenData = {
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
71
|
+
* Parses v1.5 and earlier `message.sourceTokenData`.
|
|
72
|
+
* Version 1.6+ already contains this in `message.tokenAmounts`.
|
|
73
|
+
* @param data - The source token data string to parse.
|
|
74
|
+
* @returns The parsed SourceTokenData object.
|
|
69
75
|
*/
|
|
70
76
|
export function parseSourceTokenData(data: string): SourceTokenData {
|
|
71
77
|
const decoded = defaultAbiCoder.decode([SourceTokenData], data)
|
package/src/evm/offchain.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Addressable, type Log, EventFragment } from 'ethers'
|
|
2
2
|
|
|
3
3
|
import { getLbtcAttestation, getUsdcAttestation } from '../offchain.ts'
|
|
4
|
-
import type { CCIPMessage, CCIPRequest, OffchainTokenData } from '../types.ts'
|
|
4
|
+
import type { CCIPMessage, CCIPRequest, OffchainTokenData, WithLogger } from '../types.ts'
|
|
5
5
|
import { networkInfo } from '../utils.ts'
|
|
6
6
|
import { defaultAbiCoder, interfaces, requestsFragments } from './const.ts'
|
|
7
7
|
import { type SourceTokenData, parseSourceTokenData } from './messages.ts'
|
|
@@ -32,6 +32,7 @@ export async function fetchEVMOffchainTokenData(
|
|
|
32
32
|
message: CCIPMessage
|
|
33
33
|
log: Pick<CCIPRequest['log'], 'index'>
|
|
34
34
|
},
|
|
35
|
+
ctx: WithLogger,
|
|
35
36
|
): Promise<OffchainTokenData[]> {
|
|
36
37
|
const { isTestnet } = networkInfo(request.message.header.sourceChainSelector)
|
|
37
38
|
// there's a chance there are other CCIPSendRequested in same tx,
|
|
@@ -52,6 +53,7 @@ export async function fetchEVMOffchainTokenData(
|
|
|
52
53
|
request.message.tokenAmounts,
|
|
53
54
|
usdcRequestLogs,
|
|
54
55
|
isTestnet,
|
|
56
|
+
ctx,
|
|
55
57
|
)
|
|
56
58
|
let lbtcTokenData: OffchainTokenData[] = []
|
|
57
59
|
try {
|
|
@@ -62,7 +64,7 @@ export async function fetchEVMOffchainTokenData(
|
|
|
62
64
|
tokenAmounts = request.message.tokenAmounts
|
|
63
65
|
}
|
|
64
66
|
//for lbtc we distinguish logs by hash in event, so we can pass all of them
|
|
65
|
-
lbtcTokenData = await getLbtcTokenData(tokenAmounts, request.tx.logs as Log[], isTestnet)
|
|
67
|
+
lbtcTokenData = await getLbtcTokenData(tokenAmounts, request.tx.logs as Log[], isTestnet, ctx)
|
|
66
68
|
} catch (_) {
|
|
67
69
|
// pass
|
|
68
70
|
}
|
|
@@ -78,6 +80,11 @@ export async function fetchEVMOffchainTokenData(
|
|
|
78
80
|
return offchainTokenData
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Encodes offchain token data for EVM execution.
|
|
85
|
+
* @param data - Offchain token data to encode.
|
|
86
|
+
* @returns ABI-encoded data or empty hex string.
|
|
87
|
+
*/
|
|
81
88
|
export function encodeEVMOffchainTokenData(data: OffchainTokenData): string {
|
|
82
89
|
if (data?._tag === 'usdc') {
|
|
83
90
|
return defaultAbiCoder.encode(['tuple(bytes message, bytes attestation)'], [data])
|
|
@@ -99,6 +106,7 @@ async function getUsdcTokenData(
|
|
|
99
106
|
tokenAmounts: CCIPMessage['tokenAmounts'],
|
|
100
107
|
allLogsInRequest: Pick<Log, 'topics' | 'address' | 'data'>[],
|
|
101
108
|
isTestnet: boolean,
|
|
109
|
+
{ logger = console }: WithLogger = {},
|
|
102
110
|
): Promise<OffchainTokenData[]> {
|
|
103
111
|
const attestations: OffchainTokenData[] = []
|
|
104
112
|
|
|
@@ -151,7 +159,7 @@ async function getUsdcTokenData(
|
|
|
151
159
|
// encoding of OffchainTokenData to be done as part of Chain.executeReceipt
|
|
152
160
|
} catch (err) {
|
|
153
161
|
// maybe not a USDC transfer, or not ready
|
|
154
|
-
|
|
162
|
+
logger.warn(`❌ EVM CCTP: Failed to fetch attestation for message:`, message, err)
|
|
155
163
|
}
|
|
156
164
|
}
|
|
157
165
|
attestations.push(tokenData)
|
|
@@ -167,6 +175,7 @@ async function getLbtcTokenData(
|
|
|
167
175
|
tokenAmounts: readonly SourceTokenData[],
|
|
168
176
|
allLogsInRequest: readonly Pick<Log, 'topics' | 'address' | 'data'>[],
|
|
169
177
|
isTestnet: boolean,
|
|
178
|
+
{ logger = console }: WithLogger = {},
|
|
170
179
|
): Promise<OffchainTokenData[]> {
|
|
171
180
|
const lbtcDepositHashes = new Set(
|
|
172
181
|
allLogsInRequest
|
|
@@ -181,7 +190,7 @@ async function getLbtcTokenData(
|
|
|
181
190
|
try {
|
|
182
191
|
return { _tag: 'lbtc', extraData, ...(await getLbtcAttestation(extraData, isTestnet)) }
|
|
183
192
|
} catch (err) {
|
|
184
|
-
|
|
193
|
+
logger.warn(`❌ EVM LBTC: Failed to fetch attestation for message:`, extraData, err)
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
}),
|
package/src/execution.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { memoize } from 'micro-memoize'
|
|
2
2
|
|
|
3
3
|
import type { Chain, ChainStatic } from './chain.ts'
|
|
4
4
|
import { Tree, getLeafHasher, proofFlagsToBits } from './hasher/index.ts'
|
|
5
5
|
import {
|
|
6
|
+
type CCIPCommit,
|
|
6
7
|
type CCIPExecution,
|
|
7
8
|
type CCIPMessage,
|
|
9
|
+
type CCIPRequest,
|
|
8
10
|
type CCIPVersion,
|
|
9
|
-
type CommitReport,
|
|
10
11
|
type ExecutionReport,
|
|
11
12
|
type Lane,
|
|
13
|
+
type WithLogger,
|
|
12
14
|
ExecutionState,
|
|
13
15
|
} from './types.ts'
|
|
14
16
|
|
|
@@ -16,9 +18,10 @@ import {
|
|
|
16
18
|
* Pure/sync function to calculate/generate OffRamp.executeManually report for messageIds
|
|
17
19
|
*
|
|
18
20
|
* @param messagesInBatch - Array containing all messages in batch, ordered
|
|
19
|
-
* @param lane - Arguments for
|
|
20
|
-
* @param
|
|
21
|
+
* @param lane - Arguments for leafHasher (lane info)
|
|
22
|
+
* @param messageId - Message ID to prove for manual execution
|
|
21
23
|
* @param merkleRoot - Optional merkleRoot of the CommitReport, for validation
|
|
24
|
+
* @param ctx - Context for logging
|
|
22
25
|
* @returns ManualExec report arguments
|
|
23
26
|
**/
|
|
24
27
|
export function calculateManualExecProof<V extends CCIPVersion = CCIPVersion>(
|
|
@@ -26,8 +29,9 @@ export function calculateManualExecProof<V extends CCIPVersion = CCIPVersion>(
|
|
|
26
29
|
lane: Lane<V>,
|
|
27
30
|
messageId: string,
|
|
28
31
|
merkleRoot?: string,
|
|
32
|
+
ctx?: WithLogger,
|
|
29
33
|
): Omit<ExecutionReport, 'offchainTokenData' | 'message'> {
|
|
30
|
-
const hasher = getLeafHasher(lane)
|
|
34
|
+
const hasher = getLeafHasher(lane, ctx)
|
|
31
35
|
|
|
32
36
|
const msgIdx = messagesInBatch.findIndex((message) => message.header.messageId === messageId)
|
|
33
37
|
if (msgIdx < 0) {
|
|
@@ -56,8 +60,13 @@ export function calculateManualExecProof<V extends CCIPVersion = CCIPVersion>(
|
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
export const discoverOffRamp =
|
|
60
|
-
async function discoverOffRamp_(
|
|
63
|
+
export const discoverOffRamp = memoize(
|
|
64
|
+
async function discoverOffRamp_(
|
|
65
|
+
source: Chain,
|
|
66
|
+
dest: Chain,
|
|
67
|
+
onRamp: string,
|
|
68
|
+
{ logger = console }: WithLogger = {},
|
|
69
|
+
): Promise<string> {
|
|
61
70
|
const sourceRouter = await source.getRouterForOnRamp(onRamp, dest.network.chainSelector)
|
|
62
71
|
const sourceOffRamps = await source.getOffRampsForRouter(
|
|
63
72
|
sourceRouter,
|
|
@@ -70,7 +79,7 @@ export const discoverOffRamp = moize.default(
|
|
|
70
79
|
for (const offRamp of destOffRamps) {
|
|
71
80
|
const offRampsOnRamp = await dest.getOnRampForOffRamp(offRamp, source.network.chainSelector)
|
|
72
81
|
if (offRampsOnRamp === onRamp) {
|
|
73
|
-
|
|
82
|
+
logger.debug(
|
|
74
83
|
'discoverOffRamp: found, from',
|
|
75
84
|
{ sourceRouter, sourceOffRamps, destOnRamp, destOffRamps, offRampsOnRamp },
|
|
76
85
|
'=',
|
|
@@ -83,49 +92,48 @@ export const discoverOffRamp = moize.default(
|
|
|
83
92
|
throw new Error(`No matching offRamp found for "${onRamp}" on "${dest.network.name}"`)
|
|
84
93
|
},
|
|
85
94
|
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
(dest as Chain).network.chainSelector,
|
|
89
|
-
onRamp as string,
|
|
90
|
-
],
|
|
95
|
+
transformKey: ([source, dest, onRamp]) =>
|
|
96
|
+
[source.network.chainSelector, dest.network.chainSelector, onRamp] as const,
|
|
91
97
|
},
|
|
92
98
|
)
|
|
93
99
|
|
|
94
100
|
/**
|
|
95
|
-
* Generic implementation for fetching ExecutionReceipts for given requests
|
|
96
|
-
* If more than one request is given, may yield them interleaved
|
|
97
|
-
* Completes as soon as there's no more work to be done
|
|
98
|
-
*
|
|
101
|
+
* Generic implementation for fetching ExecutionReceipts for given requests.
|
|
102
|
+
* If more than one request is given, may yield them interleaved.
|
|
103
|
+
* Completes as soon as there's no more work to be done.
|
|
104
|
+
*
|
|
105
|
+
* Two possible behaviors:
|
|
99
106
|
* - if `startBlock|startTime` is given, pages forward from that block up;
|
|
100
107
|
* completes when success (final) receipt is found for all requests (or reach latest block)
|
|
101
108
|
* - otherwise, pages backwards and returns only the most recent receipt per request;
|
|
102
109
|
* completes when receipts for all requests were seen
|
|
103
110
|
*
|
|
104
|
-
* @param dest -
|
|
105
|
-
* @param
|
|
106
|
-
* @param
|
|
107
|
-
*
|
|
108
|
-
* @param hints.page
|
|
109
|
-
|
|
110
|
-
**/
|
|
111
|
+
* @param dest - Provider to page through.
|
|
112
|
+
* @param offRamp - OffRamp contract address.
|
|
113
|
+
* @param request - CCIP request to search executions for.
|
|
114
|
+
* @param commit - Optional commit info to narrow down search.
|
|
115
|
+
* @param hints - Optional hints (e.g., `page` for getLogs pagination range).
|
|
116
|
+
*/
|
|
111
117
|
export async function* fetchExecutionReceipts(
|
|
112
118
|
dest: Chain,
|
|
113
119
|
offRamp: string,
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
request: CCIPRequest,
|
|
121
|
+
commit?: CCIPCommit,
|
|
122
|
+
hints?: { page?: number },
|
|
116
123
|
): AsyncGenerator<CCIPExecution> {
|
|
117
|
-
const onlyLast = !
|
|
124
|
+
const onlyLast = !commit?.log.blockNumber && !request.tx.timestamp // backwards
|
|
118
125
|
for await (const log of dest.getLogs({
|
|
119
|
-
|
|
126
|
+
startBlock: commit?.log.blockNumber,
|
|
127
|
+
startTime: request.tx.timestamp,
|
|
120
128
|
address: offRamp,
|
|
121
129
|
topics: ['ExecutionStateChanged'],
|
|
130
|
+
...hints,
|
|
122
131
|
})) {
|
|
123
132
|
const receipt = (dest.constructor as ChainStatic).decodeReceipt(log)
|
|
124
|
-
if (!receipt ||
|
|
125
|
-
if (onlyLast || receipt.state === ExecutionState.Success) messageIds.delete(receipt.messageId)
|
|
133
|
+
if (!receipt || receipt.messageId !== request.message.header.messageId) continue
|
|
126
134
|
|
|
127
|
-
const timestamp = await dest.getBlockTimestamp(log.blockNumber)
|
|
135
|
+
const timestamp = log.tx?.timestamp ?? (await dest.getBlockTimestamp(log.blockNumber))
|
|
128
136
|
yield { receipt, log, timestamp }
|
|
129
|
-
if (
|
|
137
|
+
if (onlyLast || receipt.state === ExecutionState.Success) break
|
|
130
138
|
}
|
|
131
139
|
}
|
package/src/extra-args.ts
CHANGED
|
@@ -1,32 +1,63 @@
|
|
|
1
1
|
import { type BytesLike, id } from 'ethers'
|
|
2
2
|
|
|
3
|
-
import { ChainFamily } from './chain.ts'
|
|
4
3
|
import { supportedChains } from './supported-chains.ts'
|
|
4
|
+
import { ChainFamily } from './types.ts'
|
|
5
5
|
|
|
6
|
+
/** Tag identifier for EVMExtraArgsV1 encoding. */
|
|
6
7
|
export const EVMExtraArgsV1Tag = id('CCIP EVMExtraArgsV1').substring(0, 10) as '0x97a657c9'
|
|
8
|
+
/** Tag identifier for EVMExtraArgsV2 encoding. */
|
|
7
9
|
export const EVMExtraArgsV2Tag = id('CCIP EVMExtraArgsV2').substring(0, 10) as '0x181dcf10'
|
|
10
|
+
/** Tag identifier for SVMExtraArgsV1 encoding. */
|
|
8
11
|
export const SVMExtraArgsV1Tag = id('CCIP SVMExtraArgsV1').substring(0, 10) as '0x1f3b3aba'
|
|
12
|
+
/** Tag identifier for SuiExtraArgsV1 encoding. */
|
|
9
13
|
export const SuiExtraArgsV1Tag = id('CCIP SuiExtraArgsV1').substring(0, 10) as '0x21ea4ca9'
|
|
10
14
|
|
|
15
|
+
/**
|
|
16
|
+
* EVM extra arguments version 1 with gas limit only.
|
|
17
|
+
*/
|
|
11
18
|
export type EVMExtraArgsV1 = {
|
|
19
|
+
/** Gas limit for execution on the destination chain. */
|
|
12
20
|
gasLimit: bigint
|
|
13
21
|
}
|
|
14
|
-
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* EVM extra arguments version 2 with out-of-order execution support.
|
|
25
|
+
* Also known as GenericExtraArgsV2.
|
|
26
|
+
*/
|
|
15
27
|
export type EVMExtraArgsV2 = EVMExtraArgsV1 & {
|
|
28
|
+
/** Whether to allow out-of-order message execution. */
|
|
16
29
|
allowOutOfOrderExecution: boolean
|
|
17
30
|
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Solana (SVM) extra arguments version 1.
|
|
34
|
+
*/
|
|
18
35
|
export type SVMExtraArgsV1 = {
|
|
36
|
+
/** Compute units for Solana execution. */
|
|
19
37
|
computeUnits: bigint
|
|
38
|
+
/** Bitmap indicating which accounts are writable. */
|
|
20
39
|
accountIsWritableBitmap: bigint
|
|
40
|
+
/** Whether to allow out-of-order message execution. */
|
|
21
41
|
allowOutOfOrderExecution: boolean
|
|
42
|
+
/** Token receiver address on Solana. */
|
|
22
43
|
tokenReceiver: string
|
|
44
|
+
/** Additional account addresses required for execution. */
|
|
23
45
|
accounts: string[]
|
|
24
46
|
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Sui extra arguments version 1.
|
|
50
|
+
*/
|
|
25
51
|
export type SuiExtraArgsV1 = EVMExtraArgsV2 & {
|
|
52
|
+
/** Token receiver address on Sui. */
|
|
26
53
|
tokenReceiver: string
|
|
54
|
+
/** Object IDs required for the receiver. */
|
|
27
55
|
receiverObjectIds: string[]
|
|
28
56
|
}
|
|
29
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Union type of all supported extra arguments formats.
|
|
60
|
+
*/
|
|
30
61
|
export type ExtraArgs = EVMExtraArgsV1 | EVMExtraArgsV2 | SVMExtraArgsV1 | SuiExtraArgsV1
|
|
31
62
|
|
|
32
63
|
/**
|
|
@@ -41,10 +72,11 @@ export function encodeExtraArgs(args: ExtraArgs, from: ChainFamily = ChainFamily
|
|
|
41
72
|
}
|
|
42
73
|
|
|
43
74
|
/**
|
|
44
|
-
* Parses extra arguments from CCIP messages
|
|
45
|
-
* @param data -
|
|
46
|
-
* @
|
|
47
|
-
|
|
75
|
+
* Parses extra arguments from CCIP messages.
|
|
76
|
+
* @param data - Extra arguments bytearray data.
|
|
77
|
+
* @param from - Optional chain family to narrow decoding attempts.
|
|
78
|
+
* @returns Extra arguments object if found, undefined otherwise.
|
|
79
|
+
*/
|
|
48
80
|
export function decodeExtraArgs(
|
|
49
81
|
data: BytesLike,
|
|
50
82
|
from?: ChainFamily,
|
package/src/gas.ts
CHANGED
|
@@ -35,14 +35,12 @@ const ccipReceive = FunctionFragment.from({
|
|
|
35
35
|
type Any2EVMMessage = Parameters<TypedContract<typeof RouterABI>['routeMessage']>[0]
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* Estimate CCIP gasLimit needed to execute a request on a contract receiver
|
|
39
|
-
*
|
|
40
|
-
* @param dest - Provider for the destination chain
|
|
41
|
-
* @param request - CCIP request info
|
|
42
|
-
* @
|
|
43
|
-
|
|
44
|
-
* @returns estimated gasLimit as bigint
|
|
45
|
-
**/
|
|
38
|
+
* Estimate CCIP gasLimit needed to execute a request on a contract receiver.
|
|
39
|
+
* @param source - Provider for the source chain.
|
|
40
|
+
* @param dest - Provider for the destination chain.
|
|
41
|
+
* @param request - CCIP request info containing `lane` and `message` details.
|
|
42
|
+
* @returns Estimated gasLimit as bigint.
|
|
43
|
+
*/
|
|
46
44
|
export async function estimateExecGasForRequest(
|
|
47
45
|
source: Chain,
|
|
48
46
|
dest: EVMChain,
|
|
@@ -60,7 +58,7 @@ export async function estimateExecGasForRequest(
|
|
|
60
58
|
}
|
|
61
59
|
},
|
|
62
60
|
) {
|
|
63
|
-
const offRamp = await discoverOffRamp(source, dest, request.lane.onRamp)
|
|
61
|
+
const offRamp = await discoverOffRamp(source, dest, request.lane.onRamp, source)
|
|
64
62
|
const destRouter = await dest.getRouterForOffRamp(offRamp, request.lane.sourceChainSelector)
|
|
65
63
|
|
|
66
64
|
const destTokenAmounts = await Promise.all(
|
package/src/hasher/common.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { concat, hexlify, keccak256, toBeHex } from 'ethers'
|
|
|
2
2
|
|
|
3
3
|
import type { CCIPMessage, CCIPVersion } from '../types.ts'
|
|
4
4
|
|
|
5
|
+
/** Function type for computing the leaf hash of a CCIP message. */
|
|
5
6
|
export type LeafHasher<V extends CCIPVersion = CCIPVersion> = (message: CCIPMessage<V>) => string
|
|
6
7
|
|
|
7
8
|
const INTERNAL_DOMAIN_SEPARATOR = toBeHex(1, 32)
|
|
@@ -10,8 +11,8 @@ export const ZERO_HASH = hexlify(new Uint8Array(32).fill(0xff))
|
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Computes the Keccak-256 hash of the concatenation of two hash values.
|
|
13
|
-
* @param a The first hash as a Hash type.
|
|
14
|
-
* @param b The second hash as a Hash type.
|
|
14
|
+
* @param a - The first hash as a Hash type.
|
|
15
|
+
* @param b - The second hash as a Hash type.
|
|
15
16
|
* @returns The Keccak-256 hash result as a Hash type.
|
|
16
17
|
*/
|
|
17
18
|
export function hashInternal(a: string, b: string): string {
|