> For the complete documentation index, see [llms.txt](https://docs.vebetterdao.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.vebetterdao.org/navigators/for-developers.md).

# For Developers

Technical reference for the Navigator system contracts.

## Architecture

The Navigator system is implemented across several contracts:

| Contract               | Role                                                                                                |
| ---------------------- | --------------------------------------------------------------------------------------------------- |
| **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.Trace208` for snapshot queries
* On 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()` reads `NavigatorRegistry.getDelegatedAmount(from)` to enforce transfer lock
* **Checkpointed** — `Checkpoints.Trace208` for snapshot-based voting power
* **Snapshotted at round start** — mid-round changes take effect next round

### Key Functions

| Function                      | Description                                 |
| ----------------------------- | ------------------------------------------- |
| `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:

1. Navigator dead at snapshot: revert `NotDelegatedToNavigator`
2. Citizen not a person at snapshot (invalid VePassport): skip immediately, reduce expected actions
3. Navigator dead now (exited/deactivated after snapshot): skip immediately, reduce expected actions
4. Navigator alive + preferences/decision set: vote normally
5. Navigator alive + no preferences/decision + skip window reached (2 hours before deadline): skip
6. Navigator alive + no preferences/decision + skip window not reached: revert (relayer retries later)

## Rewards and Fees

Fee ordering at claim time:

1. **Navigator fee** = gross reward x feePercentage / 10000 (goes to NavigatorRegistry escrow)
2. **Relayer fee** = calculated on remainder (goes to RelayerRewardsPool)
3. **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:

1. Missed allocation vote (requires delegations at round snapshot)
2. Late preferences — set after cutoff (requires delegations)
3. Stale preferences — no update for 3+ rounds (requires delegations)
4. Missed report — must submit every N rounds (requires delegations)
5. Missed governance vote (requires delegations)
6. **Below minimum stake** — stake was below `minStake` at 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%.

| Round | Stake at start | What happens                      | Stake at end | Slashed for belowMinStake?                   |
| ----- | -------------- | --------------------------------- | ------------ | -------------------------------------------- |
| 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:

| Round | Stake at start | What happens                 | Stake at end | Slashed for belowMinStake?       |
| ----- | -------------- | ---------------------------- | ------------ | -------------------------------- |
| 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

| Function                                            | Returns                                                     |
| --------------------------------------------------- | ----------------------------------------------------------- |
| `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 + totalDelegatedCitizens`
* `governanceUsers = 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.
