Staking: asset vs LP
Staking happens through the stPopulx contract (ST-PPLX tracks positions). The stake(uint256 amount, bool asset, bool locked) function distinguishes two deposit types.
Asset staking (PPLX)
- If
asset == true, the contract pulls PPLX from your wallet (transferFrom) into the configured vault. - Internal balance
amountAssetis credited; staking-tracked supply (_totalSupplyon the contract) increases by the same amount. locked: when true,assetLockedis set; withdrawal is blocked untiltimestamp + timeLock(currently 365 days) elapses.- Unstake asset: staked amount is reduced, then the main token's
swapChildPopulxToPopulxpath runs with configurabletaxUnstake(currently 5%). The post-tax balance is sent back from the vault — or minted on the fly if the vault is empty (see Token: fallback mint).
LP staking
- If
asset == false, you deposit the Uniswap V2 pair tokens (IUniswapV2Pair): LPs move from your wallet into the staking contract. amountLPis credited;lockedmay setlpLockedwith the sametimeLockrules for unstaking.- Unstake LP: LP tokens return to your wallet, optionally after an LP fee (
taxUnstakeLP) to the oracle wallet when that fee is strictly positive; otherwise the full LP amount is sent.
Zap and LP
The Zap contract credits LP staking through zapSTPPLX (Zap-only caller), not the regular asset stake path.
Daily reward flow (oracle → cron → user)
Rewards are not minted by the contract on each block. They are distributed once per day by an off-chain cron, then made claimable on-chain. Three actors are involved:
- Cron (off-chain). Every 24 h, a backend job:
- reads two daily reward pools from the backend (one for asset stakers, one for LP stakers, both expressed in PPLX/day);
- fetches every active position via
stPopulx.getStakersDetails(); - applies a 1.5× weighting to each position whose
assetLockedorlpLockedflag is true (the +50% locked bonus); - splits each pool prorata against the weighted staked supply and writes the per-wallet shares to the backend;
- records the run so a re-run for the same UTC day is a no-op (idempotence).
- Oracle (on-chain bridge). The same backend then calls
stPopulx.vestingReward(staker, amount)for each staker and amount it computed. This is the only reward-creation path on-chain and it is gated byonlyOracle: nobody else can mint reward credits. - You (on-chain claim). From the dapp you call
claimReward(uint256 key)orcompoundReward(uint256 key)directly onstPopulx. These functions are notonlyOracle— the oracle credits the vesting entry, you redeem it yourself when you want to. Each entry vests linearly overvestingTime(default 7 days); claiming early applies a per-day decaying penalty defined on the staking contract.
The PPLX paid out by claimReward / compoundReward is sourced from VaultPopulx. If the vault is dry, the main token contract mints the missing amount on the fly via swapChildPopulxToPopulx — see the Token page for the dilution mechanics and how the burn-tax era is meant to absorb it.
APY matrix
Four APY tiers are exposed at POST /getAPY and surfaced on the staking dashboard. They are display-only figures: the cron always distributes the configured daily pools in full, no matter how many wallets are staking.
| Tier | Field | Formula (display) | For |
|---|---|---|---|
| Asset, non-locked | apy_asset | (asset pool / 90 M) × 365 × 100 | Citizens with default stPPLX |
| Asset, locked | apy_asset_locked | apy_asset × 1.5 | Citizens with opt-in 12-month lock |
| LP, non-locked | apy_lp | (LP pool / 90 M) × 365 × 100 | LP stakers without lock |
| LP, locked | apy_lp_locked | apy_lp × 1.5 | Founders, Zap users, opt-in LP lockers |
Why 90 000 000?
The denominator is a fixed display baseline of 90 000 000 PPLX. It mirrors the 90% of total supply minted into VaultPopulx at deploy — i.e. the maximum capital that can ever be staked in PPLX before the contract starts minting from swapChildPopulxToPopulx. We use it as a single, stable baseline so the headline APY does not yo-yo every time someone stakes or unstakes.
It is purely a display normalization knob. The cron does not use it: it always distributes the full daily pool prorata against the actual on-chain weighted stake. As a consequence:
- When the actual staked supply is below 90 M (which is essentially always at launch), the realised APY for individual stakers is higher than the headline number. Early stakers therefore earn more than the displayed figure suggests — a built-in incentive to stake early.
- As more wallets stake, the realised APY converges toward the headline figure. If the weighted staked supply ever exceeds 90 M, the realised APY goes below the headline (it is then too optimistic) — at which point the team would revise the baseline upward to match.
Locked bonus (+50%)
The 1.5× multiplier is enforced exclusively by the off-chain cron, not by the contract: a locked position receives 50% more weight when the daily pool is divided among stakers. The contract itself only enforces the 12-month withdrawal lock; it never moves any tokens because of the locked flag. This means:
- Tweaking the bonus (or removing it) is a backend-only change — no re-deploy.
- The bonus applies uniformly to asset-locked Citizens and LP-locked Founders. Founders also keep their share of Uniswap V2 fees on top, which the cron does not touch.
Reward vesting (in-contract)
Each call to vestingReward appends a per-user entry indexed by key. Reference duration vestingTime defaults to 7 days. Calling claimReward(key) before full vesting applies a per-day decaying penalty; compoundReward(key) rolls the entry back into your stake without penalty. Exact formulas are visible in the verified staking contract on Etherscan.
What can change without a re-deploy
- Daily reward pools (asset and LP): backend configuration. Effective on the next cron run.
- Headline baseline (the 90 M denominator): backend constant. Display-only.
- Locked bonus (
1.5×): backend constant.
Anything that affects past distributions (already credited on-chain via vestingReward) is immutable.