Skip to content

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 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, so Slot0 matches slot0() 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.token0 is a TokenMetadata) — 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.for collect 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() 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.

@paramconfig - PublicClientConfig

@returnsA Public Client. PublicClient

@example

import { createPublicClient, http } from 'viem' import { mainnet } from 'viem/chains'

const client = createPublicClient({ chain: mainnet, transport: http(), })

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>

@description Creates a HTTP transport that connects to a JSON-RPC API.

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.

@paramconfig - PublicClientConfig

@returnsA Public Client. PublicClient

@example

import { createPublicClient, http } from 'viem' import { mainnet } from 'viem/chains'

const client = createPublicClient({ chain: mainnet, transport: http(), })

createPublicClient
({
transport: HttpTransport<undefined, false>

The RPC transport

transport
:
http<undefined, false>(url?: string | undefined, config?: HttpTransportConfig<undefined, false> | undefined): HttpTransport<undefined, false>

@description Creates a HTTP transport that connects to a JSON-RPC API.

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.

@remarks

A "read-only" function (constant function) on a Solidity contract is denoted by a view or pure keyword. They can only read the state of the contract, and cannot make any changes to it. Since read-only methods do not change the state of the contract, they do not require any gas to be executed, and can be called by any user without the need to pay for gas.

Internally, uses a Public Client to call the call action with ABI-encoded data.

@paramargs - ReadContractParameters

@returnsThe response from the contract. Type is inferred. ReadContractReturnType

@example

import { createPublicClient, http, parseAbi } from 'viem' import { mainnet } from 'viem/chains' import { readContract } from 'viem/contract'

const client = createPublicClient({ chain: mainnet, transport: http(), }) const result = await client.readContract({ address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', abi: parseAbi(['function balanceOf(address) view returns (uint256)']), functionName: 'balanceOf', args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'], }) // 424122n

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',
],
});

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/int24number, uint256bigint, boolboolean):

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;
};
}[];
};

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.

  • 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 & typesExpr handles, composite types, and deriving types from an ABI
  • Calls — the three calling verbs (s.read view reads, s.call for CALL-frame functions like a Uniswap quoter, s.simulate to dry-run a write), struct: true, and failure handling
  • Pool metadata example — the same idea, built up one read at a time