This tutorial will guide you through the process of building a token bridge using LayerZero v2, specifically employing the OFTAdapter on mainnet and the OFT.sol contract on all other chains. We’ll cover the setup and deployment workflow, ensuring you have a comprehensive understanding of the process.
Before we delve into the deployment process, it’s crucial to understand the key components:
- OFTAdapter.sol This is used on the mainnet to facilitate token bridging.
- OFT.sol This contract is deployed on all other chains to enable cross-chain token transfers.
Begin by deploying the OFT contracts to all the chains you intend to connect. This forms the foundation of your token bridge network. You can do this via remix, hardhat or foundry.
OFTAdapter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { OFTAdapter } from "@layerzerolabs/oft-evm/contracts/OFTAdapter.sol";
contract MyOFTAdapter is OFTAdapter {
constructor(
address _token,
address _lzEndpoint,
address _delegate
) OFTAdapter(_token, _lzEndpoint, _delegate) Ownable(_delegate) {}
}
OFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol";
contract MyOFT is OFT {
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
address _delegate
) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}
}
Peer & DVN Settings
After deployment, you need to whitelist each destination contract on every destination chain. This is done by calling the setPeer
function on each OFT contract. Here’s how you can accomplish this:
uint32 aEid = 30110; // Arbitrum
uint32 bEid = 30184; // Base
MyOFT aOFT;
MyOFT bOFT;
function addressToBytes32(address _addr) public pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
// Call on both sides per pathway
aOFT.setPeer(bEid, addressToBytes32(address(bOFT)));
bOFT.setPeer(aEid, addressToBytes32(address(aOFT)));
Ensure you replace the example endpoint IDs with the actual ones for your target chains. You can find the correct endpoint IDs in the LayerZero documentation under Supported Chains.
The next step involves setting up the Default Virtual Node (DVN) configuration. This includes optional settings such as block confirmations, security threshold, the Executor, maximum message size, and send/receive libraries. Here are the key functions you’ll need to call:
EndpointV2.setSendLibrary(aOFT, bEid, newLib)
EndpointV2.setReceiveLibrary(aOFT, bEid, newLib, gracePeriod)
EndpointV2.setReceiveLibraryTimeout(aOFT, bEid, lib, gracePeriod)
EndpointV2.setConfig(aOFT, sendLibrary, sendConfig)
EndpointV2.setConfig(aOFT, receiveLibrary, receiveConfig)
EndpointV2.setDelegate(delegate)
These configurations are stored on-chain as part of EndpointV2, along with your respective SendLibrary and ReceiveLibrary. It’s important to review these settings carefully as they control the verification mechanisms of messages sent between your OApps.
If you don’t set any custom configurations, the system will use the default configurations set by LayerZero Labs. However, it’s generally recommended to review and set your own configurations to ensure they meet your specific security and performance requirements.
Finally before doing a cross-chain transfer you need to approve the OFTadapter as a spender of your ERC20 token. This step is crucial for the token amount you want to transfer. Call the approve function on your ERC20 token contract:
erc20Token.approve(address(oftAdapter), amountToTransfer);
OFTAdapter.send(_sendParam, _fee, _refundAddress);
Remember to thoroughly test your implementation on testnets before deploying to mainnet. Additionally, keep your contracts updated and monitor for any announcements from LayerZero regarding protocol upgrades or security patches.
There’s more information in the official docs: https://docs.layerzero.network/v2/developers/evm/oft/quickstart