peard
Running a cluster

Deploying

Five programs, one seed order, and the ports this project owns.


The programs

ProgramIdWhere it is
peard7uwT6YPFpAVESKHpA6fBeTh7mQNdsYXr8jSp1nkwpHqRdevnet
peard_ammEdAdkPJMR6wpZq2ehxubV9Cy8opDrD8XisbXNUwXYi4Cdevnet
peard_perpsAZdLGAaYiie1mwuhHUfHM9zA9CC3F3BP5tJoUiAE3umZdevnet
peard_vault6ps4NbLGcZGW1WWqrp4PsqMCozHied1QGihDzP1A5zv8nowhere
peard_tote6Xniss9GBCMD4CdawyUtMYR7LEUGeHY3sr6cHYQyNcoanowhere

Nothing is on mainnet. The only mainnet activity anywhere in this project is the USTRY router, which is an off-chain sequencer and deploys no program.

Ports this project owns

The default Solana ports are occupied on the machine this was built on, so both scripts/test.sh and scripts/localnet.sh own the validator lifecycle themselves and only ever reap a validator on their own RPC port.

RPC9020
Gossip8760
Faucet9910
Dynamic range8770 to 8800
App3111
lsof -nP -tiTCP:9020 -sTCP:LISTEN     # what the scripts check before binding

RUST_LOG=error is set on every validator launch, because the default INFO stream fills a gigabyte in minutes and has killed runs.

The seed order, and why it is explicit

test = "npx ts-mocha ... tests/peard.ts tests/peard_amm.ts tests/peard_vault.ts tests/wiring.ts tests/peard_perps.ts tests/peard_tote.ts"

An explicit list, not a glob. tests/peard.ts stands up the registry global that the others read, and mocha globs alphabetically, which put peard_perps.ts first and had it racing to create that account itself.

The same dependency shapes scripts/dev.sh: the seeder needs the program, the relayer needs the registry, and the demo needs a price. A market created before the relayer runs would be inert.

Standing up a cluster

1
Deploy
anchor deploy --provider.cluster http://127.0.0.1:9020
2
Seed the registry
ANCHOR_PROVIDER_URL=http://127.0.0.1:9020 \
ANCHOR_WALLET=~/.config/solana/id.json \
npx ts-node scripts/seed.ts

Idempotent: a pairable whose PDA already exists is skipped rather than failing the run. It initialises Global on a fresh deploy and registers only. Prices come from the relayer.

3
Initialise peard_perps's global
npx ts-node scripts/init-peard_perps.ts

Separate from seed.ts because it is the single most dangerous instruction in the deploy. registry_program and usd_mint are written once and peard_perps has no setter for either: get one wrong and every perp on the cluster is unreachable, with no repair path and the deploy rent already spent.

So both are derived rather than typed. The registry program is the peard id from the workspace, and the dollar mint is read out of peard's own global, which guarantees the two programs agree about what a dollar is instead of hoping somebody pasted the same address twice.

backing_program is settable, unlike those two, because backing is an addition rather than a foundation: a cluster runs perfectly well with it unset and every market simply stays unbacked.

4
Push prices
cd relayer && npm run once
5
Rotate the authorities
npx ts-node scripts/rotate-authorities.ts --oracle <path> --fulfillment <path>

Do this before anything real happens. See below.

Rotating authorities, and the order that is enforced

On a fresh deploy the seeder 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 also holds program upgrade authority. One leak is all three.

The admin is rotated last, and only when explicitly asked for with --admin <path> --i-understand-this-is-final, because the admin is what authorises set_authorities. 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 simply running the script again.

The dollar mint is config, not code

Global.usd_mint is named rather than assumed, so devnet and mainnet differ by configuration instead of by code.

ClusterMint
mainnetEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1vCircle USDC
devnetFrgfj3XfvXeCjZb82NvbFxfGN9tf7cQjkes31ar3p1nAdUSD, a stand-in this project minted

Devnet has no USDC: both of the addresses people cite resolve to nothing there. Anything denominated against dUSD is denominated against a token whose mint authority we hold, and the app is required to say so rather than showing a dollar sign.

Pinning matters for the same reason the registry pins asset mints. "USDC" is a symbol anyone can mint, and a market quoted in a lookalike would price correctly and pay out in confetti.

Two config files, kept in step by derivation

relayer/config/relayer.yaml points at localnet, and devnet.yaml is derived from it. The only reason the second file exists is that the first must keep pointing at localnet, so a local run never accidentally pushes prices to a public cluster.

Edit relayer.yaml and re-derive rather than letting the two drift apart by hand.

Program keypairs are the most valuable thing on disk

They carry no balance and they are the program addresses of all five programs, peard_vault and peard_tote included, which cannot be deployed to their declared ids without them. They existed only inside target/ until they were copied out to ~/.config/solana/pricedin-program-keys/ on 2026-08-24.

anchor clean would have destroyed them, and with them the ability to redeploy to the same addresses.

They are not the upgrade authority. Read off devnet on 2026-08-25, all three deployed programs name 9D2d7j5yC2phSzSV5FXDiHH2CrTHQb3W3fyQ6ctXP4m3, which is id.json, the shared machine key. That is still open. See Key management.