LayerZero OFT tokens are a new standard for cross-chain assets. OFT’s (Omnichain Fungible Tokens) are not actually tokens themselves, but rather a set of instructions that tell LayerZero how to transfer tokens between different blockchains. In this tutorial we will be building a LayerZero OFT ERC20 token using Solidity and Remix.
- How LayerZero OFT Tokens Work
- Example OFT Token Solidity Code
- Example OFT Proxy Solidity Code
- OFT Token Cross Chain Transfers
How LayerZero OFT Tokens Work
LayerZero’s OFT standard is a Solidity library and pattern for creating cross chain tokens. The developer will need to deploy a token contract on each chain that they want to interact with.
We then use the library function setTrustedRemoteAddress() to create a mapping of where each token is on each chain. Note that LayerZero chain IDs are different to Ethereum chain IDs*.
So what we end up with is each token contract on each chain has a list of chains and token contract addresses for the contract that has been deployed. On Ethereum mainnet or whichever chain is the primary devs will also mint some tokens. You can see in the contract below that there is a if (block.chainid == 5) statement which means we can deploy the same contract everywhere and it will only mint the tokens on Goerli.
At this point we have all the tokens on Goerli and the library provides another fuction sendFrom() which allows us to move tokens between chains.
When a holder uses sendFrom() on Goerli the tokens are burnt while providing minting authority for the same amount on the destination chain. LayerZero provides the cross-chain communications to enable this.
The holder on the secondary receiver chain can then use the tokens as normal or send them back or to another chain reversing the process of burning them locally and minting them on the next chain.
Example OFT Token Solidity Code
This LayerZero OFT is from my Solidity Snippets Github repo.
Full code: https://github.com/jamesbachini/Solidity-Snippets/blob/main/contracts/LayerZeroOFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/*
// https://layerzero.gitbook.io/docs/technical-reference/testnet/testnet-addresses
Goerli lzEndpointAddress = 0xbfD2135BFfbb0B5378b56643c2Df8a87552Bfa23
chainId: 10121 deploymentAddress =
Optimism-Goerli lzEndpointAddress = 0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1
chainId: 10132 deploymentAddress =
*/
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "https://github.com/LayerZero-Labs/solidity-examples/blob/main/contracts/token/oft/OFTCore.sol";
import "https://github.com/LayerZero-Labs/solidity-examples/blob/main/contracts/token/oft/IOFT.sol";
contract LayerZeroOFT is OFTCore, ERC20, IOFT {
address constant lzEndpointAddress = 0xbfD2135BFfbb0B5378b56643c2Df8a87552Bfa23;
constructor() ERC20("LayerZeroOFT", "LZOFT") OFTCore(lzEndpointAddress) {
if (block.chainid == 5) { // Only mint initial supply on Goerli
_mint(msg.sender, 1_000_000 * 10 ** decimals());
}
}
function supportsInterface(bytes4 interfaceId) public view virtual override(OFTCore, IERC165) returns (bool) {
return interfaceId == type(IOFT).interfaceId || interfaceId == type(IERC20).interfaceId || super.supportsInterface(interfaceId);
}
function token() public view virtual override returns (address) {
return address(this);
}
function circulatingSupply() public view virtual override returns (uint) {
return totalSupply();
}
function _debitFrom(address _from, uint16, bytes memory, uint _amount) internal virtual override returns(uint) {
address spender = _msgSender();
if (_from != spender) _spendAllowance(_from, spender, _amount);
_burn(_from, _amount);
return _amount;
}
function _creditTo(uint16, address _toAddress, uint _amount) internal virtual override returns(uint) {
_mint(_toAddress, _amount);
return _amount;
}
}
Example OFT Proxy Solidity Code
The OFT Proxy contract is used like a vault to create a cross-chain tokenised wrapper for an existing ERC20 token. This code is straight from the LayerZero examples at: https://github.com/LayerZero-Labs/solidity-examples/blob/main/contracts/token/oft/extension/ProxyOFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../OFTCore.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract ProxyOFT is OFTCore {
using SafeERC20 for IERC20;
IERC20 internal immutable innerToken;
constructor(address _lzEndpoint, address _token) OFTCore(_lzEndpoint) {
innerToken = IERC20(_token);
}
function circulatingSupply() public view virtual override returns (uint) {
unchecked {
return innerToken.totalSupply() - innerToken.balanceOf(address(this));
}
}
function token() public view virtual override returns (address) {
return address(innerToken);
}
function _debitFrom(address _from, uint16, bytes memory, uint _amount) internal virtual override returns(uint) {
require(_from == _msgSender(), "ProxyOFT: owner is not send caller");
uint before = innerToken.balanceOf(address(this));
innerToken.safeTransferFrom(_from, address(this), _amount);
return innerToken.balanceOf(address(this)) - before;
}
function _creditTo(uint16, address _toAddress, uint _amount) internal virtual override returns(uint) {
uint before = innerToken.balanceOf(_toAddress);
innerToken.safeTransfer(_toAddress, _amount);
return innerToken.balanceOf(_toAddress) - before;
}
}
OFT Token Cross Chain Transfers
Ideally developers will want to deploy tokens from a clean address with no transaction history on any chains. This will mean the token contract addresses will be the same on each chain and make things easier to manage.
A deployment script can be written in Hardhat to fund the wallets, deploy the contracts and call setTrustedRemoteAddress() functions.
Once the token contracts are set up a simple user interface can be prepared to make it easy for users to move tokens between chains. Frontend devs can use Viem or Ethers to connect the sendFrom() function to a web3 interface.
The BTC.b cross-chain bitcoin token has an elegant, simple UI which can provide a good working example of what this could look like.