Local development
Toolchain, install, build, test, and running each application.
Toolchain
| Tool | Version |
|---|---|
| Node.js | 24.19.0 |
| pnpm | 11.22.0 |
| Aiken | 1.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.shThat 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 helppnpm build compiles packages/** first, then apps/**.
Root scripts
| Script | What it does |
|---|---|
pnpm build | Compiles every package, then every application. |
pnpm typecheck | Runs tsc across every workspace package. |
pnpm lint | ESLint across the repository. |
pnpm test | The full Vitest suite, including tests that read the live chain. |
pnpm test:unit | The suite without live-*.test.ts files. |
pnpm test:watch | Vitest in watch mode. |
pnpm clean | Removes 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 devVITE_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:e2ePlaywright 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 devServes on port 3100.
pnpm --filter @cardano-swap/docs buildPrerenders 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 buildaiken 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.