Skip to content

fix(purchases): resolve splits payout wallets historically#824

Merged
rickyrombo merged 1 commit into
mainfrom
mp/fix-purchases-payout-wallet-history
May 19, 2026
Merged

fix(purchases): resolve splits payout wallets historically#824
rickyrombo merged 1 commit into
mainfrom
mp/fix-purchases-payout-wallet-history

Conversation

@rickyrombo
Copy link
Copy Markdown
Contributor

Summary

The `v_usdc_purchases` view was joining the seller's current payout wallet (`users.spl_usdc_payout_wallet`). Anyone who changed payout wallets after a sale lost their `splits[*].user_id` attribution: the on-chain `sol_payments.to_account` (the wallet at purchase time) no longer equals the row's current `spl_usdc_payout_wallet`, so the LEFT JOIN produces NULL.

Two user-visible consequences:

  • `/v1/users/{id}/purchases/download` shows `paid_to_artist: 0` because the route code matches splits to the seller by `split.user_id == sellerUserId`.
  • `/v1/users/{id}/purchases` is slow — `users.spl_usdc_payout_wallet` isn't indexed, so the per-payment LEFT JOIN turns into a Seq Scan on `users` × splits × purchases × pages.

Fix

Replace the current-state join with a LATERAL lookup on `user_payout_wallet_history` filtered by `block_timestamp <= sp.created_at`. This mirrors Python's `add_wallet_info_to_splits` (`discovery-provider/src/queries/get_extended_purchase_gate.py:171`) which is what populated the legacy `usdc_purchases.splits` JSON.

Plus an index on `user_payout_wallet_history (spl_usdc_payout_wallet, block_timestamp)` — the PK is keyed `(user_id, block_timestamp)`, which is the wrong direction for our lookup (we know the wallet, want the user).

The fallback chain (`sol_claimable_accounts` → `users.wallet`) is unchanged. It still handles users who never set a custom payout wallet (their `to_account` is the default USDC user-bank PDA, which is stable over time).

Tests

New `api/v_usdc_purchases_splits_test.go` covers the four splits-resolution gotchas:

  • Payout wallet changed after purchase — seller's old splits still attribute correctly via history.
  • Multiple historical wallets — view picks the one current at purchase time, not the most recent.
  • No payout wallet ever set — falls back to the `sol_claimable_accounts` chain.
  • Unowned wallet (network share) — `splits[*].user_id` is null instead of an accidental match.

Incidental cleanups

  • `database.seed`: added a `blocks` baseRow and put `"blocks"` first in the `entityTables` ordered list (`user_payout_wallet_history` has an FK on `blocks.number`, so without the explicit ordering the seed sometimes inserted history rows before their referenced block).
  • Existing tests that previously set `users.spl_usdc_payout_wallet` to fake the old current-state lookup now also seed `user_payout_wallet_history` so they still resolve under the new join.

Test plan

  • `go test ./api/ -run 'TestV1UsersPurchases|TestV1UsersSales|TestExploreBestSelling|TestVUsdcPurchasesSplits' -count=1` passes locally
  • After deploy, hit the slow URL again — should be back to normal:
    `/v1/users/q7l8a/purchases?limit=50&offset=0&sort_direction=desc&sort_method=date`
  • EXPLAIN ANALYZE shows Index Scan on `user_payout_wallet_history_wallet_idx` instead of Seq Scan on `users`

🤖 Generated with Claude Code

v_usdc_purchases was joining users.spl_usdc_payout_wallet, i.e. the
seller's *current* payout wallet. Anyone who changed wallets after a
sale lost their splits[*].user_id attribution (resolved to NULL),
which the route then couldn't match to the seller -- showing
paid_to_artist=0 on the purchases page and likely contributing to the
view's slow plan as well (sequential scan on unindexed
users.spl_usdc_payout_wallet, per payment, per result row).

Fix: replace the current-state users join with a LATERAL lookup on
user_payout_wallet_history filtered by block_timestamp <= sp.created_at.
Mirrors what Python's add_wallet_info_to_splits does in the legacy
discovery-provider path.

Add an index on user_payout_wallet_history (spl_usdc_payout_wallet,
block_timestamp) -- the PK is keyed (user_id, block_timestamp), which
is the wrong direction for our lookup (we know the wallet, want the
user).

Tests cover the gotchas:
- seller changes payout wallet after a purchase: historical match
- multiple historical wallets: picks the one current at purchase time
- seller never set custom payout: sol_claimable_accounts fallback
- unowned wallet (network share): resolves to null

A couple of incidental cleanups along the way:
- database.seed: add a blocks baseRow and seed "blocks" before
  user_payout_wallet_history (which has an FK on blocks.number)
- existing tests that set users.spl_usdc_payout_wallet to fake the old
  current-state lookup now also seed user_payout_wallet_history

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@rickyrombo rickyrombo merged commit 8e7c6b6 into main May 19, 2026
5 of 6 checks passed
@rickyrombo rickyrombo deleted the mp/fix-purchases-payout-wallet-history branch May 19, 2026 00:01
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.

2 participants