On this page
Transfer and deposit
Let a user send an Omniasset and deposit it into your destination vault without making a second wallet transaction there.
Two executions, two outcomes
Source send → destination token credit → queued compose → vault shares
→ claimable tokens if deposit failsTransfer delivery and the application call are separate. A delivered transfer does not prove a successful deposit. Once tokens have been credited to the composer, router restore is not the application's refund mechanism.
Destination contract
Download DocsVaultComposer.sol. It is a complete educational example, not an audited production deployment. It uses Solidity 0.8.28 and OpenZeppelin Contracts 5.6.1. Install @openzeppelin/contracts@5.6.1 in your Solidity project before compiling.
Configure one reviewed destination Endpoint, local asset facade, source EID, received ERC-20 and ERC-4626 vault. On origin destinations, the facade is an adapter but the received ERC-20 is the original token. On remote destinations, both are the OFT. The vault's asset() must match the received token. A predicted facade is usable as configuration only after verifying it against the correct router and asset ID.
The callback checks both msg.sender == endpoint and _from == facade, then the envelope's source EID and EVM sender format. It accepts any sender on that one source route: this is a permissionless deposit flow. For a privileged application, additionally require an allowlisted composeFrom; knowing an Endpoint address alone is not authentication.
Build the source payload
After configuring your clients and resolving the asset as in the transfer starter, change the send parameters:
import { encodeAbiParameters, padHex } from "viem";
import { Options } from "@layerzerolabs/lz-v2-utilities";
const composeMsg = encodeAbiParameters(
[{ type: "address" }, { type: "uint256" }, { type: "uint256" }],
[beneficiary, minShares, deadline],
);
const composed = {
...sendParam,
to: padHex(destinationComposer, { size: 32 }),
composeMsg,
extraOptions: Options.newOptions()
.addExecutorComposeOption(0, composeGas, 0)
.toHex(),
};This is an excerpt, not a standalone script: beneficiary, minShares, deadline, destinationComposer, composeGas and the starter's sendParam are application inputs. Install @layerzerolabs/lz-v2-utilities for the option builder and pin your reviewed version. The LayerZero OFT guide documents the standard compose envelope and options.
Use a beneficiary wallet controlled on the destination; the same smart-account address on two chains may have different control. Preview vault shares for the net quoted token amount, set a user-approved minimum, and use a realistic deadline. Size compose gas from tests against the actual vault. Do not assume one universal gas amount. The example accepts zero compose native value only.
Quote quoteOFT and quoteSend using the final composed parameters, set minAmountLD, simulate, then send. The router adds its receive/deploy gas defaults; the extra compose option funds callback index 0.
Deposit and refund behavior
The example limits approval to that message's amount, deposits to the signed beneficiary, checks minShares, and resets allowance. An external self-call makes deposit and minimum-share validation atomic. Expiry, slippage or a vault revert leaves a per-GUID token credit for the beneficiary. Only that beneficiary can claim it, choosing a destination recipient.
Replays are rejected. There is no arbitrary target, owner sweep or shared-balance refund. The example assumes a standard non-rebasing token and a reviewed vault. Reentrancy protection and an immutable vault do not make a malicious vault safe.
If authentication, message decoding or callback execution itself fails (including insufficient gas), the Endpoint retains the failed compose for retry; the callback has not recorded a refund credit. Fix the cause and retry that exact compose. Never submit another source transfer as a retry.
Track the application outcome
Monitor Deposited, RefundAvailable and Refunded on your composer by GUID. The Omnisea read API reports router settlement, not these custom application events. Display transfer delivery and deposit completion separately.
Tests and next steps
The repository suite solidity-v2/test/DocsVaultComposer.t.sol bridges through the actual router/facade contracts with mock LayerZero endpoints, then executes the queued compose envelope. It covers successful deposit, replay, authentication, expiry, minimum shares and beneficiary-only refunds. Run from solidity-v2:
npx hardhat test solidity test/DocsVaultComposer.t.solThese are local tests, not a fork validation or audit of your vault. Before production add fork tests for the exact token/vault, gas estimation, blocked-token behavior and UI recovery. See the production checklist.