On this page
Bridge an ERC-20
The same EVM flow handles a canonical token, a return to its original chain, and a representation-to-representation transfer.
1. Identify the source token
const identity = await sourceClient.readContract({
address: omnisea,
abi: bridgeAbi,
functionName: "oftToOriginal",
args: [token],
});
const isRepresentation = identity[3];- Canonical token: use
quoteSendOriginalandsendOriginal. - Omniasset representation: use
quoteSendOFTandsendOFT.
2. Check the destination
For a non-original destination, call representationFor with the canonical chain ID, EVM address type 1, and left-padded original token address. A zero result means the first transfer must deploy the representation.
const isFirstTransfer = representation === zeroAddress;Use false when returning to the original chain, where Omnisea unlocks backing instead of deploying a token.
3. Build options
function u128(value: bigint) {
return value.toString(16).padStart(32, "0");
}
function lzReceiveOptions(gas: bigint) {
return `0x000301002101${u128(gas)}${u128(0n)}` as const;
}
const receiveGas = isFirstTransfer
? await sourceClient.readContract({ address: omnisea, abi: bridgeAbi, functionName: "minCreationGas" })
: await sourceClient.readContract({ address: omnisea, abi: bridgeAbi, functionName: "minTransferGas" });
const options = lzReceiveOptions(receiveGas);Read Executor options before adding destination composition.
4. Approve a canonical token
sendOriginal transfers directly from the sender to OmniseaVault, so approve the core entrypoint. sendOFT burns through the bridge and needs no ERC-20 allowance.
await walletClient.writeContract({
address: token,
abi: erc20Abi,
functionName: "approve",
args: [omnisea, amount],
});5. Quote and send
const compose = {
composer: zeroAddress,
gasLimit: 0n,
message: "0x",
} as const;
const args = [dstEid, token, amount, recipient, isFirstTransfer, compose, options] as const;
const quoteFunction = isRepresentation ? "quoteSendOFT" : "quoteSendOriginal";
const sendFunction = isRepresentation ? "sendOFT" : "sendOriginal";
const totalFee = await sourceClient.readContract({
address: omnisea,
abi: bridgeAbi,
functionName: quoteFunction,
args,
});
const hash = await walletClient.writeContract({
address: omnisea,
abi: bridgeAbi,
functionName: sendFunction,
args,
value: totalFee,
});The native value must equal the live quote exactly. If the LayerZero fee moves before submission, re-quote and retry in a bounded loop.
6. Track and recover
BridgeMessageSent includes the LayerZero GUID. Track the source transaction through LayerZero Scan or the Omnisea interface. If the main destination execution is cached as failed, anyone can retry it or pay to restore the asset to the source sender. A failed optional composer is retried separately and never repeats the bridge credit.