Documentation menu

OmniseaOFTs

The ERC-20 bridge and representation factory. Immutable, permissionless, deployed at 0xA98dF8b908F5a70b8036FD00239E66D9B73c64ad on every chain.

contract OmniseaOFTs is Ownable, ReentrancyGuard

Structs

struct Hook {
    address target;
    uint128 gasLimit;
    bytes   payload;
}

struct TokenPayload {
    MessageKind messageKind;   // Transfer | Restore
    uint32  originalChainId;
    address originalToken;
    uint256 amount;
    address sender;
    address recipient;
    string  tokenName;
    string  tokenSymbol;
    uint8   tokenDecimals;
    string  contractURI;
    address contractOwner;
    Hook    hook;
}

struct OriginalToken {
    uint32  originalChainId;
    address originalToken;
    bool    exists;
}

struct FailedMessage {
    bool    exists;
    uint32  srcEid;
    bytes32 sender;
    uint64  nonce;
    bytes   message;
    bytes32 messageHash;
    bytes32 reasonHash;
    uint256 failedAt;
}

Send functions

Each send/quote function has three overloads: without isFirstTransfer (defaults to false), with isFirstTransfer, and with isFirstTransfer + Hook.

sendOriginal

Locks an original ERC-20 held by the caller and sends it to dstEid. Requires prior ERC-20 approval. Reverts with RepresentationMustUseSendOFT if originalToken is an Omnisea representation.

function sendOriginal(
    uint32 dstEid,
    address originalToken,
    uint256 amount,
    address recipient,
    bool isFirstTransfer,
    Hook calldata hook,        // optional overload
    bytes calldata options
) external payable returns (MessagingReceipt memory receipt);

Fee-on-transfer aware: the bridged amount is the balance delta actually received by the bridge. Rebasing, reflection, blacklist, pausable, or admin-blocked transfer tokens are unsupported because they can mutate balances or block bridge transfers outside Omnisea's accounting.

sendOFT

Burns a representation held by the caller and sends it to dstEid (back home, or onward to a third chain). No approval needed. Reverts with NotRepresentation for unknown tokens.

function sendOFT(
    uint32 dstEid,
    address representation,
    uint256 amount,
    address recipient,
    bool isFirstTransfer,
    Hook calldata hook,        // optional overload
    bytes calldata options
) external payable returns (MessagingReceipt memory receipt);

quoteSendOriginal / quoteSendOFT

View functions mirroring the send overloads. Return the total fee (LayerZero fee + fixed protocol fee) the matching send will require - msg.value must equal it exactly on standard chains; on fee-token chains the value is denominated in the fee token. See Fees.

function quoteSendOriginal(uint32 dstEid, address originalToken, uint256 amount, address recipient, bool isFirstTransfer, bytes calldata options) external view returns (uint256 totalFee);
function quoteSendOFT(uint32 dstEid, address representation, uint256 amount, address recipient, bool isFirstTransfer, bytes calldata options) external view returns (uint256 totalFee);

Identity & discovery

/// Hash identifying an asset globally.
function tokenKey(uint32 originalChainId, address originalToken) public pure returns (bytes32);

/// Existing local representation, or address(0) if not deployed yet.
function representationFor(uint32 originalChainId, address originalToken) external view returns (address);

/// Deterministic address the representation will have (valid on every chain, before deployment).
function predictRepresentation(uint32 originalChainId, address originalToken) external view returns (address);

/// Reverse lookup: is this local token an Omnisea representation, and of what?
mapping(address representation => OriginalToken original) public oftToOriginal;

/// Forward lookup by token key.
mapping(bytes32 originalTokenKey => address representation) public originalToOFT;

/// Amount of each original currently locked by this bridge.
mapping(address originalToken => uint256 amount) public lockedBalance;

Fees & configuration (views)

function endpointNativeToken() public view returns (address); // address(0) = pay msg.value; else ERC-20 fee token
uint256 public fixedProtocolFee;
uint256 public fixedFeeTokenProtocolFee;
uint256 public collectedProtocolFees;
mapping(address feeToken => uint256 amount) public collectedFeeTokenProtocolFees;
uint128 public minTransferGas;
uint128 public minCreationGas;
ILayerZeroEndpointV2 public immutable endpoint;
uint32 public immutable localEndpointId;
uint32 public immutable localChainId;
address public immutable oftImplementation;
mapping(uint32 eid => bytes32 peer) public peers;
mapping(uint32 eid => uint64 setAt) public peerSetAt;
uint64 public constant endpointActivationTime = 7 days;

Failed-message recovery

Permissionless - see the recovery guide.

mapping(bytes32 guid => FailedMessage failedMessage) public failedMessages;

function retryFailedMessage(bytes32 guid) external;
function quoteRestore(bytes32 guid, bytes calldata options) external view returns (uint256 nativeFee);
function restoreFailedMessage(bytes32 guid, bytes calldata options) external payable;

Restore quotes and restore sends validate the provided LayerZero executor options against minTransferGas.

Owner functions

Operational configuration only - the owner cannot touch user funds or upgrade code (security model).

function setPeer(uint32 eid, bytes32 peer) external onlyOwner;
function peerActivationTime(uint32 eid) public view returns (uint64);
function isPeerActive(uint32 eid) public view returns (bool);
function isTrustedPeer(uint32 eid, bytes32 peer) public view returns (bool);
function setLayerZeroDelegate(address delegate) external onlyOwner;
function setFixedProtocolFee(uint256 newFee) external onlyOwner;
function setFixedFeeTokenProtocolFee(uint256 newFee) external onlyOwner;
function setMinTransferGas(uint128 newGas) external onlyOwner;
function setMinCreationGas(uint128 newGas) external onlyOwner;
function withdrawProtocolFees(address payable recipient, uint256 amount) external onlyOwner;
function withdrawFeeTokenProtocolFees(address feeToken, address recipient, uint256 amount) external onlyOwner;

setPeer can only initialize an unset endpoint ID. Setting the same peer again is a no-op; setting zero or a different peer reverts. First-time same-address CREATE3 peers activate immediately, while first-time different-address peers activate after endpointActivationTime.

Events

// Transfers
event OriginalLocked(address indexed originalToken, address indexed owner, uint256 requestedAmount, uint256 actualAmount);
event RepresentationBurned(address indexed representation, uint32 indexed originalChainId, address indexed originalToken, address owner, uint256 amount);
event OriginalUnlocked(address indexed originalToken, address indexed recipient, uint256 amount);
event RepresentationDeployed(uint32 indexed originalChainId, address indexed originalToken, address indexed representation);
event RepresentationMinted(address indexed representation, uint32 indexed originalChainId, address indexed originalToken, address recipient, uint256 amount);
event BridgeMessageSent(bytes32 indexed guid, uint32 indexed dstEid, uint32 indexed originalChainId, address originalToken, uint256 amount, address recipient, uint256 nativeFee, uint256 protocolFee);
event BridgeMessageReceived(bytes32 indexed guid, uint32 indexed srcEid, uint32 indexed originalChainId, address originalToken, address recipient, uint256 amount);

// Recovery
event MessageFailed(bytes32 indexed guid, uint32 indexed srcEid, uint32 indexed originalChainId, address originalToken, address recipient, uint256 amount, bytes32 reasonHash);
event FailedMessageRetried(bytes32 indexed guid, uint32 indexed srcEid);
event FailedMessageRestored(bytes32 indexed guid, bytes32 indexed restoreGuid, uint32 indexed dstEid, address refundRecipient, uint256 nativeFee);

// Hooks
event HookSucceeded(bytes32 indexed guid, address indexed target, address indexed localToken, address recipient, uint256 value, uint128 gasLimit, bytes32 payloadHash, bytes32 returnDataHash);
event HookFailed(bytes32 indexed guid, address indexed target, address indexed localToken, address recipient, uint256 value, uint128 gasLimit, bytes32 payloadHash, bytes32 reasonHash);

// Configuration
event PeerSet(uint32 indexed eid, bytes32 peer, uint64 setAt, uint64 activationTime, bool sameAddressPeer);
event FixedProtocolFeeChanged(uint256 previousFee, uint256 newFee);
event FixedFeeTokenProtocolFeeChanged(uint256 previousFee, uint256 newFee);
event ProtocolFeesWithdrawn(address indexed recipient, uint256 amount);
event FeeTokenProtocolFeesWithdrawn(address indexed feeToken, address indexed recipient, uint256 amount);
event LayerZeroDelegateSet(address indexed delegate);
event MinTransferGasChanged(uint128 previousGas, uint128 newGas);
event MinCreationGasChanged(uint128 previousGas, uint128 newGas);

Errors

ErrorThrown when
InvalidAddress()Zero address in constructor/config arguments
InvalidAmount()Zero amount, or zero received after fee-on-transfer
InvalidDestination()Destination EID has no peer, or recipient is zero
InvalidEndpoint()lzReceive caller is not the LayerZero endpoint
InvalidOriginal()Original token fails validation
InvalidPeer(uint32 eid, bytes32 peer)Message sender is not the active trusted peer, or zero peer configuration was attempted
InvalidSender()Unexpected message sender
InvalidHook()Hook payload/gas set with zero target
InsufficientLayerZeroGas(uint128 provided, uint128 required)Options buy less than the floor (+ hook gas)
FailedMessageNotFound(bytes32 guid)Retry/restore of unknown guid
NotRepresentation()sendOFT with a token the bridge did not deploy
PeerAlreadySet(uint32 eid, bytes32 currentPeer, bytes32 newPeer)Attempt to replace an already configured peer
RepresentationMustUseSendOFT()sendOriginal with a representation
WithdrawFailed()Native fee withdrawal transfer failed
Feedback