Thanks for your interest in TokenStork. This guide covers how to set up a local environment, the conventions the codebase follows, and what a good PR looks like.
If you are here to report a bug or suggest a feature, the issue tracker is the right place — skip to the Filing issues section.
- File an issue for a bug, a regression, or a concrete feature request.
- Open a PR for a fix, a small feature, or a documentation improvement.
- Discuss before you build for anything larger — a UI rework, a new pipeline stage, a new data source. Open an issue first so we can agree on scope before you spend time on it.
- Add or refine BCMR metadata — edit public/.well-known/bitcoin-cash-metadata-registry.json.
Good first issues are tagged good first issue when available.
- Node.js 22 (tested on 22.22.2).
- Postgres 17 with the
pg_trgmextension available (shipped with the stockpostgresqlpackage on Debian 13+). - pnpm 10+, pinned via the
packageManagerfield inpackage.json. Runcorepack enableonce after installing Node 22 and pnpm will auto-activate at the pinned version on firstpnpminvocation. - A reachable BCH node is only needed if you plan to run the sync workers. The SvelteKit app runs fine against an empty schema.
git clone https://github.com/Panmoni/tokenstork.git
cd tokenstork
pnpm install --frozen-lockfile
createdb tokenstork
export DATABASE_URL="postgres:///tokenstork"
pnpm run db:init
pnpm run dev # http://localhost:5173
Optional environment variables (all read by the app; all safe to leave unset):
CRYPTO_COMPARE_KEY— unlocks/api/bchPrice.FEAR_AND_GREED_API_KEY— unlocks/api/fearAndGreed.PUBLIC_WALLETCONNECT_PROJECT_ID— required for the wallet-login + airdrop wizard's WalletConnect v2 flow. Get one from https://cloud.walletconnect.com; the app falls back to the paste-signed-hex flow if absent, which is fine for non-airdrop development.
The airdrop wizard + mint broadcast paths additionally need a reachable BCH node + BlockBook indexer:
BCHN_RPC_URL(defaulthttp://127.0.0.1:8332) +BCHN_RPC_AUTH(user:password) — used by/api/mint/broadcastand/api/airdrops/[id]/broadcastto forward signed txs to BCHN'ssendrawtransaction.BLOCKBOOK_URL(defaulthttp://127.0.0.1:9131) — used by the airdrop builder (src/lib/server/walletUtxos.ts) to fetch the sender's UTXO set. The default response includes mempool, which is required for chunk-chaining. If you point at a different BlockBook deployment, verify it returns mempool entries.
For worker development (the Rust workers/ crate) you will additionally need BCHN_ZMQ_URL and the same BCHN_RPC_* + BLOCKBOOK_URL values.
Run both of these and make sure they pass:
pnpm run check # svelte-check + tsc
pnpm run build # production build
If you touched worker code under scripts/ or lib/:
pnpm run typecheck # scripts/tsconfig.json
If you changed the schema in db/schema.sql, confirm it still applies cleanly on an empty database and on a database that already has the previous version (the file is idempotent by design — please keep it that way).
The codebase is deliberately terse. A few things that come up often in review:
- Comments explain why, not what. Well-named identifiers already tell the reader what code does. Write a comment when there is a hidden constraint, a workaround for a known bug, or an invariant a future reader would otherwise miss. Do not write comments that narrate the diff ("added for X", "fix for issue #42") — that belongs in the commit message.
- No speculative abstractions. Three similar lines beats a premature helper. Build the abstraction when the third caller actually shows up.
- No error handling for impossible cases. Trust internal callers and framework guarantees. Validate at system boundaries — user input, external APIs, BCMR payloads, chain data — and nowhere else.
- Preserve
NUMERIC(78,0)precision. Supply and balance columns hold values larger thanNumber.MAX_SAFE_INTEGER. Stringify at the API boundary. If you find yourself writingNumber(row.current_supply), stop. - Respect the
BYTEAboundary.categoryand*_txidcolumns are raw 32-byteBYTEA. Hex encode only when crossing into JSON responses. The helpers in src/lib/server/db.ts exist for this. - All outbound HTTP goes through
timedFetch. See src/lib/server/fetch.ts. External calls without an explicit timeout are a reliability bug. - Server-only code lives under
src/lib/server/. SvelteKit refuses to bundle it into the client, which is exactly what we want for DB credentials and upstream API keys.
TokenStork uses Conventional Commits with scopes. Look at recent git log output for the house style. Examples from the tree:
feat(sveltekit): build directory home with Postgres-backed SSR loader
fix(format): harden IPFS URL fallthrough and preserve NUMERIC precision
infra(caddy): switch Caddyfile to Cloudflare Origin Certificate path
chore(deps): regenerate lockfile under pnpm 10
docs(todo): note cloudflare + DNS actions for tokenstork.com
Guidelines:
- Subject in the imperative ("add X", not "added X" or "adds X").
- Keep the subject under 72 characters. Detail goes in the body.
- One logical change per commit. If your PR touches unrelated files, split it.
- The body explains why. Surrounding context, the trade-off considered, the bug this addresses — whatever a reviewer a year from now will need.
Scopes in active use include: sveltekit, infra, caddy, api, deps, repo, gitignore, format, theme, nav, errors, directory, server. Add a new scope if none of these fit.
- Fork and branch. Work off a topic branch named for the change (
fix/ipfs-fallthrough,feat/nft-tab). The project branches offmain. - Keep PRs small and focused. Reviewers can spot regressions in 50 lines much more reliably than in 500.
- Write a real description. What does this change? Why is it needed? What did you consider and reject? A PR description that is just the commit subject will be asked to expand.
- Link the issue the PR closes (
Closes #123) when one exists. - Include a test plan. Even a three-bullet manual checklist is enough for a UI change ("directory loads, search returns matches, token detail renders").
- Do not mix formatting churn with logic changes. Run the formatter as its own commit if needed.
- Rebase, don't merge. Keep branch history linear against
main.
Bugs:
- What happened.
- What you expected to happen.
- Steps to reproduce — ideally a URL on
tokenstork.comor a minimal command sequence locally. - Your environment (OS, browser for UI bugs; Node + Postgres versions for backend bugs).
Feature requests:
- The problem you are trying to solve. Not the solution you have in mind.
- Who benefits.
- Whether this fits TokenStork's scope (a CashToken directory and explorer) or is better suited to a different project.
Security issues: do not file public issues for security vulnerabilities. Email security@panmoni.com instead.
The near-term, version-by-version roadmap lives in TODO. The architectural plan and the VPS runbook live in docs/cashtoken-index-plan.md. If your idea does not obviously fit either document, open an issue to discuss before writing code.
TokenStork deliberately does not depend on Chaingraph, third-party BlockBook instances, or any other external blockchain indexer. PRs that reintroduce such a dependency will not be merged. BCMR feeds and the Cauldron price endpoint are the only external HTTP calls in the pipeline, and both are narrowly scoped.
By contributing, you agree that your contributions will be licensed under the MIT License that covers the project.