For Developers
Technical reference for the Navigator system contracts.
Architecture
The Navigator system is implemented across several contracts:
NavigatorRegistry
Central contract — registration, staking, delegation, voting preferences, fees, slashing, lifecycle
XAllocationVoting
castNavigatorVote(citizen, roundId) — allocation votes for delegated citizens
B3TRGovernor
castNavigatorVote(proposalId, citizen) — governance votes for delegated citizens
VoterRewards
Deducts navigator fee at claim time, deposits to NavigatorRegistry escrow
VOT3
Transfer lock for delegated amounts, B3TR-to-VOT3 conversion for navigator staking
RelayerRewardsPool
Per-user skip tracking, governance action registration
NavigatorRegistry is a UUPS upgradeable contract with logic split into 6 external libraries: NavigatorStakingUtils, NavigatorDelegationUtils, NavigatorVotingUtils, NavigatorFeeUtils, NavigatorSlashingUtils, NavigatorLifecycleUtils.
Staking and B3TR-to-VOT3 Conversion
When a navigator stakes B3TR, the NavigatorRegistry converts it to VOT3 under the hood via VOT3.convertToVOT3(). This makes the staked amount count as voting power through the ERC20Votes checkpoint system.
NavigatorRegistry self-delegates on VOT3 during initialization (
VOT3.delegate(address(this)))Per-navigator staked amounts are tracked with
Checkpoints.Trace208for snapshot queriesOn withdrawal or slashing, VOT3 is converted back to B3TR via
VOT3.convertToB3TR()The staked VOT3 only counts for voting power — it cannot support proposals or be powered down
Voting Power Calculation
VotesUtils.getVotes() (allocation) and GovernorVotesLogic.getVotes() (governance) both include NavigatorRegistry.getStakedAmountAtTimepoint(account, timepoint) for non-delegated users. This returns 0 for non-navigators, so no conditional check is needed.
Delegation
Citizens delegate a specific VOT3 amount to one navigator at a time. The delegated amount is:
Locked — VOT3
_update()readsNavigatorRegistry.getDelegatedAmount(from)to enforce transfer lockCheckpointed —
Checkpoints.Trace208for snapshot-based voting powerSnapshotted at round start — mid-round changes take effect next round
Key Functions
delegate(navigator, amount)
First-time delegation
increaseDelegation(amount)
Add more VOT3 to current delegation
reduceDelegation(amount)
Reduce delegation (takes effect next round)
undelegate()
Full removal
Voting Mechanics
Allocation Voting
Two separate functions (NOT merged):
castVoteOnBehalfOf(voter, roundId)— existing auto-voting flow (unchanged)castNavigatorVote(citizen, roundId)— for delegated citizens. Voting power = delegated amount at round snapshot. Navigator's app preferences and percentages are applied.
Navigator setting preferences (setAllocationPreferences) also counts as their own allocation vote.
Governance Voting
B3TRGovernor.castNavigatorVote(proposalId, citizen):
Citizen must be delegated to a navigator at proposal snapshot
Navigator must have set a decision (1=Against, 2=For, 3=Abstain)
Voting power = delegated amount at proposal snapshot
Intent multiplier applied for rewards
Skip-or-Vote Logic
Both allocation and governance castNavigatorVote functions include built-in skip logic:
Navigator dead at snapshot: revert
NotDelegatedToNavigatorCitizen not a person at snapshot (invalid VePassport): skip immediately, reduce expected actions
Navigator dead now (exited/deactivated after snapshot): skip immediately, reduce expected actions
Navigator alive + preferences/decision set: vote normally
Navigator alive + no preferences/decision + skip window reached (2 hours before deadline): skip
Navigator alive + no preferences/decision + skip window not reached: revert (relayer retries later)
Rewards and Fees
Fee ordering at claim time:
Navigator fee = gross reward x feePercentage / 10000 (goes to NavigatorRegistry escrow)
Relayer fee = calculated on remainder (goes to RelayerRewardsPool)
Citizen receives the rest
Navigator fees are locked for a configurable number of rounds (default 4) before the navigator can claim them.
Slashing
Minor Slashing
Anyone can call reportRoundInfractions(navigator, roundId, proposalIds) after a round ends. The contract checks all six infraction types on-chain:
Missed allocation vote (requires delegations at round snapshot)
Late preferences — set after cutoff (requires delegations)
Stale preferences — no update for 3+ rounds (requires delegations)
Missed report — must submit every N rounds (requires delegations)
Missed governance vote (requires delegations)
Below minimum stake — stake was below
minStakeat round start AND still below at round end (applies regardless of delegations)
Infractions 1-5 are only evaluated if the navigator had active delegations at the round snapshot. Infraction 6 applies to all registered navigators — maintaining the minimum stake is an unconditional duty. The two-checkpoint check (start + end) gives navigators one full round to recover after a slash drops their stake.
If any infraction is found, one minor slash is applied: 5% of current remaining stake (compounding). At most one slash per round.
Below minimum stake — timing example
Assume minStake = 50,000 B3TR and minor slash = 5%.
R3
50,000
Slashed 5% for missed duties
47,500
No — was at minimum at start
R4
47,500
Navigator tops up 3,000 mid-round
50,500
No — was below at start but recovered by end
R5
50,500
Normal operation
50,500
No — above minimum
If the navigator does not recover:
R3
50,000
Slashed 5% for missed duties
47,500
No — was at minimum at start
R4
47,500
No action taken
47,500
Yes — below at start and end
R5
45,125
Slashed again (cascading)
42,869
Yes — still below
Major Slashing
Via governance: deactivateNavigator(navigator, slashPercentage, slashFees). Can slash up to 100% of stake plus forfeit all unclaimed locked fees.
Key View Functions
getStake(navigator)
Current staked amount
getStakedAmountAtTimepoint(navigator, timepoint)
Historical staked amount (for voting power)
getDelegatedAmount(citizen)
Current delegated VOT3 amount
getDelegatedAmountAtTimepoint(citizen, timepoint)
Historical delegation (for snapshot voting)
getNavigator(citizen)
Current navigator address
getNavigatorAtTimepoint(citizen, timepoint)
Historical navigator (respects dead-navigator invalidation)
isNavigator(account)
True if registered and active
canAcceptDelegations(navigator)
True if active, above min stake, not exiting
getAllocationPreferences(navigator, roundId)
App IDs and percentages for a round
getProposalDecision(navigator, proposalId)
Governance decision (1=Against, 2=For, 3=Abstain)
Relayer Integration
At round start, XAllocationVoting.startNewRound computes expected actions:
allocationUsers = autoVotingUsers + totalDelegatedCitizensgovernanceUsers = totalDelegatedCitizens(citizens only — relayers don't cast governance votes for auto-voters)
Per-user skip tracking in RelayerRewardsPool prevents deadlocks: when all vote actions for a user are skipped, the claim action is auto-reduced too.
Passport Validation
castNavigatorVote in both XAllocationVoting and B3TRGovernor validates the citizen's passport (personhood) at the round/proposal snapshot. If the citizen is not a valid person at that snapshot, the vote is skipped (not reverted) — the relayer pool expected actions are reduced and a skip event is emitted. This matches the existing auto-voting skip behavior.
Last updated
Was this helpful?