Overview
ROAM is two games sharing one wallet. The first pays you for moving, anywhere, every day. The second hides fragments of real tokenized stock at the physical doors of the companies that issue them, and pays you for walking to one and tapping it.
Four parts make it work: a spawn table (where, which token, how much), a claim server that verifies you are physically present and signs a voucher, a vault contract that holds the tokens and only releases them against a valid voucher, and a keeper that tops the vault back up. Nothing is minted. Every fragment on the map was bought and deposited before it spawned.
The two loops
Most move-to-earn products pick one and die of it. Emission-based games print a token until it collapses. Location games have nothing to offer you on a day when there is no drop nearby.
- The movement loop works on any street on earth. Your equipped sneaker has a speed band; minutes inside that band are eligible. Energy caps eligible minutes per day, which bounds the protocol's daily liability and forces a return visit.
- The drop loop is the high-variance layer. Drops are finite, pre-funded and rarity-tiered. You can ignore it entirely and still earn.
Energy is spent by both. That is the join: one budget, two ways to spend it, and the player decides.
Drops
A drop is a spawn point: a token, an amount, a rarity, coordinates and a place name. 1,240 drops are seeded across 121 cities. Each token has one drop rule that applies everywhere.
| Token | Fragment | Rarity |
|---|
Drops refill every day at midnight UTC. A drop can be caught at most 24 times per day, a wallet can catch at most 6 drops per day, and the same drop once per day. Legendary drops are monthly events announced 24 hours ahead, with a tighter 25 m radius and a short code physically stuck at the location — no code, no prize, which is what makes spoofing useless there.
Claim flow
Six steps, four of them invisible.
- You connect a wallet and enable location. The app watches your GPS position.
- Within 40 m of a drop, with accuracy under 65 m, the catch button unlocks.
- The app posts your address, the drop id and your fix to
POST /api/claim. - The server re-checks distance and accuracy, reads the GPS log behind the fix, compares it against your previous accepted fixes, applies the daily caps, then builds a voucher.
- The server signs it with the claim key (EIP-712) and returns it.
- Your wallet calls
claim(voucher, signature). The contract verifies the signature, burns the nonce and transfers the fragment.
Voucher {
address to; // your wallet
address token; // e.g. AAPL 0xaF3D…93f9
uint256 amount; // 0.017 AAPL = 17000000000000000 (18 decimals)
bytes32 spawnId; // keccak256("paris-0")
uint256 nonce; // unique, burned on use
uint256 deadline; // unix seconds, +15 minutes
}
The chain never trusts the app. It trusts one key, and that key only signs after the physical checks pass. If it leaks, the owner rotates it with setSigner and pauses the vault.
Vault contract
RoamVault.sol is deliberately small. It holds ERC-20 stock tokens and exposes one user function.
function claim(Voucher calldata v, bytes calldata sig) external // reverts if: paused, past deadline, nonce used, signer mismatch // effects: marks nonce used, transfers v.amount of v.token to v.to, emits Claimed
Owner functions: setSigner, setPaused, withdraw, transferOwnership. Anyone can relay a voucher, but tokens always go to the address inside it. Signatures use EIP-712 with domain ROAM / 1 / chainId 4663 / vault, so a voucher for one vault is worthless on another.
Anti-cheat
GPS can be faked and wallets are free, so per-wallet limits alone are worthless. Every counter is keyed on something a fresh wallet does not reset.
- Radius and accuracy. 40 m radius, accuracy under 65 m. Indoor and coarse IP fixes are rejected.
- Proof of presence. The app sends the GPS log behind the fix, not just the fix: at least 4 readings over 20 seconds. A real chipset wobbles by centimetres and its accuracy drifts every second. A mock provider returns the same coordinates and the same ±5 m forever, and that is exactly what we look for.
- Travel. Every accepted fix is kept per wallet and per device. A new catch has to be reachable from the last one at walking speed under 300 km, or by plane plus 90 minutes beyond it. Two cities inside one hour is refused whatever the distance — the trail follows the phone, so a fresh wallet clears nothing.
- Cross-checks GPS cannot fake. Your connection is geolocated at the edge; a fix more than 500 km from the network you are actually on is refused. So is a device clock more than 3 hours off the timezone of the place you claim to stand in.
- Motion. We read the accelerometer while you stand there. Hands shake; emulators do not. A perfect zero is refused.
- Rate limits. 120 minutes between catches per wallet, 10 minutes per drop, 24 catches per day from the same 11 m square, and a global bucket so nobody drains the vault faster than the keeper refills it.
Gear
Four archetypes, matched to real speed bands. Your first pair is free and never expires.
| Type | Speed band | Energy |
|---|---|---|
| Walker | 1–6 km/h | ≈ 4 |
| Jogger | 4–10 km/h | ≈ 5 |
| Runner | 8–20 km/h | ≈ 6 |
| Trainer | 1–20 km/h | ≈ 4–6 |
Four attributes: Efficiency raises what a movement minute earns. Luck raises mystery-box odds. Comfort reduces HP decay. Resilience reduces repair cost. Higher-tier gear also widens your catch radius, which is the only place the two loops touch mechanically.
Tokens
Robinhood Chain stock tokens are ERC-20s issued by Robinhood, one contract per underlying, 18 decimals, tracking the share price through a per-asset price feed. They are offered in 120+ countries, not to US persons; on-chain they are ordinary ERC-20s and move like any other. 42 are wired into the app.
Chain id 4663, RPC https://rpc.mainnet.chain.robinhood.com, explorer robinhoodchain.blockscout.com. The app adds the network to your wallet automatically.
API
GET /api/spawns?lat=&lng=&limit= drops sorted by distance
POST /api/claim { address, spawnId, lat, lng, accuracy }
-> { ok, mode, voucher, signature, contract, chainId }
GET /api/claims public feed, addresses shortened
GET /api/vault balances, payable drops, keeper runs, limits
POST /api/session { address, minutes, band } movement-loop settlement
Errors come back as { ok: false, error: "human sentence" } with a 4xx status. The claim endpoint is the only one that signs anything.
Run it yourself
npm install && npm run dev # demo mode, nothing moves on-chain node scripts/compile-vault.mjs DEPLOYER_KEY=0x… CLAIM_SIGNER_KEY=0x… node scripts/deploy-vault.mjs # fund the vault, then set NEXT_PUBLIC_VAULT_ADDRESS + CLAIM_SIGNER_KEY
The claim server stays in demo mode until CLAIM_SIGNER_KEY and NEXT_PUBLIC_VAULT_ADDRESS both exist. In demo mode every check still runs for real and the catch is logged — no voucher is signed and nothing moves on chain. Never put the signer key in the browser.