The Value Transfer API relies on three contracts per EVM chain: the TransferDelegate (handles token approvals), the LZMulticall (batches and executes bridge transactions), and the Treasury (collects fees).
Setting token allowancesThe Value Transfer API uses two separate contracts for approvals and execution. The TransferDelegate is the only contract that should be approved as a token spender. The LZMulticall (Wrapper) batches and executes bridge transactions but does not need or support token allowances.The API’s approve userStep already encodes the correct TransferDelegate address in its calldata, so you don’t need to look up or hardcode any contract address. Execute the step as returned.See Contract Addresses for the Wrapper and Delegate addresses on each chain.
Contract architecture
Token transfers go through two contracts in sequence: the Delegate pulls tokens, then the Wrapper executes the bridge. A third contract, the Treasury, collects fees behind the scenes.
| Contract | Role | Function |
|---|
| TransferDelegate (Delegate) | Approved spender for ERC20 transfers | delegateTransferFrom(token, from, to, amount) |
| LZMulticall (Wrapper) | Batches and executes bridge calls | execute(calls, quoteId) |
| Treasury | Fee collection | getFees() |
TransferDelegate (Delegate)
This is the contract users approve as the ERC20 spender. One function:
delegateTransferFrom(address token, address from, address to, uint256 amount)
When LZMulticall executes a bridge, it calls TransferDelegate to pull tokens from the user’s wallet. That call only succeeds if the user has already approved TransferDelegate for that token.
The approve userStep returned by the API already encodes the TransferDelegate address in its calldata. No need to look it up or hardcode it.
LZMulticall (Wrapper)
LZMulticall is a batch executor. It takes an array of calls and runs them in a single transaction, routing bridge operations through LayerZero.
Each LZMulticall deploys its own TransferDelegate in the constructor, so the two contracts are always paired 1:1. You can look up the paired Delegate address via the TRANSFER_DELEGATE getter on any LZMulticall deployment.
There are two execution modes:
- Direct execution — the caller is the signer. The API’s bridge
userStep uses this mode.
- Signature-based execution — a third party submits a pre-signed EIP-712 payload on behalf of the signer. Includes an expiration timestamp and per-signer nonce for replay protection.
When a call in the batch targets TransferDelegate, LZMulticall validates that the from address in the calldata matches the signer. One user can’t move another user’s tokens through the Delegate, even within the same batch.
The contract also has a sweep() function that recovers any ETH or tokens left over after execution.
LZMulticall is also the fee wrapper. Inside each execute() call, the batch deducts fees from the transfer amount and sends them to the Treasury before running the bridge. See Fees for details.
Never approve the LZMulticall (Wrapper) contract as a token spenderLZMulticall can forward arbitrary calls to any contract. If you approve it as a spender on any token, anyone can drain those tokens. Only approve the TransferDelegate as the spender.
Treasury
Treasury collects fees for cross-chain transfers using basis points on native currency.
getFees(bool payInZro, uint256 relayerFee, uint256 oracleFee)
You don’t interact with Treasury directly. The API calculates fees and includes the amounts in the bridge transaction’s value field.
Fees
LZMulticall is also the fee wrapper. Inside execute(), the batched calls deduct fees and send them to the Treasury before bridging the rest. The API builds this into the bridge userStep for you.
Fee types
Up to three fees can apply per transfer:
| Fee | Type | Description |
|---|
| Base Fee | Percentage | A percentage of the transfer amount. |
| Partner Fee | Percentage | A partner commission, configured per API key. |
| CCTP Receive Fee | Fixed | A flat fee on CCTP (Circle) routes for destination-chain receive costs. |
Zero-amount fees are omitted. The total is subtracted from the source amount before bridging, so the destination amount is the post-fee value.
How fees flow through the contracts
The fee mechanism depends on the token type:
Native tokens (e.g., ETH):
- Call
LZMulticall.execute() with msg.value set to the fee plus the bridge cost.
- Inside the batch, one call sends the fee to the Treasury.
- The remaining calls run the bridge.
ERC20 tokens (e.g., USDC):
- Approve TransferDelegate for the full source amount (fees included).
- Call
LZMulticall.execute().
- Inside the batch, TransferDelegate pulls the full token amount from the user to the Wrapper.
- A transfer sends the fee portion from the Wrapper to Treasury.
- The remaining calls bridge the post-fee amount.
Tracking
Each execute() call includes a quoteId. On confirmation, LZMulticall emits an Executed(signer, quoteId, nonce) event. The API uses this event to attribute the transfer to the API key that requested the quote, linking volume and fee data back to each integration.
How the contracts work together
A typical ERC20 cross-chain transfer:
- You request a quote. The API returns two
userSteps: approve and bridge.
- The approve step calls the ERC20 token’s
approve() with TransferDelegate as spender.
- The bridge step calls LZMulticall’s
execute() on the source chain.
- Inside that execution, LZMulticall calls
TransferDelegate.delegateTransferFrom() to pull the full token amount (fees included) from your wallet to the Wrapper.
- LZMulticall sends the fee portion to Treasury and bridges the remaining amount.
| Step | transaction.encoded.to | What it does |
|---|
| Approve | ERC20 token contract (e.g., USDC) | Calls approve(TransferDelegate, amount) |
| Bridge | LZMulticall contract | Routes the bridge through LayerZero |
Contract addresses
Each chain has its own Wrapper and Delegate deployments. See Contract Addresses for the full list.