Skip to content

[#1242] Add X handle + follow activation endpoints#1280

Merged
realproject7 merged 3 commits into
mainfrom
task/1242-x-handle-endpoints
May 26, 2026
Merged

[#1242] Add X handle + follow activation endpoints#1280
realproject7 merged 3 commits into
mainfrom
task/1242-x-handle-endpoints

Conversation

@realproject7

Copy link
Copy Markdown
Owner

Summary

Three new SIWE-authenticated endpoints for the activation flow:

  • POST /api/airdrop/verify-x-handle — calls lookupXUser(username), returns preview data (display name, avatar, followers, bio). No DB write.
  • POST /api/airdrop/confirm-x-handle — server-side re-lookup (ignores client-supplied x_user_id per RE1 Critical 2), UPSERTs pl_activations with handle + user ID + confirmed timestamp. UNIQUE index conflict → 409. R16 graceful degrade: twitterapi.io down → writes pending row without UNIQUE lock. Auto-generates pl_referral_codes entry on first confirm.
  • POST /api/airdrop/x-follow-click — records x_follow_at, sets activated_at when handle confirmed + follow click complete the activation.

Auth

All endpoints use verifySiweRequest from lib/airdrop/siwe-verify.ts. Invalid/expired signatures → 401.

Version

1.31.0 → 1.32.0 (feature)

🤖 Generated with Claude Code

Three SIWE-authenticated activation endpoints:
- verify-x-handle: lookupXUser preview without DB write
- confirm-x-handle: server-side re-lookup + UPSERT pl_activations
  with UNIQUE conflict → 409, R16 graceful degrade on API outage,
  auto-generates referral code on first confirm
- x-follow-click: records x_follow_at, sets activated_at when
  handle confirmed + follow click complete activation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
plotlink Ignored Ignored May 26, 2026 6:09am

Request Review

@realproject7 realproject7 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@re2 review — APPROVE ✅

Checked against issue #1242 acceptance criteria:

Criterion Status
All 3 endpoints reject invalid/expired sigs with 401 ✅ via verifySiweRequest
verify-x-handle returns lookup data without persisting ✅ no DB imports
confirm-x-handle server-side re-lookup (ignores client x_user_id) ✅ RE1 Critical 2
UNIQUE conflict → 409 ✅ code 23505 check
R16 graceful degrade: twitterapi.io down → pending row ✅ try/catch, null confirmedAt
x-follow-click sets activated_at when conditions met
Version 1.31.0 → 1.32.0 (feature)

Endpoint-by-endpoint review:

verify-x-handle — Pure lookup, no DB. lookupXUser error propagates as 500 (acceptable for preview — no degradation needed unlike confirm).

confirm-x-handle — Security-correct flow:

  • Server calls lookupXUser(username) directly — never trusts client-supplied x_user_id
  • R16 path: twitterapi.io failure → writes row with x_handle_confirmed_at = null, which avoids acquiring the partial UNIQUE index lock
  • UPSERT on address conflict, 23505 catches cross-wallet handle conflicts → 409
  • Auto-generates referral code via nanoid(8) on first confirm

x-follow-click — Correct activation gate:

  • Requires existing activation row (400 if missing)
  • Sets activated_at only when handle confirmed + not already activated
  • Idempotent: re-clicking updates x_follow_at but doesn't re-set activated_at

Security notes:

  • All endpoints SIWE-authenticated
  • Username lowercased before storage
  • No raw user input in queries (Supabase client parameterizes)
  • No sensitive data leaked in responses

No blocking issues found.

@project7-interns project7-interns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Summary

The endpoint structure is in place, but confirm-x-handle conflates a normal username-not-found response with the R16 outage path and the PR has no tests for the required endpoint behavior. The package lock metadata is also stale after the version bump.

Findings

  • [high] confirm-x-handle writes a pending activation row when lookupXUser() returns null. R16 only allows the pending row when twitterapi.io throws/down; a 404/not-found username should not be persisted as a pending handle. As written, any signed wallet can reserve arbitrary nonexistent handles in pl_activations with x_handle_confirmed_at = NULL, and later behavior depends on stale pending data.

    • File: src/app/api/airdrop/confirm-x-handle/route.ts:35
    • Suggestion: Distinguish null from thrown lookup errors. Return 404/no write for null; only write pending state in the catch/outage path.
  • [medium] The PR adds no tests for the #1242 acceptance cases. These routes have important mocked boundaries: SIWE auth, twitterapi lookup success/null/throw, Supabase upsert/unique conflict, and activation update. Without route tests, regressions in invalid-signature 401s, verify-without-persisting, bogus x_user_id ignore, 409 conflict handling, R16 pending behavior, and x-follow-click activation would pass unnoticed.

    • File: src/app/api/airdrop/verify-x-handle/route.ts:5
    • Suggestion: Add route tests for all three endpoints with mocked verifySiweRequest, lookupXUser, and Supabase client covering the acceptance checklist.
  • [medium] package-lock.json was not updated after package.json moved to 1.32.0; the lockfile root still reports 1.31.0.

    • File: package-lock.json:3
    • Suggestion: Regenerate or update the lockfile root version to match package.json.

Decision

Requesting changes until not-found handles are not persisted, the acceptance behavior is covered by route tests, and the lockfile metadata is consistent. CI was still pending at review time.

confirm-x-handle now returns 404 for nonexistent X users (null) and
only writes pending rows on API outage (throw). 12 tests cover:
invalid sig → 401, verify without persist, server re-lookup ignores
bogus x_user_id, 404 for ghost users, 409 UNIQUE conflict, R16
pending row, follow-click activation, no re-activation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@project7-interns project7-interns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Summary

The prior behavioral issues are addressed: nonexistent X users now return 404 without an upsert, route tests were added for the #1242 acceptance cases, and the lockfile version is fixed. One cleanup remains in the updated route before approval.

Findings

  • [medium] apiDown is assigned in the twitterapi.io catch path but never read. This leaves dead code in the route and is likely to fail the repo lint/typecheck job via unused-variable rules.
    • File: src/app/api/airdrop/confirm-x-handle/route.ts:34
    • Suggestion: Remove apiDown entirely, or use it in the response if the pending/outage path needs to expose that state.

Decision

Requesting changes until the unused variable is removed or used. CI was still pending at review time, and local lint/typecheck could not be run because this checkout does not have dependencies installed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@project7-interns project7-interns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE

Summary

The requested changes are addressed. confirm-x-handle now returns 404 without persisting when lookup returns null, only writes pending rows on lookup throw, route tests cover the #1242 acceptance paths, and package-lock metadata matches the package version.

Findings

  • None.

Decision

Approved. lint-and-typecheck and e2e are passing on the latest head.

@realproject7 realproject7 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@re2 re-review — APPROVE ✅

Verified fixes for @re1's findings:

  • [high] null vs throw — Fixed: lookupXUser returning null now correctly returns 404 with no DB write. Only the catch path (API throw) writes a pending row for R16 degradation. ✅
  • [medium] Tests — 12 route tests added across all 3 endpoints. ✅
  • [medium] Lockfile — Synced to 1.32.0. ✅
  • Dead code — Unused apiDown variable removed. ✅

CI is fully green (lint-and-typecheck + e2e both pass). No remaining issues.

@realproject7
realproject7 merged commit df3fb80 into main May 26, 2026
4 checks passed
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