> 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`.
# Fee AMM Specification

## Abstract

This specification defines a system of one-way Automated Market Makers (AMMs) designed to facilitate gas fee payments from a user using one stablecoin (the `userToken`) to a validator who prefers a different stablecoin (the `validatorToken`). Each AMM handles fee swaps from a `userToken` to a `validatorToken` at one price (0.9970 `validatorToken` per `userToken`), and allows rebalancing in the other direction at another fixed price (1.0015 `userToken` per `validatorToken`).

## Motivation

Current blockchain fee systems typically require users to hold native tokens for gas payments. This creates friction for users who prefer to transact in stablecoins.

The Fee AMM is a dedicated AMM for trading between stablecoins, which can only be used by the protocol (and by arbitrageurs rebalancing it to keep it balanced). The protocol automatically collects fees in many different coins and immediately swaps them (paying a constant price) into the token preferred by the validator. Fees accumulate in the FeeManager, and validators can claim them on-demand.

The system is designed to minimize several forms of MEV:

* **No Probabilistic MEV**: The fixed fee swap rate prevents profitable backrunning of fee swaps. There is no way to profitably spam the chain with transactions hoping an opportunity might arise.
* **No Sandwich Attacks**: Fee swaps execute at a fixed rate, eliminating sandwich attack vectors.
* **Top-of-Block Auction**: The main MEV in the AMM (from rebalancing) occurs as a single race at the top of the next block rather than creating probabilistic spam throughout.

## Specification

### Overview

The Fee AMM implements two distinct swap mechanisms:

1. **Fee Swaps**: Fixed-rate swaps at a price of `0.9970` (validator token per user token) from `userToken` to `validatorToken`
2. **Rebalancing Swaps**: Fixed-rate swaps at a price of `1.0015` (user token per validator token) from `validatorToken` to `userToken`

### Core Components

#### 1. FeeAMM Contract

The primary AMM contract managing liquidity pools and swap operations.

##### Pool Structure

```solidity
struct Pool {
    uint128 reserveUserToken;           // Reserve of userToken
    uint128 reserveValidatorToken;      // Reserve of validatorToken
}
```

Each pool is directional: `userToken` → `validatorToken`. For a pair of tokens A and B, there are two separate pools:

* Pool(A, B): for swapping A to B at fixed rate of 0.997 (fee swaps) and B to A at fixed rate of 0.9985 (rebalancing)
* Pool(B, A): for swapping B to A at fixed rate of 0.997 (fee swaps) and A to B at fixed rate of 0.9985 (rebalancing)

##### Constants

* `M = 9970` (scaled by 10000, representing 0.9970)
* `N = 9985` (scaled by 10000, representing 0.9985)
* `SCALE = 10000`
* `MIN_LIQUIDITY = 1000`

##### Key Functions

```solidity
function getPool(
    address userToken,
    address validatorToken
) external view returns (Pool memory)
```

Returns the pool structure for a given token pair.

```solidity
function getPoolId(
    address userToken,
    address validatorToken
) external pure returns (bytes32)
```

Returns the pool ID for a given token pair (used internally for pool lookup).

```solidity
function rebalanceSwap(
    address userToken,
    address validatorToken,
    uint256 amountOut,
    address to
) external returns (uint256 amountIn)
```

Executes rebalancing swaps from `validatorToken` to `userToken` at fixed rate of 1.0015 (user token per validator token). Can be executed by anyone. Calculates `amountIn = (amountOut * N) / SCALE + 1` (rounds up). Updates reserves immediately. Emits `RebalanceSwap` event.

```solidity
function mint(
    address userToken,
    address validatorToken,
    uint256 amountUserToken,
    uint256 amountValidatorToken,
    address to
) external returns (uint256 liquidity)
```

Adds liquidity to a pool with both tokens. First provider sets initial reserves and must burn `MIN_LIQUIDITY` tokens. Subsequent providers must provide proportional amounts. Receives fungible LP tokens representing pro-rata share of pool reserves.

```solidity
function mint(
    address userToken,
    address validatorToken,
    uint256 amountValidatorToken,
    address to
) external returns (uint256 liquidity)
```

Single-sided liquidity provision with validator token only. Treats the deposit as equivalent to performing a hypothetical `rebalanceSwap` first at rate `n = 0.9985` until the ratio of reserves match, then minting liquidity by depositing both. Formula: `liquidity = amountValidatorToken * _totalSupply / (V + n * U)`, where `n = N / SCALE`. Rounds down to avoid over-issuing LP tokens. Updates reserves by increasing only `validatorToken` by `amountValidatorToken`. Emits `Mint` event with `amountUserToken = 0`.

```solidity
function burn(
    address userToken,
    address validatorToken,
    uint256 liquidity,
    address to
) external returns (uint256 amountUserToken, uint256 amountValidatorToken)
```

Burns LP tokens and receives pro-rata share of reserves. Emits `Burn` event.

```solidity
function executeFeeSwap(
    address userToken,
    address validatorToken,
    uint256 amountIn
) internal returns (uint256 amountOut)
```

Executes a fee swap immediately. Calculates `amountOut = (amountIn * M) / SCALE`. Only executed by the protocol during transaction execution. Emits `FeeSwap` event. Note: `FeeSwap` events are not emitted for immediate swaps.

```solidity
function checkSufficientLiquidity(
    address userToken,
    address validatorToken,
    uint256 maxAmount
) internal view
```

Verifies sufficient validator token reserves for the fee swap. Calculates `maxAmountOut = (maxAmount * M) / SCALE`. Reverts if insufficient liquidity.

#### 2. FeeManager Contract

Tempo introduces a precompiled contract, the `FeeManager`, at the address `0xfeec000000000000000000000000000000000000`.

The `FeeManager` is a singleton contract that implements all the functions of the Fee AMM for every pool. It handles the collection and refunding of fees during each transaction, executes fee swaps immediately, stores fee token preferences for users and validators, and accumulates fees for validators to claim via `distributeFees()`.

##### Key Functions

```solidity
function setUserToken(address token) external
```

Sets the default fee token preference for the caller (user). Requires token to be a USD TIP-20 token. Emits `UserTokenSet` event. Access: Direct calls only (not via delegatecall).

```solidity
function setValidatorToken(address token) external
```

Sets the fee token preference for the caller (validator). Requires token to be a USD TIP-20 token. Cannot be called during a block built by that validator. Emits `ValidatorTokenSet` event. Access: Direct calls only (not via delegatecall).

```solidity
function collectFeePreTx(
    address user,
    address userToken,
    uint256 maxAmount
) external
```

Called by the protocol before transaction execution. The fee token (`userToken`) is determined by the protocol before calling using logic that considers: explicit tx fee token, setUserToken calls, stored user preference, tx.to if TIP-20. Reserves AMM liquidity if user token differs from validator token. Collects maximum possible fee from user. Access: Protocol only (`msg.sender == address(0)`).

```solidity
function collectFeePostTx(
    address user,
    uint256 maxAmount,
    uint256 actualUsed,
    address userToken
) external
```

Called by the protocol after transaction execution. The validator token and fee recipient are inferred from `block.coinbase`. Calculates refund amount: `refundAmount = maxAmount - actualUsed`. Refunds unused tokens to user. If user token differs from validator token, executes the fee swap immediately and accumulates the output for the validator. Access: Protocol only (`msg.sender == address(0)`).

```solidity
function distributeFees(address validator, address token) external
```

Allows anyone to trigger distribution of accumulated fees for a specific token to a validator. Transfers all accumulated fees in the specified token to the validator address. If no fees have accumulated for that token, this is a no-op.

```solidity
function collectedFees(address validator, address token) external view returns (uint256)
```

Returns the amount of accumulated fees for a validator and specific token that can be claimed via `distributeFees()`.

### Swap Mechanisms

#### Fee Swaps

* **Rate**: Fixed at m=0.9970 (validator receives 0.9970 of their preferred token per 1 user token that user pays)
* **Direction**: User token to validator token
* **Purpose**: Convert tokens paid by users as fees to tokens preferred by validators
* **Settlement**: Immediate during transaction execution
* **Access**: Protocol only
* **Routing**: If the direct `userToken → validatorToken` pool has insufficient liquidity, the Fee AMM tries one two-hop route through `userToken.quoteToken()`. Both hops must have sufficient liquidity. The validator receives `floor(floor(actualSpending × 9970 / 10000) × 9970 / 10000)` on the two-hop path.

#### Rebalancing Swaps

* **Rate**: Fixed at n=0.9985 (swapper receives 1 of the user token for every 0.9985 that they put in of the validator's preferred token)
* **Direction**: Validator token to user token
* **Purpose**: Refill reserves of validator token in the pool
* **Settlement**: Immediate
* **Access**: Anyone

### Fee Collection Flow

1. **Pre-Transaction**:
   * Protocol determines user's fee token using logic that considers: explicit tx fee token, setUserToken calls, stored user preference, tx.to if TIP-20
   * Protocol calculates maximum gas needed (`maxAmount = gasLimit * maxFeePerGas`)
   * `FeeManager.collectFeePreTx(user, userToken, maxAmount)` is called:
     * If user token differs from validator token, checks AMM has sufficient liquidity via `checkSufficientLiquidity()`
     * Collects maximum fee from user using `transferFeePreTx()`
   * If any check fails (insufficient balance, insufficient liquidity), transaction is invalid

2. **Post-Transaction**:
   * Calculate actual gas used (`actualUsed = gasUsed * gasPrice`)
   * `FeeManager.collectFeePostTx(user, maxAmount, actualUsed, userToken)` is called:
     * Validator token and fee recipient are inferred from `block.coinbase`
     * Calculates refund: `refundAmount = maxAmount - actualUsed`
     * Refunds unused tokens to user via `transferFeePostTx()`
     * If user token differs from validator token and `actualUsed > 0`, executes fee swap immediately via `executeFeeSwap()`
     * Accumulates swapped fees for the validator

3. **Fee Distribution**:
   * Validators (or anyone) can call `distributeFees(validator)` at any time to transfer accumulated fees to the validator

### Events

```solidity
event RebalanceSwap(
    address indexed userToken,
    address indexed validatorToken,
    address indexed swapper,
    uint256 amountIn,
    uint256 amountOut
)
event FeeSwap(
    address indexed userToken,
    address indexed validatorToken,
    uint256 amountIn,
    uint256 amountOut
)
event Mint(
    address indexed sender,
    address indexed userToken,
    address indexed validatorToken,
    uint256 amountUserToken,
    uint256 amountValidatorToken,
    uint256 liquidity
)
event Burn(
    address indexed sender,
    address indexed userToken,
    address indexed validatorToken,
    uint256 amountUserToken,
    uint256 amountValidatorToken,
    uint256 liquidity,
    address to
)
event UserTokenSet(address indexed user, address indexed token)
event ValidatorTokenSet(address indexed validator, address indexed token)
```

`Transfer` events are emitted as usual for transactions, with the exception of paying gas fees via TIP20 tokens. For fee payments, a single `Transfer` event is emitted post execution to represent the actual fee amount consumed (i.e. `gasUsed * gasPrice`).

### Gas

Fee swaps are designed to be gas-free from the user perspective. The pre-tx and post-tx steps in each transaction do not cost any gas.
