On this page

Transfer

Every transfer starts at the Omnisea contract on the source chain. Use one function for an original ERC-20 and another for an Omniasset representation.

Source tokenQuoteSendERC-20 approval
Original ERC-20quoteSendOriginalsendOriginalRequired
OmniassetquoteSendOFTsendOFTNot required

The current Omnisea address is the same on every supported EVM chain. See Deployments.

Detect the token role

Call oftToOriginal(token) on the source contract.

function oftToOriginal(address token)
    external
    view
    returns (uint32 originalEid, bytes originalToken, bool exists);
  • exists == false: the token is an original ERC-20 on this chain.
  • exists == true: the token is an Omniasset. The returned endpoint ID and token bytes identify its original asset.

Do not infer the role from a token name or symbol.

Check the destination

For a destination other than the original chain, check whether its Omniasset already exists:

function representationFor(uint32 originalEid, bytes originalToken)
    external
    view
    returns (address);

Pass isFirstTransfer = true when this call returns the zero address. Pass false when the Omniasset already exists or when the transfer returns to the original chain.

Passing true for an existing Omniasset changes no settlement logic; it only requires the options to meet the higher creation-gas floor and will normally produce a higher quote. Passing false for a missing Omniasset does not skip deployment, but it can underfund destination execution if the options contain only transfer gas.

For an original EVM token, originalToken is its 20-byte contract address and originalEid is the source chain's LayerZero endpoint ID.

Plain-transfer parameters

The EVM send methods use the same parameters:

struct ComposeParams {
    address composer;
    uint128 gasLimit;
    bytes message;
}

function sendOriginal(
    uint32 dstEid,
    address originalToken,
    uint256 amount,
    address recipient,
    bool isFirstTransfer,
    ComposeParams compose,
    bytes options
) external payable returns (MessagingReceipt memory);

function sendOFT(
    uint32 dstEid,
    address representation,
    uint256 amount,
    address recipient,
    bool isFirstTransfer,
    ComposeParams compose,
    bytes options
) external payable returns (MessagingReceipt memory);

For a plain ERC-20 transfer, use an empty composition:

const compose = {
  composer: zeroAddress,
  gasLimit: 0n,
  message: "0x",
} as const;

Build options from the current gas minimum. See LayerZero.

Approve originals

sendOriginal transfers the token from the sender into OmniseaVault. Approve the Omnisea core contract, not the Vault or Router.

await walletClient.writeContract({
  account,
  address: token,
  abi: erc20Abi,
  functionName: "approve",
  args: [omnisea, amount],
});

Wait for the approval receipt before sending. sendOFT burns the sender's Omniasset directly and does not use an allowance.

Quote, then send

Call the matching quote method with the exact arguments you will pass to the send method. Submit the returned fee as msg.value.

const args = [dstEid, token, amount, recipient, isFirstTransfer, compose, options] as const;

const fee = await publicClient.readContract({
  address: omnisea,
  abi: omniseaAbi,
  functionName: isRepresentation ? "quoteSendOFT" : "quoteSendOriginal",
  args,
});

const hash = await walletClient.writeContract({
  account,
  address: omnisea,
  abi: omniseaAbi,
  functionName: isRepresentation ? "sendOFT" : "sendOriginal",
  args,
  value: fee,
});

Re-quote immediately before the write. The contract requires the exact fee and rejects both underpayment and overpayment.

Amount delivered

For a standard ERC-20, the requested amount is delivered. For a fee-on-transfer original, Omnisea measures the Vault balance change and sends the amount actually received. A zero balance change reverts.

On the destination, Omnisea does one of two things:

  • destination is the original chain: unlock the original token from the Vault;
  • any other destination: deploy the Omniasset if needed, then mint it to the recipient.

Track the resulting message with Events and understand retry and restore behavior in Transfer lifecycle.

Introducing Omnipad-Launch tokens between chains