This page is the signature-level reference for the script entry point and the compiler entry
point. For the narrative introduction, see writing scripts; for the
compiled artifact returned by compile, see the artifact reference.
evscript records a script: it runs body exactly once, at recording time, against a
ScriptBuilder and captures every builder call into a frozen IR. The
callback must return the value produced by s.return(...) — returning anything else (or not
calling s.return at all) throws EvsTypeError.
Args are declared as t.* types or namedArg(name, type) wrappers — a
single declarator or a readonly list — and arrive as positional parameters after s. A scalar
arg arrives as an Expr, a TupleType arg as a Tuple handle. args
is optional; a zero-arg script omits it. A lone declarator is sugar for a one-element list
(args: t.uint256 ≡ args: [t.uint256]). A namedArg labels the arg in the viem args tuple; a
bare arg keeps the positional arg0/arg1/… name. The const type parameters mean inline args
need no as const.
import{evscript,t}from'@maxencerb/evs';
const double=evscript(
{ name: 'double', args: [t.uint256] },
(s,x)=>s.return({ doubled: x.mul(2n) }),
);
const compiled=double.compile();// sugar for compile(double)
The relevant type helpers (all exported):
Type
Meaning
ArgInput
EvsType | ArgSpec — one declarator: a bare type or a namedArg.
ArgsInput
ArgInput | readonly ArgInput[] — the accepted args input.
NormalizeArgs<a>
Normalizes to readonly ArgSpec[] (a lone declarator → a one-element list).
ArgHandle<t>
t extends TupleType ? Tuple<t> : Expr<t> — one arg’s body handle.
ArgHandles<specs>
The positional handle tuple spread into the body after s, labeled by arg name.
Recording-time validation (each violation throws EvsTypeError with the call-site location):
def.name must be a non-empty identifier (/^[A-Za-z_]\w*$/).
def.args, when present, must be a single declarator (a bare EvsType or a namedArg) or a
readonly list of them. A namedArg labels its input in the generated ABI; a bare arg is
auto-named arg{i} (the names are positional labels; viem infers args positionally regardless).
Duplicate arg names are rejected (ABI_SHAPE).
Every arg type must be a valid EvsType — a word/dynamic type, an array (a scalar array, a
one-level nested array T[][], or a tuple[]), or a TupleType (struct/tuple). Shapes
outside the supported set — tuple[][], deeper-than-[][] nesting, fixed T[N] — throw with
code UNSUPPORTED_V0.
body must be a function.
Everything the builder itself can throw during recording is catalogued in
diagnostics.
opts.locations (default true) controls source-location capture during recording. When
enabled, every recorded statement stores a SourceLoc parsed from a stack trace, which powers
error messages, sourceMap, explainRevert, and diagnostics. Pass { locations: false } to
skip the per-statement stack capture (faster recording; locations in errors and the source map
become null):
import{evscript,t}from'@maxencerb/evs';
const fast=evscript(
{ name: 'fast', args: [t.uint256] },
(s,x)=>s.return({ x }),
{ locations: false },
);
compile has its own independent locations option (below) for the emitted source map.
compile(options?: CompileOptions):CompiledEvsScript<name,args,ret>;// sugar for compile()
}
name — the literal script name; becomes the ABI function name.
args — the normalized readonly ArgSpec[] of argument specs (the type param, not a runtime
field): each { name, type } carries the surfaced arg name (a namedArg name, or the arg{i}
fallback for a bare arg) and its type.
ir — the recorded program. Serialize it with serializeIr or run it with the interpret
oracle without compiling; see testing scripts.
abi — the literal-typed ScriptAbi (one view function plus the two evs error entries).
It exists before you compile, so viem type inference works without any artifact. See
the artifact reference for its shape.
compile(options?) — identical to the free-standing compile(script, options).
namedArg names one top-level arg/param so the name surfaces in the resulting type — usable
consistently in both a script’s args and an s.fn’s params. Anywhere a bare t.* type is
accepted, a namedArg(...) is too. A named script arg surfaces as a labeled element in the viem
args tuple ([token: 0x…] instead of [arg0: 0x…]); a named s.fn param surfaces as a
labeled callback parameter ((token) => …). A bare (unnamed) arg keeps the positional
arg0/arg1/… fallback name. namedArg validates at the call site and returns a frozen object:
name must match /^[A-Za-z_]\w*$/; otherwise EvsTypeError with the call-site location.
type is any EvsType — a word type, string, bytes, an array, or a composite
t.struct/t.tuple (a named struct arg arrives as a Tuple handle, exactly like a bare
one). Only top-level args are named as a whole; nested composite fields are named via
t.struct. Deferred Solidity shapes (fixed-size T[N] arrays) throw EvsTypeError with
code UNSUPPORTED_V0; malformed inputs throw TYPE_MISMATCH. Note s.fn params remain
word/string-typed — a composite param is rejected when the fn is defined (see
functions).
Names a top-level arg/param so the name surfaces in the resulting type (issue #9): in a
script's args, the viem args tuple element is labeled ([token: …]); in an s.fn's params,
the callback parameter is labeled ((token) => …). The type bound is
EvsType
— the
full parameter-type vocabulary (widened by #25 from StringType): words, string/bytes,
arrays, and composite t.struct/t.tuple descriptors (a named struct arg arrives as a Tuple
handle, exactly like a bare one). Nested composite fields are named via t.struct and keep
their behaviour; s.fn composite params remain a v0 deferral, rejected at record time. A bare
(unnamed) top-level arg keeps the positional arg{i} fallback name.
Names a top-level arg/param so the name surfaces in the resulting type (issue #9): in a
script's args, the viem args tuple element is labeled ([token: …]); in an s.fn's params,
the callback parameter is labeled ((token) => …). The type bound is
EvsType
— the
full parameter-type vocabulary (widened by #25 from StringType): words, string/bytes,
arrays, and composite t.struct/t.tuple descriptors (a named struct arg arrives as a Tuple
handle, exactly like a bare one). Nested composite fields are named via t.struct and keep
their behaviour; s.fn composite params remain a v0 deferral, rejected at record time. A bare
(unnamed) top-level arg keeps the positional arg{i} fallback name.
Names a top-level arg/param so the name surfaces in the resulting type (issue #9): in a
script's args, the viem args tuple element is labeled ([token: …]); in an s.fn's params,
the callback parameter is labeled ((token) => …). The type bound is
EvsType
— the
full parameter-type vocabulary (widened by #25 from StringType): words, string/bytes,
arrays, and composite t.struct/t.tuple descriptors (a named struct arg arrives as a Tuple
handle, exactly like a bare one). Nested composite fields are named via t.struct and keep
their behaviour; s.fn composite params remain a v0 deferral, rejected at record time. A bare
(unnamed) top-level arg keeps the positional arg{i} fallback name.
Names a top-level arg/param so the name surfaces in the resulting type (issue #9): in a
script's args, the viem args tuple element is labeled ([token: …]); in an s.fn's params,
the callback parameter is labeled ((token) => …). The type bound is
EvsType
— the
full parameter-type vocabulary (widened by #25 from StringType): words, string/bytes,
arrays, and composite t.struct/t.tuple descriptors (a named struct arg arrives as a Tuple
handle, exactly like a bare one). Nested composite fields are named via t.struct and keep
their behaviour; s.fn composite params remain a v0 deferral, rejected at record time. A bare
(unnamed) top-level arg keeps the positional arg{i} fallback name.
namedArg('amount','uint128');// raw type strings work everywhere t.* does
// composite types can be named too — the callback receives a Tuple handle
Names a top-level arg/param so the name surfaces in the resulting type (issue #9): in a
script's args, the viem args tuple element is labeled ([token: …]); in an s.fn's params,
the callback parameter is labeled ((token) => …). The type bound is
EvsType
— the
full parameter-type vocabulary (widened by #25 from StringType): words, string/bytes,
arrays, and composite t.struct/t.tuple descriptors (a named struct arg arrives as a Tuple
handle, exactly like a bare one). Nested composite fields are named via t.struct and keep
their behaviour; s.fn composite params remain a v0 deferral, rejected at record time. A bare
(unnamed) top-level arg keeps the positional arg{i} fallback name.
Used in a script’s args, the name flows into the ABI input and surfaces as the viem args label —
hover the inferred Args type below (a bare arg would read arg0/arg1 instead):
Names a top-level arg/param so the name surfaces in the resulting type (issue #9): in a
script's args, the viem args tuple element is labeled ([token: …]); in an s.fn's params,
the callback parameter is labeled ((token) => …). The type bound is
EvsType
— the
full parameter-type vocabulary (widened by #25 from StringType): words, string/bytes,
arrays, and composite t.struct/t.tuple descriptors (a named struct arg arrives as a Tuple
handle, exactly like a bare one). Nested composite fields are named via t.struct and keep
their behaviour; s.fn composite params remain a v0 deferral, rejected at record time. A bare
(unnamed) top-level arg keeps the positional arg{i} fallback name.
Names a top-level arg/param so the name surfaces in the resulting type (issue #9): in a
script's args, the viem args tuple element is labeled ([token: …]); in an s.fn's params,
the callback parameter is labeled ((token) => …). The type bound is
EvsType
— the
full parameter-type vocabulary (widened by #25 from StringType): words, string/bytes,
arrays, and composite t.struct/t.tuple descriptors (a named struct arg arrives as a Tuple
handle, exactly like a bare one). Nested composite fields are named via t.struct and keep
their behaviour; s.fn composite params remain a v0 deferral, rejected at record time. A bare
(unnamed) top-level arg keeps the positional arg{i} fallback name.
Names a top-level arg/param so the name surfaces in the resulting type (issue #9): in a
script's args, the viem args tuple element is labeled ([token: …]); in an s.fn's params,
the callback parameter is labeled ((token) => …). The type bound is
EvsType
— the
full parameter-type vocabulary (widened by #25 from StringType): words, string/bytes,
arrays, and composite t.struct/t.tuple descriptors (a named struct arg arrives as a Tuple
handle, exactly like a bare one). Nested composite fields are named via t.struct and keep
their behaviour; s.fn composite params remain a v0 deferral, rejected at record time. A bare
(unnamed) top-level arg keeps the positional arg{i} fallback name.
namedArg('holder',
const t: TypeNamespace
t.
address:"address"
address)] },
(
s:ScriptBuilder
s,
token:Expr<"address">
token,
holder:Expr<"address">
holder)=>
s:ScriptBuilder
s.
ScriptBuilder.return<{
readonlytoken: Expr<"address">;
readonlyholder: Expr<"address">;
}>(values: {
readonlytoken: Expr<"address">;
readonlyholder: Expr<"address">;
}):ScriptReturn<{
readonlytoken: Expr<"address">;
readonlyholder: Expr<"address">;
}>
return({
token:Expr<"address">
token,
holder:Expr<"address">
holder }),// body params are labeled `token`, `holder`
);
// viem labels the call args with your names — `token`/`holder`, not `arg0`/`arg1`:
For script arguments, declaration order is still load-bearing: the args list order is the
type-level order, the runtime encode order, and the ABI inputs order — call sites stay
viem-native positional (args: [pool, fee]). The full type vocabulary lives in
the types reference.
compile turns a recorded script into a CompiledEvsScript. The
pipeline: validate the IR, lower to assembler nodes, run the peephole hook, assemble with the
mandatory verifiers, enforce the EIP-170 size cap, and merge site information into the source
map. Stage-by-stage detail is in how it works.
The constraint is structural ({ name, ir, abi }) rather than s extends EvsScript — a
deliberate, recorded deviation so that every concrete script is assignable; the result type is
exactly CompiledEvsScript with the script’s literal type parameters preserved.
Failure modes:
EvsCompileError with code EVM_VERSION for an unknown evmVersion string.
EvsCompileError with code COMPILE_LIMIT when the runtime bytecode exceeds the EIP-170
limit of 24,576 bytes; the message includes a per-region size breakdown (dispatcher, body,
fns, tails, data segments).
EvsTypeError when the value passed is not a script object.
Target opcode set ('paris', 'shanghai', or 'cancun'). Anything else throws EvsCompileError (EVM_VERSION). See EVM targets.
peephole
identity
Optimizer seam: transforms the assembler node stream before layout. No optimizer ships in v0. The mandatory verifiers run on the hook’s output.
onDiagnostic
no-op
Receives every compile-time warning (LOOP_ALLOCATION, LARGE_FRAME, ENV_FRAME_DEPENDENT). evs never logs; without a callback, diagnostics are silently dropped.
locations
true
Whether source locations are carried into the emitted source map.
The compiled artifact exposes the fully resolved options as
options: Readonly<Required<CompileOptions>> — defaults filled in. Diagnostic codes and their
meaning are documented in diagnostics.