# Gov Staker
Governance delegation and xGNS-based voting power management.
## Overview
Gov Staker accepts GNS staking, mints and burns xGNS, maintains delegatee balances and timestamped delegation history, tracks undelegation lockups, and settles two reward streams for stakers: GNS emission rewards and protocol-fee rewards in any registered token.
## Configuration
- **Undelegation Lockup**: configurable; the default is 7 days before undelegated GNS can be collected.
- **Reward Sources**: GNS emission (single token) and protocol-fee distribution (one accumulator per fee token).
- **Delegation State**: per-delegator/delegatee records, total and user timestamp histories, and reward stake events.
## Core Features
### Delegation
- Delegate GNS to any valid address; the delegated amount is mirrored as xGNS voting power.
- Redelegate between delegatees without a lockup. Redelegation performs an immediate remove-plus-add for reward accounting.
- Track undelegated balances until the configured lockup expires; collecting then burns the corresponding xGNS and returns GNS.
### Rewards
- `CollectReward` claims both the emission stream and all known protocol-fee tokens.
- `CollectEmissionReward` claims only GNS emission rewards.
- `CollectProtocolFeeReward` claims one protocol-fee token; its bucket and stake-event work is bounded per call, so remaining work is collected later.
- Launchpad can collect the matching emission and protocol-fee rewards for a registered project wallet through launchpad-only entry points.
Protocol-fee rewards are not calculated as a single current-balance share. Each token's fee buckets are divided by the total stake in force during their accrual epochs, accumulated in Q128 fixed point, and settled across each staker's timestamped stake-event segments. Fractional remainders remain in state for later collection.
### History and Snapshots
- Historical delegation snapshots are timestamp lookups used by governance's configured smoothing calculation; they are not block-height snapshots.
- Cleanup functions preserve history needed by active proposals.
## Key Functions
### `Delegate`
Transfers GNS from the caller, mints the same amount of xGNS, and assigns the delegated voting power to a target address. The caller must approve the GNS transfer first.
### `Undelegate`
Removes voting power immediately and creates a withdrawal subject to the configured lockup.
### `Redelegate`
Moves delegated balance from one delegatee to another immediately, without creating a user-facing lockup.
### `CollectReward`
Claims the caller's GNS emission rewards and all known protocol-fee rewards. The all-token path intentionally grows with the number of known fee tokens.
### `CollectEmissionReward`
Claims only the caller's accumulated GNS emission reward.
### `CollectProtocolFeeReward`
Claims one token path's accumulated protocol-fee reward. A later call may be required when pending accrual buckets or stake events exceed the per-call bound.
### `CollectUndelegatedGns`
Collects GNS after the configured undelegation lockup has passed and burns the corresponding xGNS.
### `CollectRewardFromLaunchPad`
Collects both reward streams for a registered launchpad project wallet. This entry point is callable only by the launchpad contract and sends rewards to the supplied project-wallet address.
## Delegation Logic
### Delegation Flow
1. Approve GNS spending by the gov/staker realm.
2. Delegate GNS to a delegatee; xGNS is minted and timestamped history is updated.
3. Governance reads the delegatee's history at proposal-defined timestamps for vote weight.
4. Undelegate to start the lockup, or redelegate immediately to another delegatee.
5. After the lockup expires, collect undelegated GNS.
## Usage
These snippets call the public domain proxy from a realm function with a current `cur` token.
Import the proxy package and qualify its function names in integrating code.
```go
// Delegate GNS to another address; xGNS voting power is minted 1:1.
delegatedAmount := Delegate(cross(cur), delegatee, 1_000_000_000, "g1referrer...")
// Redelegate part of the active balance immediately.
Redelegate(cross(cur), delegatee, newDelegatee, 500_000_000)
// Claim both GNS emission and all known protocol-fee tokens.
CollectReward(cross(cur))
// Or claim only one stream/token.
CollectEmissionReward(cross(cur))
CollectProtocolFeeReward(cross(cur), tokenPath)
// Start undelegation. Collect only after the configured lockup (7 days by default).
Undelegate(cross(cur), delegatee, 250_000_000)
// ...wait until the lockup has expired...
CollectUndelegatedGns(cross(cur))
```
## Security
- Timestamped delegation history and configurable smoothing reduce flash-loan-style voting manipulation; they do not provide a block snapshot.
- Undelegation removes voting power immediately, while the lockup delays GNS withdrawal.
- Protocol-fee stake changes must remain independent of the number of fee tokens; only collection folds fee buckets.
- Launchpad reward entry points are restricted to the launchpad contract and registered project wallets.
- Snapshot cleanup must preserve data still needed by active proposals.
CleanStakerDelegationSnapshotByAdmin(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, threshold int64, target string)
CollectEmissionReward(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
CollectEmissionRewardFromLaunchPad(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, to string)
CollectProtocolFeeReward(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, tokenPath string)
CollectProtocolFeeRewardFromLaunchPad(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, to string, tokenPath string)
CollectReward(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
CollectRewardFromLaunchPad(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, to string)
CollectUndelegatedGns(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}) int64
Delegate(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, to string, amount int64, referrer string) int64
ExistsDelegation(delegationID int64) bool
GetClaimableRewardByAddress(addr string) (int64, map[string]int64, interface {Error func() string})
GetClaimableRewardByLaunchpad(addr string) (int64, map[string]int64, interface {Error func() string})
GetClaimableRewardByRewardID(rewardID string) (int64, map[string]int64, interface {Error func() string})
GetCollectableWithdrawAmount(delegationID int64) int64
GetDelegations() *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree
GetDelegationWithdrawCount(delegationID int64) int
GetDelegationWithdraws(delegationID int64, offset int, count int) ([]gno.land/r/gnoswap/gov/staker.DelegationWithdraw, interface {Error func() string})
GetDelegatorDelegations(delegator string) *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree
GetEmissionAccumulatedTimestamp() int64
GetEmissionAccumulatedX128PerStake() *gno.land/p/gnoswap/uint256/v1.Uint
GetEmissionDistributedAmount() int64
GetImplementationPackagePath() string
GetLaunchpadProjectDeposit(projectAddr string) (int64, bool)
GetProtocolFeeAccumulatedTimestamp() int64
GetProtocolFeeAccumulatedX128PerStake(tokenPath string) *gno.land/p/gnoswap/uint256/v1.Uint
GetProtocolFeeAmount(tokenPath string) int64
GetTotalDelegated() int64
GetTotalDelegationAmountAtSnapshot(snapshotTime int64) (int64, bool)
GetTotalLockedAmount() int64
GetTotalxGnsSupply() int64
GetUnDelegationLockupPeriod() int64
GetUserDelegationAmountAtSnapshot(userAddr string, snapshotTime int64) (int64, bool)
GetUserDelegationIDs(delegator string, delegatee string) []int64
HasDelegationSnapshotsKey() bool
NewCounter() *gno.land/r/gnoswap/gov/staker.Counter
NewDelegation(id int64, delegateFrom string, delegateTo string, delegateAmount int64, createdHeight int64, createdAt int64) *gno.land/r/gnoswap/gov/staker.Delegation
NewDelegationManager() *gno.land/r/gnoswap/gov/staker.DelegationManager
NewDelegationTree() *gno.land/p/nt/bptree/v0.BPTree
NewDelegationWithdraw(delegationID int64, unDelegateAmount int64, createdHeight int64, createdAt int64, unDelegationLockupPeriod int64) struct{delegationID int64; unDelegateAmount int64; unDelegatedHeight int64; unDelegatedAt int64; collectedAmount int64; collectableTime int64; collectedAt int64; collected bool}
NewDelegationWithdrawWithoutLockup(delegationID int64, unDelegateAmount int64, createdHeight int64, createdAt int64) struct{delegationID int64; unDelegateAmount int64; unDelegatedHeight int64; unDelegatedAt int64; collectedAmount int64; collectableTime int64; collectedAt int64; collected bool}
NewEmissionRewardManager() *gno.land/r/gnoswap/gov/staker.EmissionRewardManager
NewEmissionRewardState(accumulatedRewardX128PerStake *gno.land/p/gnoswap/uint256/v1.Uint) *gno.land/r/gnoswap/gov/staker.EmissionRewardState
NewGovStakerStore(kvStore interface {AddAuthorizedCaller func(int, .uverse.realm, .uverse.address, gno.land/p/gnoswap/store/v1.Permission) .uverse.error; Delete func(int, .uverse.realm, string) .uverse.error; Get func(string) (interface {}, .uverse.error); GetAddress func(string) (.uverse.address, .uverse.error); GetAllKeys func() ([]string, .uverse.error); GetAuthorizedCallers func() (map[.uverse.address]gno.land/p/gnoswap/store/v1.Permission, .uverse.error); GetBPTree func(string) (*gno.land/p/nt/bptree/v0.BPTree, .uverse.error); GetBool func(string) (bool, .uverse.error); GetDomainAddress func() .uverse.address; GetInt64 func(string) (int64, .uverse.error); GetString func(string) (string, .uverse.error); GetUint64 func(string) (uint64, .uverse.error); Has func(string) bool; IsWriteAuthorized func(.uverse.address) bool; RemoveAuthorizedCaller func(int, .uverse.realm, .uverse.address) .uverse.error; Set func(int, .uverse.realm, string, interface {}) .uverse.error; UpdateAuthorizedCaller func(int, .uverse.realm, .uverse.address, gno.land/p/gnoswap/store/v1.Permission) .uverse.error}) interface {GetAllDelegations func() *gno.land/p/nt/bptree/v0.BPTree; GetDelegation func(int64) (*gno.land/r/gnoswap/gov/staker.Delegation, bool); GetDelegationCounter func() *gno.land/r/gnoswap/gov/staker.Counter; GetDelegationManager func() *gno.land/r/gnoswap/gov/staker.DelegationManager; GetEmissionRewardManager func() *gno.land/r/gnoswap/gov/staker.EmissionRewardManager; GetLaunchpadProjectDeposits func() *gno.land/r/gnoswap/gov/staker.LaunchpadProjectDeposits; GetProtocolFeeRewardManager func() *gno.land/r/gnoswap/gov/staker.ProtocolFeeRewardManager; GetTotalDelegatedAmount func() int64; GetTotalDelegationHistory func() *gno.land/r/gnoswap/gov/staker.UintTree; GetTotalLockedAmount func() int64; GetUnDelegationLockupPeriod func() int64; GetUserDelegationHistory func() *gno.land/p/nt/bptree/v0.BPTree; HasDelegation func(int64) bool; HasDelegationCounterStoreKey func() bool; HasDelegationManagerStoreKey func() bool; HasDelegationsStoreKey func() bool; HasEmissionRewardManagerStoreKey func() bool; HasLaunchpadProjectDepositsStoreKey func() bool; HasProtocolFeeRewardManagerStoreKey func() bool; HasTotalDelegatedAmountStoreKey func() bool; HasTotalDelegationHistoryStoreKey func() bool; HasTotalLockedAmountStoreKey func() bool; HasUnDelegationLockupPeriodStoreKey func() bool; HasUserDelegationHistoryStoreKey func() bool; RemoveDelegation func(int, .uverse.realm, int64) .uverse.error; SetDelegation func(int, .uverse.realm, int64, *gno.land/r/gnoswap/gov/staker.Delegation) .uverse.error; SetDelegationCounter func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.Counter) .uverse.error; SetDelegationManager func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.DelegationManager) .uverse.error; SetDelegations func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetEmissionRewardManager func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.EmissionRewardManager) .uverse.error; SetLaunchpadProjectDeposits func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.LaunchpadProjectDeposits) .uverse.error; SetProtocolFeeRewardManager func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.ProtocolFeeRewardManager) .uverse.error; SetTotalDelegatedAmount func(int, .uverse.realm, int64) .uverse.error; SetTotalDelegationHistory func(int, .uverse.realm, *gno.land/r/gnoswap/gov/staker.UintTree) .uverse.error; SetTotalLockedAmount func(int, .uverse.realm, int64) .uverse.error; SetUnDelegationLockupPeriod func(int, .uverse.realm, int64) .uverse.error; SetUserDelegationHistory func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error}
NewLaunchpadProjectDeposits() *gno.land/r/gnoswap/gov/staker.LaunchpadProjectDeposits
NewProtocolFeeRewardManager() *gno.land/r/gnoswap/gov/staker.ProtocolFeeRewardManager
NewProtocolFeeRewardState() *gno.land/r/gnoswap/gov/staker.ProtocolFeeRewardState
NewProtocolFeeTokenAccumulator() *gno.land/r/gnoswap/gov/staker.ProtocolFeeTokenAccumulator
NewProtocolFeeTokenRewardState(eventCursor int64, segmentStakedAmount int64, segmentStartX128 *gno.land/p/gnoswap/uint256/v1.Uint) *gno.land/r/gnoswap/gov/staker.ProtocolFeeTokenRewardState
NewUintTree() *gno.land/r/gnoswap/gov/staker.UintTree
NewUserDelegationTree() *gno.land/p/nt/bptree/v0.BPTree
Redelegate(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, delegatee string, newDelegatee string, amount int64) int64
RegisterInitializer(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, initializer func(int, .uverse.realm, gno.land/r/gnoswap/gov/staker.IGovStakerStore) gno.land/r/gnoswap/gov/staker.IGovStaker)
Render(path string) string
SetAmountByProjectWallet(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, addr string, amount int64, add bool)
SetUnDelegationLockupPeriodByAdmin(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, period int64)
Undelegate(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, from string, amount int64) int64
UpgradeImpl(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, packagePath string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.