One round trip
Sequential, dependent reads — pool → tokens → metadata — collapse into a single
eth_call. No multicall contracts, no waterfall of requests.
import { function evscript<const name extends string, const args extends ArgsInput = readonly [], ret extends Record<string, ReturnValue> = Record<string, ReturnValue>>(def: { name: name; args?: args;}, body: (s: ScriptBuilder, ...args: ArgHandles<NormalizeArgs<args>>) => ScriptReturn<ret>, opts?: { locations?: boolean;}): EvsScript<name, NormalizeArgs<args>, ret>
evscript, const t: TypeNamespace
t, function compile<s extends { readonly name: string; readonly ir: ScriptIr; readonly abi: readonly unknown[];}>(script: s, options?: CompileOptions): CompiledOf<s>
compile } from '@maxencerb/evs';import { const erc20Abi: readonly [{ readonly type: "event"; readonly name: "Approval"; readonly inputs: readonly [{ readonly indexed: true; readonly name: "owner"; readonly type: "address"; }, { readonly indexed: true; readonly name: "spender"; readonly type: "address"; }, { readonly indexed: false; readonly name: "value"; readonly type: "uint256"; }];}, { readonly type: "event"; readonly name: "Transfer"; readonly inputs: readonly [{ readonly indexed: true; readonly name: "from"; readonly type: "address"; }, { readonly indexed: true; readonly name: "to"; readonly type: "address"; }, { readonly indexed: false; readonly name: "value"; readonly type: "uint256"; }];}, ... 8 more ..., { ...;}]
erc20Abi } from 'viem';// Two DEPENDENT reads, one eth_call: the second call's target IS the first// call's result — a chain multicall can't batch (it needs every address up front).const const poolToken0: EvsScript<"poolToken0", readonly [ArgSpec<"", "address">], { readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}>
poolToken0 = evscript<"poolToken0", readonly ["address"], { readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}>(def: { name: "poolToken0"; args?: readonly ["address"] | undefined;}, body: (s: ScriptBuilder, arg0: Expr<"address">) => ScriptReturn<{ readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}>, opts?: { locations?: boolean;}): EvsScript<"poolToken0", readonly [ArgSpec<"", "address">], { readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}>
evscript({ name: "poolToken0"
name: 'poolToken0', args?: readonly ["address"] | undefined
args: [const t: TypeNamespace
t.address: "address"
address] }, (s: ScriptBuilder
s, pool: Expr<"address">
pool) => { const token0 = s: ScriptBuilder
s.ScriptBuilder.read: SubcallVerb<readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}], "token0">(p: SubcallParams<readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}], "token0", ViewMutability> & { readonly struct?: false;}) => Expr<"address"> (+2 overloads)
read({ SubcallParams<readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }]; }], "token0", ViewMutability>.address: IntoExpr<"address">
address: pool: Expr<"address">
pool, abi: readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}]
abi: const poolAbi: readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}]
poolAbi, SubcallParams<readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }]; }], "token0", ViewMutability>.functionName: "token0"
functionName: 'token0' });const token0: Expr<"address">
const const symbol: Expr<"string">
symbol = s: ScriptBuilder
s.ScriptBuilder.read: SubcallVerb<readonly [{ readonly type: "event"; readonly name: "Approval"; readonly inputs: readonly [{ readonly indexed: true; readonly name: "owner"; readonly type: "address"; }, { readonly indexed: true; readonly name: "spender"; readonly type: "address"; }, { readonly indexed: false; readonly name: "value"; readonly type: "uint256"; }];}, { readonly type: "event"; readonly name: "Transfer"; readonly inputs: readonly [{ readonly indexed: true; readonly name: "from"; readonly type: "address"; }, { readonly indexed: true; readonly name: "to"; readonly type: "address"; }, { readonly indexed: false; readonly name: "value"; readonly type: "uint256"; }];}, ... 8 more ..., { ...;}], "symbol">(p: SubcallParams<...> & { ...;}) => Expr<...> (+2 overloads)
read({ SubcallParams<readonly [{ readonly type: "event"; readonly name: "Approval"; readonly inputs: readonly [{ readonly indexed: true; readonly name: "owner"; readonly type: "address"; }, { readonly indexed: true; readonly name: "spender"; readonly type: "address"; }, { readonly indexed: false; readonly name: "value"; readonly type: "uint256"; }]; }, ... 9 more ..., { ...; }], "symbol", ViewMutability>.address: IntoExpr<"address">
address: const token0: Expr<"address">
token0, abi: readonly [{ readonly type: "event"; readonly name: "Approval"; readonly inputs: readonly [{ readonly indexed: true; readonly name: "owner"; readonly type: "address"; }, { readonly indexed: true; readonly name: "spender"; readonly type: "address"; }, { readonly indexed: false; readonly name: "value"; readonly type: "uint256"; }];}, { readonly type: "event"; readonly name: "Transfer"; readonly inputs: readonly [{ readonly indexed: true; readonly name: "from"; readonly type: "address"; }, { readonly indexed: true; readonly name: "to"; readonly type: "address"; }, { readonly indexed: false; readonly name: "value"; readonly type: "uint256"; }];}, ... 8 more ..., { ...;}]
abi: const erc20Abi: readonly [{ readonly type: "event"; readonly name: "Approval"; readonly inputs: readonly [{ readonly indexed: true; readonly name: "owner"; readonly type: "address"; }, { readonly indexed: true; readonly name: "spender"; readonly type: "address"; }, { readonly indexed: false; readonly name: "value"; readonly type: "uint256"; }];}, { readonly type: "event"; readonly name: "Transfer"; readonly inputs: readonly [{ readonly indexed: true; readonly name: "from"; readonly type: "address"; }, { readonly indexed: true; readonly name: "to"; readonly type: "address"; }, { readonly indexed: false; readonly name: "value"; readonly type: "uint256"; }];}, ... 8 more ..., { ...;}]
erc20Abi, SubcallParams<readonly [{ readonly type: "event"; readonly name: "Approval"; readonly inputs: readonly [{ readonly indexed: true; readonly name: "owner"; readonly type: "address"; }, { readonly indexed: true; readonly name: "spender"; readonly type: "address"; }, { readonly indexed: false; readonly name: "value"; readonly type: "uint256"; }]; }, ... 9 more ..., { ...; }], "symbol", ViewMutability>.functionName: "symbol" | "name" | "allowance" | "balanceOf" | "decimals" | "totalSupply"
functionName: 'symbol' }); return s: ScriptBuilder
s.ScriptBuilder.return<{ readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}>(values: { readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}): ScriptReturn<{ readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}>
return({ token0: Expr<"address">
token0, symbol: Expr<"string">
symbol });});
const const script: CompiledEvsScript<"poolToken0", readonly [ArgSpec<"", "address">], { readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}>
script = compile<EvsScript<"poolToken0", readonly [ArgSpec<"", "address">], { readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}>>(script: EvsScript<"poolToken0", readonly [ArgSpec<"", "address">], { readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}>, options?: CompileOptions): CompiledEvsScript<"poolToken0", readonly [ArgSpec<"", "address">], { readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}>
compile(const poolToken0: EvsScript<"poolToken0", readonly [ArgSpec<"", "address">], { readonly token0: Expr<"address">; readonly symbol: Expr<"string">;}>
poolToken0);// → runtime bytecode + a literal-typed ABI viem infers your args & result fromOne round trip
Sequential, dependent reads — pool → tokens → metadata — collapse into a single
eth_call. No multicall contracts, no waterfall of requests.
Typed end to end
Calls are typed like viem’s readContract. The compiled artifact carries an as const
ABI, so readContract infers your script’s argument and return types exactly.
Nothing to deploy
Scripts execute deploylessly via the eth_call code parameter or a state override.
The chain never sees a deployment; any standard RPC node works.
Solidity-grade semantics
Checked arithmetic with solc 0.8 panic codes, verbatim revert bubbling, three calling verbs
(s.read view reads, s.call for CALL-frame functions like a Uniswap quoter, s.simulate
to dry-run a write), and a differential test suite that pins the bytecode against a reference
interpreter, viem’s codecs, and real solc output.