Skip to content
Fossa

Settlement

Usage claims, receipt accumulators, and finalization.

Initialize a receipt bucket

Paid reusable claims append receipts to a bucket for their offer. Send instruction 23 with [market, model, terms, bucket(w), payer(s)]. Precreate 736 bytes of program-owned storage; a fresh bucket also signs. The payload is slot:u32, duration:u32, optionally followed by epoch:i64.

Duration is positive and at most half DISPUTE_SECONDS: 1,800 seconds in the standard build. Without an explicit epoch, initialization uses floor(now / duration) × duration. An explicit epoch must be that current epoch or the next one. Claims can append only during [epoch, epoch + duration). Multiple buckets can serve the same offer and epoch; a full bucket requires another bucket with room. Next-epoch storage can be initialized before rollover.

The RECEIPT1 header pins market at 8, model at 40, provider at 72, bond vault at 104, rent payer at 136, and slot at 168. Bond bump is at 172, finalized flag at 173, epoch start/end at 176/184, latest dispute deadline at 192, receipt count at 200, aggregate outstanding charge at 208, and outstanding obligation count at 216. Sixteen 32-byte accumulator peaks follow at 224. A finalized bucket can be reused by its recorded payer for a strictly later epoch after its outstanding totals reach zero. Retired buckets can instead be closed through instruction 27 to recover their lamports.

Claim payment

Send instruction 7 with [market, model, capacity(w), user(w), booking(w), provider(s), user_vault(w), payout(w), mint, user_authority, token_program, receipt_bucket(w)]. Reusable claims use this little-endian payload, with offsets excluding the opcode:

  • 0/4/8: uncached-input, cached-input, and output counts, each u32.
  • 12: generation, u64.
  • 20: bucket epoch, i64.
  • 28: receipt index, u64.
  • 36..156: canonical 120-byte receipt.

The payload is exactly 156 bytes, or 157 including opcode 7. The former 20-byte reusable format is rejected. A reusable claim must be a top-level transaction instruction; cross-program invocation (CPI) is rejected. Legacy generation-zero claims retain 11 accounts and the 12-byte usage payload, with CPI still supported.

The selected provider must sign. Status must be reserved, the generation must match, and the clock must not exceed the claim deadline. Uncached plus cached input cannot exceed maximum input; output cannot exceed maximum output. Cached usage requires the booking's cache permission. The contract calculates each category at its pinned rate and rejects a charge above the reserve.

The published receipt must exactly match the booking's owner, user account, address, generation, calculated charge, and dispute deadline. For a positive charge, the epoch must equal the bucket's epoch start and the index must equal its current receipt count. A stale epoch or index fails atomically; refresh the bucket before constructing a replacement claim. Within one transaction, successive positive claims to the same bucket use successive indices.

A checked transfer pays the pinned payout account. Unused reserve becomes free balance; active bookings decrease; exposure replaces the reserve with the actual charge. A positive reusable claim commits a receipt and returns booking storage to available status. A zero charge releases its obligation immediately and appends no receipt: it still requires the full payload, canonical receipt body, and twelfth account, but does not validate bucket contents, epoch, or index. Legacy paid storage becomes claimed status 1. The provider signature authorizes the reported counts; the contract does not measure token generation.

Receipt commitments

A receipt is 120 bytes: owner at 0, user account at 32, booking address at 64, generation as u64 at 96, charge as u64 at 104, and dispute deadline as i64 at 112. The bucket supports at most 65,535 receipts in an epoch. Paid claims update its accumulator peaks, aggregate charge, outstanding count, and latest deadline.

The leaf hash commits to a domain tag, program ID, bucket address, epoch, index, and receipt bytes. Internal nodes hash b"fossa/node/v1", left digest, and right digest. The accumulator merges occupied peaks according to the previous receipt count's set bits.

The provider's signed transaction carries the epoch, index, and full receipt in instruction 7. Retain successful transactions and their resolved account keys to reconstruct inclusion proofs after booking storage is reused. Decode only top-level claims for the expected program and market; ignore failed transactions and zero-charge claims. Bucket, model, and provider come from the claim's account list; usage counts come from its payload. Versioned transactions also need their resolved address-table keys.

Claim logs additionally publish the fossa/receipt/v1 tag, bucket, epoch, index, receipt, and 76 detail bytes containing model, provider, and usage counts. Logs can truncate, so they are not the sole recovery source for the 156-byte format. Historical 20-byte claims still depend on retained logs or an existing archive. The commitment authenticates recorded accounting, not reported token usage, and does not guarantee that an RPC will retain transaction history indefinitely.

Finalize undisputed charges

Instruction 24 finalizes a receipt bucket with [market, model, capacity(w), receipt_bucket(w)] and an empty payload. No signer is required. The clock must reach the epoch end and be strictly greater than the bucket's latest dispute deadline. It subtracts the bucket's remaining aggregate charge from offer exposure and its remaining obligation count from offer obligations, then clears those totals and marks the bucket finalized.

Challenged receipts have already moved out of that aggregate; their cases require separate resolution. Finalization transfers no tokens: claims paid the provider earlier, and this instruction releases the remaining collateral obligation. A finalized bucket can be reused through instruction 23 or closed through instruction 27. Both preserve liabilities already recorded in independent dispute cases.

Legacy paid bookings finalize individually through instruction 9, accounts [market, model, capacity(w), booking(w)], empty payload. Status must be claimed 1 and the clock must be strictly past the dispute deadline. It releases the recorded charge exposure and one obligation, then sets status 4.