Documentation menu

OFT & ONFT representations

When an asset first arrives on a chain, the local bridge deploys its representation - a minimal token contract that exists only to mirror the original across chains.

ERC-20ERC-721
Template contractOFT.solONFT.sol
Implementation address (all chains)0x6cd48A87251f059D092C241638598780C7A0EeAC0x17B33D4Aa7dc42F4f8A7E7456C5c3a83CB8d6001
Deployed asOpenZeppelin deterministic cloneOpenZeppelin deterministic clone
StandardsERC-20ERC-721 + ERC-2981 royalties
Sole minter & burnerlocal OmniseaOFTslocal OmniseaONFTs

Properties

  • Initialized once, by the bridge. The clone is initialized at deployment with metadata carried in the first transfer's payload, read from the original at send time. There is no other initializer path, and the template implementation itself is locked (_disableInitializers).
  • Mint and burn are bridge-only. Supply on a chain changes only when the bridge delivers a transfer in (mint) or sends one out (burn). Aggregate representation supply across all chains always equals what is locked on the home chain.
  • Standard interfaces. Representations behave as ordinary ERC-20s / ERC-721s for wallets, DEXes, and marketplaces. No transfer hooks, no fees, no rebasing.
  • Chain-invariant address. A given original's representation has the same address on every chain - computable in advance via predictRepresentation(originalChainId, originalToken) (why).

What metadata carries over

FieldERC-20 (OFT)ERC-721 (ONFT)
name, symbolyesyes
decimalsyes (defaults to 18 if the original doesn't implement it)-
contractURIyesyes
tokenURI-yes per token, carried with every transfer and stored at mint
ERC-2981 royalties-yes default royalty (receiver + bps) propagated from the original
Contract ownershipyesyes

All reads are defensive staticcalls: originals that don't implement an interface still bridge, with empty-string / zero / 18-decimal defaults.

Issuers keep control of their token

When the bridge reads the original token's owner() at send time and it is non-zero, ownership of the freshly deployed representation is transferred to that address. The original issuer - not Omnisea - owns their token's representation on every chain.

The owner's powers are metadata-only:

// OFT representation (ERC-20)
function setContractURI(string calldata contractURI_) external onlyOwner;

// ONFT representation (ERC-721)
function setContractURI(string calldata contractURI_) external onlyOwner;
function setDefaultRoyalty(address receiver, uint96 royaltyBps) external onlyOwner;
function deleteDefaultRoyalty() external onlyOwner;

The owner cannot mint, burn, freeze, or move balances - those paths are onlyBridge, and the bridge only acts on verified LayerZero messages.

Reading a representation

// On the representation itself:
address bridge          = representation.bridge();
uint32  originalChainId = representation.originalChainId();
address originalToken   = representation.originalToken();

// Or ask the local bridge (authoritative reverse lookup):
(uint32 chainId, address original, bool exists) = bridge.oftToOriginal(token);   // ERC-20
(uint32 chainId, address original, bool exists) = bridge.onftToOriginal(token);  // ERC-721

exists == true means the token was deployed by the bridge, and (originalChainId, originalToken) is its canonical identity. UIs should display representations under their original's identity (e.g. "USDC - bridged from Base") rather than as independent tokens.

Representations emit Mint(to, amount) / Burn(from, amount) (ERC-20) and Mint(to, tokenId, tokenURI) / Burn(from, tokenId) (ERC-721) alongside the standard transfer events, which makes per-chain supply easy to index.

What representations are not

  • They are not upgradable - the implementation is immutable, and the issuer-owner's powers stop at metadata.
  • They are not wrapped tokens in the lock-box sense on their own chain: there is no local redemption. Exiting a representation means bridging it (burn) toward any chain, including its home chain (unlock).
Feedback