Documentation

Reference

View as Markdown

CLI

Reference for the paralock CLI: the local devnet, the devnet maker, swaps and payments from the terminal, recovery commands, and the files it writes.

The paralock CLI runs the whole Paralock stack on your machine: a Zcash regtest network, one local EVM chain per supported chain, the contracts, a maker, and swaps and payments from the terminal. It is part of the repository and is not published to npm.

Setup

Run it from a checkout of the repository, after building the packages:

Shell
pnpm install && pnpm build
pnpm paralock doctor

pnpm paralock <command> runs node packages/cli/dist/main.js <command>. The CLI keeps its state in .paralock/ at the repository root (the nearest directory with pnpm-workspace.yaml). Without a command, or with an unknown one, it prints the help.

Prerequisites, all checked by doctor: Node.js 20 or newer, pnpm, a running Docker daemon (OrbStack or Docker Desktop on macOS), ths (Thus Spoke Zakura, the Zcash regtest environment), and Foundry 1.8 or newer with anvil --network tempo.

Commands

CommandWhat it does
paralock doctor [--evm <chains>]Checks prerequisites, builds and ports, and prints a fix for each problem.
paralock dev [--evm <chains>] [--web] [--web-port <port>] [--ths-log <file>]Everything at once: doctor, devnet, maker, and optionally the web app. Reuses whatever is already running.
paralock devnet up [--evm <chains>] [--ths-log <file>]Starts Zcash regtest and the EVM chains, deploys the contracts, writes .paralock/devnet.json.
paralock devnet statusHeights and addresses of the running devnet.
paralock devnet downStops what Paralock started and deletes the devnet file.
paralock maker [--port <port>]Runs the devnet maker over HTTP (default port 7420).
paralock swap --zec <amount> [options]Swaps ZEC for stablecoins as the devnet taker.
paralock pay --usd <amount> --to <0x…> [options]Pays an exact dollar amount from shielded ZEC.
paralock swapsLists unfinished swaps saved in .paralock/taker.
paralock resume <id>Continues a saved swap after a crash or restart.
paralock refund <id> [--no-shield]Refunds a saved swap's ZEC once its refund height is reached.

Chains

--evm <chains> takes a comma-separated list from tempo, base, ethereum, arbitrum, robinhood, hyperevm, or all. The default is tempo,base,ethereum. Each chain runs on its own anvil:

ChainPortChain idStablecoin
tempo854531337OUSD stand-in (TIP-20), anvil --network tempo
base854631338MockUSD "USDC"
ethereum854731339MockUSD "USDC"
arbitrum854831340MockUSD "USDC"
robinhood854931341MockUSD "USDC"
hyperevm855031342MockUSD "USDC"

Other local services:

ServiceAddress
Zcash RPC (Zakura regtest)http://127.0.0.1:18232
lightwalletdhttp://127.0.0.1:9067
ths dashboardhttp://127.0.0.1:32805
Makerhttp://127.0.0.1:7420
Web app (dev --web)http://localhost:3000

doctor

Shell
pnpm paralock doctor [--evm <chains>]

Checks, in order: Node.js 20 or newer, pnpm, the Docker daemon, ths, Foundry 1.8 or newer (anvil and forge), anvil --network tempo support, installed dependencies, built packages (every package has its dist/), the contract libraries (contracts/lib/forge-std, contracts/lib/tempo-std), and the ports above for the selected chains. A port that is in use passes if the service on it is the one Paralock would start there (it will be reused). The web port is only a warning unless dev --web will use it.

Each failed check prints a fix, for example pnpm build or foundryup. The exit code is 1 if a required check fails.

dev

Shell
pnpm paralock dev [--evm <chains>] [--web] [--web-port <port>] [--ths-log <file>]
FlagDefaultMeaning
--evm <chains>tempo,base,ethereumChains to run.
--weboffAlso start the web app (next dev).
--web-port <port>3000Port of the web app. Implies --web.
--ths-log <file>noneCaptured ths start output, when ths was started outside Paralock (see devnet up).

Runs doctor (and stops if a required check fails), then devnet up, then starts the maker in the background (.paralock/maker.pid, log in .paralock/maker.log) and, with --web, the web app (.paralock/web.pid, .paralock/web.log). A healthy maker or web app that is already running is reused. A maker that dev started is restarted if it doesn't quote every devnet chain yet. It ends by printing the running services and the next commands to try.

Shell
pnpm paralock dev --evm all --web

devnet up

Shell
pnpm paralock devnet up [--evm <chains>] [--ths-log <file>]
  1. Zcash. Starts ths (Zakura regtest and lightwalletd) with ths start --no-open, captures its output to .paralock/ths.log and waits up to 3 minutes for it to be ready. The ths mnemonic appears only in that output. If ths is already running and .paralock/ths.log is missing, the command fails: run ths stop and retry, or pass --ths-log <file> with the output of your own ths start.
  2. EVM chains. Starts one anvil per chain with a 1-second block time (.paralock/anvil-<chain>.pid and .log), or reuses an anvil already answering on the port with the right chain id.
  3. Contracts. Runs forge script script/Deploy.s.sol per chain, which deploys ParalockHTLC, ParalockAdaptorEscrow and the local stablecoin and writes contracts/deployments/<chainId>.json. Contracts already live on the chain are reused.
  4. Devnet file. Writes .paralock/devnet.json and prints the endpoints, contract addresses, keys and ths account balances.

Chains from an earlier devnet up that are still running stay in the file, so devnet up --evm arbitrum adds Arbitrum to a running devnet.

RoleZcash (ths account)EVM (anvil dev account)
Makeraccount 5dev 0
Takeraccount 1 (shielded wallet) and account 2 (swap address)dev 1
Relayerdev 2

The taker's savings stay shielded in account 1. Each swap unshields exactly what it needs into account 2's transparent address, which only Paralock spends from. Change and refunds go back to account 1 and are shielded again.

devnet status and devnet down

Shell
pnpm paralock devnet status
pnpm paralock devnet down

status prints the Zcash regtest height and, per chain, the RPC, block height, contract addresses, quote token and the block event scans start from.

down stops the maker and web app started by dev, runs ths stop, stops the anvils Paralock started, and deletes .paralock/devnet.json and .paralock/ths.log. Processes Paralock did not start are left running. The swap stores (.paralock/taker, .paralock/maker) and .paralock/recipients.jsonl are kept.

maker

Shell
pnpm paralock maker [--port 7420]

Runs the devnet maker over HTTP with makerHandler, for every devnet chain and both modes. It:

  • persists quotes, swaps and per-swap keys in .paralock/maker, so a restarted maker resumes every unfinished swap;
  • mines a Zcash regtest block every 3 seconds (regtest has no miners);
  • uses DEVNET_POLICY (40-block Zcash window, 45-second EVM lock), a 50 bps spread, and the CoinGecko price with a fixed $1,270 fallback (devnet only);
  • quotes 0.0002 to 5 ZEC (20,000 to 500,000,000 zat), keeps quotes for 120 seconds and handles up to 25 swaps at once;
  • shields the ZEC it claims back into its shielded balance;
  • allows CORS from any origin and caps request bodies at 16 KiB.

Ctrl-C (or SIGTERM) stops it cleanly.

swap

Shell
pnpm paralock swap --zec <amount> [options]

Swaps ZEC from the devnet taker's wallet for the stablecoin on a chain. The maker must be running.

FlagDefaultMeaning
--zec <amount>requiredZEC to sell, in decimal ZEC (0.5), converted to zatoshi.
--chain <chain>tempoWhere to receive the stablecoins. Must be a chain of the running devnet.
--privateoffAdaptor-signature mode: no hash on either chain.
--fresh-recipientoffReceive at a new address created for this swap. Its key is appended to .paralock/recipients.jsonl.
--relayoffLet the relayer (anvil dev account 2) submit the claim, so the recipient needs no gas.
--standard-onlyoffOnly allow 0.1, 0.5, 1 or 5 ZEC.
--jitter <ms>noneRandom delay of up to this many milliseconds before claiming.
--no-shieldoffDon't unshield first or reshield after. The faucet funds the swap address directly.
--refund-demooffLock ZEC and let the maker vanish, then refund after the Zcash timeout.

What it does: gets a validated quote and prints it (amounts, rate, mode, both lock windows); unshields the amount plus fees from the taker's shielded balance into the swap address (topping up from the faucet if needed); runs the swap, printing each step; then shields leftover change again. If the swap fails after the ZEC is locked, it waits for the refund height, refunds, and exits with code 1. The swap is saved in .paralock/taker, so paralock refund <id> also works after a restart.

Shell
pnpm paralock swap --zec 1                                   # ZEC to OUSD on Tempo (htlc)
pnpm paralock swap --zec 1 --chain base --private            # ZEC to USDC on Base, no hash on either chain
pnpm paralock swap --zec 0.5 --fresh-recipient --relay --standard-only
pnpm paralock swap --zec 0.5 --refund-demo                   # the maker never locks; refund after the timeout

See Privacy for what each privacy flag hides.

pay

Shell
pnpm paralock pay --usd <amount> --to <0x…> [options]

Pays someone an exact dollar amount from shielded ZEC: an exact-out quote, so the recipient gets exactly --usd and the ZEC amount rounds up.

FlagDefaultMeaning
--usd <amount>requiredDollars to deliver, in decimal (42.50), converted to 6-decimal token units.
--to <0x…>requiredThe recipient's EVM address.
--chain <chain>tempoChain to pay on.
--privateoffAdaptor-signature mode.
--relayoffLet the relayer submit the claim.
--label <text>noneA label for the payment, such as a merchant name.
--no-shieldoffDon't unshield first or reshield after.
Shell
pnpm paralock pay --usd 25 --to 0xShop… --chain base --label "Acme Coffee"
pnpm paralock pay --usd 42.50 --to 0xShop… --chain tempo --private --relay

See Payments and payment links.

swaps, resume and refund

Shell
pnpm paralock swaps
pnpm paralock resume <id>
pnpm paralock refund <id> [--no-shield]
CommandBehavior
swapsLists unfinished swaps in .paralock/taker (their ZEC may still be locked): id, mode, status, amount, chain and refund block. The web app shares this store, so swaps started there appear too.
resume <id>Calls taker.resume(id, { claim: true }): claims if the maker's lock is there and still safe to claim, then follows the maker's ZEC claim. A funding that never reached the network is broadcast late only if the maker committed to it, it is at least 5 blocks from its expiry and the maker still holds the swap; otherwise the swap is marked abandoned.
refund <id>Calls taker.refund(id): waits for the refund height (mining regtest blocks), broadcasts the refund and prints its txid. Then shields the refunded ZEC, unless --no-shield.

The swap id is the hashlock (htlc mode) or the quote id (adaptor mode); swaps prints it. See Persistence and recovery.

Files in .paralock/

.paralock/ is gitignored. It holds the ths mnemonic, development keys and fresh-recipient keys: never commit it.

PathWritten byContents
devnet.jsondevnet upEndpoints, contract addresses, quote tokens, start blocks, the ths mnemonic and the dev keys. Read by the SDK's loadDevnetFile.
ths.logdevnet upCaptured ths start output, including the mnemonic.
anvil-<chain>.pid, anvil-<chain>.logdevnet upEach anvil Paralock started.
maker.pid, maker.log, web.pid, web.logdevThe maker and web app dev started in the background.
maker/makerThe maker's FileSwapStore: quotes, swaps and per-swap keys.
taker/swap, pay, swaps, resume, refund, the web appThe taker's FileSwapStore: secrets, keys and refund data.
recipients.jsonlswap --fresh-recipient, the web appOne line per fresh recipient: address, private key, swap id, chain, time.

devnet up also writes contracts/deployments/<chainId>.json for the local chain ids 31337 to 31342.