DEV Community

Cover image for Every Sports App Resets Your Streak Eventually. Mine Can't. πŸ”’βš‘
Ishant gupta
Ishant gupta

Posted on

Every Sports App Resets Your Streak Eventually. Mine Can't. πŸ”’βš‘

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. Cool. Except every single one of them throws that history away the second you stop opening the app. Switch apps and your streak resets to zero. Get banned, or the app shuts down, or they just quietly decide to wipe inactive accounts one day β€” and your history is just... gone. Because it was never actually yours. It was a number sitting in someone else's database, and they could reset it, inflate it, or delete it whenever they felt like it. You had zero say in it.

And that bugged me way more than it probably should have. Like β€” 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, and nobody's really questioned that.

So I kept the weekend scope deliberately small: prove one fan's loyalty to one team, for real, end to end β€” instead of sketching 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 here ⚽ β€” that check-in sends an actual transaction that creates or updates a program-owned account, not a row in my database somewhere. Your streak count, your badge tier, the actual badge tokens β€” none of it exists anywhere I control. Which honestly felt a little weird to build, in a good way.

Once that core loop worked, I built 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 history, and a leaderboard ranking actual fans by actual streaks. There's also a "Demo Preview" toggle that fills the passport with sample numbers, clearly labeled as sample data β€” because a screen full of zeros just 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, same UI and flow, but honestly? They're stubbed. Sample fixtures, no chain writes, and the app says so directly instead of 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 β€” 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, myself included the first time πŸ™ƒ
  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 fires πŸŽ‰, and if you crossed a threshold, a badge-unlocked toast shows up.
  7. Scroll down and the full Fan Passport fills in β€” tier, progress bar, achievements, recent activity, your actual rank on that team's leaderboard.
  8. Once you've crossed tier 1 (3 check-ins), hit Claim Badge β€” it mints a real SPL token straight to your wallet. Go check Phantom's token list after, it's just sitting there. Permanently. Not a UI element pretending to be a thing πŸ‘€

πŸ“Έ SCREENSHOT: Phantom's transaction-approval popup mid check-in β€” the single most convincing frame, proves it's a real signed tx

Code

GitHub Repository:
https://github.com/ishantgupta30/Loyalty-Ledger

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 at all. It fires 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 basically the whole point of this project in one sentence: the streak isn't something my frontend can fake, inflate, or quietly reset, because it was never my frontend's data to begin with. Anyone β€” not just this app β€” can read the account directly and verify exactly what it says. No trust required.

SCREENSHOT: Solana Explorer page for a confirmed check_in tx

The leaderboard problem (this one got me)

This took way longer than it had any right to. 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 is fine if you already know a wallet and want to look up their record. But it's a total dead end if you want to ask "who are the top Argentina fans," because seeds only 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. Learned that one the hard way mid-build.

So I ended up adding sport and team as actual fields on the account data, not just seed inputs. Which meant a full account-struct change, a rebuild, and a redeploy β€” genuinely annoying mid-weekend β€” but it's what makes a real getProgramAccounts call (filtered client-side by sport/team) actually able to answer "rank every Argentina fan by streak" using real chain state instead of a cached guess or a fake 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 pretty 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 to feel like a UI action instead of a bank transfer. Solana's 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, boring, practical reason "check in every match, forever" is something you could really build, instead of something that only works in a demo video.

There's a side effect of the PDA design I honestly 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 needs my permission or my infrastructure staying online. That's a meaningfully different thing from "an app that happens to use Solana" β€” and it kind of just fell out of choosing PDAs over a normal database, rather than being the original plan.

What actually broke along the way

Being honest here instead of pretending this all went smoothly: I burned a genuinely embarrassing amount of time on devnet SOL. The official faucet rate-limits per IP, and I hit that wall 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 backup 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 β€” just stops being true. That was the whole point of building it this way instead of with a normal backend.

Top comments (6)

Collapse
 
karang123 profile image
karan

fire doing this during the world cup specifically hits so different, like I'm not just watching the games anymore I'm building something because of them. genuinely didn't expect a check-in app to make me feel proud of my team but here we are
πŸ”₯πŸ”₯

Collapse
 
ishantgupta profile image
Ishant gupta

That's exactly the feeling I was hoping to capture. 😊 Watching the World Cup is exciting on its own, but giving fans a way to build something permanent around that passion felt even more meaningful. Really glad it resonated with youβ€”thanks for checking it out!

Collapse
 
kushan2653 profile image
Kushan

This is one of the few blockchain submissions where the chain actually feels necessary instead of being added for the buzzword. I especially liked the explanation of why you had to redesign the account structure for the leaderboard. Nice write-up and cool project!

Collapse
 
ishantgupta profile image
Ishant gupta

Thanks, I really appreciate that! That was actually my biggest goal with this project. I wanted Solana to solve the core ownership problem rather than just be part of the tech stack. Glad the leaderboard redesign section resonated with you tooβ€”it was one of those lessons I only learned by building. πŸ˜„

Collapse
 
harshyadav07 profile image
harsh

just checked in for my team and I'm not kidding my heart did a little thing when the streak ticked up, this world cup already has me feeling things and it's just a check-in button πŸ’€πŸ™€

Collapse
 
ishantgupta profile image
Ishant gupta

πŸ˜‚ I'll take that as a huge compliment! Hopefully the next check-ins feel even better when you're unlocking new tiers and badges. Thanks for giving it a spin!