Indexer: live podium predictions from Postgres per category/epoch (GET /v1/arena/podiums) #273
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:open_issues
agents
agent:security_audit
agent:verify
area
timecurve
arena
arena-v2
audit
block:feature_review
block:security
bug
contracts
design
devops
docs
documentation
enhancement
epic
feature
frontend
indexer
low priority
megaeth
operations
ops
performance
QA
qa checklist
referrals
removal
security
smart-contracts
tech-debt
testing
time-arena
timecurve
visual-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
code/yieldomega#273
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Complete the #254 gap: while the arena is live,
GET /v1/arena/podiumsmust return top-3 winner slots and scores derived from indexed Postgres (per category and current epoch), not from headTimeArena.podium(category)RPC alone. Consumers (Simple/arena, Arena views, play skills) use this route whenVITE_INDEXER_URLis set.Related: #254 (schema + HTTP baseline), #238 (epic), #247 (independent podium epochs), #252 (WarBow), frontend
PODIUM_CONTRACT_CATEGORY_INDEX(#256).Current codebase
HTTP (
GET /v1/arena/podiums)indexer/src/api_arena.rs(arena_podiums).podium_contract[0..3]), which is populated by head RPCpodium(cat)inindexer/src/chain_timer.rs.MAX(epoch)per category fromidx_arena_podium_epoch(rolled epochs). Response always setspodium_prediction: truewithout building leaders from the DB.last_buy,time_booster,defended_streak,warbow), not the UX order the frontend expects when mapping rows by array index.Frontend contract
frontend/src/pages/arena/usePodiumReads.ts— with indexer enabled, mapsresponse.rows[0..3]directly to UI slots.frontend/src/pages/arena/podiumCopy.tsx: Last Buy · WarBow · Defended Streak · Time Booster viaPODIUM_CONTRACT_CATEGORY_INDEX = [0, 3, 2, 1](onchain cat indices).frontend/src/lib/indexerApi.tsdocumentspodium_predictionfor live sale windows but the indexer does not yet populate live leaders from events.Indexed inputs (partial)
lastBuyEpoch(buyCount)idx_arena_buy(no epoch column; no order index)totalEffectiveTimerSecAdded[buyer]bestDefendedStreak[buyer]battlePoints[buyer]in currentpodiumEpoch[3]idx_warbow_epoch_score(post-log eth_call snapshots)Settlement history:
idx_arena_podium_epoch/ viewidx_arena_podium_snapshot— rolled epochs only, not live boards.Product / onchain authority
contracts/src/arena/TimeArena.sol(_updateTopThree,_applyBuyWarBowBp, defended-streak hooks). Spec:docs/product/arena-v2.md,docs/product/time-arena.md.docs/architecture/overview.md, yieldomega guardrails). Predictions must match chain at the same block tag, not invent alternate rules.Stale design note
docs/indexer/design.md(TimeCurve podiums section) still describes live DB predictions during!sale_ended; Arena v2 implementation was never completed — this issue tracks that work.Why this is needed
GET /v1/arena/podiums: live predictions per category per epoch. #254 remains open on this slice.podium(3)is often empty mid-epoch while BP exists onchain; live sale UI must show indexed leaders (docs/indexer/design.md).skills/play-active-time-arena) need a single HTTP source aligned with event history, not an extra RPC fan-out per page load.category+ stable sort so Simple/Arena do not swap WarBow and Time Booster.Constraints and guardrails
docs/licensing.md).indexer/src/warbow_score.rs). Prefer event-sourced projections where events carry enough data; use bounded RPC only where logs omit scores.indexer/src/reorg.rs).lastBuyEpoch(from chain timer /LastBuyEpochStarted).podiumEpoch[cat](RPC oridx_arena_podium_epoch+ 1 for live window).sale_endedgating (arena is live untilpaused).rollPodiumEpoch: live board for that category clears onchain; API may return empty slots or fall back to settledidx_arena_podium_epochrow for the just-finished epoch — document chosen behavior in API + invariants.GET /v1/arena/podiumsshould stay O(1) or indexed top-3 queries, not full-table scans; target p95 acceptable on Anvil-rich dataset (see #255 wallet-stats note).SCHEMA_VERSIONinindexer/src/api.rsand extendfrontend/src/lib/indexerApi.tstypes.Relevant files
indexer/src/api_arena.rs,indexer/src/chain_timer.rsindexer/src/ingestion.rs,indexer/src/decoder.rs,indexer/src/persist.rs,indexer/src/warbow_score.rsindexer/migrations/indexer/tests/integration_stage2.rsfrontend/src/lib/indexerApi.ts,frontend/src/pages/arena/usePodiumReads.ts,frontend/src/pages/arena/podiumCopy.tsxdocs/indexer/design.md,docs/testing/invariants-and-business-logic.md,docs/frontend/arena-views.md,skills/play-active-time-arena/SKILL.mdcontracts/src/arena/TimeArena.sol,contracts/test/TimeArena.t.solRecommended direction
idx_arena_podium_liveor per-cat tables) storing, per (category, epoch, player): score, optional tie-break fields,block_number,updated_at— updated on ingest whenBuy, WarBow PvP logs, orLastBuyEpochStartedaffect leaders.indexer/src/arena_podium_live.rs):B, eth_call atBthe same views TimeArena uses:buyCount,totalEffectiveTimerSecAdded,bestDefendedStreak,battlePoints(WarBow may reuse/extendwarbow_score.rs)._updateTopThreemath in Rust only if every input is recoverable from indexed events without drift — prefer snapshot-at-block when in doubt.arena_podiumsread path:last_buy_epoch+podium_epoch[cat]from chain timer (extend snapshot if missing).PODIUM_CONTRACT_CATEGORY_INDEX), query top-3 from Postgres for (cat, current_epoch).podium_prediction: truewhen rows are DB-derived; setfalsewhen intentionally mirroring RPC (e.g. post-roll settled-only mode, if implemented).category_index+ sort; updateusePodiumReadsif needed).GET /v1/arena/podiums?category=warbowlater — out of scope unless trivial; single route is enough for v1.Acceptance criteria
ARENA_INDEX_TABLES/rollback_after.Buy+ WarBow BP mutations).GET /v1/arena/podiumsreturns top-3winners+valuesfrom Postgres for the current epoch per category while the arena is live.epoch(string decimal) per row matching onchainpodiumEpoch/lastBuyEpochsemantics.categorywithout slot swap.podium_prediction: trueonly when values are DB-derived; document any RPC fallback.idx_warbow_epoch_score/ live table has data, even if headpodium(3)is empty.SCHEMA_VERSIONbump +indexerApi.tstypes updated.INV-INDEXER-PODIUM-PREDICT-LIVE(or successor) added/updated indocs/testing/invariants-and-business-logic.md; cross-linkdocs/indexer/design.md.integration_stage2.rscovers HTTP body shape + persist counts for at least two categories (e.g. Last Buy order + WarBow BP).Test plan — functional paths
GET /v1/arena/podiums→ 200, four rows, zero/empty winners,epochfrom head RPCbuyCountscore matches onchain at buy blocklastBuyEpochtotalEffectiveTimerSecAddedleader at head blockbestDefendedStreakat head blockbattlePointsfor currentpodiumEpoch[3]LastBuyEpochStarted/ hard resetPodiumEpochRolledfor cat 1chain_timerunavailablechain_timer_unavailable(unchanged)VITE_INDEXER_URLcast call podium(cat)at same block (manual or scripted)Commands:
YIELDOMEGA_PG_TEST_URL=… cargo test --test integration_stage2; local stackbash scripts/start-local-anvil-stack.sh+ indexer; optional Playwright arena podium specs if present.Test plan — attack, hack, and abuse vectors
0x+ 40 hex; API validates format; ignore malformed rows in top-3rollback_afterdeletes live rows; integration reorg testcast callatread_block_numberGET /v1/arena/podiums(category, epoch, score DESC); limit work to top-3 per cat; rate limit at reverse proxy (ops)warbowBpGeneration/ epoch; test epoch roll clears live WarBow tablebuy_index/ block+log order from chain, not wall clockaddress(0)handling; frontend already pads zerosVerification criteria (close checklist)
cargo clippy --all-targets -- -D warningsandcargo testgreen inindexer/.integration_stage2passes with Postgres.curl $INDEXER/v1/arena/podiumsmatchescast callpodium for eachPODIUM_CONTRACT_CATEGORY_INDEXat same block.mentioned in commit
5a5c8dc72fSummary
Implemented live podium predictions for
GET /v1/arena/podiums(#273) — merged tomainat5a5c8dc.What changed
idx_arena_podium_live— top-3 slots per(category, epoch); included inARENA_INDEX_TABLES/ reorg rollback.arena_podium_live.rs— block-taggedpodium()snapshots onBuy,LastBuyEpochStarted,PodiumEpochRolled, and WarBow BP events; WarBow leaders rebuilt fromidx_warbow_epoch_score.epochfromchain_timer(lastBuyEpoch/podiumEpoch[cat]),podium_prediction: trueonly when DB has entrants; RPC fallback otherwise.lastBuyEpochandpodiumEpoch[0..3];GET /v1/arena/timersexposespodium_epochs.2.5.0— types inindexerApi.ts; docs/invariants/skills cross-linked (INV-INDEXER-PODIUM-PREDICT-LIVE).Verification checklist
sqlx migrate run(new20260601140000_idx_arena_podium_live_gl273).cd indexer && cargo clippy --all-targets -- -D warnings && cargo testYIELDOMEGA_PG_TEST_URL=… cargo test --test integration_stage2(includesarena_podiums_live_predictions_smoke).curl $INDEXER/v1/arena/podiums— four UX rows, WarBow non-empty when BP exists,podium_prediction: true.cast call TimeArena.podium(cat)for cats 0,1,2; WarBow vs maxbattlePointsin epoch.VITE_INDEXER_URL: Simple/Arena podium cards match indexer rows.LastBuyEpochStarted/PodiumEpochRolled: live board resets for new epoch (no prior-epoch buyers in Last Buy top-3).idx_arena_podium_liverows for reorged blocks.Follow-ups
GET /v1/arena/podiums?category=warbowfilter (out of scope for v1).QA agent team: please run the checklist above on a fresh Anvil + indexer deploy and report pass/fail on this issue (leave open until signed off).
mentioned in commit
d902d7fff9QA verification complete (#273)
Verified live podium predictions on
issue/273-verifyworktree, fixed one blocker, merged tomain(d902d7f).What changed in this verification pass
chain_timer/ WarBow epoch reads:podiumDeadlineandpodiumEpochare Solidity public array getters (uint256index). The indexer useduint8selectors that revert onTimeArena, soGET /v1/arena/podiums(and/v1/arena/timers) returned503 chain_timer_unavailable. Fixed inindexer/src/chain_timer.rsandindexer/src/warbow_score.rs(INV-INDEXER-273-CHAIN-TIMER-SELECTORS).bash scripts/verify-podium-live-anvil.sh(default ports 8546/3101 to avoid stack clashes).indexer/README.mdcross-links.Automated checks run (all green)
cargo clippy --all-targets -- -D warnings(indexer)YIELDOMEGA_PG_TEST_URL=… cargo test --test integration_stage2(includesarena_podiums_live_predictions_smoke+ rollback)bash scripts/verify-podium-live-anvil.sh— UX order,podium_predictionsemantics, Last Buy / Time Booster vs block-taggedpodium(), WarBow vsbattlePoints,idx_arena_podium_liverows populatedManual re-verify checklist
yieldomega-pgon 5433 or setDATABASE_URL/YIELDOMEGA_PG_TEST_URL)bash scripts/verify-podium-live-anvil.shexits 0bash scripts/start-local-anvil-stack.sh→ 3+ wallet buys →curl $INDEXER/v1/arena/podiumsmatches onchain atread_block_numberVITE_INDEXER_URL:/arenapodium cards match indexer (four categories, UX order)podium(3)emptyPodiumEpochRolled/LastBuyEpochStarted: live board resets per documented API behaviorrollback_after(covered inintegration_stage2)Related: #254 slice for live podiums is satisfied by this work; QA may close #254 after spot-checking the same checklist there.
mentioned in issue #254
mentioned in issue #278
mentioned in issue #300
mentioned in issue #302
mentioned in issue #306
mentioned in issue #307
mentioned in issue #356
marked as related to #356