Summary
On Acala mainnet, module-incentives is no longer paying staking/LP rewards, even though the dApp still advertises a non-zero APY (~38% for ACA staking). The pool reward counters are frozen.
Root cause is not a logic bug in the pallet but an operational/funding gap: module_incentives::Config::RewardsSource resolves to the "unreleased native vault" account aca/urls, and that account is empty (~1 ACA). Every accumulate_incentives cycle therefore fails its internal transfer and rolls back silently, so no reward is ever accumulated. Meanwhile the recurring native emission (currencies.updateBalance, ~8.33M ACA / 30 days) is being minted into the Treasury (aca/trsy), not into the rewards vault.
Result: stakers/LPs accrue 0 rewards while the configured IncentiveRewardAmounts (and the UI APY derived from it) remain non-zero — a misleading "phantom APY".
Environment
- Network: Acala mainnet
- Observed around block:
#11,439,600 (2026-06-28)
- RPC used:
wss://acala-rpc.aca-api.network/ws
- Tooling:
@polkadot/api / @polkadot/api-cli
Observed behavior
rewards.poolInfos({ Earning: { Token: "ACA" } }).rewards.ACA.0 (total accumulated reward) does not change across many AccumulatePeriods (verified unchanged over ~15 min / ~75 blocks while the chain produced blocks normally at ~12s).
incentives.incentiveRewardAmounts({ Earning: { Token: "ACA" } }, { Token: "ACA" }) = 96.45 ACA per 5 blocks — i.e. incentives are still configured as active.
system.account(aca/urls) free balance ≈ 1.0456 ACA, reserved = 0, locks = [], vesting.vestingSchedules(aca/urls) = [].
Expected behavior
With a non-zero IncentiveRewardAmounts, each AccumulatePeriod should transfer the configured amount from RewardsSource to the incentives account and increase poolInfos.totalReward, so stakers' pending rewards grow.
Root cause analysis
RewardsSource is the unreleased native vault:
runtime/acala/src/lib.rs: UnreleasedNativeVaultAccountId = PalletId(*b"aca/urls").into_account_truncating();
runtime/acala/src/lib.rs: impl module_incentives::Config for Runtime { type RewardsSource = UnreleasedNativeVaultAccountId; ... type UpdateOrigin = EnsureRootOrThreeFourthsGeneralCouncil; ... }
In modules/incentives/src/lib.rs, accumulate_incentives calls transfer_rewards_and_update_records, which is #[transactional]:
T::Currency::transfer(
reward_currency_id,
&T::RewardsSource::get(), // aca/urls
&Self::account_id(),
reward_amount,
ExistenceRequirement::AllowDeath,
)?;
<orml_rewards::Pallet<T>>::accumulate_reward(&pool_id, reward_currency_id, reward_amount)?;
When aca/urls lacks balance, the transfer(...)? fails, the #[transactional] body rolls back, accumulate_reward is never reached, and the caller only logs a log::warn! ("…failed to accumulate…"). The module doc string even states the operational requirement explicitly:
"Rewards come from RewardsSource, please transfer enough tokens to RewardsSource before start incentive plan."
So the pallet behaves as written — the vault simply isn't funded.
Why the funds exist but don't reach the vault
The only forward-scheduled task on chain mints the native emission to the Treasury, not the rewards vault:
scheduler.agenda(#11,582,700) decodes (against runtime metadata) to:
currencies.updateBalance(
who: aca/trsy (Treasury, 23M5ttkmR6KcoTAAE6gcmibnKFtVaTP5yxnY8HF1BmrJ2A1i),
currency: ACA,
amount: +8,333,333.333333333333 ACA
)
origin: Root
periodic: every 216,000 blocks (~30 days), 35 repetitions remaining
8,333,333.33 × 12 = 100,000,000 ACA/yr (the Exodus emission). Balances on chain:
- Treasury
aca/trsy: ~75.2M ACA (and growing monthly)
- Incentives
aca/inct: ~51.07M ACA (already-accrued/claimable)
- Rewards vault
aca/urls: ~1.05 ACA
So the budget exists; the link from Treasury → aca/urls is simply not being maintained.
Impact
- Stakers/LPs earn nothing while the UI shows a positive APY (poor UX / misleading signal).
IncentiveRewardAmounts remains non-zero, so off-chain APY calculators report a yield that is not paid.
Suggested resolution (no runtime upgrade required)
Either of the following, via UpdateOrigin (Root or 3/4 General Council):
- Fund the vault: transfer/
updateBalance ACA into aca/urls (one-off or a recurring schedule mirroring the Treasury one) so accrual resumes.
- Or wind down explicitly: set
IncentiveRewardAmounts to 0 for the affected pools so the dApp stops displaying an unpaid APY.
A short note on whether the Treasury → aca/urls disconnect is intentional would also help clarify the incentive program's status for the community.
Reproduction (Polkadot.js Apps → Developer → Chain state, RPC wss://acala-rpc.aca-api.network/ws)
incentives.rewardsSource() → resolves to aca/urls
system.account(<aca/urls>) → free ≈ 1 ACA
incentives.incentiveRewardAmounts({Earning:{Token:ACA}}, {Token:ACA}) → 96.45 ACA / 5 blocks
rewards.poolInfos({Earning:{Token:ACA}}) → read totalReward, re-read a few minutes later → unchanged
scheduler.agenda(11582700) → decode inline call → currencies.updateBalance(aca/trsy, ACA, +8.33M), periodic, 35 left
system.account(<aca/trsy>) → ≈ 75.2M ACA
UPDATE (2026-07-03)
- The Council proposal referenced above (
0xdc3d2be9..., funding aca/urls with 4,244,541 ACA) expired without a 3rd vote (stuck at 2/3, threshold 3). It is no longer in generalCouncil.proposals.
- @xlc mentioned a general referendum fallback if the council failed to vote in time. As of now, no such referendum or public proposal exists (
democracy.publicProps = 0, active referenda = 0).
- Re-measured the actual accrual rate over a clean 10-minute window: pending rewards grew by 0.0000 ACA (vs. an expected ~8,571 ACA/day pro-rata to my stake). So the situation is worse than initially described — rewards are not just throttled, they are fully frozen right now.
aca/urls remains at ~1.05 ACA.
@xlc could you open the referendum, or is there another path forward?
Summary
On Acala mainnet,
module-incentivesis no longer paying staking/LP rewards, even though the dApp still advertises a non-zero APY (~38% for ACA staking). The pool reward counters are frozen.Root cause is not a logic bug in the pallet but an operational/funding gap:
module_incentives::Config::RewardsSourceresolves to the "unreleased native vault" accountaca/urls, and that account is empty (~1 ACA). Everyaccumulate_incentivescycle therefore fails its internal transfer and rolls back silently, so no reward is ever accumulated. Meanwhile the recurring native emission (currencies.updateBalance, ~8.33M ACA / 30 days) is being minted into the Treasury (aca/trsy), not into the rewards vault.Result: stakers/LPs accrue 0 rewards while the configured
IncentiveRewardAmounts(and the UI APY derived from it) remain non-zero — a misleading "phantom APY".Environment
#11,439,600(2026-06-28)wss://acala-rpc.aca-api.network/ws@polkadot/api/@polkadot/api-cliObserved behavior
rewards.poolInfos({ Earning: { Token: "ACA" } }).rewards.ACA.0(total accumulated reward) does not change across manyAccumulatePeriods (verified unchanged over ~15 min / ~75 blocks while the chain produced blocks normally at ~12s).incentives.incentiveRewardAmounts({ Earning: { Token: "ACA" } }, { Token: "ACA" })=96.45 ACAper 5 blocks — i.e. incentives are still configured as active.system.account(aca/urls)free balance ≈ 1.0456 ACA,reserved = 0,locks = [],vesting.vestingSchedules(aca/urls) = [].Expected behavior
With a non-zero
IncentiveRewardAmounts, eachAccumulatePeriodshould transfer the configured amount fromRewardsSourceto the incentives account and increasepoolInfos.totalReward, so stakers' pending rewards grow.Root cause analysis
RewardsSourceis the unreleased native vault:runtime/acala/src/lib.rs:UnreleasedNativeVaultAccountId = PalletId(*b"aca/urls").into_account_truncating();runtime/acala/src/lib.rs:impl module_incentives::Config for Runtime { type RewardsSource = UnreleasedNativeVaultAccountId; ... type UpdateOrigin = EnsureRootOrThreeFourthsGeneralCouncil; ... }In
modules/incentives/src/lib.rs,accumulate_incentivescallstransfer_rewards_and_update_records, which is#[transactional]:When
aca/urlslacks balance, thetransfer(...)?fails, the#[transactional]body rolls back,accumulate_rewardis never reached, and the caller only logs alog::warn!("…failed to accumulate…"). The module doc string even states the operational requirement explicitly:So the pallet behaves as written — the vault simply isn't funded.
Why the funds exist but don't reach the vault
The only forward-scheduled task on chain mints the native emission to the Treasury, not the rewards vault:
scheduler.agenda(#11,582,700)decodes (against runtime metadata) to:8,333,333.33 × 12 = 100,000,000ACA/yr (the Exodus emission). Balances on chain:aca/trsy: ~75.2M ACA (and growing monthly)aca/inct: ~51.07M ACA (already-accrued/claimable)aca/urls: ~1.05 ACASo the budget exists; the link from Treasury →
aca/urlsis simply not being maintained.Impact
IncentiveRewardAmountsremains non-zero, so off-chain APY calculators report a yield that is not paid.Suggested resolution (no runtime upgrade required)
Either of the following, via
UpdateOrigin(Root or 3/4 General Council):updateBalanceACA intoaca/urls(one-off or a recurring schedule mirroring the Treasury one) so accrual resumes.IncentiveRewardAmountsto 0 for the affected pools so the dApp stops displaying an unpaid APY.A short note on whether the Treasury →
aca/urlsdisconnect is intentional would also help clarify the incentive program's status for the community.Reproduction (Polkadot.js Apps → Developer → Chain state, RPC
wss://acala-rpc.aca-api.network/ws)incentives.rewardsSource()→ resolves toaca/urlssystem.account(<aca/urls>)→ free ≈ 1 ACAincentives.incentiveRewardAmounts({Earning:{Token:ACA}}, {Token:ACA})→ 96.45 ACA / 5 blocksrewards.poolInfos({Earning:{Token:ACA}})→ readtotalReward, re-read a few minutes later → unchangedscheduler.agenda(11582700)→ decode inline call →currencies.updateBalance(aca/trsy, ACA, +8.33M), periodic, 35 leftsystem.account(<aca/trsy>)→ ≈ 75.2M ACAUPDATE (2026-07-03)
0xdc3d2be9..., fundingaca/urlswith 4,244,541 ACA) expired without a 3rd vote (stuck at 2/3, threshold 3). It is no longer ingeneralCouncil.proposals.democracy.publicProps= 0, active referenda = 0).aca/urlsremains at ~1.05 ACA.@xlc could you open the referendum, or is there another path forward?