Skip to content

halaprix/domino

Repository files navigation

domino

        ┌─────────┬─────────┐
        │  ●   ●  │  ●      │
        │         │    ●    │
        │  ●   ●  │      ●  │
        └─────────┴─────────┘
   turns M calls × N steps into M multicalls

   _|                            _|
 _|_|_|    _|_|    _|_|_|  _|_|        _|_|_|      _|_|
_|    _|  _|    _|  _|    _|    _|  _|  _|    _|  _|    _|
_|    _|  _|    _|  _|    _|    _|  _|  _|    _|  _|    _|
  _|_|_|    _|_|    _|    _|    _|  _|  _|    _|    _|_|

CI npm version bundle size TypeScript MIT License

A state machine for on-chain reads. Define steps, push results through. One multicall per step.

npm install @halaprix/domino

But wait — it's just a state machine

Multicall is great for batched reads. But what about when step 2 needs step 1's results?

Instead of N separate RPC calls per step, domino runs your state machine as a batch — one multicall per step. You define the steps, it wires them together.

import { createPublicClient, http, mainnet } from "viem"
import { MulticallResolver, Eip1193Executor } from "@halaprix/domino"

const provider = createPublicClient({ chain: mainnet, transport: http() })
const resolver = new MulticallResolver(new Eip1193Executor(provider))

// 🧠 Any state machine — define steps, domino batches them:
const result = await resolver.run({
  taskName: "price-check",

  // Step 1: batch of independent reads
  *steps() {
    yield {
      calls: [
        { key: "price", abi: oracleAbi, functionName: "latestAnswer" },
        { key: "decimals", abi: erc20Abi, functionName: "decimals" },
      ],
    }

    // Step 2: uses results from step 1
    const price = this.getResult("price").value
    const decimals = this.getResult("decimals").value
    const scaledPrice = price * (10n ** (18n - decimals))

    yield {
      calls: [], // optional — if you need more steps
    }
  },

  // finalize: assemble the answer
  finalize() {
    const price = this.getResult("price").value
    const decimals = this.getResult("decimals").value
    return { price, decimals, scaledPrice: price * (10n ** (18n - decimals)) }
  },
})

That's the whole API. Two pages — read the source of erc4626.ts if you want to see a complete example.

Built-in task builders

For convenience, domino ships with pre-built task builders:

import { buildErc4626Task, resolveErc4626Vault } from "@halaprix/domino"

// One vault — 2 multicalls (metadata + convertToAssets)
const vault = await resolveErc4626Vault({
  client: executor,
  vault: "0x...",
  owner: "0x...",
})
// { name, symbol, decimals, balance, assets, ... }

// 100 vaults — still just 2 multicalls
const vaults = await resolveErc4626VaultsBulk({
  client: executor,
  entries: vaultAddresses.map(a => ({ vault: a, owner })),
})

Same pattern for ERC20, and you can buildErc4626Task() / buildErc20Task() to compose them into custom pipelines.

Historical blocks

Query any block with EIP-1898:

const oldVault = await resolveErc4626Vault({
  client: executor,
  vault: "0x...",
  block: { blockNumber: 19_000_000n },
})

Works with blockHash, blockTag, or blockNumber. Even on chains where Multicall3 didn't exist yet — domino falls back to deployless multicall automatically.

When NOT to use it

  • Pure batches (no dependencies) → plain multicall is simpler.
  • Write transactions → wrong tool. This reads only.
  • Single reads → just use client.readContract() directly.

API at a glance

Export What it is
MulticallResolver Convenience layer — call run() to execute a state machine
Eip1193Executor Single engine — works with any EIP-1193 provider
runMultistepTasks() Core FSM — bare-metal version of the resolver
buildErc20Task() Build a task definition for ERC20 token reads
buildErc4626Task() Build a task definition for ERC4626 vault reads
resolveErc20Token() One-shot ERC20: { symbol, decimals, balance }
resolveErc4626Vault() One-shot ERC4626: { name, assets, ... }
BlockParam { blockNumber?, blockTag?, blockHash? }

Documentation

Contributing

See our Contributing Guide.

License

MIT

About

Batched on-chain reads that respect state — 2-step FSM multicall turns N×M RPC calls into M rounds. viem + ethers v5/v6. 53 tests. 2KB gzip.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors