Files
smom-dbis-138/frontend-dapp/src/utils/transactionSimulator.ts
T

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 '⚠️'
}