176 lines
4.2 KiB
TypeScript
176 lines
4.2 KiB
TypeScript
/**
|
|
* Transaction Simulator - Enhanced transaction simulation utilities
|
|
*/
|
|
|
|
import type { Address } from 'viem'
|
|
import { formatEther } from 'viem'
|
|
import type { Abi, AbiFunction, Hex, PublicClient } from 'viem'
|
|
|
|
export interface SimulationResult {
|
|
success: boolean
|
|
gasEstimate: bigint
|
|
returnValue?: unknown
|
|
error?: string
|
|
traces?: unknown[]
|
|
logs?: unknown[]
|
|
}
|
|
|
|
export interface SimulatedCall {
|
|
to: Address
|
|
value: bigint
|
|
data: `0x${string}`
|
|
gas?: bigint
|
|
gasPrice?: bigint
|
|
}
|
|
|
|
/**
|
|
* Simulate a contract call
|
|
*/
|
|
export async function simulateCall(
|
|
publicClient: Pick<PublicClient, 'call'>,
|
|
call: SimulatedCall
|
|
): Promise<SimulationResult> {
|
|
try {
|
|
// Use publicClient.call for simulation
|
|
const result = await publicClient.call({
|
|
...call,
|
|
account: call.to, // Use target as account for simulation
|
|
})
|
|
|
|
return {
|
|
success: true,
|
|
gasEstimate: 21000n,
|
|
returnValue: result.data,
|
|
}
|
|
} catch (error: unknown) {
|
|
return {
|
|
success: false,
|
|
gasEstimate: 0n,
|
|
error: error instanceof Error ? error.message : 'Simulation failed',
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simulate contract function call
|
|
*/
|
|
export async function simulateFunctionCall(
|
|
publicClient: Pick<PublicClient, 'call' | 'estimateGas'>,
|
|
contractAddress: Address,
|
|
abi: Abi,
|
|
functionName: string,
|
|
args: unknown[],
|
|
from?: Address
|
|
): Promise<SimulationResult> {
|
|
try {
|
|
// Encode function call
|
|
const { encodeFunctionData } = await import('viem')
|
|
const data = encodeFunctionData({
|
|
abi,
|
|
functionName: functionName as never,
|
|
args,
|
|
})
|
|
|
|
// Estimate gas
|
|
const gasEstimate = await publicClient.estimateGas({
|
|
to: contractAddress,
|
|
data,
|
|
account: from || contractAddress,
|
|
})
|
|
|
|
// Simulate call
|
|
const result = await publicClient.call({
|
|
to: contractAddress,
|
|
data,
|
|
account: from || contractAddress,
|
|
})
|
|
|
|
// Decode return value if possible
|
|
let returnValue: unknown = null
|
|
try {
|
|
const { decodeFunctionResult } = await import('viem')
|
|
const functionAbi = abi.find((item): item is AbiFunction => item.type === 'function' && item.name === functionName)
|
|
if (functionAbi && functionAbi.outputs && result.data && result.data !== '0x') {
|
|
returnValue = decodeFunctionResult({
|
|
abi,
|
|
functionName: functionAbi.name as never,
|
|
data: result.data,
|
|
})
|
|
}
|
|
} catch {
|
|
// Decoding failed, keep raw data
|
|
returnValue = result.data
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
gasEstimate,
|
|
returnValue,
|
|
}
|
|
} catch (error: unknown) {
|
|
// Try to extract revert reason
|
|
const errorRecord = error as { data?: unknown; cause?: { data?: unknown }; message?: string }
|
|
let errorMessage = errorRecord.message || 'Simulation failed'
|
|
if (errorRecord.data || errorRecord.cause?.data) {
|
|
try {
|
|
const { decodeErrorResult } = await import('viem')
|
|
const errorData = (errorRecord.data || errorRecord.cause?.data) as Hex
|
|
const decoded = decodeErrorResult({
|
|
abi,
|
|
data: errorData,
|
|
})
|
|
errorMessage = decoded.errorName || errorMessage
|
|
} catch {
|
|
// Decoding failed
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: false,
|
|
gasEstimate: 0n,
|
|
error: errorMessage,
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simulate batch of calls
|
|
*/
|
|
export async function simulateBatch(
|
|
publicClient: Pick<PublicClient, 'call'>,
|
|
calls: SimulatedCall[]
|
|
): Promise<SimulationResult[]> {
|
|
const results: SimulationResult[] = []
|
|
|
|
for (const call of calls) {
|
|
const result = await simulateCall(publicClient, call)
|
|
results.push(result)
|
|
|
|
// If one fails, we might want to stop or continue
|
|
// For now, continue with all calls
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
/**
|
|
* Estimate gas cost in ETH
|
|
*/
|
|
export function estimateGasCost(
|
|
gasEstimate: bigint,
|
|
gasPrice: bigint
|
|
): string {
|
|
const cost = gasEstimate * gasPrice
|
|
return formatEther(cost)
|
|
}
|
|
|
|
/**
|
|
* Get simulation status emoji
|
|
*/
|
|
export function getSimulationStatusEmoji(result: SimulationResult): string {
|
|
if (result.success) return '✅'
|
|
if (result.error?.includes('revert')) return '❌'
|
|
if (result.error?.includes('gas')) return '⛽'
|
|
return '⚠️'
|
|
}
|