Skip to main content

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:
  1. Rejects native value: reverts with LZ_OnlyAltToken if msg.value > 0
  2. Reads ERC-20 balance: _suppliedNative() returns IERC20(nativeErc20).balanceOf(address(this)) instead of msg.value
  3. 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

AspectOFTOFTAltOFTAdapterOFTAdapterAlt
Fee paymentmsg.value (native)ERC-20 transferFrom (LZD)msg.value (native)ERC-20 transferFrom (LZD)
EndpointEndpointV2EndpointV2AltEndpointV2EndpointV2Alt
msg.valuerequired for feesmust be 0required for feesmust be 0
Native dropsupportednot supportedsupportednot supported
Token modelnew omnichain tokennew omnichain tokenwraps existing tokenwraps 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 ERC-20 token on Tempo, use OFTAdapterAlt. This uses lock/unlock: it locks tokens in the adapter on send and unlocks on receive.
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 on Tempo
        address _lzEndpoint,   // EndpointV2Alt address
        address _delegate
    ) OFTAdapterAlt(_token, _lzEndpoint, _delegate) Ownable(_delegate) {}
}
Do not use OFTAdapterAlt for TIP-20 tokens. TIP-20 tokens only support burn(amount) (burning from msg.sender), not burn(from, amount). Use OFTBurnSelfMintAlt instead — it transfers tokens to itself first, then burns from the adapter address.

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.

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

ScenarioTempo contractOther chains
New ERC-20, minted on TempoOFTAltOFT
Existing ERC-20 on Tempo, lock/unlock bridgingOFTAdapterAltOFT or OFTAdapter
Existing TIP-20 on Tempo, mint/burn bridgingOFTBurnSelfMintAltOFT 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. OFTBurnSelfMintAlt is the OFTBurnSelfMint variant for chains using EndpointV2Alt. It bridges TIP-20 tokens using a transfer-then-burn pattern: on send, it transfers tokens to itself first, then burns from the adapter address. This is required because TIP-20 tokens only support burn(amount) (burning from msg.sender), not burn(from, amount). On receive, it mints tokens directly to the recipient. The contract must hold ISSUER_ROLE on the TIP-20 token. 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.