OmniseaOFTs
The ERC-20 bridge and representation factory. Immutable, permissionless, deployed at 0xA98dF8b908F5a70b8036FD00239E66D9B73c64ad on every chain.
contract OmniseaOFTs is Ownable, ReentrancyGuardStructs
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
| Error | Thrown 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 |