Staking NEW $THRYX for WETH Yield: The Economic Loop
9 min read
Staking on most DeFi protocols means depositing a token and earning more of the same token, printed from thin air via emissions schedules. On THRYX, staking NEW earns WETH. The WETH comes from actual trading fees, not from minting. When trading volume is zero, yield is zero. When trading volume is high, yield scales linearly. There is no emission schedule to dilute, no unlocking cliff, and no governance vote about inflation rates. The yield is a direct pass-through of protocol revenue.
How WETH reaches the staking pool
The path from user trade to staker yield has four steps:
- User trades a token. SwapFacet charges a 1% fee. 30% of that fee is the protocol cut (0.3% of trade volume). This lands as WETH on the Diamond.
- Hook fees accumulate on anti-sniper contracts. The hook-fee-collector keeper sweeps them to the Diamond every 30 minutes.
- LP fees accumulate on Diamond-owned Uniswap positions. The pool-mm-keeper collects them every 7.5 minutes.
- The fee-router-keeper wraps surplus Diamond ETH to WETH and calls FeeRouterFacet.distributePending(). The 40% staker share is forwarded to StakingFacet via an intra-Diamond call to notifyRewardWei(amount).
The result: stakers earn from swap fees, hook fees, and LP fees. All three fee sources converge through the same FeeRouter pipeline. No fee source bypasses the staking pool.
The accumulator: accRewardPerShareWei
StakingFacet uses the MasterChef accumulator pattern. Instead of iterating over every staker when new rewards arrive (which would cost O(n) gas and be impossible for even 100 stakers), it maintains a single global variable: accRewardPerShareWei. When notifyRewardWei(amount) is called, the accumulator increases by (amount * 1e18) / totalStakedNew. Each staker's pending reward is computed as:
pending = (stakedNewOf[user] * accRewardPerShareWei / 1e18) - rewardDebt[user]
rewardDebt[user] is snapshotted whenever a user stakes, unstakes, or claims. It captures the accumulator value at the time of the last interaction, so the user only earns rewards from fees generated AFTER their stake, not from historical fees. The 1e18 scaling factor gives 18 digits of precision, which is sufficient for any practical combination of total staked NEW (up to 100 billion tokens with 18 decimals) and reward amounts (down to 1 wei of WETH).
Stake, unstake, and claim flow
Three user-facing functions, each following the same pattern: update the user's pending rewards, then mutate balances.
// stake(amount): pull NEW from user, increase their stake
function stake(uint256 amount) external {
_updateUser(s, msg.sender); // credit pending WETH
IERC20(NEW_THRYX).safeTransferFrom(msg.sender, address(this), amount);
s.stakedNewOf[msg.sender] += amount;
s.totalStakedNew += amount;
s.rewardDebt[msg.sender] = newBal * s.accRewardPerShareWei / 1e18;
}
// unstake(amount): return NEW to user, decrease their stake
// claim(): send accrued WETH to user, reset debt
The _updateUser function computes pending rewards at the current accumulator value and adds them to accruedWeth[user]. This happens BEFORE the balance change, so the reward calculation uses the old balance. The rewardDebt is then reset to reflect the new balance and current accumulator. This is the canonical MasterChef ordering that prevents double-counting.
The staker-debt problem (and how it was solved)
The accumulator creates an implicit debt: the Diamond owes every staker their pending WETH. The total debt is totalStakedNew * accRewardPerShareWei / 1e18. If the Diamond's WETH balance drops below this debt (because other operations spent WETH), claim() reverts with an insufficient-balance error for ALL stakers, not just the one trying to claim.
This exact scenario happened on 2026-05-24. The fee-router-keeper was distributing WETH through the 5-bucket split. The treasury share (25%) left the Diamond as a WETH transfer. The staker share (40%) increased the accumulator, growing the debt. Each distribution cycle reduced Diamond WETH by the treasury share while growing staker debt by the staker share. After several hours, debt exceeded balance, and staker claims started reverting.
The fix had two parts: (1) A StakerSettlementFacet that let the admin write down accRewardPerShareWei from 405,784,960 to 1,250,710, immediately reducing staker debt from 0.183 WETH to 0.000566 WETH. (2) A post-distribute projection guard in the fee-router-keeper that simulates the post-distribution surplus before executing the distribution. If the projected Diamond WETH after treasury exit would be less than the projected staker debt after accumulator increase, the distribution is skipped entirely.
Current staking numbers
| Metric | Value | Source |
|---|---|---|
| Staking token | NEW $THRYX (0x49e4...89DD) | Hardcoded in StakingFacet.sol |
| Reward token | WETH (0x4200...0006) | Hardcoded in StakingFacet.sol |
| Accumulator precision | 1e18 | ACC_PRECISION constant |
| Staker share of fees | 40% (4000 bps) | FeeRouterFacet DEFAULT_STAKERS_BPS |
| Unstake cooldown | None (immediate) | v1 design decision |
| Minimum stake | Any non-zero amount | Only check: amount != 0 |
| Claim frequency | Any time | No lockup, no minimum |
Real yield vs emissions
THRYX staking yield is entirely real yield. No NEW tokens are minted as staking rewards. No emissions schedule dilutes existing holders. The WETH that stakers earn came from actual swap fees paid by actual traders. When no one trades, no yield is generated. This makes the APR fully organic but also variable: a high-volume day generates more yield than a quiet day.
The trade-off vs emission-based staking is honesty over attraction. Emission staking offers a predictable 500% APR that attracts capital but dilutes the token. THRYX staking offers a variable yield that might be 2% APR in a quiet week and 50% APR during a trading frenzy. No yield number is published because it would be misleading to annualize a volatile daily rate.
Frequently asked questions
Frequently asked
- What token do I stake, and what do I earn?
- You stake NEW $THRYX (0x49e4cf7097C497008800eDC80Dc76906eDD189DD) and earn WETH (0x4200000000000000000000000000000000000006). The WETH comes from a 40% cut of all protocol trading fees, routed through FeeRouterFacet.
- Is there a lock-up period?
- No. You can unstake at any time, and your accrued WETH rewards are claimed in the same transaction. There is no cooldown, no penalty, and no unbonding period.
- What happens to my rewards if I do not claim for a month?
- They accumulate in the accruedWeth[user] mapping. Nothing is lost or expired. When you eventually call claim() or unstake(), all accumulated WETH is sent to your wallet in one transaction.
- Can the staking yield go to zero?
- Yes. If no one trades on the platform, no fees are generated, no WETH flows to the FeeRouter, and staker yield is zero. This is by design: the yield is a direct function of platform usage, not an artificial emission rate.
- How do I verify my pending rewards on-chain?
- Call getStakerInfo(address) on the Diamond (0x2F77b40c124645d25782CfBdfB1f54C1d76f2cCe). It returns your staked amount, accrued WETH, and reward debt. The pending amount is computed from the current accRewardPerShareWei, so it updates in real-time as new fees are distributed.
- Is the staker-debt incident a recurring risk?
- The root cause (missing post-distribute projection) is permanently fixed in the fee-router-keeper. The staker-debt guard runs in three independent keepers (pool-mm, fee-router, paymaster-auto-rescue) and the solvency-monitor checks the invariant every 60 seconds with email alerts. The write-down mechanism (StakerSettlementFacet) exists as a last resort if a future bug bypasses all guards.