Documentation menu

Bridge an ERC-721

NFT transfers go through OmniseaONFTs - 0x5c0cFc7B0758e5bedE5E9E3A8BA226591f09dC5b on every chain. The flow mirrors the ERC-20 guide with two differences: the value argument is a token ID instead of an amount, and approvals use the ERC-721 interface.

Direction check

const ONFT_BRIDGE = "0x5c0cFc7B0758e5bedE5E9E3A8BA226591f09dC5b";

const [originalChainId, originalToken, isRepresentation] = await client.readContract({
  address: ONFT_BRIDGE,
  abi: onftBridgeAbi,
  functionName: "onftToOriginal",
  args: [collection],
});
  • Original collection -> quoteSendOriginal / sendOriginal (the NFT is locked in the bridge).
  • Representation -> quoteSendONFT / sendONFT (the representation is burned; the original unlocks when it returns home).

Approve

Either a single token or operator approval works:

await walletClient.writeContract({
  address: collection,
  abi: erc721Abi,
  functionName: "approve",
  args: [ONFT_BRIDGE, tokenId],
});

The bridge checks getApproved(tokenId) or isApprovedForAll(owner, bridge) before pulling the token.

Quote and send

const options = lzReceiveOptions(isFirstTransfer ? 500_000n : 400_000n); // ≥ 1_500_000n to Tempo

const totalFee = await client.readContract({
  address: ONFT_BRIDGE,
  abi: onftBridgeAbi,
  functionName: "quoteSendOriginal",
  args: [DST_EID, collection, tokenId, recipient, isFirstTransfer, options],
});

await walletClient.writeContract({
  address: ONFT_BRIDGE,
  abi: onftBridgeAbi,
  functionName: "sendOriginal",
  args: [DST_EID, collection, tokenId, recipient, isFirstTransfer, options],
  value: totalFee,
});

isFirstTransfer works exactly as for ERC-20s: true when the collection has no representation on the destination yet (representationFor(originalChainId, originalToken) == address(0)), which buys the deployment gas.

Metadata

The first transfer deploys an ONFT representation initialized with the original collection's name, symbol, and contractURI. Per-token tokenURI values resolve through the representation so marketplaces render bridged NFTs correctly.

Tracking and recovery

Identical to ERC-20: follow the guid from BridgeMessageSent -> BridgeMessageReceived, inspect on LayerZero Scan, and use failed-message recovery if destination execution fails. A locked NFT can always be restored to the source chain by anyone if its outbound transfer fails.

Feedback