Skip to content

feat(jobs): cut coin stats over to on-chain data and remove Birdeye - #1007

Merged
rickyrombo merged 1 commit into
mainfrom
mjp-coinstats-flip-onchain
Aug 4, 2026
Merged

feat(jobs): cut coin stats over to on-chain data and remove Birdeye#1007
rickyrombo merged 1 commit into
mainfrom
mjp-coinstats-flip-onchain

Conversation

@rickyrombo

@rickyrombo rickyrombo commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What

Phase-2 cutover of the coin-stats shadow rollout: retires the Birdeye-backed CoinStatsJob, makes the on-chain job the source of truth, and removes Birdeye from the service entirely. Follows #1006 (AUDIO anchor on-chain), which was the last Birdeye call outside CoinStatsJob.

Mechanism (why consumers don't change)

CoinStatsOnchainJob already read the AUDIO anchor from artist_coin_stats and iterated artist_coins. The cutover just repoints its upsert from artist_coin_stats_onchainartist_coin_stats. Every consumer — the artist_coin_prices view, v1_coins, v1_coin_insights, get_users, coin_dbc — keeps reading artist_coin_stats unchanged.

(Renaming tables was rejected: Postgres views bind to table OID, so a rename would leave the view pointing at the old data.)

The AUDIO row bootstrap holds: AUDIO is one of the 57 artist_coins, so the on-chain job upserts its row (price kept via COALESCE, never clobbered), and AudioPriceJob maintains its price from the pool.

Birdeye removal

  • Delete the birdeye/ client package, the dead server BirdeyeClient interface + field + unused mock_birdeye_client.go, and the BirdeyeToken config field.
  • Delete jobs/coin_stats.go and its indexer scheduling.
  • Drop the now-unused artist_coin_stats_onchain shadow table and artist_coin_stats_comparison view (migration 0235).
  • De-Birdeye the artist_coin_prices view comment, swagger coin_insights description, and stale test/model comments. (Applied migrations 0229–0231 keep their historical comments.)

Behavior — market-cap change (verified against live prod data)

Rendered stats — price, market cap, liquidity, holder, total supply, 24h change — are all on-chain-derived. The ~40 unrendered Birdeye-only columns remain in the table (NULL on new rows; consumers COALESCE(...,0)).

Market cap moves −8.5% in aggregate ($4.64M → $4.25M), and it is a stale-price effect on dormant coins — not market aggregation and not a supply basis. Evidence from the live /v1/coins payload:

  • No missing markets. Birdeye's own numberMarkets = 1 for 53 of 57 coins. Only AUDIO (12) and KITT/UWU/JAY (2) have more, and those match within ~2%.
  • Not supply. totalSupply == circulatingSupply for every coin.
  • Stale last-trade price. The 34 coins whose Birdeye market cap exceeds on-chain by >1.3× have 0 trades in 24h and average 87 days since their last trade; Birdeye holds their last-trade quote while the on-chain value tracks the current pool. Coins that actually trade agree within 0.3% (AUDIO within 0.2%; corroborated by DexScreener, which reads the live pool).
  • Concentration. 100% of the aggregate drop is dormant coins; the active-coin bucket is −0.3%. Every liquid coin a user would look at is effectively unchanged — dormant coins simply reprice from a months-old quote to their current pool price.

Schema dump

sql/01_schema.sql hand-edited to remove only the shadow table + comparison view (the committed dump was stale on unrelated merged migrations; a full regen would have pulled in new_chain_queue etc.). Diff is 1 comment reword + 113 deletions. Verified by reloading the dump into a fresh DB.

Test

go build ./..., go vet ./..., go test ./jobs/ (coin-stats + audio-price) and go test ./api/ (Coin/Wallet/Users) all pass against the regenerated DB.

🤖 Generated with Claude Code

Retires the Birdeye-backed CoinStatsJob. CoinStatsOnchainJob now writes
artist_coin_stats directly (it already read the AUDIO anchor from that table
and iterated artist_coins), so every consumer — the artist_coin_prices view,
v1_coins, v1_coin_insights, get_users, coin_dbc — keeps reading the same table
unchanged. This is the Phase-2 cutover of the shadow-table rollout: the on-chain
values were validated against Birdeye via artist_coin_stats_comparison before
flipping.

With CoinStatsJob gone and AudioPriceJob already sourcing the AUDIO/USDC pool
on-chain, nothing calls Birdeye anymore, so this removes it entirely:
- delete the birdeye client package, the dead server BirdeyeClient interface +
  field + unused mock, and the BirdeyeToken config field
- drop the now-unused artist_coin_stats_onchain shadow table and the
  artist_coin_stats_comparison view (migration 0235)
- de-Birdeye the artist_coin_prices view comment, swagger coin_insights
  description, and stale test/model comments

Rendered stats (price, market cap, liquidity, holder, total supply, 24h change)
are all on-chain-derived; the ~40 unrendered Birdeye-only columns stay in the
table (NULL for new rows; consumers COALESCE to 0). Market cap shifts -8.5% in
aggregate, concentrated entirely in dormant coins: Birdeye holds a stale
last-trade price for the ~34 coins with no trades in months, while the on-chain
value tracks the current pool. Coins that actually trade match within 0.3%;
numberMarkets=1 for 53/57 and totalSupply==circulatingSupply, so it is neither a
multi-market nor a supply-basis effect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rickyrombo
rickyrombo force-pushed the mjp-coinstats-flip-onchain branch from 383afad to 88987a6 Compare August 4, 2026 03:17
@rickyrombo

Copy link
Copy Markdown
Contributor Author

Verified: the market-cap change is Birdeye correcting to reality, not on-chain under-counting

Followed up on the market-cap delta with live prod data + on-chain checks. Summary:

No missing markets / not a supply artifact. numberMarkets = 1 for 53/57 coins (Birdeye's own field); totalSupply == circulatingSupply everywhere. So the gap is neither multi-market aggregation nor circulating-vs-total.

It's Birdeye price staleness on dormant coins, and our value is provably correct. Three independent sources converge on the on-chain price, not Birdeye's:

  1. Meteora SDK, live on-chain — decoding each DBC pool and running the live sqrt_price through the SDK's getPriceFromSqrtPrice (identical formula to our price_from_sqrt_price) reproduces our served price to 10 sig figs (DUBS/ZAZA/ICT/CG all exact).
  2. The product's own swap router (/meteora/swap_coin_quote) — the real executable price is our spot ±1% (symmetric buy/sell = the pool fee), and flat across a 400× trade-size range (negligible curve impact).
  3. Birdeye sits 8–61% above even the executable buy price for these coins — i.e. not achievable in the market. (Jupiter's price API echoed Birdeye only because these tokens are TOKEN_NOT_TRADABLE on Jupiter, so it wasn't an independent source.)

Every coin that has traded recently (ICT, DREAM, JAY, AUDIO) matches Birdeye within ~1%. Aggregate market-cap change is −8.5%, 100% of it in coins that haven't traded in months (avg 87 days), where Birdeye holds a stale last-trade quote.

Net: switching the anchor + stats on-chain makes prices more accurate for the long tail, with zero change to any liquid coin.

@rickyrombo
rickyrombo merged commit 159391c into main Aug 4, 2026
2 checks passed
@rickyrombo
rickyrombo deleted the mjp-coinstats-flip-onchain branch August 4, 2026 03:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant