Protocol
Operations

Fee collection

How the three kinds of fee the pool accrues are collected, who can collect each, and where the tokens go.

The pool accrues three kinds of fee, and each is collected differently. Protocol fees sit in private fee notes owned by the treasury identity and are collected in batches with a zero-knowledge proof. Escape-hatch fees and relayer tips are public counters that the pool program pays out on request. Until a fee is collected, its tokens stay in the pool token account, or for SOL in the pool's lamport account.

FeeAccrues asCollected byPaid toProof
Protocol feePrivate fee notes in the fee commitment treeAnyone submitting a valid batchThe treasury payout account governance set for the tokenA fee-collection proof by the treasury identity
Escape-hatch feeA public counter per pool index and tokenAnyoneThe same treasury payout accountNone
Relayer tipA public counter per relayer and tokenThe relayer, which must signAny account of that token the relayer namesNone

SOL is paid in lamports from the pool's lamport account, and the treasury payout address for SOL is a wallet rather than a token account. A wallet that holds no lamports must receive at least the rent-exempt minimum for an empty account, or the Solana runtime refuses the collection. See SOL.

Protocol fee notes

How they accrue

Every value transaction publishes three fee commitments, one per token slot, each committing to a token and the protocol fee paid in it. A slot that carries no token commits to zero, so it looks like any other. The fee inserter appends them to the fee commitment tree of the pool index that receives the transaction's new notes, and only then can they be collected. Each fee note belongs to the treasury identity the fee enclave record named when the transaction executed.

Who can collect them

The treasury owner commitment is an identity commitment like a user's, and whoever knows its opening can collect. The fee enclave derives that identity's keys from its QuorumOS quorum key and attests the commitment every time it boots (see the fee enclave). Collecting also needs each fee note's amount and blindings. The fee enclave seals these under its escrow key for every transaction it signs, and its relay archives the sealed copy to storage that refuses overwrites.

Collecting a batch of fee notes

A collection spends from one to twenty fee notes of one token at once. The treasury's prover builds a proof off chain, then anyone submits it and the pool pays out.

sequenceDiagram
participant T as Treasury prover
participant M as Chain mirror
T->>T: open escrowed fee notes
T->>M: fee note paths, nullifier gaps
M-->>T: witnesses at recent roots
T->>T: fee-collection proof

1. The treasury's prover gathers openings and witnesses

The escrowed fee notes open with the fee enclave's escrow key, the key they were sealed under. From a chain mirror it fetches each note's path in the fee commitment tree and a witness that the note's fee nullifier is not yet in the fee-nullifier tree.

2. The treasury's prover proves the batch

The fee-collection proof shows that the collector knows the opening of the treasury owner commitment, and that:

  • each note is in the fee commitment tree at a recent root, owned by that commitment and in the named token;
  • each note's fee nullifier is derived from the treasury's nullifier key and is not yet in the fee-nullifier tree;
  • the notes' amounts add up to the stated total.

In a batch of fewer than twenty, the remaining positions are padding.

sequenceDiagram
participant S as Submitter
participant P as Shielded pool
participant X as Treasury token account
S->>P: fee collection
P->>P: check treasury account
P->>P: resolve roots, verify proof
P->>P: record real nullifiers
P->>X: total from pool token account

3. Anyone submits the collection

The transaction needs one signer, who pays the Solana fee. The submitter chooses when the fees move, not where they go.

4. The shielded pool checks the request and the destination

The program checks that the batch holds between one and twenty real notes and that the token matches. It refuses any destination other than the treasury payout account governance set for the token.

5. The shielded pool resolves the roots and verifies the proof

Both roots the proof used must still be recent; a fee commitment tree root stays usable while it is among the tree's 512 most recent. The program then verifies the proof under the fee-collection key compiled into it.

6. The shielded pool records the nullifiers and pays the total

The program records each real fee nullifier, refusing one already recorded, so no fee note is collected twice. It then pays the total from the pool token account to the treasury payout account, or in lamports to the treasury wallet for SOL. It refuses a destination that is the pool's own account, because a transfer to itself would spend the fee notes and move nothing.

Escape-hatch fees

The escape hatch charges the default withdrawal rate on the note it spends and adds the fee to a public accrual for that pool index and token. Anyone may create the accrual, and an escape that owes a fee fails until it exists. Anyone may also collect it: the collection pays the whole balance to the treasury payout account for the token and zeroes the counter. As with protocol fees, the submitter decides only when the fees move.

Relayer tips

A relayer's tip is taken from the sender's notes and credited to a public accrual keyed by the relayer's address and the token. The accrual's address is part of the transaction's context digest, which ties a paid transaction to one relayer (see relayers). Only the relayer can collect, because it must sign. The collection pays everything accrued in one token to any account of that token the relayer names, and zeroes the counter.

Setting the treasury payout account

Only a governance proposal can set the treasury payout account for a token, and only once the proposal's timelock has elapsed; anyone may then execute it. See governance and upgrades.

What collection reveals

A protocol fee collection publishes the token, the total, the number of real notes and their fee nullifiers. It does not reveal which transactions the notes came from, because fee nullifiers are derived with the treasury's own key and match no user nullifier. An escape-hatch or tip collection reveals only what its public counter already showed.

What can go wrong

  • The destination is not the treasury payout account governance set for the token, or governance has set none.
  • The proof's roots are no longer recent, or a fee note has not yet been inserted into the fee commitment tree.
  • A fee note in the batch was already collected, which fails the whole collection.
  • An escape-hatch accrual has a zero balance, or an escape that owes a fee runs before its accrual exists.
  • A SOL payout to an empty wallet is below the rent-exempt minimum.
  • Governance names the pool's own account as the payout account, and every collection in that token fails until it names another. The fees stay collectable.

Last updated

On this page