Deploying
Five programs, one seed order, and the ports this project owns.
The programs
| Program | Id | Where it is |
|---|---|---|
peard | 7uwT6YPFpAVESKHpA6fBeTh7mQNdsYXr8jSp1nkwpHqR | devnet |
peard_amm | EdAdkPJMR6wpZq2ehxubV9Cy8opDrD8XisbXNUwXYi4C | devnet |
peard_perps | AZdLGAaYiie1mwuhHUfHM9zA9CC3F3BP5tJoUiAE3umZ | devnet |
peard_vault | 6ps4NbLGcZGW1WWqrp4PsqMCozHied1QGihDzP1A5zv8 | nowhere |
peard_tote | 6Xniss9GBCMD4CdawyUtMYR7LEUGeHY3sr6cHYQyNcoa | nowhere |
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.
| RPC | 9020 |
| Gossip | 8760 |
| Faucet | 9910 |
| Dynamic range | 8770 to 8800 |
| App | 3111 |
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
anchor deploy --provider.cluster http://127.0.0.1:9020
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.
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.
cd relayer && npm run once
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.
| Cluster | Mint | |
|---|---|---|
| mainnet | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | Circle USDC |
| devnet | Frgfj3XfvXeCjZb82NvbFxfGN9tf7cQjkes31ar3p1nA | dUSD, 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.
peard