StakingBoost Technical Specification
Complete technical specification for the “Stake SHIELD → Boost shXRP Yield” feature.
See also: Whitepaper (PDF) — Section 3 contains the full mathematical framework with formal notation.
Overview
Section titled “Overview”StakingBoost implements a Synthetix-style reward accumulator to distribute FXRP rewards pro-rata to SHIELD stakers. When stakers claim their rewards, shXRP shares are minted directly to their wallet via vault.donateOnBehalf(), creating differentiated yield for stakers vs non-stakers.
Formula (from Whitepaper)
Section titled “Formula (from Whitepaper)”Boost APY = Base APY + (Annual Protocol Revenue → FXRP) × (Your Locked SHIELD ÷ Total Locked SHIELD)Key Property: 100% of the boost allocation is distributed pro-rata to SHIELD lockers. No minting. No inflation. Pure revenue-share.
Architecture
Section titled “Architecture”Contract Interactions
Section titled “Contract Interactions”┌─────────────────────────────────────────────────────────────────────────────┐│ Revenue Distribution Flow ││ ││ ┌──────────────┐ deposit/withdraw fees ┌───────────────────┐ ││ │ ShXRPVault │ ───────────────────────────► │ RevenueRouter │ ││ │ (ERC-4626) │ │ (Fee Splitter) │ ││ └──────┬───────┘ └─────────┬─────────┘ ││ │ │ ││ │ donateOnBehalf() │ distribute() ││ │ (mints shXRP) │ ││ │ ▼ ││ │ ┌─────────────────────┐ ││ │ │ Revenue Split │ ││ │ │ ────────────────── │ ││ │ │ 50% → Burn SHIELD │ ││ │ │ 40% → FXRP → Boost │ ││ │ │ 10% → Reserves │ ││ │ └─────────┬─────────┘ ││ │ │ ││ │ │ distributeBoost() ││ │ ▼ ││ │ ┌─────────────────────┐ ││ │ │ StakingBoost │ ││ │ │ ─────────────────── │ ││ │ │ rewardPerToken += │ ││ │ │ fxrp/totalStaked │ ││ └─────────────────────────────────────│ │ ││ claim() │ SHIELD stakers │ ││ └─────────────────────┘ │└─────────────────────────────────────────────────────────────────────────────┘Circular Dependency Solution
Section titled “Circular Dependency Solution”StakingBoost and ShXRPVault have a mutual dependency:
- StakingBoost needs vault address to call
donateOnBehalf() - Vault needs StakingBoost address for access control on
donateOnBehalf()
Solution: Three-Step Deployment
// Step 1: Deploy vault with placeholderShXRPVault vault = new ShXRPVault(fxrp, "shXRP", "shXRP", router, address(0));
// Step 2: Deploy StakingBoost with real vaultStakingBoost boost = new StakingBoost(shield, fxrp, address(vault), router);
// Step 3: Wire vault to StakingBoost (one-time setter)vault.setStakingBoost(address(boost));Synthetix Reward Accumulator
Section titled “Synthetix Reward Accumulator”The reward accumulator pattern enables O(1) gas complexity for reward distribution, regardless of the number of stakers.
Core State Variables
Section titled “Core State Variables”uint256 public rewardPerTokenStored; // Global accumulatormapping(address => uint256) public userRewardPerTokenPaid; // Per-user checkpointmapping(address => uint256) public rewards; // Pending rewards per userMathematics
Section titled “Mathematics”On Distribution (distributeBoost):
rewardPerTokenStored += (fxrpAmount * 1e18) / totalStakedComputing Earned Rewards:
earned = (stake.amount * (rewardPerTokenStored - userRewardPerTokenPaid)) / 1e18 + rewardsOn Stake/Withdraw/Claim:
// Update user's pending rewardsrewards[user] = earned(user)// Checkpoint the global accumulatoruserRewardPerTokenPaid[user] = rewardPerTokenStoredGas Complexity
Section titled “Gas Complexity”| Operation | Gas Complexity | Notes |
|---|---|---|
stake() | O(1) | Single storage update |
withdraw() | O(1) | Single storage update |
distributeBoost() | O(1) | Single division, no loops |
claim() | O(1) | Fixed number of storage operations |
earned() | O(1) | View function, no loops |
Smart Contract Interface
Section titled “Smart Contract Interface”StakingBoost.sol
Section titled “StakingBoost.sol”// State variablesIERC20 public immutable shieldToken;IERC20 public immutable fxrpToken;IShXRPVault public immutable vault;address public revenueRouter;uint256 public constant LOCK_PERIOD = 30 days;uint256 public globalBoostCapBps = 2500; // 25% max boostuint256 public totalStaked;uint256 public rewardPerTokenStored;
struct Stake { uint256 amount; uint256 stakedAt;}mapping(address => Stake) public stakes;mapping(address => uint256) public userRewardPerTokenPaid;mapping(address => uint256) public rewards;
// Core functionsfunction stake(uint256 amount) external;function withdraw(uint256 amount) external;function claim() external returns (uint256);function claimAndWithdraw(uint256 amount) external;
// View functionsfunction earned(address account) external view returns (uint256);function getStakeInfo(address account) external view returns ( uint256 amount, uint256 stakedAt, uint256 unlockTime, uint256 pendingRewards);
// Distribution (called by RevenueRouter)function distributeBoost(uint256 fxrpAmount) external;
// Admin functionsfunction setGlobalBoostCap(uint256 newCapBps) external onlyOwner;function setRevenueRouter(address newRouter) external onlyOwner;function recoverTokens(address token, address to, uint256 amount) external onlyOwner;ShXRPVault.sol Additions
Section titled “ShXRPVault.sol Additions”// State (not immutable - set via setter)IStakingBoost public stakingBoost;
// One-time setter (solves circular dependency)function setStakingBoost(address _stakingBoost) external onlyOwner;
// Donation function (StakingBoost only)function donateOnBehalf(address user, uint256 fxrpAmount) external returns (uint256 shares);RevenueRouter.sol (FXRP-Based)
Section titled “RevenueRouter.sol (FXRP-Based)”// Input token: FXRP (from vault fees)IERC20 public immutable fxrpToken;
// Allocationsuint256 public burnAllocationBps = 5000; // 50% → SHIELD buyback & burnuint256 public boostAllocationBps = 4000; // 40% → Direct FXRP to StakingBoost
// Security featuresuint256 public maxSlippageBps = 500; // 5% max slippageuint256 public lastKnownPrice; // FXRP/SHIELD price tracking
// Core distributionfunction distribute() external; // Distributes FXRP balancefunction setBoostAllocation(uint256 bps) external onlyOwner;function setBurnAllocation(uint256 bps) external onlyOwner;
// Internal flow:// 1. burnAmount → _swapAndBurnShield() → FXRP→SHIELD swap → burn// 2. boostAmount → _distributeBoost() → Direct FXRP to StakingBoost// 3. remainder → Protocol reservesKey Change (Dec 2025): RevenueRouter now accepts FXRP directly from vault fees (not wFLR). StakingBoost receives FXRP directly for reward distribution, eliminating an extra swap step.
Events
Section titled “Events”StakingBoost Events
Section titled “StakingBoost Events”event Staked(address indexed user, uint256 amount, uint256 unlockTime);event Withdrawn(address indexed user, uint256 amount);event RewardDistributed(uint256 fxrpAmount, uint256 newRewardPerToken);event RewardClaimed(address indexed user, uint256 fxrpAmount, uint256 shXRPShares);event GlobalBoostCapUpdated(uint256 oldCap, uint256 newCap);event RevenueRouterUpdated(address indexed oldRouter, address indexed newRouter);ShXRPVault Events
Section titled “ShXRPVault Events”event StakingBoostUpdated(address indexed oldBoost, address indexed newBoost);event DonatedOnBehalf(address indexed user, uint256 fxrpAmount, uint256 sharesMinted);Security Considerations
Section titled “Security Considerations”Access Control
Section titled “Access Control”| Function | Access | Reason |
|---|---|---|
distributeBoost() | onlyRevenueRouter | Prevents arbitrary reward injection |
donateOnBehalf() | onlyStakingBoost | Prevents unauthorized share minting |
setStakingBoost() | onlyOwner + one-time | Immutable after initial setup |
setGlobalBoostCap() | onlyOwner | Governance control |
setRevenueRouter() | onlyOwner | Governance control |
recoverTokens() | onlyOwner | Emergency recovery (not staked tokens) |
Reentrancy Protection
Section titled “Reentrancy Protection”All state-changing functions use:
ReentrancyGuardfrom OpenZeppelin- CEI (Checks-Effects-Interactions) pattern
function claim() external nonReentrant { // Check uint256 reward = earned(msg.sender); require(reward > 0, "No rewards to claim");
// Effect rewards[msg.sender] = 0; userRewardPerTokenPaid[msg.sender] = rewardPerTokenStored;
// Interaction fxrpToken.safeTransfer(address(vault), reward); vault.donateOnBehalf(msg.sender, reward);}Edge Cases
Section titled “Edge Cases”- Zero Total Staked:
distributeBoost()with no stakers leaves FXRP in contract (no division by zero) - Late Joiner: New stakers only earn from distributions after their stake
- Partial Withdraw: Updates checkpoint before releasing tokens
- Double Claim: Second claim returns 0 (rewards already claimed)
Test Coverage
Section titled “Test Coverage”test/boost-flow.ts (16 tests)
Section titled “test/boost-flow.ts (16 tests)”Tests 1-4: Reward Accumulator
distributeBoostupdatesrewardPerTokenStoredcorrectlyearned()returns correct proportional amounts- Multiple distributions accumulate correctly
- Late stakers only earn from post-stake distributions
Tests 5-8: Claim Integration
claim()mints shXRP to staker only- Non-stakers have zero earned, receive nothing
- Multiple stakers get proportional shares
- Only StakingBoost can call
donateOnBehalf()
Tests 9-12: End-to-End
- Complete flow: stake → distribute → claim → verify shXRP
getStakeInfo()returns correct pending rewardsclaimAndWithdraw()convenience function works- Admin can update global boost cap
Security Tests
- Zero totalStaked handled gracefully
- Only owner can set RevenueRouter
setStakingBoost()is one-time only- Reentrancy protection verified
- Correct events emitted
- Excess token recovery works
Configuration Parameters
Section titled “Configuration Parameters”| Parameter | Default | Range | Description |
|---|---|---|---|
LOCK_PERIOD | 30 days | Fixed | Minimum stake duration |
globalBoostCapBps | 2500 | 0-10000 | Max boost % (25% default) |
boostAllocationBps | 4000 | 0-5000 | % of revenue to boost (40%) |
Upgrade Considerations
Section titled “Upgrade Considerations”Current Design Limitations
Section titled “Current Design Limitations”- One-Time Setter:
vault.setStakingBoost()cannot be changed after initial setup - Immutable Vault: StakingBoost’s vault reference is immutable
Upgrade Path
Section titled “Upgrade Path”To upgrade StakingBoost:
- Deploy new StakingBoost contract
- Deploy new ShXRPVault contract
- Wire them using the three-step process
- Migrate user stakes via governance proposal
Future Improvements (Post-V1)
Section titled “Future Improvements (Post-V1)”- Governance-controlled StakingBoost setter on vault
- Proxy pattern for upgradeable StakingBoost
- Multi-asset boost support
Deployment Checklist
Section titled “Deployment Checklist”- Deploy ShXRPVault with
stakingBoost = address(0) - Deploy StakingBoost with real vault address
- Call
vault.setStakingBoost(stakingBoostAddress) - Verify bidirectional wiring
- Update RevenueRouter’s boost allocation if needed
- Run full test suite
- Verify on block explorer
Last Updated: December 6, 2025
