domino v1.3.0

FSM Multicall Executor · EIP-1193 Native

Batched on-chain reads.

domino solves the N×M RPC problem. Define step dependencies directly — step 2 takes step 1's output, and domino resolves the entire task graph in M multicalls instead of N×M individual calls.

Execution graph flow in Multicall3 A task graph enters domino's Eip1193Executor. Step 1 reads balanceOf, step 2 passes that result into convertToAssets, and a derive step calculates final position without extra RPC calls. TASK GRAPH · vaultPosition() EIP1193 EXECUTOR N×M RPC eliminated multicall #1 multicall #2 step 2 STEP 1: balanceOf STEP 2: convertToAssets DERIVED RESULT GRAPH

defineTasksingle-use graph built

naive sequential loopN×M calls bypassed

multicall #1balanceOf(owner)

multicall #2convertToAssets(ref)

t.derive()local calculation

resolved in 2 multicalls16.8 KB gzipped

Topology

One graph, M multicalls

Multicall is great for batched reads. But when step 2 depends on step 1, naive code makes N×M calls. domino's FSM engine automatically computes graph depth and executes one multicall per step batch.

fsm execution
Results from step N flow automatically into step N+1 parameters via typed Ref<T> handles.
eip-1193 native
Direct integration with viem PublicClient, window.ethereum, or ethers adapters — zero extra protocol layer.
multichain parallel
MultichainResolver executes identical task graphs across multiple EVM chains in parallel.

Procedure

Workflow

Four simple steps to replace N×M RPC loops with self-triggering multicalls.

  1. 01 npm install @halaprix/domino viem Install domino and viem. Works with Node.js 18+ and modern browser environments.
  2. 02 const executor = new Eip1193Executor(provider); Wrap any EIP-1193 provider (viem PublicClient, window.ethereum, or an ethers adapter).
  3. 03 const position = defineTask((t) => { ... }); Describe call dependencies using human-readable ABIs or standard JSON ABIs. Pass result refs directly to subsequent call args.
  4. 04 const [result] = await runMultistepTasks(executor, [position()]); Execute the task graph. domino runs minimum required multicalls and returns a fully typed result object.

Guarantees

Capabilities & Contracts

domino is engineered for strict production guarantees and zero runtime bloat.

Engine guarantees and safety invariants
Guarantee Metric Meaning
gzip transfer budget 16.8 KB Strict bundle ceiling enforced in CI — whole library including Multicall3 bytecodes and viem utils.
single-use task protection Guaranteed Tasks are single-run; re-submitting an existing task instance throws DominoTaskReuseError to prevent race conditions.
memoized abi parser LRU 256 Deduplicates human-readable ABI parsing across array-identity and value-equality boundaries.
per-task isolation runSettled Isolate task failures with runSettled / resolver.runSettled without aborting the entire batch.
typescript inference Strict Return types inferred directly from human-readable ABIs and t.derive callbacks.

Performance

N×M Calls vs. Domino M Multicalls

By collapsing dependent calls into M sequential multicalls, domino dramatically reduces RPC roundtrips and latency.

  • problem Querying 100 vaults with a 2-step pipeline (`balanceOf` → `convertToAssets`) requires 200 sequential HTTP/WebSocket RPC roundtrips.
  • solution domino groups step 1 across all 100 vaults into Multicall #1, feeds all 100 balance outputs into Multicall #2, completing the pipeline in 2 multicalls.
  • erc4626 resolver Built-in helper `resolveErc4626Vault({ executor, vault, owner })` resolves vault balances, total assets, share prices, and user positions in 2 multicalls.

Install

Setup & Quick Start

Version 1.3.0 is available on npm. Lightweight, fully typed, and zero external runtime configuration.

$ npm install @halaprix/domino viem

# define a multistep task graph with domino

import { createPublicClient, http } from "viem";

import { Eip1193Executor, defineTask, runMultistepTasks } from "@halaprix/domino";

const provider = createPublicClient({ transport: http() });

const executor = new Eip1193Executor(provider);

function vaultPosition(vault, owner) {

return defineTask((t) => {

const balance = t.call({ target: vault, abi: vaultAbi, functionName: "balanceOf", args: [owner] });

const assets = t.call({ target: vault, abi: vaultAbi, functionName: "convertToAssets", args: [balance] });

const hasBalance = t.derive([balance], (bal) => bal > 0n);

return { balance, assets, hasBalance };

});

}

const [res] = await runMultistepTasks(executor, [vaultPosition(vaultAddr, ownerAddr)]);

Supports viem, window.ethereum, ethers v5/v6 (via EIP-1193 adapter), and custom RPC transport wrappers.