Why Alt variants on Tempo
Tempo has no native gas token. Sending msg.value > 0 reverts, which means the standard fee payment path (fees via msg.value to the endpoint) does not work.
Tempo uses EndpointV2Alt instead of the standard EndpointV2. EndpointV2Alt replaces native token fee payment with ERC-20 token transfers using LZEndpointDollar (LZD). OFTAlt and OAppAlt are the contract variants designed to work with this ERC-20 fee path.
Building an OApp? OAppAlt changes only the fee payment path: _payNative transfers LZD to the endpoint instead of using msg.value. The receive side is identical to OAppReceiver. Everything else (messaging, peer configuration, security) works the same way. Import from @layerzerolabs/oapp-alt-evm/contracts/oapp/OAppAlt.sol.
How standard OFT fee payment works
On a typical EVM chain, OFT pays LayerZero messaging fees by attaching native value:
// Standard OFT on Ethereum, Arbitrum, etc.
MessagingFee memory fee = oft.quoteSend(sendParam, false);
oft.send{value: fee.nativeFee}(sendParam, fee, refundAddress);
The endpoint’s _payNative function reads msg.value and forwards native tokens to the message library. The endpoint refunds excess native value to the sender.
EndpointV2Alt and the ERC-20 fee path
On Tempo, EndpointV2Alt overrides _payNative to delegate to _payToken, which:
- Rejects native value: reverts with
LZ_OnlyAltToken if msg.value > 0
- Reads ERC-20 balance:
_suppliedNative() returns IERC20(nativeErc20).balanceOf(address(this)) instead of msg.value
- Exposes the fee token:
nativeToken() returns the LZD address instead of address(0)
OFTAlt contracts use this ERC-20 fee path. Before calling send(), the caller must approve and transfer LZD to the endpoint.
Comparison table
| Aspect | OFT | OFTAlt | OFTAdapter | OFTAdapterAlt |
|---|
| Fee payment | msg.value (native) | ERC-20 transferFrom (LZD) | msg.value (native) | ERC-20 transferFrom (LZD) |
| Endpoint | EndpointV2 | EndpointV2Alt | EndpointV2 | EndpointV2Alt |
| msg.value | required for fees | must be 0 | required for fees | must be 0 |
| Native drop | supported | not supported | supported | not supported |
| Token model | new omnichain token | new omnichain token | wraps existing token | wraps existing token |
| Import path | @layerzerolabs/oft-evm/contracts/OFT.sol | @layerzerolabs/oft-alt-evm/contracts/OFTAlt.sol | @layerzerolabs/oft-evm/contracts/OFTAdapter.sol | @layerzerolabs/oft-alt-evm/contracts/OFTAdapterAlt.sol |
Contract interface
OFTAlt extends the standard OFT interface but targets EndpointV2Alt:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;
import { OFTAlt } from "@layerzerolabs/oft-alt-evm/contracts/OFTAlt.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
contract MyOFTAlt is OFTAlt {
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint, // EndpointV2Alt address on Tempo
address _delegate
) OFTAlt(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}
}
For adapting an existing token on Tempo, use OFTAdapterAlt:
import { OFTAdapterAlt } from "@layerzerolabs/oft-alt-evm/contracts/OFTAdapterAlt.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
contract MyOFTAdapterAlt is OFTAdapterAlt {
constructor(
address _token, // existing ERC-20 or TIP-20 on Tempo
address _lzEndpoint, // EndpointV2Alt address
address _delegate
) OFTAdapterAlt(_token, _lzEndpoint, _delegate) Ownable(_delegate) {}
}
For TIP-20 tokens that
require mint/burn on Tempo, use TIP20MintBurnOFTAltAdapter. The adapter must
hold the ISSUER_ROLE to mint and burn.
Fee payment differences
Standard OFT (other chains)
// Fees paid via msg.value
MessagingFee memory fee = oft.quoteSend(sendParam, false);
oft.send{value: fee.nativeFee}(sendParam, fee, refundAddress);
OFTAlt (Tempo)
// Fees paid via ERC-20 transfer
MessagingFee memory fee = oft.quoteSend(sendParam, false);
// Approve the OFT to spend LZD for the fee
IERC20(lzd).approve(address(oft), fee.nativeFee);
// Send with msg.value = 0
oft.send{value: 0}(sendParam, fee, refundAddress);
On Tempo, you must wrap a whitelisted stablecoin into LZD and approve the OFT
before calling send(). See the LZD
reference for the wrap
flow.
TempoOFTWrapper
The TempoOFTWrapper simplifies the fee payment flow by bundling wrap, approve, and send into a single call. Instead of managing 5 transactions, you approve once and call sendOFT:
function sendOFT(
address _oft,
address _feeToken,
SendParam calldata _sendParam,
uint256 _maxNativeFee
) public returns (MessagingReceipt memory, OFTReceipt memory)
Parameters
| Name | Type | Description |
|---|
| _oft | address | OFT contract to send through |
| _feeToken | address | whitelisted 6-decimal stablecoin to pay fees with. Cannot be LZD itself |
| _sendParam | SendParam | standard OFT send parameters |
| _maxNativeFee | uint256 | maximum LZD fee. Reverts if quoted fee exceeds this |
If oft.token() == _feeToken, the wrapper pulls amountLD + nativeFee in a single transfer. Otherwise, it pulls asset and fee tokens separately.
See the how-to guide for a complete walkthrough.
| Contract | Address |
|---|
| TempoOFTWrapper | 0xbb95daf376cd63f258d7c37a4efe57c10055e8e0 |
Interoperability
OFTAlt on Tempo communicates with standard OFT deployments on other chains without issue. The LayerZero protocol handles translation between fee models:
- Sending from Tempo: the sender pays fees in LZD via OFTAlt. The destination chain receives the message as usual.
- Receiving on Tempo: the sender on the source chain pays fees in the source chain’s native token using standard OFT. The Tempo-side OFTAlt receives the message without requiring LZD from the receiver.
No changes are needed to existing OFT contracts on other chains. The cross-chain message format is the same.
Choosing the right contract
| Scenario | Tempo contract | Other chains |
|---|
| New ERC-20, minted on Tempo | OFTAlt | OFT |
| Existing ERC-20 on Tempo, lock/unlock bridging | OFTAdapterAlt | OFT or OFTAdapter |
| Existing TIP-20 on Tempo, mint/burn bridging | TIP20MintBurnOFTAltAdapter | OFT or OFTAdapter |
OFTAlt is for new omnichain tokens where Tempo is a mint chain. It deploys a fresh ERC-20 on Tempo and handles cross-chain mint/burn.
OFTAdapterAlt wraps an existing ERC-20 that already lives on Tempo. It locks tokens in the adapter on send and unlocks on receive. This is the Alt equivalent of OFTAdapter, targeting EndpointV2Alt instead of EndpointV2.
TIP20MintBurnOFTAltAdapter wraps a TIP-20 token using mint/burn instead of lock/unlock. The adapter must hold ISSUER_ROLE on the TIP-20 token. TIP-20 tokens use mint/burn because the ISSUER_ROLE model is designed around controlled supply, and lock/unlock would concentrate tokens in the adapter contract instead of adjusting supply directly.
On other chains, keep using standard OFT or OFTAdapter. No changes needed. The Tempo-side adapter connects to EndpointV2Alt while the other chains continue using EndpointV2. LayerZero routes messages between them without extra configuration.