Skip to content

Switch challenge reads to sol_reward_disbursements#809

Open
rickyrombo wants to merge 2 commits into
mainfrom
mp/challenges-read-from-sol-indexer
Open

Switch challenge reads to sol_reward_disbursements#809
rickyrombo wants to merge 2 commits into
mainfrom
mp/challenges-read-from-sol-indexer

Conversation

@rickyrombo
Copy link
Copy Markdown
Contributor

Summary

Routes that joined challenge_disbursements (Python-indexer-written) now read from a new compatibility view v_challenge_disbursements backed by the Go indexer's sol_reward_disbursements table. Notifications continue to be created via a new trigger on the sol_* table. Python keeps dual-writing the legacy table for now.

This is a deliberate wedge: only the challenges read path is moved. Other Solana-derived reads (transactions history, tips, purchases, withdrawals) are unchanged and stay on the legacy Python-written tables.

In scope (this PR)

Schema

  • New migration ddl/migrations/0198_v_challenge_disbursements.sql:
    • Adds created_at TIMESTAMP DEFAULT NOW() to sol_reward_disbursements (the column did not exist; backfilled from challenge_disbursements.created_at for matching signatures).
    • Creates view v_challenge_disbursements (challenge_id, specifier, amount, signature, slot, created_at, user_id) over sol_reward_disbursements JOIN users ON users.wallet = recipient_eth_address.
  • sql/01_schema.sql updated so sqlc parses the new column + view.

Trigger

  • ddl/functions/handle_challenge_disbursements.sql: adds handle_sol_reward_disbursement trigger on sol_reward_disbursements that mirrors the legacy notification-creation logic. Dedupes via the same group_id, so it dual-fires safely with the existing trigger during cutover. Also emits pg_notify('challenge_disbursed', ...) for future consumers (no subscriber wired yet).

Go readers — now point at the view

  • api/v1_challenges_disbursements.go
  • api/v1_challenges_undisbursed.go
  • api/v1_users_challenges.go
  • api/v1_challenges_info.go
  • api/v1_coins_post_redeem.go
  • api/dbv1/queries/get_undisbursed_challenges.sql (+ regenerated get_undisbursed_challenges.sql.go and models.go via sqlc generate)

Test fixtures

  • database/seed.go — added sol_reward_disbursements base row.
  • api/v1_challenges_disbursements_test.go, api/v1_challenges_info_test.go — switched to seeding users + sol_reward_disbursements with recipient_eth_address matching users.wallet so the view resolves user_id.

Not in scope / still to be done

These were considered during planning but explicitly deferred:

  • Python decommission. index_rewards_manager continues to poll the Reward Manager program and write challenge_disbursements, reward_manager_txs, and audio_transactions_history rewards rows. The legacy trigger on challenge_disbursements stays active; the new trigger's dedupe handles overlap. To be addressed when the discovery-provider service is dropped entirely.
  • /v1/challenges/{id}/attest port. Still served from Python. Anti-abuse oracle attestation signing needs to be ported to Go; not done in this PR per scope discussion.
  • pg_notify('challenge_disbursed', ...) consumer. Trigger emits the event but nothing subscribes. Reserved for the future Python decommission (or a Go-side challenge-event-bus port).
  • audio_transactions_history rewards rows (USER_REWARD / TRENDING_REWARD). Reads of audio-transactions history still go through the legacy table; rewards typing in a future typed-history view is a separate workstream.
  • user_balances refresh. index_rewards_manager calls enqueue_immediate_balance_refresh on disbursement. Until user_balances readers are audited and migrated to sol_user_balances, the Python refresh hook keeps running.
  • Other Solana domains. Tips (user_tips, aggregate_user_tips), purchases (usdc_purchases), transactions history (audio + usdc), withdrawals, and Stripe/Coinbase/Coinflow top-up vendor detection are all untouched.

Test plan

  • go build ./... clean (verified locally).
  • go test ./api/ -run 'TestGetChallengeDisbursements|TestV1ChallengesInfo' against a running test postgres on :21300.
  • Verify in a dev DB that a row inserted into sol_reward_disbursements produces a notification row with type=challenge_reward and the expected group_id, and that a duplicate insert is a no-op.
  • Verify SELECT COUNT(*) FROM v_challenge_disbursements is consistent with SELECT COUNT(*) FROM challenge_disbursements modulo rows whose recipient_eth_address doesn't map to a current user.
  • Spot-check /v1/challenges/disbursements, /v1/users/{id}/challenges, /v1/challenges/undisbursed, /v1/challenges/{id}/info against a populated DB.
  • Confirm /v1/coins/{mint}/redeem idempotency check still rejects a second redemption for the same code+specifier.

🤖 Generated with Claude Code

@rickyrombo rickyrombo force-pushed the mp/challenges-read-from-sol-indexer branch from efdaa39 to 6bd6ee5 Compare May 14, 2026 20:31
Routes that joined challenge_disbursements now read from a compatibility
view v_challenge_disbursements over the new Solana indexer's
sol_reward_disbursements table, with user_id resolved via
recipient_eth_address -> users.wallet. Python continues to dual-write
challenge_disbursements until the discovery-provider service is
decommissioned in a future change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@rickyrombo rickyrombo force-pushed the mp/challenges-read-from-sol-indexer branch from 6bd6ee5 to e060a83 Compare May 14, 2026 20:40
…sements

Restores the indexes the legacy challenge_disbursements had on user_id
and created_at. v_challenge_disbursements inlines into the route SQL, so
filters like cd.user_id = X push down to users.wallet -> probe
sol_reward_disbursements.recipient_eth_address, which would otherwise
seq-scan. Same for the default created_at sort on
/v1/challenges/disbursements and the date-range filter in
/v1/challenges/{id}/info.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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