Skip to content

Writing a one way bridge USDT program

  • Initialize contract on chain 1

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.13;
    import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    import { console } from "forge-std/console.sol";
    import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
    contract BridgeETH is Ownable {
    uint256 public balance;
    address public tokenAddress;
    event Deposit(address indexed depositor, uint amount);
    mapping(address => uint256) public pendingBalance;
    constructor(address _tokenAddress) Ownable(msg.sender) {
    tokenAddress = _tokenAddress;
    }
    function deposit(IERC20 _tokenAddress, uint256 _amount) public {
    require(address(_tokenAddress) == tokenAddress);
    require(_tokenAddress.allowance(msg.sender, address(this)) >= _amount);
    require(_tokenAddress.transferFrom(msg.sender, address(this), _amount));
    emit Deposit(msg.sender, _amount);
    }
    function withdraw(IERC20 _tokenAddress, uint256 _amount) public {
    require(address(_tokenAddress) == tokenAddress);
    require(pendingBalance[msg.sender] >= _amount);
    pendingBalance[msg.sender] -= _amount;
    _tokenAddress.transfer(msg.sender, _amount);
    }
    function burnedOnOppositeChain(address userAccount, uint256 _amount) public onlyOwner {
    pendingBalance[userAccount] += _amount;
    }
    }
  • Initialize contract on chain 2

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.13;
    import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    import { console } from "forge-std/console.sol";
    import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
    interface IBUSDT is IERC20 {
    function mint(address _to, uint256 _amount) external;
    function burn(address _from, uint256 _amount) external;
    }
    contract BridgeETH is Ownable {
    uint256 public balance;
    address public tokenAddress;
    event Burn(address indexed burner, uint amount);
    mapping(address => uint256) public pendingBalance;
    constructor(address _tokenAddress) Ownable(msg.sender) {
    tokenAddress = _tokenAddress;
    }
    function burn(IBUSDT _tokenAddress, uint256 _amount) public {
    require(address(_tokenAddress) == tokenAddress);
    _tokenAddress.burn(msg.sender, _amount);
    emit Burn(msg.sender, _amount);
    }
    function withdraw(IBUSDT _tokenAddress, uint256 _amount) public {
    require(pendingBalance[msg.sender] >= _amount);
    pendingBalance[msg.sender] -= _amount;
    _tokenAddress.mint(msg.sender, _amount);
    }
    function depositedOnOppositeChain(address userAccount, uint256 _amount) public onlyOwner {
    pendingBalance[userAccount] += _amount;
    }
    }
  • Grab the ABIs of both the contracts

    const ABI1 = [{
    "inputs": [
    {
    "internalType": "address",
    "name": "userAccount",
    "type": "address"
    },
    {
    "internalType": "uint256",
    "name": "_amount",
    "type": "uint256"
    }
    ],
    "name": "depositedOnOppositeChain",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
    }]
    const ABI2 = [{
    "inputs": [
    {
    "internalType": "address",
    "name": "userAccount",
    "type": "address"
    },
    {
    "internalType": "uint256",
    "name": "_amount",
    "type": "uint256"
    }
    ],
    "name": "burnedOnOppositeChain",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
    }
    ]
  • Write the Node.js contract that will listen on events from one blockchain and call a function on the other blockchain

    import { Contract, JsonRpcProvider, Wallet, id } from "ethers";
    import { abi } from "./abi";
    const provider = new JsonRpcProvider("https://eth-mainnet.g.alchemy.com/v2/nnY0qPUQLYsUvb5BKJM5bh81sI6O0PQG");
    const PRIVATE_KEY = "fba7342ef6879df2c735644c734ea69c140f423d84eb2d53fbdfd53fd5d7c586";
    const CONTRACT_ADDRESS = "0xdac17f958d2ee523a2206206994597c13d831ec7";
    function getWallet() {
    const wallet = new Wallet(PRIVATE_KEY, provider);
    return wallet;
    }
    async function pollBlock(blockNumber: number) {
    console.log("before logs")
    const logs = await provider.getLogs({
    address: CONTRACT_ADDRESS,
    fromBlock: blockNumber,
    toBlock: blockNumber,
    topics: [id("Deposit(address,uint256)")]
    });
    logs.forEach(async log => {
    const from = log.topics[1];
    const amount = log.topics[2];
    await sendTxn(from, amount);
    });
    }
    async function sendTxn(from: string, amount: string) {
    const wallet = getWallet();
    const contract = new Contract(CONTRACT_ADDRESS, abi, wallet);
    const tx = await contract.depositedOnOppositeChain(from, amount);
    tx.wait();
    }
    pollBlock(21493826)