/** * Typed client for the lucidly-simulator service. Mirrors the v1 + v2 HTTP API exactly. * * The simulator runs Trail leaf calls (and multi-leaf plans) against anvil forks of the supported * chains — Ethereum (1), Base (8453), Arbitrum (42161), Optimism (10) — and returns receipts, * decoded revert reasons, and pre/post balance diffs. It never broadcasts to real chain: no * signing key is configured on the service. * * Typical usage: * * const client = new LucidlyClient({ chain: "base", rpcUrl: "...", simulatorUrl: "https://..." }); * const r = await client.simulator!.simulatePlan({ * chainId: 8453, * vault: "0x...", * plan: { steps: [ * { kind: "leaf", label: "approve USDC", call: { target: "0x...", calldata: "0x...", value: "0x0" } }, * { kind: "leaf", label: "supply on Aave", call: { target: "0x...", calldata: "0x...", value: "0x0" } }, * ] }, * watchTokens: [USDC, aUSDC], * }); * if (!r.success) console.warn("step", r.failedStepIndex, "reverted:", r.stepResults[r.failedStepIndex]?.revertReason); * * Wire types are mirrored from lucidly-simulator/src/types.ts. They're duplicated here intentionally * — re-importing across packages would couple the simulator's deploy to the SDK's publish cadence. * Schema changes that aren't backward-compatible bump the apiVersion ("1" → "2" → …). */ export type SimulatorChainId = 1 | 8453 | 42161 | 10; export interface SimLeafCall { /** Target contract address (40-char hex). */ target: `0x${string}`; /** Hex-encoded calldata. The simulator does NOT encode for you — encode upstream. */ calldata: `0x${string}`; /** Optional msg.value in wei, hex-encoded. Default '0x0'. */ value?: `0x${string}`; } export interface SimPlanStep { /** v2 currently supports only "leaf". Automation + api kinds land in v2.1. */ kind: "leaf"; /** Optional human label — flows into the response stepResults[i].label. */ label?: string; call: SimLeafCall; } export interface SimulatedReceipt { transactionHash: string; status: "success" | "reverted"; /** uint256 as decimal string. */ gasUsed: string; blockNumber: number; logs: Array<{ address: string; topics: string[]; data: string; }>; } export interface SimBalanceDelta { account: string; /** "native" for ETH, otherwise the token address. */ asset: "native" | string; decimals: number | null; symbol: string | null; /** uint256 as decimal string. */ before: string; after: string; /** Signed delta as decimal string. Negative means the account sent / had its balance reduced. */ delta: string; } export interface SimulationTiming { totalMs: number; snapshotMs: number; executeMs: number; diffMs: number; revertMs: number; } export interface SimulateRequestV1 { chainId: SimulatorChainId; /** Vault address — the simulator impersonates this on the fork and funds it with 100 ETH. */ vault: `0x${string}`; call: SimLeafCall; /** ERC20 token addresses to snapshot pre/post. ETH/native is always snapshotted. */ watchTokens?: `0x${string}`[]; /** Extra accounts to snapshot beyond the vault. */ watchAccounts?: `0x${string}`[]; /** Caller-supplied correlation id (audit-only; does not affect simulation). */ requestId?: string; } export interface SimulateResponseV1 { apiVersion: "1"; requestId: string | null; chainId: SimulatorChainId; forkBlock: number; success: boolean; receipt: SimulatedReceipt | null; revertReason: string | null; balanceChanges: SimBalanceDelta[]; timing: SimulationTiming; warnings: string[]; } export interface SimulateRequestV2 { chainId: SimulatorChainId; vault: `0x${string}`; plan: { steps: SimPlanStep[]; }; watchTokens?: `0x${string}`[]; watchAccounts?: `0x${string}`[]; requestId?: string; } export interface SimulateResponseV2 { apiVersion: "2"; requestId: string | null; chainId: SimulatorChainId; forkBlock: number; /** True iff every step succeeded. If any step reverted, false and execution stopped at that step. */ success: boolean; /** Per-step result. Length ≤ steps.length — entries after a failing step are NOT present. */ stepResults: Array<{ stepIndex: number; kind: "leaf"; label: string | null; ok: boolean; receipt: SimulatedReceipt | null; revertReason: string | null; balanceChanges: SimBalanceDelta[]; timingMs: number; }>; /** Net deltas across the entire plan (after-last-step vs. before-first-step). */ balanceChanges: SimBalanceDelta[]; /** Index of the failing step, or -1 on success. */ failedStepIndex: number; timing: SimulationTiming; warnings: string[]; } export interface SimulatorStatus { chains: Array<{ chainId: SimulatorChainId; ready: boolean; forkBlock: number | null; /** Anvil uptime in seconds. */ uptime: number | null; rpcUrl: string | null; }>; ts: string; } /** Thrown when the simulator returns a non-2xx response or when the request fails to reach it. */ export declare class SimulatorError extends Error { readonly code: string; readonly status?: number | undefined; readonly requestId?: string | undefined; readonly issues?: unknown[] | undefined; constructor(code: string, message: string, status?: number | undefined, requestId?: string | undefined, issues?: unknown[] | undefined); } export interface SimulatorClientOptions { /** Base URL of the simulator service, e.g. "https://simulator.rdv8.xyz" or "http://localhost:42071". */ url: string; /** Optional fetch implementation. Defaults to globalThis.fetch. Pass a custom one for retries/logging. */ fetch?: typeof fetch; /** Optional request timeout in ms. Default 30s — sims usually return in <1s but include headroom for cold anvil starts. */ timeoutMs?: number; } export declare class SimulatorClient { private readonly url; private readonly fetchImpl; private readonly timeoutMs; constructor(opts: SimulatorClientOptions); /** GET /health — returns true if the simulator HTTP service is up (anvils may still be warming). */ health(): Promise; /** GET /status — per-chain anvil state. Useful for "is chain X ready to sim against?" checks. */ status(): Promise; /** * v1 — simulate a single leaf call. Convenience wrapper around `simulatePlan` for the common * "I just want to preview one call" case. Returns the v1 response shape (single receipt, single * revert reason, overall balance changes). */ simulate(req: SimulateRequestV1): Promise; /** * v2 — simulate a multi-step plan. Steps execute sequentially under ONE shared anvil snapshot * (so step N sees the state left by step N-1, just like a real multicall). Aborts on first revert. */ simulatePlan(req: SimulateRequestV2): Promise; private post; private fetchWithTimeout; }