Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions src/pages/docs/protocol/blockspace/overview.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Blockspace Overview
description: Technical specification for Tempo block structure including header fields, payment lanes, and system transaction ordering.
description: Technical specification for Tempo block structure including header fields and payment-lane gas accounting.
---

# Blockspace Overview
Expand All @@ -11,32 +11,26 @@ This specification defines the structure of valid blocks in the Tempo blockchain

## Motivation

Tempo blocks extend the Ethereum block format in multiple ways: there are new header fields to account for payment lanes and shared gas accounting, and system transactions are added to the block body for the fee AMM and other protocol operations. This specification contains all the modifications to the block format.
Tempo blocks extend the Ethereum block format with header fields for payment-lane gas accounting, sub-second timestamps, and optional consensus context.

## Specification

### Header fields
Tempo extends an Ethereum header with three extra scalars.
```rust title="Header struct"
pub struct Header {
```rust title="TempoHeader struct"
pub struct TempoHeader {
pub general_gas_limit: u64,
pub shared_gas_limit: u64,
pub timestamp_millis_part: u64,
pub inner: Header,
pub consensus_context: Option<TempoConsensusContext>,
}
```
- `inner` is the canonical Ethereum header (parent_hash, state_root, gas_limit, etc.).
- `general_gas_limit` and `shared_gas_limit` partition the canonical `gas_limit` for payment and non-payment capacity (see [payment lane specification](/docs/protocol/blockspace/payment-lane-specification)).
- `consensus_context` is optional consensus metadata and is absent on older blocks.
- `general_gas_limit` and `shared_gas_limit` are lane-specific execution limits. The canonical `inner.gas_limit` remains a separate block-wide limit (see [payment lane specification](/docs/protocol/blockspace/payment-lane-specification)).
- `timestamp_millis_part` stores the sub‑second component; the full timestamp is `inner.timestamp * 1000 + timestamp_millis_part` .

### Block body
The block body in Tempo retains the canonical Ethereum block body structure, with the addition of system transactions. Transactions are ordered in the following sections:

1. Start-of-block system transaction(s).
2. Proposer lane transactions, subject to `general_gas_limit` on non-payment transactions.
3. Remaining transactions that consume the shared gas budget.
4. Protocol-defined end-of-block system transactions, when required.

### System transactions

A valid Tempo block must include any required protocol-defined system transactions in the expected order before and after user transactions.
The block body retains the canonical Ethereum structure. User transactions are admitted and ordered under Tempo's payment-lane rules. Under the T4 and later rules, blocks do not require separate start-of-block or Fee AMM system transactions.
20 changes: 10 additions & 10 deletions src/pages/docs/protocol/exchange/providing-liquidity.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Provide liquidity to the DEX by placing limit orders or flip orders in the oncha

When your orders are filled, you earn the spread between bid and ask prices while helping facilitate trades for other users.

You can only place orders on pairs between a token and its designated quote token. All TIP-20 tokens specify a quote token for trading pairs. [pathUSD](/docs/protocol/exchange/quote-tokens#pathusd) can be used as a simple choice for a quote token.
You can only place orders on pairs between a token and its designated quote token. Every user-created TIP-20 has a designated TIP-20 quote token. [pathUSD](/docs/protocol/exchange/quote-tokens#pathusd) is the root quote token and is the exception: its own quote-token field is the zero address.

## Orderbook liquidity overview

Expand Down Expand Up @@ -40,30 +40,30 @@ function place(
- `token` - The token address you're trading (must trade against its quote token)
- `amount` - The amount of the token denominated in `token`
- `isBid` - `true` for a buy order, `false` for a sell order
- `tick` - The price tick: `(price - 1) * 100_000` where price is in quote token per token
- `tick` - The price tick: `(price - 1) * 100_000` where price is in quote token per token. Orders must use ticks divisible by `10`.

**Returns:**
- `orderId` - Unique identifier for this order

**Example: Place a bid to buy 1000 USDG at $0.9990**
```solidity
// tick = (0.9990 - 1) * 100_000 = -10
// tick = (0.9990 - 1) * 100_000 = -100
uint128 orderId = exchange.place(
USDG_ADDRESS,
1000e6, // Amount: 1000 USDG
true, // isBid: buying USDG
-10 // tick: price = $0.9990
-100 // tick: price = $0.9990
);
```

**Example: Place an ask to sell 1000 USDG at $1.0010**
```solidity
// tick = (1.0010 - 1) * 100_000 = 10
// tick = (1.0010 - 1) * 100_000 = 100
uint128 orderId = exchange.place(
USDG_ADDRESS,
1000e6, // Amount: 1000 USDG
false, // isBid: selling USDG
10 // tick: price = $1.0010
100 // tick: price = $1.0010
);
```

Expand Down Expand Up @@ -97,8 +97,8 @@ uint128 orderId = exchange.placeFlip(
USDG_ADDRESS,
1000e6, // Amount: 1000 USDG
true, // isBid: start as a buy order
-10, // tick: buy at $0.9990
10 // flipTick: sell at $1.0010 after filled
-100, // tick: buy at $0.9990
100 // flipTick: sell at $1.0010 after filled
);
```

Expand All @@ -114,7 +114,7 @@ Flip orders act like a liquidity pool position, automatically providing liquidit

## Understanding Ticks

Prices are specified using ticks with 0.1 basis point (0.001%) precision:
Prices use 0.1-basis-point tick units. Orders must be placed on 10-tick boundaries, so usable prices are 1 basis point (0.01%) apart:

**Tick Formula:** `tick = (price - 1) × 100_000`

Expand Down Expand Up @@ -185,7 +185,7 @@ You can only cancel your own orders. Attempting to cancel another user's order w

**Indexers and analytics:**

- Subscribe to the `OrderFlipped(orderId, newSide, newTick, ...)` event on the DEX
- Subscribe to `OrderFlipped(orderId, maker, token, amount, isBid, tick, flipTick)` on the DEX
- Key order state by `orderId` and let it change side and tick over its lifetime
- Do not expect `nextOrderId` to advance per flip

Expand Down
4 changes: 2 additions & 2 deletions src/pages/docs/protocol/fees/fee-amm/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ The Fee AMM (Automated Market Maker) is a dedicated system for converting transa

## How the Fee AMM converts stablecoin fees

When a user pays fees in a stablecoin that differs from the validator's preference, the Fee AMM automatically converts the payment:
When a direct pool can convert the user's fee token into the validator's preferred token, one fee-swap hop applies:

- **User pays**: 1.0 of their chosen stablecoin
- **Validator receives**: 0.9970 of their preferred stablecoin
- **Liquidity providers earn**: 0.003 (0.3%) as fees

This conversion happens automatically at the end of each block through batched swaps, preventing MEV attacks like sandwiching.
If a direct pool lacks liquidity, the protocol can route through the user's quote token. That route applies the `0.9970` rate once per hop. Conversion happens immediately during transaction execution at fixed prices, preventing MEV attacks like sandwiching.

## Fee AMM vs. Exchange

Expand Down
99 changes: 28 additions & 71 deletions src/pages/docs/protocol/fees/spec-fee-amm.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,24 +89,12 @@ Executes rebalancing swaps from `validatorToken` to `userToken` at fixed rate of
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`.
Adds validator-token liquidity to a pool. A new pool mints `amountValidatorToken / 2 - MIN_LIQUIDITY` and locks `MIN_LIQUIDITY`. Later deposits mint `amountValidatorToken * _totalSupply / (V + n * U)`, where `n = N / SCALE`. Rounds down, updates the validator-token reserve, and emits `Mint`.

```solidity
function burn(
Expand All @@ -127,23 +115,15 @@ function executeFeeSwap(
) 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
```
Executes a fee swap immediately. Calculates `amountOut = (amountIn * M) / SCALE`. Only executed by the protocol during transaction execution. No event is emitted for this internal swap.

Verifies sufficient validator token reserves for the fee swap. Calculates `maxAmountOut = (maxAmount * M) / SCALE`. Reverts if insufficient liquidity.
Before execution, the protocol's internal route planner checks whether the direct pool or the supported two-hop route has enough validator-token liquidity. This route-planning hook is not part of the public FeeManager ABI.

#### 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()`.
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(validator, token)`.

##### Key Functions

Expand All @@ -159,26 +139,7 @@ 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)`).
Fee collection runs through internal pre- and post-transaction protocol hooks. They reserve liquidity, collect the fee, refund the unused amount, execute any conversion, and credit the validator. They are not callable Solidity functions in the public FeeManager ABI.

```solidity
function distributeFees(address validator, address token) external
Expand All @@ -190,7 +151,7 @@ Allows anyone to trigger distribution of accumulated fees for a specific token t
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()`.
Returns the amount of accumulated fees for a validator and specific token that can be claimed via `distributeFees(validator, token)`.

### Swap Mechanisms

Expand All @@ -213,23 +174,20 @@ Returns the amount of accumulated fees for a validator and specific token that c

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()`
- For EIP-1559 and Tempo transactions, `maximumGasPrice = maxFeePerGas` and `effectiveGasPrice = min(maxFeePerGas, baseFeePerGas + maxPriorityFeePerGas)`; legacy transactions use `gasPrice` for both
- Protocol checks the payer can cover `ceil(gasLimit × maximumGasPrice / 10^12)`
- Protocol collects `ceil(gasLimit × effectiveGasPrice / 10^12)` units of the user's fee token
- If conversion is needed, the internal route planner reserves each hop's output liquidity using `floor(amountIn × 9970 / 10000)`
- 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
- Protocol calculates `actualFee = ceil(max(gasUsed - gasReservoir, 0) × effectiveGasPrice / 10^12)`
- Refunds the collected amount minus `actualFee`
- If the user's token differs from the validator's token and `actualFee > 0`, executes the planned fee swap immediately
- Accumulates the resulting fees for the validator

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

### Events

Expand All @@ -240,21 +198,15 @@ event RebalanceSwap(
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 sender,
address indexed to,
address indexed userToken,
address indexed validatorToken,
uint256 amountUserToken,
uint256 amountValidatorToken,
uint256 liquidity
)
);
event Burn(
address indexed sender,
address indexed userToken,
Expand All @@ -263,12 +215,17 @@ event Burn(
uint256 amountValidatorToken,
uint256 liquidity,
address to
)
event UserTokenSet(address indexed user, address indexed token)
event ValidatorTokenSet(address indexed validator, address indexed token)
);
event UserTokenSet(address indexed user, address indexed token);
event ValidatorTokenSet(address indexed validator, address indexed token);
event FeesDistributed(
address indexed validator,
address indexed token,
uint256 amount
);
```

`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`).
`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 after execution for the actual six-decimal token amount charged.

### Gas

Expand Down
Loading
Loading