Problem
register-by-wallet and onboard are two separate API routes that both upsert the same users row. When called concurrently (e.g., wallet connect triggers register while user clicks refresh), they race:
- Both query for existing user separately
- Both attempt INSERT — first succeeds, second gets 23505 (unique violation)
- Second falls back to UPDATE but uses different conflict keys (FID vs primary_address)
- Result: partial updates, corrupted row with mixed data from both routes
Root Cause
Both routes use INSERT-then-catch-23505-then-UPDATE pattern with different conflict resolution:
register-by-wallet: updates by fid or primary_address
onboard: updates by existingUser.id
Fix
- Unify the upsert logic — both routes should use the same conflict resolution strategy, preferably
.upsert() with explicit conflict keys
- Use
existingUser.id as the primary update key in both routes (most reliable — UUID, always unique)
- Add a shared
upsertUser() helper in lib/actions.ts that both routes call, ensuring consistent behavior
- Handle all user types correctly: FID users, wallet-only users, agent users
Files to modify
src/app/api/user/register-by-wallet/route.ts — use shared upsert logic
src/app/api/user/onboard/route.ts — use shared upsert logic
lib/actions.ts or new lib/user-upsert.ts — shared upsert helper
Branch
task/633-upsert-race-fix
Acceptance criteria
Self-Verification (T3)
Problem
register-by-walletandonboardare two separate API routes that both upsert the sameusersrow. When called concurrently (e.g., wallet connect triggers register while user clicks refresh), they race:Root Cause
Both routes use INSERT-then-catch-23505-then-UPDATE pattern with different conflict resolution:
register-by-wallet: updates byfidorprimary_addressonboard: updates byexistingUser.idFix
.upsert()with explicit conflict keysexistingUser.idas the primary update key in both routes (most reliable — UUID, always unique)upsertUser()helper inlib/actions.tsthat both routes call, ensuring consistent behaviorFiles to modify
src/app/api/user/register-by-wallet/route.ts— use shared upsert logicsrc/app/api/user/onboard/route.ts— use shared upsert logiclib/actions.tsor newlib/user-upsert.ts— shared upsert helperBranch
task/633-upsert-race-fixAcceptance criteria
Self-Verification (T3)
npm run dev, connect a NEW wallet (not in DB yet)npm run build— no errorsnpm run typecheck— no type errors