BikeControl (SwiftControl)
使用 Zwift Click、Zwift Play、Shimano Di2 或任何藍牙控制器,搭配 MyWhoosh、Rouvy、Zwift 等。iOS、Android、Windows、macOS 均可免費試用。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证RUNNER — run-to-earn app: GPS run tracking, sneaker NFTs and the RUNNER token on Robinhood Chain.
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Whitepaper (v1.0).
A simple GPS running app with a run-to-earn layer: own a RUNNER Genesis sneaker NFT, run inside its pace zone, and earn the RUNNER token.
Try it: https://runearn.fun
Live on Robinhood Chain (25 October 2026). RUNNER trades against IMD in its Uniswap v4 pool (liquidity locked forever); RUNNER Genesis sneakers are on sale with real ETH. Run rewards are on: emission started 25 October 2026 (10,000,000 RUNNER/day, halving every 30 days); verified runs can be claimed.
| Contract | Address |
|---|---|
| RUNNER token | 0x52cE7fC26a4Bb4B8aFef02C1b00486FDF4Bb804e |
| RunnerHook (RUNNER/IMD pool, 4% fee) | 0x962c745D5feC4BC0Fd6dE15D47fc17C40C4c68EC |
| RunnerRouter (IMD) | 0x55866De8BD1e5Fbf79433Ee2526D3405F64692Ba |
| RunnerEthRouter (ETH) | 0xcAc6938E8309Dc9687C82DFb745B21950Fe0F6A1 |
| RUNNER Genesis NFT | 0x7ded256c6646630dFcf15317aeB7cF40027932CC |
| IMD | 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127 |
Each deployed contract was checked byte for byte against the audited build.
Android app: download it from https://runearn.fun/download (an APK, no Play Store needed). It keeps recording with the screen locked. Building and publishing it: see NATIVE.md.
Or add the website to your home screen:
Found a bug or have an idea? Open an issue — include your phone model, browser and what happened. You can also reach it from Settings → Send feedback in the app.
It's a static web app — no build step:
python3 -m http.server 5190
Then open http://localhost:5190.
contracts/RunnerGenesis.sol — 9,999 sneaker NFTs (ERC-721) on Robinhood Chain. Open sale, no reveal: buyers pick any token id and pay its price (Common 0.005 / Uncommon 0.015 / Rare 0.04 / Epic 0.1 ETH). Rarity and price come from the token id with the same formula the app uses, so app and chain always agree.
0x0f397E46e4e810313dBC782CDb0236347157f1B4 is set at deployment as owner, treasury (mint payments) and royalty receiver. Whoever sends the deployment gets no control.setTransferValidator), which marketplaces use to enforce creator earnings.contractURI() → /nft/collection.json (name, images, website, fee). OpenSea gives the contract owner() edit rights, so the dev wallet can sign in to OpenSea and add socials, website and banner. After a mainnet launch, turn on enforced creator earnings in OpenSea Studio → Creator earnings. The required OpenSea contracts only exist on mainnet, not on testnet./nft/:id (JSON) and /nft/image/:id (SVG) are served by api/nft.js and api/nft-image.js from the same trait and art code as the app. The owner can change them with setBaseURI and setContractURI.paris — creation-code keccak 0x270c71d725f5555873c1391b02799879056d7d4923759ab75fe6cd3d9c32ed9e./admin.html, sign in with any wallet holding a little ETH, compile (the page checks the fingerprint) and deploy. Then set NFT_CONTRACT in js/config.js.contracts/RunnerToken.sol — ERC-20 on Robinhood Chain mainnet, fixed supply of 1,000,000,000 RUNNER, all created at deployment. There is no mint function, and burning is the only way the supply changes. Not deployed yet. Read AUDIT.md first.
| Share | Amount | Goes to |
|---|---|---|
| Run rewards | 800,000,000 (80%) | Locked in the contract, paid out only by claim() |
| Liquidity | 100,000,000 (10%) | Locked forever in the RUNNER/IMD Uniswap v4 pool by the RunnerHook |
| Team | 100,000,000 (10%) | 0x0f4FE718eb8492533E600b30F3287E2f04087332 |
No transfer tax; a 4% trading fee paid in IMD. RUNNER itself is a plain ERC-20: every transfer moves exactly the amount sent. It trades against IMD in one Uniswap v4 pool owned by the RunnerHook (contracts/RunnerHook.sol, adapted from PepesFamily's audited PepesEarnIMD). The hook takes 4% of the IMD side of every swap: 2% to the protocol wallet and 2% to RUNNER holders, both in IMD. The fee is fixed in the hook, and a swap that wouldn't fill completely (a price limit, or selling for more IMD than the pool holds) is refused rather than charged on the unfilled part; a swap that would move nothing is refused too, and so are donations to the pool (they would be lost to the locked position). The fee rounds up, so every trade pays. Our routers deliver the holder share in a way that never blocks a trade: if IMD transfers to the token ever fail, the share waits in the hook until anyone calls flush. The hook adds the 100M liquidity allocation as single-sided liquidity at a starting market cap of about 635 IMD and has no way to remove it (locked forever); nobody else can add liquidity to that pool. RunnerRouter (IMD) and RunnerEthRouter (ETH, routed through the IMD/ETH pool) buy and sell in one transaction. Extra RUNNER/ETH pool: the admin page (/admin.html?c=token → RUNNER/ETH pool) creates a Uniswap v4 RUNNER/ETH pool with a 10% LP fee and no hook (like the extra Pepes pools) through Uniswap's own PositionManager (0x58daec31…4fa7), at the price implied by the RUNNER/IMD and IMD/ETH pools, and adds a full-range position from the dev wallet. Trades there pay the 10% to the liquidity provider, not the 4% split. Trades through other pools someone may create are not charged; the owner can exclude such pools from holder rewards (excludePool: only contracts shaped like a v2/v3 pool qualify, and an exclusion can't be undone). Don't send tokens to the hook's address: it has no way to return them.
Holder rewards, in IMD. The hook delivers the holders' IMD to the token (syncHolderRewards books it as that day's rewards). Each UTC day's IMD is shared by every wallet that held at least 300,000 RUNNER for that whole day, in proportion to its lowest balance that day. It can be claimed (claimHolderRewards) for 7 days in the app's Rewards tab, which shows whether a wallet qualifies today and its share of each of the last 7 days; holders can also Connect wallet there (WalletConnect QR or phone app, MetaMask, Coinbase Wallet, Rainbow, OKX; via Privy, without signing in) to claim from the wallet that holds their RUNNER, and any address can be looked up. The admin page can connect the dev wallet the same way for owner actions. Set in (free at dashboard.reown.com) before launch; empty uses Privy's shared, rate-limited default. The admin page's card shows and claims the protocol's IMD () and sweeps expired holder rewards. After that, sends what's left to the protocol wallet; anyone can call it. The pool (PoolManager), the hook, other excluded pools and the contract get no share; the team wallet does. The threshold is per address: a vault or wrapper holding 300,000+ on behalf of many smaller wallets counts as one holder and could pass rewards on to them. Opening balances count for the deploy day. A pool excluded later stops counting for the days still claimable that nobody has been paid from yet; a day someone was already paid from keeps its total, so every holder of it is paid at the same rate.
api/rewards.js (Vercel function) is the source of truth for anything worth real RUNNER. The app uploads every finished run's GPS track. The server re-scores it with the same rules the app uses (js/rules.js) and checks the following before crediting it:
Level-ups and repairs are paid from the server balance. Levels run 0–30. By default a level-up costs 1,100 × (next level)^1.5 RUNNER (about a month to level 30 for a Common) and gives 1 point (Common, Uncommon) or 2 points (Rare, Epic). Level-up and repair costs follow emission.
Game settings (dev wallet, /admin.html?c=token → Controls after launch, applied immediately): the level-price base and curve, an optional per-runner daily max, and NFT boosts. Any future NFT collection can be listed with a boost %, e.g. Season 2 holders earn +10%, up to +100% in total. Claim asks the server to sign, then the wallet sends claim() to the token, paying a tiny ETH gas fee.
Setup, once both contracts are deployed:
/admin.html?c=token → Claim server → Generate key (in your browser). Add it in Vercel → Settings → Environment Variables as SIGNER_PRIVATE_KEY.KV_REST_API_URL and KV_REST_API_TOKEN variables. Redeploy.setSigner on Blockscout).Until all three are done, the app keeps working in local mode.
Fair play.
Trust score: every run gets a 0–100 score (api/_lib/trust.js). Scripted or spoofed tracks tend to be too clean: perfectly regular timing, constant speed, straight lines, unchanging GPS accuracy, or a copy of someone else's route.
24-hour hold: earnings become usable 24 hours after a run.
Automatic rules (no human review):
| Trust score | Outcome |
|---|---|
| 70+ | Paid in full |
| 40–69 | Paid 50% (no penalty) |
| Under 40, a fake-GPS app, or a copied route | Not paid + 1 strike |
3 strikes in 30 days suspend the wallet automatically: 7 days, then 30 days, then permanently. A suspension also forfeits earnings still on hold. Strikes expire after 30 days. All thresholds are game settings on the admin page, which also shows a log of every automatic decision and keeps an optional manual ban.
Bans: banned wallets can't earn, spend or claim.
Rate limits: requests are capped per IP and per wallet.
Step cadence (phone app): the app records the phone's step counter during a run. Running is about 140–190 steps a minute; on a bike, scooter or in a car the counter barely moves. Steps that don't match the GPS pace lower the trust score, so the run goes to review.
Fake GPS: the app ignores mock-location positions and the server lowers the score of runs where it saw any.
Phone app only (device attestation): every upload from the app carries a Google Play Integrity token (Android) or an Apple App Attest proof (iPhone), tied to that exact run. With Phone app only switched on in the admin page (setting requireAttestation), only runs from the genuine app on an unmodified phone earn real RUNNER. Website runs still track and count for stats, but earn nothing. Switch it on at launch, after the setup below.
Limits: attestation proves the app and phone are real, not who carries the phone. Per-wallet energy, the daily emission and the strike rules bound the damage.
Tests (tests/, see AUDIT.md): node rewards-server.test.mjs .. covers sessions, scoring, anti-cheat, spending, claims, halving rates, halving costs, the energy table, level costs, game settings, NFT boosts, the per-runner cap, schedule changes, the daily budget, partial claims, the trust score, the 24-hour hold, the automatic fair-play rules (strikes and suspensions), manual bans, rate limits, step cadence, mock locations and device attestation (108 checks). Google's verdicts are faked in the tests; App Attest runs with real signatures and a test certificate chain. A server-signed claim was also verified against RunnerToken in a local EVM.
You do these steps yourself: they need your Google and Apple accounts, and the keys must never be pasted into a chat. The step counter needs no setup.
Android: Google Play Integrity
fun.runearn.app.runner-integrity). No role is needed. Open it → Keys → Add key → Create new key → JSON. A file downloads.PLAY_INTEGRITY_SA (Production) and paste the whole contents of that JSON file. Redeploy. Then delete the file from your Downloads.js/config.js → PLAY_CLOUD_PROJECT_NUMBER (it isn't secret, so Claude can do this for you), then rebuild the app.iPhone: Apple App Attest
App.entitlements, set App Attest Environment to production.APPLE_TEAM_ID = your Team ID. Redeploy.Switch it on (launch day): open /admin.html?c=token → Controls after launch. The line under Phone app only should show ✓ for Android and iPhone. Tick Phone app only and press Save game settings.
WALLETCONNECT_PROJECT_IDjs/config.jscollectProtocolFeessweepExpiredRun-reward emission (halving). The launch schedule is 10,000,000 RUNNER a day from launch day (startEmission, once), halving every 30 days (10M → 5M → 2.5M …) and never dropping below 100,000 a day. The dev wallet can set a new schedule (setEmissionSchedule: daily amount, halving period, floor). It starts the next UTC day and can never exceed 20,000,000 a day. With the launch schedule:
| Period | Emission |
|---|---|
| Month 1 | ≈38% of the pool |
| First 3 months | ≈66% |
| Month 8 onward | 100k/day for about 5.5 more years |
Runners' earn rates halve on the same dates, the server never credits more than the day's emission, and claims beyond today's emission become partial claims finished the next day: on-chain a claim is all-or-nothing, so the server signs a total that fits today's remaining emission and the rest on later days.
Run-reward claims. The RUNNER server signs "this account has earned X in total", and claim() pays X minus what was already claimed. The runner chooses: lock 7 days and get 100%, or take it now: 80% to the wallet, 20% burned. Locked rewards can be released after 7 days (anyone may trigger it; it always pays the runner), or taken early at 80%. The server signs the payout choice too, so nobody else can switch a runner's claim into a lock. The owner can rotate the server key, pause claims and change the emission schedule from the next UTC day (never above 20M/day). It can never change the fee or take IMD from the holder rewards. Because the owner picks the server key, a stolen owner key could claim up to the daily emission for itself, so the owner key (the dev wallet) must be kept safe. Ownership moves in two steps: transferOwnership proposes, and the new owner must call acceptOwnership; transferOwnership(address(0)) cancels a pending proposal (same for the NFT and the hook).
Build: Solidity 0.8.24, optimizer 200 runs, EVM paris — creation-code keccak 0x75d8d27c4c1220ebabaf525493933771ca45bab43110bb52f09fa74ce0d226d5.
Contracts and tests: contracts/RunnerHook.sol, RunnerRouter.sol, RunnerEthRouter.sol build with Foundry (scripts/setup-contracts.sh fetches the pinned Uniswap v4 libraries, then forge test; FORK_RPC=https://robinhood.drpc.org forge test also runs them against the live chain). The token is also tested in tests/token.test.mjs.
Deploy, in order (/admin.html): 1 · Pool hook (checks contracts/build/RunnerHook.json against its fingerprint, then deploys through the CREATE2 deployer at an address carrying the hook flags) → set HOOK_CONTRACT → 2 · RUNNER token → set TOKEN_CONTRACT → Open the pool (step 5, dev wallet).