Zerochord

Local development

Toolchain, install, build, test, and running each application.

Toolchain

ToolVersion
Node.js24.19.0
pnpm11.22.0
Aiken1.1.23

The root package.json pins the Node engine and the package manager, and onchain/aiken.toml pins the Aiken compiler.

The repository keeps a local copy of these under .toolchain/, activated with:

source .toolchain/env.sh

That script puts the pinned Node and Aiken on PATH and sources secrets/koios.env when it exists, so a Koios bearer token is picked up without living in your shell profile. Both directories are excluded from version control.

Install and build

pnpm install
pnpm build
pnpm test
node apps/cli/dist/main.js help

pnpm build compiles packages/** first, then apps/**.

Root scripts

ScriptWhat it does
pnpm buildCompiles every package, then every application.
pnpm typecheckRuns tsc across every workspace package.
pnpm lintESLint across the repository.
pnpm testThe full Vitest suite, including tests that read the live chain.
pnpm test:unitThe suite without live-*.test.ts files.
pnpm test:watchVitest in watch mode.
pnpm cleanRemoves dist and build info from every package.

Two TypeScript versions, on purpose

Source is compiled by TypeScript 7.0.2, the native port.

typescript-eslint 8.67.0 declares a peer range that excludes TypeScript 7 and refuses to load against it, because the TypeScript 7 package exports only a version string rather than the classic compiler API.

So tools/lint is a workspace package whose only dependencies are typescript-eslint 8.67.0 and typescript 6.0.3. Under pnpm's isolated node_modules the linter resolves the classic API from there, while every source package is compiled by TypeScript 7.0.2. eslint.config.js at the root loads the linter from that path.

TypeScript 6.0.3 exists only as the linter's own peer and compiles nothing.

TypeScript 7 changed three defaults, and the project sets all three explicitly rather than inheriting them: strict, module and types.

Testing

The suite is Vitest 4.1.11.

vitest.config.ts includes packages/**/src/**/*.test.ts and apps/**/src/**/*.test.ts, and excludes e2e. It aliases each @cardano-swap/* package to its source, so tests run against source rather than build output.

Test files run one at a time

fileParallelism is off. Almost every suite reads the live chain, and several build real transactions from the one operator wallet. Run in parallel they contend for the same wallet UTxO and hammer the provider hard enough to be rate limited, which makes tests fail for reasons that have nothing to do with the code under test.

A slower suite that means what it says is worth more than a fast one that fails at random.

Live tests

Files named live-*.test.ts read the live chain. pnpm test:unit excludes them.

A Koios bearer token raises the rate limit well above the anonymous tier. Reads work without one, and a full pool discovery across several venues is rate limited without it. Put it in secrets/koios.env, which .toolchain/env.sh sources.

Tests that would spend or emit a transaction are gated behind their own environment variables and do nothing by default. See Configuration reference.

Two emulators, chosen for a measured reason

The Lucid Evolution emulator imports no UPLC evaluator at all. It performs ledger bookkeeping only, so it cannot catch a validator logic bug and its execution unit figures are whatever it was given.

@blaze-cardano/emulator imports a real evaluator through @blaze-cardano/vm and checks budgets, raising on an underestimated budget.

Use @blaze-cardano/emulator for any test where script correctness or execution unit accuracy matters. Use @lucid-evolution/lucid for building transactions. This is a deliberate two library split.

Running the web application

cp apps/web/.env.example apps/web/.env
pnpm --filter @cardano-swap/web dev

VITE_CARDANO_SWAP_NETWORK must be set. There is no default network, and the app throws a named error when it is missing.

The build replaces three Node modules with browser implementations, so the same configuration loader runs in both places. src/shims/node-fs.ts serves the bundled config/<network>.json files from a fixed virtual directory, and any other path throws. That keeps the browser and the command line from drifting apart on the asset set.

End to end tests

pnpm --filter @cardano-swap/web test:e2e

Playwright starts a Vite server on 127.0.0.1:5273 and runs against live preprod, so the quote the test asserts on is a real one at the chain tip of the moment. Set CARDANO_SWAP_E2E_NETWORK=mainnet for the read only run that proves nothing in the interface is tied to one network.

The signing step is skipped rather than faked: a headless browser has no CIP-30 extension, and a faked one would test the fake.

Running this documentation site

pnpm --filter @cardano-swap/docs dev

Serves on port 3100.

pnpm --filter @cardano-swap/docs build

Prerenders every page to static HTML in docs/.output/public. docs/serve.json carries the rewrite rule a static host needs, sending anything outside /api/ to the shell.

Content lives in docs/content/docs, and navigation order comes from the meta.json file in each directory. A directory whose meta.json sets "root": true becomes a top level tab.

The on-chain project

cd onchain
aiken check
aiken build

aiken build writes the blueprint to onchain/plutus.json, which is what cardano-swap deploy show and deploy settlement read.

aiken check runs the validator's own tests, which are in validators/settlement.ak alongside the validator.

Repository conventions

Internal artifacts, working notes and scratch work live in resources/ and scratch/, both excluded from version control. Anything worth keeping becomes a command or a document.

footprint.*.json is a published artifact and is committed, which the .gitignore states explicitly.

On this page