Skip to main content

Does my existing OFT/OApp work on Tempo?

Yes. Source-chain contracts remain unchanged, but the Tempo side requires Alt contract variants and pays fees in LZD instead of msg.value. The message format, pathway configuration, and security model are the same.
Source-chain contracts may need updated enforcedOptions to account for Tempo’s higher gas costs. See what changes on Tempo below.

What changes on Tempo

See the overview for how each standard LayerZero component maps to its Tempo equivalent. The key changes relevant to OFT/OApp integrations:
  • OFT/OApp contracts are replaced by their Alt variants: OFTAlt, OFTAdapterAlt, TIP20MintBurnOFTAltAdapter, or OAppAlt depending on your use case
  • Fees are paid in LZD instead of msg.value (this is because Tempo has no native gas token). You must wrap a stablecoin into LZD and approve the OFT before calling send{value: 0}()
  • Native drop is not supported (addExecutorNativeDropOption reverts)
  • Gas costs are higher on Tempo for certain operations (state creation, new accounts). Set custom enforcedOptions with higher gas limits for lzReceive when Tempo is the destination. See TIP-1010 for the full gas schedule

Sending from other chains to Tempo

When sending to Tempo from another chain, the normal fee model applies on the source chain:
  • Pay fees in the source chain’s native token (ETH, MATIC, etc.) as usual
  • No LZD is needed on the source chain
  • The receiver on Tempo does not pay anything to receive the message
No changes are required to your existing OFT/OApp contracts on the source chain.

Sending from Tempo to other chains

When sending from Tempo, you pay the LayerZero fee in LZD. There are two approaches:

Direct flow (1 view call + 5 transactions)

// 1. Quote the fee (view call)
MessagingFee memory fee = oft.quoteSend(sendParam, false);

// 2. Approve the asset token to the OFT (bridge amount)
IERC20(usdce).approve(address(oft), sendParam.amountLD);

// 3. Approve the fee token to LZD (fee amount)
IERC20(usdce).approve(address(lzd), fee.nativeFee);

// 4. Wrap stablecoin into LZD (token must be whitelisted by LZD)
lzd.wrap(usdce, msg.sender, fee.nativeFee);

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

// 6. Send with msg.value = 0
oft.send{value: 0}(sendParam, fee, refundAddress);

Wrapper flow (1 view call + 2 transactions)

The TempoOFTWrapper simplifies this by handling wrapping, approvals, and sending in a single transaction:
// 1. Quote the fee
MessagingFee memory fee = oft.quoteSend(sendParam, false);

// 2. Approve the fee token to the wrapper (bridge amount + messaging fee)
IERC20(usdce).approve(address(wrapper), sendParam.amountLD + fee.nativeFee);

// 3. Call sendOFT: wrapper handles wrap, approve, and send atomically
wrapper.sendOFT(
    address(oft),       // OFT contract
    usdce,              // fee token (whitelisted stablecoin)
    sendParam,          // standard send parameters
    fee.nativeFee       // max acceptable fee (reverts if re-quote exceeds this)
);
The wrapper:
  1. Pulls tokens from the caller:
    • Same token (e.g., USDC.e for both bridging and fees): pulls amountLD + nativeFee in a single transfer
    • Different tokens (e.g., bridging EURC.e, fees in USDC.e): pulls amountLD and nativeFee separately, requiring two approvals
  2. Wraps the fee portion into LZD
  3. Approves LZD to the OFT
  4. Calls oft.send() with the correct fee
  5. Reverts the entire transaction if the re-quoted fee exceeds maxNativeFee
You can preflight with an eth_call to sendOFT to obtain oftReceipt.amountSentLD before sending the live transaction, and avoid dust.
If the bridged token differs from the fee token (e.g., bridging EURC.e but paying fees in USDC.e), you will need to approve the asset token and fee token to the wrapper separately before calling sendOFT.

With LZMulticall (frontend pattern)

The Stargate frontend uses LZMulticall to bundle wrapping, approvals, and sending into a single transaction. On Tempo, it bundles the LZD wrap and approval steps alongside the OFT send.
ContractAddress
LZMulticall0x4683ce822272cd66cea73f5f1f9f5cbcaef4f066
TransferDelegate0x3c18440268a78d651a3847653692fc82c31731c9
TempoOFTWrapper0xbb95daf376cd63f258d7c37a4efe57c10055e8e0
LZEndpointDollar0x0ceb237e109ee22374a567c6b09f373c73fa4cbb

Common pitfalls

  • LZD is not available on DEXes. LZD is an ERC-20, not a TIP-20 token, so it cannot be traded on the Tempo DEX. Always use LZD.wrap() to obtain fee tokens.
  • Wrapper becomes msg.sender. When using the TempoOFTWrapper, the wrapper is the sender for the OFT call. Do not use it for compose messages where the composer refunds the original sender. The refund goes to the wrapper, not the user, and the funds will be permanently lost.
  • Fee token must be a whitelisted stablecoin, not LZD. The feeToken passed to the wrapper must be a 6-decimal stablecoin whitelisted by LZD (pathUSD, USDC.e, or USDT0). Passing LZD itself reverts.
See the LZD reference failure modes table for a complete list of revert reasons.