DEV Community

Cover image for I put my fan streak on Solana so nobody could reset it
Ishant gupta
Ishant gupta

Posted on

I put my fan streak on Solana so nobody could reset it

DEV Weekend Challenge: Passion Edition Submission

This is a submission for Weekend Challenge: Passion Edition

What I Built

Loyalty Ledger — a fan loyalty tracker where your check-in streak, badges, and history live on Solana instead of some app's database.

Live app: https://loyalty-ledger-blond.vercel.app

Here's the problem I kept coming back to. Every sports app wants you to check in, engage, "prove your loyalty" — collect points, build a streak, unlock a badge. And every single one of them throws that history away the moment you stop using it. Switch apps and your streak resets to zero. Get banned, or the app shuts down, or they just decide to wipe inactive accounts — your history is gone, because it was never really yours. It was a number in someone else's database, and they could reset it, inflate it, or delete it whenever they felt like it, and you'd have zero recourse.

That felt like a weirdly solvable problem to just... not solve. We figured out how to make ownership portable for money, for domain names, for digital art. But "I've supported Argentina since 2019" still lives and dies inside one company's backend.

So the scope for the weekend was deliberately narrow: prove one fan's loyalty to one team, for real, end to end, rather than sketch out ten features that are all half-fake. You connect a wallet, pick a sport and team, and check in. FIFA World Cup is the fully working path — that check-in sends a real transaction that creates or updates a program-owned account, not a row in my database. Your streak count, your badge tier, the actual badge tokens — none of it exists anywhere I control.

Once that core loop worked, I built out the rest of the identity around it: a Fan Passport that shows your streak, a derived "Fan Score," your tier (Rookie → Devoted → Veteran → Legend), a progress bar toward the next tier, an achievements grid with locked/unlocked states, a recent-activity feed pulled from real on-chain transaction history, and a leaderboard ranking real fans by real streaks. There's also a "Demo Preview" toggle that shows the passport filled in with sample numbers — clearly labeled as sample data — because a screen full of zeros doesn't demo well, and I'd rather be upfront about that than pretend it's real.

demo

NBA and "Other International Sport" are in the app too, using the exact same UI and flow, but they're honestly stubbed — sample fixtures, no chain writes, and the app says so directly rather than hiding it. I'd rather ship one path that's completely real than three that are all a little bit fake.

Demo

Live app: https://loyalty-ledger-blond.vercel.app
github : https://github.com/ishantgupta30/Loyalty-Ledger

📸 SCREENSHOT: Fan Passport with real (or Demo Preview) data —<br>
     streak, Fan Score, tier stars, achievements. Your hero image.

To actually try it:

  1. Install Phantom if you don't have it, and switch it to Devnet — Settings → Developer Settings → Change Network → Devnet. This is the step almost everyone misses first.
  2. Grab free devnet SOL from the faucet to cover transaction fees, which run a fraction of a cent per check-in. No real money touches this project anywhere.
  3. Open the app and click Connect Wallet, top right.
  4. Pick FIFA World Cup and a team.
  5. Hit check-in and approve the transaction Phantom pops up.
  6. Watch the streak and Fan Score update, confetti fire, and — if you crossed a threshold — a badge-unlocked toast appear.
  7. Scroll down to see the full Fan Passport fill in: tier, progress bar, achievements, recent activity, and your real rank on that team's leaderboard.
  8. Once you've crossed tier 1 (3 check-ins), hit Claim Badge — it mints an actual SPL token to your wallet. Check Phantom's token list afterward; it's sitting there, permanently, not just a UI element.

 📸 SCREENSHOT: Phantom's transaction-approval popup mid check-in —<br>
     the single most convincing frame, proves it's a real signed tx

Code

How I Built It

The account design

The core piece is a small Anchor program. Every fan gets a PDA — a Program Derived Address, an account owned by the program itself, not by me — keyed to (wallet, sport, team):

pub struct FandomRecord {
    pub wallet: Pubkey,
    pub streak_count: u32,
    pub last_checkin_ts: i64,
    pub bump: u8,
    pub highest_tier_claimed: u8,
    #[max_len(32)]
    pub sport: String,
    #[max_len(32)]
    pub team: String,
}
Enter fullscreen mode Exit fullscreen mode

Check-in doesn't touch a database. It sends a check_in instruction that either creates this account (first check-in) or updates it (extends the streak, or resets it if too much time passed between check-ins). That's the entire point of the project in one line: the streak isn't something my frontend can fake, inflate, or quietly reset, because it isn't my frontend's data to begin with. Anyone — not just this app — can read the account directly and verify exactly what it says.

SCREENSHOT: Solana Explorer page for a confirmed check_in tx

The leaderboard problem

This was the part that took longer than expected. My first cut of the account only stored wallet, streak_count, and last_checkin_ts — sport and team lived purely in the PDA seeds. Which works fine if you already know a wallet and want to look up their record, but it's a dead end if you want to ask "who are the top Argentina fans." Seeds let you derive one specific account when you already know the inputs; you can't run that backwards to enumerate every account matching a team.

So I added sport and team as actual fields on the account data, not just seed inputs. That meant a full account-struct change, a rebuild, and a redeploy — annoying mid-weekend, but it's what makes a real getProgramAccounts call (filtered client-side by sport/team) able to answer "rank every Argentina fan by streak" using actual chain state instead of a cached guess or a fabricated list.

Why Solana, specifically

I wanted this to be a project where pulling out the blockchain would actually break something, not just remove a buzzword from the pitch. Check-ins are frequent and individually worthless — potentially one per match, per fan, across a lot of fans over a tournament. That only works as a real product, not a novelty, if each check-in is near-free and confirms fast enough that it feels like a UI action instead of a bank transfer. Solana is one of the few chains where both of those are true at once. A check-in here confirms in about a second and costs a fraction of a cent, which is the actual reason "check in every match, forever" is a plausible thing to build instead of something that only works in a demo video.

There's a side effect of the PDA design I didn't fully plan for going in: because the fan record is owned by the program and not by my database, any other app that knows the program id can read it directly — no API key, no integration meeting with me, no trusting my numbers. A ticketing platform could check the same record to offer a loyalty discount. A streaming service could unlock a perk off the same tier. None of that requires my permission or my infrastructure staying online. That's a meaningfully different thing from "an app that happens to use Solana," and it more or less fell out of choosing PDAs over a normal database rather than being the original plan.

What actually broke along the way

In the spirit of being honest about this instead of writing it up like everything went smoothly: I burned a genuinely embarrassing amount of time on devnet SOL. The official faucet rate-limits per IP, and I hit that limit hard while iterating on deploys — every account-struct change meant a full redeploy, and every redeploy needed enough SOL to cover rent for a fresh program buffer. I ended up bouncing between the CLI faucet, a couple of alternate faucets, and eventually just asking around, before I could get back to actually building.

Prize Categories

Best Use of Solana — the PDA-per-fan account design and the on-chain leaderboard aren't decorative. Pull Solana out of this project and the core claim — that a fan's streak can't be faked, inflated, or reset by the app itself — stops being true. That was the whole point of building it this way instead of with a normal backend.

Top comments (4)

Collapse
 
karang123 profile image
karan

okay I'm not gonna lie I got kind of hyped when phantom popped up asking me to approve the check-in lol, like I actually sat there for a sec just watching my streak update and it hit different knowing some app can't just quietly reset it later. genuinely didn't expect to feel anything about a check-in button but here we are 😭

Collapse
 
ishantgupta profile image
Ishant gupta

literally checking in with my team for the world cup rn, this timing could not be better ngl 😭⚽

Collapse
 
kushan2653 profile image
Kushan

not me getting genuinely nervous before checking in like it's the actual match lol, this hits so different during the world cup specifically🫣

Collapse
 
ishantgupta profile image
Ishant gupta

lmaooo "nervous before checking in like it's the actual match" is sending me, that's such a good sign the world cup timing actually worked out 😭