> For AI agents: the documentation index is at https://docs.umbraprivacy.com/llms.txt, and every page is available as Markdown at its URL with .md appended.

# The fee enclave (/docs/fees/fee-enclave)

The fee enclave is one of the six attesters every value transaction needs. It opens the transaction's three fee commitments, checks them against the fee rules and any discount it grants, and signs. Its signing key lives in an AWS Nitro enclave, and the chain accepts that key only after checking the enclave's attestation.

## What it is sent

Your device sends the fee enclave one request per value transaction, encrypted to the enclave with a hybrid of ML-KEM-768 and X25519 and sealed with AES-256-GCM. Inside it are:

- the transaction's digest preimage and the account addresses the context digest binds, which together are the transaction's public payload;
- the openings of the three fee commitments: for each of the transaction's three token slots, the token, the fee total and two blinding values;
- the opening of the fee voucher, the commitment that records any fee reduction the transaction receives;
- a statement of the program the transaction was made through, if any, which the enclave checks against the transaction's memo digest.

The public payload is what the chain will show once the transaction lands. The fee openings and the voucher are the private part, and they are why the enclave exists: the proof shows the fee totals were computed correctly from the private notes, and the enclave confirms what those totals are without anyone else seeing them.

## What it checks

The enclave runs these checks in order and stops at the first that fails.

### 1. The request is for its cluster

The enclave serves one guardian cluster, fixed when it starts. It compares the cluster identifier in the transaction's preimage with its own and refuses a request for any other cluster. This keeps a signature made for one deployment from being replayed into another.

### 2. The payload envelope is well formed

It parses the envelope at the head of the payload and refuses one whose counts are out of range: more token entries, deposit lanes or withdrawal lanes than the shape allows, a lane or the relayer tip pointing at a token entry that does not exist, a program-owned-spend flag that is neither 0 nor 1, or a token table that is not in strictly ascending order. These are structural checks; the proof enforces the rest.

### 3. No fee or voucher commitment uses a zero blinding

Each fee commitment is a Poseidon hash of the token, the fee total and two blinding values. A zero blinding would let anyone who guesses the token and total recompute the commitment and confirm the guess, so the enclave refuses a request in which either blinding of any slot is zero. It refuses a zero voucher blinding for the same reason: without one, the voucher commitment is a hash of the transaction's public nullifiers and a voucher table with few possible values, so anyone could tell which discount the transaction received.

### 4. The enclave writes the voucher from its schedule

The enclave carries a discount schedule, set by its operator when it starts: rows of program, token and four reductions in basis points, one per kind of movement. It first checks the program statement. The named program must be bound into the transaction's memo digest exactly as the client says, or the request is refused. It then writes the voucher itself. For each of the three token slots it looks up the row for the proven program and that token, and fills the voucher entry with that row's reductions, or with an empty entry when there is no row or no program. Whatever the client put in the voucher is overwritten. With an empty schedule every entry comes out empty, so no transaction gets a discount.

Each reduction is written as the field's negation of its basis points, between 0 and 10,000, so the circuit, which adds a voucher entry to the rate, subtracts it. The circuit itself does not bound the entry; it only refuses a resulting rate below zero or above 10,000 basis points. The enclave never writes a positive entry, and that is the only thing that keeps a voucher from raising a rate. See [discounts](https://docs.umbraprivacy.com/docs/fees/fee-model#which-rate-applies).

### 5. Every token that moves publicly has a fee slot

Each token named by a live deposit lane or withdrawal lane must also be one of the three fee slots' tokens. Otherwise the transaction could move a token whose fee nobody had committed to, and the enclave refuses it.

### 6. The commitments are recomputed

The enclave recomputes the three fee commitments from their openings, and the voucher commitment from the voucher and the transaction's input nullifiers. It never accepts a commitment it was handed.

### 7. The digest and public input are rebuilt

It writes the commitments it just computed into its copy of the preimage, over whatever the client put there, and from that and the bound addresses rebuilds the transaction's context digest and the aggregate public input the zero-knowledge proof must match. If the client's fee or voucher commitments differ from the recomputed ones, the digest the enclave signs differs from the transaction's, and the combined attester signature will not verify on chain.

If every check holds, the enclave signs the context digest with its BLS authorisation key and returns the signature with the fee commitments it computed. It also seals the fee openings, the voucher and the program statement into an escrow envelope under a key derived from its QuorumOS quorum key, bound to the cluster and the context digest, and returns that envelope too. Every response, accepted or refused, is signed with the enclave's per-boot response key and carries a Nitro attestation document over the response.

The enclave does not verify the zero-knowledge proof, screen anyone or check nullifiers. The shielded pool program verifies the proof and the nullifiers, and the screener screens.

## What it sees

The fee enclave learns each fee slot's token and fee total in plaintext, inside the enclave. Fees are basis points of what moves, so a fee total together with the rate reveals roughly how much value moved in that token. It also sees the transaction's public payload before the chain does, and the program statement. It is not sent note openings or identity commitments, so it does not learn who the sender or recipients are.

## How the chain trusts it

The fee enclave program keeps one record for the enclave. The record holds the enclave's BLS authorisation key, the treasury owner commitment, the controller key that signs the enclave's own boot transactions, the measurement its last attestation proved, and four flags: attested, activated, frozen and measurement mismatched. The program also keeps an attestation policy: up to four approved enclave measurements, none of which may be a debug-mode enclave's, and a freshness window between 1,600 and 10,800 slots. The program counts every slot as 200 milliseconds when it compares the window with a document's timestamp, whatever the chain's actual slot time, so the window spans 320 to 2,160 seconds.

The shielded pool reads that record for every value transaction. It refuses the transaction unless the record's status is Active, adds the authorisation key to the combined attester signature's key, and stages the three fee commitments for the fee tree, bound to the record's treasury owner commitment. That is how the treasury identity comes to own the fee notes: the enclave's attestation fixes the commitment, and the pool copies it from the record.

### Booting

A boot takes two transactions, both signed by the enclave's controller.

1. **`advance_boot`** records the enclave public key of the new boot and moves the boot nonce on by one. It clears the authorisation key, the treasury commitment and the measurement, and marks the attestation unverified, so the enclave stops counting as Active until the boot is finished.
2. **`finalize_boot`** takes the authorisation key, a proof of possession for it, the treasury owner commitment, the measurement and the attestation document's timestamp. The program checks that the Nitro attestation verifier has created a marker, whoever opened it, for a document whose user data is the Keccak-256 hash of the recorded enclave key, the authorisation key, the treasury owner commitment and the boot nonce, and that the marker records the root-anchor epoch the verifier's configuration holds now. It also requires the measurement to be in the approved set, the timestamp to be within the freshness window of the chain's clock and later than any timestamp the program has accepted before, and the proof of possession to verify.

The boot nonce in the user data means a document made for an earlier boot cannot finish a later one. The treasury owner commitment in the user data means the enclave's own measured code chose which treasury its fee notes pay. The anchor epoch means a document verified under a root anchor governance has since replaced cannot finish a boot, even inside the freshness window. The proof of possession stops anyone registering a key computed from other attesters' keys. How the marker itself is produced is on [trusted execution](https://docs.umbraprivacy.com/docs/cryptography/trusted-execution).

`finalize_key_rotation` does the same for a running enclave that moves to fresh keys without a new `advance_boot`.

### Status

The record's status is recomputed from its flags whenever one changes. The chart below shows the statuses and what moves the record between them.

```mermaid
stateDiagram-v2
[*] --> Registered: governance registers
Registered --> Attested: finalize_boot
Attested --> Active: governance activates
Active --> Attested: governance deactivates
Active --> Registered: advance_boot or measurement dropped
Attested --> Registered: advance_boot or measurement dropped
Active --> Frozen: governance freezes
Frozen --> Active: governance unfreezes
Registered --> MeasurementEvicted: anyone evicts
Registered --> Decommissioned: controller decommissions
MeasurementEvicted --> [*]: governance closes
Decommissioned --> [*]: governance closes
```

*The fee enclave record's statuses and the action behind each change.*

A frozen flag wins over everything else, so a frozen record is Frozen whatever its other flags say, and unfreezing returns it to whichever status the remaining flags give. A record whose measurement the policy no longer approves, or whose attestation is unverified, is Registered. An attested record that has not been activated is Attested. Only an attested, activated, unfrozen record on an approved measurement is Active.

Two statuses are terminal. The controller can decommission the enclave from any status except Frozen; the chart draws that arrow from Registered only, to stay readable. Anyone can evict it once its attested measurement has been dropped from the policy and 5,400 slots have passed since that policy change, which gives the operator time to boot onto an approved image first. A terminal record can only be closed, which returns its rent to whoever paid for it, and the program then serves a new record.

### Governance

Every change to the record or the policy, other than the controller's boots and decommission and anyone's eviction, is a timelocked proposal of the program's governance: replacing the attestation policy, registering the enclave under a controller, rotating the controller, activating, deactivating, freezing, unfreezing, closing the record and reassigning these authorities. Each operation has its own authority key and delay, a proposal opens after the delay and may execute for 216,000 slots after that. See [governance and upgrades](https://docs.umbraprivacy.com/docs/security/governance).

A policy change takes effect when it executes. If it drops the measurement the enclave is running, the record leaves Active in that same transaction, and every value transaction stops until the enclave boots onto an approved image.

## If it is unavailable

The fee enclave's key is part of the combined attester signature, so no value transaction can land while the enclave is down, frozen or not Active. Registration does not involve it, and neither does the escape hatch. See [liveness](https://docs.umbraprivacy.com/docs/security/liveness).

## Reference

**Instructions**

| Tag  | Instruction                      | Version | Payload bytes | Accounts |
| ---- | -------------------------------- | ------- | ------------- | -------- |
| `0`  | `initialize_governance`          | v0      | 41            | 3        |
| `1`  | `initialize_config`              | v0      | 262           | 3        |
| `2`  | `governance_propose`             | v0      | 33            | 4        |
| `3`  | `governance_cancel`              | v0      | 0             | 4        |
| `4`  | `execute_reassign_authority`     | v0      | 41            | 5        |
| `5`  | `execute_set_attestation_policy` | v0      | 262           | 6        |
| `6`  | `execute_register_enclave`       | v0      | 32            | 6        |
| `7`  | `execute_rotate_controller`      | v0      | 32            | 5        |
| `8`  | `execute_close_enclave`          | v0      | 0             | 6        |
| `9`  | `execute_activate`               | v0      | 0             | 5        |
| `10` | `execute_deactivate`             | v0      | 0             | 6        |
| `11` | `execute_freeze`                 | v0      | 0             | 6        |
| `12` | `execute_unfreeze`               | v0      | 0             | 5        |
| `13` | `advance_boot`                   | v0      | 32            | 2        |
| `14` | `finalize_boot`                  | v0      | 296           | 5        |
| `15` | `finalize_key_rotation`          | v0      | 328           | 5        |
| `16` | `decommission`                   | v0      | 0             | 2        |
| `17` | `evict_unapproved_enclave`       | v0      | 0             | 2        |
| `18` | `propose_reassign_authority`     | v0      | 41            | 5        |

Read at build time from `contracts/solana/tools/umbra-manifest/manifest/fee-enclave.json`.

## Related

- [The fee model](https://docs.umbraprivacy.com/docs/fees/fee-model): The rules the enclave enforces.
- [Guardian nodes](https://docs.umbraprivacy.com/docs/guardian-nodes): What they do, and what they can and cannot do.
- [Trust assumptions](https://docs.umbraprivacy.com/docs/security/assumptions#who-holds-which-power): Every party's powers side by side.


Last updated 12 October 2026
