> Feedback: If these docs are stale, missing, or confusing, post sanitized feedback to `https://docs.tempo.xyz/api/feedback` with `source: "mcp"`, a short `message`, and any relevant `toolName`, `relatedResource`, or `client`.
# Fees

## 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](/docs/protocol/tip20/spec) token whose currency is USD, as long as that stablecoin has sufficient liquidity on the enshrined [fee AMM](/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_base_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. Since TIP-20 tokens have 6 decimal places — where 1 token unit = 1 **microdollar** (10^-6 USD) — the fee for a transaction can be calculated as `ceil(base_fee * gas_used / 10^12)`.

Attodollars provide sufficient precision for low-fee transactions. Since TIP-20 tokens have only 6 decimal places (microdollars), expressing fees directly in token units per gas would not provide enough precision for transactions with very low gas costs. By using attodollars (10^-18 USD) and dividing by 10^12 to convert to microdollars, the protocol ensures that even small fee amounts can be accurately represented and calculated.

### Base Fee Model

Tempo uses a fixed base fee rather than the dynamic base fee mechanism specified in EIP-1559. The base fee is set such that a TIP-20 transfer costs less than $0.001.

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

Congestion is managed through:

* **Payment lanes**: Reserved blockspace for TIP-20 transfers as specified in the [Payment Lane Specification](/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.

The payment lane mechanism ensures that payment transactions are not crowded out by other network activity. General network congestion from non-payment use cases cannot affect payment throughput or fees, providing the consistency that payment applications require.

## 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](/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](/docs/protocol/fees/spec-fee-amm) for the net amount of the fee payment.

:::info\[Atomic fee handling]
The protocol executes the max fee deduction and refund atomically. If insufficient liquidity is encountered at any point during fee calculation (for example, if a previous transaction by the same sender in the same block exhausted the fee token reserves), the entire transaction reverts.
:::

## 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](/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](/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`, `transferWithMemo`, or `startReward` 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](/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).

### Transaction level

Tempo's [new transaction type](/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](/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)`
* `startReward(uint256 amount, uint32 seconds_)`

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](/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](/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](/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](/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);
```

After setting a validator token preference, all fees collected in blocks the validator proposes will be automatically converted to the chosen token (if needed) and transferred to the validator's account.

On the Moderato testnet, validators currently expect alphaUSD (one of the tokens distributed by the faucet) as their fee token.

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

## Gas Parameters

As of [TIP-1010](https://github.com/tempoxyz/tempo/blob/main/tips/tip-1010.md), Tempo uses the following mainnet gas parameters:

| Parameter | Value |
|-----------|-------|
| Base fee | 20 billion attodollars per gas (`2 × 10^10`) |
| Total block gas limit | 500M gas |
| General gas limit | 30M gas/block |

A standard TIP-20 transfer (~50,000 gas) costs approximately 1,000 microdollars (0.1 cent / $0.001) at the base fee.

### Removing validator preference

To remove a validator token preference, set it to the zero address:

```solidity
// Remove validator token preference
feeManager.setValidatorToken(address(0));
```

## 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** (validator receives 0.9970 of their token per 1.0 user token paid).

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()`.

### Fee swap mechanics

Fee swaps always execute at a fixed rate of **0.9970**:

```
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

Here's a complete example of the fee lifecycle:

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()` to claim their accumulated fees
10. Liquidity providers earn 0.0024 USDT from the 0.3% fee

### Gas costs

The fee conversion process adds minimal overhead to transactions:

* **Pre-transaction**: ~5,000 gas for balance and liquidity checks
* **Post-transaction**: ~3,000 gas for refund and queue operations
* **Block settlement**: Amortized across all transactions in the block

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