Skip to Content
DocumentationAnalog GMPBuilding With OATSBuild Your First Omni-Chain dApp

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.

Step 1: Get the OATS Token Code

  1. Open Remix IDE: https://remix.ethereum.org/ 
  2. Create a new file named OATSToken.sol.
  3. Copy and paste the following code into the file. This is a simplified version of the OATSToken contract for demonstration.

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

  1. Compile: In Remix, go to the "Solidity Compiler" tab, select a compiler version ^0.8.0, and click "Compile OATSToken.sol".
  2. 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:
    • Click "Deploy" and confirm the transaction in MetaMask.
  3. Copy the Deployed Address: Once deployed, copy the address of your token contract on Sepolia.

Step 3: Deploy on Arbitrum Sepolia

  1. Switch Network: In MetaMask, switch to the "Arbitrum Sepolia" network.
  2. Deploy Again: Repeat the deployment steps from Step 2, but this time connected to Arbitrum Sepolia. Use the same constructor arguments, but for _gateway use the Arbitrum address. See Gateway Addresses & Network IDs for more details.
  3. Copy the Deployed Address: Copy the address of your token contract on Arbitrum Sepolia.

Step 4: Configure the Tokens

  1. On Ethereum Sepolia:
    • In Remix, at the deployed contract address for your Sepolia token, call the setSupportedToken function.
    • 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.
  2. 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

  1. Switch to Sepolia: In MetaMask, switch back to the Ethereum Sepolia network.
  2. Estimate Cost: In Remix, for your Sepolia token contract, call the estimateSendCost function with the Arbitrum Sepolia networkId. This will give you the required fee in wei.
  3. Send Tokens:
    • Call the send function.
    • destinationNetworkId: The Arbitrum Sepolia networkId.
    • recipient: The address you want to receive the tokens on Arbitrum.
    • amount: The amount of tokens to send (in wei, e.g., 1000000000000000000 for 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.

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Â