Compute rewards
Every hour, each Mac that worked gets a score s = A · T · q. The hour's release from compute.mlx is shared by weight × score, capped at κ/24 MLX per unit, and paid by one compute_reward transaction into locked balances.
Everything here is in lib/compute/rewards.ts (planHour, settleCompute) and lib/compute/seats.ts (deliveryT, pairUp), and the ledger's compute_reward handler in lib/chain/ledger.ts. Hours are UTC clock hours.
The score of one Mac for one hour
s = A · T · q
A = m / 60
m = the distinct minutes of the hour in which the Mac was available (at most 60)
T = min(1, done / max(1, m / p))
done = complete results the server accepted from this Mac this hour
p = minutes per job that count as full delivery:
15 s on a dedicated seat, 12 s on a free seat
q = quality(ok, bad)A: availability
A minute counts when, during it, the server saw any of these from the Mac (noteHeartbeat, noteJob):
- a heartbeat with an eligible state (see Free seats), from a Mac linked to a wallet;
- a job handed out to it;
- a complete result from it.
A Mac that is paused or off is not available.
T: delivery
done counts complete results the server took: the job the Mac was handed, once, and no faster than the timing floor (see Jobs and checks). It does not wait for the comparison with other Macs. A wrong answer still counts as delivered. It is caught by q instead.
The target depends on the seat. A Mac whose wallet holds a dedicated seat (starter seats count) is measured against one job per 15 available seconds (DEDICATED_MIN_PER_JOB). A Mac on a free seat is measured against one short job per 12 seconds (FREE_CYCLE_S): the longest throttle wait plus a few seconds of work. A Mac that takes every job it is offered reaches T = 1.
Hours before DELIVERY_V2_AT are scored with the older, looser target for dedicated seats, so they replay as they were paid.
q: quality
ok and bad count, over the Mac's whole life (cr:q2:<pk>, from 27 Oct 2026), how often its answers agreed with the majority of Macs of its GPU group that ran the same job (see Jobs and checks). With n = ok + bad:
n below 10: q = (ok + 10 − n) / 10 (checks not made yet count as passed)
from 10 on: q = ok / nSo a new Mac starts at 1, and every failed check counts from the first one. An earlier version kept q at 1 for any Mac below 10 checks, so a script could fail every check and stay at 1 by linking a fresh device key before reaching it.
Rows: one per wallet
planHour groups the Macs that scored above zero by the wallet they are linked to. Each wallet gets one row with a weight w and a score:
| Wallet | Weight w | Score |
|---|---|---|
| Seats carried | Σ units carried (carryOrder) | Σ (units × s) / Σ units |
| Free seats | 0.125 for each Mac without a seat to carry (COMPUTE_FREE_WEIGHT) | Σ (w × s) / Σ w |
The row's weight is the two added together, and its score the weighted average, at most 1. Every Mac that worked counts: each seat is carried by one Mac, biggest with biggest (the Mac linked first breaks a tie), and earns the units of the smaller class; every other Mac earns on a free seat. See Pairing seats with Macs. The seat part and the free part are each capped at κ/24 per unit, and only the free part is scaled by the free-seat share. At most 5 free-seat Macs on one network count in an hour (Fair use). Until 29 Oct 2026 a wallet without a seat scored by its best Mac only and Macs beyond its seats earned nothing.
The hour's release
B = compute.mlx's MLX balance when the hour is planned
E_h = λ_h · B
λ_h = 1 − 2^(−1 / half-life in hours) = 1.9768e-5The half-life is 4 years (COMPUTE_HALF_LIFE_DAYS, COMPUTE_LAMBDA_HOUR in lib/chain/curve.ts). MLX that is released but not earned is never moved. It stays in compute.mlx and goes into later releases.
Who gets what
S = Σ over rows of w · score
reward_r = min( κ/24 · w · score , E_h · w · score / S ) κ = 8 MLX per unit per dayThen the free-seat cap. If the free rows together got more than 10% of E_h (COMPUTE_FREE_SHARE), every free row is scaled down by the same factor so that together they get exactly 10% of E_h. Rows with seats are not scaled up.
Each reward is rounded down to whole base units (6 decimals). Rows that round to zero are dropped.
At most, a seat earns 8 MLX per unit a day and a free seat 1 MLX a day (0.0417 an hour). The seat table gives each seat's cap.
Settlement
The produce cron runs every minute and calls settleCompute:
- It takes a short Redis lock (
cr:lock), so two runs never settle together. - It starts at the hour after the last settled one (
cr:last), or atCOMPUTE_REWARDS_START. - An hour is settled once it has ended and its last heartbeats are in. A run catches up a few hours at most.
planHourcomputes the payouts, and the sequencer sends one system transaction:
{
type: "compute_reward",
hour: <start of the hour, ms since 1970, a whole UTC hour>,
payouts: [ { to: <wallet>, amount: <MLX base units, string>, weight: <w>, score: <score> }, … ]
}The ledger checks it again before it pays:
houris a whole UTC hour, not beforeCOMPUTE_REWARDS_START, and already over.- The hour has not been paid before (
cr:<hour>in the ledger store). This makes settlement idempotent: a second attempt fails with "That hour was already paid", whichsettleComputetreats as done. - Each payout goes to an existing, non-protocol wallet, and no wallet twice.
0 < weightand0 < score ≤ 1.amount ≤ κ · weight / 24, plus one base unit for rounding.compute.mlxholds enough.
Each payout moves MLX from compute.mlx into a compute lock on the wallet, one lock per UTC day. See Lock-ins. An hour with nothing to pay sends no transaction, and is still marked settled.
compute_reward is not added to each wallet's transaction list: 24 a day would push real history out. The day's lock is the record, and the wallet shows one "Mined on your Macs" row per day.
Mining referrals
Since 29 Oct 2026 the hour's payouts also pay the miners' inviters (planReferrals in lib/compute/referrals.ts, checked again by the ledger in compute_reward). A wallet's inviter is the referrer recorded when it was created, and it never changes. Since 29 Oct 2026 16:30 UTC every new wallet must name one (create_account without an existing referrer is refused: the invite code is the inviting wallet's name). Wallets with a linked Mac at that time are epoch 0 (epoch0 system transaction, epoch0: true on the account): no upstream, so the chain stops at them and nobody above them is paid on their mining. For each payout, up to three wallets above the miner are paid a share of the payout:
| Tier | Level 1 / 2 / 3 |
|---|---|
normal | 20% / 4% / 0.8% |
kol | 25% / 5% / 1% |
strategic | 30% / 6% / 1.2% |
Each inviter is paid at its own tier (ref_tier sets it, publicly). An inviter qualifies for an hour only when:
- one of its own linked Macs checked in within the last 7 days;
- none of those Macs checks in from the same network as the miner's Macs (an opaque hash of the network, kept 7 days,
wk:net:<pk>; never the address); - it isn't flagged as suspicious.
A share that doesn't qualify is not paid and stays in the budget; the levels above are still paid. Free seats' mining counts. Bonuses are not mining, so they earn no bonus themselves. They come from community.mlx, within a budget of 300 million MLX (stats.refMined counts what was paid); when it is spent, no new bonus is paid. Each bonus is a lock of kind referral on the inviter, ending when the payout's own lock ends, and it folds into the balance the same way mined MLX does.
https://mlx.app/api/referrals/{name}| Field | Type | Notes |
|---|---|---|
tier | string | normal, kol or strategic |
invitedBy | string or null | the wallet's own inviter |
levels | [number, number, number] | wallets invited at levels 1, 2 and 3 (recounted every 10 minutes) |
today | string | bonus settled today (UTC), base units |
locked | string | bonus still locked, base units |
earned | string | all bonus ever paid, base units |
budget | object | total and used, base units |
live | boolean | whether bonuses are being paid |
Reading it
Every reward ever paid, reconciled against the balance of compute.mlx:
https://mlx.app/api/computeA wallet's MLX from hours not yet settled, and how fast it is growing, is in GET /api/account/<name>/mining. See Compute.
What the hourly plan does not include
- No bond after a lock-in ends. The whitepaper's §11.6 describes one. The code doesn't check one today.
- No fee recycling. Paid jobs are not live, so nothing is recycled into
compute.mlxyet (whitepaper §11.4).