This guide explains how to connect any Move/Sui application to the Governance Framework. It is app‑agnostic: adapt names, paths, and types to your project.
Time units: governance delays/periods are milliseconds (ms) (
Clock::timestamp_ms).
Vote types:2 = Yes,1 = No,0 = Abstain(abstain does not count toward quorum).
Quorum:0 = Fixed(absolute min votes),1 = Fractional(percentage in parts‑per‑100_000).
Modules involved:governance::{core, proposals, votes, types, config, quorum, snapshot}(and optionalsafe_math).
Your app should expose:
- A shared
Configobject holding parameters that governance may change. - An admin
Capabilitythat gates mutations toConfig. - Capability‑gated setters of the form
set_*(&Capability, &mut Config, ...)for every field you want to govern.
Rationale: Governance executes changes by calling these setters once a proposal passes.
The adapter is the only interface end‑users call. It bridges your app to the framework.
Define an enum that expresses what a proposal will do if it passes. Each variant should:
- Include a target binding (e.g.,
config_id: ID) so execution can verify it is mutating the intended object. - Carry just the new value(s) needed by the corresponding setter(s).
One variant per governed setter is a clean default. Include multiple fields if your setter takes multiple args.
Expose an entry like initialize_governance<Token>(...) that calls:
core::init_governance<Capability, Token, ProposalKind>(capability, voting_delay_ms, voting_period_ms, proposal_threshold, quorum_strategy_u8, quorum_value, ctx)
Notes:
quorum_strategy_u8:0 = Fixed,1 = Fractional.quorum_valuemeaning:- Fixed → minimum votes required (
yes + no). - Fractional → percentage per
100_000(e.g., 5% =5_000).
- Fixed → minimum votes required (
The core initializer publishes a shared GovernanceSystem<Capability, Token, Kind> that stores your capability, proposals table, config, and ID cursor.
Provide an entry that:
- Constructs a
ProposalKindpayload, including the target binding (e.g.,config_id: ID). - Calls
proposals::create_proposal(gs, proposer, description, clock, kind, ctx)whereproposer = tx_context::sender(ctx).
The framework will compute starts_at = now + voting_delay and ends_at = starts_at + voting_period (ms). It increments the ID cursor and emits ProposalCreated.
Provide an entry that forwards to votes::cast_vote(...):
- Accept a
Coin<Token>; itsvaluebecomes voting power. - Enforce vote type mapping
2/1/0for Yes/No/Abstain. - Voting is allowed when
now ∈ [starts_at, ends_at]and proposal status isActive.
The framework prevents double‑voting, locks the coin under the voter’s address, updates tallies, and emits VoteCast.
Provide an entry that:
- Borrows the proposal mutably and obtains the payload with
types::get_kind(...). - Verifies target binding (e.g.,
id(config) == *config_id). Mismatches must abort. - Calls your capability‑gated setter(s) to perform the change.
- Calls
proposals::execute_proposal(...)to mark status asExecutedand emitProposalExecuted.
Provide an entry that calls proposals::cancel_proposal(...). Only the original proposer can cancel while the proposal is Active.
Quorum is checked by quorum::is_quorum_reached(...) after the voting window ends:
- Fixed: require
yes + no ≥ minimum_votes. - Fractional: require
yes + no ≥ floor(total_supply_snapshot * percentage / 100_000).
If using fractional quorum, you must implement snapshot::get_total_supply_snapshot(proposal_id) to return a correct per‑proposal total. Without a valid value, every proposal will compare against zero and fail quorum.
The reference
quorumcode excludes abstain from quorum and mentions an optionalsafe_math::safe_mul_div_u64to avoid overflow duringsupply * percentage / 100_000calculations.
- ID binding: carry a target identifier (e.g.,
config_id: ID) in every proposal and assert it in execution. - Capability gating: mutate app state only via your capability‑gated setters; never bypass them.
- Voting window: enforce inclusive checks (
now ≥ starts_at && now ≤ ends_at). - Double‑vote prevention: handled by the framework; do not re‑implement.
- Rounding policy: fractional quorum uses floor; switch to ceil if policy requires (adjust math accordingly).
- String size: on‑chain
descriptionis stored; keep it compact.
- Proposals are sequentially numbered starting at 0 (
next_proposal_idcursor). get_proposal_state(...)returnsActiveboth before start (UI may label as “Pending”) and during the voting window. Use timestamps for display.- Events emitted:
ProposalCreated,VoteCast,ProposalExecuted. Use these for indexing and analytics.
- State transitions: create → (pending) → active → passed/rejected → executed/cancelled.
- Quorum edges: off‑by‑one around fixed thresholds; fractional math rounding behavior.
- Voting rules: invalid type, outside window, double vote.
- Execution auth: ID binding mismatch should abort.
- Snapshots: ensure
get_total_supply_snapshotis populated when fractional strategy is enabled.
Export at least:
initialize_governance<Token>(...)create_proposal<Token>(...)vote<Token>(...)execute_proposal<Token>(...)cancel_proposal<Token>(...)
Keep adapter code focused on payload construction, authorization, and binding checks; delegate lifecycle, tallies, and events to the framework modules.
governance::core— root shared object, init, proposal table helpersgovernance::proposals— creation/cancellation/execution, events, status derivationgovernance::votes— casting votes, locking coins, talliesgovernance::types— proposal struct, enums, quorum strategy helpersgovernance::config— voting delay/period/threshold + quorum strategy storagegovernance::quorum— fixed/fractional logic (denominator100_000, optionalsafe_math)governance::snapshot— total supply snapshots for fractional quorum
Next: Read the demo guide