diff --git a/database/seed.go b/database/seed.go index 5f32b52c..b37704af 100644 --- a/database/seed.go +++ b/database/seed.go @@ -534,12 +534,6 @@ var ( "timestamp": nil, "price": 0.0, }, - "artist_coin_volume_accumulator": { - "mint": nil, - "last_processed_slot": 0, - "total_volume": 0.0, - "total_volume_usd": 0.0, - }, "sol_meteora_dbc_pools": { "account": nil, "slot": 1, diff --git a/ddl/migrations/0234_drop_artist_coin_volume_accumulator.sql b/ddl/migrations/0234_drop_artist_coin_volume_accumulator.sql new file mode 100644 index 00000000..dfc84c5c --- /dev/null +++ b/ddl/migrations/0234_drop_artist_coin_volume_accumulator.sql @@ -0,0 +1,8 @@ +-- Drop the artist coin volume accumulator. The all-time volume stat it fed was +-- removed from the product (unverifiable vendor figure; no other aggregator even +-- reports it), and CoinStatsOnchainJob no longer computes volume. +BEGIN; + +DROP TABLE IF EXISTS artist_coin_volume_accumulator; + +COMMIT; diff --git a/jobs/coin_stats_onchain.go b/jobs/coin_stats_onchain.go index d5f758bf..3deba88e 100644 --- a/jobs/coin_stats_onchain.go +++ b/jobs/coin_stats_onchain.go @@ -95,12 +95,6 @@ type coinAgg struct { Liquidity float64 `db:"liquidity"` } -type volumeRow struct { - Mint string `db:"mint"` - TotalVolume float64 `db:"total_volume"` - TotalVolumeUsd float64 `db:"total_volume_usd"` -} - type priceRow struct { Mint string `db:"mint"` Price float64 `db:"price"` @@ -138,16 +132,7 @@ func (j *CoinStatsOnchainJob) run(ctx context.Context) error { return fmt.Errorf("error computing aggregates: %w", err) } - // 2. Advance the all-time volume accumulator with trades in new slots. - if err := j.updateVolumeAccumulator(ctx, audioPrice); err != nil { - return fmt.Errorf("error updating volume accumulator: %w", err) - } - volumes, err := j.readVolumeAccumulator(ctx) - if err != nil { - return fmt.Errorf("error reading volume accumulator: %w", err) - } - - // 3. Snapshot current on-chain prices (hourly bin) and read the ~24h-ago price. + // 2. Snapshot current on-chain prices (hourly bin) and read the ~24h-ago price. if err := j.snapshotPrices(ctx, now); err != nil { return fmt.Errorf("error snapshotting prices: %w", err) } @@ -194,9 +179,7 @@ func (j *CoinStatsOnchainJob) run(ctx context.Context) error { } } - vol := volumes[mint] // zero value if absent - - if err := j.upsertStats(ctx, mint, agg, marketCap, totalSupply, vol, history, priceChange); err != nil { + if err := j.upsertStats(ctx, mint, agg, marketCap, totalSupply, history, priceChange); err != nil { j.logger.Error("error upserting onchain stats", zap.String("mint", mint), zap.Error(err)) } } @@ -301,65 +284,6 @@ func (j *CoinStatsOnchainJob) queryAggregates(ctx context.Context, audioPrice fl return out, nil } -// updateVolumeAccumulator adds trading volume from balance changes in new slots -// (per-coin watermark) to the running accumulator, valued in USD at the current -// AUDIO price so historical USD isn't repriced at today's rate. -func (j *CoinStatsOnchainJob) updateVolumeAccumulator(ctx context.Context, audioPrice float64) error { - sql := ` - WITH vaults AS ( - SELECT base_mint AS mint, quote_vault AS vault FROM sol_meteora_dbc_pools - UNION - SELECT token_a_mint AS mint, token_b_vault AS vault FROM sol_meteora_damm_v2_pools - ), - delta AS ( - SELECT - v.mint, - SUM(ABS(c.change))::numeric / POWER(10, 8) AS vol_audio, - MAX(c.slot) AS max_slot - FROM vaults v - JOIN sol_token_account_balance_changes c - ON c.account = v.vault AND c.mint = @audio_mint - WHERE c.slot > COALESCE( - (SELECT last_processed_slot FROM artist_coin_volume_accumulator acc WHERE acc.mint = v.mint), - 0 - ) - GROUP BY v.mint - ) - INSERT INTO artist_coin_volume_accumulator (mint, last_processed_slot, total_volume, total_volume_usd, updated_at) - SELECT d.mint, d.max_slot, d.vol_audio, d.vol_audio * @audio_price, NOW() - FROM delta d - WHERE d.max_slot IS NOT NULL - ON CONFLICT (mint) DO UPDATE SET - total_volume = artist_coin_volume_accumulator.total_volume + EXCLUDED.total_volume, - total_volume_usd = artist_coin_volume_accumulator.total_volume_usd + EXCLUDED.total_volume_usd, - last_processed_slot = EXCLUDED.last_processed_slot, - updated_at = NOW() - ` - _, err := j.pool.Exec(ctx, sql, pgx.NamedArgs{ - "audio_mint": j.audioMint, - "audio_price": audioPrice, - }) - return err -} - -func (j *CoinStatsOnchainJob) readVolumeAccumulator(ctx context.Context) (map[string]volumeRow, error) { - rows, err := j.pool.Query(ctx, - `SELECT mint, total_volume, total_volume_usd FROM artist_coin_volume_accumulator`, - ) - if err != nil { - return nil, err - } - list, err := pgx.CollectRows(rows, pgx.RowToStructByName[volumeRow]) - if err != nil { - return nil, err - } - out := make(map[string]volumeRow, len(list)) - for _, r := range list { - out[r.Mint] = r - } - return out, nil -} - // snapshotPrices records the current on-chain USD price for each coin in an // hourly bin, used to compute the 24h price change. func (j *CoinStatsOnchainJob) snapshotPrices(ctx context.Context, now time.Time) error { @@ -481,18 +405,17 @@ func (j *CoinStatsOnchainJob) upsertStats( agg coinAgg, marketCap *float64, totalSupply *float64, - vol volumeRow, history24h *float64, priceChange *float64, ) error { sql := ` INSERT INTO artist_coin_stats_onchain ( mint, price, market_cap, liquidity, holder, total_supply, - total_volume, total_volume_usd, history_24h_price, price_change_24h_percent, + history_24h_price, price_change_24h_percent, created_at, updated_at ) VALUES ( @mint, @price, @market_cap, @liquidity, @holder, @total_supply, - @total_volume, @total_volume_usd, @history_24h_price, @price_change_24h_percent, + @history_24h_price, @price_change_24h_percent, NOW(), NOW() ) ON CONFLICT (mint) DO UPDATE SET @@ -502,8 +425,6 @@ func (j *CoinStatsOnchainJob) upsertStats( liquidity = EXCLUDED.liquidity, holder = EXCLUDED.holder, total_supply = COALESCE(EXCLUDED.total_supply, artist_coin_stats_onchain.total_supply), - total_volume = EXCLUDED.total_volume, - total_volume_usd = EXCLUDED.total_volume_usd, history_24h_price = COALESCE(EXCLUDED.history_24h_price, artist_coin_stats_onchain.history_24h_price), price_change_24h_percent = COALESCE(EXCLUDED.price_change_24h_percent, artist_coin_stats_onchain.price_change_24h_percent), updated_at = NOW() @@ -515,8 +436,6 @@ func (j *CoinStatsOnchainJob) upsertStats( "liquidity": agg.Liquidity, "holder": agg.Holder, "total_supply": totalSupply, - "total_volume": vol.TotalVolume, - "total_volume_usd": vol.TotalVolumeUsd, "history_24h_price": history24h, "price_change_24h_percent": priceChange, }) diff --git a/jobs/coin_stats_onchain_test.go b/jobs/coin_stats_onchain_test.go index 506d94a7..75277761 100644 --- a/jobs/coin_stats_onchain_test.go +++ b/jobs/coin_stats_onchain_test.go @@ -79,13 +79,6 @@ func TestCoinStatsOnchainJob(t *testing.T) { {"account": "acct5", "mint": coinMint, "owner": "owner1", "balance": 3, "slot": 1}, {"account": "acct4", "mint": coinMint, "owner": "owner4", "balance": 0, "slot": 1}, }, - // Volume through the DBC quote vault (AUDIO leg): |1e8| + |-2e8| = 3e8 -> 3 AUDIO. - "sol_token_account_balance_changes": { - {"signature": "sig1", "mint": audioMint, "owner": "trader1", "account": quoteVault, - "change": 100_000_000, "balance": 100_000_000, "slot": 10, "block_timestamp": "2024-01-01 00:00:00"}, - {"signature": "sig2", "mint": audioMint, "owner": "trader2", "account": quoteVault, - "change": -200_000_000, "balance": 0, "slot": 11, "block_timestamp": "2024-01-01 00:01:00"}, - }, // Active DBC pool -> liquidity = base_reserve*price + quote_reserve*audioPrice // = (1e9/1e6)*4.0 + (5e10/1e8)*2.0 = 1000*4 + 500*2 = 5000. "sol_meteora_dbc_pools": { @@ -119,17 +112,15 @@ func TestCoinStatsOnchainJob(t *testing.T) { liquidity float64 totalSupply float64 marketCap float64 - totalVolume float64 - totalVolUSD float64 history24h float64 priceChange24 float64 ) err := pool.QueryRow(ctx, ` SELECT price, holder, liquidity, total_supply, market_cap, - total_volume, total_volume_usd, history_24h_price, price_change_24h_percent + history_24h_price, price_change_24h_percent FROM artist_coin_stats_onchain WHERE mint = $1`, coinMint). Scan(&price, &holder, &liquidity, &totalSupply, &marketCap, - &totalVolume, &totalVolUSD, &history24h, &priceChange24) + &history24h, &priceChange24) require.NoError(t, err) assert.InDelta(t, 4.0, price, 1e-9, "price from pools_price_usd") @@ -137,8 +128,6 @@ func TestCoinStatsOnchainJob(t *testing.T) { assert.InDelta(t, 5000.0, liquidity, 1e-6, "TVL = base_usd + quote_usd") assert.InDelta(t, 1000.0, totalSupply, 1e-9, "supply from RPC") assert.InDelta(t, 4000.0, marketCap, 1e-6, "price * supply") - assert.InDelta(t, 3.0, totalVolume, 1e-9, "AUDIO volume through vault") - assert.InDelta(t, 6.0, totalVolUSD, 1e-9, "volume * audioPrice") assert.InDelta(t, 2.0, history24h, 1e-9, "24h-ago snapshot") assert.InDelta(t, 100.0, priceChange24, 1e-6, "24h percent change") diff --git a/sql/01_schema.sql b/sql/01_schema.sql index 90cf4cce..0b312200 100644 --- a/sql/01_schema.sql +++ b/sql/01_schema.sql @@ -7735,26 +7735,6 @@ CREATE VIEW public.artist_coin_stats_comparison AS COMMENT ON VIEW public.artist_coin_stats_comparison IS 'Compares Birdeye artist_coin_stats vs on-chain artist_coin_stats_onchain per coin (values + % diff) to validate CoinStatsOnchainJob before cutover.'; --- --- Name: artist_coin_volume_accumulator; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.artist_coin_volume_accumulator ( - mint text NOT NULL, - last_processed_slot bigint DEFAULT 0 NOT NULL, - total_volume double precision DEFAULT 0 NOT NULL, - total_volume_usd double precision DEFAULT 0 NOT NULL, - updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL -); - - --- --- Name: TABLE artist_coin_volume_accumulator; Type: COMMENT; Schema: public; Owner: - --- - -COMMENT ON TABLE public.artist_coin_volume_accumulator IS 'Running per-coin trading volume (AUDIO + USD) accumulated from pool quote-vault balance changes, watermarked by last_processed_slot. Written by CoinStatsOnchainJob.'; - - -- -- Name: associated_wallets; Type: TABLE; Schema: public; Owner: - -- @@ -11387,14 +11367,6 @@ ALTER TABLE ONLY public.artist_coin_stats ADD CONSTRAINT artist_coin_stats_pkey PRIMARY KEY (mint); --- --- Name: artist_coin_volume_accumulator artist_coin_volume_accumulator_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.artist_coin_volume_accumulator - ADD CONSTRAINT artist_coin_volume_accumulator_pkey PRIMARY KEY (mint); - - -- -- Name: artist_coins artist_coins_pkey; Type: CONSTRAINT; Schema: public; Owner: - --