<!-- tempo-docs-context -->
> Source: [/docs/protocol/fees/spec-fee](https://tempo.xyz/developers/docs/protocol/fees/spec-fee.md)
> Section: [Tempo EVM](https://tempo.xyz/developers/docs/development.md). Build on Tempo EVM: wallets and keys, transactions and fees, TIP-20 Tokens, contracts, exchange, network details, nodes, protocol specifications, and changelog.
> [Documentation index](https://tempo.xyz/developers/llms.txt) · [Docs skill](https://tempo.xyz/developers/SKILL.md)

> Tempo MCP: Use `search`, `find_pages`, `read_page`, and `code` at `https://mcp.tempo.xyz` for Tempo and related documentation.
> If an indexed page is missing or reflects an older site, read this site’s [documentation index](https://tempo.xyz/developers/llms.txt) and Markdown pages directly.

[**Specification**](https://tempo.xyz/developers/docs/protocol)

# Fee specification: the canonical reference

## Abstract

This spec lays out how fees work on Tempo, including how fees are calculated, who pays them, and how the default fee token for a transaction is determined.

## Motivation

Tempo has no native token. Transaction fees are paid directly in USD-denominated stablecoins. This design removes the need for users or applications to hold volatile assets for gas, keeping the entire payment experience USD-native.

Users can pay gas fees in any [TIP-20](https://tempo.xyz/developers/docs/protocol/tip20/spec) token whose currency is USD, as long as that stablecoin has sufficient liquidity on the enshrined [fee AMM](https://tempo.xyz/developers/docs/protocol/fees/spec-fee-amm) against the token that the current validator wants to receive.

In determining *which* token a user pays fees in, we want to maximize customizability (so that wallets or users can implement more sophisticated UX than is possible at the protocol layer), minimize surprise (particularly surprises in which a user pays fees in a stablecoin they did not expect to), and have sane default behavior so that users can begin using basic functions like payments even using wallets that are not customized for Tempo support.

## Fee units

Fees in the `max_priority_fee_per_gas` and `max_fee_per_gas` fields of transactions, as well as in the block's `base_fee_per_gas` field, are specified in **attodollars** (10^-18 USD) per gas. TIP-20 tokens have 6 decimal places: 1 base unit is 0.000001 token. For USD-denominated fee accounting, this is 1 **microdollar** (10^-6 USD). The fee in base units is calculated as `ceil(effective_gas_price * gas_used / 10^12)`.

The effective gas price includes the base fee and the applicable priority fee. Attodollars provide the precision needed to calculate fees below one token base unit per gas. Divide by 10^12 and round up to convert the total fee to base units.

### Base Fee Model

Tempo uses a bounded dynamic base fee. The base fee can fall when block gas usage is below target and rise back toward the cap when usage increases. The cap is set such that a TIP-20 transfer costs about $0.0006, with a quiet-period floor around $0.00003.

The bounded base fee combined with USD-denominated payment provides predictable unit economics. Applications can budget for transaction costs without exposure to native token price fluctuations.

Congestion is managed through:

* **Payment lanes**: Reserved blockspace for TIP-20 transfers as specified in the [Payment Lane Specification](https://tempo.xyz/developers/docs/protocol/blockspace/payment-lane-specification). Approximately 94% of blockspace is reserved for payment transactions, with the remaining 6% available for general computation. This allocation is conservative and the blockspace available for general computation may increase over time as total throughput scales.
* **Priority fees**: The `max_priority_fee_per_gas` field allows transactions to bid for faster inclusion during periods of high demand.
* **Block gas limits**: Standard per-block gas limits constrain total computation per block.

Payment lanes reserve capacity for eligible payment transactions. They do not guarantee inclusion or a fixed fee: the base fee responds to block gas usage, and payments still compete within the available capacity.

## Fee payment

Before the execution of each transaction, the protocol takes the following steps:

* Determine the [`fee_payer`](#fee-payer) of the transaction.
* Determine the `fee_token` of the transaction, according to the [rules for fee token preferences](#fee-token-preferences). If the fee token cannot be determined, the transaction is invalid.
* Compute the `max_fee` of the transaction as `gas_limit * gas_price`.
* Deduct `max_fee` from the `fee_payer`'s balance of `fee_token`. If `fee_payer` does not have sufficient balance in `fee_token`, the transaction is invalid.
* Reserve `max_fee` of liquidity on the [fee AMM](https://tempo.xyz/developers/docs/protocol/fees/spec-fee-amm) between the `fee_token` and the validator's preferred fee token. If there is insufficient liquidity, the transaction is invalid.

After the execution of each transaction:

* Compute the `refund_amount` as `(gas_limit - gas_used) * gas_price`.
* Credit the `fee_payer`'s address with `refund_amount` of `fee_token`.
* Log a `Transfer` event from the user to the [fee manager contract](https://tempo.xyz/developers/docs/protocol/fees/spec-fee-amm) for the net amount of the fee payment.

:::info[Atomic fee handling]
Fee-balance and liquidity checks happen before execution. A transaction that cannot fund its fees is invalid and cannot be included in that state; this is different from an included transaction whose application call reverts and still pays fees.
:::

## Fee payer

Tempo supports *sponsored transactions* in which the `fee_payer` is a different address from the `tx.origin` of the transaction. This is supported by Tempo's [new transaction type](https://tempo.xyz/developers/docs/protocol/transactions/spec-tempo-transaction), which has a `fee_payer_signature` field.

If no `fee_payer_signature` is provided, then the `fee_payer` of the transaction is its sender (`tx.origin`).

If the `fee_payer_signature` field is set, then it is used to derive the `fee_payer` for the transaction, as described in the [transaction spec](https://tempo.xyz/developers/docs/protocol/transactions/spec-tempo-transaction).

For purposes of [fee token preferences](#fee-token-preferences), the `fee_payer` is the account that chooses the fee token.

### Fee sponsorship flow

Presence of the `fee_payer_signature` field authorizes a third party to pay the transaction's gas costs while the original sender executes the transaction logic.

:::steps
#### Sender signs the transaction

The sender signs the transaction with their private key, signing over a blank fee token field. This means the sender delegates the choice of which fee token to use to the fee payer.

#### Fee payer selects and signs

The fee payer selects which fee token to use, then signs over the transaction.

#### Transaction submission

The fee token and fee payer signature is added to the transaction using the `fee_payer_signature` field and is then submitted.

#### Network validation

The network validates both signatures and executes the transaction.
:::

#### Validation

When `feePayerSignature` is present:

* Both sender and fee payer signatures must be valid
* Fee payer must have sufficient balance in the fee token
* Transaction is rejected if either signature fails or fee payer's balance is insufficient

## Fee token preferences

The protocol checks for token preferences in five ways, with this order of precedence:

1. Transaction (set by the `fee_token` field of the transaction)
2. Account (set on the FeeManager contract by the `fee_payer` of the transaction)
3. TIP-20 contract (if the transaction is calling `transfer` or `transferWithMemo` on a TIP-20 token contract, the transaction uses that token as its fee token)
4. Stablecoin DEX (for certain swap calls, the transaction uses the `tokenIn` argument as its fee token)
5. PathUSD (as a fallback)

The protocol checks preferences at each of these levels, stopping at the first one at which a preference is specified. At that level, the protocol performs the following checks. If any of the checks fail, the transaction is invalid (without looking at any further levels):

* The token must be a TIP-20 token whose currency is USD.
* The user must have sufficient balance in that token to pay the `gasLimit` on the transaction at the transaction's `gasPrice`.
* There must be sufficient liquidity on the [fee AMM](https://tempo.xyz/developers/docs/protocol/fees/spec-fee-amm), as discussed in that specification.

If no preference is specified at the transaction, account, or contract level, the protocol falls back to [pathUSD](#pathusd).

:::note
To avoid depending on the pathUSD fallback, set an explicit fee token preference. For most accounts, this is [OUSD](https://tempo.xyz/developers/docs/guide/ousd), the recommended stablecoin on Tempo. Call `setUserToken` on the FeeManager, or set `fee_token` on each transaction.
:::

### Transaction level

Tempo's [new transaction type](https://tempo.xyz/developers/docs/protocol/transactions/spec-tempo-transaction), allows transactions to specify a `fee_token` on the transaction. This overrides any preferences set at the account, contract, or validator level.

For [sponsored transactions](#fee-payer), the `tx.origin` address does not sign over the `fee_token` field (allowing the `fee_payer` to choose the fee token).

### Account level

An account can specify a fee token preference for all transactions for which it is the `fee_payer` (including both transactions it sponsors as well as non-sponsored transactions for which it is the `tx.origin`). This overrides any preference set at the contract or validator level.

To set its preference, the account can call the `setUserToken` function on the FeeManager precompile.

At this step, the protocol does one more check:

* If the transaction is not a [Tempo transaction](https://tempo.xyz/developers/docs/protocol/transactions/spec-tempo-transaction) *and* the transaction is a top-level call to the `setUserToken` function on the FeeManager, then the protocol checks the `token` argument to the function:
  * If that token is a TIP-20 whose currency is USD, that token is used as the fee token (unless the transaction specifies a `fee_token` at the [transaction level](#transaction-level)).
  * If that token is not a TIP-20 or its currency is not USD, the transaction is invalid.

### TIP-20 contracts

If the top-level call of a transaction is to one of the following functions on a TIP-20 token whose currency is USD:

* `transfer(address to, uint256 amount)`
* `transferWithMemo(address to, uint256 amount, bytes32 memo)`

then that TIP-20 token is used as the user's fee token for that transaction (unless there is a preference specified at the [transaction](#transaction-level) or [account](#account-level) level).

For [Tempo Transactions](https://tempo.xyz/developers/docs/protocol/transactions/spec-tempo-transaction), this rule applies only if *all* top-level calls are to the same TIP-20 contract, and each such call is to one of the functions listed above, with `fee_payer == tx.origin`.

### Stablecoin DEX contract

If the top-level call of a transaction is to the [Stablecoin DEX](https://tempo.xyz/developers/docs/protocol/exchange/spec) contract, the function being called is either `swapExactAmountIn` or `swapExactAmountOut`, and the `tokenIn` argument to that function is the address of a TIP-20 token for which the currency is USD, then the `tokenIn` argument is used as the user's fee token for the transaction (unless there is a preference specified at the [transaction](#transaction-level) or [account](#account-level) level).

For [Tempo Transactions](https://tempo.xyz/developers/docs/protocol/transactions/spec-tempo-transaction), this rule applies only if there is only one top-level call in the transaction.

### pathUSD

If no fee preference is set at the transaction, account, or contract level, the protocol falls back to [pathUSD](https://tempo.xyz/developers/docs/protocol/exchange/quote-tokens#pathusd) as the user's fee token preference.

## Validator preferences

Validators can set a default fee token preference that determines which stablecoin they receive for transaction fees. When users pay in different tokens, the Fee AMM automatically converts to the validator's preferred token.

### Setting validator preference

To set their preference, validators call the `setValidatorToken` function on the FeeManager precompile:

```solidity
// Set your preferred fee token
feeManager.setValidatorToken(preferredTokenAddress);
```

Fees are converted to the chosen token when needed and accumulate in the FeeManager for the block's fee-recipient address. Anyone can call `distributeFees(validator, token)` to transfer those accumulated fees to that recipient. Query `validatorTokens(recipient)` instead of assuming a network-wide preference.

If validators have not specified a fee token preference, the protocol falls back to expecting pathUSD as their fee token.

## Gas Parameters

Tempo uses the following mainnet gas parameters:

| Parameter | Value |
|-----------|-------|
| Base fee cap | 12 billion attodollars per gas (`1.2 × 10^10`) |
| Base fee floor | 600 million attodollars per gas (`6 × 10^8`) |
| Total block gas limit | 500M gas |
| General gas limit | 30M gas/block |
| Precompile input | `30 × ceil(calldata_length / 32)` gas ([TIP-1100](https://tips.sh/1100)) |
| Duplicate validation | 20 gas per value processed ([TIP-1105](https://tips.sh/1105)) |

A standard TIP-20 transfer (~50,000 gas) costs approximately 600 microdollars ($0.0006) at the cap and approximately 30 microdollars ($0.00003) at the floor.

Precompile input charges include the selector and are deducted before dispatch and ABI decoding. Duplicate-check charges apply to AccountKeychain call scopes and ZoneFactory role lists before sorting. T11 requires canonical ABI encoding without gaps, overlaps, trailing bytes, or nonzero padding. T12 permits trailing bytes while retaining the other checks; see the [T12 activation schedule](https://tempo.xyz/developers/docs/protocol/upgrades/t12).

### Removing validator preference

The zero address is not a valid fee token, so `setValidatorToken(address(0))` reverts. To choose the fallback token explicitly, set pathUSD:

```solidity
feeManager.setValidatorToken(0x20c0000000000000000000000000000000000000);
```

## Fee lifecycle

This section describes the complete flow of how fees are collected, converted, and distributed from user to validator.

### Fee flow steps

When a user submits a transaction on Tempo, fees are paid in their chosen stablecoin (determined by the [fee token preferences](#fee-token-preferences) hierarchy). If the validator prefers a different stablecoin, the Fee AMM automatically converts the user's payment to the validator's preferred token.

#### 1. User submits transaction

The transaction is submitted with the fee token determined by the preference hierarchy.

#### 2. Pre-transaction collection

Before the transaction executes, the `FeeManager` contract collects the maximum possible fee amount from the user:

* Verifies the user has sufficient balance in their chosen fee token
* Checks if the Fee AMM has enough liquidity (if conversion is needed)
* Collects the maximum fee amount based on the transaction's gas limit

If either check fails, the transaction is rejected before execution.

#### 3. Transaction execution

The transaction executes normally. The actual gas consumed may be less than the maximum that was collected.

#### 4. Post-transaction refund

After execution, the `FeeManager`:

* Calculates the actual fee owed based on gas used
* Refunds any unused tokens to the user
* Queues the actual fee amount for conversion (if needed)

#### 5. Fee swap execution

If the user's fee token differs from the validator's preferred token, the fee swap executes immediately during the post-transaction step at a fixed rate of **0.9970 per hop** (a direct pool pays 0.9970 validator tokens per 1.0 user token before integer rounding).

If the user's fee token matches the validator's preference, no conversion is needed.

Fees accumulate in the FeeManager contract. Validators can claim their accumulated fees at any time by calling `distributeFees(validator, token)`.

### Fee swap mechanics

Each fee-swap hop uses a fixed rate of **0.9970**, rounded down to integer token base units. A two-hop route applies the rate twice. For a direct pool:

```
validatorTokenOut = userTokenIn × 0.9970
```

This means:

* User pays 1.0 USDG for fees
* Validator receives 0.9970 USDT (if that's their preferred token)
* The 0.003 (0.3%) difference goes to liquidity providers as a fee

### Example flow

For a direct USDG → USDT pool, the fee lifecycle is:

1. **Alice** wants to send a transaction and pays fees in **USDG** (her preferred token)
2. **Validator** prefers to receive fees in **USDT**
3. Alice's transaction has a max fee of 1.0 USDG
4. The FeeManager collects 1.0 USDG from Alice before execution
5. Transaction executes and uses 0.8 USDG worth of gas
6. The FeeManager refunds 0.2 USDG to Alice
7. The Fee AMM immediately swaps 0.8 USDG → 0.7976 USDT (0.8 × 0.9970)
8. The 0.7976 USDT is added to the validator's accumulated fees
9. Validator calls `distributeFees(validator, token)` to claim their accumulated fees
10. The conversion spread remains in the pool; LP returns depend on subsequent rebalancing and withdrawals

### Gas costs

Fee collection and refund use protocol-internal hooks, not public EVM calls with separate fixed gas charges. Use `eth_estimateGas` for the complete transaction rather than adding a fixed pre-transaction or post-transaction allowance.

For complete technical specifications on the Fee AMM mechanism, see the [Fee AMM Protocol Specification](https://tempo.xyz/developers/docs/protocol/fees/spec-fee-amm).
