Epic: #1229 · Spec: §3.2, §3.3, §6.3 · Type: CODE · Estimate: 0.7 day · Depends on: T2.3 + T2.4a
Scope
Three new endpoints, all use lib/airdrop/siwe-verify.ts for auth. All accept { message, signature, ... } in body.
1. POST /api/airdrop/verify-x-handle
Body: { message, signature, username }
Action: Calls lookupXUser(username) → returns { display_name, avatar_url, follower_count, x_user_id, bio_snippet } for client to display in confirmation card. Does NOT persist to pl_activations.
2. POST /api/airdrop/confirm-x-handle
Body: { message, signature, username }
Action:
- Verifies SIWE → recovers address
- Server re-looks-up the username via
lookupXUser (does NOT trust client-supplied x_user_id — RE1 Critical 2 fix)
- Derives
x_user_id server-side from the fresh lookup
- UPSERTs
pl_activations row with address + x_handle (lower-cased) + x_user_id + x_handle_confirmed_at = NOW()
- UNIQUE INDEX on
LOWER(x_handle) WHERE confirmed catches 409 conflicts → return 409
- Also generates
pl_referral_codes entry on first call per address if not exists
- Twitterapi.io outage path (R16): if
lookupXUser throws, write x_handle only with x_handle_confirmed_at = NULL (pending state) — does NOT acquire UNIQUE lock
3. POST /api/airdrop/x-follow-click
Body: { message, signature }
Action:
- Verifies SIWE → recovers address
- Records
pl_activations.x_follow_at = NOW()
- Sets
activated_at = NOW() if SIWE + handle confirmed + this click together complete activation
Acceptance
Dependencies
T2.3 (lookupXUser), T2.4a (siwe-verify)
Epic: #1229 · Spec: §3.2, §3.3, §6.3 · Type: CODE · Estimate: 0.7 day · Depends on: T2.3 + T2.4a
Scope
Three new endpoints, all use
lib/airdrop/siwe-verify.tsfor auth. All accept{ message, signature, ... }in body.1.
POST /api/airdrop/verify-x-handleBody:
{ message, signature, username }Action: Calls
lookupXUser(username)→ returns{ display_name, avatar_url, follower_count, x_user_id, bio_snippet }for client to display in confirmation card. Does NOT persist topl_activations.2.
POST /api/airdrop/confirm-x-handleBody:
{ message, signature, username }Action:
lookupXUser(does NOT trust client-suppliedx_user_id— RE1 Critical 2 fix)x_user_idserver-side from the fresh lookuppl_activationsrow withaddress+x_handle(lower-cased) +x_user_id+x_handle_confirmed_at = NOW()LOWER(x_handle) WHERE confirmedcatches 409 conflicts → return 409pl_referral_codesentry on first call per address if not existslookupXUserthrows, writex_handleonly withx_handle_confirmed_at = NULL(pending state) — does NOT acquire UNIQUE lock3.
POST /api/airdrop/x-follow-clickBody:
{ message, signature }Action:
pl_activations.x_follow_at = NOW()activated_at = NOW()if SIWE + handle confirmed + this click together complete activationAcceptance
verify-x-handlereturns lookup data without persistingconfirm-x-handleserver-side re-lookup verified (test by sending bogusx_user_idin body — server ignores it)x-follow-clickupdatesx_follow_at+ setsactivated_atwhen all 3 conditions metDependencies
T2.3 (
lookupXUser), T2.4a (siwe-verify)