Using & hosting Chainstitch

Everything you need to go from git clone to a running notebook — and from your laptop to a team instance. The README on GitHub stays the full reference.

Quick start

npm install
npm run dev

Open http://localhost:3000 — a fresh instance boots with an Example project (Ethereum mainnet via a public RPC) whose Welcome notebook is a runnable tour of blocks, variables, conditions and recipes: open it and hit Run all, no contracts and no wallet needed. The SQLite database creates and migrates itself; there is no other setup. Every project you create seeds the same tour against its own RPC — there's an Anvil preset for local chains, or point it at any endpoint. Delete the example when you're done.

A wallet is only needed for write blocks. Injected wallets (MetaMask, Rabby, …) work out of the box; for WalletConnect set NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID (see .env.example). Reads and RPC calls run without any wallet, straight from your browser.

Projects & contracts

A project is one chain config: chain id, RPC URL and optional block explorer. Its Contracts tab is the address book every notebook, codegen flavor and State card reads from.

Add contracts by fetching a verified ABI — paste an address and the lookup queries Sourcify and Blockscout (Etherscan too when the server sets ETHERSCAN_API_KEY; a free key covers 60+ chains) — or by dropping ABI JSON files(raw arrays or Foundry/Hardhat artifacts). Proxies are handled automatically on lookup: the implementation ABI is paired with the proxy address, via explorer hints or an EIP-1967 slot read. On an anvil fork, pick the chain the fork is based on in the lookup's chain select.

Notebooks & blocks

Notebooks run like Jupyter: per-block or top-to-bottom, with outputs decoded and saved. Name a block's output and reference it downstream as {{pool}} or {{receipt.blockNumber}} — dot/bracket paths reach into structured results.

BlockWhat it does
ReadCalls a view/pure function from the address book, args from a typed form.
WriteSimulates first — revert reasons surface before the wallet prompt — then sends and awaits the receipt.
RPCgetBlock, getBalance, getLogs, … plus a raw custom-method escape hatch for anvil cheatcodes.
Text / VariableMarkdown notes, and named constants usable as {{chips}} anywhere.
Sender groupRuns child blocks as a chosen caller (impersonation on anvil needs no keys).
If group / run-whenBlocks run only when a condition on prior outputs holds ({{allowance}} < {{amount}}).
ExpectHard assertion: condition, required event, or expected revert — fails Run all / CI when unmet.
Recipe cellReruns a saved recipe's steps in one click — see below.

Amount args are unit-aware when the contract exposes decimals(): enter 1.5 and the field stores raw units (1500000 for USDC). Toggle the unit badge for raw / {{variable}} entry. Payable value accepts ETH and stores wei. Address fields autocomplete from the address book and resolve ENS names on blur. Results show formatUnits next to the raw integer when the value looks like a token amount.

The toolbar's handoff panel (tree icon) summarizes the notebook for teammates: call sequence for frontend, expected events for backend/indexer, and {{variable}} wiring — the same brief agents get via get_notebook_handoff.

Keyboard shortcuts follow Jupyter: B adds a block, M a markdown cell, Shift+Enter runs and advances, Cmd+Enterruns in place. Notebooks and recipes open in browser-style tabs above the editor; each project's Overview page manages its documents.

Recipes

Select blocks in any notebook and bookmark them as a named recipe — the classic is check the allowance, approve only if it's too low. Insert one from the add-block menu either as a linked recipe cell (one click reruns every step; cells follow when the recipe is edited) or as editable blocks. Recipes live in the sidebar under your notebooks and open in the same editor: tweak steps with the full typed forms, test-run in place, then Save to publish.

State tab

Pin view functions (name, totalSupply, slot0, …) per contract into a live dashboard — one multicall fetches everything, refreshed on demand. Drag cards to rearrange, drag an edge to resize, drop in section titles.

Codegen & AI import

Every block deterministically generates its source — wagmi hooks, viem, Python (web3.py), Rust (alloy) or Solidity — with ABIs and addresses pulled from the address book. A notebook-level toggle shows the full runnable source, and the whole notebook exports as a JSON call manifest. The notebook is the integration spec you hand to your app team, and it can't drift because you just ran it.

AI import converts a Foundry .t.sol test into runnable blocks: vm.prank becomes a sender group, vm.deal/vm.warp become cheatcode cells, asserts become condition checks. Bring your own Google AI Studio key (free tier) — calls go straight from your browser to Google and never touch the Chainstitch server.

AI agents & MCP

Every instance is an MCP server (Model Context Protocol) at /api/mcp: connect Cursor, Claude Code or any MCP-capable coding agent and it can read your address book, author notebooks, and pull any notebook back as generated source. No extra process — it's the app you already have running.

Local mode needs no credentials (the instance is already yours). Point the agent at localhost:

// .cursor/mcp.json — local mode
{
  "mcpServers": {
    "chainstitch": { "url": "http://localhost:3000/api/mcp" }
  }
}

Team mode uses a personal API token(SIWE can't be done headlessly). Create one under Settings → Agent tokens, then pass it as a Bearer header. The token inherits your workspace and project roles — revoke it anytime.

// .cursor/mcp.json — team mode
{
  "mcpServers": {
    "chainstitch": {
      "url": "https://your-instance.example/api/mcp",
      "headers": {
        "Authorization": "Bearer cst_…"
      }
    }
  }
}
ToolWhat it does
list_projectsProjects with chain id and your role
list_contractsThe address book, as function/event signatures
add_contractAdd an ABI — pass one, or auto-fetch verified source by address
list_notebooks / get_notebookBrowse; read a notebook as a portable manifest
create_notebookAuthor a notebook from a manifest (missing ABIs created on the fly)
update_notebook_blocksReplace a notebook's content in place — the old version stays restorable in edit history
get_notebook_codeWhole notebook as wagmi / viem / Python / Rust / Solidity source
get_notebook_handoffIntegration brief: call sequence, expected events, {{variable}} wiring
get_notebook_formatThe manifest format spec, for the agent to read first

The handoff becomes a prompt, in both directions: an agent turns "set up the deposit flow" into a runnable notebook (it hands back the URL — you hit Run all), surfaces get_notebook_handoff so frontend sees the call sequence and backend sees expected event logs, and pulls any tested notebook back out as wagmi hooks instead of re-deriving calls from an ABI. In the editor, the handoff panel (toolbar tree icon) shows the same brief with Frontend / Backend tabs.

The same manifest travels without an agent too: GET /api/notebooks/:id/fileexports it (as does the editor's download button), POST /api/projects/:id/notebooks/import imports it — so notebooks can live in the contracts repo and go through PR review. Manifests carry blocks plus the ABIs they reference, never RPC URLs.

Agents create and read definitions; execution stays in your browser (or the CLI below). Writes stay signed by your wallet — the server never holds keys.

Expect blocks & CI

An Expect cell fails the run when unmet (unlike a soft Condition group that only skips children):

KindChecks
condition{{balance}} > 0 — same grammar as if / run-when
eventDecoded event on the last write (or from a variable)
revertSimulates a call and requires it to revert (optional reason)

Export the notebook as a chainstitch-notebook file and run it headlessly — exit code non-zero when any expect fails:

# local anvil
npx chainstitch run ./flow.notebook.json --rpc-url http://127.0.0.1:8545

# fresh fork
npx chainstitch run ./flow.notebook.json --fork-url $ETH_RPC_URL

Writes use sender-group impersonation on anvil, or pass --private-key for a burner. The CLI does not talk to the Chainstitch server.

Local chains & forks

anvil                  # plain local chain
anvil --fork-url $RPC  # fork mainnet/testnet state

Point a project at http://127.0.0.1:8545 (chain id 31337). The custom RPC block drives cheatcodes — anvil_setBalance, anvil_impersonateAccount, evm_snapshot, evm_revert — and sender groups impersonate whales on forks without touching a private key. Write blocks sent by an impersonated sender need no wallet at all.

Team mode & self-hosting

Same codebase, same database, one environment variable apart:

local (default)team
Sign-innoneSign-In with Ethereum (SIWE)
Who gets inwhoever can reach the portinvited wallets only
Sharingby wallet address, with roles
Best foryour laptop, trusted networksa team instance on a server

Team mode needs four variables (see .env.example):

APP_MODE=team
OWNER_WALLETS=0xYourAddress                    # comma-separated owners
BETTER_AUTH_SECRET=$(openssl rand -base64 32)  # session signing key
APP_URL=https://notebook.example.com           # exact URL users visit

Serve it over HTTPS behind any reverse proxy (sessions and SIWE messages are domain-bound to APP_URL), start with docker compose up -d --build or npm run build && npm start, sign in with an owner wallet, and invite teammates from Settings(the gear button). Invites are just a wallet address and a role — no email server — claimed on that wallet's first sign-in.

RoleCan do
viewerread everything, run blocks with their own wallet
editor+ edit contracts, notebooks, blocks, state views
owner+ project settings & RPC URLs, members & invites, deletes

Share at whichever radius fits: the workspace (Settings → invite), a single project (its Share button — grantees see nothing else), or an anyone-with-the-link URL(viewer or editor, no account; resetting the link revokes every copy). Note that project access includes the project's RPC URL, since blocks execute in each member's browser — use rate-limited keys for shared projects.

Never expose a local-mode instance to the internet — it has no auth by design. Private keys never touch the server in either mode: writes are signed in the browser, reads run client-side, and the server stores notebook definitions, never execution results.

Security & your data

Chainstitch is built so the scary things can't happen by construction, not by policy:

InvariantWhat it means
Keys never touch the serverWrites are signed in your own browser wallet. There is no server-side signing capability at all, in any mode.
Execution is client-sideReads, writes and RPC calls go straight from your browser to your RPC endpoint. The server never proxies chain traffic.
Results are never stored server-sideThe database holds notebook definitions (projects, ABIs, blocks) — not what they returned when you ran them.
Your data is one file you ownEverything lives in a single SQLite file on your machine or server. No telemetry, no license server, nothing phones home.

Team-mode authentication is Sign-In with Ethereum done carefully: single-use nonces, signatures domain-bound to your APP_URL(a message signed for another site can't be replayed against yours), httpOnly Secure session cookies, and an invite-only sign-in policy — an uninvited wallet cannot even create an account. Signing in costs nothing and touches no chain; it proves wallet ownership, nothing more. Removing a member locks them out immediately.

Authorization is enforced server-side, in one data-access layer that scopes every query by workspace and project role — never just hidden in the UI. Cross-tenant isolation and the role matrix are pinned by regression suites (130+ assertions) that run in CI on every change.

The one trade-off to know about: anyone who can open a project can see its RPC URL, because their browser executes calls against it. Use rate-limited or public RPC keys for shared projects. Share links carry viewer or editor rights only (never owner), are re-validated on every request, and resetting one instantly invalidates every copy.

The server makes exactly one kind of outbound request — the ABI lookup — and it is restricted to a fixed allowlist of explorer hosts, with redirects refused and responses size-capped. Found a hole anyway? Report it privately via GitHub's Security tab — acknowledgement within 72 hours, assessment within 7 days, credit when the fix ships.

Operations

Everything lives in one SQLite file (data/chainstitch.db, override with CHAINSTITCH_DB_PATH). Back it up while running with sqlite3 data/chainstitch.db ".backup backup.db". Upgrades are pull → build → restart; schema migrations apply automatically on boot. Switching an existing local instance to team mode keeps every project; switching back bypasses auth again, so only do that on a machine you trust end-to-end.

Deployment guides (Docker, Node, Caddy, backups) live in the README's self-hosting section.