diff --git a/public/samples/interchain-token-iinterchaintoken.sol b/public/samples/interchain-token-iinterchaintoken.sol index b69d4c507..e40d4c916 100644 --- a/public/samples/interchain-token-iinterchaintoken.sol +++ b/public/samples/interchain-token-iinterchaintoken.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {ITokenManager} from "@axelar-network/interchain-token-service/contracts/interfaces/ITokenManager.sol"; import {ITokenManagerType} from "@axelar-network/interchain-token-service/contracts/interfaces/ITokenManagerType.sol"; import {IInterchainToken} from "@axelar-network/interchain-token-service/contracts/interfaces/IInterchainToken.sol"; @@ -15,45 +16,56 @@ import {AddressBytesUtils} from "@axelar-network/interchain-token-service/contra * and security of your smart contracts before using * in production. */ -contract MyInterchainToken is ERC20, Ownable { +contract MyInterchainToken is ERC20, AccessControl { using AddressBytesUtils for address; + address public creator; + bytes32 public tokenId; + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + ITokenManager public tokenManager; IInterchainTokenService public service = - IInterchainTokenService(0xF786e21509A9D50a9aFD033B5940A2b7D872C208); - - address public creator; + IInterchainTokenService(0xB5FB4BE02232B1bBA4dC8f81dc24C26980dE9e3C); constructor() ERC20("MyInterchainToken", "MITKN") { // Mint 1,000 tokens to the creator creator = msg.sender; _mint(creator, 1000 * 10**18); + _grantRole(MINTER_ROLE, msg.sender); // Register this token (could also be done 1-time smart contract invocation) - // Not a good practice beacuse it can't go to non-EVM chains + // Not a good practice because it can't go to non-EVM chains deployTokenManager(""); } - function mint(address to, uint256 amount) external onlyOwner { + function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { _mint(to, amount); } - function burn(address from, uint256 amount) external onlyOwner { + function burn(address from, uint256 amount) external onlyRole(MINTER_ROLE) { _burn(from, amount); } function deployTokenManager(bytes32 salt) internal { - bytes memory params = service.getParamsMintBurn( + require(msg.value > 0, "Gas payment is required"); + + bytes memory params = tokenManager.params( msg.sender.toBytes(), address(this) ); - bytes32 tokenId = service.deployCustomTokenManager( + + service.deployTokenManager( salt, + "", // This field remains empty if the token manager is deployed on the local chain; however, you need to specify it when deploying on a remote chain. ITokenManagerType.TokenManagerType.MINT_BURN, - params + params, + msg.value // gasValue + ); + + tokenId = service.interchainTokenId( + msg.sender, + salt ); - tokenManager = ITokenManager(service.getTokenManagerAddress(tokenId)); - transferOwnership(address(tokenManager)); } function myCustomFunction() public { @@ -62,23 +74,21 @@ contract MyInterchainToken is ERC20, Ownable { _mint(creator, 1000 * 10**18); } - /* - * Deploy this token, then register it with the Interchain Token Service - * You'll be given a TokenManager which you can set here, allowing the - * local send methods to function. - */ - function setTokenManager(address _tokenManager) public onlyOwner { - tokenManager = ITokenManager(_tokenManager); + function transferMintership(address newMinter) public { + require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter"); + + _revokeRole(MINTER_ROLE, msg.sender); + _grantRole(MINTER_ROLE, newMinter); } /** - * @notice Implementation of the interchainTransfer method - * @dev We chose to either pass `metadata` as raw data on a remote contract call, or, if no data is passed, just do a transfer. - * A different implementation could have `metadata` that tells this function which function to use or that it is used for anything else as well. + * @notice Implementation of the interchainTransfer method. + * @dev We chose to either pass `metadata` as raw data on a remote contract call, or if no data is passed, just do a transfer. + * A different implementation could use metadata to specify a function to invoke, or for other purposes as well. * @param destinationChain The destination chain identifier. * @param recipient The bytes representation of the address of the recipient. - * @param amount The amount of token to be transfered. - * @param metadata Either empty, to just facilitate an interchain transfer, or the data can be passed for an interchain contract call with transfer as per semantics defined by the token service. + * @param amount The amount of token to be transferred. + * @param metadata Optional metadata for the call for additional effects (such as calling a destination contract). */ function interchainTransfer( string calldata destinationChain, @@ -86,11 +96,10 @@ contract MyInterchainToken is ERC20, Ownable { uint256 amount, bytes calldata metadata ) external payable { - address sender = msg.sender; - // Metadata semantics are defined by the token service and thus should be passed as-is. - tokenManager.transmitInterchainTransfer{value: msg.value}( - sender, + service.transmitInterchainTransfer{ value: msg.value }( + tokenId, + address(this), destinationChain, recipient, amount, @@ -101,12 +110,12 @@ contract MyInterchainToken is ERC20, Ownable { /** * @notice Implementation of the interchainTransferFrom method * @dev We chose to either pass `metadata` as raw data on a remote contract call, or, if no data is passed, just do a transfer. - * A different implementation could have `metadata` that tells this function which function to use or that it is used for anything else as well. - * @param sender the sender of the tokens. They need to have approved `msg.sender` before this is called. - * @param destinationChain the string representation of the destination chain. - * @param recipient the bytes representation of the address of the recipient. - * @param amount the amount of token to be transfered. - * @param metadata either empty, to just facilitate a cross-chain transfer, or the data to be passed to a cross-chain contract call and transfer. + * A different implementation could use metadata to specify a function to invoke, or for other purposes as well. + * @param sender The sender of the tokens. They need to have approved `msg.sender` before this is called. + * @param destinationChain The string representation of the destination chain. + * @param recipient The bytes representation of the address of the recipient. + * @param amount The amount of token to be transferred. + * @param metadata Optional metadata for the call for additional effects (such as calling a destination contract.) */ function interchainTransferFrom( address sender, @@ -121,8 +130,9 @@ contract MyInterchainToken is ERC20, Ownable { _approve(sender, msg.sender, _allowance - amount); } - tokenManager.transmitInterchainTransfer{value: msg.value}( - sender, + service.transmitInterchainTransfer{ value: msg.value }( + tokenId, + address(this), destinationChain, recipient, amount,