Phase 1 runbook — testnet desks (46630)
Phase 1 runbook: testnet desks on Robinhood Chain 46630.
Status: shipped on Robinhood testnet 46630 after a real USDG
deposit/redeem path on the blotter. Live Stock Token copies stay
fail-closed until a cited 46630 router and feed exist — see
phase-1-live.md.
Phase 1 wires Phase 0’s cash vault and factory to Robinhood testnet
46630. It does not deploy $SEAT, does not set a SwapAdapter
router, and does not invent USDG / token / oracle / router addresses.
make paper still runs the Phase 0 paper engine with no key and no RPC.
1. Obtain authoritative endpoints
Do not invent these. Copy them from the issuer / chain docs you trust:
- Testnet RPC →
RH_TESTNET_RPC_URL/NEXT_PUBLIC_RH_TESTNET_RPC_URL - Testnet USDG →
USDG_ADDRESS - Deployer / owner →
OWNER(should be the address that signs the deploy) - Opted-in leader →
LEADER_ADDRESS(protocol owner chooses; keeper does not) - Keeper hot wallet →
KEEPER_ADDRESS(optional)
Leave PRIVATE_KEY empty in .env.example. Put the real key only in a local
.env that is gitignored.
Default CHAIN_ID=46630. Do not point local work at mainnet 4663.
cp .env.example .env
# fill RH_TESTNET_RPC_URL, USDG_ADDRESS, OWNER, optional LEADER_ADDRESS2. Deploy core contracts
From the repo root (Foundry uses foundry.toml robinhood_testnet):
make deploy-testnetThis is forge script contracts/script/DeployTestnet.s.sol --rpc-url robinhood_testnet --broadcast.
It deploys RiskModule, SwapAdapter (router = 0), FeeModule, DeskFactory.
It never deploys SeatToken and never calls setRouter.
If LEADER_ADDRESS is set and the broadcaster is OWNER, the script also
calls factory.createDesk(leader) and logs the vault. If KEEPER_ADDRESS is
set, it calls vault.setKeeper. If MAX_FILL_USDG is set, it calls
RiskModule.configureDesk.
3. Record addresses (do not type them by hand)
pnpm exec tsx scripts/write-addresses.tsThe script reads broadcast/DeployTestnet.s.sol/46630/run-latest.json if
present and writes 46630 entries in app/src/lib/addresses.ts. Mainnet
stays null. If there is no broadcast file, addresses stay null and the
app keeps the Phase 0 paper blotter (honest fallback).
4. Create a desk (if the deploy skipped it)
DeskFactory.createDesk(LEADER_ADDRESS) # owner only, one vault per leader
DeskVault.setKeeper(KEEPER_ADDRESS) # owner only
RiskModule.configureDesk(...) # optional capsThe keeper reads vault.leader(). It does not choose a leader.
5. Two deposits and one redeem
On the app (make app-dev), connect a wallet on chain 46630:
- Approve USDG for the vault, deposit amount A.
- Deposit amount B (pro-rata shares).
- Redeem some shares.
Instant redeem pays USDG immediately when the vault is unpaused and
cashUsdg covers the assets. Otherwise the redeem is queued
(WithdrawQueued) until processWithdrawals.
The blotter should show on-chain NAV, cash, shares, and your seat. Mainnet
4663 writes are refused.
6. Keeper fill tape
make keeper-testnetEXECUTION_MODE defaults to PAPER. The keeper binds to DESK_ADDRESS
(leader() on chain, else LEADER_ADDRESS), processes the static fixture
tape, and appends outcomes to keeper/data/fills.json with
source=fixture. The app’s /api/fills serves that file. Fixtures are
never labeled live.
LiveExecutor throws unless CHAIN_ID is 4663 or 46630, a router is
configured, and the symbol is isTradeEligible on that chain.
CHAIN_ID=4663 is allowed for the capped desk. Phase 1 testnet has no
router, so the 46630 live path never submits.
7. Verify without testnet keys
forge test -vvv
pnpm --filter @seat/app typecheck
make paperIf RH_TESTNET_RPC_URL, USDG_ADDRESS, and a key are missing, stop after
this dry-run. Do not fabricate addresses in addresses.ts.
8. Capped mainnet desk (4663)
Facts (USDG, NVDA/AAPL/SPY, Chainlink feeds, SwapRouter02) are in
phase-1-live.md. Code path:
DeskVault.setDepositCap(50_000e6)— Idea.md $50k cap.ExactInputRouter02wraps cited Uniswap SwapRouter02 (exactInputSingle, fee 3000).DeployMainnetwires oracle feeds + allowlisted MAG7 + createDesk.- Never deploys
SeatToken. Refuses unlessCONFIRM_MAINNET=I_UNDERSTANDandchainid == 4663.
# after filling RH_RPC_URL, OWNER, LEADER_ADDRESS, PRIVATE_KEY
CONFIRM_MAINNET=I_UNDERSTAND make deploy-mainnet
pnpm exec tsx scripts/write-addresses.tsDo not broadcast from this repo unless those env vars are set on purpose. The 46630 cash vault is unchanged (immutable, no router).
Out of scope for Phase 1
$SEAT / SeatToken, extra leaders, and ungated mainnet AUM. Those
moved to Phase 2 — see phase-2.md. The 30-day live wait
was skipped by choice.