Available Networks
toda is deployed as a pair of Anchor programs on Solana. The commitment program is the protocol; the staking program is dormant until the token launches.
Programs
| Program | ID | Purpose |
|---|---|---|
toda-commit | 5SwaBH3pXzEKhmL6pbqZ1JgeDiGaf6i5oVj8951RX9sm | Per-user commitment accounts |
toda-staking | DPV1ytT8jGMwUx4FKZJRXs6MTCsxmTA8nQfkEtAM2kae | Self-custody stake vaults |
Both program IDs are declared in Anchor.toml for devnet and localnet.
toda-commit is live on devnet. Devnet state is not permanent and the cluster is reset periodically - anchor on devnet to build and test, not to make claims that must survive.
toda-staking is declared but not deployed to any cluster. Nothing requires it until staking activates; its test suite runs the compiled program in LiteSVM, without a validator. See Staking.
Cluster configuration
| Variable | Default | Notes |
|---|---|---|
SOLANA_RPC_URL | https://api.devnet.solana.com | Any Solana RPC endpoint |
TODA_PROGRAM_ID | - | Required. The commitment program |
ANCHOR_AUTHORITY_KEYPAIR | ./authority-keypair.json | The anchoring authority. Gitignored - never commit it |
ANCHOR_INTERVAL_MS | 300000 | Anchor service sweep interval |
SIWS_CHAIN_ID | devnet | Written into the SIWS message the wallet signs |
Generate and fund a devnet authority with:
bin/dev-keypair.sh
The script is idempotent and the resulting keypair is gitignored.
Account layout
The commitment account is a fixed-size PDA. It does not grow with the log.
| Field | Bytes | Type |
|---|---|---|
| Anchor discriminator | 8 | - |
owner | 32 | Pubkey |
authority | 32 | Pubkey |
merkle_root | 32 | [u8; 32] |
memory_count | 8 | u64 (LE) |
nonce | 8 | u64 (LE) |
last_updated | 8 | i64 (LE) |
bump | 1 | u8 |
| Total | 129 |
A user with one memory and a user with ten million memories occupy the same 129 bytes.
Deriving addresses
Both PDAs derive from the owner's wallet, so any client can compute them without contacting toda.
import { PublicKey } from "@solana/web3.js";
// The commitment account.
const [commitPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), owner.toBuffer()],
TODA_COMMIT_PROGRAM_ID,
);
// The stake authority (whose associated token account is the vault).
const [stakeAuthority] = PublicKey.findProgramAddressSync(
[Buffer.from("stake"), owner.toBuffer()],
TODA_STAKING_PROGRAM_ID,
);
@toda/solana exposes these as deriveUserCommitPda() and deriveStakeAuthorityPda().
Service ports
Local development defaults:
| Service | Port | In docker-compose.yml |
|---|---|---|
| Dashboard | 3000 | ✅ |
| API | 3001 | ✅ |
| Anchor service | - | ✅ (worker) |
| Extension API | 3002 | ❌ run with bun run dev |
| Docs | 3003 | ❌ run with bun run dev |
| Postgres | 5432 | ✅ |
Protocol constants
Defined in @toda/config/constants and shared by every component. They are part of the wire format - see the commitment log.
DOMAIN_SEP_LEAF = "toda:leaf:v1"
DOMAIN_SEP_NODE = "toda:node:v1"
USER_COMMIT_SEED = "user"
STAKE_AUTHORITY_SEED = "stake"
BATCH_SEAL = { maxLeaves: 64, maxAgeMs: 300_000 }
Moving to mainnet
Anchoring on mainnet makes commitments permanent and publicly meaningful. Before you do:
- Deploy
toda-committo mainnet and setTODA_PROGRAM_IDaccordingly. - Point
SOLANA_RPC_URLat a mainnet endpoint you control or pay for. Public endpoints rate-limit, and the anchor service reads the nonce before every commit. - Set
SIWS_CHAIN_ID=mainnetso the message users sign names the right cluster. - Fund the authority keypair with enough SOL to cover sustained anchoring, and monitor its balance. An unfunded authority means anchoring silently stops - the tip goes stale while writes continue to succeed.
- Read production readiness first.
A commitment account is a PDA under a specific program ID on a specific cluster. Moving clusters means starting a new log - the devnet tip has no meaning on mainnet. Anchor on the cluster whose permanence you actually need.