Skip to content

evs

Write typed EVM read scripts in plain TypeScript. Compile them to bytecode. Run dozens of on-chain reads in a single eth_call — no contracts to deploy, full viem inference end to end.
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 from

One 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.