Mint & Burn Connector Scheme

Create contract with name UTSConnectorMintBurnShowcase .

You need to import:

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@entangle-labs/uts-contracts/contracts/ERC20/UTSBase";

UTSBase is our main contract that should be inherited by any Token or Connector.

In this implementation we also used Ownable, Pausable and IERC20Metadata

contract UTSConnectorMintBurnShowcase is UTSBase, Ownable, Pausable {}

We are starting with defining contract and dependencies. As access control we are choosing Ownable and we also want make this contract as Pausable, because in bridge flow it is also nice to have an option to pause all interactions to make some changes for example.

constructor(
    address underlyingToken_,
    address _router,  
    uint256[] memory _allowedChainIds,
    ChainConfig[] memory _chainConfigs
) Ownable(msg.sender) {
    __UTSBase_init(underlyingToken_, IERC20Metadata(underlyingToken_).decimals());

    _setRouter(_router);
    _setChainConfig(_allowedChainIds, _chainConfigs);
}

Then we need do define constructor. Since it is connector, we need underlyingToken_(the token that will be bridged through UTS protocol). We need also setup _router address, that can be found here.

_allowedChainIds are simply whitelist of chain id's, where you are allowing to bridge tokens.

_chainConfigs is array of settings responsible for bridge settings. We described this config here.

In constructor we need to call __UTSBase_init function to initialize UTS Base contract. Also we need to set router and chain config.

We can skip setting router and chain configs in constructor, but before starting bridging we need to do it with relevant public functions.

After this step we need to override 3 functions: _mintTo, _burnFrom and _authorizeCall. We need to do it, because this connector is mint/burn, so, internal logic should be of main bridging function should be done via mint/burn of underlying token.

Of course to implement mint/burn logic you need to grant mintable/burnable roles on your underlying token to Connector contract.

function _authorizeCall() internal override onlyOwner() {}
function _burnFrom(
    address spender,
    address /* from */, 
    bytes memory /* to */, 
    uint256 amount, 
    uint256 /* dstChainId */, 
    bytes memory /* customPayload */
) internal override whenNotPaused() returns(uint256 bridgedAmount) {
    IERC20Extended(_underlyingToken).burnFrom(spender, amount);

    return amount;
}
function _mintTo(
    address to,
    uint256 amount,
    bytes memory /* customPayload */,
    Origin memory /* origin */
) internal override whenNotPaused() returns(uint256 receivedAmount) {
    IERC20Extended(_underlyingToken).mint(to, amount);

    return amount;
}

So, here is very simple logic, in the inbounding redeem transaction, we need to mint tokens, in the outbounding bridge transaction, we need burn tokens.

Also we will implement 3 more functions to controll pause state and getter for underlying token decimals.

function unpause() external {
    _authorizeCall();
    _unpause();
}
    
function pause() external onlyOwner() {
    _pause();
}

function underlyingDecimals() external view returns(uint8) {
    return _decimals;
}

Also we need extended ERC20 interface to call mint and burn functions. In this example, we use standard mint and burnFrom token functions, but you can use any interface and logic supported by your underlyingToken contract.

interface IERC20Extended {    
    function mint(address to, uint256 amount) external;
    function burnFrom(address from, uint256 amount) external;
}

So, our contract is ready, now it can be deployed on networks and we can start bridging tokens between each chain.

You can find full code here

Last updated