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, parameters: simulateBlocks.Parameters, ): Promise> { 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 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[0] const result = await client.request({ 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 ?? '', }) })() return { data, gasUsed, logs, status, ...(status === 'success' ? { result, } : { error, }), } }), })), tokenMetadata: result.tokenMetadata ?? {}, } as unknown as simulateBlocks.ReturnType } 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, 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, 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, parameters: simulateCalls.Parameters, ): Promise> { 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 } export declare namespace simulateCalls { export type Parameters< calls extends readonly unknown[] = readonly unknown[], account extends Account | Address | undefined = | Account | Address | undefined, > = Omit & { /** Account attached to the calls (msg.sender). */ account?: account | undefined /** Calls to simulate. */ calls: Calls> /** State overrides. */ stateOverrides?: StateOverride | undefined } export type ReturnType< calls extends readonly unknown[] = readonly unknown[], > = { /** Block results. */ block: Block /** Call results. */ results: MulticallResults< Narrow, 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_ }