Accounting and claims
The split
For a paid query of value V returning hits h₁…h₅ with scores s₁…s₅:
owners = V × 70% nodes = V × 20% stake = V − owners − nodes
wᵢ = max(1, round(sᵢ × 10⁶))
owner(hᵢ) += owners × wᵢ / Σw node(hᵢ) += nodes × wᵢ / Σw
rounding dust goes to the owner and node of the top hitSettlement
Every accepted payment is a signed authorization queued in the ledger with its split. The keeper calls settleBatch on the payouts contract, which pulls each authorization into the contract and confirms it by the contract's own balance increase. A settled authorization moves its split from accrued to settled; a failed one is removed and its split reversed.
Roots and claims
After settlement the keeper builds a Merkle tree of (address, cumulative settled amount) leaves, OpenZeppelin standard encoding, and posts its root with the total. Anyone can call claim(account, cumulative, proof); the contract pays cumulative − claimed[account] to the account. The tree is public at /api/claims.
Invariants
totalClaimed ≤ totalCumulative ≤ totalClaimed + USDG.balanceOf(payouts): the contract never allocates more than it received.totalCumulativenever decreases between roots.- An authorization nonce is accepted once: the ledger refuses a nonce it has seen and the token refuses one used on chain.
- The owner can only withdraw USDG that no root has allocated.
State machine of a payment
signed → verified (API) → queued (ledger) → settled (settleBatch) → in root (postRoot) → claimed
↘ failed (reversed)Security
- Payments are EIP-3009 authorizations bound to the payouts contract address, a value, a time window and a nonce.
- The keeper role can only settle and post roots; changing it takes a 24 hour timelock on chain.
- Claims are checked against the posted root and capped by the posted total.
- Votes, proposals, node registrations and removals are signed messages checked on the server.