viem 2.55.13 → 2.55.16
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/CHANGELOG.md +24 -0
- package/_cjs/actions/index.js +4 -2
- package/_cjs/actions/index.js.map +1 -1
- package/_cjs/actions/public/simulateCalls.js +213 -142
- package/_cjs/actions/public/simulateCalls.js.map +1 -1
- package/_cjs/actions/public/watchBlockHeaders.js +61 -0
- package/_cjs/actions/public/watchBlockHeaders.js.map +1 -0
- package/_cjs/clients/decorators/public.js +2 -0
- package/_cjs/clients/decorators/public.js.map +1 -1
- package/_cjs/errors/version.js +1 -1
- package/_cjs/index.js.map +1 -1
- package/_cjs/tempo/actions/earn.js +2 -0
- package/_cjs/tempo/actions/earn.js.map +1 -1
- package/_cjs/tempo/actions/zone.js +18 -8
- package/_cjs/tempo/actions/zone.js.map +1 -1
- package/_cjs/utils/siwe/parseSiweMessage.js +16 -3
- package/_cjs/utils/siwe/parseSiweMessage.js.map +1 -1
- package/_cjs/utils/siwe/validateSiweMessage.js +13 -3
- package/_cjs/utils/siwe/validateSiweMessage.js.map +1 -1
- package/_esm/actions/index.js +1 -0
- package/_esm/actions/index.js.map +1 -1
- package/_esm/actions/public/simulateCalls.js +238 -150
- package/_esm/actions/public/simulateCalls.js.map +1 -1
- package/_esm/actions/public/watchBlockHeaders.js +81 -0
- package/_esm/actions/public/watchBlockHeaders.js.map +1 -0
- package/_esm/clients/decorators/public.js +2 -0
- package/_esm/clients/decorators/public.js.map +1 -1
- package/_esm/errors/version.js +1 -1
- package/_esm/index.js.map +1 -1
- package/_esm/tempo/actions/earn.js +2 -0
- package/_esm/tempo/actions/earn.js.map +1 -1
- package/_esm/tempo/actions/zone.js +20 -8
- package/_esm/tempo/actions/zone.js.map +1 -1
- package/_esm/utils/siwe/parseSiweMessage.js +18 -3
- package/_esm/utils/siwe/parseSiweMessage.js.map +1 -1
- package/_esm/utils/siwe/validateSiweMessage.js +16 -3
- package/_esm/utils/siwe/validateSiweMessage.js.map +1 -1
- package/_types/actions/index.d.ts +1 -0
- package/_types/actions/index.d.ts.map +1 -1
- package/_types/actions/public/simulateCalls.d.ts +5 -3
- package/_types/actions/public/simulateCalls.d.ts.map +1 -1
- package/_types/actions/public/watchBlockHeaders.d.ts +45 -0
- package/_types/actions/public/watchBlockHeaders.d.ts.map +1 -0
- package/_types/clients/decorators/public.d.ts +24 -0
- package/_types/clients/decorators/public.d.ts.map +1 -1
- package/_types/errors/version.d.ts +1 -1
- package/_types/index.d.ts +1 -0
- package/_types/index.d.ts.map +1 -1
- package/_types/tempo/actions/earn.d.ts.map +1 -1
- package/_types/tempo/actions/zone.d.ts +21 -5
- package/_types/tempo/actions/zone.d.ts.map +1 -1
- package/_types/utils/siwe/parseSiweMessage.d.ts.map +1 -1
- package/_types/utils/siwe/validateSiweMessage.d.ts.map +1 -1
- package/actions/index.ts +9 -0
- package/actions/public/simulateCalls.ts +324 -181
- package/actions/public/watchBlockHeaders.ts +131 -0
- package/clients/decorators/public.ts +33 -0
- package/errors/version.ts +1 -1
- package/index.ts +8 -0
- package/package.json +1 -1
- package/tempo/actions/earn.ts +2 -0
- package/tempo/actions/zone.ts +56 -5
- package/utils/siwe/parseSiweMessage.ts +20 -3
- package/utils/siwe/validateSiweMessage.ts +13 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AbiStateMutability, Address, Narrow } from 'abitype'
|
|
2
2
|
import * as AbiConstructor from 'ox/AbiConstructor'
|
|
3
|
+
import * as AbiEvent from 'ox/AbiEvent'
|
|
3
4
|
import * as AbiFunction from 'ox/AbiFunction'
|
|
4
5
|
|
|
5
6
|
import { parseAccount } from '../../accounts/utils/parseAccount.js'
|
|
@@ -8,9 +9,11 @@ import type { Transport } from '../../clients/transports/createTransport.js'
|
|
|
8
9
|
import { ethAddress, zeroAddress } from '../../constants/address.js'
|
|
9
10
|
import { deploylessCallViaBytecodeBytecode } from '../../constants/contracts.js'
|
|
10
11
|
import { BaseError } from '../../errors/base.js'
|
|
12
|
+
import { CallExecutionError } from '../../errors/contract.js'
|
|
13
|
+
import { ExecutionRevertedError } from '../../errors/node.js'
|
|
11
14
|
import type { ErrorType } from '../../errors/utils.js'
|
|
12
15
|
import type { Account } from '../../types/account.js'
|
|
13
|
-
import type { Block } from '../../types/block.js'
|
|
16
|
+
import type { Block, BlockTag } from '../../types/block.js'
|
|
14
17
|
import type { Call, Calls } from '../../types/calls.js'
|
|
15
18
|
import type { Chain } from '../../types/chain.js'
|
|
16
19
|
import type { Log } from '../../types/log.js'
|
|
@@ -18,15 +21,14 @@ import type { Hex } from '../../types/misc.js'
|
|
|
18
21
|
import type { MulticallResults } from '../../types/multicall.js'
|
|
19
22
|
import type { StateOverride } from '../../types/stateOverride.js'
|
|
20
23
|
import type { Mutable } from '../../types/utils.js'
|
|
24
|
+
import { type PadErrorType, pad } from '../../utils/data/pad.js'
|
|
25
|
+
import { hexToBigInt } from '../../utils/encoding/fromHex.js'
|
|
26
|
+
import { type CallErrorType, call } from './call.js'
|
|
27
|
+
import { type GetBlockErrorType, getBlock } from './getBlock.js'
|
|
21
28
|
import {
|
|
22
|
-
type
|
|
23
|
-
|
|
24
|
-
} from '
|
|
25
|
-
import { hexToBigInt } from '../../utils/index.js'
|
|
26
|
-
import {
|
|
27
|
-
type CreateAccessListErrorType,
|
|
28
|
-
createAccessList,
|
|
29
|
-
} from './createAccessList.js'
|
|
29
|
+
type GetBlockNumberErrorType,
|
|
30
|
+
getBlockNumber,
|
|
31
|
+
} from './getBlockNumber.js'
|
|
30
32
|
import {
|
|
31
33
|
type SimulateBlocksErrorType,
|
|
32
34
|
type SimulateBlocksParameters,
|
|
@@ -36,6 +38,33 @@ import {
|
|
|
36
38
|
const getBalanceCode =
|
|
37
39
|
'0x6080604052348015600e575f80fd5b5061016d8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063f8b2cb4f1461002d575b5f80fd5b610047600480360381019061004291906100db565b61005d565b604051610054919061011e565b60405180910390f35b5f8173ffffffffffffffffffffffffffffffffffffffff16319050919050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100aa82610081565b9050919050565b6100ba816100a0565b81146100c4575f80fd5b50565b5f813590506100d5816100b1565b92915050565b5f602082840312156100f0576100ef61007d565b5b5f6100fd848285016100c7565b91505092915050565b5f819050919050565b61011881610106565b82525050565b5f6020820190506101315f83018461010f565b9291505056fea26469706673582212203b9fe929fe995c7cf9887f0bdba8a36dd78e8b73f149b17d2d9ad7cd09d2dc6264736f6c634300081a0033'
|
|
38
40
|
|
|
41
|
+
// Runtime bytecode from `contracts/src/deployless/StaticCall.sol`, compiled with
|
|
42
|
+
// Solidity 0.8.35.
|
|
43
|
+
const staticCallCode =
|
|
44
|
+
'0x608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063fd00430c1461002d575b5f5ffd5b6100476004803603810190610042919061012b565b610049565b005b80825f375f5f825f865afa610060573d5f5f3e3d5ffd5b3d5f5f3e3d5ff35b5f5ffd5b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61009982610070565b9050919050565b6100a98161008f565b81146100b3575f5ffd5b50565b5f813590506100c4816100a0565b92915050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f8401126100eb576100ea6100ca565b5b8235905067ffffffffffffffff811115610108576101076100ce565b5b602083019150836001820283011115610124576101236100d2565b5b9250929050565b5f5f5f6040848603121561014257610141610068565b5b5f61014f868287016100b6565b935050602084013567ffffffffffffffff8111156101705761016f61006c565b5b61017c868287016100d6565b9250925050925092509256fea2646970667358221220635ed99185cacf3f2acba6921f23687c969cec2bbaf5f9ad599f507e6e105e6964736f6c63430008230033'
|
|
45
|
+
|
|
46
|
+
const staticCallAddressBase = 0x00000000000000000000000000000000deadbeefn
|
|
47
|
+
|
|
48
|
+
// ERC20 & ERC721 share this selector – both are `Transfer(address,address,uint256)`.
|
|
49
|
+
const transferEventSelector = AbiEvent.getSelector(
|
|
50
|
+
AbiEvent.from(
|
|
51
|
+
'event Transfer(address indexed from, address indexed to, uint256 value)',
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const balanceOfFunction = AbiFunction.from(
|
|
56
|
+
'function balanceOf(address) returns (uint256)',
|
|
57
|
+
)
|
|
58
|
+
const decimalsFunction = AbiFunction.from(
|
|
59
|
+
'function decimals() returns (uint256)',
|
|
60
|
+
)
|
|
61
|
+
const tokenUriFunction = AbiFunction.from(
|
|
62
|
+
'function tokenURI(uint256) returns (string)',
|
|
63
|
+
)
|
|
64
|
+
const symbolFunction = AbiFunction.from('function symbol() returns (string)')
|
|
65
|
+
const staticCallFunction = AbiFunction.from(
|
|
66
|
+
'function query(address target, bytes data)',
|
|
67
|
+
)
|
|
39
68
|
export type SimulateCallsParameters<
|
|
40
69
|
calls extends readonly unknown[] = readonly unknown[],
|
|
41
70
|
account extends Account | Address | undefined = Account | Address | undefined,
|
|
@@ -83,8 +112,10 @@ export type SimulateCallsReturnType<
|
|
|
83
112
|
export type SimulateCallsErrorType =
|
|
84
113
|
| AbiFunction.encodeData.ErrorType
|
|
85
114
|
| AbiFunction.from.ErrorType
|
|
86
|
-
|
|
|
87
|
-
|
|
|
115
|
+
| CallErrorType
|
|
116
|
+
| GetBlockErrorType
|
|
117
|
+
| GetBlockNumberErrorType
|
|
118
|
+
| PadErrorType
|
|
88
119
|
| SimulateBlocksErrorType
|
|
89
120
|
| ErrorType
|
|
90
121
|
|
|
@@ -162,204 +193,224 @@ export async function simulateCalls<
|
|
|
162
193
|
})
|
|
163
194
|
: undefined
|
|
164
195
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
196
|
+
// Resolve moving canonical tags once so both simulations share a block. Preserve
|
|
197
|
+
// `pending` because its prospective number cannot be queried directly.
|
|
198
|
+
// Do not infer the base from results because clients disagree on simulated numbering.
|
|
199
|
+
const blockTag_ = blockTag ?? client.experimental_blockTag ?? 'latest'
|
|
200
|
+
let baseBlockNumber = blockNumber
|
|
201
|
+
if (
|
|
202
|
+
traceAssetChanges &&
|
|
203
|
+
typeof baseBlockNumber !== 'bigint' &&
|
|
204
|
+
blockTag_ !== 'earliest' &&
|
|
205
|
+
blockTag_ !== 'pending'
|
|
206
|
+
) {
|
|
207
|
+
if (blockTag_ === 'latest')
|
|
208
|
+
baseBlockNumber = await getBlockNumber(client, { cacheTime: 0 })
|
|
209
|
+
else {
|
|
210
|
+
const block = await getBlock(client, { blockTag: blockTag_ })
|
|
211
|
+
if (typeof block.number !== 'bigint')
|
|
212
|
+
throw new BaseError(
|
|
213
|
+
`Block tag \`${blockTag_}\` did not resolve to a number.`,
|
|
214
|
+
)
|
|
215
|
+
baseBlockNumber = block.number
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const block_ =
|
|
219
|
+
typeof baseBlockNumber === 'bigint'
|
|
220
|
+
? { blockNumber: baseBlockNumber }
|
|
221
|
+
: { blockTag: blockTag_ }
|
|
222
|
+
|
|
223
|
+
// Discover ERC20/721 addresses the calls move assets in. Simulating the batch as a
|
|
224
|
+
// whole is what makes this correct: the calls run sequentially, with the caller's
|
|
225
|
+
// state overrides, on the same base block the results are measured against.
|
|
226
|
+
const discovery = traceAssetChanges
|
|
227
|
+
? await simulateBlocks(client, {
|
|
228
|
+
...block_,
|
|
229
|
+
blocks: [
|
|
230
|
+
{
|
|
231
|
+
calls: calls.map((call) => ({
|
|
232
|
+
...(call as Call),
|
|
233
|
+
from: account!.address,
|
|
234
|
+
})) as any,
|
|
235
|
+
stateOverrides,
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
traceTransfers,
|
|
239
|
+
validation,
|
|
240
|
+
})
|
|
241
|
+
: undefined
|
|
242
|
+
|
|
243
|
+
const assetAddresses = discovery
|
|
244
|
+
? [
|
|
245
|
+
...new Set([
|
|
246
|
+
...tokensFromLogs(
|
|
247
|
+
discovery[0]!.calls.flatMap((call) => call.logs ?? []),
|
|
248
|
+
account!.address,
|
|
249
|
+
),
|
|
250
|
+
// Included even for calls without data: contracts that mint on receiving
|
|
251
|
+
// native value (WETH) emit `Deposit`, not a `Transfer` the logs would catch.
|
|
252
|
+
// Candidates without code fall out at `isBalance`.
|
|
253
|
+
...parameters.calls.map((call: any) => call.to?.toLowerCase()),
|
|
254
|
+
]),
|
|
255
|
+
].filter(
|
|
256
|
+
(address): address is Address =>
|
|
257
|
+
Boolean(address) && address !== ethAddress && address !== zeroAddress,
|
|
258
|
+
)
|
|
180
259
|
: []
|
|
260
|
+
const staticCallAddress = getStaticCallAddress([
|
|
261
|
+
...(account ? [account.address] : []),
|
|
262
|
+
...assetAddresses,
|
|
263
|
+
...(stateOverrides?.map(({ address }) => address) ?? []),
|
|
264
|
+
])
|
|
265
|
+
const staticCallStateOverrides: StateOverride = [
|
|
266
|
+
{ address: staticCallAddress, code: staticCallCode },
|
|
267
|
+
]
|
|
181
268
|
|
|
182
|
-
const blocks = await
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
269
|
+
const [balanceCallsPre, blocks] = await Promise.all([
|
|
270
|
+
traceAssetChanges
|
|
271
|
+
? Promise.all([
|
|
272
|
+
readBalance(client, {
|
|
273
|
+
account: account!.address,
|
|
274
|
+
...block_,
|
|
275
|
+
data: getBalanceData!,
|
|
276
|
+
stateOverride: stateOverrides,
|
|
277
|
+
}),
|
|
278
|
+
...assetAddresses.map((address) =>
|
|
279
|
+
readBalance(client, {
|
|
280
|
+
account: account!.address,
|
|
281
|
+
address,
|
|
282
|
+
...block_,
|
|
283
|
+
data: AbiFunction.encodeData(balanceOfFunction, [
|
|
284
|
+
account!.address,
|
|
285
|
+
]),
|
|
286
|
+
staticCallAddress,
|
|
287
|
+
stateOverride: stateOverrides,
|
|
288
|
+
}),
|
|
289
|
+
),
|
|
290
|
+
])
|
|
291
|
+
: [],
|
|
292
|
+
simulateBlocks(client, {
|
|
293
|
+
...block_,
|
|
294
|
+
blocks: [
|
|
295
|
+
{
|
|
296
|
+
calls: [...calls, { to: zeroAddress }].map((call) => ({
|
|
297
|
+
...(call as Call),
|
|
298
|
+
from: account?.address,
|
|
299
|
+
})) as any,
|
|
300
|
+
stateOverrides,
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
...(traceAssetChanges
|
|
304
|
+
? [
|
|
305
|
+
// ETH post balances
|
|
306
|
+
{
|
|
307
|
+
calls: [{ data: getBalanceData }],
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
// Asset post balances
|
|
311
|
+
{
|
|
312
|
+
calls: assetAddresses.map((address) => ({
|
|
313
|
+
to: staticCallAddress,
|
|
314
|
+
data: encodeStaticCall(
|
|
315
|
+
address,
|
|
316
|
+
AbiFunction.encodeData(balanceOfFunction, [
|
|
317
|
+
account!.address,
|
|
318
|
+
]),
|
|
200
319
|
),
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
to: address,
|
|
205
|
-
from: zeroAddress,
|
|
206
|
-
nonce: i,
|
|
207
|
-
})),
|
|
208
|
-
stateOverrides: [
|
|
209
|
-
{
|
|
210
|
-
address: zeroAddress,
|
|
211
|
-
nonce: 0,
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
},
|
|
215
|
-
]
|
|
216
|
-
: []),
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
calls: [...calls, { to: zeroAddress }].map((call) => ({
|
|
220
|
-
...(call as Call),
|
|
221
|
-
from: account?.address,
|
|
222
|
-
})) as any,
|
|
223
|
-
stateOverrides,
|
|
224
|
-
},
|
|
320
|
+
})),
|
|
321
|
+
stateOverrides: staticCallStateOverrides,
|
|
322
|
+
},
|
|
225
323
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
// Asset post balances
|
|
234
|
-
{
|
|
235
|
-
calls: assetAddresses.map((address, i) => ({
|
|
236
|
-
abi: [
|
|
237
|
-
AbiFunction.from(
|
|
238
|
-
'function balanceOf(address) returns (uint256)',
|
|
324
|
+
// Decimals
|
|
325
|
+
{
|
|
326
|
+
calls: assetAddresses.map((address) => ({
|
|
327
|
+
to: staticCallAddress,
|
|
328
|
+
data: encodeStaticCall(
|
|
329
|
+
address,
|
|
330
|
+
AbiFunction.encodeData(decimalsFunction),
|
|
239
331
|
),
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
},
|
|
252
|
-
],
|
|
253
|
-
},
|
|
254
|
-
|
|
255
|
-
// Decimals
|
|
256
|
-
{
|
|
257
|
-
calls: assetAddresses.map((address, i) => ({
|
|
258
|
-
to: address,
|
|
259
|
-
abi: [
|
|
260
|
-
AbiFunction.from('function decimals() returns (uint256)'),
|
|
261
|
-
],
|
|
262
|
-
functionName: 'decimals',
|
|
263
|
-
from: zeroAddress,
|
|
264
|
-
nonce: i,
|
|
265
|
-
})),
|
|
266
|
-
stateOverrides: [
|
|
267
|
-
{
|
|
268
|
-
address: zeroAddress,
|
|
269
|
-
nonce: 0,
|
|
270
|
-
},
|
|
271
|
-
],
|
|
272
|
-
},
|
|
273
|
-
|
|
274
|
-
// Token URI
|
|
275
|
-
{
|
|
276
|
-
calls: assetAddresses.map((address, i) => ({
|
|
277
|
-
to: address,
|
|
278
|
-
abi: [
|
|
279
|
-
AbiFunction.from(
|
|
280
|
-
'function tokenURI(uint256) returns (string)',
|
|
332
|
+
})),
|
|
333
|
+
stateOverrides: staticCallStateOverrides,
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
// Token URI
|
|
337
|
+
{
|
|
338
|
+
calls: assetAddresses.map((address) => ({
|
|
339
|
+
to: staticCallAddress,
|
|
340
|
+
data: encodeStaticCall(
|
|
341
|
+
address,
|
|
342
|
+
AbiFunction.encodeData(tokenUriFunction, [0n]),
|
|
281
343
|
),
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
})),
|
|
305
|
-
stateOverrides: [
|
|
306
|
-
{
|
|
307
|
-
address: zeroAddress,
|
|
308
|
-
nonce: 0,
|
|
309
|
-
},
|
|
310
|
-
],
|
|
311
|
-
},
|
|
312
|
-
]
|
|
313
|
-
: []),
|
|
314
|
-
],
|
|
315
|
-
traceTransfers,
|
|
316
|
-
validation,
|
|
317
|
-
})
|
|
344
|
+
})),
|
|
345
|
+
stateOverrides: staticCallStateOverrides,
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// Symbols
|
|
349
|
+
{
|
|
350
|
+
calls: assetAddresses.map((address) => ({
|
|
351
|
+
to: staticCallAddress,
|
|
352
|
+
data: encodeStaticCall(
|
|
353
|
+
address,
|
|
354
|
+
AbiFunction.encodeData(symbolFunction),
|
|
355
|
+
),
|
|
356
|
+
})),
|
|
357
|
+
stateOverrides: staticCallStateOverrides,
|
|
358
|
+
},
|
|
359
|
+
]
|
|
360
|
+
: []),
|
|
361
|
+
],
|
|
362
|
+
traceTransfers,
|
|
363
|
+
validation,
|
|
364
|
+
}),
|
|
365
|
+
])
|
|
318
366
|
|
|
319
|
-
const block_results =
|
|
367
|
+
const block_results = blocks[0]!
|
|
320
368
|
const [
|
|
321
|
-
block_ethPre,
|
|
322
|
-
block_assetsPre,
|
|
323
|
-
,
|
|
324
369
|
block_ethPost,
|
|
325
370
|
block_assetsPost,
|
|
326
371
|
block_decimals,
|
|
327
372
|
block_tokenURI,
|
|
328
373
|
block_symbols,
|
|
329
|
-
] = traceAssetChanges ? blocks : []
|
|
374
|
+
] = traceAssetChanges ? blocks.slice(1) : []
|
|
330
375
|
|
|
331
376
|
// Extract call results from the simulation.
|
|
332
377
|
const { calls: block_calls, ...block } = block_results
|
|
333
|
-
const results = block_calls.slice(0, -1)
|
|
378
|
+
const results = block_calls.slice(0, -1)
|
|
334
379
|
|
|
335
380
|
// Extract pre-execution ETH and asset balances.
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
const balancesPre = [...ethPre, ...assetsPre].map((call) =>
|
|
339
|
-
call.status === 'success' ? hexToBigInt(call.data) : null,
|
|
381
|
+
const balancesPre = balanceCallsPre.map((call) =>
|
|
382
|
+
isBalance(call) ? hexToBigInt(call.data) : null,
|
|
340
383
|
)
|
|
341
384
|
|
|
342
385
|
// Extract post-execution ETH and asset balances.
|
|
343
386
|
const ethPost = block_ethPost?.calls ?? []
|
|
344
387
|
const assetsPost = block_assetsPost?.calls ?? []
|
|
345
|
-
const
|
|
346
|
-
|
|
388
|
+
const balanceCallsPost = [...ethPost, ...assetsPost]
|
|
389
|
+
const balancesPost = balanceCallsPost.map((call) =>
|
|
390
|
+
isBalance(call) ? hexToBigInt(call.data) : null,
|
|
347
391
|
)
|
|
348
392
|
|
|
349
393
|
// Extract asset symbols & decimals.
|
|
350
|
-
const decimals = (block_decimals?.calls ?? []).map((
|
|
351
|
-
|
|
352
|
-
) as (
|
|
353
|
-
const symbols = (block_symbols?.calls ?? []).map((
|
|
354
|
-
|
|
394
|
+
const decimals = (block_decimals?.calls ?? []).map((call) =>
|
|
395
|
+
decodeAssetResult(call, decimalsFunction),
|
|
396
|
+
) as (bigint | null)[]
|
|
397
|
+
const symbols = (block_symbols?.calls ?? []).map((call) =>
|
|
398
|
+
decodeAssetResult(call, symbolFunction),
|
|
355
399
|
) as (string | null)[]
|
|
356
|
-
const tokenURI = (block_tokenURI?.calls ?? []).map((
|
|
357
|
-
|
|
400
|
+
const tokenURI = (block_tokenURI?.calls ?? []).map((call) =>
|
|
401
|
+
decodeAssetResult(call, tokenUriFunction),
|
|
358
402
|
) as (string | null)[]
|
|
359
403
|
|
|
360
404
|
const changes: Mutable<SimulateCallsReturnType<calls>['assetChanges']> = []
|
|
361
405
|
for (const [i, balancePost] of balancesPost.entries()) {
|
|
362
|
-
const
|
|
406
|
+
const balancePre_ = balancesPre[i]
|
|
407
|
+
const preCall = balanceCallsPre[i]
|
|
408
|
+
const balancePre =
|
|
409
|
+
typeof balancePre_ === 'bigint'
|
|
410
|
+
? balancePre_
|
|
411
|
+
: i > 0 && preCall?.status === 'success' && preCall.data === '0x'
|
|
412
|
+
? 0n
|
|
413
|
+
: null
|
|
363
414
|
|
|
364
415
|
if (typeof balancePost !== 'bigint') continue
|
|
365
416
|
if (typeof balancePre !== 'bigint') continue
|
|
@@ -383,9 +434,6 @@ export async function simulateCalls<
|
|
|
383
434
|
}
|
|
384
435
|
})()
|
|
385
436
|
|
|
386
|
-
if (changes.some((change) => change.token.address === token.address))
|
|
387
|
-
continue
|
|
388
|
-
|
|
389
437
|
changes.push({
|
|
390
438
|
token,
|
|
391
439
|
value: {
|
|
@@ -402,3 +450,98 @@ export async function simulateCalls<
|
|
|
402
450
|
results,
|
|
403
451
|
} as unknown as SimulateCallsReturnType<calls>
|
|
404
452
|
}
|
|
453
|
+
|
|
454
|
+
function encodeStaticCall(address: Address, data: Hex) {
|
|
455
|
+
return AbiFunction.encodeData(staticCallFunction, [address, data])
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Extract token addresses that the account transferred, from a simulated block's logs.
|
|
459
|
+
// Only topics 0-2 are read: ERC721's fourth topic is the token id, which is irrelevant
|
|
460
|
+
// as `balanceOf(address)` on a 721 returns the owner's NFT count.
|
|
461
|
+
function tokensFromLogs(logs: readonly Log[], account: Address): Address[] {
|
|
462
|
+
const account_ = pad(account.toLowerCase() as Hex, { size: 32 })
|
|
463
|
+
return logs
|
|
464
|
+
.filter((log) => {
|
|
465
|
+
if (log.topics[0]?.toLowerCase() !== transferEventSelector) return false
|
|
466
|
+
// `traceTransfers` emits synthetic native transfer logs under `ethAddress`, which
|
|
467
|
+
// is measured separately.
|
|
468
|
+
if (log.address.toLowerCase() === ethAddress) return false
|
|
469
|
+
return (
|
|
470
|
+
log.topics[1]?.toLowerCase() === account_ ||
|
|
471
|
+
log.topics[2]?.toLowerCase() === account_
|
|
472
|
+
)
|
|
473
|
+
})
|
|
474
|
+
.map((log) => log.address.toLowerCase() as Address)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Whether a `balanceOf` probe returned a balance. A candidate without code (an EOA that
|
|
478
|
+
// merely received value) succeeds with empty data instead of reverting, so `status`
|
|
479
|
+
// alone is not enough.
|
|
480
|
+
function isBalance(call: { data: Hex; status: 'success' | 'failure' }) {
|
|
481
|
+
return call.status === 'success' && /^0x[\da-f]{64}$/i.test(call.data)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function decodeAssetResult(
|
|
485
|
+
call: { data: Hex; status: 'success' | 'failure' },
|
|
486
|
+
abiFunction: AbiFunction.AbiFunction,
|
|
487
|
+
) {
|
|
488
|
+
if (call.status === 'failure' || call.data === '0x') return null
|
|
489
|
+
try {
|
|
490
|
+
return AbiFunction.decodeResult(abiFunction, call.data)
|
|
491
|
+
} catch {
|
|
492
|
+
return null
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async function readBalance<chain extends Chain | undefined>(
|
|
497
|
+
client: Client<Transport, chain>,
|
|
498
|
+
parameters: {
|
|
499
|
+
account: Address
|
|
500
|
+
address?: Address | undefined
|
|
501
|
+
blockNumber?: bigint | undefined
|
|
502
|
+
blockTag?: BlockTag | undefined
|
|
503
|
+
data: Hex
|
|
504
|
+
staticCallAddress?: Address | undefined
|
|
505
|
+
stateOverride: StateOverride | undefined
|
|
506
|
+
},
|
|
507
|
+
) {
|
|
508
|
+
const {
|
|
509
|
+
account,
|
|
510
|
+
address,
|
|
511
|
+
blockNumber,
|
|
512
|
+
blockTag,
|
|
513
|
+
data,
|
|
514
|
+
staticCallAddress,
|
|
515
|
+
stateOverride,
|
|
516
|
+
} = parameters
|
|
517
|
+
try {
|
|
518
|
+
const result = await call({ ...client, ccipRead: false }, {
|
|
519
|
+
account: address ? zeroAddress : account,
|
|
520
|
+
data: address ? encodeStaticCall(address, data) : data,
|
|
521
|
+
stateOverride:
|
|
522
|
+
address && staticCallAddress
|
|
523
|
+
? [
|
|
524
|
+
...(stateOverride ?? []),
|
|
525
|
+
{ address: staticCallAddress, code: staticCallCode },
|
|
526
|
+
]
|
|
527
|
+
: stateOverride,
|
|
528
|
+
...(address ? { to: staticCallAddress } : {}),
|
|
529
|
+
...(typeof blockNumber === 'bigint' ? { blockNumber } : { blockTag }),
|
|
530
|
+
} as never)
|
|
531
|
+
return { data: result.data ?? '0x', status: 'success' as const }
|
|
532
|
+
} catch (error) {
|
|
533
|
+
if (
|
|
534
|
+
!(error instanceof CallExecutionError) ||
|
|
535
|
+
!(error.cause instanceof ExecutionRevertedError)
|
|
536
|
+
)
|
|
537
|
+
throw error
|
|
538
|
+
return { data: '0x' as const, status: 'failure' as const }
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function getStaticCallAddress(addresses: readonly Address[]): Address {
|
|
543
|
+
const occupied = new Set(addresses.map((address) => address.toLowerCase()))
|
|
544
|
+
let value = staticCallAddressBase
|
|
545
|
+
while (occupied.has(`0x${value.toString(16).padStart(40, '0')}`)) value++
|
|
546
|
+
return `0x${value.toString(16).padStart(40, '0')}`
|
|
547
|
+
}
|