From vechain-ai
Provides domain knowledge on VeBetterDAO Navigators: freshness/governance intent rewards multipliers and delegation system. Use for contracts, staking, fees, slashing in frontend/backend code.
npx claudepluginhub vechain/vechain-ai-skills --plugin secure-github-actionsThis skill uses the workspace's default tool permissions.
Domain knowledge for the Navigators feature: rewards multipliers and navigator delegation system. Both phases ship together in one release.
Monitors deployed URLs for regressions after deploys, merges, or upgrades by checking HTTP status, console errors, network failures, performance (LCP/CLS/INP), content, and API health.
Share bugs, ideas, or general feedback.
Domain knowledge for the Navigators feature: rewards multipliers and navigator delegation system. Both phases ship together in one release.
Applied in RoundVotesCountingUtils.countVote() via FreshnessUtils library — modifies reward weight only, NOT on-chain voting power.
| Behavior | Multiplier |
|---|---|
| Updated this round (weekly) | x3 (30000 bp) |
| Updated within 2 rounds (bi-weekly) | x2 (20000 bp) |
| No update for >= 3 rounds | x1 (10000 bp) |
lastFingerprint == bytes32(0))VoterRewards.setFreshnessMultipliers(tier1, tier2, tier3)Checkpoints.Trace208 in VoterRewards (snapshotted at round start)Applied in GovernorVotesLogic before registerVote(). Per-proposal at registration time — each proposal independent.
| Vote Type | Multiplier |
|---|---|
| For / Against | x1 (10000 bp) |
| Abstain | x0.30 (3000 bp) |
VoterRewards.setIntentMultipliers(forAgainst, abstain)RoundVotesCountingUtils.countVote())GovernorVotesLogic before registerVote)cycleToVoterToGMWeight, claimed against the GM pool — it does not multiply the allocation/governance reward weight. The legacy weight * GM path in VoterRewards.registerVote() only runs when gmPoolAmount == 0VoterRewards.initializeV7 creates two checkpoints per multiplier:
submitReport(metadataURI)balanceOf() returns full VOT3 balance (including delegated). Use unlockedBalance() for transferable amount_update() enforces: balance - delegatedAmount >= transferAmountINavigatorRegistry.getDelegatedAmount(from) — no role grant, no mapping on VOT3; VOT3 stores only the registry addressXAllocationVoting.disableAutoVotingFor)| Function | Purpose | Events |
|---|---|---|
delegate(navigator, amount) | First-time only. Auto-clears stale delegation from dead navigator. | DelegationCreated |
increaseDelegation(amount) | Add more VOT3 to existing delegation | DelegationIncreased |
reduceDelegation(amount) | Partial or full reduction (full = removes delegation) | DelegationDecreased or DelegationRemoved |
undelegate() | Fully remove delegation | DelegationRemoved |
NavigatorRegistry tracks delegated citizens globally:
Checkpoints.Trace208 totalDelegatedCitizens — incremented on delegate, decremented on undelegatemapping(address => uint256) navigatorCitizenCount — per-navigator countgetTotalDelegatedCitizensAtTimepoint(timepoint) — used by XAllocationVoting.startNewRound to compute governanceUsersannounceExit() / deactivateNavigator(): navigatorCitizenCount zeroed and totalDelegatedCitizens decremented immediatelyTwo separate functions (NOT merged):
castVoteOnBehalfOf(voter, roundId) — existing auto-voting flow (unchanged)castNavigatorVote(citizen, roundId) — navigator-delegated citizens:
castVoteOnBehalfOf behaviorRelayerAction.VOTE for the callerNavigator setting preferences = their own vote (personal VOT3 balance + staked B3TR converted to VOT3)
castNavigatorVote(proposalId, citizen) — separate function:
RelayerAction.VOTE in RelayerRewardsPoolgetVotes(account, timepoint) on both governors:
timepoint: returns delegated amount only (this is the citizen's voting power)VOT3.getPastVotes (+ getDepositVotingPower in XAllocationVoting)navigatorRegistry == address(0) (backwards compatible)getQuadraticVotingPower calls getVotes internally, so delegation cascades to sqrt(delegated) * 1e9castVote reverts with DelegatedToNavigator if citizen had a navigator at snapshot. Navigator votes use getDelegatedAmountAtTimepoint directlyCitizens counted in relayer expected actions:
XAllocationVoting.startNewRound computes:
allocationUsers = autoVotingUsers + totalDelegatedCitizensgovernanceUsers = totalDelegatedCitizens (citizens only — relayers don't cast governance votes for auto-users)B3TRGovernor.getActiveProposals()RelayerRewardsPool.setTotalActionsForRoundWithGovernance(roundId, allocationUsers, governanceUsers, activeProposalIds)allocationUsers * 2 (vote + claim) + governanceUsers * activeProposals (governance votes)Skip-or-vote flow:
castNavigatorVote (both allocation and governance) merges vote and skip into a single function:
NotDelegatedToNavigator (citizen was never delegated)NavigatorVoteSkipped / NavigatorGovernanceVoteSkipped)Skip window = 2 hours (~720 blocks) before round/proposal deadline. Constants: CITIZEN_SKIP_WINDOW_BLOCKS (allocation), GOVERNANCE_SKIP_WINDOW_BLOCKS (governance).
Per-user skip tracking (RelayerRewardsPool V3):
reduceUserAllocationVote(roundId, user) — reduces one allocation vote action for a specific userreduceUserGovernanceVote(roundId, user, proposalId) — reduces one governance vote action per user/proposalActive proposals caching:
B3TRGovernor.proposalsForRound mapping populated at proposal creationgetActiveProposals() filters by Active state from current round's proposalsRelayerRewardsPool.activeProposalsForRound at round startLate preferences infraction:
preferenceCutoffPeriod blocks (~24hr) before round deadlineAuto-voting disabled on delegation:
NavigatorRegistry.delegate() calls XAllocationVoting.disableAutoVotingFor(citizen) (privileged call)Preferred relayer:
preferredRelayer manually via relayerRewardsPool.setPreferredRelayer(relayer)Fee ordering for citizens (at claim time):
Rewards proportional to delegated amount at snapshot only (not full balance). Citizen's own GM level applies.
Fee ordering:
1. Navigator fee = gross reward * navigatorFeePercentage / 10000 (goes to NavigatorRegistry escrow)
2. Relayer fee = relayerRewardsPool.calculateRelayerFee(gross - navigatorFee) (goes to RelayerRewardsPool)
3. Citizen receives the rest
Inside NavigatorRegistry: mapping(navigator => mapping(round => amount))
Reportable by anyone via public function. Slashed funds to treasury.
Six infraction types:
preferenceCutoffPeriod (~24hr) before round deadline (requires delegations)minStake at round start AND still below at round end (applies regardless of delegations)Infraction flags (bit flags in NavigatorSlashingUtils):
FLAG_MISSED_ALLOCATION = 1 << 0 (1)FLAG_LATE_PREFERENCES = 1 << 1 (2)FLAG_STALE_PREFERENCES = 1 << 2 (4)FLAG_MISSED_REPORT = 1 << 3 (8)FLAG_MISSED_GOVERNANCE = 1 << 4 (16)FLAG_BELOW_MIN_STAKE = 1 << 5 (32)Reporting model:
reportRoundInfractions(navigator, roundId, proposalIds) after the round endsstakeAtRoundStart < minStake AND stakeAtRoundEnd < minStake. This gives navigators one full round to recover after a slash drops their stake.RoundStillActiveIf stake drops below minimum: stays active but can't accept new delegations and will be slashed if not topped up by round end.
Below min stake timing example (minStake = 50K):
| Round | Start stake | Event | End stake | belowMinStake? |
|---|---|---|---|---|
| R3 | 50K | Slashed 5% for missed duties → 47.5K | 47.5K | No (was at min at start) |
| R4 | 47.5K | Navigator tops up 3K mid-round | 50.5K | No (below at start, recovered by end) |
| R5 | 50.5K | — | 50.5K | No (above min) |
Vs. if navigator does NOT recover:
| Round | Start stake | Event | End stake | belowMinStake? |
|---|---|---|---|---|
| R3 | 50K | Slashed 5% → 47.5K | 47.5K | No (was at min at start) |
| R4 | 47.5K | No action | 47.5K | Yes (below at start AND end) |
| R5 | 45.125K | Slashed again | 42.87K | Yes (cascading) |
For: manipulation, bribery, vote buying, undisclosed compensation. Process: 5 navigators lock stakes to trigger → public findings within 4 rounds → governance vote.
announceExit() — event emitted, 1 round notice (governance-configurable)isDelegated() returns false, getDelegatedAmount() returns 0castNavigatorVote fails with NotDelegatedToNavigatorNavigatorCannotAcceptDelegations)undelegate() first (stale delegation auto-cleared)withdrawStake() becomes available — stake returned immediately. Locked fees follow their individual 4-round schedules via claimFee(roundId)finalizeExit() — exit is self-enforcing via checkpoint timingCitizens don't need to take any action — VOT3 automatically unlocked via lazy invalidation:
getDelegatedAmount(citizen) checks if navigator is dead → returns 0getDelegatedAmount → sees 0 → transfer alloweddelegate(newNavigator, amount) directly — old stale checkpoint auto-clearedgetDelegatedAmountAtTimepoint not affected)No DelegationRemoved events emitted when a navigator exits or is deactivated. Indexers must treat ExitAnnounced / NavigatorDeactivated events as bulk removal of all citizen delegations for that navigator.
Navigators stake B3TR, which is converted to VOT3 under the hood. The staked amount is held by NavigatorRegistry as VOT3 and counts as the navigator's personal voting power (checkpointed via stakedAmount Trace208). NavigatorRegistry self-delegates on VOT3 during initialization so the total voting supply is accurate.
The staked VOT3 only counts for voting power — it cannot be used to support proposals or powered down. It does not earn direct rewards.
| Actor | Voting power source | What's NOT counted |
|---|---|---|
| Navigator (own vote) | Personal VOT3 balance + staked B3TR (as VOT3) | Delegated VOT3 from citizens |
| Citizen (navigated vote) | Their delegated VOT3 amount at snapshot | Remaining undelegated VOT3 |
Delegated VOT3 never leaves the citizen's wallet. Navigator decides what to vote; each citizen's delegated amount is cast as a separate vote.
B3TR↔VOT3 conversion flow: On register()/addStake(): B3TR transferred to contract → converted to VOT3 via VOT3.convertToVOT3(). On reduceStake()/withdrawStake()/slash: VOT3 converted back to B3TR via VOT3.convertToB3TR() → transferred out.
Economics: staked B3TR gives the navigator voting power. They must also attract delegation to earn fees. Example: 50K B3TR staked (= 50K VOT3 voting power) → up to 500K VOT3 delegated → navigator earns 20% fee on rewards generated by those 500K VOT3.
| Protection | Contract | Error |
|---|---|---|
| Self-delegation blocked | NavigatorDelegationUtils | SelfDelegationNotAllowed |
| Delegators can't register as navigator | NavigatorStakingUtils | DelegatorCannotRegister |
| Citizens can't manual vote (allocation) | XAllocationVoting | DelegatedToNavigator |
| Citizens can't manual vote (governance) | GovernorVotesLogic | DelegatedToNavigator |
| Navigators can't enable auto-voting | XAllocationVoting | NavigatorCannotEnableAutoVoting |
| Citizens can't enable auto-voting | XAllocationVoting | DelegatedToNavigator |
| Fee deposit restricted to VoterRewards | NavigatorRegistry | UnauthorizedCaller |
| Lazy invalidation on exit/deactivation | NavigatorDelegationUtils | _isNavigatorDead() |
NavigatorRegistry — UUPS upgradeable facade with 6 libraries: NavigatorStakingUtils, NavigatorDelegationUtils, NavigatorVotingUtils, NavigatorFeeUtils, NavigatorSlashingUtils, NavigatorLifecycleUtilsVOT3.sol (V2) — reads delegation lock from NavigatorRegistry, no duplicate storageXAllocationVoting.sol (V9) — castNavigatorVote, disableAutoVotingFor, startNewRound with citizens + governanceB3TRGovernor.sol (V10) — castNavigatorVote, getActiveProposals, proposalsForRound, relayerRewardsPoolVoterRewards.sol (V7) — multiplier checkpoints, navigator fee deduction, intent multiplierRelayerRewardsPool.sol (V3) — per-user skip tracking, setTotalActionsForRoundWithGovernance, activeProposalsForRoundVOT3.sol — circulating supply for max stake capVeBetterPassport — personhood checks (validated at snapshot for navigated citizens; non-persons skipped)RelayerRewardsPool — relayer registration check, preferredRelayer, per-user skip trackingB3TRGovernor — active proposals query at round start/packages/contracts/contracts/navigator/NavigatorRegistry.sol — main facade/packages/contracts/contracts/navigator/libraries/NavigatorStorageTypes.sol — single ERC-7201 namespace/packages/contracts/contracts/navigator/libraries/NavigatorStakingUtils.sol/packages/contracts/contracts/navigator/libraries/NavigatorDelegationUtils.sol/packages/contracts/contracts/navigator/libraries/NavigatorVotingUtils.sol/packages/contracts/contracts/navigator/libraries/NavigatorFeeUtils.sol/packages/contracts/contracts/navigator/libraries/NavigatorSlashingUtils.sol/packages/contracts/contracts/navigator/libraries/NavigatorLifecycleUtils.sol/packages/contracts/contracts/interfaces/INavigatorRegistry.sol/packages/contracts/contracts/x-allocation-voting-governance/XAllocationVoting.sol/packages/contracts/contracts/x-allocation-voting-governance/libraries/VotesUtils.sol/packages/contracts/contracts/governance/B3TRGovernor.sol/packages/contracts/contracts/governance/libraries/GovernorVotesLogic.sol/packages/contracts/contracts/governance/libraries/GovernorStorageTypes.sol/packages/contracts/contracts/governance/libraries/GovernorConfigurator.sol/packages/contracts/contracts/VoterRewards.sol/packages/contracts/contracts/VOT3.sol/packages/contracts/contracts/RelayerRewardsPool.sol