diff --git a/ddl/functions/handle_associated_wallet.sql b/ddl/functions/handle_associated_wallet.sql index 292fd140..0f3f8cca 100644 --- a/ddl/functions/handle_associated_wallet.sql +++ b/ddl/functions/handle_associated_wallet.sql @@ -1,33 +1,39 @@ CREATE OR REPLACE FUNCTION handle_associated_wallets() RETURNS TRIGGER AS $$ DECLARE - v_mint varchar; + v_mint varchar; + v_user_id int; + v_wallet text; + v_chain text; BEGIN - -- For INSERT, always run - IF TG_OP = 'INSERT' THEN - FOR v_mint IN - SELECT DISTINCT mint FROM sol_token_account_balances WHERE owner = NEW.wallet - LOOP - PERFORM update_sol_user_balance_mint(NEW.user_id, v_mint); - END LOOP; + IF TG_OP = 'DELETE' THEN + v_user_id := OLD.user_id; + v_wallet := OLD.wallet; + v_chain := OLD.chain; + ELSE + v_user_id := NEW.user_id; + v_wallet := NEW.wallet; + v_chain := NEW.chain; END IF; - -- For UPDATE, only run if is_delete changed - IF TG_OP = 'UPDATE' AND (NEW.is_delete IS DISTINCT FROM OLD.is_delete) THEN - FOR v_mint IN - SELECT DISTINCT mint FROM sol_token_account_balances WHERE owner = NEW.wallet - LOOP - PERFORM update_sol_user_balance_mint(NEW.user_id, v_mint); - END LOOP; + -- Only act on INSERT, DELETE, or an is_delete flip on UPDATE — a no-op + -- metadata UPDATE shouldn't recompute balances (preserves prior behavior). + IF TG_OP = 'UPDATE' AND (NEW.is_delete IS NOT DISTINCT FROM OLD.is_delete) THEN + RETURN NULL; END IF; - -- For DELETE, always run - IF TG_OP = 'DELETE' THEN - FOR v_mint IN - SELECT DISTINCT mint FROM sol_token_account_balances WHERE owner = OLD.wallet - LOOP - PERFORM update_sol_user_balance_mint(OLD.user_id, v_mint); - END LOOP; + -- wAUDIO / artist-coin (sol) balances: recompute every sol mint this wallet + -- holds. For a chain=eth wallet this loop simply finds nothing. + FOR v_mint IN + SELECT DISTINCT mint FROM sol_token_account_balances WHERE owner = v_wallet + LOOP + PERFORM update_sol_user_balance_mint(v_user_id, v_mint); + END LOOP; + + -- AUDIO (ETH-side) balance: linking / unlinking a chain=eth wallet changes + -- the user's aggregated eth_user_balances total. + IF v_chain = 'eth' THEN + PERFORM update_eth_user_balance(v_user_id); END IF; RETURN NULL; @@ -46,4 +52,4 @@ BEGIN EXCEPTION WHEN others THEN NULL; END $$; -COMMENT ON TRIGGER on_associated_wallets ON associated_wallets IS 'Updates sol_user_balances when associated_wallets are added and removed'; \ No newline at end of file +COMMENT ON TRIGGER on_associated_wallets ON associated_wallets IS 'Updates sol_user_balances and eth_user_balances when associated_wallets are added and removed'; \ No newline at end of file diff --git a/ddl/functions/handle_eth_wallet_balance_change.sql b/ddl/functions/handle_eth_wallet_balance_change.sql new file mode 100644 index 00000000..134c872d --- /dev/null +++ b/ddl/functions/handle_eth_wallet_balance_change.sql @@ -0,0 +1,52 @@ +-- Keeps eth_user_balances fresh when the eth-indexer writes a wallet balance. +-- A single eth_wallet_balances row can back a user's primary wallet and/or any +-- number of users' linked chain=eth associated_wallets, so we recompute every +-- user the changed wallet maps to. NEW.wallet is already lowercase hex (the +-- eth_wallet_balances PK); users.wallet is lowered to match. +CREATE OR REPLACE FUNCTION handle_eth_wallet_balance_change() +RETURNS TRIGGER AS $$ +DECLARE + v_user_id int; +BEGIN + -- Skip metadata-only updates (e.g. an updated_at touch with no balance + -- delta) so we don't churn eth_user_balances for no reason. + IF TG_OP = 'UPDATE' AND NEW.balance IS NOT DISTINCT FROM OLD.balance THEN + RETURN NULL; + END IF; + + FOR v_user_id IN + SELECT user_id + FROM users + WHERE LOWER(wallet) = NEW.wallet + AND is_current = TRUE + + UNION + + SELECT user_id + FROM associated_wallets + WHERE LOWER(wallet) = NEW.wallet + AND chain = 'eth' + AND is_current = TRUE + AND is_delete = FALSE + LOOP + PERFORM update_eth_user_balance(v_user_id); + END LOOP; + + RETURN NULL; +EXCEPTION + WHEN OTHERS THEN + RAISE WARNING 'An error occurred in %: %', TG_NAME, SQLERRM; + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +DO $$ +BEGIN + CREATE TRIGGER on_eth_wallet_balance_changes + AFTER INSERT OR UPDATE ON eth_wallet_balances + FOR EACH ROW EXECUTE PROCEDURE handle_eth_wallet_balance_change(); +EXCEPTION + WHEN others THEN NULL; +END $$; +COMMENT ON TRIGGER on_eth_wallet_balance_changes ON eth_wallet_balances IS + 'Recomputes eth_user_balances for affected users whenever an eth wallet balance changes.'; diff --git a/ddl/functions/update_eth_user_balance.sql b/ddl/functions/update_eth_user_balance.sql new file mode 100644 index 00000000..8d3a9ea4 --- /dev/null +++ b/ddl/functions/update_eth_user_balance.sql @@ -0,0 +1,40 @@ +-- Recompute one user's aggregated ETH-side AUDIO balance (wei) into +-- eth_user_balances. ETH analog of update_sol_user_balance_mint: sums +-- eth_wallet_balances across the user's primary wallet + linked chain=eth +-- associated_wallets. Always writes exactly one row (no GROUP BY) so unlinking +-- the last wallet correctly drives the balance to 0 rather than leaving a stale +-- value. +CREATE OR REPLACE FUNCTION update_eth_user_balance(p_user_id int) +RETURNS VOID AS $$ +BEGIN + INSERT INTO eth_user_balances (user_id, balance, updated_at, created_at) + SELECT + p_user_id, + COALESCE(SUM(ewb.balance), 0), + NOW(), + NOW() + FROM eth_wallet_balances ewb + WHERE ewb.wallet IN ( + -- eth_wallet_balances PK is lowercase hex; users.wallet can be + -- mixed-case, associated_wallets are canonical lowercase (0207). + SELECT LOWER(wallet) + FROM users + WHERE user_id = p_user_id + AND is_current = TRUE + AND wallet IS NOT NULL + + UNION ALL + + SELECT LOWER(wallet) + FROM associated_wallets + WHERE user_id = p_user_id + AND chain = 'eth' + AND is_current = TRUE + AND is_delete = FALSE + ) + ON CONFLICT (user_id) + DO UPDATE SET + balance = EXCLUDED.balance, + updated_at = NOW(); +END; +$$ LANGUAGE plpgsql; diff --git a/ddl/migrations/0213_add_eth_user_balances.sql b/ddl/migrations/0213_add_eth_user_balances.sql new file mode 100644 index 00000000..5c18cadc --- /dev/null +++ b/ddl/migrations/0213_add_eth_user_balances.sql @@ -0,0 +1,50 @@ +BEGIN; +SET LOCAL statement_timeout = 0; + +-- Pre-aggregated AUDIO (ETH-side, in wei) balance per user — the ETH analog of +-- sol_user_balances. Replaces the per-user LATERAL aggregate that +-- v_user_balances ran over eth_wallet_balances on every read (the GetUsers hot +-- path): summing a user's wallets live, per row, made GetUsers scale with the +-- whole user base whenever the planner materialized the view instead of +-- parameterizing it. This table turns that into a single PK lookup, exactly +-- like the sol side. +-- +-- Kept fresh by triggers: +-- * handle_eth_wallet_balance_change (eth_wallet_balances writes) +-- * handle_associated_wallets (chain=eth wallet link / unlink) +-- and recomputed for one user via update_eth_user_balance(user_id). +CREATE TABLE IF NOT EXISTS eth_user_balances ( + user_id INT PRIMARY KEY, + balance NUMERIC NOT NULL DEFAULT 0, + updated_at TIMESTAMP NOT NULL DEFAULT now(), + created_at TIMESTAMP NOT NULL DEFAULT now() +); +COMMENT ON TABLE eth_user_balances IS 'Per-user AUDIO ERC-20 balance (wei), summed across users.wallet + chain=eth associated_wallets. Pre-aggregated mirror of eth_wallet_balances, maintained by triggers (handle_eth_wallet_balance_change / handle_associated_wallets) and recomputed by update_eth_user_balance(user_id). ETH-side analog of sol_user_balances.'; + +-- Backfill from eth_wallet_balances. eth_wallet_balances.wallet is lowercase +-- hex (PK); associated_wallets.wallet is already lowercased for chain=eth +-- (migration 0207), and users.wallet is lowered to match. UNION ALL (not +-- UNION) mirrors the previous view semantics exactly — if a wallet is both a +-- user's primary and one of their own linked wallets it counts the same way it +-- did before. +INSERT INTO eth_user_balances (user_id, balance, updated_at) +SELECT user_id, SUM(balance), MAX(updated_at) +FROM ( + SELECT u.user_id, ewb.balance, ewb.updated_at + FROM users u + JOIN eth_wallet_balances ewb ON ewb.wallet = LOWER(u.wallet) + WHERE u.is_current = TRUE + + UNION ALL + + SELECT aw.user_id, ewb.balance, ewb.updated_at + FROM associated_wallets aw + JOIN eth_wallet_balances ewb ON ewb.wallet = LOWER(aw.wallet) + WHERE aw.chain = 'eth' + AND aw.is_current = TRUE + AND aw.is_delete = FALSE +) b +GROUP BY user_id +ON CONFLICT (user_id) DO NOTHING; + +COMMIT; diff --git a/ddl/views/v_user_balances.sql b/ddl/views/v_user_balances.sql index 4e170275..29d224f2 100644 --- a/ddl/views/v_user_balances.sql +++ b/ddl/views/v_user_balances.sql @@ -4,47 +4,30 @@ SELECT u.user_id, -- Total ETH-side AUDIO balance in wei: primary users.wallet + all linked - -- chain=eth associated_wallets, summed server-side. eth_wallet_balances - -- already includes ERC-20 balanceOf + staking + delegation (Multicall3 sum - -- in eth/indexer/multicall.go), matching the legacy Python - -- cache_user_balance.py semantic. The IN-list values are LOWER()'d - -- because eth_wallet_balances stores wallets lowercased (eth-indexer - -- normalizes via lowerHex()) while associated_wallets historically - -- accepted mixed-case input. LOWER() is on the *values* being looked - -- up, not on ewb.wallet, so eth_wallet_balances_pkey still drives the - -- lookup — no seq scan. - COALESCE(eth.total_balance, 0)::varchar AS eth_balance, + -- chain=eth associated_wallets, pre-aggregated into eth_user_balances and + -- maintained by handle_eth_wallet_balance_change / handle_associated_wallets + -- triggers (recomputed via update_eth_user_balance). A single PK lookup — + -- it replaces the per-user LATERAL aggregate over eth_wallet_balances that + -- previously made GetUsers scan the whole user base whenever the planner + -- materialized this view instead of parameterizing it. + COALESCE(eub.balance, 0)::varchar AS eth_balance, -- wAUDIO total for the user — sol_user_balances already pre-aggregates -- user_bank PDAs + linked Solana wallets per (user_id, mint), maintained -- by handle_sol_claimable_accounts / update_sol_user_balance triggers. - -- One PK lookup; replaces the two LATERAL subqueries the previous shape - -- of this view used. wAUDIO base units (8 decimals). + -- One PK lookup. wAUDIO base units (8 decimals). COALESCE(sub.balance, 0)::varchar AS sol_balance, GREATEST( - COALESCE(eth.updated_at, '1970-01-01'::timestamp), + COALESCE(eub.updated_at, '1970-01-01'::timestamp), COALESCE(sub.updated_at, '1970-01-01'::timestamp) ) AS updated_at FROM users u LEFT JOIN sol_user_balances sub ON sub.user_id = u.user_id AND sub.mint = '9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM' -LEFT JOIN LATERAL ( - SELECT SUM(ewb.balance) AS total_balance, - MAX(ewb.updated_at) AS updated_at - FROM eth_wallet_balances ewb - WHERE ewb.wallet IN ( - SELECT LOWER(u.wallet) - UNION ALL - SELECT LOWER(aw.wallet) - FROM associated_wallets aw - WHERE aw.user_id = u.user_id - AND aw.chain = 'eth' - AND aw.is_current = TRUE - AND aw.is_delete = FALSE - ) -) eth ON TRUE +LEFT JOIN eth_user_balances eub + ON eub.user_id = u.user_id WHERE u.is_current = TRUE; -COMMENT ON VIEW v_user_balances IS 'Per-user AUDIO/wAUDIO balance totals. One row per current user with eth_balance (wei) and sol_balance (wAUDIO base units, 8 decimals — multiply by 10^10 to compare to wei). eth_balance sums eth_wallet_balances across users.wallet + chain=eth associated_wallets (current, not deleted). sol_balance is sol_user_balances for the wAUDIO mint, already pre-aggregated across user_bank PDAs + linked Solana wallets by handle_sol_claimable_accounts / update_sol_user_balance triggers.'; +COMMENT ON VIEW v_user_balances IS 'Per-user AUDIO/wAUDIO balance totals. One row per current user with eth_balance (wei) and sol_balance (wAUDIO base units, 8 decimals — multiply by 10^10 to compare to wei). eth_balance is eth_user_balances (pre-aggregated across users.wallet + chain=eth associated_wallets, maintained by handle_eth_wallet_balance_change / handle_associated_wallets). sol_balance is sol_user_balances for the wAUDIO mint, pre-aggregated across user_bank PDAs + linked Solana wallets by handle_sol_claimable_accounts / update_sol_user_balance triggers.'; diff --git a/sql/01_schema.sql b/sql/01_schema.sql index dfb89eff..ebfef701 100644 --- a/sql/01_schema.sql +++ b/sql/01_schema.sql @@ -3,8 +3,8 @@ -- --- Dumped from database version 17.9 (Debian 17.9-1.pgdg13+1) --- Dumped by pg_dump version 17.9 (Debian 17.9-1.pgdg13+1) +-- Dumped from database version 17.10 (Debian 17.10-1.pgdg13+1) +-- Dumped by pg_dump version 17.10 (Debian 17.10-1.pgdg13+1) SET statement_timeout = 0; SET lock_timeout = 0; @@ -2137,33 +2137,39 @@ CREATE FUNCTION public.handle_associated_wallets() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE - v_mint varchar; + v_mint varchar; + v_user_id int; + v_wallet text; + v_chain text; BEGIN - -- For INSERT, always run - IF TG_OP = 'INSERT' THEN - FOR v_mint IN - SELECT DISTINCT mint FROM sol_token_account_balances WHERE owner = NEW.wallet - LOOP - PERFORM update_sol_user_balance_mint(NEW.user_id, v_mint); - END LOOP; + IF TG_OP = 'DELETE' THEN + v_user_id := OLD.user_id; + v_wallet := OLD.wallet; + v_chain := OLD.chain; + ELSE + v_user_id := NEW.user_id; + v_wallet := NEW.wallet; + v_chain := NEW.chain; END IF; - -- For UPDATE, only run if is_delete changed - IF TG_OP = 'UPDATE' AND (NEW.is_delete IS DISTINCT FROM OLD.is_delete) THEN - FOR v_mint IN - SELECT DISTINCT mint FROM sol_token_account_balances WHERE owner = NEW.wallet - LOOP - PERFORM update_sol_user_balance_mint(NEW.user_id, v_mint); - END LOOP; + -- Only act on INSERT, DELETE, or an is_delete flip on UPDATE — a no-op + -- metadata UPDATE shouldn't recompute balances (preserves prior behavior). + IF TG_OP = 'UPDATE' AND (NEW.is_delete IS NOT DISTINCT FROM OLD.is_delete) THEN + RETURN NULL; END IF; - -- For DELETE, always run - IF TG_OP = 'DELETE' THEN - FOR v_mint IN - SELECT DISTINCT mint FROM sol_token_account_balances WHERE owner = OLD.wallet - LOOP - PERFORM update_sol_user_balance_mint(OLD.user_id, v_mint); - END LOOP; + -- wAUDIO / artist-coin (sol) balances: recompute every sol mint this wallet + -- holds. For a chain=eth wallet this loop simply finds nothing. + FOR v_mint IN + SELECT DISTINCT mint FROM sol_token_account_balances WHERE owner = v_wallet + LOOP + PERFORM update_sol_user_balance_mint(v_user_id, v_mint); + END LOOP; + + -- AUDIO (ETH-side) balance: linking / unlinking a chain=eth wallet changes + -- the user's aggregated eth_user_balances total. + IF v_chain = 'eth' THEN + PERFORM update_eth_user_balance(v_user_id); END IF; RETURN NULL; @@ -2959,6 +2965,49 @@ END; $$; +-- +-- Name: handle_eth_wallet_balance_change(); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.handle_eth_wallet_balance_change() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + v_user_id int; +BEGIN + -- Skip metadata-only updates (e.g. an updated_at touch with no balance + -- delta) so we don't churn eth_user_balances for no reason. + IF TG_OP = 'UPDATE' AND NEW.balance IS NOT DISTINCT FROM OLD.balance THEN + RETURN NULL; + END IF; + + FOR v_user_id IN + SELECT user_id + FROM users + WHERE LOWER(wallet) = NEW.wallet + AND is_current = TRUE + + UNION + + SELECT user_id + FROM associated_wallets + WHERE LOWER(wallet) = NEW.wallet + AND chain = 'eth' + AND is_current = TRUE + AND is_delete = FALSE + LOOP + PERFORM update_eth_user_balance(v_user_id); + END LOOP; + + RETURN NULL; +EXCEPTION + WHEN OTHERS THEN + RAISE WARNING 'An error occurred in %: %', TG_NAME, SQLERRM; + RETURN NULL; +END; +$$; + + -- -- Name: handle_event(); Type: FUNCTION; Schema: public; Owner: - -- @@ -6572,6 +6621,47 @@ end $$; +-- +-- Name: update_eth_user_balance(integer); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.update_eth_user_balance(p_user_id integer) RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + INSERT INTO eth_user_balances (user_id, balance, updated_at, created_at) + SELECT + p_user_id, + COALESCE(SUM(ewb.balance), 0), + NOW(), + NOW() + FROM eth_wallet_balances ewb + WHERE ewb.wallet IN ( + -- eth_wallet_balances PK is lowercase hex; users.wallet can be + -- mixed-case, associated_wallets are canonical lowercase (0207). + SELECT LOWER(wallet) + FROM users + WHERE user_id = p_user_id + AND is_current = TRUE + AND wallet IS NOT NULL + + UNION ALL + + SELECT LOWER(wallet) + FROM associated_wallets + WHERE user_id = p_user_id + AND chain = 'eth' + AND is_current = TRUE + AND is_delete = FALSE + ) + ON CONFLICT (user_id) + DO UPDATE SET + balance = EXCLUDED.balance, + updated_at = NOW(); +END; +$$; + + -- -- Name: update_sol_user_balance_mint(integer, character varying); Type: FUNCTION; Schema: public; Owner: - -- @@ -8276,6 +8366,25 @@ CREATE TABLE public.eth_indexer_checkpoints ( COMMENT ON TABLE public.eth_indexer_checkpoints IS 'Resumable backfill checkpoints for the eth-indexer (last block whose Transfer events have been processed, keyed by subscription name).'; +-- +-- Name: eth_user_balances; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.eth_user_balances ( + user_id integer NOT NULL, + balance numeric DEFAULT 0 NOT NULL, + updated_at timestamp without time zone DEFAULT now() NOT NULL, + created_at timestamp without time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: TABLE eth_user_balances; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON TABLE public.eth_user_balances IS 'Per-user AUDIO ERC-20 balance (wei), summed across users.wallet + chain=eth associated_wallets. Pre-aggregated mirror of eth_wallet_balances, maintained by triggers (handle_eth_wallet_balance_change / handle_associated_wallets) and recomputed by update_eth_user_balance(user_id). ETH-side analog of sol_user_balances.'; + + -- -- Name: eth_wallet_balances; Type: TABLE; Schema: public; Owner: - -- @@ -10585,6 +10694,29 @@ CREATE TABLE public.user_tips ( ); +-- +-- Name: v_challenge_disbursements; Type: VIEW; Schema: public; Owner: - +-- + +CREATE VIEW public.v_challenge_disbursements AS + SELECT rd.challenge_id, + rd.specifier, + (rd.amount)::text AS amount, + rd.signature, + rd.slot, + rd.created_at, + users.user_id + FROM (public.sol_reward_disbursements rd + JOIN public.users ON (((lower((users.wallet)::text) = rd.recipient_eth_address) AND (users.is_current = true)))); + + +-- +-- Name: VIEW v_challenge_disbursements; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON VIEW public.v_challenge_disbursements IS 'Compatibility view that exposes sol_reward_disbursements in the column shape the API routes used to read from challenge_disbursements. Resolves user_id via the indexer-populated recipient_eth_address (see migration 0172). Join uses LOWER(users.wallet) because the Go indexer stores recipient_eth_address as lowercase.'; + + -- -- Name: v_token_transactions_history; Type: VIEW; Schema: public; Owner: - -- @@ -10708,19 +10840,12 @@ COMMENT ON VIEW public.v_usdc_purchases IS 'Compatibility view exposing sol_purc CREATE VIEW public.v_user_balances AS SELECT u.user_id, - (COALESCE(eth.total_balance, (0)::numeric))::character varying AS eth_balance, + (COALESCE(eub.balance, (0)::numeric))::character varying AS eth_balance, (COALESCE(sub.balance, (0)::bigint))::character varying AS sol_balance, - GREATEST(COALESCE(eth.updated_at, '1970-01-01 00:00:00'::timestamp without time zone), COALESCE(sub.updated_at, '1970-01-01 00:00:00'::timestamp without time zone)) AS updated_at + GREATEST(COALESCE(eub.updated_at, '1970-01-01 00:00:00'::timestamp without time zone), COALESCE(sub.updated_at, '1970-01-01 00:00:00'::timestamp without time zone)) AS updated_at FROM ((public.users u LEFT JOIN public.sol_user_balances sub ON (((sub.user_id = u.user_id) AND (sub.mint = '9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM'::text)))) - LEFT JOIN LATERAL ( SELECT sum(ewb.balance) AS total_balance, - max(ewb.updated_at) AS updated_at - FROM public.eth_wallet_balances ewb - WHERE (ewb.wallet IN ( SELECT lower((u.wallet)::text) AS lower - UNION ALL - SELECT lower((aw.wallet)::text) AS lower - FROM public.associated_wallets aw - WHERE ((aw.user_id = u.user_id) AND (aw.chain = 'eth'::public.wallet_chain) AND (aw.is_current = true) AND (aw.is_delete = false))))) eth ON (true)) + LEFT JOIN public.eth_user_balances eub ON ((eub.user_id = u.user_id))) WHERE (u.is_current = true); @@ -10728,7 +10853,7 @@ CREATE VIEW public.v_user_balances AS -- Name: VIEW v_user_balances; Type: COMMENT; Schema: public; Owner: - -- -COMMENT ON VIEW public.v_user_balances IS 'Per-user AUDIO/wAUDIO balance totals. One row per current user with eth_balance (wei) and sol_balance (wAUDIO base units, 8 decimals — multiply by 10^10 to compare to wei). eth_balance sums eth_wallet_balances across users.wallet + chain=eth associated_wallets (current, not deleted). sol_balance is sol_user_balances for the wAUDIO mint, already pre-aggregated across user_bank PDAs + linked Solana wallets by handle_sol_claimable_accounts / update_sol_user_balance triggers.'; +COMMENT ON VIEW public.v_user_balances IS 'Per-user AUDIO/wAUDIO balance totals. One row per current user with eth_balance (wei) and sol_balance (wAUDIO base units, 8 decimals — multiply by 10^10 to compare to wei). eth_balance is eth_user_balances (pre-aggregated across users.wallet + chain=eth associated_wallets, maintained by handle_eth_wallet_balance_change / handle_associated_wallets). sol_balance is sol_user_balances for the wAUDIO mint, pre-aggregated across user_bank PDAs + linked Solana wallets by handle_sol_claimable_accounts / update_sol_user_balance triggers.'; -- @@ -11382,6 +11507,14 @@ ALTER TABLE ONLY public.eth_indexer_checkpoints ADD CONSTRAINT eth_indexer_checkpoints_pkey PRIMARY KEY (name); +-- +-- Name: eth_user_balances eth_user_balances_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.eth_user_balances + ADD CONSTRAINT eth_user_balances_pkey PRIMARY KEY (user_id); + + -- -- Name: eth_wallet_balances eth_wallet_balances_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -13881,7 +14014,7 @@ CREATE TRIGGER on_associated_wallets AFTER INSERT OR DELETE OR UPDATE ON public. -- Name: TRIGGER on_associated_wallets ON associated_wallets; Type: COMMENT; Schema: public; Owner: - -- -COMMENT ON TRIGGER on_associated_wallets ON public.associated_wallets IS 'Updates sol_user_balances when associated_wallets are added and removed'; +COMMENT ON TRIGGER on_associated_wallets ON public.associated_wallets IS 'Updates sol_user_balances and eth_user_balances when associated_wallets are added and removed'; -- @@ -13968,6 +14101,20 @@ CREATE TRIGGER on_dbc_pool_change AFTER INSERT ON public.sol_meteora_dbc_pools F COMMENT ON TRIGGER on_dbc_pool_change ON public.sol_meteora_dbc_pools IS 'Notifies when DBC pools are added, removed, or updated.'; +-- +-- Name: eth_wallet_balances on_eth_wallet_balance_changes; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER on_eth_wallet_balance_changes AFTER INSERT OR UPDATE ON public.eth_wallet_balances FOR EACH ROW EXECUTE FUNCTION public.handle_eth_wallet_balance_change(); + + +-- +-- Name: TRIGGER on_eth_wallet_balance_changes ON eth_wallet_balances; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON TRIGGER on_eth_wallet_balance_changes ON public.eth_wallet_balances IS 'Recomputes eth_user_balances for affected users whenever an eth wallet balance changes.'; + + -- -- Name: events on_event; Type: TRIGGER; Schema: public; Owner: - -- @@ -14133,7 +14280,7 @@ CREATE TRIGGER on_track_notify_pending_purchase_revalidation AFTER INSERT OR UPD -- Name: user_challenges on_trending_user_challenge; Type: TRIGGER; Schema: public; Owner: - -- -CREATE TRIGGER on_trending_user_challenge AFTER INSERT ON public.user_challenges FOR EACH ROW WHEN (((new.challenge_id)::text = ANY ((ARRAY['tt'::character varying, 'tut'::character varying])::text[]))) EXECUTE FUNCTION public.handle_trending(); +CREATE TRIGGER on_trending_user_challenge AFTER INSERT ON public.user_challenges FOR EACH ROW WHEN (((new.challenge_id)::text = ANY (ARRAY[('tt'::character varying)::text, ('tut'::character varying)::text]))) EXECUTE FUNCTION public.handle_trending(); -- diff --git a/sql/03_migration_tracker.sql b/sql/03_migration_tracker.sql index ad0171a7..e16adc9a 100644 --- a/sql/03_migration_tracker.sql +++ b/sql/03_migration_tracker.sql @@ -3,8 +3,8 @@ -- --- Dumped from database version 17.9 (Debian 17.9-1.pgdg13+1) --- Dumped by pg_dump version 17.9 (Debian 17.9-1.pgdg13+1) +-- Dumped from database version 17.10 (Debian 17.10-1.pgdg13+1) +-- Dumped by pg_dump version 17.10 (Debian 17.10-1.pgdg13+1) SET statement_timeout = 0; SET lock_timeout = 0; @@ -108,8 +108,7 @@ functions/find_track.sql f09431015118f31aa7b4ac49d14e7639 2026-05-27 00:22:35.66 functions/get_user_score.sql 2d5e3003ae1074cbe22ee780dd0ff901 2026-05-27 00:22:35.742438+00 functions/get_user_scores.sql b22e36599e6503649d7cabff0609bc05 2026-05-27 00:22:35.829695+00 functions/handle_artist_coins.sql 21ed1610d80cceca2262efb1338060ed 2026-05-27 00:22:35.905536+00 -functions/handle_associated_wallet.sql 6c9299c3b14bc1db4a578f4ad46e2225 2026-05-27 00:22:35.99395+00 -functions/handle_challenge_disbursements.sql 1bbf82cc035971d828ed96f3ea1a23d6 2026-05-29 22:00:00.000000+00 +functions/handle_challenge_disbursements.sql 1bbf82cc035971d828ed96f3ea1a23d6 2026-05-29 22:00:00+00 functions/handle_chat_blast.sql ccd276957c1b68bfc82fb5a11bac09ee 2026-05-27 00:22:36.155215+00 functions/handle_chat_message.sql 31bebee3d0437133f1f53c2eadb7b391 2026-05-27 00:22:36.229275+00 functions/handle_chat_message_reaction.sql b898313aa8f31c61df7f1e6dd791ef31 2026-05-27 00:22:36.304728+00 @@ -134,7 +133,7 @@ functions/handle_track.sql 89ef5a957b3662310fb30f914aab9ed4 2026-05-27 00:22:37. functions/handle_usdc_purchase.sql c35bccc2789ae0641d413d28a131ef8d 2026-05-27 00:22:37.800109+00 functions/handle_user.sql 169778112e5362ee20af56d9b8f274c5 2026-05-27 00:22:37.955619+00 functions/handle_user_balance_changes.sql 1ae7f99f4f37194cdf27dc3189ebc858 2026-05-27 00:22:38.02983+00 -functions/handle_user_challenges.sql 8a1287bc971c83e7440b88da7c86e93d 2026-05-29 22:00:00.100000+00 +functions/handle_user_challenges.sql 8a1287bc971c83e7440b88da7c86e93d 2026-05-29 22:00:00.1+00 functions/handle_user_tip.sql e4bde4e7e04b0ed8254690959513bd69 2026-05-27 00:22:38.167097+00 functions/is_country_eur.sql c5641d570edb9cd47cd4e38d883e941a 2026-05-27 00:22:38.234372+00 functions/notify_on_row.sql 8b0232ed60eb108aa45f68c6dfe05d59 2026-05-27 00:22:38.304698+00 @@ -148,7 +147,6 @@ views/v_challenge_disbursements.sql 74a0a05af6a02f82af3695d5c3ade45c 2026-05-27 views/v_usdc_purchases.sql 322a527e132aed647c39b70d63aa6b51 2026-05-27 00:22:39.061667+00 preflight/0001_initial_block.sql 1e59cca66b2208eda67a87ac0d09b67d 2026-05-27 00:22:39.249328+00 migrations/0204_backfill_eth_wallet_balances_tracked.sql d8b752794c42edb94f0d43633e14208c 2026-05-28 00:56:10.178339+00 -views/v_user_balances.sql ff6db739c1a77101021d4629647a44df 2026-05-28 17:28:07.629633+00 migrations/0205_sol_transfer_memo_tables.sql 49aa6bdf68df733710e7820153a78393 2026-05-28 19:54:42.181969+00 migrations/0206_backfill_sol_transfer_memo_types.sql 8b63b2e420c94b740a15e7f1fbd02ae7 2026-05-28 19:54:42.266989+00 functions/handle_usdc_withdrawal.sql f590eb5d53ec82c63d3d2d7b1913cbef 2026-05-28 19:54:42.61986+00 @@ -157,11 +155,24 @@ migrations/0207_canonicalize_associated_wallets_eth.sql 83e88b1102c1bc5e2526a919 migrations/0203_seed_phase_1_challenges.sql b027784464de897b26d4b420ca51a970 2026-05-29 16:22:36.535877+00 migrations/0204_seed_phase_2_challenges.sql 168a6d57c056e2e8f7fe14c36fc1c367 2026-05-29 16:22:36.811563+00 migrations/0205_seed_phase_3_challenges.sql dc2a08647a63c0e355c6a3b2cc23a8bd 2026-05-29 16:22:37.200322+00 -migrations/0208_seed_challenge_checkpoints.sql ed11876806de4dd1d80b389894b4db45 2026-05-29 16:22:38.000000+00 -migrations/0209_user_events_blocknumber_idx.sql 19ed339385266f28e83399125b6593df 2026-05-29 19:30:00.000000+00 -migrations/0210_notification_cooldown_partial_gin.sql 7156a9b6e236e17acef7d6b91cb1291b 2026-05-29 19:30:00.100000+00 -migrations/0211_seed_phase_3_user_event_checkpoints.sql e37093a8ba1d1a4a3bcca7f98b612be2 2026-05-29 19:30:00.200000+00 +migrations/0208_seed_challenge_checkpoints.sql ed11876806de4dd1d80b389894b4db45 2026-05-29 16:22:38+00 +migrations/0209_user_events_blocknumber_idx.sql 19ed339385266f28e83399125b6593df 2026-05-29 19:30:00+00 +migrations/0210_notification_cooldown_partial_gin.sql 7156a9b6e236e17acef7d6b91cb1291b 2026-05-29 19:30:00.1+00 +migrations/0211_seed_phase_3_user_event_checkpoints.sql e37093a8ba1d1a4a3bcca7f98b612be2 2026-05-29 19:30:00.2+00 migrations/0212_drop_v_challenge_disbursements.sql 90097355681a3a9b3cde2586511f0f16 2026-05-29 23:22:07.541889+00 +migrations/0204_fix_challenge_disbursements_case_sensitivity.sql a73b4830e81426df9a6f8aeaf61ff861 2026-05-30 01:37:22.201761+00 +migrations/0213_add_eth_user_balances.sql 111eb6a0ba9326bacacbc3c1726d19ff 2026-05-30 01:37:22.343704+00 +functions/handle_associated_wallet.sql c069cb48c574cc01689ab45f6e036443 2026-05-30 01:37:22.553815+00 +functions/handle_comment_mention.sql 518e297c3053c240ec1cb804ff4228ee 2026-05-30 01:37:22.69354+00 +views/v_user_balances.sql 9ee4060617250285c419e5caf6cb5fab 2026-05-30 01:37:23.77109+00 +functions/handle_comment_notification.sql f9ee033ab1fe8ee8dc3437e328873242 2026-05-30 01:37:22.773681+00 +functions/handle_comment_reaction.sql 8153e3cdb922265857b6beaf20d29733 2026-05-30 01:37:22.856663+00 +functions/handle_comment_thread.sql 6eb74eb92cf3a01421498df96c6832f3 2026-05-30 01:37:22.955997+00 +functions/handle_eth_wallet_balance_change.sql 3e31160b4bc55e951d9dfa4d994c180b 2026-05-30 01:37:23.054573+00 +functions/handle_fan_club_text_post.sql 531bf682bcfd67c6866faf8ccdf7603b 2026-05-30 01:37:23.160142+00 +functions/handle_tastemaker.sql 04690b53bd094a59717ef3a5b5d2c0a0 2026-05-30 01:37:23.34542+00 +functions/handle_trending.sql b754ca4670313bbd331102244f6182e5 2026-05-30 01:37:23.437507+00 +functions/update_eth_user_balance.sql 52d3ab427477d0a49ce68bc1ee270aec 2026-05-30 01:37:23.595691+00 \.