BridgeMint
0xf41fa74d10a50090649808914b0d9f10f2959b0eVerifiedNative balance0USDX
Account typeSmart contract
NetworkTuven 99359
Overview
- Contract name
- BridgeMint
- Verification
- Verified source
- Creation transaction
- 0x96d0dc655fcf9f6bb65a4a4e5bcab6db4539caa7a408a47388f5aee57389609d
- Balance updated at block
- 38088
// 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
[
{
"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"
}
]