peard
Overview

Quickstart

Validator, deploy, registry, prices, a demo market and the app, in one command.


Everything below runs against a throwaway local validator. Nothing here touches a public cluster.

The default Solana ports are occupied on the machine this was built on, so every script owns the validator lifecycle itself on 9020 / 8760 / 9910 and only ever reaps a validator on its own RPC port. If you run other validators, check that block is free before you start: lsof -ti :9020.

The whole stack, one command

1
Build the programs
anchor build

This is also what writes target/idl/*.json, which the relayer and the reference pages both read.

2
Run the suite
./scripts/test.sh        # 157 passing on 2026-08-25

A fresh ledger every run. State carries over otherwise and every init instruction fails with "already in use".

3
Start everything
./scripts/dev.sh         # then http://localhost:3111

Validator, deploy, registry, one relayer cycle for prices, a demo market with holders and fees, and the app. Ctrl-C tears it all down.

The order in that script is not arbitrary. The seeder needs the program, the relayer needs the registry, and the demo needs a price. A pairable with no price cannot settle anything, so a market created before the relayer runs would be inert.

scripts/demo.ts launches $PEAR against the pear pairable with three holders of very different sizes and five rounds of fees, so the pro-rata split is visibly doing something. Passing --history writes a synthetic seasonal pear series so the chart has a shape before the relayer has run for a year, and the market page prints SYNTHETIC beside the print count.

The pieces, separately

./scripts/localnet.sh          # validator + deploy + seed, then tear down
./scripts/localnet.sh --hold   # same, but leave it up until Ctrl-C
cd relayer && npm install
npm test                 # 296 passing on 2026-08-25, offline
npm run probe            # read every provider, touch no cluster
npm run once             # one real cycle against the localnet
npm run verify-mints     # re-check the pinned hard-grade mints
npm run sync-badges      # --write to correct drifted provider counts
cd relayer
npm run sweep            # claim pool fees, then credit every market
npm run crank            # sync each perp's index and charge funding
npm run convert:dry      # quote every native market's inbox, print the edge
npx ts-node scripts/launch.ts --all --dry   # every entry, with reasons
npx ts-node scripts/launch.ts --id BTC      # one market, for real

What a full local run does not price

A full cycle pushes most of the registry. The ones that do not are worth knowing about, because an unbound pairable and a working one look identical in the UI until somebody tries to trade against it, which is why the relayer reports UNBOUND on every cycle rather than skipping quietly.

  • Honestly unbound: NYC month-to-date rainfall, the Ethereum base fee, and the Mad Lads floor. The last one wants floorlaunch's Tensor and Magic Eden ladder pipeline lifted across.
  • SOL-STAKE-APY holds on a fresh localnet, because nothing is staked there yet.
  • Every hard entry, on devnet, because the pinned mints are mainnet addresses. scripts/launch.ts calls that the honest answer rather than a fault.

After a fresh deploy, rotate the authorities

scripts/seed.ts points admin, oracle and fulfilment authority all at the deployer. That is convenient and it is also a single point of total compromise: the oracle signs the index that decides who gets liquidated on every perp, the admin can change the rules, and on a fresh deploy they are the same key that holds program upgrade authority. One leak is all three.

npx ts-node scripts/rotate-authorities.ts --oracle <path> --fulfillment <path>

The admin is rotated last and only when explicitly asked for, because the admin is what authorises the instruction. Hand it to a key you got wrong and there is no second attempt. Everything else is rotated while the original admin still holds the pen, so a mistake there is recoverable by running the script again.

See Key management for what each key is actually for.

Re-running the seeder

scripts/seed.ts is idempotent: a pairable whose PDA already exists is skipped rather than failing the run, so it is safe to re-run after adding entries. It registers only. Prices come from the relayer, so a half-seeded registry is inert rather than dangerous.