create-effectstream-app

star 80

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.

effectstream By effectstream schedule Updated 6/3/2026

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:

  1. bun run dev MUST run at least once before you report completion. Not tsc, not vite build, not "looks right to me" — the actual full-stack boot. If you can't get to it, the task is not done.
  2. Verify incrementally, one step at a time. Don't batch bun install && bun run build:evm && bun run build:midnight && bun run dev into 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.
  3. 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.
  4. Probe the environment before scaffolding. Run which bun 2>&1 always; then run the which check from the Tools section of each chain reference the template will use (references/chains/<chain>.md). Add docker --version if 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.
  5. 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).
  • pollingInterval and confirmationDepth — 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 cardanoTransfer primitive. 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 nullifier tracking — 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 generate POST / PATCH / DELETE routes 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 lovelace amount, 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.ts keys, 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.

  1. Flat packages/* layout, no client/, shared/, or src/ wrapper dirs. Grammar, config, STM, and API all live directly in packages/node/. The orchestrator config (start.dev.ts) lives at the project root, not inside the node package.

  2. No raw SQL outside packages/database/sql/*.sql and packages/database/migrations/*.sql. Every other file (state machine, API routes, tests) must use pgtyped-generated PreparedQuery objects exported from @my-template/database. Use World.resolve(query, params) inside STM transitions and runPreparedQuery(query.run(params, dbConn), label) in API routes. The one exception is reading the engine's internal effectstream.sync_protocol_pagination table for NTP recovery — that schema isn't owned by the template.

    NEVER hand-write *.queries.ts files. These files contain byte-offset locs arrays that pgtyped uses to substitute parameters into the SQL string. A hand-written locs that's off by even one character produces malformed SQL → Postgres aborts the transaction → the sync node crashes mid-STM with current transaction is aborted (code 25P02), hiding the original error. The bug is invisible to TypeScript (the IQueryParams interface 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 running bun run build:pgtypes (which invokes @effectstream/db/scripts/pgtyped-update.ts). Commit the generated output — but if you find yourself typing .queries.ts content into a Write tool call, STOP and run the script instead. There is no "I'll just stub it out" shortcut here; the byte offsets cannot be eyeballed.

  3. All @effectstream/* packages share a single coordinated version. Never mix 0.100.13 and 0.100.5 across @effectstream/* deps. To find the version to pin: (a) query npm first with npm 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 root package.json — find it by walking up from the cwd looking for a package.json that has a packages/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 install won't resolve them.

  4. Declare every sibling import as workspace:* in the importer's package.json. Bun resolves these through the workspace graph (no node_modules/<scope>/<pkg> symlinks are needed on Mac). E.g. packages/node/package.json must include "@my-template/database": "workspace:*" and "@my-template/contracts-evm": "workspace:*"; without these declarations the import fails at runtime with Cannot find module '@my-template/database'. The reference templates use workspace:* everywhere — match that. Some chains have nested workspace subpackages that need to be listed explicitly in the root workspaces array because packages/* doesn't recurse (see the relevant references/chains/{chain}.md). Docker is the exception where workspace resolution doesn't "just work" — the Dockerfile creates the symlinks inline after bun install (see references/docker.md). link.sh is not part of a normal template — it's an advanced helper for engine+template co-iteration only; do not generate one by default.

  5. Always use { cwd: path.join(root, "packages/contracts-X") }, never { resolveFrom }, for chain launchers. resolveFrom goes through require.resolve which lives in Bun's .bun/ cache and breaks both locally (resolves the wrong path) and in Docker (no node_modules/@my-template/* symlinks). cwd uses direct filesystem paths and works everywhere.

  6. 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 run bun 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 in references/chains/{chain}.md.

  7. 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.

  8. BatcherConfig.namespace and the frontend's EffectstreamConfig appName must match exactly. A mismatch produces 401 Invalid signature from the batcher's /send-input endpoint — the signed message contains appName, the batcher validates against namespace.

  9. 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's CARDANO_SUBMIT_TX dev 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 installbun run build:evm (per chain) → bun run build:midnightbun run build:pgtypesbun 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 && or ; — 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-verify to 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 install printing error: and exiting non-zero is a failure, even if it produced some node_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, docker installed; 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. Check npm view @effectstream/orchestrator version and re-pin.
  • Cannot find package @midnight-ntwrk/wallet-sdk-address-format later at runtime → add it to the root package.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, no Cannot find module or current transaction is aborted errors. Then Ctrl-C cleanly. 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 tsc and 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, despite bun install succeeding → the importer's package.json is 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.md
  • Cannot 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 root package.json. → references/multi-env.md
  • 401 Invalid signature from batcher /send-inputBatcherConfig.namespace ≠ frontend EffectstreamConfig.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 via bun run build:pgtypes. → references/database.md
  • Sync node crashes with current transaction is aborted (code 25P02) after submitting a tx → the .queries.ts byte-offset locs are wrong, almost always because someone hand-wrote the file. Run bun run build:pgtypes to 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 browsernode-fetch in the bundle calls require("fs").promises at module init. Alias it to a native-fetch shim in vite.config.ts. The Playwright render test catches this; vite build doesn't. → references/frontend.md
  • MQTT BlockWatcher silently never fires → Bun's ws module is missing createWebSocketStream. Frontend must set VITE_IS_BUN=true to fall back to HTTP /block-heights polling. → references/frontend.md
  • Orchestrator works locally but Cannot find module @my-template/contracts-X in Docker → the orchestrator launcher uses resolveFrom instead of cwd. → 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:

  1. Where it is — full path to the template directory.
  2. What chains/features it includes — e.g. "EVM (Hardhat) + batcher + Vite/React frontend + Phase A/B/C tests + Dockerfile + mainnet config".
  3. How to run itbun install && bun run dev, plus the frontend URL (default http://localhost:10599).
  4. 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:evmErc1155DevModule#MCT_ERC1155 deployed to 0x5FbD…; 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.
  5. 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 placeholder createRoom example).

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 &. See docs/site/docs/home/100-components/104-l2-contract.md and docs/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 / &linkAddress rather than rolling your own. See docs/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. See docs/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 achievements instead of bespoke endpoints. See docs/site/docs/home/100-components/114-achievements.md and docs/site/docs/home/400-paima-standards/prc1.md.
  • mapPrimitivesToGrammar helper auto-derives grammar entries from primitives — shrinks grammar.ts substantially when you have many chain primitives. See docs/site/docs/home/100-components/111-grammar.md.
  • Three database schemaseffectstream (engine internals like sync_protocol_pagination), primitives (per-primitive tracking), public (your tables). See docs/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. See docs/site/docs/home/100-components/103-api.md.
  • Orchestrator CLI is more than just start and stop: status, restart <name>, logs <name>, silence/unsilence, list. Useful for README and operations guidance. See docs/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-emulator for in-memory unit tests (no real PGLite). Skill's references/tests.md uses real PGLite; emulator is a faster alternative for narrow unit tests. See docs/site/docs/home/500-packages/520-node/db-emulator.md.
  • @effectstream/precompile — for compiling Compact/zkir at build time. See docs/site/docs/home/500-packages/510-sdk/precompile.md.
  • config.dev.resetPublicData — development flag that truncates the public schema on each boot. Useful in templates with a "reset" command. See docs/site/docs/home/100-components/117-node-startup.md and docs/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.

Install via CLI
npx skills add https://github.com/effectstream/effectstream --skill create-effectstream-app
Repository Details
star Stars 80
call_split Forks 41
navigation Branch main
article Path SKILL.md
More from Creator
effectstream
effectstream Explore all skills →