Parent: #877
Depends on: #878, #879
Scope
Daily check-in via Sign-In With Ethereum (SIWE). Streak tier applies a multiplier on all other PL earnings (not standalone points).
Streak Tiers
| Streak |
Boost |
| 0-6 days |
+0% |
| 7+ days |
+5% |
| 14+ days |
+10% |
| 30+ days |
+20% |
| 50+ days |
+30% |
| 100+ days |
+50% (cap) |
API
POST /api/airdrop/checkin
Request: SIWE signed message + signature from connected wallet
Logic:
- Verify SIWE signature → extract wallet address
- Query
pl_streaks for this address
- Reject if same calendar day (UTC) as
last_checkin
- Reject if
last_checkin was less than 30 minutes ago
- If
last_checkin was the previous calendar day → increment current_streak
- If 2+ days missed → drop one tier:
- streak 100+ → set to 100
- streak 50-99 → set to 50
- streak 30-49 → set to 30
- streak 14-29 → set to 14
- streak 7-13 → set to 7
- streak 1-6 → set to 0
- Update
last_checkin = NOW(), update longest_streak if applicable
- Return:
{ streak, boostPercent, nextTier, checkedInToday }
Streak Helper
lib/airdrop/streak.ts:
getStreakBoost(streak: number): number — returns multiplier (e.g., 0.20 for 30d streak)
dropOneTier(streak: number): number — returns new streak after one missed day
getNextTier(streak: number): { days: number, boost: number } | null
Files
src/app/api/airdrop/checkin/route.ts
lib/airdrop/streak.ts
Acceptance Criteria
Parent: #877
Depends on: #878, #879
Scope
Daily check-in via Sign-In With Ethereum (SIWE). Streak tier applies a multiplier on all other PL earnings (not standalone points).
Streak Tiers
API
POST /api/airdrop/checkinRequest: SIWE signed message + signature from connected wallet
Logic:
pl_streaksfor this addresslast_checkinlast_checkinwas less than 30 minutes agolast_checkinwas the previous calendar day → incrementcurrent_streaklast_checkin = NOW(), updatelongest_streakif applicable{ streak, boostPercent, nextTier, checkedInToday }Streak Helper
lib/airdrop/streak.ts:getStreakBoost(streak: number): number— returns multiplier (e.g., 0.20 for 30d streak)dropOneTier(streak: number): number— returns new streak after one missed daygetNextTier(streak: number): { days: number, boost: number } | nullFiles
src/app/api/airdrop/checkin/route.tslib/airdrop/streak.tsAcceptance Criteria