BridgeMint

0xF41FA74d10a50090649808914B0d9F10f2959b0eVerified
Native balance0USDX
Account typeSmart contract
NetworkTuven 99359

Overview

Contract name
BridgeMint
Verification
Verified source
Balance updated at block
16777

Verified contract source

Verified
Contract name
BridgeMint
Compiler
v0.8.29+commit.ab55807c
EVM version
prague
License
none
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./BridgeBase.sol";

/// @title BridgedERC20
/// @notice ERC20 token representing a bridged asset on the Tuven chain.
///         Only the BridgeMint contract can mint and burn.
contract BridgedERC20 is ERC20 {
    address public immutable bridge;
    uint8 private immutable _decimals;

    modifier onlyBridge() {
        require(msg.sender == bridge, "BridgedERC20: caller is not the bridge");
        _;
    }

    constructor(
        string memory name_,
        string memory symbol_,
        uint8 decimals_,
        address bridge_
    ) ERC20(name_, symbol_) {
        _decimals = decimals_;
        bridge = bridge_;
    }

    function decimals() public view override returns (uint8) {
        return _decimals;
    }

    function mint(address to, uint256 amount) external onlyBridge {
        _mint(to, amount);
    }

    function burn(address from, uint256 amount) external onlyBridge {
        _burn(from, amount);
    }
}

/// @title BridgeMint
/// @notice Bridge mint contract deployed on the Tuven chain.
///         The relayer calls mint() when tokens are locked on the source chain.
///         Users call burn() to bridge tokens back to the source chain.
contract BridgeMint is BridgeBase {
    uint256 public nonce;

    /// @notice Maps source chain token address -> bridged ERC20 on Tuven.
    mapping(address => address) public bridgedTokens;

    /// @notice Maps bridged token address -> source chain token address.
    mapping(address => address) public sourceTokens;

    /// @notice Tracks which nonces have been consumed (replay protection).
    mapping(uint256 => bool) public processedNonces;

    event BridgedTokenCreated(
        address indexed sourceToken,
        address indexed bridgedToken,
        string name,
        string symbol
    );

    event Minted(
        address indexed token,
        address indexed to,
        uint256 amount,
        uint256 nonce
    );

    event Burned(
        address indexed token,
        address indexed from,
        uint256 amount,
        uint256 destChainId,
        uint256 nonce
    );

    /// @param _relayer Address of the off-chain relayer
    constructor(address _relayer) BridgeBase(_relayer) {}

    /// @notice Register a new bridged token for a source chain token.
    ///         Only the relayer can register new bridged tokens.
    /// @param sourceToken  Token address on the source chain
    /// @param name_        Name of the bridged token
    /// @param symbol_      Symbol of the bridged token
    /// @param decimals_    Decimals of the bridged token
    /// @return bridgedToken Address of the newly deployed BridgedERC20
    function registerToken(
        address sourceToken,
        string calldata name_,
        string calldata symbol_,
        uint8 decimals_
    ) external onlyRelayer returns (address bridgedToken) {
        require(sourceToken != address(0), "BridgeMint: zero source token");
        require(bridgedTokens[sourceToken] == address(0), "BridgeMint: token already registered");

        BridgedERC20 token = new BridgedERC20(name_, symbol_, decimals_, address(this));
        bridgedToken = address(token);

        bridgedTokens[sourceToken] = bridgedToken;
        sourceTokens[bridgedToken] = sourceToken;

        emit BridgedTokenCreated(sourceToken, bridgedToken, name_, symbol_);
    }

    /// @notice Mint bridged tokens to a recipient (called by relayer).
    /// @param sourceToken  Token address on the source chain
    /// @param to           Recipient on Tuven
    /// @param amount       Amount to mint
    /// @param _nonce       Unique nonce from the lock event on the source chain
    function mint(address sourceToken, address to, uint256 amount, uint256 _nonce)
        external
        onlyRelayer
        whenNotPaused
    {
        require(!processedNonces[_nonce], "BridgeMint: nonce already processed");
        require(amount > 0, "BridgeMint: zero amount");
        require(to != address(0), "BridgeMint: zero recipient");

        address bridgedToken = bridgedTokens[sourceToken];
        require(bridgedToken != address(0), "BridgeMint: token not registered");

        processedNonces[_nonce] = true;

        BridgedERC20(bridgedToken).mint(to, amount);

        emit Minted(sourceToken, to, amount, _nonce);
    }

    /// @notice Burn bridged tokens to bridge back to the source chain. The bridge
    ///         fee is taken here in bridged tokens (re-minted to `feeRecipient`, still
    ///         backed 1:1 by tokens locked on the source chain); `Burned.amount` is
    ///         the NET amount the relayer unlocks.
    /// @param bridgedToken Bridged token address on Tuven
    /// @param amount       Amount to burn (gross, fee included)
    /// @param destChainId  Destination chain ID (source chain)
    function burn(address bridgedToken, uint256 amount, uint256 destChainId)
        external
        whenNotPaused
    {
        require(amount > 0, "BridgeMint: zero amount");
        address sourceToken = sourceTokens[bridgedToken];
        require(sourceToken != address(0), "BridgeMint: not a bridged token");

        (uint256 fee, uint256 net) = _splitFee(amount);
        require(net > 0, "BridgeMint: amount too small");

        uint256 currentNonce = nonce;
        nonce++;

        BridgedERC20(bridgedToken).burn(msg.sender, amount);
        if (fee > 0) {
            BridgedERC20(bridgedToken).mint(feeRecipient, fee);
            emit FeeCollected(bridgedToken, msg.sender, fee);
        }

        emit Burned(sourceToken, msg.sender, net, destChainId, currentNonce);
    }
}
Contract ABI
JSON
[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_relayer",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "EnforcedPause",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ExpectedPause",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "sourceToken",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "bridgedToken",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      }
    ],
    "name": "BridgedTokenCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "destChainId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "name": "Burned",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      }
    ],
    "name": "FeeCollected",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldRecipient",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newRecipient",
        "type": "address"
      }
    ],
    "name": "FeeRecipientUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "oldFeeBps",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newFeeBps",
        "type": "uint256"
      }
    ],
    "name": "FeeUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "name": "Minted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Paused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldRelayer",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newRelayer",
        "type": "address"
      }
    ],
    "name": "RelayerUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Unpaused",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_FEE_BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "bridgedTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "bridgedToken",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "destChainId",
        "type": "uint256"
      }
    ],
    "name": "burn",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeBps",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sourceToken",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "_nonce",
        "type": "uint256"
      }
    ],
    "name": "mint",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "nonce",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "paused",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "processedNonces",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sourceToken",
        "type": "address"
      },
      {
        "internalType": "string",
        "name": "name_",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol_",
        "type": "string"
      },
      {
        "internalType": "uint8",
        "name": "decimals_",
        "type": "uint8"
      }
    ],
    "name": "registerToken",
    "outputs": [
      {
        "internalType": "address",
        "name": "bridgedToken",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "relayer",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_feeBps",
        "type": "uint256"
      }
    ],
    "name": "setFeeBps",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_feeRecipient",
        "type": "address"
      }
    ],
    "name": "setFeeRecipient",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_relayer",
        "type": "address"
      }
    ],
    "name": "setRelayer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "sourceTokens",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "unpause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

YOUR GATEWAY TO TUVEN

Connect to possibility.

Choose a wallet. Your assets stay in your control.

No wallet detected. On mobile, open this page in your wallet’s built-in browser.
Get MetaMask

Tuven · Chain 99359 · USDX