概述

ERC20 预编译提供了一个符合标准 ERC20 规范的接口,使 EVM 工具和库能够将存储在 bank 模块中的原生 Cosmos SDK 代币当作标准 ERC20 代币进行交互。每个已注册的代币对都会获得自己唯一的预编译合约地址,并直接与 bank 模块交互,不会发生代币转换或包装。所有余额、转账和操作都直接作用于原生 bank 模块的代币余额。 地址:动态(在代币对注册时分配) 相关模块:x/erc20

Gas 成本

Gas 成本为近似值,可能会因代币复杂度和链配置而有所不同。
方法Gas 成本
name()~3,000 gas
symbol()~3,000 gas
decimals()~2,000 gas
totalSupply()~2,500 gas
balanceOf(address)~2,900 gas
allowance(address,address)~3,000 gas
transfer(address,uint256)~35,000 gas
transferFrom(address,address,uint256)~40,000 gas
approve(address,uint256)~30,000 gas
increaseAllowance(address,uint256)~30,500 gas
decreaseAllowance(address,uint256)~30,500 gas

代币对注册

ERC20 预编译通过代币对注册系统工作:
  1. 原生 Cosmos 代币:每个 Cosmos SDK 面额(例如 test、atest)都作为原生代币存在于 bank 模块中
  2. ERC20 接口:对应的 ERC20 预编译合约会在唯一地址上提供接口
  3. 直接访问 Bank 模块:ERC20 接口直接操作 bank 模块余额,不存在单独的 ERC20 代币状态
  4. 动态地址:每个代币对在注册时都会获得各自的预编译地址
预编译地址基于代币面额进行确定性生成。可查询 x/erc20 模块以找到特定代币对应的预编译地址。

方法

totalSupply

返回当前存在的代币总量。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function getTotalSupply() external view returns (uint256 totalSupply) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("totalSupply()")
        );
        
        require(success, "Total supply query failed");
        totalSupply = abi.decode(result, (uint256));
        return totalSupply;
    }
}

balanceOf

返回指定账户的代币余额。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function getBalance(address account) external view returns (uint256 balance) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("balanceOf(address)", account)
        );
        
        require(success, "Balance query failed");
        balance = abi.decode(result, (uint256));
        return balance;
    }
    
    // Get caller's balance
    function getMyBalance() external view returns (uint256) {
        return this.getBalance(msg.sender);
    }
}

transfer

将代币从调用者账户转移给接收方。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    event TokenTransfer(address indexed from, address indexed to, uint256 amount);
    
    function transferTokens(address to, uint256 amount) external returns (bool success) {
        require(to != address(0), "Cannot transfer to zero address");
        require(amount > 0, "Amount must be positive");
        
        (bool callSuccess, bytes memory result) = tokenContract.call(
            abi.encodeWithSignature("transfer(address,uint256)", to, amount)
        );
        
        require(callSuccess, "Transfer call failed");
        success = abi.decode(result, (bool));
        require(success, "Transfer returned false");
        
        emit TokenTransfer(msg.sender, to, amount);
        return success;
    }
}

transferFrom

使用授权额度将代币从一个账户转移到另一个账户。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    event TokenTransferFrom(address indexed from, address indexed to, uint256 amount, address indexed spender);
    
    function transferFromTokens(address from, address to, uint256 amount) external returns (bool success) {
        require(from != address(0), "Cannot transfer from zero address");
        require(to != address(0), "Cannot transfer to zero address");
        require(amount > 0, "Amount must be positive");
        
        (bool callSuccess, bytes memory result) = tokenContract.call(
            abi.encodeWithSignature("transferFrom(address,address,uint256)", from, to, amount)
        );
        
        require(callSuccess, "TransferFrom call failed");
        success = abi.decode(result, (bool));
        require(success, "TransferFrom returned false");
        
        emit TokenTransferFrom(from, to, amount, msg.sender);
        return success;
    }
}

approve

设置某个 spender 可支配调用者代币的授权额度。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    event TokenApproval(address indexed owner, address indexed spender, uint256 amount);
    
    function approveSpender(address spender, uint256 amount) external returns (bool success) {
        require(spender != address(0), "Cannot approve zero address");
        
        (bool callSuccess, bytes memory result) = tokenContract.call(
            abi.encodeWithSignature("approve(address,uint256)", spender, amount)
        );
        
        require(callSuccess, "Approve call failed");
        success = abi.decode(result, (bool));
        require(success, "Approve returned false");
        
        emit TokenApproval(msg.sender, spender, amount);
        return success;
    }
    
    // Helper function to approve maximum amount
    function approveMax(address spender) external returns (bool) {
        return this.approveSpender(spender, type(uint256).max);
    }
}

allowance

返回 spender 仍被允许代表 owner 支出的剩余代币数量。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function checkAllowance(address owner, address spender) external view returns (uint256 allowance) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("allowance(address,address)", owner, spender)
        );
        
        require(success, "Allowance query failed");
        allowance = abi.decode(result, (uint256));
        return allowance;
    }
}

name

返回代币的名称。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function getTokenName() external view returns (string memory name) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("name()")
        );
        
        require(success, "Name query failed");
        name = abi.decode(result, (string));
        return name;
    }
}

symbol

返回代币的符号。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function getTokenSymbol() external view returns (string memory symbol) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("symbol()")
        );
        
        require(success, "Symbol query failed");
        symbol = abi.decode(result, (string));
        return symbol;
    }
}

decimals

返回代币使用的小数位数。
小数位处理:ERC20 预编译合约可能需要处理 Cosmos 原生代币与 ERC20 表示之间的复杂小数位转换。某些 Cosmos 代币使用 6 位小数(例如 test),而 ERC20 通常使用 18 位。请始终核实小数位数,以确保金额计算准确无误。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function getTokenDecimals() external view returns (uint8 decimals) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("decimals()")
        );
        
        require(success, "Decimals query failed");
        decimals = abi.decode(result, (uint8));
        return decimals;
    }
}

完整 Solidity 接口与 ABI

ERC20 Solidity 接口
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
ERC20 ABI
{
  "_format": "hh-sol-artifact-1",
  "contractName": "IERC20MetadataAllowance",
  "sourceName": "solidity/precompiles/erc20/IERC20MetadataAllowance.sol",
  "abi": [
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "owner",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Approval",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "from",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Transfer",
      "type": "event"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "owner",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        }
      ],
      "name": "allowance",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "approve",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "balanceOf",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "decimals",
      "outputs": [
        {
          "internalType": "uint8",
          "name": "",
          "type": "uint8"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "subtractedValue",
          "type": "uint256"
        }
      ],
      "name": "decreaseAllowance",
      "outputs": [
        {
          "internalType": "bool",
          "name": "approved",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "addedValue",
          "type": "uint256"
        }
      ],
      "name": "increaseAllowance",
      "outputs": [
        {
          "internalType": "bool",
          "name": "approved",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "name",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "symbol",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "totalSupply",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "transfer",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "from",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "transferFrom",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]
}

Overview

The ERC20 precompile provides a standard ERC20-compliant interface that allows EVM tooling and libraries to interact with native Cosmos SDK tokens stored in the bank module as if they were standard ERC20 tokens. Each registered token pair gets its own unique precompile contract address that directly interfaces with the bank module - no token conversion or wrapping occurs. All balances, transfers, and operations work directly on the native bank module token balances. Address: Dynamic (assigned per token pair registration) Related Module: x/erc20

Gas Costs

Gas costs are approximated and may vary based on token complexity and chain settings.
MethodGas Cost
name()~3,000 gas
symbol()~3,000 gas
decimals()~2,000 gas
totalSupply()~2,500 gas
balanceOf(address)~2,900 gas
allowance(address,address)~3,000 gas
transfer(address,uint256)~35,000 gas
transferFrom(address,address,uint256)~40,000 gas
approve(address,uint256)~30,000 gas
increaseAllowance(address,uint256)~30,500 gas
decreaseAllowance(address,uint256)~30,500 gas

Token Pair Registration

The ERC20 precompile works through a token pair registration system:
  1. Native Cosmos Token: Each Cosmos SDK denomination (e.g., test, atest) exists as a native token in the bank module
  2. ERC20 Interface: A corresponding ERC20 precompile contract provides an interface at a unique address
  3. Direct Bank Module Access: The ERC20 interface operates directly on bank module balances - there is no separate ERC20 token state
  4. Dynamic Addresses: Each token pair gets its own precompile address when registered
The precompile address is deterministically generated based on the token denomination. Query the x/erc20 module to find the precompile address for a specific token.

Methods

totalSupply

Returns the total amount of tokens in existence.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function getTotalSupply() external view returns (uint256 totalSupply) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("totalSupply()")
        );
        
        require(success, "Total supply query failed");
        totalSupply = abi.decode(result, (uint256));
        return totalSupply;
    }
}

balanceOf

Returns the token balance of a specified account.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function getBalance(address account) external view returns (uint256 balance) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("balanceOf(address)", account)
        );
        
        require(success, "Balance query failed");
        balance = abi.decode(result, (uint256));
        return balance;
    }
    
    // Get caller's balance
    function getMyBalance() external view returns (uint256) {
        return this.getBalance(msg.sender);
    }
}

transfer

Moves tokens from the caller’s account to a recipient.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    event TokenTransfer(address indexed from, address indexed to, uint256 amount);
    
    function transferTokens(address to, uint256 amount) external returns (bool success) {
        require(to != address(0), "Cannot transfer to zero address");
        require(amount > 0, "Amount must be positive");
        
        (bool callSuccess, bytes memory result) = tokenContract.call(
            abi.encodeWithSignature("transfer(address,uint256)", to, amount)
        );
        
        require(callSuccess, "Transfer call failed");
        success = abi.decode(result, (bool));
        require(success, "Transfer returned false");
        
        emit TokenTransfer(msg.sender, to, amount);
        return success;
    }
}

transferFrom

Moves tokens from one account to another using an allowance.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    event TokenTransferFrom(address indexed from, address indexed to, uint256 amount, address indexed spender);
    
    function transferFromTokens(address from, address to, uint256 amount) external returns (bool success) {
        require(from != address(0), "Cannot transfer from zero address");
        require(to != address(0), "Cannot transfer to zero address");
        require(amount > 0, "Amount must be positive");
        
        (bool callSuccess, bytes memory result) = tokenContract.call(
            abi.encodeWithSignature("transferFrom(address,address,uint256)", from, to, amount)
        );
        
        require(callSuccess, "TransferFrom call failed");
        success = abi.decode(result, (bool));
        require(success, "TransferFrom returned false");
        
        emit TokenTransferFrom(from, to, amount, msg.sender);
        return success;
    }
}

approve

Sets the allowance of a spender over the caller’s tokens.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    event TokenApproval(address indexed owner, address indexed spender, uint256 amount);
    
    function approveSpender(address spender, uint256 amount) external returns (bool success) {
        require(spender != address(0), "Cannot approve zero address");
        
        (bool callSuccess, bytes memory result) = tokenContract.call(
            abi.encodeWithSignature("approve(address,uint256)", spender, amount)
        );
        
        require(callSuccess, "Approve call failed");
        success = abi.decode(result, (bool));
        require(success, "Approve returned false");
        
        emit TokenApproval(msg.sender, spender, amount);
        return success;
    }
    
    // Helper function to approve maximum amount
    function approveMax(address spender) external returns (bool) {
        return this.approveSpender(spender, type(uint256).max);
    }
}

allowance

Returns the remaining number of tokens that a spender is allowed to spend on behalf of an owner.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function checkAllowance(address owner, address spender) external view returns (uint256 allowance) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("allowance(address,address)", owner, spender)
        );
        
        require(success, "Allowance query failed");
        allowance = abi.decode(result, (uint256));
        return allowance;
    }
}

name

Returns the name of the token.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function getTokenName() external view returns (string memory name) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("name()")
        );
        
        require(success, "Name query failed");
        name = abi.decode(result, (string));
        return name;
    }
}

symbol

Returns the symbol of the token.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function getTokenSymbol() external view returns (string memory symbol) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("symbol()")
        );
        
        require(success, "Symbol query failed");
        symbol = abi.decode(result, (string));
        return symbol;
    }
}

decimals

Returns the number of decimals used for the token.
Decimal Handling: The ERC20 precompile may need to handle complex decimal conversions between Cosmos native tokens and ERC20 representation. Some Cosmos tokens use 6 decimals (e.g., test) while ERC20 typically uses 18. Always verify the decimal count for accurate amount calculations.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Example {
    address public immutable tokenContract;
    
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }
    
    function getTokenDecimals() external view returns (uint8 decimals) {
        (bool success, bytes memory result) = tokenContract.staticcall(
            abi.encodeWithSignature("decimals()")
        );
        
        require(success, "Decimals query failed");
        decimals = abi.decode(result, (uint8));
        return decimals;
    }
}

Full Solidity Interface & ABI

ERC20 Solidity Interface
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
ERC20 ABI
{
  "_format": "hh-sol-artifact-1",
  "contractName": "IERC20MetadataAllowance",
  "sourceName": "solidity/precompiles/erc20/IERC20MetadataAllowance.sol",
  "abi": [
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "owner",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Approval",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "from",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Transfer",
      "type": "event"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "owner",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        }
      ],
      "name": "allowance",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "approve",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "balanceOf",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "decimals",
      "outputs": [
        {
          "internalType": "uint8",
          "name": "",
          "type": "uint8"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "subtractedValue",
          "type": "uint256"
        }
      ],
      "name": "decreaseAllowance",
      "outputs": [
        {
          "internalType": "bool",
          "name": "approved",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "addedValue",
          "type": "uint256"
        }
      ],
      "name": "increaseAllowance",
      "outputs": [
        {
          "internalType": "bool",
          "name": "approved",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "name",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "symbol",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "totalSupply",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "transfer",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "from",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "transferFrom",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]
}