Send NFTs Across Chains
Introduction
LayerZero ONFT V1FV enables cross-chain transfers of existing ERC721 tokens. For testing purposes, the ShimmerEVM Testnet is chosen as the source chain, while the BNB Testnet is chosen as the destination chain.
You can clone the Utilities for LayerZero ONFT V1 from IOTA Community GitHub Repository.
Why Would a User Need to Send ERC721 Tokens Across Chains?
By facilitating the movement of ERC721 tokens across chains, users gain flexibility and can optimize their NFT usage according to their specific needs, preferences, and circumstances.
Enable the Existing ERC721 Tokens for Cross-Chain Sending
To enable the existing ERC721 tokens for cross-chain sending, you will need the ProxyONFT
contract on the source
chain,
and the ONFT contract on the destination chain) are needed.
The origin NFT token will be locked in the ProxyONFT
contract so that the ONFT-wrapped tokens will be minted on the
destination chain. If the NFT token already exists on the destination chain (i.e., when the ONFT-wrapped token on
the destination chain is sent back to the source chain), no new token minting will happen. Instead, the NFT tokens will be
transferred from the ONFT contract to the user's wallet address. Relevant code
Enable Cross-Chain Sending for Unloached ERC721 NFTs
If you are launching a new ERC721 token, you can use the ONFT standard to enable cross-chain sending without the need of
ProxyONFT
. As with existing tokens, the NFT will be locked on the source chain and minted or transferred on the destination chain.
Scripts
Deploy the ProxyONFT and ONFT Contracts
For ERC721
MyProxyONFT721.sol:
- CTOR:
minGasToTransferAndStore
: The minimum gas needed to transfer and store your NFT is typically 100k for ERC721. This value would vary depending on your contract complexity; it's recommended to test. If this value is set too low, the destination tx will fail, and a manual retry is needed.lzEndpoint
: LayerZero Endpoint on the source chain.proxyToken
: deployed contract address of the NFT tokens on the source chain.
- CTOR:
MyONFT721.sol:
- CTOR:
name
: name of the ONFT-wrapped tokens on the destination chainsymbol
: symbol of the ONFT-wrapped tokens on the destination chainminGasToTransferAndStore
: The minimum gas needed to transfer and store your NFT typically 100k for ERC721. This value would vary depending on your contract complexity; it's recommended to test. If this value is set too low, the destination tx will fail, and a manual retry is needed.lzEndpoint
: - lzEndpoint: LayerZero Endpoint on the destination chain
- CTOR:
Set the Trusted Remote
For existing ERC721 tokens, the ProxyONFT
and ONFT
contract instances must be paired.
For the upcoming ERC721 tokens that want to leverage the ONFT
standard, the ONFT
contract instance on the source chain
needs to be paired with another ONFT
contract instance on the destination chain.
You can set this using the setTrustedRemote
method.
Set the minGasLimit
Both the ProxyONFT
and the ONFT
contract instanceS need to be set for minimum gas on destination(minGasLimit
).
You can set his using the setMinDstGas
method.
It is required that minDstGas
<= providedGasLimit
, which is to be set via adapterParams
upon cross-chain sending on
the source chain.
Set the Batch Size Limit
Both the ProxyONFT
and the ONFT
contract instances need to set a limit for the batch size on the source
chain to limit the number of tokens to be sent to another chain when using the
sendBatchFrom
method.
You can set this using the setDstChainIdToBatchLimit
method; the default value is 1.
How To Send Tokens From a Source Chain to a Destination Chain (and Vice-Versa)
Required Contracts
From the Source Chain to the Destination Chain
For the existing ERC721 tokens, you will need the ProxyONFT
contract on the source chain and the ONFT
contract on
the destination chain. The procedure is as follows:
- The sender approves his ERC721 tokens for the
ProxyONFT
contract. - The sender calls the function
estimateSendFee()
of the ProxyONFT contract to estimate cross-chain fee to be paid in native on the source chain. - The sender calls the function
sendFrom()
of the ProxyONFT contract to transfer tokens on source chain to destination chain. - (Optional) Wait for the transaction finalization on the destination chain by using the @layerzerolabs/scan-client library.
From the Destination Chain Back to the Source Chain
To send back the ONFT-wrapped tokens on the destination chain to the source chain, the procedure is similar as the
approve step is also required, but the operations will happen on the ONFT
contract.
References and Tools
AdapterParams
- You can use the LayerZero Repository as a reference to set gas drop on the destination in
adapterParams
.- The provided gas drop must be
<=
the config one. Otherwise, you will getdstNativeAmt
too large error.
- The provided gas drop must be
- You can use the LayerZero Repository as a refernce to set default
adapterParams
without needing a gas drop.
LayerZero
Install and compile the Library
After you have cloned the IOTA Community Utilities for LayerZero ONFT V1 repository, you should run the following command to install:
yarn
then compile the contracts:
yarn compile
Set Your Configuration
You should copy the
template .env.example
file to a
file called .env
, and edit any of the configuration options you see fit.
cp .env.example .env
Deploy the Contracts
Deploy a mock ERC721
yarn deploy-ERC721-mock-smr-Testnet
Expected log output :
npx hardhat run scripts/deploy_ERC721.ts --network shimmerEvmTestnet
Deployed ERC721Mock contract address:0xFddbA8928a763679fb8C99d12541B7c6177e9c3c
Done in 4.49s.
Deploy ProxyONFT721
on the source chain
You can use the following command to deploy ProxyONFT721 on the source chain (e.g., ShimmerEVM Testnet):
yarn deploy-proxy-onft-smr-Testnet
Expected log output :
npx hardhat run scripts/deploy_proxy_onft721.ts --network shimmerEvmTestnet
Deployed MyProxyONFT721 contract address:0x7B0D46219C915e7Ff503C7F83a805c0b2F4ab2F2
Done in 4.50s.
Deploy ProxyONFT721
on the destination chain
You can use the following command to deploy ProxyONFT721 on the destination chain (e.g., BNB Testnet):
yarn set-min-dest-gas-onft-bnb-Testnet
Expected log output :
export isForProxy=false && npx hardhat run scripts/set_min_destination_gas.ts --network bnbTestnet
setMinDstGas - isForProxy:false, proxyONFTContractAddress:0x7B0D46219C915e7Ff503C7F83a805c0b2F4ab2F2, onftContractAddress:0xC617A0Bd9DC6093a304515d3dbFF4244333fDeBB, lzEndpointIdOnRemoteChain:10230, minDstGas:150000
setMinDstGas (packetType 0) tx: 0xce044ded17daa77a8aefc3d39b99c5381216eb4057ddce6253affde6cda2091c
setMinDstGas (packetType 1) tx: 0x3a26ae40ac058099bfd8b85910009a5e5e8b03f16a5f032b572827d48be8f2b0
Done in 9.34s.
Set the Minimum Destination Gas
On the source chain
You can use the following command to set the minimum destination gas on the ProxyONFT
contract on the source chain (e.g., ShimmerEVM Testnet):
yarn set-min-dest-gas-proxy-onft-smr-Testnet
Expected log output :
export isForProxy=true && npx hardhat run scripts/set_min_destination_gas.ts --network shimmerEvmTestnet
setMinDstGas - isForProxy:true, proxyONFTContractAddress:0x7B0D46219C915e7Ff503C7F83a805c0b2F4ab2F2, onftContractAddress:0xC617A0Bd9DC6093a304515d3dbFF4244333fDeBB, lzEndpointIdOnRemoteChain:10102, minDstGas:150000
setMinDstGas (packetType 0) tx: 0xcab06e9989448153a4bbc1bb166fc2d33467f3311d1851bf2ff719d982daa613
setMinDstGas (packetType 1) tx: 0xe78fd3f0bf668fafbc423decd2cf14a27d74543af3ac9daf031f0b278c22ea78
Done in 6.07s.
On the destination chain
You can use the following command to set the minimum destination gas on the ONFT
contract on the destination chain (e.g., BNB Testnet):
yarn set-min-dest-gas-onft-bnb-Testnet
Expected log output :
export isForProxy=false && npx hardhat run scripts/set_min_destination_gas.ts --network bnbTestnet
setMinDstGas - isForProxy:false, proxyONFTContractAddress:0x7B0D46219C915e7Ff503C7F83a805c0b2F4ab2F2, onftContractAddress:0xC617A0Bd9DC6093a304515d3dbFF4244333fDeBB, lzEndpointIdOnRemoteChain:10230, minDstGas:150000
setMinDstGas (packetType 0) tx: 0xce044ded17daa77a8aefc3d39b99c5381216eb4057ddce6253affde6cda2091c
setMinDstGas (packetType 1) tx: 0x3a26ae40ac058099bfd8b85910009a5e5e8b03f16a5f032b572827d48be8f2b0
Done in 9.34s.
Set the batch size limit
On the source chain
You can use the following command to set batch size limits on the ProxyONFT
contract on the source chain (e.g., ShimmerEVM Testnet):
yarn set-batch-size-limit-proxy-onft-smr-Testnet
Expected log output :
export isForProxy=true && npx hardhat run scripts/set_batch_size_limit.ts --network shimmerEvmTestnet
setBatchSizeLimit - isForProxy:true, proxyONFTContractAddress:0x7B0D46219C915e7Ff503C7F83a805c0b2F4ab2F2, onftContractAddress:0xC617A0Bd9DC6093a304515d3dbFF4244333fDeBB, lzEndpointIdOnRemoteChain:10102, batchSizeLimit:1
setBatchSizeLimit tx: 0x70c23b3d3d5e94ef82e50944f7eba93fa1fe8db3a5487ac371015e7a14482e75
Done in 4.28s.
On the destination chain
You can use the following command to set batch size limits on the ONFT
contract on the destination chain (e.g., BNB Testnet):
yarn set-batch-size-limit-onft-bnb-Testnet
Expected log output :
export isForProxy=false && npx hardhat run scripts/set_batch_size_limit.ts --network bnbTestnet
setBatchSizeLimit - isForProxy:false, proxyONFTContractAddress:0x7B0D46219C915e7Ff503C7F83a805c0b2F4ab2F2, onftContractAddress:0xC617A0Bd9DC6093a304515d3dbFF4244333fDeBB, lzEndpointIdOnRemoteChain:10230, batchSizeLimit:1
setBatchSizeLimit tx: 0x8cb44c2195ac93da552c646677e6585c95ab172df19463637541933ec70dc9b8
Done in 4.26s.
Set the Trusted Remote
On the source chain
You can use the following command to set a trusted remote on the ProxyONFT
contract on the source chain (e.g., ShimmerEVM Testnet):
yarn set-remote-proxy-onft-smr-Testnet
Expected log output :
export isForProxy=true && npx hardhat run scripts/set_trusted_remote.ts --network shimmerEvmTestnet
setTrustedRemote - isForProxy:true, proxyONFTContractAddress:0x7B0D46219C915e7Ff503C7F83a805c0b2F4ab2F2, onftContractAddress:0xC617A0Bd9DC6093a304515d3dbFF4244333fDeBB, lzEndpointIdOnRemoteChain:10102
setTrustedRemote tx: 0xce52c0f25090ef7c1668ef04ff2f6098551c9f56b3ce881d17181bf106457016
Done in 4.24s.
On the destination chain
You can use the following command to set a trusted remote on the ONFT
contract on the destination chain (e.g., BNB Testnet):
yarn set-remote-onft-bnb-Testnet
Expected log output :
export isForProxy=false && npx hardhat run scripts/set_trusted_remote.ts --network bnbTestnet
setTrustedRemote - isForProxy:false, proxyONFTContractAddress:0x7B0D46219C915e7Ff503C7F83a805c0b2F4ab2F2, onftContractAddress:0xC617A0Bd9DC6093a304515d3dbFF4244333fDeBB, lzEndpointIdOnRemoteChain:10230
setTrustedRemote tx: 0x311a0568b5afce7d601df2613f8ff80428d8a4d2f2c91012e0e4a8cbc0aedf59
Done in 4.88s.
Send Origin Tokens From the Source Chain To the Destination Chain
yarn send-onft-from-smr-Testnet
Expected log output:
npx hardhat run scripts/send_onft.ts --network shimmerEvmTestnet
sendONFT - proxyONFTContractAddress:0x7B0D46219C915e7Ff503C7F83a805c0b2F4ab2F2, onftContractAddress:0xC617A0Bd9DC6093a304515d3dbFF4244333fDeBB, lzEndpointIdOnSrcChain:10230, lzEndpointIdOnDestChain:10102, gasDropInWeiOnDestChain:0, providedGasLimit:200000, receivingAccountAddress:0x5e812d3128D8fD7CEac08CEca1Cd879E76a6E028, sender: 0x57A4bD139Fb673D364A6f12Df9177A3f686625F3, nftTokenId:2, nftTokenAddress:0xFddbA8928a763679fb8C99d12541B7c6177e9c3c
sendONFT - approve tx: 0xa871bc79e45bf20f33c626044d6e208460c5745ab1f13d476dcbe04e1da7e592
sendONFT - estimated nativeFee: 158.319172348046094655
sendONFT - send tx on source chain: 0x72779c7549053194e42bcc78f78cf65e876867f0516dc91f28986c854e652596
Wait for cross-chain tx finalization by LayerZero ...
sendONFT - received tx on destination chain: 0x2700a9d35c139eb84ba07b75490e6627a30e00bde130e3cb7c1cbb81c0326138
Done in 53.50s.
Send ONFT-Wrapped Tokens Back From the Destination Chain Back To the Origin Chain
yarn send-onft-back-from-bnb-Testnet
Expected log output:
npx hardhat run scripts/send_onft_back.ts --network bnbTestnet
sendONFTBack - proxyONFTContractAddress:0x7B0D46219C915e7Ff503C7F83a805c0b2F4ab2F2, onftContractAddress:0xC617A0Bd9DC6093a304515d3dbFF4244333fDeBB, lzEndpointIdOnSrcChain:10230, lzEndpointIdOnDestChain:10102, gasDropInWeiOnDestChain:0, providedGasLimit:200000, receivingAccountAddress:0x57A4bD139Fb673D364A6f12Df9177A3f686625F3, sender: 0x60917645A28258a75836aF63633850c5F3561C1b, nftTokenId:2, nftTokenAddress:0xFddbA8928a763679fb8C99d12541B7c6177e9c3c
sendONFTBack - approve tx: 0xe5bfff108528efdc67e72896845f0ad3e0186b4ed64835e7c5f3552eaab69d99
sendONFTBack - estimated nativeFee: 0.000498452810033053
sendONFTBack - send tx on source chain: 0xa43bb5547a5a35730fe183b4d554416a4ea34852e510d21f24d173db75db4e79
Wait for cross-chain tx finalization by LayerZero ...
sendONFTBack - received tx on destination chain: 0xb05fa2de194153819b26d17893278c485abbaf355fa24f26fbc7a4c759994cde
Done in 212.16s.