Skip to main content

Local development

Requirements​

  • Node 22 and pnpm 10 (the repository is a pnpm workspace: packages/*, services/*, apps/*).
  • Foundry (forge, anvil, cast). The setup script installs it from npm and pins solc 0.8.26.
  • Git submodules under contracts/lib/ (Infinity core and periphery, OpenZeppelin, forge-std).

Setup​

git clone https://github.com/camdengrieh/zk-pad.git
cd zk-pad
pnpm run setup # = bash scripts/setup-toolchain.sh (Foundry, solc 0.8.26, submodules)
pnpm install

scripts/bootstrap-refs.sh clones the reference repositories (Clanker v4, Railgun, Privacy Pools...) into /tmp/claude-0/ref/. You only need them to compare the port against upstream.

Build and test the contracts​

pnpm build:contracts # cd contracts && forge build
pnpm test:contracts # cd contracts && forge test

When several processes build at once, give each its own output directory:

cd contracts
FOUNDRY_OUT=out-mine FOUNDRY_CACHE_PATH=cache-mine forge test -vv

Tests deploy a real PancakeSwap Infinity stack (Vault, CLPoolManager, CLProtocolFeeController) from the submodules; see contracts/test/utils/InfinityDeployer.sol.

One-command local environment​

scripts/dev.sh # anvil + deploy + env files + relayer + attestor; Ctrl-C stops all
scripts/dev.sh --demo # ... and drive one full claim through the relayer
scripts/dev.sh --ci # start, run the demo, stop, exit with the demo's status

What it does:

  1. Starts anvil (chain id 31337, port ANVIL_PORT, default 8545).

  2. Runs contracts/script/LocalDev.s.sol: the Infinity stack from the submodules (Vault, CLPoolManager, CLProtocolFeeController with the BSC default 0.03% dynamic-pool fee), WBNB, USDT (18 decimals) and XAUt (6 decimals) mocks, always-fresh mock Chainlink feeds, the behavioural Railgun mock, and hookless WBNB/USDT and XAUt/USDT reference pools seeded with liquidity. It then runs the same deployment and wiring as production (DeployBase) with the roles in contracts/config/31337.json, and writes contracts/deployments/31337.json.

  3. Builds the SDK and both services, then writes .dev/relayer.env and .dev/attestor.env (copied to services/*/.env.local) and, if apps/web exists, apps/web/.env.local.

  4. Starts the relayer (:8080) and two attestors (:8081 and :8082, anvil accounts #3 and #4, sharing the local hint key and pepper), because 31337.json sets an attestor threshold of 2 and a handle bind needs two signatures. All read the deployment file through DEPLOYMENT_FILE. The web app's env gets NEXT_PUBLIC_ATTESTOR_URLS with both URLs. The indexer and the web app are started separately (pnpm -C services/indexer dev, pnpm -C apps/web dev).

  5. With --demo/--ci, it runs packages/sdk/examples/local-claim.mjs:

    • a fresh stealth beneficiary;
    • a WBNB launch built with buildLaunchParams (anti-snipe on), then buys and sells through ZkPadSwapRouter and collectRewards;
    • a relayed registerStealth + RegisterShieldTemplates;
    • the creator's creatorConsolidateAndShield;
    • a relayed multicall(consolidate, claim) to the Railgun adapter;
    • a relayed direct withdrawal of the leftover USDT.

    It checks that the stealth key never paid gas.

The first run compiles everything with via-IR (about 12 minutes). Point FOUNDRY_OUT and FOUNDRY_CACHE_PATH at an existing build to skip it, and set SKIP_TS_BUILD=1 to reuse the dist/ builds. Logs are in .dev/.

Anvil accounts used: #0 deployer and owner, #1 team fee recipient, #2 guardian, #3 to #5 attestors (threshold 2; dev.sh runs #3 and #4), #6 demo creator, #7 demo trader, #9 relayer. Ports can be changed with ANVIL_PORT, RELAYER_PORT, ATTESTOR_PORT and ATTESTOR2_PORT.

In your own scripts, register the local addresses with the SDK:

import { readFileSync } from 'node:fs';
import { registerDeployment } from '@zk-pad/sdk';
registerDeployment(JSON.parse(readFileSync('contracts/deployments/31337.json', 'utf8')));

TypeScript packages​

pnpm -r typecheck
pnpm -r test
pnpm -r build

Per package, for example: pnpm -C packages/sdk test, pnpm -C services/relayer test.

This documentation site​

pnpm -C apps/docs start # dev server with hot reload
pnpm -C apps/docs build # production build; fails on any broken link
pnpm -C apps/docs serve # serve the build locally