Skip to content

Reward epochs and fees

Mining projects pay out per epoch, from the project’s own vault, in the project’s own reward token.

sequenceDiagram
  participant V as Validators
  participant H as Hub
  participant PV as ProjectVault (Sepolia)
  participant M as Miner
  Note over V: epoch E closes
  V->>V: leader proposes receipt v2; others recompute from their finality votes and attest on exact match
  V->>H: POST /v1/nodes/epoch-receipts (attested receipt)
  H->>H: check quorum, hashes, caps, fee split, payout addresses
  H->>PV: settleEpoch(totals, merkle_root)
  Note over PV: challenge window (1 h): guardian may veto
  M->>H: GET /v1/me/claims (amount + proof)
  M->>H: POST /v1/me/withdrawals/claims (gasless)
  H->>PV: claimMany(...) → tokens to the miner
  Note over PV: release(E): developer and treasury shares paid out

For every task round of the project finalized in epoch E with record F:

for each voter v in F.voters: units[v.payout_address] += max(1, ceil(F.gas_used / gas_per_unit))

gas_per_unit is 1,000,000 on the testnet. Then the project’s caps apply: max_recipients_per_epoch (the largest earners are kept) and max_units_per_epoch (everyone is scaled down proportionally).

epoch_cap = floor(daily_emission × epoch_secs / 86400)
per-unit: g_i = units_i × reward_per_unit, scaled down if Σ g_i > epoch_cap
epoch-pool: g_i = floor(epoch_cap × units_i / Σ units)
miner amount_i = floor(g_i × fee_split_bp.miner / 10000)
developer = floor(G × fee_split_bp.developer / 10000)
treasury = G − Σ amount_i − developer (absorbs rounding dust)

The fee split is in basis points and must sum to 10,000, with the miner share at least 5,000. Example from the live reference project: per-unit, 1 NECTA per unit, split 8500 / 1000 / 500, so a miner earns 0.85 NECTA per verified unit, the developer 0.10 and the treasury 0.05.

After close(E) = (E+1)·epoch_secs + round_secs + 120 + max_skew + settle, a validator leader proposes the receipt (v: 2, with project_id, consensus_hash, reward_token, compute_units with amounts, totals, and a merkle_root); the other validators recompute it from their own finality votes and attest only on an exact match; the epoch votes are counted by SecureWeave. The Hub verifies the quorum and that every recipient is a payout address from that epoch’s snapshot, then calls ProjectVault.settleEpoch. The full receipt with signatures is public:

Terminal window
curl -s https://testnet-rpc.necter.network/v1/explorer/epochs/<project_id>/<epoch>

Payouts are pull: each epoch has a Merkle root of (project_id, epoch, miner, amount) leaves.

leaf = keccak256(keccak256(abi.encode(bytes32 project_id, uint64 epoch, address miner, uint256 amount)))

Claims open after the vault’s challenge window (vault_challenge_secs = 1 hour), during which the guardian can veto a bad settlement. GET /v1/me/claims lists your claimable entries with proofs; POST /v1/me/withdrawals/claims claims up to 100 at once through the Hub’s relayer, so miners pay no gas. The developer and treasury shares are paid by release(epoch) after the same window.

runway_epochs = floor((vault_balance − reserved) / epoch_cap)

If the free balance is below one epoch_cap when a snapshot is built, the project is marked underfunded: its tasks run as validator rounds (no miner units) and the store shows it as paused until you top up the vault. A settlement that exceeds the free balance reverts and is retried after the next Funded event. Vault and funding