Build Your First Omni-Chain dApp
This guide explains how to deploy OATS tokens and bridge them between Ethereum Sepolia and Arbitrum Sepolia testnets via burn/mint or lock/unlock.
Prerequisites
- MetaMask browser extension installed.
- Testnet ETH on both Ethereum Sepolia and Arbitrum Sepolia. You can get this from public faucets.
Burn/Mint
Step 1: Get the OATS Token Code
- Open Remix IDE: https://remix.ethereum.org/Â
- Create a new file named
OATSToken.sol. - Copy and paste the following code into the file. This is a simplified version of the
OATSTokencontract for demonstration.
OATSToken.sol
OATSToken.sol// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
// A simplified interface for the Analog Gateway
interface IGateway {
function submitMessage(address destinationAddress, uint16 network, uint64 gasLimit, bytes calldata data) external payable returns (bytes32);
function estimateMessageCost(uint16 network, uint16 messageSize, uint64 gasLimit) external view returns (uint256);
}
// A simplified interface for receiving messages
interface IGmpReceiver {
function onGmpReceived(bytes32 id, uint128 networkId, bytes32 source, uint64 nonce, bytes calldata data) external payable returns (bytes32);
}
contract OATSToken is ERC20, ERC20Burnable, Ownable, IGmpReceiver {
IGateway public immutable gateway;
mapping(uint16 => address) public supportedTokens;
constructor(string memory name, string memory symbol, address _gateway) ERC20(name, symbol) Ownable(msg.sender) {
gateway = IGateway(_gateway);
_mint(msg.sender, 1000000 * 10**decimals());
}
function setSupportedToken(uint16 networkId, address tokenAddress) public onlyOwner {
supportedTokens[networkId] = tokenAddress;
}
function estimateSendCost(uint16 destinationNetworkId) public view returns (uint256) {
bytes memory message = abi.encode(msg.sender, msg.sender, 0);
return gateway.estimateMessageCost(destinationNetworkId, uint16(message.length), 100000);
}
function send(uint16 destinationNetworkId, address recipient, uint256 amount) public payable {
require(supportedTokens[destinationNetworkId] != address(0), "Unsupported network");
_burn(msg.sender, amount);
bytes memory message = abi.encode(msg.sender, recipient, amount);
gateway.submitMessage{value: msg.value}(supportedTokens[destinationNetworkId], destinationNetworkId, 100000, message);
}
function onGmpReceived(bytes32, uint128, bytes32, uint64, bytes calldata data) external payable override returns (bytes32) {
// In a real scenario, you'd verify the gateway and source.
(, address to, uint256 amount) = abi.decode(data, (address, address, uint256));
_mint(to, amount);
return bytes32(0);
}
}
Here are the main functions you'll interact with as a user:
balanceOf(address account)Returns the number of OATS tokens owned by a specific account. This is a standard ERC20 function.
- Parameters:
account: The address you want to query the balance of.
- Returns: The token balance of the account.
transfer(address recipient, uint256 amount)Transfers a specified amount of OATS tokens to another address on the same network. This is a standard ERC20 function.
- Parameters:
recipient: The address of the person receiving the tokens.amount: The number of tokens to send.
estimateSendCost(uint16 destinationNetworkId) Calculates the estimated cost in the native currency (e.g., ETH) to send tokens to another network.
- Parameters:
destinationNetworkId: The unique identifier of the network you want to send tokens to.
- Returns: The estimated fee for the transaction.
send(uint16 destinationNetworkId, address recipient, uint256 amount)Sends a specified amount of OATS tokens to a recipient on a different supported network. This function will burn your tokens on the current network and trigger the minting of tokens on the destination network.
- Parameters:
destinationNetworkId: The unique identifier of the network you are sending tokens to.recipient: The address of the recipient on the destination network.amount: The number of tokens you want to send.
When calling the send function, you must also send a payment in the native currency (e.g., ETH) that is at least equal to the value returned by estimateSendCost. This payment covers the transaction fees on the destination network.
Step 2: Deploy on Ethereum Sepolia
- Compile: In Remix, go to the "Solidity Compiler" tab, select a compiler version
^0.8.0, and click "Compile OATSToken.sol". - Deploy:
- Go to the "Deploy & Run Transactions" tab.
- Select "Injected Provider - MetaMask" as the environment.
- Make sure your MetaMask is connected to the "Ethereum Sepolia" network.
- In the "Deploy" section, next to the "Deploy" button, provide the constructor arguments:
name: "MyOATSToken"symbol: "MOT"_gateway: (Use the Sepolia Analog GMP Gateway address). See Gateway Addresses & Network IDs for more details.
- Click "Deploy" and confirm the transaction in MetaMask.
- Copy the Deployed Address: Once deployed, copy the address of your token contract on Sepolia.
Step 3: Deploy on Arbitrum Sepolia
- Switch Network: In MetaMask, switch to the "Arbitrum Sepolia" network.
- Deploy Again: Repeat the deployment steps from Step 2, but this time connected to Arbitrum Sepolia. Use the same constructor arguments, but for
_gatewayuse the Arbitrum address. See Gateway Addresses & Network IDs for more details. - Copy the Deployed Address: Copy the address of your token contract on Arbitrum Sepolia.
Step 4: Configure the Tokens
- On Ethereum Sepolia:
- In Remix, at the deployed contract address for your Sepolia token, call the
setSupportedTokenfunction. networkId: (The chain ID for Arbitrum Sepolia). See Gateway Addresses & Network IDs for more details.tokenAddress: The address of the token you deployed on Arbitrum Sepolia.- Click "transact" and confirm in MetaMask.
- In Remix, at the deployed contract address for your Sepolia token, call the
- On Arbitrum Sepolia:
- Switch your MetaMask to Arbitrum Sepolia.
- In Remix, at the deployed contract address for your Arbitrum token, call
setSupportedToken. networkId: (The chain ID for Ethereum Sepolia). See Gateway Addresses & Network IDs for more details.tokenAddress: The address of the token you deployed on Ethereum Sepolia.- Click "transact" and confirm.
Step 5: Transfer Tokens
- Switch to Sepolia: In MetaMask, switch back to the Ethereum Sepolia network.
- Estimate Cost: In Remix, for your Sepolia token contract, call the
estimateSendCostfunction with the Arbitrum SepolianetworkId. This will give you the required fee in wei. - Send Tokens:
- Call the
sendfunction. destinationNetworkId: The Arbitrum SepolianetworkId.recipient: The address you want to receive the tokens on Arbitrum.amount: The amount of tokens to send (in wei, e.g.,1000000000000000000for 1 token).- In the "Value" field at the top of the Remix panel, enter the cost estimated in the previous step, in wei.
- Click "transact" and confirm in MetaMask.
- Call the
Your newly minted tokens will arrive at your Arbitrum Sepolia wallet address in a few minutes. You can confirm the transaction and view your updated balance on ArbiscanÂ