From vechain-ai
Provides domain knowledge for VeBetterDAO auto-voting and relayer system including XAllocationVoting, VoterRewards, RelayerRewardsPool contracts, relayer nodes, and Next.js dashboard. For relayer, gasless voting, rewards work.
npx claudepluginhub vechain/vechain-ai-skills --plugin secure-github-actionsThis skill uses the workspace's default tool permissions.
Complete domain knowledge for the auto-voting and relayer ecosystem. This skill provides context for working on any component of the system: smart contracts, relayer node, relayer dashboard, or documentation.
Suggests manual /compact at logical task boundaries in long Claude Code sessions and multi-phase tasks to avoid arbitrary auto-compaction losses.
Share bugs, ideas, or general feedback.
Complete domain knowledge for the auto-voting and relayer ecosystem. This skill provides context for working on any component of the system: smart contracts, relayer node, relayer dashboard, or documentation.
VeBetterDAO's auto-voting system lets users automate their weekly X Allocation voting. Users pick favorite apps once, toggle auto-voting on, and relayers (off-chain services) handle the rest: casting votes, claiming rewards, all gasless. Relayers earn a fee from the reward pool. Tokens never leave the user's wallet.
With the Navigators feature, relayers also serve navigator-delegated citizens — voting allocation rounds AND governance proposals on their behalf. Citizens are fully counted in expected actions alongside auto-voting users.
Users (toggle auto-voting, set preferences)
Citizens (delegate VOT3 to a navigator)
|
v
Smart Contracts (on-chain logic)
- XAllocationVoting (v9): auto-voting state, vote execution, castNavigatorVote
- B3TRGovernor (v10): governance castNavigatorVote, getActiveProposals
- VoterRewards (v7): reward claiming, fee deduction (relayer + navigator fees)
- RelayerRewardsPool (v3): relayer registration, reward distribution, per-user skip tracking
- NavigatorRegistry: delegation state, preferences, governance decisions
|
v
Relayer Nodes (off-chain execution)
- relayer-node/ (standalone CLI, no monorepo dependency)
- Monitor rounds, batch vote/claim, loop every 5 min
- Serve both auto-voting users and navigator citizens
|
v
Relayer Dashboard (monitoring/analytics)
- apps/relayer-dashboard/ (static Next.js, GitHub Pages)
- Round analytics, relayer stats, ROI tracking
Apps can register as relayers, ask users to set them as preference, and run a node. They earn relayer fees instead of paying veDelegate for votes. Important: apps should ADD themselves to preference lists, not replace other apps.
| Feature | veDelegate | VeBetterDAO Auto-Voting |
|---|---|---|
| X Allocation voting | Yes | Yes |
| Governance voting | Yes (always "abstain") | Yes (citizens: navigator's decision) |
| Compounding (B3TR->VOT3) | Auto | Manual |
| Token custody | Leaves wallet | Stays in wallet |
| Centralization | Single entity | Many relayers |
| Cost to apps | Apps pay veDelegate | Apps earn fees |
veDelegate: docs.vedelegate.vet / github.com/vechain-energy/vedelegate-for-dapps
Auto-voting added in v8, navigator voting added in v9. Now uses external library architecture.
Storage (in AutoVotingLogic / XAllocationVotingStorageTypes):
_autoVotingEnabled: Checkpointed per-user status (changes take effect next round)_userVotingPreferences: Array of app IDs per user (max 15, validated, no duplicates)_totalAutoVotingUsers: Checkpointed total countnavigatorRegistry: INavigatorRegistry reference (V9)Key functions:
toggleAutoVoting(address user) // Enable/disable
setUserVotingPreferences(bytes32[] memory appIds) // Set apps (1-15)
castVoteOnBehalfOf(address voter, uint256 roundId) // Relayer executes auto-vote
castNavigatorVote(address citizen, uint256 roundId) // Relayer executes citizen vote (vote-or-skip)
disableAutoVotingFor(address user) // Privileged, called by NavigatorRegistry on delegation
getUserVotingPreferences(address) // View preferences
isUserAutoVotingEnabled(address) // Current status
isUserAutoVotingEnabledForRound(address, uint256) // Status at round snapshot
getTotalAutoVotingUsersAtRoundStart() // Count at last emission
getTotalAutoVotingUsersAtTimepoint(uint48) // Historical count
Vote execution (castVoteOnBehalfOf — auto-voting users):
_countVote()Navigator vote execution (castNavigatorVote — citizens):
Skip-or-vote decision flow (merged into the function, no separate skip call):
NotDelegatedToNavigatorpool.reduceUserAllocationVote, emit NavigatorVoteSkippedRelayerAction.VOTENavigatorVoteSkippedSkipWindowNotReachedSkip window: XAllocationVoting.citizenSkipWindowBlocks() — storage-configurable (V9 migration), default ~720 blocks (~2 hours before round deadline). Read from chain via the getter; do not hardcode — governance can update it via setCitizenSkipWindowBlocks and the value can differ per network.
startNewRound() — expected actions setup:
Computes expected actions for the round:
allocationUsers = autoVotingUsers + totalDelegatedCitizensgovernanceUsers = totalDelegatedCitizens (citizens only — relayers don't cast governance for auto-users)governor.getActiveProposals()pool.setTotalActionsForRoundWithGovernance(roundId, allocationUsers, governanceUsers, activeProposals)V10 added navigator governance voting and relayer integration.
castNavigatorVote(proposalId, citizen) — citizens governance vote:
Same skip-or-vote pattern as XAllocationVoting:
NotDelegatedToNavigatorpool.reduceUserGovernanceVote, emit NavigatorGovernanceVoteSkippedRelayerAction.VOTE in RelayerRewardsPoolNavigatorGovernanceVoteSkippedGovernanceSkipWindowNotReachedSkip window: B3TRGovernor.governanceSkipWindowBlocks() — storage-configurable (V10 migration), default ~720 blocks (~2 hours before proposal deadline). Read from chain via the getter; do not hardcode — governance can update it via setGovernanceSkipWindowBlocks and the value can differ per network.
Maps navigator decision: 1=Against, 2=For, 3=Abstain → governor support 0, 1, 2. Applies intent multiplier for rewards.
getActiveProposals() — returns currently active proposal IDs (filtered from proposalsForRound mapping, populated at proposal creation time). Used by XAllocationVoting.startNewRound to compute governance expected actions.
V6 added relayer fee integration. V7 added navigator fee deduction and rewards multipliers.
V7 storage additions: navigatorRegistry
Fee flow in claimReward(uint256 cycle, address voter):
RelayerRewardsPool.deposit(fee, cycle)registerRelayerAction(msg.sender, voter, cycle, CLAIM) — credits caller with 1 weight pointFee formula:
Navigator fee = gross * navigatorFeePercentage / 10000 (citizens only, goes to NavigatorRegistry escrow)
Relayer fee = min((gross - navigatorFee) * relayerFeePercentage / 100, feeCap) (auto-voters AND citizens)
Important: msg.sender calling claimReward() IS the relayer credited for CLAIM action.
Early access: During window, reverts if caller is the voter or not a registered relayer. Applies to both auto-voters and citizens.
Manages registration, action tracking, reward distribution, per-user skip tracking.
Core storage:
totalRewards[roundId] // Pool amount (funded by fees)
relayerWeightedActions[roundId][relayer] // Per-relayer weighted work
totalWeightedActions[roundId] // Expected weighted total
completedWeightedActions[roundId] // Completed weighted total
registeredRelayers[address] // Registration mapping
relayerAddresses[] // All registered addresses
voteWeight = 3 // Points per vote action
claimWeight = 1 // Points per claim action
earlyAccessBlocks = 432,000 // ~5 days on VeChain
relayerFeePercentage = 10 // 10%
feeCap = 100 ether // 100 B3TR
V3 storage additions:
activeProposalsForRound[roundId] // Cached governance proposal IDs
userAllocationVoteReduced[roundId][user] // Per-user allocation skip flag
userGovernanceVoteReduced[roundId][user][proposalId] // Per-user/proposal governance skip flag
userClaimReduced[roundId][user] // Per-user claim auto-reduction flag
Reward formula:
relayerShare = (relayerWeightedActions / completedWeightedActions) * totalRewards
Claimability: isRewardClaimable(roundId) requires:
emissions.isCycleEnded(roundId))completedWeightedActions >= totalWeightedActions)Registration (open to anyone):
registerRelayer() — self-registrationunregisterRelayer(address) — callable by admin or the relayer itselfExpected actions setup:
setTotalActionsForRound(roundId, userCount) — legacy, delegates to setTotalActionsForRoundWithGovernance with governanceUsers=0setTotalActionsForRoundWithGovernance(roundId, allocationUsers, governanceUsers, activeProposalIds) — V3: sets expected actions. Total = allocationUsers * 2 (vote+claim) + governanceUsers * activeProposals (governance votes). Caches activeProposalIdsAction reduction:
reduceExpectedActionsForRound(roundId, userCount) — bulk reduction for ineligible auto-voting usersreduceUserAllocationVote(roundId, user) — per-user allocation skip. If all votes for user are skipped, auto-reduces claimreduceUserGovernanceVote(roundId, user, proposalId) — per-user/proposal governance skip. Same auto-reduce logicAction registration:
registerRelayerAction(relayer, voter, roundId, action) — records vote or claim workdeposit(amount, roundId) — funds pool from fee deductionsEarly access:
roundSnapshot + earlyAccessBlocksroundDeadline + earlyAccessBlocksRound N: Users enable auto-voting / citizens delegate to navigators (checkpointed)
Round N+1:
1. startNewRound() - snapshot locks status
- allocationUsers = autoVotingUsers + totalDelegatedCitizens
- governanceUsers = totalDelegatedCitizens
- fetches activeProposals from B3TRGovernor
- calls setTotalActionsForRoundWithGovernance(roundId, allocationUsers, governanceUsers, activeProposals)
2. ALLOCATION VOTING:
a. Relayers call castVoteOnBehalfOf(voter, roundId) for auto-voting users
- Ineligible users: reduceExpectedActionsForRound()
- Each successful vote: +3 weighted points
b. Relayers call castNavigatorVote(citizen, roundId) for citizens
- Navigator dead/no-prefs + skip window reached: skip (reduceUserAllocationVote)
- Navigator alive + prefs set: vote, +3 weighted points
3. GOVERNANCE VOTING (concurrent with allocation):
For each active proposal:
Relayers call B3TRGovernor.castNavigatorVote(proposalId, citizen) for each citizen
- Navigator dead/no-decision + skip window reached: skip (reduceUserGovernanceVote)
- Navigator alive + decision set: vote, +3 weighted points
4. Round ends (deadline block)
5. CLAIMS:
Relayers call VoterRewards.claimReward(cycle, user) for auto-voters and citizens
- Navigator fee deducted first (citizens only), then relayer fee
- Each successful claim: +1 weighted point
- If all votes for a citizen were skipped, claim is auto-reduced (no claim needed)
6. All expected actions completed (or reduced via skips) -> pool unlocks
7. Relayers call RelayerRewardsPool.claimRewards(roundId)
Relayers serve navigator-delegated citizens in addition to auto-voting users. This is ADDITIVE on top of existing auto-voting logic.
| Aspect | Auto-Voting Users | Navigator Citizens |
|---|---|---|
| Vote function (allocation) | castVoteOnBehalfOf(voter, roundId) | castNavigatorVote(citizen, roundId) |
| Vote function (governance) | N/A | B3TRGovernor.castNavigatorVote(proposalId, citizen) |
| Discovery | AutoVotingToggled events | DelegationCreated/Removed events |
| Preferences | User's own app list (equal split) | Navigator's AllocationPreferencesSet (custom %) |
| Governance | Not applicable | Navigator's GovernanceDecisionSet |
| Voting power | Full VOT3 balance at snapshot | Delegated amount at snapshot (checkpointed) |
| Personhood check | Yes | No |
| In expected actions? | Yes (allocation only) | Yes (allocation + governance) |
| Skip mechanism | reduceExpectedActionsForRound (bulk) | Per-user skip with skip window |
| Claim function | claimReward(cycle, voter) | Same — claimReward(cycle, citizen) |
| Fees at claim | Relayer fee only | Navigator fee first, then relayer fee |
Citizens ARE counted in expected actions. At round start:
allocationUsers = autoVotingUsers + totalDelegatedCitizens (for allocation vote + claim)governanceUsers = totalDelegatedCitizens (for governance votes — citizens only, NOT auto-voters)governanceUsers is separate because relayers don't cast governance votes for auto-voting usersThe skip window (storage-configurable per network, default ~720 blocks / ~2 hours before deadline — read from XAllocationVoting.citizenSkipWindowBlocks() / B3TRGovernor.governanceSkipWindowBlocks()) + per-user skip tracking prevent deadlock: if a navigator fails to set preferences, relayers can skip that citizen's votes once the skip window opens, reducing expected actions proportionally.
castNavigatorVote handles vote and skip in a single function:
SkipWindowNotReached / GovernanceSkipWindowNotReached (relayer retries later)When a citizen's vote is skipped:
reduceUserAllocationVote(roundId, citizen) — decrements expected actionsreduceUserGovernanceVote(roundId, citizen, proposalId) — per-proposal decrementB3TRGovernor.castNavigatorVote registers RelayerAction.VOTE in RelayerRewardsPool for each governance vote. Relayers earn the same 3 weight points per governance vote as per allocation vote.
When a user delegates to a navigator, their auto-voting is automatically disabled via XAllocationVoting.disableAutoVotingFor(citizen). Prevents double-counting and conflicting vote paths. On undelegate, user must re-enable auto-voting manually.
preferredRelayer manually via relayerRewardsPool.setPreferredRelayer(relayer)Standalone CLI tool. No monorepo dependency.
Deps: @vechain/sdk-core, @vechain/sdk-network, @vechain/vebetterdao-contracts
relayer-node/src/
index.ts # Entry, env parsing, main loop, SIGINT
config.ts # Mainnet + testnet-staging addresses
contracts.ts # 26 view functions + event pagination
relayer.ts # Batch vote/claim with isolation/retry
display.ts # Terminal UI (box drawing + chalk)
types.ts # Shared interfaces
Env vars: MNEMONIC / RELAYER_PRIVATE_KEY, RELAYER_NETWORK, RUN_ONCE, DRY_RUN
Cycle: Discover users from events -> filter voted -> batch castVoteOnBehalfOf + castNavigatorVote -> batch governance castNavigatorVote -> batch claimReward -> loop 5min
Static Next.js 14 (output: "export"), Chakra UI v3, VeChain Kit, Recharts. GitHub Pages under /b3tr.
Data: Static report.json (hourly GH Action, temporary) + on-chain reads via useCallClause
Pages (state-based nav, not file routing):
/round?roundId=X - 2-col layout, summary/actions/financialsHooks:
contracts.ts - ABIs + addresses from @repo/configuseCurrentRoundId - XAllocationVoting.currentRoundId()useTotalAutoVotingUsers - getTotalAutoVotingUsersAtTimepoint()useRegisteredRelayers - getRegisteredRelayers()useRoundRewardStatus - isRewardClaimable() + getTotalRewards()useReportData - fetches /data/report.jsonuseB3trToVthoRate - oracle exchange rateCommands: yarn relayer:dev:staging, yarn relayer:dev:mainnet, yarn relayer:build:staging, yarn relayer:build:mainnet
| Action | Gas | VTHO | B3TR equiv |
|---|---|---|---|
| Vote (5-8 apps) | ~441K | ~4.41 | ~0.075 |
| Claim | ~208K | ~2.08 | ~0.035 |
| Total/user/round | ~6.49 | ~0.11 |
Average user: ~10.8k-22.6k VOT3, earns ~90-190 B3TR/round. At 10% fee: ~9-19 B3TR per user into pool. Relayer cost: ~0.11 B3TR. Margin: ~8.9-18.9 B3TR/user.
@vechain/vebetterdao-contracts