Skip to main content

What is LZD

LZEndpointDollar (LZD) is the canonical USD-denominated fee token for LayerZero on Tempo. Because Tempo has no native gas token, EndpointV2Alt uses LZD as the ERC-20 token for all LayerZero messaging fees. The endpoint could accept a single TIP-20 token directly, but that would force users to hold one specific stablecoin. LZD is an ERC-20 wrapper around multiple whitelisted 6-decimal stablecoins (pathUSD, USDC.e, or USDT0), so users can pay fees with whichever USD stablecoin they have. Users wrap a supported stablecoin into LZD, then approve the endpoint or OFT to spend it.

Endpoint mode detection

On any EVM chain, you can check the endpoint type by calling nativeToken(). On Tempo, it returns the LZD address:
address feeToken = ILayerZeroEndpointV2(endpoint).nativeToken();

if (feeToken == address(0)) {
    // Standard Endpoint: pay fees with native token (msg.value)
} else {
    // Endpoint Alt: pay fees with ERC20 at feeToken address
    // On Tempo, feeToken = LZD address
}

Fee payment flow

Paying LayerZero fees on Tempo requires wrapping a whitelisted stablecoin into LZD before sending:
  1. Quote: call quoteSend(sendParam, _payInLzToken) to receive both nativeFee and lzTokenFee. LayerZero lets you choose the fee token via _payInLzToken. At the time of writing, _payInLzToken is not enabled on Tempo, so set it to false and use nativeFee (LZD).
  2. Approve stablecoin: approve the LZD contract to spend your stablecoin.
  3. Wrap: call LZD.wrap(feeToken, recipient, amount) to convert your stablecoin into LZD.
  4. Approve LZD: approve the OFT contract to spend your LZD.
  5. Send: call send() with msg.value = 0 and MessagingFee(nativeFee, 0).
// 1. Quote the fee
MessagingFee memory fee = oft.quoteSend(sendParam, false);

// 2. Approve stablecoin to LZD
IERC20(usdce).approve(address(lzd), fee.nativeFee);

// 3. Wrap USDC.e into LZD
lzd.wrap(usdce, msg.sender, fee.nativeFee);

// 4. Approve LZD to the OFT
IERC20(address(lzd)).approve(address(oft), fee.nativeFee);

// 5. Send with msg.value = 0
oft.send{value: 0}(sendParam, fee, refundAddress);
At the time of writing, _payInLzToken is not enabled on Tempo, so set it to false.

API reference

wrap

function wrap(address _token, address _to, uint256 _amount) public nonReentrant onlyWhitelistedToken(_token)
Wraps a whitelisted stablecoin into LZD. Transfers the underlying token from msg.sender and mints LZD to _to.

Parameters

NameTypeDescription
_tokenaddresswhitelisted stablecoin to wrap
_toaddressaddress to receive minted LZD
_amountuint256amount to wrap (6-decimal)

unwrap

function unwrap(address _token, address _to, uint256 _amount) public nonReentrant onlyWhitelistedToken(_token)
Unwraps LZD back into a whitelisted stablecoin. Burns LZD from msg.sender and transfers the underlying token to _to.

Parameters

NameTypeDescription
_tokenaddresswhitelisted stablecoin to receive
_toaddressaddress to receive underlying tokens
_amountuint256amount to unwrap (6-decimal)

whitelistToken

function whitelistToken(address _token) public onlyOwner
Adds a token to the whitelist. This is an owner-only admin function, so new fee tokens can only be added by the LZD contract owner. The token must have exactly 6 decimals and must not already be whitelisted.

Parameters

NameTypeDescription
_tokenaddresstoken address to add

unwhitelistToken

function unwhitelistToken(address _token) public onlyOwner
Removes a token from the whitelist.

Parameters

NameTypeDescription
_tokenaddresstoken address to remove

isWhitelistedToken

function isWhitelistedToken(address _token) public view returns (bool isWhitelisted)
Returns whether a token is whitelisted.

Parameters

NameTypeDescription
_tokenaddresstoken to check

getWhitelistedTokens

function getWhitelistedTokens() public view returns (address[] memory tokens)
Returns all whitelisted token addresses.

getTokenBalance

function getTokenBalance(address _token) public view returns (uint256 balance)
Returns the contract’s balance of a specific underlying token.

Parameters

NameTypeDescription
_tokenaddresstoken to check

decimals

function decimals() public view returns (uint8)
Returns 6. LZD uses 6 decimals to match its underlying whitelisted stablecoins.

Events

TokenWrapped

event TokenWrapped(address indexed token, address indexed from, address indexed to, uint256 amount)
Emitted when a whitelisted token is wrapped into LZD.

TokenUnwrapped

event TokenUnwrapped(address indexed token, address indexed from, address indexed to, uint256 amount)
Emitted when LZD is unwrapped back into a whitelisted token.

TokenWhitelisted

event TokenWhitelisted(address indexed token, bool whitelisted)
Emitted when a token is added to or removed from the whitelist.

Errors

ErrorCause
NotWhitelisted(token)token is not on the whitelist
Whitelisted(token)token is already whitelisted (duplicate add)
InvalidToken(token)token is zero address or the LZD contract
InvalidTokenDecimals(token, decimals)token does not have 6 decimals

Quoting fees

quoteSend() returns a MessagingFee with both nativeFee and lzTokenFee. LayerZero lets applications choose the payment token via _payInLzToken. At the time of writing, _payInLzToken is not enabled on Tempo, so set it to false and read the fee from nativeFee (LZD):
// false = quote in native fee token (LZD on Tempo)
MessagingFee memory fee = oft.quoteSend(sendParam, false);
// fee.nativeFee = amount of LZD required
// fee.lzTokenFee = 0 on Tempo while _payInLzToken is disabled

msg.value behavior

Tempo’s EndpointV2Alt rejects any transaction that sends native value:
  • msg.value > 0 reverts with LZ_OnlyAltToken
  • There is no native drop. addExecutorNativeDropOption is not supported
Always set msg.value = 0 when calling send() on Tempo. Do not attach any native value to LayerZero transactions.

Failure modes

Revert reasonCauseMitigation
OFTAltCore__msg_value_not_zeromsg.value > 0 when calling send() on an OFTAltset msg.value = 0
LZ_OnlyAltTokenmsg.value > 0 reaching the endpoint directlyset msg.value = 0
LZ_LzTokenUnavailablequoteSend(_, true) calleduse quoteSend(_, false)
NotWhitelisted(token)fee token not whitelisted by LZDuse pathUSD, USDC.e, or USDT0
InvalidTokenDecimals(token, d)token does not have 6 decimalsuse a 6-decimal stablecoin
ERC-20 transfer failureinsufficient LZD balance or missing approvalwrap stablecoin into LZD and approve the OFT

Contract addresses

ContractAddress
LZEndpointDollar0x0ceb237e109ee22374a567c6b09f373c73fa4cbb