Quick start
You will build a real batch reader: given a list of Uniswap V3 pool addresses and a user
address, fetch each pool’s fee, its slot0 (price/tick), and for both of its tokens
the symbol, decimals, and the user’s balance — dozens of reads, several of them dependent
(a token read’s target is the output of a pool read), all in a single eth_call that
returns one fully-typed array. You need
@maxencerb/evs and viem installed and an RPC URL.
The script
Section titled “The script”The pool ABI is elided below for readability (hover any identifier to see its inferred type — this page uses Twoslash). Everything else is the whole script:
import { function namedArg<const name extends string, const type extends EvsType>(name: name, type: type): ArgSpec<name, type>
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, 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 } 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';// Derive the slot0 struct type straight from the ABI — no re-typing its 7 fields.const Slot0 = const t: TypeNamespace
t.fromOutputs<readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}], "slot0">(abi: readonly [...], name: "slot0"): { ...;}
fromOutputs(const uniswapV3PoolAbi: readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}]
uniswapV3PoolAbi, 'slot0');const Slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { readonly name: "observationCardinality"; readonly type: "uint16"; }, { readonly name: "observationCardinalityNext"; readonly type: "uint16"; }, { readonly name: "feeProtocol"; readonly type: "uint8"; }, { readonly name: "unlocked"; readonly type: "bool"; }];}
// Compose the output shape from structs; a struct member can itself be a struct.const const TokenMetadata: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>
TokenMetadata = const t: TypeNamespace
t.struct<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>(spec: { readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}): StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>
struct({ address: "address"
address: const t: TypeNamespace
t.address: "address"
address, symbol: "string"
symbol: const t: TypeNamespace
t.string: "string"
string, decimals: "uint8"
decimals: const t: TypeNamespace
t.uint8: "uint8"
uint8, userBalance: "uint256"
userBalance: const t: TypeNamespace
t.uint256: "uint256"
uint256,});const const PoolMetadata: StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { readonly name: "observationCardinality"; readonly type: "uint16"; }, { readonly name: "observationCardinalityNext"; readonly type: "uint16"; }, { readonly name: "feeProtocol"; readonly type: "uint8"; }, { readonly name: "unlocked"; readonly type: "bool"; }]; };}>
PoolMetadata = const t: TypeNamespace
t.struct<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { ...; }, { ...; }, { ...; }, { ...; }]; };}>(spec: { readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { ...; }, { ...; }, { ...; }, { ...; }]; };}): StructTypeOf<...>
struct({ token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>
token0: const TokenMetadata: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>
TokenMetadata, token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>
token1: const TokenMetadata: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>
TokenMetadata, fee: "uint24"
fee: const t: TypeNamespace
t.uint24: "uint24"
uint24, slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { readonly name: "observationCardinality"; readonly type: "uint16"; }, { readonly name: "observationCardinalityNext"; readonly type: "uint16"; }, { readonly name: "feeProtocol"; readonly type: "uint8"; }, { readonly name: "unlocked"; readonly type: "bool"; }];}
slot0: const Slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { readonly name: "observationCardinality"; readonly type: "uint16"; }, { readonly name: "observationCardinalityNext"; readonly type: "uint16"; }, { readonly name: "feeProtocol"; readonly type: "uint8"; }, { readonly name: "unlocked"; readonly type: "bool"; }];}
Slot0,});
export const const poolsData: EvsScript<"poolsData", readonly [ArgSpec<"", "address[]">, ArgSpec<"", "address">], { readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { readonly name: "observationCardinality"; readonly type: "uint16"; }, { readonly name: "observationCardinalityNext"; readonly type: "uint16"; }, { readonly name: "feeProtocol"; readonly type: "uint8"; }, { readonly name: "unlocked"; readonly type: "bool"; }]; }; }>>;}>
poolsData = evscript<"poolsData", readonly ["address[]", "address"], { readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, ... 4 more ..., { ...; }]; }; }>>;}>(def: { ...;}, body: (s: ScriptBuilder, arg0: Expr<...>, arg1: Expr<...>) => ScriptReturn<...>, opts?: { locations?: boolean;}): EvsScript<...>
evscript( { name: "poolsData"
name: 'poolsData', args?: readonly ["address[]", "address"] | undefined
args: [const t: TypeNamespace
t.array<"address">(elem: "address"): "address[]" (+1 overload)
array(const t: TypeNamespace
t.address: "address"
address), const t: TypeNamespace
t.address: "address"
address] }, (s: ScriptBuilder
s, pools: Expr<"address[]">
pools, user: Expr<"address">
user) => { // A reusable, typed subroutine: one token's metadata. `namedArg(name, type)` names its params. const const tokenMetadata: EvsFn<readonly [ArgSpec<"token", "address">, ArgSpec<"owner", "address">], Tuple<StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>>, readonly [token: bigint, owner: bigint]>
tokenMetadata = s: ScriptBuilder
s.ScriptBuilder.fn<readonly [ArgSpec<"token", "address">, ArgSpec<"owner", "address">], Tuple<StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>>>(name: string, params: readonly [ArgSpec<"token", "address">, ArgSpec<"owner", "address">], body: (token: Expr<"address">, owner: Expr<"address">) => Tuple<StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>>): EvsFn<...>
fn( 'tokenMetadata', [namedArg<"token", "address">(name: "token", type: "address"): ArgSpec<"token", "address">
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('token', const t: TypeNamespace
t.address: "address"
address), namedArg<"owner", "address">(name: "owner", type: "address"): ArgSpec<"owner", "address">
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('owner', const t: TypeNamespace
t.address: "address"
address)] as type const = readonly [ArgSpec<"token", "address">, ArgSpec<"owner", "address">]
const, (token: Expr<"address">
token, owner: Expr<"address">
owner) => s: ScriptBuilder
s.ScriptBuilder.tuple<StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>>(type: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>, init?: { readonly symbol?: IntoExpr<"string"> | undefined; readonly address?: IntoExpr<"address"> | undefined; readonly decimals?: IntoExpr<"uint8"> | undefined; readonly userBalance?: IntoExpr<"uint256"> | undefined;} | undefined): Tuple<...>
tuple(const TokenMetadata: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>
TokenMetadata, { address?: IntoExpr<"address"> | undefined
address: token: Expr<"address">
token, symbol?: IntoExpr<"string"> | undefined
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: token: Expr<"address">
token, 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" | "decimals" | "allowance" | "balanceOf" | "totalSupply"
functionName: 'symbol' }), decimals?: IntoExpr<"uint8"> | undefined
decimals: 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 ..., { ...;}], "decimals">(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 ..., { ...; }], "decimals", ViewMutability>.address: IntoExpr<"address">
address: token: Expr<"address">
token, 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 ..., { ...; }], "decimals", ViewMutability>.functionName: "symbol" | "name" | "decimals" | "allowance" | "balanceOf" | "totalSupply"
functionName: 'decimals' }), userBalance?: IntoExpr<"uint256"> | undefined
userBalance: 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 ..., { ...;}], "balanceOf">(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 ..., { ...; }], "balanceOf", ViewMutability>.address: IntoExpr<"address">
address: token: Expr<"address">
token, 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 ..., { ...; }], "balanceOf", ViewMutability>.functionName: "symbol" | "name" | "decimals" | "allowance" | "balanceOf" | "totalSupply"
functionName: 'balanceOf', 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 ..., { ...; }], "balanceOf", ViewMutability>.args?: readonly [`0x${string}` | Expr<"address">] | undefined
args: [owner: Expr<"address">
owner] }), }), );
// One output row per input pool, collected in a counted loop. const const metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { readonly name: "observationCardinality"; readonly type: "uint16"; }, { readonly name: "observationCardinalityNext"; readonly type: "uint16"; }, { readonly name: "feeProtocol"; readonly type: "uint8"; }, { readonly name: "unlocked"; readonly type: "bool"; }]; };}>>
metadata = s: ScriptBuilder
s.ScriptBuilder.newArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { ...; }, { ...; }, { ...; }, { ...; }]; };}>>(elem: StructTypeOf<...>, length: IntoExpr<"uint256">): MutArray<...>
newArray(const PoolMetadata: StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { readonly name: "observationCardinality"; readonly type: "uint16"; }, { readonly name: "observationCardinalityNext"; readonly type: "uint16"; }, { readonly name: "feeProtocol"; readonly type: "uint8"; }, { readonly name: "unlocked"; readonly type: "bool"; }]; };}>
PoolMetadata, pools: Expr<"address[]">
pools.Expr<"address[]">.length(this: Expr<DynType | ArrayType>): Expr<"uint256">
length()); s: ScriptBuilder
s.ScriptBuilder.for<"uint256">(range: { type: "uint256"; from: IntoExpr<"uint256">; until: IntoExpr<"uint256">; step?: IntoExpr<"uint256"> | undefined;}, body: (i: Expr<"uint256">, loop: LoopCtl) => void): void
for({ type: "uint256"
type: const t: TypeNamespace
t.uint256: "uint256"
uint256, from: IntoExpr<"uint256">
from: 0n, until: IntoExpr<"uint256">
until: pools: Expr<"address[]">
pools.Expr<"address[]">.length(this: Expr<DynType | ArrayType>): Expr<"uint256">
length() }, (i: Expr<"uint256">
i) => { const const pool: Expr<"address">
pool = pools: Expr<"address[]">
pools.Expr<"address[]">.at(this: Expr<"address[]">, i: IntoExpr<"uint256">): Expr<"address"> (+1 overload)
at(i: Expr<"uint256">
i); const const token0: Expr<"address">
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"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}], "token0">(p: SubcallParams<...> & { ...;}) => Expr<...> (+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: const 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"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}]
abi: const uniswapV3PoolAbi: readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}]
uniswapV3PoolAbi, 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" | "token1" | "fee" | "slot0"
functionName: 'token0' }); const const token1: Expr<"address">
token1 = 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"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}], "token1">(p: SubcallParams<...> & { ...;}) => Expr<...> (+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"; }]; }, { ...; }, { ...; }, { ...; }], "token1", ViewMutability>.address: IntoExpr<"address">
address: const 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"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}]
abi: const uniswapV3PoolAbi: readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}]
uniswapV3PoolAbi, SubcallParams<readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }]; }, { ...; }, { ...; }, { ...; }], "token1", ViewMutability>.functionName: "token0" | "token1" | "fee" | "slot0"
functionName: 'token1' }); const metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { readonly name: "observationCardinality"; readonly type: "uint16"; }, { readonly name: "observationCardinalityNext"; readonly type: "uint16"; }, { readonly name: "feeProtocol"; readonly type: "uint8"; }, { readonly name: "unlocked"; readonly type: "bool"; }]; };}>>
metadata.MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<...>; readonly fee: "uint24"; readonly slot0: { ...; }; }>>.set(i: IntoExpr<"uint256">, v: IntoTuple<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, ... 4 more ..., { ...; }]; };}>>): void
set( i: Expr<"uint256">
i, s: ScriptBuilder
s.ScriptBuilder.tuple<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { ...; }, { ...; }, { ...; }, { ...; }]; };}>>(type: StructTypeOf<...>, init?: { ...;} | undefined): Tuple<...>
tuple(const PoolMetadata: StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { readonly name: "observationCardinality"; readonly type: "uint16"; }, { readonly name: "observationCardinalityNext"; readonly type: "uint16"; }, { readonly name: "feeProtocol"; readonly type: "uint8"; }, { readonly name: "unlocked"; readonly type: "bool"; }]; };}>
PoolMetadata, { // DEPENDENT reads: token0/token1 are outputs of the pool read above, and become // the *target* of the token reads inside tokenMetadata — on-chain, in one call. token0?: IntoTuple<{ readonly type: "tuple"; readonly components: { readonly [x: number]: { readonly name: "symbol"; readonly type: "string"; } | { readonly name: "address"; readonly type: "address"; } | { readonly name: "decimals"; readonly type: "uint8"; } | { readonly name: "userBalance"; readonly type: "uint256"; }; readonly 0: { readonly name: "symbol"; readonly type: "string"; }; readonly 1: { readonly name: "address"; readonly type: "address"; }; readonly 2: { readonly name: "decimals"; readonly type: "uint8"; }; readonly 3: { readonly name: "userBalance"; readonly type: "uint256"; }; readonly length: never; ... 33 more ...; readonly [Symbol.unscopables]: never; };}> | undefined
token0: const tokenMetadata: (token: IntoExpr<"address">, owner: IntoExpr<"address">) => Tuple<StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>>
tokenMetadata(const token0: Expr<"address">
token0, user: Expr<"address">
user), token1?: IntoTuple<{ readonly type: "tuple"; readonly components: { readonly [x: number]: { readonly name: "symbol"; readonly type: "string"; } | { readonly name: "address"; readonly type: "address"; } | { readonly name: "decimals"; readonly type: "uint8"; } | { readonly name: "userBalance"; readonly type: "uint256"; }; readonly 0: { readonly name: "symbol"; readonly type: "string"; }; readonly 1: { readonly name: "address"; readonly type: "address"; }; readonly 2: { readonly name: "decimals"; readonly type: "uint8"; }; readonly 3: { readonly name: "userBalance"; readonly type: "uint256"; }; readonly length: never; ... 33 more ...; readonly [Symbol.unscopables]: never; };}> | undefined
token1: const tokenMetadata: (token: IntoExpr<"address">, owner: IntoExpr<"address">) => Tuple<StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256";}>>
tokenMetadata(const token1: Expr<"address">
token1, user: Expr<"address">
user), fee?: IntoExpr<"uint24"> | undefined
fee: 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"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}], "fee">(p: SubcallParams<...> & { ...;}) => Expr<...> (+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"; }]; }, { ...; }, { ...; }, { ...; }], "fee", ViewMutability>.address: IntoExpr<"address">
address: const 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"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}]
abi: const uniswapV3PoolAbi: readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}]
uniswapV3PoolAbi, SubcallParams<readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }]; }, { ...; }, { ...; }, { ...; }], "fee", ViewMutability>.functionName: "token0" | "token1" | "fee" | "slot0"
functionName: 'fee' }), // `struct: true` decodes slot0's 7 named outputs into one Slot0-shaped object. slot0?: IntoTuple<{ readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { readonly name: "observationCardinality"; readonly type: "uint16"; }, { readonly name: "observationCardinalityNext"; readonly type: "uint16"; }, { readonly name: "feeProtocol"; readonly type: "uint8"; }, { readonly name: "unlocked"; readonly type: "bool"; }];}> | undefined
slot0: 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"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}], "slot0">(p: SubcallParams<...> & { ...;}) => Tuple<...> (+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"; }]; }, { ...; }, { ...; }, { ...; }], "slot0", ViewMutability>.address: IntoExpr<"address">
address: const 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"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}]
abi: const uniswapV3PoolAbi: readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "token1"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }];}, { readonly type: "function"; readonly name: "fee"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "uint24"; }];}, { readonly type: "function"; readonly name: "slot0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [...];}]
uniswapV3PoolAbi, SubcallParams<readonly [{ readonly type: "function"; readonly name: "token0"; readonly stateMutability: "view"; readonly inputs: readonly []; readonly outputs: readonly [{ readonly name: ""; readonly type: "address"; }]; }, { ...; }, { ...; }, { ...; }], "slot0", ViewMutability>.functionName: "token0" | "token1" | "fee" | "slot0"
functionName: 'slot0', struct: true
struct: true }), }), ); });
return s: ScriptBuilder
s.ScriptBuilder.return<{ readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { ...; }, { ...; }, { ...; }, { ...; }]; }; }>>;}>(values: { readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { ...; }, { ...; }, { ...; }, { ...; }]; }; }>>;}): ScriptReturn<...>
return({ metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, { readonly name: "tick"; readonly type: "int24"; }, { readonly name: "observationIndex"; readonly type: "uint16"; }, { readonly name: "observationCardinality"; readonly type: "uint16"; }, { readonly name: "observationCardinalityNext"; readonly type: "uint16"; }, { readonly name: "feeProtocol"; readonly type: "uint8"; }, { readonly name: "unlocked"; readonly type: "bool"; }]; };}>>
metadata }); },);Each line is plain TypeScript that records what the script will do; the callback runs once, at recording time. The pieces, in order:
t.fromOutputs(abi, 'slot0')derives a struct type from a function’s ABI outputs, soSlot0matchesslot0()exactly without hand-retyping its seven fields — see deriving types from an ABI.t.struct({ ... })builds composite types, and a member can itself be a struct (PoolMetadata.token0is aTokenMetadata) — see composite types.s.fn(name, [namedArg(...)], body)defines a reusable typed subroutine compiled once and called twice here (token0 and token1) — see user functions.s.read({ ..., struct: true })decodes a multi-output getter into one named object instead of a positional tuple — see calls.s.newArray+s.forcollect one row per pool, the multicall-style fan-out — see control flow.
The dependency that makes this a script and not a multicall: token0 is the output of the
pool read and the address of the token reads. Multicall must know every target up front, so
it cannot express this in one round trip; evs records the chain and runs it on-chain. See
why evs.
Compile and run it
Section titled “Compile and run it”compile() validates the recorded script and returns a frozen artifact; spread toViem() into
a plain viem readContract and add the functionName and typed args:
import { function namedArg<const name extends string, const type extends EvsType>(name: name, type: type): ArgSpec<name, type>
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, 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 } from '@maxencerb/evs';import { function createPublicClient<transport extends Transport, chain extends Chain | undefined = undefined, accountOrAddress extends Account | Address | undefined = undefined, rpcSchema extends RpcSchema | undefined = undefined>(parameters: PublicClientConfig<transport, chain, accountOrAddress, rpcSchema>): PublicClient<transport, chain, ParseAccount<accountOrAddress>, rpcSchema>
Creates a Public Client with a given Transport configured for a Chain.
A Public Client is an interface to "public" JSON-RPC API methods such as retrieving block numbers, transactions, reading from smart contracts, etc through Public Actions.
createPublicClient, 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, function http<rpcSchema extends RpcSchema | undefined = undefined, raw extends boolean = false>(url?: string | undefined, config?: HttpTransportConfig<rpcSchema, raw>): HttpTransport<rpcSchema, raw>
http } from 'viem';const const code: CompiledEvsScript<"poolsData", readonly [ArgSpec<"", "address[]">, ArgSpec<"", "address">], { readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, ... 5 more ..., { ...; }]; }; }>>;}>
code = const poolsData: EvsScript<"poolsData", readonly [ArgSpec<"", "address[]">, ArgSpec<"", "address">], { readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, ... 5 more ..., { ...; }]; }; }>>;}>
poolsData.EvsScript<"poolsData", readonly [ArgSpec<"", "address[]">, ArgSpec<"", "address">], { readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<...>; readonly fee: "uint24"; readonly slot0: { ...; }; }>>; }>.compile(options?: CompileOptions): CompiledEvsScript<"poolsData", readonly [ArgSpec<"", "address[]">, ArgSpec<"", "address">], { readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [...]; }; }>>;}>
compile();// code.runtimeBytecode — the script body (EIP-170: at most 24,576 bytes, enforced)// code.abi — literal-typed ABI: the 'poolsData' view function plus the two evs error entries
const const client: { account: undefined; batch?: { multicall?: boolean | Prettify<MulticallBatchOptions> | undefined; } | undefined; cacheTime: number; ccipRead?: false | { request?: (parameters: CcipRequestParameters) => Promise<CcipRequestReturnType>; } | undefined; chain: undefined; dataSuffix?: DataSuffix | undefined; experimental_blockTag?: BlockTag | undefined; key: string; ... 65 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;}
client = createPublicClient<HttpTransport<undefined, false>, undefined, undefined, undefined>(parameters: { batch?: { multicall?: boolean | Prettify<MulticallBatchOptions> | undefined; } | undefined | undefined; cacheTime?: number | undefined | undefined; ccipRead?: { request?: (parameters: CcipRequestParameters) => Promise<CcipRequestReturnType>; } | false | undefined | undefined; ... 6 more ...; transport: HttpTransport<...>;}): { ...;}
Creates a Public Client with a given Transport configured for a Chain.
A Public Client is an interface to "public" JSON-RPC API methods such as retrieving block numbers, transactions, reading from smart contracts, etc through Public Actions.
createPublicClient({ transport: HttpTransport<undefined, false>
The RPC transport
transport: http<undefined, false>(url?: string | undefined, config?: HttpTransportConfig<undefined, false> | undefined): HttpTransport<undefined, false>
http('https://eth.merkle.io') });
const const result: { metadata: readonly { token0: { symbol: string; address: `0x${string}`; decimals: number; userBalance: bigint; }; token1: { symbol: string; address: `0x${string}`; decimals: number; userBalance: bigint; }; fee: number; slot0: { sqrtPriceX96: bigint; tick: number; observationIndex: number; observationCardinality: number; observationCardinalityNext: number; feeProtocol: number; unlocked: boolean; }; }[];}
result = await const client: { account: undefined; batch?: { multicall?: boolean | Prettify<MulticallBatchOptions> | undefined; } | undefined; cacheTime: number; ccipRead?: false | { request?: (parameters: CcipRequestParameters) => Promise<CcipRequestReturnType>; } | undefined; chain: undefined; dataSuffix?: DataSuffix | undefined; experimental_blockTag?: BlockTag | undefined; key: string; ... 65 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;}
client.readContract: <ScriptAbi<"poolsData", readonly [ArgSpec<"", "address[]">, ArgSpec<"", "address">], { readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, ... 5 more ..., { ...; }]; }; }>>;}>, "poolsData", readonly [...]>(args: ReadContractParameters<...>) => Promise<...>
Calls a read-only function on a contract, and returns the response.
readContract({ ...const code: CompiledEvsScript<"poolsData", readonly [ArgSpec<"", "address[]">, ArgSpec<"", "address">], { readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, ... 5 more ..., { ...; }]; }; }>>;}>
code.CompiledEvsScript<"poolsData", readonly [ArgSpec<"", "address[]">, ArgSpec<"", "address">], { readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<...>; readonly fee: "uint24"; readonly slot0: { ...; }; }>>; }>.toViem(): { abi: ScriptAbi<"poolsData", readonly [ArgSpec<"", "address[]">, ArgSpec<"", "address">], { readonly metadata: MutArray<StructTypeOf<{ readonly token0: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly token1: StructTypeOf<{ readonly address: "address"; readonly symbol: "string"; readonly decimals: "uint8"; readonly userBalance: "uint256"; }>; readonly fee: "uint24"; readonly slot0: { readonly type: "tuple"; readonly components: readonly [{ readonly name: "sqrtPriceX96"; readonly type: "uint160"; }, ... 5 more ..., { ...; }]; }; }>>; }>; code: Hex;} (+2 overloads)
toViem(), // { abi, code } — one deployless eth_call, nothing deployed functionName: "poolsData"
functionName: 'poolsData', args: readonly [arg0: readonly `0x${string}`[], arg1: `0x${string}`]
args: [ ['0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640'], // USDC/WETH 0.05% pool (mainnet) '0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640', ],});The typed result
Section titled “The typed result”result is inferred entirely from the script — no as const, no codegen. Each s.return key
becomes a named output; structs become objects, tuple[]/arrays become arrays, and every leaf
maps by its ABI type (address → `0x${string}`, uint8/uint24/int24 → number,
uint256 → bigint, bool → boolean):
type Result = { metadata: readonly { token0: { address: `0x${string}`; symbol: string; decimals: number; userBalance: bigint }; token1: { address: `0x${string}`; symbol: string; decimals: number; userBalance: bigint }; fee: number; slot0: { sqrtPriceX96: bigint; tick: number; observationIndex: number; observationCardinality: number; observationCardinalityNext: number; feeProtocol: number; unlocked: boolean; }; }[];};What just happened
Section titled “What just happened”Nothing was deployed and no transaction was sent. toViem() hands viem the script’s init
bytecode under the code parameter; viem issues a single plain two-parameter eth_call (no
to) in which the script is instantiated counterfactually, runs — decoding your arguments,
looping over the pools, performing the dependent reads with values flowing between them
on-chain — and returns one ABI-encoded result that viem decodes into the typed array above.
Everything inside the call is discarded by the node afterwards. A state-override mode with a
deterministic script address also exists; see execution.
Where next
Section titled “Where next”- Why evs? — the dependent-read problem and the comparison with multicall3
- Writing scripts — the script header, positional args, and the recording-time vs run-time model
- Values & types —
Exprhandles, composite types, and deriving types from an ABI - Calls — the three calling verbs (
s.readview reads,s.callforCALL-frame functions like a Uniswap quoter,s.simulateto dry-run a write),struct: true, and failure handling - Pool metadata example — the same idea, built up one read at a time