Decentralized protocol governance via GNS staking and voting.
Overview
Governance enables GNS holders to stake through gov/staker, receive xGNS, delegate voting power, create proposals, and vote on protocol changes. For more details, check out docs.
Configuration
The governance.Config type defines the governance parameters. The values used by a proposal are stored with that proposal; later reconfiguration does not rewrite an existing proposal. The current configuration can be changed through the governance Reconfigure handler. This type can be found in ../config.gno.
Field
Description
Default
VotingStartDelay
Delay before voting starts after proposal creation
1 day
VotingPeriod
Duration for collecting votes
7 days
VotingWeightSmoothingDuration
Duration used for timestamp-based voting-weight averaging
1 day
Quorum
Percentage of total xGNS supply required for proposal passage; total supply includes launchpad-held issuance
50%
ProposalCreationThreshold
Minimum xGNS balance required to create a proposal
1,000,000,000 xGNS
ExecutionDelay
Waiting period after voting ends before execution
1 day
ExecutionWindow
Time window during which an approved proposal can be executed
30 days
All values are configurable; the values above are the defaults in NewDefaultConfig.
Delegate GNS through gov/staker to receive an equal amount of xGNS.
Assign voting power to a delegatee (which may be the delegator itself).
Vote on proposals with the delegatee's timestamped delegation weight.
Undelegation removes voting power immediately; the default lockup before collecting GNS is 7 days and is configurable.
Launchpad-backed xGNS is included in total xGNS supply for quorum, but is not an ordinary user delegation record.
Proposal Types
Text: Signal proposals without execution.
CommunityPoolSpend: Treasury disbursements encoded as a community-pool transfer on execution.
ParameterChange: Protocol parameter updates dispatched to registered handlers.
Proposal Lifecycle
Creation
Requires the configured ProposalCreationThreshold xGNS balance (1,000,000,000 xGNS by default).
One active proposal per address.
Valid type and parameters are required. Community-pool spend amounts must be strictly positive, recipients must be valid, and token paths must be registered.
A proposal stores its configuration version, creation timestamp, creation block height, quorum amount, and timestamp used for historical delegation lookup. The block height is metadata; voting-weight lookup is timestamp-based.
Voting
Voting starts after the configured start delay (1 day by default) and runs for the configured voting period (7 days by default).
Weight is the average of the caller's delegation at the proposal's stored snapshot timestamp and at proposal creation time. The snapshot timestamp is createdAt - VotingWeightSmoothingDuration (clamped at zero), and the smoothing duration defaults to 24 hours.
Each address can vote only once on a proposal. Vote returns the applied vote weight as a decimal string.
Execution
A proposal is considered valid and executable when:
The voting period has ended.
Total votes meet the quorum amount computed at creation from the total xGNS supply (including launchpad-held issuance) and the proposal's configured quorum percentage.
YES votes strictly exceed NO votes (ties do not pass).
The configured execution delay (1 day by default) has passed after voting ends.
Execution occurs within the configured execution window (30 days by default).
Text proposals are informational and cannot be executed; anyone can trigger execution of an approved executable proposal.
An approved community-pool spend can still fail at execution if the pool no longer has enough of the registered token.
Technical Details
Vote Weight Calculation
1// `smoothing` is the proposal's configured VotingWeightSmoothingDuration.2snapshotTime=max(createdAt-smoothing,0)3weightAtSnapshot=getDelegationAt(voter,snapshotTime)4weightAtCreation=getDelegationAt(voter,createdAt)5voteWeight=(weightAtSnapshot+weightAtCreation)/2
The lookups read delegation history by Unix timestamp. This is not a block-height snapshot.
Quorum Calculation
1quorumWeight=totalXGnsSupplyAtProposalCreation// includes launchpad-held xGNS2quorumAmount=quorumWeight*quorumPercent/100// quorumPercent defaults to 50
The quorum amount is stored on the proposal and is not recomputed from the proposer's or voters' smoothed voting weight. A proposal passes only when total votes reach quorum and accumulated YES votes strictly exceed accumulated NO votes.
Rewards Distribution
Gov/staker exposes two reward streams:
GNS emission rewards use the emission accumulator and each staker's own stake history.
Protocol-fee rewards are tracked per token and accrual epoch. Each consumed bucket is divided by the total stake in force during that epoch, added to that token's Q128 accumulator, and settled over the staker's stake-event segments. Sub-unit Q128 remainders are retained for later collection.
CollectReward settles the emission stream and every known protocol-fee token. CollectEmissionReward and CollectProtocolFeeReward(tokenPath) are narrower paths; the token-specific protocol-fee path bounds both pending accrual buckets and stake events per call, so remaining work is collected later. Launchpad project wallets use the corresponding launchpad-only entry points.
Usage
These snippets call the public domain proxies from a realm function with a current cur token.
Import the corresponding proxy packages and qualify their function names in integrating code.
1// Through gov/staker: delegate GNS for xGNS voting power. 2Delegate(cross(cur),delegatee,1_000_000_000,"g1referrer...") 3 4// Create a text or community-pool proposal. 5ProposeText(cross(cur),"Title","Description") 6ProposeCommunityPoolSpend(cross(cur),"Title","Description",recipient,tokenPath,amount) 7 8// A parameter-change execution uses a registered handler. This is one valid 9// Reconfigure message (all seven parameters are required).10execution:="gno.land/r/gnoswap/gov/governance*EXE*Reconfigure*EXE*86400,604800,86400,50,1000000000,86400,2592000"11ProposeParameterChange(cross(cur),"Update config","Rationale",1,execution)1213// Vote; the return value is the applied weight formatted as a decimal string.14voteWeight:=Vote(cross(cur),proposalId,true)// YES1516// Execute after the configured timelock and then, if needed, start undelegation.17Execute(cross(cur),proposalId)18Undelegate(cross(cur),delegatee,250_000_000)1920// Collect only after the configured undelegation lockup has expired.21CollectUndelegatedGns(cross(cur))
Security
Timestamp-based smoothing reduces flash-loan-style voting manipulation; it is not a block snapshot.
Sybil resistance comes from stake-weighted delegation.
The execution delay and window constrain when approved executable proposals can run.
A single active proposal is allowed per proposer address.
Quorum is fixed from the creation-time total xGNS supply, including launchpad-held issuance.
Community-pool balance is checked when the approved transfer executes, not when the proposal is created.
NewProposalCommunityPoolSpendData creates proposal data for a community pool spend proposal. Automatically generates the execution message for the token transfer.
Parameters:
tokenPath: path of the token to transfer
to: recipient address for the transfer
amount: amount of tokens to transfer
communityPoolPackagePath: package path of the community pool contract
Returns:
*ProposalData: proposal data configured for community pool spending
NewProposalExecutionData creates proposal data for a parameter change proposal. Each message in executions should be formatted as <pkgPath>*EXE*<function>*EXE*<params>, separated by *GOV* when there are multiple messages.
Parameters:
numToExecute: number of parameter changes to execute
executions: raw encoded execution string with parameter changes
Returns:
*ProposalData: proposal data configured for parameter changes
NewProposalScheduleStatus creates a new schedule status with calculated timestamps. This constructor takes the governance timing parameters and calculates all important timestamps for the proposal's lifecycle.
Parameters:
votingStartDelay: delay before voting starts (seconds)
votingPeriod: duration of voting period (seconds)
executionDelay: delay before execution can start (seconds)
executionWindow: window during which execution is allowed (seconds)
createdAt: timestamp when proposal was created
Returns:
*ProposalScheduleStatus: new schedule status with calculated times
NewProposalStatus creates a new proposal status with the specified configuration. This initializes all status components with the governance configuration and timing.
Parameters:
config: governance configuration to use
maxVotingWeight: maximum voting weight for this proposal
executable: whether this proposal type can be executed
createdAt: timestamp when proposal was created
quorumWeight: total xGNS supply at proposal creation, used as the quorum base
CreateParameterHandlers initializes and configures all supported parameter handlers. This function defines all the parameter changes that can be executed through governance proposals. It covers configuration changes for various system components including pools, staking, fees, etc.
Returns:
*ParameterRegistry: fully configured registry with all supported handlers
1typeParameterHandlerinterface{ 2// Execute processes the parameters and applies the changes to the system. 3// The `_ int, rlm realm` discriminator pair forwards the governance proxy's 4// realm value into the handler so any cross-realm calls inside the closure 5// run under the proxy's identity (the only address with caller-allowlist 6// permission against the targeted /r/ realms). 7// 8// Parameters: 9// - _: integer discriminator required by the crossing entrypoint; callers use 010// - rlm: current governance proxy realm context forwarded to the handler11// - params: serialized parameter values to validate and apply in handler order12//13// Returns:14// - error: nil when the parameter change succeeds, or an execution/validation error15Execute(_int,rlmrealm,params[]string)error16}
ParameterHandler interface defines the contract for parameter execution handlers. Each handler is responsible for executing specific parameter changes in the system.
1typeParameterHandlerOptionsstruct{2pkgPathstring// Package path of the target contract3functionstring// Function name to be called4paramCountint// Expected number of parameters5handlerFuncfunc(_int,rlmrealm,_[]string)error// Function that executes the parameter change6paramValidators[]paramValidator// Optional per-parameter validators for proposal-time checks7compositeValidatorcompositeValidator// Optional cross-parameter validator for static business rules8}
ParameterHandlerOptions contains the configuration and execution logic for a parameter handler. This struct encapsulates all information needed to identify and execute a parameter change.
NOTE: handlerFunc uses `rlm realm` (rather than `cur realm`) as the realm parameter name. The v2 preprocessor reserves the `cur` name for the first realm-type parameter of top-level crossing function declarations and `t.Run` closures only; using it as the realm-parameter name on a multi-parameter struct-field function value trips a parser check ("only the first realm type argument of a crossing function may have name `cur`"). Naming it `rlm` keeps the lowering identical without the syntax constraint.
Execute validates parameter count and executes the handler function. This method ensures the correct number of parameters are provided before execution.
Parameters:
_: integer discriminator required by the crossing entrypoint; callers use 0
rlm: governance proxy realm threaded into the wrapped handler
params: serialized parameter values to pass to the handler
Returns:
error: parameter-count error or the error returned by the wrapped handler
ParameterRegistry manages the collection of parameter handlers for governance execution. This registry allows proposals to execute parameter changes across different system contracts.
ParameterChangesInfos parses the execution messages and returns structured parameter change information. Each message is expected to be in format: pkgPath*EXE*function*EXE*params
Returns:
[]ParameterChangeInfo: slice of parsed parameter change information
error: validation error if any execution message is malformed
CommunityPoolSpendTokenPath returns the token path for a community pool spend proposal. It returns an empty string when the proposal has no community-pool spend data.
Returns:
string: token package path to spend, or empty string for other proposal types
IsActive determines whether the proposal is active at current. Upcoming, voting, and passed executable proposals are active; rejected, expired, executed, canceled, and passed text proposals are inactive.
Parameters:
current: timestamp at which the proposal status is evaluated
Returns:
bool: true when the proposal can still be voted on or executed at current
IsPassedExecutableAt checks if the current time has passed the execution start time. When true, approved proposals can be executed (after execution delay).
IsPassedExpiredAt checks if the current time has passed the execution expiration time. When true, the proposal can no longer be executed and has expired.
StatusType determines the current status of the proposal based on timing, voting, and actions. This is the main status calculation method that considers all factors.
Parameters:
current: current timestamp to evaluate status at
Returns:
ProposalStatusType: current status of the proposal
IsPassed determines if the proposal has passed the voting requirements. A proposal passes when total vote weight reaches quorum and "yes" votes strictly exceed "no" votes.
Returns:
bool: true when quorum is met and yes weight is greater than no weight