svmgov Program Reference
Program ID: govYkyQ3ePtGULAtY6V75qjWE8UH4vCUVQ1W4HdCAZU
Complete reference for all on-chain accounts, instructions, and error codes in the svmgov Anchor program.
If any term is unfamiliar, use the svmgov Terminology page as a glossary while reading this reference.
Account Structures
GlobalConfig
PDA seeds: ["global_config"]
The global governance configuration singleton. Initialized by the admin once.
| Field | Type | Description |
|---|---|---|
admin | Pubkey | Admin who can update config. Set to the program upgrade authority at init; transferable via the two-step nominate_admin / accept_admin flow |
pending_admin | Option<Pubkey> | Nominated next admin awaiting acceptance; None when no transfer is in progress |
max_title_length | u16 | Maximum bytes in a proposal title (≤ 200) |
max_description_length | u16 | Maximum bytes in a proposal description (≤ 500) |
max_support_epochs | u64 | Max epochs a proposal can remain in the support phase |
min_proposal_stake_lamports | u64 | Minimum stake (lamports) to create a proposal |
cluster_support_pct_min_bps | u64 | % of cluster stake required to activate voting (basis points) |
discussion_epochs | u64 | Epochs reserved for discussion after voting is activated |
voting_epochs | u64 | Number of epochs in the active voting window |
snapshot_epoch_extension | u64 | Epochs of extension before the snapshot slot is set |
snapshot_slot_offset | i64 | Slot offset from the target epoch start slot; may be negative if the computed slot remains in the future |
bump | u8 | PDA bump |
ProposalIndex
PDA seeds: ["index"]
Tracks the global proposal counter. Incremented on each new proposal.
| Field | Type | Description |
|---|---|---|
current_index | u32 | Sequential proposal counter |
bump | u8 | PDA bump |
Proposal
PDA seeds: ["proposal", seed.to_le_bytes(), spl_vote_account_pubkey]
The main proposal account. One per governance proposal.
| Field | Type | Description |
|---|---|---|
author | Pubkey | Validator who created the proposal |
title | String | Short title (length in bytes ≤ max_title_length, ≤ 200) |
description | String | GitHub URL for full proposal text (length in bytes ≤ max_description_length, ≤ 500) |
creation_epoch | u64 | Epoch when created |
start_epoch | u64 | Epoch when voting begins |
end_epoch | u64 | Epoch when voting ends |
proposer_stake_weight_bp | u64 | Proposer’s stake weight (basis points) at creation |
cluster_support_lamports | u64 | Running total of supporting stake (lamports) |
for_votes_lamports | u64 | Accumulated lamports voted For |
against_votes_lamports | u64 | Accumulated lamports voted Against |
abstain_votes_lamports | u64 | Accumulated lamports voted Abstain |
voting | bool | true once support threshold is reached |
finalized | bool | true after finalize_proposal |
proposal_bump | u8 | PDA bump (used as seed for CPI signing) |
creation_timestamp | i64 | Unix timestamp at creation |
vote_count | u32 | Number of votes cast |
index | u32 | Sequential proposal index from ProposalIndex |
consensus_result | Option<Pubkey> | The NCN ConsensusResult PDA for this proposal |
snapshot_slot | u64 | Slot of stake snapshot (set when voting activates) |
proposal_seed | u64 | Seed used to derive proposal PDA (needed for CPI) |
vote_account_pubkey | Pubkey | Proposer’s SPL vote account |
Support
PDA seeds: ["support", proposal_pubkey, spl_vote_account_pubkey]
A receipt proving that a validator has already supported a proposal. Prevents double-supporting.
| Field | Type | Description |
|---|---|---|
proposal | Pubkey | The supported proposal |
validator | Pubkey | The supporting validator |
bump | u8 | PDA bump |
Vote
PDA seeds: ["vote", proposal_pubkey, spl_vote_account_pubkey]
Records a validator’s vote on a proposal.
| Field | Type | Description |
|---|---|---|
validator | Pubkey | Validator who voted |
proposal | Pubkey | The voted-on proposal |
for_votes_bp | u64 | Basis points allocated to For |
against_votes_bp | u64 | Basis points allocated to Against |
abstain_votes_bp | u64 | Basis points allocated to Abstain |
for_votes_lamports | u64 | Actual lamports for For (stake × bp / 10,000) |
against_votes_lamports | u64 | Actual lamports for Against |
abstain_votes_lamports | u64 | Actual lamports for Abstain |
stake | u64 | Validator’s total stake at vote time |
override_lamports | u64 | Stake already overridden by delegators |
vote_timestamp | i64 | Unix timestamp |
bump | u8 | PDA bump |
VoteOverride
PDA seeds: ["vote_override", proposal_pubkey, spl_stake_account_pubkey, validator_vote_pubkey]
Records a delegator’s override of their validator’s vote.
| Field | Type | Description |
|---|---|---|
delegator | Pubkey | Delegator who overrode |
stake_account | Pubkey | Stake account used for the override |
validator | Pubkey | Validator’s vote account being overridden |
proposal | Pubkey | The proposal |
vote_account_validator | Pubkey | The validator’s Vote PDA address |
for_votes_bp | u64 | Delegator’s For allocation (basis points) |
against_votes_bp | u64 | Delegator’s Against allocation |
abstain_votes_bp | u64 | Delegator’s Abstain allocation |
for_votes_lamports | u64 | Delegator’s For (stake × bp / 10,000) |
against_votes_lamports | u64 | Delegator’s Against |
abstain_votes_lamports | u64 | Delegator’s Abstain |
stake_amount | u64 | Delegator’s stake amount in this account |
vote_override_timestamp | i64 | Unix timestamp |
bump | u8 | PDA bump |
VoteOverrideCache
PDA seeds: ["vote_override_cache", proposal_pubkey, validator_vote_pubkey]
Aggregates all delegator overrides for a validator on a proposal. Used when a delegator overrides before the validator has voted.
| Field | Type | Description |
|---|---|---|
validator | Pubkey | Validator vote account |
proposal | Pubkey | Proposal |
vote_account_validator | Pubkey | The validator’s Vote PDA address |
for_votes_bp | u64 | Accumulated For (basis points) from all delegators |
against_votes_bp | u64 | Accumulated Against |
abstain_votes_bp | u64 | Accumulated Abstain |
for_votes_lamports | u64 | Accumulated For (lamports) |
against_votes_lamports | u64 | Accumulated Against |
abstain_votes_lamports | u64 | Accumulated Abstain |
total_stake | u64 | Total delegator stake in this cache |
bump | u8 | PDA bump |
VoteOverrideCache is created with init_if_needed to prevent prefunding DoS attacks. The validator’s cast_vote checks for an existing cache and applies it to reduce their effective stake.
Instructions
initialize_config
One-time initialization. Sets all governance parameters and stores the signer as admin.
Signer: the program’s upgrade authority. Init is gated by passing the program’s program and program_data accounts and requiring program_data.upgrade_authority_address == admin. The signer becomes the stored admin.
Required accounts: admin (signer), global_config (PDA, init), system_program, program (this program), program_data (this program’s ProgramData account).
| Parameter | Type | Description | Validation |
|---|---|---|---|
max_title_length | u16 | Max title bytes | 1–200 |
max_description_length | u16 | Max description bytes | 1–500 |
max_support_epochs | u64 | Max epochs in support phase | |
min_proposal_stake_lamports | u64 | Min stake to create a proposal | |
cluster_support_pct_min_bps | u64 | Cluster support threshold | 0–10,000 |
discussion_epochs | u64 | Epochs of discussion | |
voting_epochs | u64 | Epochs for voting | |
snapshot_epoch_extension | u64 | Extension epochs before snapshot | |
snapshot_slot_offset | i64 | Slot offset from epoch start for the snapshot |
update_config
Updates any subset of config fields. All params are Option<T>; only the Some fields are written.
Signer: the stored admin (GlobalConfig.admin).
The same bounds as initialize_config apply to max_title_length (1–200), max_description_length (1–500), and cluster_support_pct_min_bps (0–10,000).
nominate_admin
Step 1 of the two-step admin transfer. Records proposed_admin in GlobalConfig.pending_admin. Overwrites any prior nomination; rejects the all-zero pubkey. Emits AdminNominated.
Signer: the current admin.
| Parameter | Type | Description |
|---|---|---|
proposed_admin | Pubkey | The nominee who must call accept_admin to take over |
accept_admin
Step 2 of the two-step admin transfer. Promotes the signer to admin and clears pending_admin. Emits AdminTransferred.
Signer: the pending admin (must equal GlobalConfig.pending_admin).
Because the transfer only completes when the nominee signs, authority can never be handed to a key that cannot sign — and each side signs its own, separate transaction, which makes multisig-to-multisig handoffs (e.g. Squads) feasible.
initialize_index
Creates the global ProposalIndex PDA. Can be called by anyone. Must be called once before any proposals.
create_proposal
Creates a new governance proposal.
Signer: validator (must match spl_vote_account.node_pubkey)
| Parameter | Type | Validation |
|---|---|---|
seed: u64 | u64 | Unique seed for PDA derivation |
title: String | String | Non-empty, ≤ max_title_length |
description: String | String | Non-empty, ≤ max_description_length, must start with https://github.com |
Additional checks: proposer stake ≥ min_proposal_stake_lamports
support_proposal
A validator signals support for a proposal.
Signer: validator
Adds the validator’s stake to proposal.cluster_support_lamports. When the total crosses the cluster support threshold:
- Sets
proposal.voting = true - Calculates
snapshot_slotfrom the target epoch start slot plusGlobalConfig.snapshot_slot_offset - Sets
start_epochandend_epoch - Derives
consensus_resultPDA - CPIs to
ncn-snapshot::init_ballot_box
cast_vote
A validator votes on an active proposal.
Signer: validator (must match meta_merkle_leaf.voting_wallet)
| Parameter | Type | Validation |
|---|---|---|
for_votes_bp | u64 | |
against_votes_bp | u64 | |
abstain_votes_bp | u64 | Sum of all three must equal 10,000 |
Flow:
- Verifies proposal is in voting phase and epoch range
- Reads
MetaMerkleProofPDA - CPIs to
ncn-snapshot::verify_merkle_proof - If
VoteOverrideCacheexists: applies cached delegator votes, reduces validator’s effective stake - Records
VotePDA with lamport amounts
modify_vote
Modifies an existing validator vote during the voting phase.
Signer: validator
Subtracts old vote lamports from proposal totals, adds new values, re-verifies Merkle proof.
cast_vote_override
A delegator overrides their validator’s vote using a stake account proof.
Signer: delegator (must match stake_merkle_leaf.voting_wallet)
| Parameter | Type | Description |
|---|---|---|
for_votes_bp | u64 | |
against_votes_bp | u64 | |
abstain_votes_bp | u64 | Must sum to 10,000 |
stake_merkle_proof | Vec<[u8;32]> | Proof path for the stake account |
stake_merkle_leaf | StakeMerkleLeaf | Stake account leaf data |
Two paths:
- Path A (validator has voted): Immediately adjusts the validator’s
Vote— subtracts delegator’s portion from old vote, adds delegator’s preferred allocation, updatesoverride_lamports - Path B (validator hasn’t voted yet): Stores in
VoteOverrideCacheto be applied when the validator votes
modify_vote_override
Modifies an existing delegator override. Reverses the old override’s effect and applies new values.
Signer: delegator
finalize_proposal
Permissionless. Marks a proposal as finalized after its voting period ends.
Conditions: proposal.voting == true, !proposal.finalized, clock.epoch >= proposal.end_epoch
flush_merkle_root
Admin-only recovery instruction. Re-anchors a supported proposal’s snapshot/voting window forward off the current epoch (current_epoch + snapshot_epoch_extension), recomputing the snapshot_slot and consensus_result and creating a new BallotBox. Used when an NCN snapshot fails to reach consensus and the proposal must be rescheduled to allow a fresh snapshot. Only callable before voting starts (clock.epoch < start_epoch); gating it to the admin prevents an individual proposer from postponing their own vote.
Signer: program admin only (global_config.admin)
Error Codes
| Error | Description |
|---|---|
NotEnoughStake | Insufficient stake to create or vote |
TitleEmpty | Proposal title is empty |
TitleTooLong | Title exceeds max length (bytes) |
DescriptionEmpty | Description is empty |
DescriptionTooLong | Description exceeds max length (bytes) |
DescriptionInvalid | Description must be a https://github.com link |
InvalidProposalId | Invalid proposal ID |
VotingNotStarted | Proposal has not yet entered voting phase |
ProposalClosed | Voting period has ended |
ProposalFinalized | Proposal already finalized |
InvalidVoteDistribution | For + Against + Abstain ≠ 10,000 bps |
VotingPeriodNotEnded | Tried to finalize before end_epoch |
InvalidVoteAccount | Vote account mismatch |
FailedDeserializeNodePubkey | Could not read node_pubkey from vote account |
VoteNodePubkeyMismatch | Signer doesn’t match vote account’s node_pubkey |
InvalidClusterStake | Cluster stake is zero |
InvalidStartEpoch | Start epoch in the past |
InvalidVotingLength | Voting length must be > 0 |
InvalidVoteAccountVersion | Unsupported vote account version |
InvalidVoteAccountSize | Vote account has unexpected size |
InvalidStakeAccount | Stake account invalid |
InvalidStakeState | Stake account in unexpected state |
InvalidStakeAccountSize | Stake account has unexpected size |
InvalidSnapshotProgram | Provided program ID is not the ncn-snapshot program |
UnauthorizedAdmin | Only the program admin (global_config.admin) can call flush_merkle_root |
MerkleRootAlreadySet | Cannot set Merkle root that is already set |
InvalidMerkleRoot | Merkle root cannot be all zeros |
InvalidSnapshotSlot | Snapshot slot must be past or current |
MustBeOwnedBySnapshotProgram | Account must be owned by ncn-snapshot |
InvalidConsensusResultPDA | ConsensusResult PDA address mismatch |
CannotDeserializeMetaMerkleProofPDA | MetaMerkleProof deserialization failed |
CannotDeserializeConsensusResult | ConsensusResult deserialization failed |
CannotModifyAfterStart | Cannot modify proposal after voting starts |
VotingLengthTooLong | Voting length exceeds max_support_epochs |
ArithmeticOverflow | Overflow in stake calculations |
SnapshotProgramUpgraded | Snapshot program upgrade protection triggered |
MerkleRootNotSet | Merkle root not yet set for this proposal |
SupportPeriodExpired | Support period has expired |
UnauthorizedAdmin | Signer is not the configured GlobalConfig.admin |
InvalidProgram | program/program_data do not belong to this program (init upgrade-authority gate) |
InvalidClusterSupportPctMin | cluster_support_pct_min_bps must be 0–10,000 |
InvalidMaxTitleLength | max_title_length must be 1–200 (bytes) |
InvalidMaxDescriptionLength | max_description_length must be 1–500 (bytes) |
InvalidAdmin | Nominated admin cannot be the default (all-zero) pubkey |
NoPendingAdmin | accept_admin called with no pending nomination |
NotPendingAdmin | Signer is not the pending admin nominee |