- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
453 lines
13 KiB
TypeScript
453 lines
13 KiB
TypeScript
import type { Abi, AbiStateMutability, Address, Narrow } from 'abitype'
|
|
import * as BlockOverrides from 'ox/BlockOverrides'
|
|
import type * as RpcSchema from 'ox/RpcSchema'
|
|
import type { RpcSchemaTempo } from 'ox/tempo'
|
|
|
|
import {
|
|
type ParseAccountErrorType,
|
|
parseAccount,
|
|
} from '../../accounts/utils/parseAccount.js'
|
|
import type { Client } from '../../clients/createClient.js'
|
|
import type { Transport } from '../../clients/transports/createTransport.js'
|
|
import { AbiDecodingZeroDataError } from '../../errors/abi.js'
|
|
import type { BaseError } from '../../errors/base.js'
|
|
import { RawContractError } from '../../errors/contract.js'
|
|
import { UnknownNodeError } from '../../errors/node.js'
|
|
import type { ErrorType as ErrorType_ } from '../../errors/utils.js'
|
|
import type { Account } from '../../types/account.js'
|
|
import type { Block, BlockTag } from '../../types/block.js'
|
|
import type { Call, Calls } from '../../types/calls.js'
|
|
import type { Chain } from '../../types/chain.js'
|
|
import type { Log } from '../../types/log.js'
|
|
import type { Hex } from '../../types/misc.js'
|
|
import type { MulticallResults } from '../../types/multicall.js'
|
|
import type { StateOverride } from '../../types/stateOverride.js'
|
|
import type { TransactionRequest } from '../../types/transaction.js'
|
|
import type { ExactPartial, UnionOmit } from '../../types/utils.js'
|
|
import {
|
|
type DecodeFunctionResultErrorType,
|
|
decodeFunctionResult,
|
|
} from '../../utils/abi/decodeFunctionResult.js'
|
|
import {
|
|
type EncodeFunctionDataErrorType,
|
|
encodeFunctionData,
|
|
} from '../../utils/abi/encodeFunctionData.js'
|
|
import { concat } from '../../utils/data/concat.js'
|
|
import {
|
|
type NumberToHexErrorType,
|
|
numberToHex,
|
|
} from '../../utils/encoding/toHex.js'
|
|
import { getContractError } from '../../utils/errors/getContractError.js'
|
|
import {
|
|
type GetNodeErrorReturnType,
|
|
getNodeError,
|
|
} from '../../utils/errors/getNodeError.js'
|
|
import {
|
|
type FormatBlockErrorType,
|
|
formatBlock,
|
|
} from '../../utils/formatters/block.js'
|
|
import { formatLog } from '../../utils/formatters/log.js'
|
|
import {
|
|
type FormatTransactionRequestErrorType,
|
|
formatTransactionRequest,
|
|
} from '../../utils/formatters/transactionRequest.js'
|
|
import {
|
|
type SerializeStateOverrideErrorType,
|
|
serializeStateOverride,
|
|
} from '../../utils/stateOverride.js'
|
|
import {
|
|
type AssertRequestErrorType,
|
|
assertRequest,
|
|
} from '../../utils/transaction/assertRequest.js'
|
|
|
|
export type TokenMetadata = {
|
|
[address: Hex]: {
|
|
name: string
|
|
symbol: string
|
|
currency: string
|
|
}
|
|
}
|
|
|
|
type CallExtraProperties = ExactPartial<
|
|
UnionOmit<
|
|
TransactionRequest,
|
|
'blobs' | 'data' | 'kzg' | 'to' | 'sidecars' | 'value'
|
|
>
|
|
> & {
|
|
/** Account attached to the call (msg.sender). */
|
|
account?: Account | Address | undefined
|
|
/** Recipient. `null` if contract deployment. */
|
|
to?: Address | null | undefined
|
|
}
|
|
|
|
/**
|
|
* Simulates a set of calls on block(s) via `tempo_simulateV1`.
|
|
*
|
|
* Returns simulated block results and token metadata for any TIP-20
|
|
* tokens involved in the simulation.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* import { createClient, http, parseUnits } from 'viem'
|
|
* import { tempo } from 'viem/chains'
|
|
* import { Actions } from 'viem/tempo'
|
|
*
|
|
* const client = createClient({
|
|
* account: '0x...',
|
|
* chain: tempo,
|
|
* transport: http(),
|
|
* })
|
|
*
|
|
* const { blocks, tokenMetadata } = await Actions.simulate.simulateBlocks(client, {
|
|
* blocks: [{
|
|
* calls: [
|
|
* Actions.token.transfer.call({
|
|
* token: '0x20c0...01',
|
|
* to: '0x...',
|
|
* amount: parseUnits('100', 6),
|
|
* }),
|
|
* ],
|
|
* }],
|
|
* traceTransfers: true,
|
|
* })
|
|
* ```
|
|
*
|
|
* @param client - Client.
|
|
* @param parameters - {@link simulateBlocks.Parameters}
|
|
* @returns Simulated blocks and token metadata. {@link simulateBlocks.ReturnType}
|
|
*/
|
|
export async function simulateBlocks<
|
|
chain extends Chain | undefined,
|
|
const calls extends readonly unknown[],
|
|
>(
|
|
client: Client<Transport, chain>,
|
|
parameters: simulateBlocks.Parameters<calls>,
|
|
): Promise<simulateBlocks.ReturnType<calls>> {
|
|
const {
|
|
blockNumber,
|
|
blockTag = client.experimental_blockTag ?? 'latest',
|
|
blocks,
|
|
returnFullTransactions,
|
|
traceTransfers,
|
|
validation,
|
|
} = parameters
|
|
|
|
try {
|
|
const blockStateCalls = []
|
|
for (const block of blocks) {
|
|
const blockOverrides = block.blockOverrides
|
|
? BlockOverrides.toRpc(block.blockOverrides)
|
|
: undefined
|
|
const calls = block.calls.map((call_) => {
|
|
const call = call_ as Call<unknown, CallExtraProperties>
|
|
const account = call.account
|
|
? parseAccount(call.account)
|
|
: client.account
|
|
? parseAccount(client.account)
|
|
: undefined
|
|
const data = call.abi ? encodeFunctionData(call) : call.data
|
|
const request = {
|
|
...call,
|
|
account,
|
|
data: call.dataSuffix
|
|
? concat([data || '0x', call.dataSuffix])
|
|
: data,
|
|
from: call.from ?? account?.address,
|
|
} as const
|
|
assertRequest(request)
|
|
return formatTransactionRequest(request)
|
|
})
|
|
const stateOverrides = block.stateOverrides
|
|
? serializeStateOverride(block.stateOverrides)
|
|
: undefined
|
|
|
|
blockStateCalls.push({
|
|
blockOverrides,
|
|
calls,
|
|
stateOverrides,
|
|
})
|
|
}
|
|
|
|
const blockNumberHex =
|
|
typeof blockNumber === 'bigint' ? numberToHex(blockNumber) : undefined
|
|
const block = blockNumberHex || blockTag
|
|
|
|
type tempo_simulateV1 = RpcSchema.ToViem<RpcSchemaTempo.Tempo>[0]
|
|
const result = await client.request<tempo_simulateV1>({
|
|
method: 'tempo_simulateV1',
|
|
params: [
|
|
{
|
|
blockStateCalls,
|
|
returnFullTransactions,
|
|
traceTransfers,
|
|
validation,
|
|
} as tempo_simulateV1['Parameters'][0],
|
|
block,
|
|
],
|
|
})
|
|
|
|
return {
|
|
blocks: result.blocks.map((block, i) => ({
|
|
...formatBlock(block as never),
|
|
calls: block.calls?.map((call, j) => {
|
|
const { abi, args, functionName, to } = blocks[i].calls[j] as Call<
|
|
unknown,
|
|
CallExtraProperties
|
|
>
|
|
|
|
const data = call.error?.data ?? call.returnData
|
|
const gasUsed = BigInt(call.gasUsed)
|
|
const logs = call.logs?.map((log) => formatLog(log))
|
|
const status = call.status === '0x1' ? 'success' : 'failure'
|
|
|
|
const result =
|
|
abi && status === 'success' && data !== '0x'
|
|
? decodeFunctionResult({
|
|
abi,
|
|
data,
|
|
functionName,
|
|
})
|
|
: null
|
|
|
|
const error = (() => {
|
|
if (status === 'success') return undefined
|
|
|
|
let error: Error | undefined
|
|
if (data === '0x') error = new AbiDecodingZeroDataError()
|
|
else if (data) error = new RawContractError({ data })
|
|
|
|
if (!error) return undefined
|
|
return getContractError(error, {
|
|
abi: (abi ?? []) as Abi,
|
|
address: to ?? '0x',
|
|
args,
|
|
functionName: functionName ?? '<unknown>',
|
|
})
|
|
})()
|
|
|
|
return {
|
|
data,
|
|
gasUsed,
|
|
logs,
|
|
status,
|
|
...(status === 'success'
|
|
? {
|
|
result,
|
|
}
|
|
: {
|
|
error,
|
|
}),
|
|
}
|
|
}),
|
|
})),
|
|
tokenMetadata: result.tokenMetadata ?? {},
|
|
} as unknown as simulateBlocks.ReturnType<calls>
|
|
} catch (e) {
|
|
const cause = e as BaseError
|
|
const error = getNodeError(cause, {})
|
|
if (error instanceof UnknownNodeError) throw cause
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export declare namespace simulateBlocks {
|
|
export type Parameters<
|
|
calls extends readonly unknown[] = readonly unknown[],
|
|
> = {
|
|
/** Blocks to simulate. */
|
|
blocks: readonly {
|
|
/** Block overrides. */
|
|
blockOverrides?: BlockOverrides.BlockOverrides | undefined
|
|
/** Calls to execute. */
|
|
calls: Calls<Narrow<calls>, CallExtraProperties>
|
|
/** State overrides. */
|
|
stateOverrides?: StateOverride | undefined
|
|
}[]
|
|
/** Whether to return the full transactions. */
|
|
returnFullTransactions?: boolean | undefined
|
|
/** Whether to trace transfers. */
|
|
traceTransfers?: boolean | undefined
|
|
/** Whether to enable validation mode. */
|
|
validation?: boolean | undefined
|
|
} & (
|
|
| {
|
|
/** The balance of the account at a block number. */
|
|
blockNumber?: bigint | undefined
|
|
blockTag?: undefined
|
|
}
|
|
| {
|
|
blockNumber?: undefined
|
|
/**
|
|
* The balance of the account at a block tag.
|
|
* @default 'latest'
|
|
*/
|
|
blockTag?: BlockTag | undefined
|
|
}
|
|
)
|
|
|
|
export type ReturnType<
|
|
calls extends readonly unknown[] = readonly unknown[],
|
|
> = {
|
|
blocks: readonly (Block & {
|
|
calls: MulticallResults<
|
|
Narrow<calls>,
|
|
true,
|
|
{
|
|
extraProperties: {
|
|
data: Hex
|
|
gasUsed: bigint
|
|
logs?: Log[] | undefined
|
|
}
|
|
error: Error
|
|
mutability: AbiStateMutability
|
|
}
|
|
>
|
|
})[]
|
|
tokenMetadata: TokenMetadata
|
|
}
|
|
|
|
export type ErrorType =
|
|
| AssertRequestErrorType
|
|
| DecodeFunctionResultErrorType
|
|
| EncodeFunctionDataErrorType
|
|
| FormatBlockErrorType
|
|
| FormatTransactionRequestErrorType
|
|
| GetNodeErrorReturnType
|
|
| ParseAccountErrorType
|
|
| SerializeStateOverrideErrorType
|
|
| NumberToHexErrorType
|
|
| ErrorType_
|
|
}
|
|
|
|
/**
|
|
* Simulates execution of a batch of calls via `tempo_simulateV1`.
|
|
*
|
|
* A convenience wrapper around {@link simulateBlocks} that runs all
|
|
* calls in a single block and returns flattened results.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* import { createClient, http, parseUnits } from 'viem'
|
|
* import { tempo } from 'viem/chains'
|
|
* import { Actions, Addresses } from 'viem/tempo'
|
|
*
|
|
* const client = createClient({
|
|
* account: '0x...',
|
|
* chain: tempo,
|
|
* transport: http(),
|
|
* })
|
|
*
|
|
* const { results, tokenMetadata } = await Actions.simulate.simulateCalls(client, {
|
|
* calls: [
|
|
* Actions.token.approve.call({
|
|
* token: '0x20c0...01',
|
|
* spender: Addresses.stablecoinDex,
|
|
* amount: parseUnits('100', 6),
|
|
* }),
|
|
* Actions.dex.buy.call({
|
|
* tokenIn: '0x20c0...01',
|
|
* tokenOut: '0x20c0...02',
|
|
* amountOut: parseUnits('10', 6),
|
|
* maxAmountIn: parseUnits('100', 6),
|
|
* }),
|
|
* Actions.token.transfer.call({
|
|
* token: '0x20c0...02',
|
|
* to: '0x...',
|
|
* amount: parseUnits('10', 6),
|
|
* }),
|
|
* ],
|
|
* })
|
|
* ```
|
|
*
|
|
* @param client - Client.
|
|
* @param parameters - {@link simulateCalls.Parameters}
|
|
* @returns Results, block, and token metadata. {@link simulateCalls.ReturnType}
|
|
*/
|
|
export async function simulateCalls<
|
|
const calls extends readonly unknown[],
|
|
chain extends Chain | undefined,
|
|
account extends Account | Address | undefined = undefined,
|
|
>(
|
|
client: Client<Transport, chain>,
|
|
parameters: simulateCalls.Parameters<calls, account>,
|
|
): Promise<simulateCalls.ReturnType<calls>> {
|
|
const {
|
|
blockNumber,
|
|
blockTag,
|
|
calls,
|
|
stateOverrides,
|
|
traceTransfers,
|
|
validation,
|
|
} = parameters
|
|
|
|
const account = parameters.account
|
|
? parseAccount(parameters.account)
|
|
: undefined
|
|
|
|
const result = await simulateBlocks(client, {
|
|
blockNumber,
|
|
blockTag: blockTag as undefined,
|
|
blocks: [
|
|
{
|
|
calls: calls.map((call) => ({
|
|
...(call as Call),
|
|
from: account?.address,
|
|
})) as any,
|
|
stateOverrides,
|
|
},
|
|
],
|
|
traceTransfers,
|
|
validation,
|
|
})
|
|
|
|
const { calls: block_calls, ...block } = result.blocks[0]
|
|
|
|
return {
|
|
block,
|
|
results: block_calls,
|
|
tokenMetadata: result.tokenMetadata,
|
|
} as unknown as simulateCalls.ReturnType<calls>
|
|
}
|
|
|
|
export declare namespace simulateCalls {
|
|
export type Parameters<
|
|
calls extends readonly unknown[] = readonly unknown[],
|
|
account extends Account | Address | undefined =
|
|
| Account
|
|
| Address
|
|
| undefined,
|
|
> = Omit<simulateBlocks.Parameters, 'blocks' | 'returnFullTransactions'> & {
|
|
/** Account attached to the calls (msg.sender). */
|
|
account?: account | undefined
|
|
/** Calls to simulate. */
|
|
calls: Calls<Narrow<calls>>
|
|
/** State overrides. */
|
|
stateOverrides?: StateOverride | undefined
|
|
}
|
|
|
|
export type ReturnType<
|
|
calls extends readonly unknown[] = readonly unknown[],
|
|
> = {
|
|
/** Block results. */
|
|
block: Block
|
|
/** Call results. */
|
|
results: MulticallResults<
|
|
Narrow<calls>,
|
|
true,
|
|
{
|
|
extraProperties: {
|
|
data: Hex
|
|
gasUsed: bigint
|
|
logs?: Log[] | undefined
|
|
}
|
|
error: Error
|
|
mutability: AbiStateMutability
|
|
}
|
|
>
|
|
/** Token metadata resolved from the simulation. */
|
|
tokenMetadata: TokenMetadata
|
|
}
|
|
|
|
export type ErrorType = simulateBlocks.ErrorType | ErrorType_
|
|
}
|