Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Devnet-only vanity program IDs

The mainnet-track program IDs — mail_program at MaiLyqjRuHp8SSQHjiLMPmhBcuLitSta4YdoTiibXu4 and alias_program at ALiasg6qDnwcY8HfyeC1AjXRFjyqpXxW4omtwF1i125q — also have a live devnet deployment. But the postoffice singleton account under that ID on devnet is permanently bricked: leftover state from an old dev iteration with no delegate ever assigned, and the delegate-gated AdminCloseAccount reclaim tool cannot close an account that never had a delegate (see Closing accounts for the general tool; it structurally cannot reach this one). Rather than add a new on-chain “rescue” instruction, this repo carries a second, devnet-only program identity for each program, reachable only through an opt-in devnet Cargo feature — a clean, empty postoffice, with the mainnet-track IDs and their deployment left completely untouched. The domain program, born after the bricking in the item-28 split, carries a devnet twin too — not because anything of its own is bricked, but because the cross-program identity assertions below mean a devnet build must swap all three IDs together or none.

Why this has to be a compile-time swap, not a config value

Each program asserts its own identity against a compiled-in constant on every instruction (Assert::is_mail_program / Assert::is_alias_program / Assert::is_domain_program, program_common/src/assert.rs) — this is how alias_program and domain_program verify the postoffice account they’ve been handed genuinely belongs to mail_program when they derive the postoffice PDA cross-program. An SBF program is a static binary; there’s no way for it to read an environment variable or config file to decide “which cluster am I on.” So the devnet identity is selected with a Cargo feature, devnet, that swaps the embedded MAIL_PROGRAM_ID / ALIAS_PROGRAM_ID / DOMAIN_PROGRAM_ID (mail_model/src/lib.rs) and their Address-typed twins (program_common/src/address.rs) to a second, freshly-mined vanity set — default OFF, so every normal build stays on the mainnet-track identity.

This mirrors the modexp-free build’s shape exactly: a deploy-target build selected by an explicit feature flag, not a new default. The two features are independent and compose: a real devnet deploy of domain_program needs both, since enable_big_mod_exp_syscall is separately inactive on devnet (see that page — since the split only the domain program carries the proof path, so mail_program and alias_program need just devnet).

The devnet vanity IDs

  • mail_program (devnet): MaiLrDyjMHm7zC5yak9jmqDHctHXAgV3C1cFHW7Yd6f
  • alias_program (devnet): ALiasqsSbBw3txjZi6EqfcxFHc4sMYKRSVzDQdpG1X6S
  • domain_program (devnet): DmaiNHGvprK2op7xqZHXp8UVXXmPUtkas96Goh5sCJQn

The matching keypairs are checked into the repo at mail_client/tests/{mail,alias,domain}_program-dev-keypair.json, beside the other devnet test fixtures (keypair/ holds only the mainnet-track keypairs for all three programs) — devnet-only material, not sensitive the way a mainnet upgrade authority would be (see CLAUDE.md’s “secrets in the tree” note covering the intentionally checked-in test/devnet keys).

This is the second devnet-only generation: the first (MaiLb9JN…fedMW / ALiasgDpo…fsTkX, keypairs now only in git history) is retired — its deployments remain on devnet but nothing in this repo targets them anymore.

The target/deploy/ footgun

cargo build-sbf and the on-chain test harness always read and write the same fixed filename slot — target/deploy/{name}-keypair.json and target/deploy/{name}.so — regardless of which feature you’re building with. There is no feature-based subdirectory. Switching between a mainnet-track build and a devnet build means copying the right keypair into that slot every time:

# Point target/deploy/ at devnet:
cp mail_client/tests/mail_program-dev-keypair.json   target/deploy/mail_program-keypair.json
cp mail_client/tests/alias_program-dev-keypair.json  target/deploy/alias_program-keypair.json
cp mail_client/tests/domain_program-dev-keypair.json target/deploy/domain_program-keypair.json

# Point it back at the mainnet-track identity before any other work
# (all three mainnet-track keypairs live in keypair/):
cp keypair/mail_program-keypair.json   target/deploy/mail_program-keypair.json
cp keypair/alias_program-keypair.json  target/deploy/alias_program-keypair.json
cp keypair/domain_program-keypair.json target/deploy/domain_program-keypair.json

Verify which one is actually staged with solana address -k target/deploy/mail_program-keypair.json after every switch — this is a sharper version of the existing target/deploy/ trap documented in HANDOFF.md (a stale/wrong keypair there silently deploys or tests against the wrong address), now with two valid destinations to mix up instead of one valid vs. one accidental-random one.

Never run the mail_client surfpool integration suite (cargo test -p mail-client --features devnet --test api) — don’t combine these. tests/api/surfpool.rs deploys and address-checks target/deploy/{name}.so against whichever IDs the test binary itself was compiled with; running it under devnet while target/deploy/ holds the mainnet-track .so (or vice versa) produces confusing failures that look like an ABI break rather than a keypair mixup. The suite is not devnet-aware and isn’t meant to be — it always exercises the mainnet-track identity against a local surfpool validator.

Building and deploying the devnet identity

# 1. Stage the devnet keypairs (see above).
cp mail_client/tests/mail_program-dev-keypair.json   target/deploy/mail_program-keypair.json
cp mail_client/tests/alias_program-dev-keypair.json  target/deploy/alias_program-keypair.json
cp mail_client/tests/domain_program-dev-keypair.json target/deploy/domain_program-keypair.json

# 2. Build. domain_program composes --no-default-features (drops dnssec-proof —
#    enable_big_mod_exp_syscall is still inactive on devnet, see
#    modexp-free-deploy.md) with --features devnet (swaps the embedded IDs);
#    mail_program and alias_program are modexp-free unconditionally and need
#    just the devnet feature.
#    --tools-version v1.53 is NOT optional: build-sbf defaults to
#    platform-tools v1.54, whose .so deploys fine but faults with an "Access
#    violation in program section" on EVERY instruction at runtime (this bit
#    us the first time through — the deploy and even `postmaster init`'s
#    --skip-preflight path looked like they succeeded; only a confirmed
#    non-skip-preflight send or `solana confirm -v` reveals the crash).
cargo build-sbf --manifest-path mail_program/Cargo.toml \
  --tools-version v1.53 --features devnet
cargo build-sbf --manifest-path alias_program/Cargo.toml \
  --tools-version v1.53 --features devnet
cargo build-sbf --manifest-path domain_program/Cargo.toml \
  --tools-version v1.53 --no-default-features --features devnet

# 3. Sanity-check before spending anything:
solana address -k target/deploy/mail_program-keypair.json
solana address -k target/deploy/alias_program-keypair.json
solana address -k target/deploy/domain_program-keypair.json
llvm-readelf --dyn-syms target/deploy/domain_program.so | grep -i mod_exp   # expect empty

# 4. Fresh deploy under the new IDs (NOT an upgrade of the bricked deployment).
solana program deploy target/deploy/mail_program.so \
  --program-id target/deploy/mail_program-keypair.json --url https://api.devnet.solana.com
solana program deploy target/deploy/alias_program.so \
  --program-id target/deploy/alias_program-keypair.json --url https://api.devnet.solana.com
solana program deploy target/deploy/domain_program.so \
  --program-id target/deploy/domain_program-keypair.json --url https://api.devnet.solana.com

Then build and use the CLI against the new identity — cluster targeting (solana config / JSON_RPC_URL) and the devnet Cargo feature are orthogonal, both need to be set:

cargo build --release -p mail-client --bin sithbit --features postmaster,devnet
# point at devnet: solana config set --url https://api.devnet.solana.com (or JSON_RPC_URL)

./target/release/sithbit postmaster init \
  --seed <seed0> --seed <seed1> -k <delegate-keypair>

This lands InitPostoffice on the fresh postoffice PDA seeded off the new devnet MAIL_PROGRAM_ID — a never-initialized account, entirely distinct from the bricked mainnet-track-ID one.

See also