Inspiration

I've always loved walking and have made it a goal to at least get 15,000 steps per day. When I was curious about step-tracking apps (there's nothing wrong with Apple Health, I just wanted something cooler), I found pay-to-walk apps. I tried a few of them out, however the vast majority of them had 10,000 step count limits. This limit irritated me, as my daily steps typically range from anywhere between 12,000-18,000 steps.

What it does

Stride Quest is a fitness rewards app that allows users to convert their steps into tokens, which can then be redeemed for gift cards and other digital rewards.

The main loop of the app consists of four steps:

  1. Hit fitness milestones (step thresholds throughout the day)
  2. Watch a sponsor ad
  3. Spin for Stride Tokens
  4. Redeem rewards

There are three separate ways to earn Stride Tokens;

  • 1.Walking alone earns free tokens. Every 300 steps earns 1 token, capped at 15,000 steps a day, for up to 50 tokens with no ad required.
  • 2.Milestones add wheel spins. One unlocks every 1,000 steps, 15 a day at the same 15,000-step cap, each spin gated behind a sponsor ad.
  • 3.Bonus spins refill on a timer. 6 more ad-gated spins arrive every 6 hours, independent of how many steps you've taken that day.

A daily streak stacks bonus spins on top of all three: escalating rewards at 7, 14, 30, 45, 75, and 100 days, from a single bonus spin up to ten, before the streak resets and climbs again.

Tokens redeem two ways: gift cards from 20 retail brands, or donations to one of five charities. The two redemption types draw on separate daily caps, so a single account cannot drain the payout budget. The donation cap is the higher of the two, for the slightly cynical reason that donating to charity is not an attractive thing to commit fraud for.

Step tracking is behind an explicit consent gate. The pedometer does not read anything until you turn it on, and revoking it stops collection immediately rather than at the next app launch.

There is no subscription and nothing to buy. Revenue comes from the sponsor ads, which is what makes the payouts fundable.

How I built it

On mobile it's Expo SDK 54 with React Native and expo-router. Step counts come from the expo-sensors Pedometer, state from Zustand 5 and TanStack Query 5, crash reporting from Sentry, ads from react-native-google-mobile-ads.

The web and API side runs React Router 7 with a Hono server, Auth.js for email, Google, and Apple sign-in, and Neon serverless Postgres reached through raw SQL. Hosted on Railway, live at stridequest.tech.

The anti-fraud layer is most of the interesting work:

  • App Attest checks that step counts come from a real build. @expo/app-integrity lets the server tell my actual app apart from a script posting step counts at it.
  • AdMob's server-side verification closes the trust gap. With nonce replay protection, "I watched an ad" is confirmed by Google to my server rather than asserted by the client.
  • Rate limiting keys on the edge client IP, not the rotating rightmost forwarded hop.
  • Day boundaries are timezone-correct. An early version let you cross a UTC midnight, change your timezone, and re-earn the same day. The fix keys accumulation to monotonic local value-days instead.
  • A parity test now checks all three gift-card catalogues. The list still lives in three places, and a web-only brand addition once shipped two unredeemable buttons to production.

Challenges I ran into

Step laundering is not solvable on the server, and it took me a long time to verify this problem. If a client fabricates a step count and re-posts it, the re-post corroborates the original. I specified two separate server-side defenses before accepting that a fabricated count is formally inseparable from "I walked before opening the app." The real fix is HealthKit provenance. What shipped instead is an economic mitigation: gating the payout on a watched ad makes laundering cost the launderer more than it returns.

A build flag silently disarmed five security guards. The production host was building with NODE_ENV=development, so five hardening guards compiled to no-ops. Every local build looked correct, because a local build always defaults to production. I had to personally search through the deployed JavaScript bundle to find the error.

Accomplishments that I'm proud of

The app has zero axe WCAG A/AA violations across 12 routes in both light and dark themes, and a VoiceOver pass came back clean too. Accessibility was not an afterthought bolted on at the end.

I also ran a 13-agent adversarial security audit: six lanes hunting for issues, six independent refuters assigned to try to disprove each one before it counted. It raised 18 candidates; 14 survived refutation. Of the three that would have blocked the iOS build specifically, refuters correctly knocked two down as false, leaving one real, low-severity issue standing. The process that kills your own bad findings is worth more than the process that generates them.

What I learned

I learned how to set up server-side flips for sandbox versus production API keys. This was also the first time that I had to store a database, which I am using Postgres for. When reading through the code files myself, I had to learn the fundamental syntax differences between Java and Javascript. When using Claude Code, I created a few skills to improve code accuracy, reduce variability, and enhance the frontend: tiered-workflow, un-slop UI, un-slop Text, and Dummy Check. Also, I learned to verify against the artifact that actually runs (not the source, not the local build, not the test suite).

What's next for Stride Quest

Now that Stride Quest is on the App Store, I hope to release versio 1.0

Built With

Share this project:

Updates

Submission history