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
| Item | Value |
|---|---|
| Chain ID | 4217 |
| LayerZero EID | 30410 |
| RPC | https://rpc.tempo.xyz |
| Explorer | explore.tempo.xyz (opens in a new tab) |
| Contract verification | contracts.tempo.xyz (opens in a new tab) (Sourcify) |
| Admin Safe (3 of 6) | 0x1Ba19a54a01AE967f5E3895764Caaa6919FD2bEe (opens in a new tab) |
| ProxyAdmin | 0x000000dbfaA1Fb91ca46867cE6D41aB6da4f7428 (opens in a new tab) |
| LayerZero EndpointV2Alt | 0x20Bb7C2E2f4e5ca2B4c57060d1aE2615245dCc9C (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
| Contract | Address |
|---|---|
| frxUSD (TIP-20 token, 6 decimals) | 0x20C0000000000000000000003554d28269E0f3c2 (opens in a new tab) |
frxUSD OFT: FraxOFTMintableAdapterUpgradeableTIP20 | 0x00000000D61733e7A393A10A5B48c311AbE8f1E5 (opens in a new tab) |
sfrxUSD OFT: FraxOFTUpgradeableTempo | 0x00000000fD8C4B8A413A06821456801295921a71 (opens in a new tab) |
frxETH OFT: FraxOFTUpgradeableTempo | 0x000000008c3930dCA540bB9B3A5D0ee78FcA9A4c (opens in a new tab) |
sfrxETH OFT: FraxOFTUpgradeableTempo | 0x00000000883279097A49dB1f2af954EAd0C77E3c (opens in a new tab) |
WFRAX OFT: FraxOFTUpgradeableTempo | 0x00000000E9CE0f293D1Ce552768b187eBA8a56D4 (opens in a new tab) |
FrxUSDPolicyAdminTempo (Proxy) | 0x766c2BD9C6dDc5BeE9ACd7D4C8ADD5b969952969 (opens in a new tab) |
| TIP-403 Policy ID | 5 |
RemoteHopV201Tempo (Proxy) | 0x0000006D38568b00B457580b734e0076C62de659 (opens in a new tab) |
RemoteAdmin | 0x05b4a311Aac6658C0FA1e0247Be898aae8a8581f (opens in a new tab) |
TempoFeeInclusiveWrapper | 0x4877c528468a64FD5Fba87dDefd517dca72814a3 (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)
| Item | Value |
|---|---|
| Chain ID | 42431 |
| LayerZero EID | 40444 |
| RPC | https://rpc.moderato.tempo.xyz |
| frxUSD TIP-20 | 0x20c000000000000000000000cf5b0F48F7fEDC7F |
FraxOFTMintableAdapterUpgradeableTIP20 (Proxy) | 0x16FBfCF4970D4550791faF75AE9BaecE75C85A27 |
FrxUSDPolicyAdminTempo (Proxy) | 0x667daf76CC0fC083B88caa20D8Bd8565884aE97C |
| TIP-403 Policy ID | 375843 |
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 │
└─────────────────────────────────────────────────────────────────┘| Precompile | Address |
|---|---|
TIP_FEE_MANAGER | 0xfeEC000000000000000000000000000000000000 |
STABLECOIN_DEX | 0xDEc0000000000000000000000000000000000000 |
TIP20_FACTORY | 0x20Fc000000000000000000000000000000000000 |
Routing Model
- Fraxtal is the hub chain for Tempo.
Tempo -> FraxtalandFraxtal -> Tempoare direct OFT routes for all five assets.Tempo -> any other mesh chainrelays through Fraxtal with HopV2 (RemoteHopV201Tempoon Tempo,FraxtalHopV2at0x00000000e18aFc20Afe54d4B2C8688bB60c06B36on Fraxtal).- Cross-chain stake/unstake and mint/redeem hops are not available on Tempo. Those hops pay fees in native
msg.value, whichEndpointV2Altdoes 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):
| Setting | Tempo | Most other chains |
|---|---|---|
| Confirmations, Tempo → Fraxtal | 20 Tempo blocks | Chain-specific |
Enforced gas, Fraxtal → Tempo (SEND / SEND_AND_CALL) | 2,000,000 / 2,500,000 | 200,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:
- Resolve the caller's fee token via
TIP_FEE_MANAGER.userTokens(caller), falling back to PATH_USD when none is set. - If that token is on the
LZEndpointDollarwhitelist (currently PATH_USD, USDC.e and USDT0), pull the fee in that token and wrap it directly. - Otherwise (frxUSD included), swap it through
STABLECOIN_DEXinto 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 token | Address |
|---|---|
| PATH_USD | 0x20C0000000000000000000000000000000000000 |
| USDC.e | 0x20C000000000000000000000b9537d11c60E8b50 |
| USDT0 | 0x20C00000000000000000000014f22CA97301EB73 |
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:
| Function | Returns |
|---|---|
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
| Function | Description |
|---|---|
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)
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
- Tempo Integration Specs (opens in a new tab)
- FraxOFTUpgradeableTempo (opens in a new tab)
- FraxOFTMintableAdapterUpgradeableTIP20 (opens in a new tab)
- TempoAltTokenBase (opens in a new tab)
- SupplyTrackingModule (opens in a new tab)
- FrxUSDPolicyAdminTempo (opens in a new tab)
- RemoteHopV201Tempo (opens in a new tab)
- HopV201Tempo (opens in a new tab)
- TempoGasTokenBase (opens in a new tab)
- TempoFeeInclusiveWrapper (verified source) (opens in a new tab)