name: create-effectstream-app
description: Scaffold a new Effectstream template (a multi-chain blockchain app — sync node + state machine + on-chain contracts + optional batcher/frontend/tests/Docker) following the canonical Bun-monorepo layout. Also handles migrating older templates from @paimaexample/* or @paima/* (paima-engine-v1) layouts into the current @effectstream/* shape. Use this skill whenever the user wants to create, scaffold, generate, bootstrap, or start a new Effectstream / Paima app, dApp, game template, or multi-chain template — even if they don't say "template" — and whenever they want to upgrade, modernize, port, or migrate an existing Paima or Effectstream project to the v2 layout.
create-effectstream-app
Effectstream templates are standalone Bun monorepos that wire together: on-chain contracts, a sync node with a state machine, a typed database, an orchestrator that runs the whole local stack with bun run dev, and (optionally) a batcher / frontend / test suite / Dockerfile. Every template uses the same flat packages/* layout — complexity is additive (more packages), not structural (deeper nesting).
This skill exists because there are ~80 sharp edges (Bun workspace symlinking, Midnight version pinning, pgtyped working-dir, Foundry-vs-Hardhat artifact split, MQTT broker on Bun, contract address resolution timing, batcher namespace matching, …) that an agent will not discover from reading the repo alone. Most of them only blow up at runtime, in Docker, or when the user actually opens the page in a browser — i.e. after the agent has already declared success.
Workflow choice — make this decision first:
- Create a new template (95% case) → start with Discovery below, then follow Build order.
- Migrate an existing Paima/Effectstream-v1 project → jump to
references/migration.md. The rest of this file still applies after the structural migration.
Verification is mandatory — read this before you write any code
A scaffold that hasn't been run is not a scaffold; it's a draft. Half the sharp edges this skill exists to avoid only surface at runtime, in Docker, or when the user opens the page in a browser. Static review of the generated files will not catch them.
The rules:
bun run devMUST run at least once before you report completion. Nottsc, notvite build, not "looks right to me" — the actual full-stack boot. If you can't get to it, the task is not done.- Verify incrementally, one step at a time. Don't batch
bun install && bun run build:evm && bun run build:midnight && bun run devinto one command — when something breaks you won't know which step. Run each separately and check the exit code / output before moving to the next.- Stop on the first failure. Fix the actual problem before continuing. Don't paper over it with
|| true, don't skip ahead and hope a later step fixes it, don't add a TODO and move on.- Probe the environment before scaffolding. Run
which bun 2>&1always; then run thewhichcheck from the Tools section of each chain reference the template will use (references/chains/<chain>.md). Adddocker --versionif Docker was requested. If a tool needed by the template is missing, tell the user before writing 60+ files you can't verify. Do NOT default to "the user probably doesn't have these installed" — check.- When reporting, quote the verification trail. Default assumption in your final message is "I ran it and here's what happened" — not "you should run this." If a step genuinely failed and you couldn't fix it, name the exact command, the exact error, and what you tried. See What to give the user when you're done.
Full per-step acceptance criteria in Verification — mandatory, incremental, stop on error.
Discovery — confirm the infrastructure with the user before scaffolding
Don't pick chains, sync protocols, primitives, contracts, or batchers by default — these are user decisions with real production consequences. Each one has its own ongoing cost (indexer per chain, RPC bills per sync protocol, gas + audit + deployment burden per contract, batcher infra). Suggest options, present trade-offs, then wait for explicit confirmation on each axis before writing any code.
This phase looks like a short interview, not a code change. Run it even if the user's prompt sounds complete — the prompt almost never spells out cost or deployment implications, and the wrong default is much more expensive than a thirty-second clarifying conversation.
The confirmation matrix (echo this back to the user before scaffolding, and don't proceed until they say yes on every row):
| Axis | Suggest | User confirms | Cost they're accepting |
|---|---|---|---|
| Chains | Smallest set that satisfies the use case | Exact chain list | Indexer + RPC + monitoring per chain, 24/7 in production |
| Sync protocols | One addMain (NTP_MAIN) + one addParallel per chain |
Specific protocol per chain | RPC calls / block × polling rate. Higher confirmation depth = slower but safer. |
| Primitives | Built-in primitives that don't require contracts (read-only watchers) | Exact primitive list per chain + the events they listen to | Per-block scan work for each primitive — adds up |
| Contracts | Minimal set or "none if a primitive covers it" | What gets deployed, by whom, on which chains | Deployment gas + audit + lifetime upgrade burden — borne by the user |
| Batchers | Yes only if gasless UX or time/size amortization is needed | Yes/no per chain that supports batching | Batcher process + private-key custody + namespace coordination with the frontend |
| STM design | Sketched after the above are locked | Grammar keys, transitions, DB tables, API endpoints | The shape of the rewrite if it changes later |
If the user only said "build me an X" and the matrix isn't filled in, stop and ask. The skill assumes every row has been agreed before any of the build-order steps below run.
1. Which chain(s)?
Each chain a template indexes costs real money in production — an RPC provider or self-hosted node, an indexer process per chain, monitoring per chain. Each chain also doubles the local-dev orchestrator surface (more processes to boot, more failure modes, longer bun run test). So:
- Confirm the minimal chain set that satisfies the use case. If the user says "I want a Cardano + Midnight game," push back: do both chains have a real role, or is one of them speculative? Single-chain templates are 2-3× simpler to operate.
- Surface the cost: "Each chain you target needs an indexer running 24/7 in production. Are you OK with that for
<chain>?" — especially for Cardano (Dolos + UTxO-RPC) and Midnight (node + indexer + proof-server) which are heavier than EVM. - Match the chain to the feature: if the only requirement is "watch ADA transfers to an address," that's Cardano-only; don't add EVM. If the user wants ZK private state, Midnight is the only choice. If the user just wants tokens, EVM is usually enough.
Don't proceed until the user has named the chains explicitly.
2. Which sync protocols?
A sync protocol is how the engine reads a chain. Each app has exactly one addMain (the NTP_MAIN clock) and one addParallel per chain you target. The defaults are usually right but worth surfacing:
| Chain | Default sync protocol | Pick differently if |
|---|---|---|
| EVM | EVM_RPC_PARALLEL |
(only option today) |
| Midnight | MIDNIGHT_PARALLEL |
(only option today) |
| Cardano | CARDANO_UTXORPC_PARALLEL (via Dolos) |
You need CARP indexer instead — pick CARDANO_CARP_PARALLEL |
| Bitcoin | BITCOIN_RPC_PARALLEL |
(only option today) |
| NEAR | NEAR_RPC_PARALLEL |
(only option today) |
| Avail | AVAIL_PARALLEL |
(only option today) |
| Celestia | CELESTIA_PARALLEL |
(only option today) |
What you DO need to confirm with the user even when there's only one protocol:
startBlockHeight— replay from genesis or from now? Genesis is correct but slow on long-running chains. For testnet/dev, "current tip - 5" is the common idiom; for mainnet, the user should pick a deliberate starting block (the block their contract was deployed at, typically).pollingIntervalandconfirmationDepth— defaults vary per chain. Surface them: shorter intervals = lower latency, more RPC calls, higher cost. Higher confirmation depth = safer against reorgs, slower index. The user should approve these values per chain before scaffolding.
3. Which contract(s) — and does the use case even need contracts?
Contracts come with the highest cost: the user has to deploy them, audit them, manage upgrades, and pay deployment gas. The agent doesn't deploy contracts; the user does. So:
- Enumerate what the use case actually needs on-chain. For a "watch incoming ADA" template, no contract is needed — the engine reads native transfers via the
cardanoTransferprimitive. Adding a contract here would be cost and complexity for nothing. - If contracts are needed, propose the minimal interface (e.g. "a single ERC-721 contract extending OpenZeppelin's
ERC721") and let the user accept, modify, or replace it. - Mention deployment: "You'll need to deploy this to
mainnet. Want me to scaffold a deploy script and a placeholder address that's env-var configurable?" - Cross-check against the primitive surface (next step) — sometimes a built-in primitive removes the need for a custom contract entirely.
4. Which primitives?
Primitives are how the engine sees on-chain events. Many use cases don't need contracts at all — they need a built-in primitive that watches a chain-native event:
cardanoTransfer/cardanoMintBurn/cardanoPoolDelegation— watch Cardano without deploying anything.evmErc20/evmErc721/evmErc1155— watch existing token contracts without deploying your own.bitcoinAddress— watch a Bitcoin address.midnightGeneric— read Midnight ledger contract state (still needs the Midnight contract deployed, but it might be an existing one).- Midnight
nullifiertracking — read NULLIFIERS without owning the contract.
These primitives must still be explicitly confirmed with the user — they shape the indexer's read pattern, the database schema, and the STM grammar. Ask: "I'm planning to use <primitive> to watch <event> on <chain>. That's a read-only watch — no contract deployment required. OK?"
The default-unless-asked rule: don't add primitives the user didn't sign off on. Several primitives are opt-in for cost reasons — see Core invariant §9 and the relevant references/chains/{chain}.md § Sharp edges for which primitives those are.
5. Do you need a batcher? (per chain, if applicable)
A batcher aggregates user transactions and submits them to one or more chains. Batchers come with their own infrastructure cost: an extra long-running process, a custodial private key (gas-payer), and a namespace that MUST match the frontend's EffectstreamConfig appName exactly (mismatch → 401 Invalid signature).
Confirm with the user, per chain the template targets:
- Suggest YES if the frontend will submit user actions and you want (a) gasless UX (the batcher pays gas, not the user), or (b) time/size amortization (group N user txs into one chain tx).
- Suggest NO if the frontend builds and submits transactions directly using the user's own wallet (e.g. Cardano templates with Lucid Evolution submit to YACI/Dolos directly; many Midnight templates submit Compact proofs via Lace; Bitcoin templates typically don't batch). A no-batcher template's HTTP API stays GET-only.
Available adapters (one per target chain):
| Adapter | Chain | Batching criteria |
|---|---|---|
EffectstreamL2DefaultAdapter |
EVM | time, size, hybrid |
EvmContractAdapter |
EVM (custom contract) | time, size, hybrid |
MidnightAdapter |
Midnight | size (typically 1; ZK proofs are heavy) |
BitcoinAdapter |
Bitcoin | hybrid |
NearAdapter |
NEAR | time, size |
NearIntentAdapter |
NEAR (DIP-4 intents) | time, size |
CelestiaAdapter |
Celestia | size (PFB has its own gas semantics) |
For each batcher the user wants, also confirm: which gas-payer key (where will the private key live in production — env var, KMS, etc.) and what batching criteria (time window? size threshold? hybrid?).
6. State machine design
Once chains, contracts, and primitives are confirmed, design the STM with the user before writing transitions. The STM is where every write to the DB happens; getting its shape wrong means rewriting all the dependent layers. Specifically:
- All writes go through the STM. The HTTP API is GET-only by default —
/api/*routes read from the database but never write. This is what keeps the system deterministic (every replay produces the same state) and crash-safe (no double-writes on retry). Do not generatePOST/PATCH/DELETEroutes without an explicit user request, and even then, treat that as an escape hatch and document why determinism doesn't matter for that endpoint. - One transition per user-meaningful action. For each grammar key, ask the user what the on-chain trigger is (a tx? a chain event? a scheduled tick?) and what the resulting DB writes should be. Sketch the input → state-transition → DB-write chain before scaffolding.
- Ask clarifying questions. If the user said "store ADA sent to my address," reasonable questions are: only
lovelaceamount, or full UTxO metadata? Do we care about the sender too? Should multiple incoming UTxOs in one tx be one row or several? What's the unique constraint —(tx_hash, output_index)? Asking up front is much cheaper than reshaping the schema later. - Echo back the design in a short bullet list before scaffolding (
grammar.tskeys, STM transitions, DB tables, API endpoints). Get the user's "yes" on that summary before writing code.
If anything is ambiguous, ASK. The skill assumes you'll do the interview; downstream steps are written for the case where chains + sync protocols + primitives + contracts + batchers + STM are already agreed.
Core invariants (the rules that keep the template buildable)
These hold across every Effectstream template. Violating any of them produces errors that are slow to diagnose because they surface far from the cause.
Flat
packages/*layout, noclient/,shared/, orsrc/wrapper dirs. Grammar, config, STM, and API all live directly inpackages/node/. The orchestrator config (start.dev.ts) lives at the project root, not inside the node package.No raw SQL outside
packages/database/sql/*.sqlandpackages/database/migrations/*.sql. Every other file (state machine, API routes, tests) must use pgtyped-generatedPreparedQueryobjects exported from@my-template/database. UseWorld.resolve(query, params)inside STM transitions andrunPreparedQuery(query.run(params, dbConn), label)in API routes. The one exception is reading the engine's internaleffectstream.sync_protocol_paginationtable for NTP recovery — that schema isn't owned by the template.NEVER hand-write
*.queries.tsfiles. These files contain byte-offsetlocsarrays that pgtyped uses to substitute parameters into the SQL string. A hand-writtenlocsthat's off by even one character produces malformed SQL → Postgres aborts the transaction → the sync node crashes mid-STM withcurrent transaction is aborted (code 25P02), hiding the original error. The bug is invisible to TypeScript (theIQueryParamsinterface still typechecks) and invisible to static review (the file looks plausible). It only surfaces at runtime under a real integration test. The file MUST be generated by runningbun run build:pgtypes(which invokes@effectstream/db/scripts/pgtyped-update.ts). Commit the generated output — but if you find yourself typing.queries.tscontent into aWritetool call, STOP and run the script instead. There is no "I'll just stub it out" shortcut here; the byte offsets cannot be eyeballed.All
@effectstream/*packages share a single coordinated version. Never mix0.100.13and0.100.5across@effectstream/*deps. To find the version to pin: (a) query npm first withnpm view @effectstream/orchestrator version(any@effectstream/*package works; they're always co-versioned). (b) If npm returns nothing, fall back to the"version"field of the engine monorepo's rootpackage.json— find it by walking up from the cwd looking for apackage.jsonthat has apackages/effectstream-sdk/sibling, or ask the user where the engine repo lives. (c) If neither works, ask the user explicitly rather than guessing. Never leave<latest>placeholders —bun installwon't resolve them.Declare every sibling import as
workspace:*in the importer'spackage.json. Bun resolves these through the workspace graph (nonode_modules/<scope>/<pkg>symlinks are needed on Mac). E.g.packages/node/package.jsonmust include"@my-template/database": "workspace:*"and"@my-template/contracts-evm": "workspace:*"; without these declarations the import fails at runtime withCannot find module '@my-template/database'. The reference templates useworkspace:*everywhere — match that. Some chains have nested workspace subpackages that need to be listed explicitly in the rootworkspacesarray becausepackages/*doesn't recurse (see the relevantreferences/chains/{chain}.md). Docker is the exception where workspace resolution doesn't "just work" — the Dockerfile creates the symlinks inline afterbun install(seereferences/docker.md).link.shis not part of a normal template — it's an advanced helper for engine+template co-iteration only; do not generate one by default.Always use
{ cwd: path.join(root, "packages/contracts-X") }, never{ resolveFrom }, for chain launchers.resolveFromgoes throughrequire.resolvewhich lives in Bun's.bun/cache and breaks both locally (resolves the wrong path) and in Docker (nonode_modules/@my-template/*symlinks).cwduses direct filesystem paths and works everywhere.Compile every contract package before building anything that depends on it. Each chain has its own build script (
bun run build:<chain>); after SQL changes also runbun run build:pgtypes. Cascading failures from skipped compile steps are the #1 cause of "the node won't start" debugging sessions. Per-chain build commands live inreferences/chains/{chain}.md.Multi-environment uses file-name suffixes (
config.dev.ts/config.mainnet.ts), not env-var switches. Each environment has its own entry point (main.dev.ts/main.mainnet.ts) that imports its corresponding config. The orchestrator runs only in dev — mainnet runs the node directly.BatcherConfig.namespaceand the frontend'sEffectstreamConfigappNamemust match exactly. A mismatch produces401 Invalid signaturefrom the batcher's/send-inputendpoint — the signed message containsappName, the batcher validates againstnamespace.Some chain primitives are opt-in because of indexing/runtime cost — confirm with the user before adding any of them. Default-on primitives scan every block of every chain they target; adding one speculatively means the production indexer does extra RPC work forever. The per-chain reference files enumerate which primitives are opt-in vs default (e.g. EVM's
PrimitiveTypeEVMEffectstreamL2, Cardano'sCARDANO_SUBMIT_TXdev process, Midnight's nullifier tracking). When the user has confirmed a primitive is needed, the chain file has the wiring; when they haven't, leave it out — the symptoms of a missing-but-needed primitive are silent ("blocks finalize but the row never appears"), so the right time to confirm is during Discovery, not later.
Build order
Follow this order strictly. Each step depends on the previous one being verified-working. Don't try to scaffold everything in parallel and then debug — verify each layer in turn.
For each step the right-hand column tells you which reference file to load. Don't preload them; load on demand to keep context lean.
| # | Step | Reference to load | Concept docs (read on demand) |
|---|---|---|---|
| 0 | Discovery is done (chains, contracts, primitives, STM design confirmed with the user — see Discovery). Pick a template name → @my-template/* scope. |
references/architecture.md |
docs/site/docs/home/0-intro/1-what-is-effectstream.md, docs/site/docs/home/100-components/100-components.md, docs/site/docs/home/200-chains/200-chains.md (chain Feature Support Matrix) |
| 1 | Root package.json (workspaces, effectstream.default, scripts). |
references/architecture.md |
docs/site/docs/home/500-packages/550-tools/orchestrator.md (consumer of effectstream.default) |
| 2 | start.dev.ts at project root (orchestrator config — declares chains/services). |
references/orchestrator.md |
docs/site/docs/home/100-components/106-processes.md, docs/site/docs/home/500-packages/550-tools/orchestrator.md |
| 3 | For each chain: packages/contracts-{chain}/. Compile and verify before moving on. |
references/chains/{chain}.md |
docs/site/docs/home/100-components/105-contracts.md, docs/site/docs/home/200-chains/211-contracts.md, docs/site/docs/home/200-chains/{201-evm,202-midnight,203-cardano,204-avail,205-bitcoin,209-celestia,210-near}.md |
| 4 | packages/database/ (migrations + pgtyped .sql). Run bun run build:pgtypes to generate sql/*.queries.ts — never write the generated file by hand (see Core invariant §2). Verify the script exits 0 before moving on. |
references/database.md |
docs/site/docs/home/100-components/109-database.md, docs/site/docs/home/1000-effectstream-engine/1002-database.md (three-schema split: effectstream/primitives/public), docs/site/docs/home/500-packages/520-node/db.md |
| 5 | packages/node/ (grammar → config.dev.ts → state-machine.ts → api.ts → main.dev.ts). |
references/grammar-stm.md |
docs/site/docs/home/100-components/102-state-machine.md, docs/site/docs/home/100-components/111-grammar.md (incl. &-reserved keys + mapPrimitivesToGrammar), docs/site/docs/home/100-components/103-api.md, docs/site/docs/home/100-components/117-node-startup.md, docs/site/docs/home/100-components/118-primitives.md, docs/site/docs/home/100-components/113-randomness.md, docs/site/docs/home/500-packages/520-node/{sm,runtime}.md |
| 6 | (optional) packages/batcher/ with adapter factories + batcher.dev.ts. |
references/batcher.md |
docs/site/docs/home/100-components/108-batcher/{1200-overview,1220-core-concepts,1240-configuration}.md, docs/site/docs/home/500-packages/550-tools/batcher-sdk.md |
| 7 | (optional) packages/frontend/. |
references/frontend.md |
docs/site/docs/home/100-components/115-frontend.md, docs/site/docs/home/100-components/112-wallets.md, docs/site/docs/home/500-packages/{550-tools/frontend-sdk,510-sdk/wallets}.md |
| 8 | packages/tests/ — phases A (infra) and B (STM+DB+API) MANDATORY for every template; C (frontend) if a frontend exists. The test contents adapt to whichever chains the template targets — the structure is constant. Source of truth: templates/evm-midnight-v2/packages/tests/. A template without tests is unfinished — the tests are how anyone (including you) knows the scaffold actually boots and indexes. |
references/tests.md |
(skill-only; docs have no Phase A/B/C testing concept) |
| 9 | (optional) config.mainnet.ts, main.mainnet.ts, batcher.mainnet.ts, "start:mainnet" script. Every *.mainnet.ts file MUST start with a "PLACEHOLDER FOR PRODUCTION" disclaimer comment telling the user to replace hard-coded values and source secrets from env vars — see the multi-env reference for the exact block. |
references/multi-env.md |
docs/site/docs/home/300-deployment/301-deploy-game.md, docs/site/docs/home/100-components/199-environment-variables.md |
| 10 | (optional, only if the user asks for containerization) Dockerfile + .dockerignore. Most templates don't need Docker on day one — skip unless the user explicitly requests it. |
references/docker.md |
(skill-only; docs have no Docker section) |
| 11 | README.md at project root following the canonical structure. |
references/readme.md |
(skill-only style guide) |
| 12 | Verify — MANDATORY, incremental, stop on error. Run each command separately and check it succeeded before moving to the next: bun install → bun run build:evm (per chain) → bun run build:midnight → bun run build:pgtypes → bun run dev (must reach a known boot state) → bun run test. If Docker was requested, also docker build then docker run <image> bun run test. Don't report completion until at least bun run dev has booted. Full criteria: Verification — mandatory, incremental, stop on error. |
— | — |
Why orchestrator before contracts
The orchestrator declares which chains the template targets, which fixes the set of contract packages you need to create and the npm scripts each one must expose for its launcher (launchEvm needs hardhat:start, launchMidnight needs midnight-node:start, etc.). Designing contracts first leads to mismatches with the launcher's expectations.
Why grammar before state machine
The grammar (grammar.ts) is referenced via typeof grammar in new Stm<typeof grammar, {}>(grammar), so the state machine's addStateTransition("key", ...) calls and parsedInput types come from it. Writing transitions before the grammar means rewriting them.
Why compile + pgtypes before node
packages/node/ imports @my-template/contracts-evm (for contractAddressesEvmMain()) and @my-template/database (for the PreparedQuery exports). Both are generated outputs — uncompiled contract artifacts produce export {}; missing .queries.ts files leave the imports unresolvable. The node won't even typecheck.
Decision cheat sheet (for the Discovery interview)
Use these to frame trade-offs for the user — they don't override the rule that the user makes the call.
Chains. Suggest the smallest set: EVM if the use case is fungible-token / NFT / arbitrary contract logic. Midnight only when ZK / private state is required. Cardano when stake-pool, UTxO, or native-asset semantics matter. Bitcoin when watching addresses or ordinals. Multi-chain templates (EVM + Midnight, EVM + Cardano) are common but every additional chain doubles the orchestrator/test surface AND adds an indexer cost in production.
Batcher. Suggest yes if the frontend submits user transactions and you want gasless UX or time/size amortization. Suggest no if the frontend builds and submits transactions directly (e.g. Cardano via Lucid Evolution). A no-batcher app's node API stays GET-only.
Frontend. Suggest yes if the user wants a UI for wallet connection + on-chain writes via the batcher. Suggest no if the user just wants an indexer with an HTTP read API. Don't scaffold a frontend that does nothing.
Mainnet support. Suggest yes for anything shippable. Skip only for local-only experimentation. Mainnet implies a *.mainnet.ts set with env-var validation and a "PLACEHOLDER FOR PRODUCTION" disclaimer at the top of each file (see references/multi-env.md).
Docker. Suggest no unless the user asks for containerization. Most templates don't need Docker on day one.
Verification — mandatory, incremental, stop on error
Verification is not optional. The scaffold is not done until bun run dev has booted to a known state (success, or a specific recorded failure you couldn't fix). See the banner at the top of this file for the rules. This section gives the per-step acceptance criteria.
Ground rules
- One step at a time. Run each command separately. Do NOT chain them with
&∨— when something breaks, you need to know which step. The cost of running six commands instead of one is trivial compared to debugging a batched failure. - Stop on the first failure. Don't continue with later steps hoping they'll mask the earlier one. Don't add
|| true,--ignore-scripts,--force, or--no-verifyto work around an error. Diagnose, fix, re-run the failed step, then move on. - Check, don't assume. Look at the actual exit code and output before declaring success.
bun installprintingerror:and exiting non-zero is a failure, even if it produced somenode_modules/. - Tools may already be installed. Before reporting "I couldn't run X because Y isn't available," actually try to run X. The engine monorepo and developer machines typically have
bun,forge,compact,dockerinstalled; assume they're there until a command actually fails.
Step 0 — Environment probe (do this BEFORE scaffolding)
Run which bun 2>&1 first — if bun is missing, stop immediately. You can't build or verify anything without it.
Then, for each chain the template targets, open the chain's reference file and run the which command from its Tools (probe before scaffolding) section. The chain reference is the source of truth for what binaries each chain needs and what to do if one is missing:
| Chain | Reference (Tools section) |
|---|---|
| EVM | references/chains/evm.md — needs forge |
| Midnight | references/chains/midnight.md — needs compact |
| Cardano | references/chains/cardano.md |
| Bitcoin | references/chains/bitcoin.md |
| Avail | references/chains/avail.md |
| Celestia | references/chains/celestia.md |
| NEAR | references/chains/near.md |
If Docker was requested, also run docker --version 2>&1.
If a chain's required tool is missing, surface this to the user before writing any code — don't scaffold something you can't verify. When adding a new chain to the skill, add its tool list to the chain's own reference (not here) so the entry-point remains a router.
Step 1 — bun install
Run at the project root. Accept only: exit code 0, no error: lines in stderr. Workspace sibling deps declared as workspace:* resolve through Bun's workspace graph (no node_modules/<scope>/<pkg> symlinks expected or needed on Mac).
Common failures:
error: package "@effectstream/X" not found→ version pinned to a tag that doesn't exist on npm. Checknpm view @effectstream/orchestrator versionand re-pin.Cannot find package @midnight-ntwrk/wallet-sdk-address-formatlater at runtime → add it to the rootpackage.json(phantom dep).
Step 2 — bun run build:<chain> (per chain in the template)
Run the per-chain build script for every chain the template targets. Each one is bun run build:<chain> (e.g. build:evm, build:midnight, build:near).
Accept only: exit code 0 AND the chain's generated artifacts exist and are non-empty (not export {}). Until this passes, packages/node/ can't import the chain's generated bindings and won't typecheck. Per-chain acceptance criteria (which artifact path to check, what counts as "non-empty") live in references/chains/{chain}.md. If a build fails with version-mismatch or toolchain errors, the chain file's Sharp edges is the first place to look.
Step 3 — bun run build:pgtypes
Accept only: exit code 0 AND packages/database/sql/*.queries.ts files exist AND are non-empty AND contain PreparedQuery instances (not export {}). Empty output means the script ran from the wrong cwd — use the root script, not raw pgtyped.
Step 4 — bun run dev (MANDATORY — every template must reach this)
This step is required before reporting completion. It's the only verification that catches runtime issues like missing workspace:* declarations, Cannot find module errors, port collisions, and orchestrator-config bugs that all earlier static checks miss.
Accept only one of:
- Success path: Orchestrator boots, chain nodes are up on expected ports (8545 / 9944 / 8088 / etc.), sync node logs
indexing block N, noCannot find moduleorcurrent transaction is abortederrors. ThenCtrl-Ccleanly. Record the highest block height observed. - Recorded-failure path: A specific command failed with a specific error message. Quote both verbatim in your final report. Do NOT round "didn't reach a stable state" off to "looks fine."
The wrong outcomes:
- "I didn't run it because the user probably doesn't have
<tool>" — check before assuming, see Step 0 and the chain file's Tools section. - "I ran
tscand it typechecked" — typechecking does not verify boot. - "It ran for 30 seconds, looked OK, I killed it" — record what state it reached. "Looked OK" is not an acceptance criterion.
If bun run dev doesn't terminate on its own (which it shouldn't — it's a long-running process), let it run until you see the sync node indexing AND any chain-specific deployment processes complete, then stop it cleanly. Use the orchestrator's stop command or Ctrl-C, not kill -9.
Step 5 — bun run test
Accept only: Phase A passes (chains up), Phase B passes (tx → DB → API round trip), and Phase C passes if a frontend exists. The Playwright render test in Phase C catches Vite/browser-only bugs that vite build succeeds on (e.g., node-fetch polyfill blowing up at mount). If any phase fails, fix the underlying issue — don't skip the phase.
Step 6 — docker build (only if Docker was opted in)
Accept only: exit code 0. Catches missing chain-specific system deps and the workspace-symlink workaround. Bun on Linux does NOT resolve workspace siblings the way it does on Mac, so the Dockerfile MUST create node_modules/<scope>/<pkg> symlinks inline after bun install. Per-chain Docker requirements (system deps to install, binaries to vendor) are in references/docker.md and the relevant references/chains/{chain}.md.
Step 7 — docker run <image> bun run test (only if Docker)
Accept only: the in-container test run passes. Catches cwd vs resolveFrom issues in the orchestrator config that only fail on Linux/Docker.
Sharp edges you will hit if you skip the references
These are the chain-agnostic failure modes that have wasted the most engineering time. Each one has full context in the linked reference file — flagging them here so the agent recognizes the cryptic error when it happens.
Cannot find module '@my-template/database'at runtime, despitebun installsucceeding → the importer'spackage.jsonis missing"@my-template/database": "workspace:*". Bun resolves siblings through the workspace graph but only for declared deps. In Docker, also confirm the Dockerfile creates the inline workspace symlinks (Linux doesn't resolve them automatically). →references/architecture.md,references/docker.mdCannot find module '@midnight-ntwrk/wallet-sdk-address-format'→ it's a phantom dependency (declared by no one but required at runtime). Every template must add it to the rootpackage.json. →references/multi-env.md401 Invalid signaturefrom batcher/send-input→BatcherConfig.namespace≠ frontendEffectstreamConfig.appName. Both must equal the same string (often""or the template name). →references/batcher.md- pgtyped generates empty
.queries.ts→ script was run from the wrong working directory. Always run viabun run build:pgtypes. →references/database.md - Sync node crashes with
current transaction is aborted (code 25P02)after submitting a tx → the.queries.tsbyte-offsetlocsare wrong, almost always because someone hand-wrote the file. Runbun run build:pgtypesto regenerate. The 25P02 is a cascade error; the real error is whatever pgtyped's malformed substitution produced one statement earlier. → Core invariant §2,references/database.md - Frontend builds fine but blank page in browser →
node-fetchin the bundle callsrequire("fs").promisesat module init. Alias it to a native-fetch shim invite.config.ts. The Playwright render test catches this;vite builddoesn't. →references/frontend.md - MQTT BlockWatcher silently never fires → Bun's
wsmodule is missingcreateWebSocketStream. Frontend must setVITE_IS_BUN=trueto fall back to HTTP/block-heightspolling. →references/frontend.md - Orchestrator works locally but
Cannot find module @my-template/contracts-Xin Docker → the orchestrator launcher usesresolveFrominstead ofcwd. →references/orchestrator.md
Chain-specific sharp edges (Midnight version matrix, Cardano CARDANO_SUBMIT_TX filter, EVM PrimitiveTypeEVMEffectstreamL2 opt-in, NEAR Rust toolchain, …) live in each chain's reference file under its Sharp edges section. Always read references/chains/{chain}.md for every chain the template targets before scaffolding it.
What to give the user when you're done
Do not report completion until bun run dev has booted to a known state. See Verification — mandatory, incremental, stop on error. Once it has, report:
- Where it is — full path to the template directory.
- What chains/features it includes — e.g. "EVM (Hardhat) + batcher + Vite/React frontend + Phase A/B/C tests + Dockerfile + mainnet config".
- How to run it —
bun install && bun run dev, plus the frontend URL (defaulthttp://localhost:10599). - Verification trail — quote what you actually ran, in order. Default framing is "I ran it and here's what happened," not "you should run this." For each step in Verification, name the command, the exit code or key output (highest block height, contract address, test count), and any errors. Example:
bun install→ exit 0;bun run build:evm→Erc1155DevModule#MCT_ERC1155deployed to0x5FbD…;bun run dev→ orchestrator booted, sync node at block 14, frontend responding at http://localhost:10599. If a step failed and you couldn't fix it, name the exact command and exact error verbatim, plus what you tried — don't paraphrase. Speculating "the tools are probably not installed" without checking is not an acceptable failure mode. - Next steps that the user must do — e.g. fill in real RPC URLs for
config.mainnet.ts, customize the grammar/STM for actual app logic (the scaffold ships with a placeholdercreateRoomexample).
Engine features to know about (don't reinvent these)
Before adding custom code, check whether the engine already ships the feature. The docs (under docs/site/docs/home/) are the source of truth for concepts; this skill is the source of truth for operational gotchas. Specifically:
&-prefixed grammar keys are reserved for engine system commands. Built-ins include&B(batched inputs),&createAccount,&linkAddress,&unlinkAddress. Never define a custom grammar key starting with&. Seedocs/site/docs/home/100-components/104-l2-contract.mdanddocs/site/docs/home/100-components/111-grammar.md.- EffectStream Accounts (multi-wallet linking, primary address). If the user needs "the same user across multiple wallets," wire up
&createAccount/&linkAddressrather than rolling your own. Seedocs/site/docs/home/100-components/116-accounts.md. - Deterministic randomness via
data.randomGenerator(Prando-based, seeded by block hash). Calls are stateful — call order matters for determinism. Seedocs/site/docs/home/100-components/113-randomness.md. - PRC-1 Achievements — built-in HTTP surface for leaderboards and achievement metadata. If the user mentions achievements or leaderboards, use
export const achievementsinstead of bespoke endpoints. Seedocs/site/docs/home/100-components/114-achievements.mdanddocs/site/docs/home/400-paima-standards/prc1.md. mapPrimitivesToGrammarhelper auto-derives grammar entries from primitives — shrinksgrammar.tssubstantially when you have many chain primitives. Seedocs/site/docs/home/100-components/111-grammar.md.- Three database schemas —
effectstream(engine internals likesync_protocol_pagination),primitives(per-primitive tracking),public(your tables). Seedocs/site/docs/home/1000-effectstream-engine/1002-database.md. Knowing the split disambiguates "engine table vs my table" when writing custom queries. /api/*built-in endpoints —/health,/block-heights,/addresses,/scheduled-data,/tables/:name,/primitives/:name,/rpc/evm,/grammar, and an OpenAPI explorer at/documentation. Don't add a redundant custom/health. Seedocs/site/docs/home/100-components/103-api.md.- Orchestrator CLI is more than just
startandstop:status,restart <name>,logs <name>,silence/unsilence,list. Useful for README and operations guidance. Seedocs/site/docs/home/500-packages/550-tools/orchestrator.md. - PRCs (Paima/Effectstream standards) — PRC-1 achievements, PRC-6 Midnight dApp metrics, etc. If the user is building a discoverable or interoperable app, reference the relevant PRC. See
docs/site/docs/home/400-paima-standards/. db-emulatorfor in-memory unit tests (no real PGLite). Skill'sreferences/tests.mduses real PGLite; emulator is a faster alternative for narrow unit tests. Seedocs/site/docs/home/500-packages/520-node/db-emulator.md.@effectstream/precompile— for compiling Compact/zkir at build time. Seedocs/site/docs/home/500-packages/510-sdk/precompile.md.config.dev.resetPublicData— development flag that truncates thepublicschema on each boot. Useful in templates with a "reset" command. Seedocs/site/docs/home/100-components/117-node-startup.mdanddocs/site/docs/home/1000-effectstream-engine/1002-database.md.
When NOT to use this skill
This skill scaffolds the structure. It does not write the application's actual game/business logic — the grammar, STM transitions, and DB schema you'll generate are placeholders meant to be replaced. If the user is asking to add a new action to an existing template, edit a state-machine transition, fix a bug, or modify game rules, just edit the relevant file directly; you don't need this skill's full workflow.
Likewise, if the user is asking conceptual questions about Effectstream architecture without intending to create a new project, answer directly — don't trigger this skill to dump references. Point them at the right doc under docs/site/docs/home/ instead.