Protocol
Crosschain Strategy
Tempo (TIP-20)

frxUSD on Tempo

frxUSD is the first stablecoin to implement Tempo's TIP-20 standard (opens in a new tab) with full support for stablecoin-denominated gas payments and cross-chain supply tracking.

Tempo is a Frax Mesh spoke in the Fraxtal hub model. All five Frax assets (frxUSD, sfrxUSD, frxETH, sfrxETH, WFRAX) are live and wired to Fraxtal, and HopV2 relays Tempo to every other mesh chain through Fraxtal.

For details on the TIP-20 standard itself, see Tempo's official documentation (opens in a new tab).

Network Constants

ItemValue
Chain ID4217
LayerZero EID30410
RPChttps://rpc.tempo.xyz
Explorerexplore.tempo.xyz (opens in a new tab)
Contract verificationcontracts.tempo.xyz (opens in a new tab) (Sourcify)
Admin Safe (3 of 6)0x1Ba19a54a01AE967f5E3895764Caaa6919FD2bEe (opens in a new tab)
ProxyAdmin0x000000dbfaA1Fb91ca46867cE6D41aB6da4f7428 (opens in a new tab)
LayerZero EndpointV2Alt0x20Bb7C2E2f4e5ca2B4c57060d1aE2615245dCc9C (opens in a new tab)
LZEndpointDollar (endpoint fee token)0x0cEb237E109eE22374a567c6b09F373C73FA4cBb (opens in a new tab)

Tempo has no native gas token. Its LayerZero endpoint is EndpointV2Alt, which charges messaging fees in LZEndpointDollar, an ERC-20 that wraps a whitelist of Tempo stablecoins. Every Frax contract on Tempo therefore rejects msg.value and collects fees through ERC-20 approvals instead (see Stablecoin-Denominated Gas).

Contract Addresses

Mainnet

ContractAddress
frxUSD (TIP-20 token, 6 decimals)0x20C0000000000000000000003554d28269E0f3c2 (opens in a new tab)
frxUSD OFT: FraxOFTMintableAdapterUpgradeableTIP200x00000000D61733e7A393A10A5B48c311AbE8f1E5 (opens in a new tab)
sfrxUSD OFT: FraxOFTUpgradeableTempo0x00000000fD8C4B8A413A06821456801295921a71 (opens in a new tab)
frxETH OFT: FraxOFTUpgradeableTempo0x000000008c3930dCA540bB9B3A5D0ee78FcA9A4c (opens in a new tab)
sfrxETH OFT: FraxOFTUpgradeableTempo0x00000000883279097A49dB1f2af954EAd0C77E3c (opens in a new tab)
WFRAX OFT: FraxOFTUpgradeableTempo0x00000000E9CE0f293D1Ce552768b187eBA8a56D4 (opens in a new tab)
FrxUSDPolicyAdminTempo (Proxy)0x766c2BD9C6dDc5BeE9ACd7D4C8ADD5b969952969 (opens in a new tab)
TIP-403 Policy ID5
RemoteHopV201Tempo (Proxy)0x0000006D38568b00B457580b734e0076C62de659 (opens in a new tab)
RemoteAdmin0x05b4a311Aac6658C0FA1e0247Be898aae8a8581f (opens in a new tab)
TempoFeeInclusiveWrapper0x4877c528468a64FD5Fba87dDefd517dca72814a3 (opens in a new tab)

All OFTs and the hop share their addresses with Somnia and Robinhood (deterministic 0x000000... deployment); see Addresses for the full crosschain matrix.

frxUSD is the only Frax asset on Tempo that is a native TIP-20 token. Users hold the TIP-20 at 0x20C0...f3c2, and the adapter at 0x00000000D617...f1E5 mints and burns it. The other four assets are standard ERC-20 OFTs with 18 decimals.

An FPI OFT also exists at 0x00000000bC4aEF4bA6363a437455Cb1af19e2aEb, but it is not connected to the mesh. FPI was retired before Tempo was wired, so this contract has no peers and cannot bridge.

Testnet (Moderato)

ItemValue
Chain ID42431
LayerZero EID40444
RPChttps://rpc.moderato.tempo.xyz
frxUSD TIP-200x20c000000000000000000000cf5b0F48F7fEDC7F
FraxOFTMintableAdapterUpgradeableTIP20 (Proxy)0x16FBfCF4970D4550791faF75AE9BaecE75C85A27
FrxUSDPolicyAdminTempo (Proxy)0x667daf76CC0fC083B88caa20D8Bd8565884aE97C
TIP-403 Policy ID375843

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                      TEMPO PRECOMPILES                          │
├─────────────────┬─────────────────┬─────────────────────────────┤
│ TIP403_REGISTRY │ STABLECOIN_DEX  │ TIP_FEE_MANAGER             │
│ Policy control  │ Token swaps     │ User gas preferences        │
└────────┬────────┴────────┬────────┴──────────────┬──────────────┘
         │                 │                       │
         ▼                 ▼                       ▼
┌─────────────────────────────────────────────────────────────────┐
│                    FRAX CONTRACTS                               │
├─────────────────────────────────────────────────────────────────┤
│ FrxUSDPolicyAdminTempo      - Freeze/thaw via TIP-403           │
│ FraxOFTMintableAdapterTIP20 - frxUSD bridge (mint/burn TIP-20)  │
│ FraxOFTUpgradeableTempo     - OFTs with stablecoin gas payment  │
│ RemoteHopV201Tempo          - HopV2 routing via Fraxtal         │
│ TempoFeeInclusiveWrapper    - Single-approval frxUSD bridging   │
└─────────────────────────────────────────────────────────────────┘
PrecompileAddress
TIP_FEE_MANAGER0xfeEC000000000000000000000000000000000000
STABLECOIN_DEX0xDEc0000000000000000000000000000000000000
TIP20_FACTORY0x20Fc000000000000000000000000000000000000

Routing Model

  • Fraxtal is the hub chain for Tempo.
  • Tempo -> Fraxtal and Fraxtal -> Tempo are direct OFT routes for all five assets.
  • Tempo -> any other mesh chain relays through Fraxtal with HopV2 (RemoteHopV201Tempo on Tempo, FraxtalHopV2 at 0x00000000e18aFc20Afe54d4B2C8688bB60c06B36 on Fraxtal).
  • Cross-chain stake/unstake and mint/redeem hops are not available on Tempo. Those hops pay fees in native msg.value, which EndpointV2Alt does not support.

LayerZero Configuration

The Tempo ↔ Fraxtal pathway uses the same five required DVNs as the rest of the mesh (LayerZero Labs, Horizen, Nethermind, Canary, Frax). Two settings are specific to Tempo (see Configuration for the full mesh settings):

SettingTempoMost other chains
Confirmations, Tempo → Fraxtal20 Tempo blocksChain-specific
Enforced gas, Fraxtal → Tempo (SEND / SEND_AND_CALL)2,000,000 / 2,500,000200,000 / 250,000

Deliveries on Tempo use more gas than on other EVM chains, so Frax enforces a higher minimum for them. Messages from Tempo to Fraxtal carry the standard 200,000 / 250,000 gas.

Key Features

Stablecoin-Denominated Gas

Users pay bridge fees in their preferred Tempo stablecoin. The OFTs and the hop automatically:

  1. Resolve the caller's fee token via TIP_FEE_MANAGER.userTokens(caller), falling back to PATH_USD when none is set.
  2. If that token is on the LZEndpointDollar whitelist (currently PATH_USD, USDC.e and USDT0), pull the fee in that token and wrap it directly.
  3. Otherwise (frxUSD included), swap it through STABLECOIN_DEX into whichever whitelisted token is cheapest to buy, then wrap.

Users never need a native gas token to bridge. If no whitelisted token can be bought with the caller's fee token, quoting and sending revert with NoSwappableWhitelistedToken.

Whitelisted fee tokenAddress
PATH_USD0x20C0000000000000000000000000000000000000
USDC.e0x20C000000000000000000000b9537d11c60E8b50
USDT00x20C00000000000000000000014f22CA97301EB73

Cross-Chain Supply Tracking

The frxUSD adapter includes SupplyTrackingModule, which records every frxUSD flow between Tempo and each peer chain:

// Outflows: frxUSD sent from Tempo to `_eid`
function totalTransferTo(uint32 _eid) external view returns (uint256);
// Inflows: frxUSD received on Tempo from `_eid`
function totalTransferFrom(uint32 _eid) external view returns (uint256);
 
// initialTotalSupply(_eid) + totalTransferTo(_eid) - totalTransferFrom(_eid)
function totalSupply(uint32 _eid) external view returns (uint256);

totalSupply(_eid) is only meaningful once governance has set initialTotalSupply(_eid). Until then it reverts with an arithmetic underflow whenever inflows from that chain exceed outflows, which is the case for Fraxtal today. Use totalTransferTo and totalTransferFrom directly for analytics.

TIP-403 Policy Compliance

Transfer restrictions on frxUSD are enforced at the protocol level via Tempo's TIP-403 Registry (opens in a new tab). frxUSD uses a blacklist policy (ID 5) administered by FrxUSDPolicyAdminTempo:

function freeze(address account) external;   // Owner or freezer: block account transfers
function thaw(address account) external;     // Owner only: unblock account
function isFrozen(address account) external view returns (bool);

Integration Guide

All amounts for frxUSD on Tempo use 6 decimals, and the adapter's decimalConversionRate is 1, so no dust is removed. The other four OFTs use 18 decimals and drop dust below 1e12, as on every other chain.

Quoting Bridge Fees

quoteSend() returns fee.nativeFee in LZEndpointDollar units, not in the user's token. Convert it with quoteUserTokenFee() to get the amount to approve:

import { SendParam, MessagingFee, IOFT } from "@fraxfinance/layerzero-v2-upgradeable/oapp/contracts/oft/interfaces/IOFT.sol";
import { OptionsBuilder } from "@fraxfinance/layerzero-v2-upgradeable/oapp/contracts/oapp/libs/OptionsBuilder.sol";
 
address oft = 0x00000000D61733e7A393A10A5B48c311AbE8f1E5; // frxUSD adapter on Tempo
address frxUSD = 0x20C0000000000000000000003554d28269E0f3c2;
uint32 dstEid = 30255; // Fraxtal
uint256 amount = 100e6; // 100 frxUSD (6 decimals)
 
SendParam memory sendParam = SendParam({
    dstEid: dstEid,
    to: bytes32(uint256(uint160(msg.sender))),
    amountLD: amount,
    minAmountLD: amount,
    extraOptions: OptionsBuilder.newOptions(),
    composeMsg: '',
    oftCmd: ''
});
 
// Fee in LZEndpointDollar units
MessagingFee memory fee = IOFT(oft).quoteSend(sendParam, false);
 
// The caller's fee token (PATH_USD if they never set one)
address feeToken = ITIPFeeManager(0xfeEC000000000000000000000000000000000000).userTokens(msg.sender);
if (feeToken == address(0)) feeToken = 0x20C0000000000000000000000000000000000000;
 
// Amount of feeToken that will actually be pulled
uint256 feeInToken = ITempoOFT(oft).quoteUserTokenFee(feeToken, fee.nativeFee);

For a whitelisted fee token, feeInToken == fee.nativeFee. For any other token, it is the StablecoinDEX exact-out quote at the time of the call.

Sending Cross-Chain

Tempo OFTs revert on any msg.value. Approve the fee token instead, and pass the unmodified fee from quoteSend():

// 1. Approve the bridged token (frxUSD adapter only: it burns the TIP-20 from the caller)
IERC20(frxUSD).approve(oft, amount);
 
// 2. Approve the fee token. If it is the same token as step 1, approve the sum instead.
IERC20(feeToken).approve(oft, feeInToken);
 
// 3. Send WITHOUT msg.value
IOFT(oft).send(sendParam, fee, payable(msg.sender));

If the user's fee token is frxUSD, approve amount + feeInToken in a single call. A DEX-routed fee can move between quote and send, so leave a small buffer on the fee approval.

Sending Beyond Fraxtal (HopV2)

To reach any chain other than Fraxtal in one transaction, call RemoteHopV201Tempo. As on other chains, the hop pulls the underlying token (IOFT(oft).token()), so approve that, not the OFT. Its fee functions follow the same split as the OFTs:

FunctionReturns
quote(oft, dstEid, recipient, amount, dstGas, data)Fee in LZEndpointDollar units, covering the Fraxtal leg plus the onward leg
quoteStatic(oft, dstEid, recipient, amount, dstGas, data, userToken)The same fee converted into userToken units, which is the amount to approve
sendOFT(oft, dstEid, recipient, amount, dstGas, data)Pulls amount of the token and the fee in the caller's fee token; reverts on any msg.value
address hop = 0x0000006D38568b00B457580b734e0076C62de659;
bytes32 recipient = bytes32(uint256(uint160(msg.sender)));
uint32 dstEid = 30110; // Arbitrum, relayed via Fraxtal
 
uint256 feeInToken = IRemoteHopV201Tempo(hop).quoteStatic(oft, dstEid, recipient, amount, 0, "", feeToken);
 
IERC20(frxUSD).approve(hop, feeToken == frxUSD ? amount + feeInToken : amount);
if (feeToken != frxUSD) IERC20(feeToken).approve(hop, feeInToken);
 
IRemoteHopV201Tempo(hop).sendOFT(oft, dstEid, recipient, amount, 0, "");

Check that recipient is not zero before calling: a zero recipient is accepted on Tempo and the funds can only be recovered by governance on the destination.

Fee-Inclusive frxUSD Bridging

TempoFeeInclusiveWrapper lets an integrator bridge frxUSD with a single approval of frxUSD. The fee is quoted in frxUSD, deducted from the caller's budget, and only the net remainder is bridged. It is immutable, ownerless, and supports frxUSD plain transfers only; compose data is rejected.

address wrapper = 0x4877c528468a64FD5Fba87dDefd517dca72814a3;
 
// Preview: feeToken is frxUSD, netAmount is what will be bridged
(address feeToken, uint256 feeAmount, uint256 netAmount) =
    IWrapper(wrapper).quoteFeeInclusive(oft, dstEid, recipient, budget, 0, "");
 
IERC20(frxUSD).approve(wrapper, budget);
 
// minNetAmount must be non-zero: derive it from netAmount less your slippage tolerance
IWrapper(wrapper).sendOFTFeeInclusive(oft, dstEid, recipient, budget, minNetAmount, 0, "");

Contract Reference

FraxOFTUpgradeableTempo / FraxOFTMintableAdapterUpgradeableTIP20

FunctionDescription
send()Bridge tokens; reverts on msg.value, pulls the fee via ERC-20 approval
quoteSend()Fee in LZEndpointDollar units; reverts if the caller's fee token has no swap path
quoteUserTokenFee(token, endpointFee)Convert an endpoint fee into token units
nativeToken()The LZEndpointDollar address

FraxOFTMintableAdapterUpgradeableTIP20 (supply tracking)

FunctionDescription
totalTransferTo(uint32)Outflows from Tempo to a chain
totalTransferFrom(uint32)Inflows to Tempo from a chain
totalTransferToSum()Total outflows
totalTransferFromSum()Total inflows
totalTransfers(uint32)(from, to) for a chain
initialTotalSupply(uint32)Governance-set starting supply for a chain
totalSupply(uint32)initialTotalSupply + totalTransferTo - totalTransferFrom

FrxUSDPolicyAdminTempo

FunctionDescription
freeze(address) / freezeMany(address[])Block account transfers (owner or freezer)
thaw(address) / thawMany(address[])Unblock accounts (owner only)
isFrozen(address)Check freeze status
addFreezer(address) / removeFreezer(address)Manage freezers (owner only)
isFreezer(address)Check freezer status
policyId()TIP-403 policy ID
getPolicyData()Policy type and admin from the registry

RemoteHopV201Tempo

FunctionDescription
sendOFT()Bridge via Fraxtal to any mesh chain; no msg.value
quote()Fee in LZEndpointDollar units
quoteStatic(..., userToken)Fee in userToken units
approvedOft(address)Whether an OFT can be routed (all five Frax OFTs are approved)
remoteHop(uint32)Registered hop for an EID
paused()Hop pause state

TempoFeeInclusiveWrapper

FunctionDescription
sendOFTFeeInclusive()Bridge frxUSD with the fee deducted from the budget
quoteFeeInclusive()Preview (feeToken, feeAmount, netAmount)

Events

Supply Tracking

event AddToTotalTransferFromEid(uint32 indexed eid, uint256 previousAmount, uint256 amount);
event AddToTotalTransferFromSum(uint256 previousAmount, uint256 amount);
event AddToTotalTransferToEid(uint32 indexed eid, uint256 previousAmount, uint256 amount);
event AddToTotalTransferToSum(uint256 previousAmount, uint256 amount);
event SetInitialTotalSupply(uint32 indexed eid, uint256 amount);

Policy

event AccountFrozen(address indexed account);
event AccountThawed(address indexed account);
event AddFreezer(address indexed account);
event RemoveFreezer(address indexed account);
event PolicyIdSet(uint64 indexed policyId);

Source Code