No GPS track
rejectno_gpsA manual entry is a number someone typed. Indoor efforts pass only with device evidence: a footpod reporting cadence, a trainer reporting power.
Connect Strava. Go outside. VitalShares verifies the effort against a GPS trace, a pace floor and a duplicate-route check, then pays you in USDG — usually before you have finished stretching. Trading fees on the token fund the pool, so payouts grow as the token does.
An activity with no GPS polyline is rejected outright, unless a footpod or power meter corroborates it.
Routes are fingerprinted twice — the polyline itself, and a coarse start/end bucket that survives jitter.
One payout row per activity, enforced by a unique constraint. The transaction hash is stored before broadcast.
Every one of them is idempotent. Replay any step and nothing is paid twice.
An activity:create webhook hits /api/webhooks/strava.
Answered in one INSERT, under Strava’s 2-second deadline.
A row lands in verification_jobs with a 20s delay.
The delay lets Strava finish attaching the GPS map.
Full activity detail is pulled and run through the rule set.
GPS, pace, duplicate route, account age, daily cap.
claim_reward_slot() takes one of three daily slots.
A primary key, not a count — concurrent uploads collide, not double-pay.
Nonce and gas pinned, transaction signed, hash persisted.
Stored before broadcast, so a retry re-sends the same bytes.
Receipt confirmed, ledger updated, email sent.
Anything without a receipt stays in-flight — never re-sent.
One pure function, no database reads, fully testable. Tune the numbers, not the plumbing.
no_gpsA manual entry is a number someone typed. Indoor efforts pass only with device evidence: a footpod reporting cadence, a trainer reporting power.
pace_implausibleSub-2:30/km for a run is quicker than the 10k world record. Strava's own reported average speed is cross-checked against the derived pace.
duplicate_routeTwo fingerprints per activity, compared across a 24-hour window: the exact polyline, and start/end coordinates bucketed to ~110 m with the distance.
account_too_newA Strava account under 24 hours old earns nothing. If the creation date cannot be read at all, the activity is rejected rather than trusted.
daily_cap_reached3 rewarded activities per UTC day. Not a count-and-hope — a primary key on (user, day, slot), so simultaneous uploads collide instead of both paying.
activity_too_oldActivities older than 72 hours are outside the reward window, which stops a new account importing a year of history on day one.
This runs the production reward function in your browser. No approximation.
Anything left of the line is faster than 2:30/km — quicker than a world-record race, so it is a vehicle, a GPS glitch, or a fake.
A share of every trading fee on the VitalShares token flows into the reward treasury. More volume means a bigger pool, and a bigger pool means the per-kilometre rate and the per-activity cap go up.
A share of every fee on the VitalShares token is routed to the reward treasury instead of being taken out.
That share accumulates as USDG, on top of whatever the treasury is seeded with.
Per-kilometre rates and the per-activity cap are raised as the float allows, rather than being fixed forever.
Bigger payouts bring more people, more people mean more volume, and more volume means more fees.
Rewards are funded by fees, not by a fixed pot that drains. The treasury balance is readable on-chain at any time, and the current rate is always the one shown in the payout model above — it is raised deliberately, never promised in advance.
Sign up, paste a wallet address, connect Strava. The first verified kilometre settles without you touching anything else.
Create an account