The fee model
The three kinds of fee a value transaction can carry, how protocol fee rates are chosen and bounded, what stays private, and a worked example.
A value transaction can carry three kinds of fee: a protocol fee in basis points of what moves, a tip to the relayer that submits it, and Solana's own transaction fee. The protocol fee is private. The tip is public.
Protocol fees
Four kinds of movement
The transaction circuit looks at every pairing of a place value comes from with a place value goes to, and gives each pairing one of four actions. Value comes from a deposit lane (a public token account paying in) or from an input note. It goes to an output note or to a withdrawal lane (a public token account being paid).
Deposit. Value from a deposit lane into a note. This is tokens entering the pool.
Transfer. Value from an input note into an output note. This is a private transfer (Private Send), and also the change a transaction returns to its sender.
Withdraw. Value from an input note out through a withdrawal lane. This is tokens leaving the pool.
Pass-through. Value from a deposit lane straight out through a withdrawal lane in the same transaction, without ever resting in a note.
Each action has its own rate, so governance can price entering, moving and leaving differently.
How the minimum fee is computed
The prover supplies an allocation: how much of each source pays into each destination, token by token. The circuit checks that the allocation adds up exactly to each destination's amount and never draws more from a source than it holds, and that it pairs only a source and destination of the same token.
For each destination, the circuit then multiplies every amount allocated to it by the rate for that pairing's action and token, adds the products, divides by 10,000 and rounds up to the next base unit of the token. The fee the transaction declares for that destination must be at least that number. It may be more; it may not be less.
Rounding up per destination means splitting a payment into several smaller notes never lowers the fee: the code's own tests check that the rounded-up fees of the parts are never less than the rounded-up fee of the whole. The only way to pay a zero fee on a non-zero amount is a zero rate.
The fee is paid in the token being moved, out of the transaction's own inputs. The circuit proves, for each token, that inputs plus deposits equal outputs plus withdrawals plus protocol fees plus the relayer tip, so a fee cannot be created from nothing or skipped. See value conservation.
Which rate applies
The rate for each pairing is built up in four steps, shown below and explained after it.
- Default rateone per action, set by governance
- Per-token ratefrom the fee-rate tree, never above the default
- Discountthe fee enclave's voucher, if any
- Self-exemptionzero when value stays with the sender
- Applied rate
Default rates. The shielded pool's process policy holds one default rate per action. Governance sets them with the timelocked process_policy_set, and the program refuses any default above 500 basis points (5%). That cap is the program's, on what governance can set. The circuit has its own, looser bound: it refuses a default, and the final rate of any pairing, above 10,000 basis points (100%).
Per-token rates. The policy also holds the root of a fee-rate tree, two levels of eight, so room for 64 tokens. Each leaf is a Poseidon hash of the token and its four rates packed together. A transaction can prove that its token has a leaf and use that leaf's rates, and the circuit refuses a leaf rate above the action's default. A transaction can also simply prove nothing about a leaf and pay the defaults. Since a prover can always take that route, a per-token rate can only lower the fee below the default, never raise it.
Discounts. The fee enclave can grant a reduction for a given program and token. When a transaction proves it was made through that program, the enclave writes the reduction into the transaction's fee voucher. With no schedule configured, it writes an empty voucher and nobody gets a discount. See the fee enclave.
Inside the circuit a voucher entry is an adjustment: a field element the circuit adds to the rate, with no bound of its own. The circuit only requires the result to land between 0 and 10,000 basis points. What keeps the adjustment a reduction is the fee enclave. It writes either zero or the field's negation of a reduction between 0 and 10,000 basis points, so adding it subtracts the reduction, and a reduction larger than the rate makes the result fall below zero and the proof fail. It overwrites whatever voucher the client sent, and it signs a context digest computed with the voucher commitment it built itself, so a transaction whose proof uses any other voucher fails the combined attester signature.
Moving value to yourself. The policy holds one self-exemption flag per action. The circuit compares the owner of each source with the owner of each destination, treating your identity commitment and your platform wallet as the same owner. When the two match and the action's flag is set, that pairing's rate is zero. Change returning to you is the common case: with the transfer flag set, the change note of a private transfer pays nothing.
What stays private
Every value transaction stages three fee commitments in its pool's fee store, one per token slot, including slots that pay nothing, and an insert later appends them to the pool's fee tree. Each is a Poseidon hash of the token, the slot's fee total and two blinding values, so the chain learns neither which tokens paid nor how much. The pool binds each leaf to the treasury owner commitment held in the fee enclave's record, which is how the fee notes come to belong to the treasury identity. The treasury collects them later in batches. See fee collection.
The fee enclave, which checks the fee notes before it signs, does see each slot's token and total. See what the fee enclave sees.
The relayer tip
The relayer that submits a transaction can be paid a tip. Unlike the protocol fee, the tip's amount and token are public: both are part of the transaction's public input, so the program can credit them. A transaction that pays no tip must name no tip token.
The program credits the tip to an accrual account derived from the submitter's address and the token, and the relayer collects it later with claim_submitter_tip. Because the tip's token is public, paying a tip in a particular token tells observers that the transaction moved that token. See relayers.
Solana's transaction fee
Solana charges every transaction its own fee in SOL, paid by whoever signs as fee payer. For registration and for a deposit you submit yourself, that is your wallet. For a relayed transaction it is the relayer, which is one reason relayers charge a tip.
The escape hatch
An escape-hatch exit does not go through the fee enclave. The program computes the fee itself: the policy's withdrawal default rate applied to the note's amount, rounded up the same way. It holds the rest in escrow for 48 hours before it is released to the destination, and credits the fee to an escape-hatch fee accrual for that token. Anyone may later sweep that accrual with claim_escape_hatch_fee, which pays it only to the treasury payout account governance has set for the token. See the escape hatch.
A worked example
The numbers below are illustrative. Governance sets the real rates, and they can differ per token.
Suppose the transfer default is 10 basis points, the transfer self-exemption flag is set, USDC has no leaf in the fee-rate tree, and you hold a 1,000 USDC note. You send 400 USDC to someone else as a private transfer, and pay the relayer a 0.05 USDC tip.
- The 400 USDC note for the recipient is a transfer to another owner, so its fee is at least 400 × 10 / 10,000 = 0.4 USDC.
- Your change note returns value to your own identity. With the flag set, it is charged at zero.
- One fee commitment carries the 0.4 USDC for the treasury. The other two slots carry zero.
- The change note holds 1,000 − 400 − 0.4 − 0.05 = 599.55 USDC.
A chain observer sees the tip of 0.05 in USDC. It sees nothing about the 400, the 0.4 or the 599.55.
Related
Last updated