From 2abca88d62c5013e24e681d137e689ba037b4fd0 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 15 Jan 2026 18:52:21 +0000 Subject: [PATCH 1/5] docs: add TIP-1007 specification for fee token introspection Add specification for getFeeToken() view function in FeeManager precompile that allows smart contracts to read the fee token being used for the current transaction during execution. This feature was requested by LayerZero/Stargate for their integration with Tempo's TIP-20 fee system. Refs: https://github.com/tempoxyz/tempo/issues/2085 --- docs/pages/protocol/tips/tip-1007.mdx | 182 ++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 docs/pages/protocol/tips/tip-1007.mdx diff --git a/docs/pages/protocol/tips/tip-1007.mdx b/docs/pages/protocol/tips/tip-1007.mdx new file mode 100644 index 0000000000..4f381795d8 --- /dev/null +++ b/docs/pages/protocol/tips/tip-1007.mdx @@ -0,0 +1,182 @@ +# Fee Token Introspection + +This document specifies the addition of fee token introspection functionality to the FeeManager precompile, enabling smart contracts to query the fee token being used for the current transaction. + +- **TIP ID**: TIP-1007 +- **Authors/Owners**: Georgios Konstantopoulos +- **Status**: Draft +- **Related Specs/TIPs**: [Fee Manager](/protocol/fees/spec-fee), [TIP-20](/protocol/tip20/spec) +- **Protocol Version**: TBD + +--- + +# Overview + +## Abstract + +TIP-1007 adds a `getFeeToken()` view function to the FeeManager precompile that returns the fee token address being used for the current transaction. This enables smart contracts to introspect which TIP-20 token is paying for gas fees during execution, allowing for dynamic logic based on the fee token choice. + +## Motivation + +Tempo transactions support paying gas fees in any USD-denominated TIP-20 token via the fee token preference system. However, prior to this TIP, there was no way for a smart contract to determine which fee token is being used for the current transaction during execution. + +This capability is needed by protocols like LayerZero/Stargate that want to: + +- Adjust their internal logic based on which fee token is being used +- Provide fee token-aware pricing or routing decisions +- Emit events or logs that include the fee token for off-chain indexing +- Implement fee token-specific behavior in cross-chain messaging + +### Request Context + +This feature was requested by the LayerZero team during their Stargate integration with Tempo: + +> Is there any way to access or read the selected `fee_token` from *inside a smart contract* during execution? + +--- + +# Specification + +## New Function + +The following function is added to the `IFeeManager` interface: + +```solidity +interface IFeeManager { + // ... existing functions ... + + /// @notice Returns the fee token being used for the current transaction + /// @return The address of the TIP-20 token paying for gas fees + /// @dev This value is set by the protocol before transaction execution begins. + /// Returns address(0) if no fee token has been set (e.g., free transactions + /// or calls outside of a transaction context). + /// This function reads from transient storage, so it is safe to call + /// from staticcall contexts. + function getFeeToken() external view returns (address); +} +``` + +## Behavior + +### Fee Token Resolution + +The fee token returned by `getFeeToken()` is the same token that was resolved by the protocol during transaction validation, following the fee token preference rules: + +1. **Transaction field**: If the transaction specifies a `fee_token` field, that token is used +2. **User preference**: Otherwise, the fee payer's `userTokens` preference from FeeManager +3. **TIP-20 inference**: For direct TIP-20 calls (`transfer`, `transferWithMemo`, `distributeReward`), the target token is used +4. **Default**: Falls back to pathUSD (`0x20C0000000000000000000000000000000000000`) + +### Storage + +The fee token is stored in **transient storage** (EIP-1153) within the FeeManager precompile. This means: + +- The value is automatically cleared at the end of each transaction +- No persistent storage writes occur, minimizing gas costs +- The value is consistent across all calls within a transaction (including internal calls and subcalls) + +### Timing + +The fee token is set by the protocol in the `validate_against_state_and_deduct_caller` handler phase, before any user code executes. This ensures the value is available throughout the entire transaction execution. + +### Gas Cost + +Reading the fee token costs the standard warm transient storage read cost (100 gas for TLOAD). + +### Edge Cases + +| Scenario | Return Value | +|----------|--------------| +| Normal transaction | The resolved fee token address | +| Free transaction (zero gas price) | The resolved fee token (may still be set) | +| `eth_call` simulation | `address(0)` (no transaction context) | + +The only case where `address(0)` is returned is in simulation contexts (e.g., `eth_call`) where the protocol handler does not execute. + +## Example Usage + +```solidity +import { IFeeManager } from "./interfaces/IFeeManager.sol"; + +contract FeeTokenAware { + IFeeManager constant FEE_MANAGER = IFeeManager(0xfeeC000000000000000000000000000000000000); + address constant PATH_USD = 0x20C0000000000000000000000000000000000000; + + function doSomething() external { + address feeToken = FEE_MANAGER.getFeeToken(); + + if (feeToken == PATH_USD) { + // User is paying fees in pathUSD + } else if (feeToken != address(0)) { + // User is paying fees in a different USD stablecoin + } else { + // No fee token context (e.g., eth_call simulation) + } + } +} +``` + +## Interface Definition + +The complete updated `IFeeManager` interface: + +```solidity +interface IFeeManager { + // Structs + struct FeeInfo { + uint128 amount; + bool hasBeenSet; + } + + // User preferences + function userTokens(address user) external view returns (address); + function validatorTokens(address validator) external view returns (address); + function setUserToken(address token) external; + function setValidatorToken(address token) external; + + // Fee functions + function distributeFees(address validator, address token) external; + function collectedFees(address validator, address token) external view returns (uint256); + + // Transaction context (TIP-1007) + function getFeeToken() external view returns (address); + + // Events + 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); + + // Errors + error OnlyValidator(); + error OnlySystemContract(); + error InvalidToken(); + error PoolDoesNotExist(); + error InsufficientFeeTokenBalance(); + error InternalError(); + error CannotChangeWithinBlock(); + error CannotChangeWithPendingFees(); + error TokenPolicyForbids(); +} +``` + +--- + +# Invariants + +- `getFeeToken()` must return a consistent value across all calls within the same transaction +- `getFeeToken()` must return `address(0)` in simulation contexts (e.g., `eth_call`) where no transaction handler runs +- `getFeeToken()` must be callable from `staticcall` contexts without reverting +- The fee token returned must match the token used for actual fee deduction in `collectFeePreTx` and `collectFeePostTx` +- Reading the fee token must not modify any state (view function) + +## Test Cases + +The test suite must cover: + +1. **Basic functionality**: `getFeeToken()` returns the correct fee token address +2. **Zero when unset**: Returns `address(0)` when no fee token is set +3. **Consistency**: Same value returned from nested calls within a transaction +4. **Static call safety**: Works correctly when called via `staticcall` +5. **Transient storage**: Value is cleared between transactions +6. **Different fee tokens**: Works with various TIP-20 fee tokens (pathUSD, USDC, etc.) +7. **Dispatch coverage**: Function selector is correctly dispatched by the precompile From fed5265c159a48c2723639adc4555030f24585d2 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 15 Jan 2026 19:06:54 +0000 Subject: [PATCH 2/5] docs: remove LayerZero integration details from TIP-1007 --- docs/pages/protocol/tips/tip-1007.mdx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/pages/protocol/tips/tip-1007.mdx b/docs/pages/protocol/tips/tip-1007.mdx index 4f381795d8..6814666969 100644 --- a/docs/pages/protocol/tips/tip-1007.mdx +++ b/docs/pages/protocol/tips/tip-1007.mdx @@ -20,19 +20,13 @@ TIP-1007 adds a `getFeeToken()` view function to the FeeManager precompile that Tempo transactions support paying gas fees in any USD-denominated TIP-20 token via the fee token preference system. However, prior to this TIP, there was no way for a smart contract to determine which fee token is being used for the current transaction during execution. -This capability is needed by protocols like LayerZero/Stargate that want to: +This capability is needed by protocols that want to: - Adjust their internal logic based on which fee token is being used - Provide fee token-aware pricing or routing decisions - Emit events or logs that include the fee token for off-chain indexing - Implement fee token-specific behavior in cross-chain messaging -### Request Context - -This feature was requested by the LayerZero team during their Stargate integration with Tempo: - -> Is there any way to access or read the selected `fee_token` from *inside a smart contract* during execution? - --- # Specification From db9fa4d63e681e65b717a914cea2e5e11ac6a880 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 15 Jan 2026 19:07:28 +0000 Subject: [PATCH 3/5] docs: reference partner instead of specific protocol name --- docs/pages/protocol/tips/tip-1007.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/protocol/tips/tip-1007.mdx b/docs/pages/protocol/tips/tip-1007.mdx index 6814666969..96154f4155 100644 --- a/docs/pages/protocol/tips/tip-1007.mdx +++ b/docs/pages/protocol/tips/tip-1007.mdx @@ -20,7 +20,7 @@ TIP-1007 adds a `getFeeToken()` view function to the FeeManager precompile that Tempo transactions support paying gas fees in any USD-denominated TIP-20 token via the fee token preference system. However, prior to this TIP, there was no way for a smart contract to determine which fee token is being used for the current transaction during execution. -This capability is needed by protocols that want to: +This capability was requested by a partner that wants to: - Adjust their internal logic based on which fee token is being used - Provide fee token-aware pricing or routing decisions From 9a78c8208de0cca8218e0c88ba66431205d7ead8 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 15 Jan 2026 19:15:36 +0000 Subject: [PATCH 4/5] docs(fee-amm): fix rebalancing swap rate direction The rebalancing swap rate was incorrectly described as 'validator token per user token'. Since rebalancing swaps go from validatorToken to userToken, the rate should be expressed as 'user token per validator token' to be consistent with how fee swaps describe their rate (output per input). Changed 0.9985 to 1.0015 (= 10000/9985) to reflect the correct ratio. --- docs/pages/protocol/fees/spec-fee-amm.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/pages/protocol/fees/spec-fee-amm.mdx b/docs/pages/protocol/fees/spec-fee-amm.mdx index 9b032c7daa..bbad615cd7 100644 --- a/docs/pages/protocol/fees/spec-fee-amm.mdx +++ b/docs/pages/protocol/fees/spec-fee-amm.mdx @@ -9,7 +9,7 @@ import { Callout } from 'vocs/components' ## 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 (0.9985 `validatorToken` per `userToken`). +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 @@ -30,7 +30,7 @@ The system is designed to minimize several forms of MEV: 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 `0.9985` (validator token per user token) from `validatorToken` to `userToken` +2. **Rebalancing Swaps**: Fixed-rate swaps at a price of `1.0015` (user token per validator token) from `validatorToken` to `userToken` ### Core Components @@ -85,7 +85,7 @@ function rebalanceSwap( ) external returns (uint256 amountIn) ``` -Executes rebalancing swaps from `validatorToken` to `userToken` at fixed rate of 0.9985 (validator token per user token). Can be executed by anyone. Calculates `amountIn = (amountOut * N) / SCALE + 1` (rounds up). Updates reserves immediately. Emits `RebalanceSwap` event. +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( From d385162bc45d64c4b97d76cf9ab8ce5afe84d5a6 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 15 Jan 2026 19:40:16 +0000 Subject: [PATCH 5/5] docs: address review feedback on TIP-1007 --- docs/pages/protocol/tips/tip-1007.mdx | 60 +++++---------------------- 1 file changed, 10 insertions(+), 50 deletions(-) diff --git a/docs/pages/protocol/tips/tip-1007.mdx b/docs/pages/protocol/tips/tip-1007.mdx index 96154f4155..2411ac23ab 100644 --- a/docs/pages/protocol/tips/tip-1007.mdx +++ b/docs/pages/protocol/tips/tip-1007.mdx @@ -20,7 +20,7 @@ TIP-1007 adds a `getFeeToken()` view function to the FeeManager precompile that Tempo transactions support paying gas fees in any USD-denominated TIP-20 token via the fee token preference system. However, prior to this TIP, there was no way for a smart contract to determine which fee token is being used for the current transaction during execution. -This capability was requested by a partner that wants to: +This capability was requested by a partner. It could be useful for contracts that want to: - Adjust their internal logic based on which fee token is being used - Provide fee token-aware pricing or routing decisions @@ -42,10 +42,8 @@ interface IFeeManager { /// @notice Returns the fee token being used for the current transaction /// @return The address of the TIP-20 token paying for gas fees /// @dev This value is set by the protocol before transaction execution begins. - /// Returns address(0) if no fee token has been set (e.g., free transactions - /// or calls outside of a transaction context). - /// This function reads from transient storage, so it is safe to call - /// from staticcall contexts. + /// Returns address(0) if no fee token has been set (e.g., in eth_call + /// simulations where the transaction handler does not run). function getFeeToken() external view returns (address); } ``` @@ -54,12 +52,7 @@ interface IFeeManager { ### Fee Token Resolution -The fee token returned by `getFeeToken()` is the same token that was resolved by the protocol during transaction validation, following the fee token preference rules: - -1. **Transaction field**: If the transaction specifies a `fee_token` field, that token is used -2. **User preference**: Otherwise, the fee payer's `userTokens` preference from FeeManager -3. **TIP-20 inference**: For direct TIP-20 calls (`transfer`, `transferWithMemo`, `distributeReward`), the target token is used -4. **Default**: Falls back to pathUSD (`0x20C0000000000000000000000000000000000000`) +The fee token returned by `getFeeToken()` is the same token that was resolved by the protocol during transaction validation, following the [fee token preference rules](/protocol/fees/spec-fee#fee-token-resolution). ### Storage @@ -75,7 +68,7 @@ The fee token is set by the protocol in the `validate_against_state_and_deduct_c ### Gas Cost -Reading the fee token costs the standard warm transient storage read cost (100 gas for TLOAD). +Reading the fee token costs the standard warm transient storage read cost (100 gas for TLOAD). This is the cost of calling `getFeeToken()` itself; callers should account for additional gas used by the CALL opcode to invoke the precompile. ### Edge Cases @@ -110,47 +103,14 @@ contract FeeTokenAware { } ``` -## Interface Definition +## Interface Addition -The complete updated `IFeeManager` interface: +The following function is added to `IFeeManager`: ```solidity -interface IFeeManager { - // Structs - struct FeeInfo { - uint128 amount; - bool hasBeenSet; - } - - // User preferences - function userTokens(address user) external view returns (address); - function validatorTokens(address validator) external view returns (address); - function setUserToken(address token) external; - function setValidatorToken(address token) external; - - // Fee functions - function distributeFees(address validator, address token) external; - function collectedFees(address validator, address token) external view returns (uint256); - - // Transaction context (TIP-1007) - function getFeeToken() external view returns (address); - - // Events - 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); - - // Errors - error OnlyValidator(); - error OnlySystemContract(); - error InvalidToken(); - error PoolDoesNotExist(); - error InsufficientFeeTokenBalance(); - error InternalError(); - error CannotChangeWithinBlock(); - error CannotChangeWithPendingFees(); - error TokenPolicyForbids(); -} +/// @notice Returns the fee token being used for the current transaction +/// @return The address of the TIP-20 token paying for gas fees +function getFeeToken() external view returns (address); ``` ---