ERC20 模块(x/erc20)实现了单一代币表示 v2(STRv2),确保 Cosmos coin 与 EVM ERC-20 代币之间的无缝互操作。它会自动为 IBC 代币创建 ERC-20 表示,并维护双向转换。

模块概览

用途:在 Cosmos 与 EVM 环境中提供统一的代币表示 核心功能:
  • 为 Cosmos coin 和 IBC 代币自动创建 ERC-20 包装
  • 代币双向转换(Cosmos 与 EVM)
  • 在确定性地址上提供原生代币预编译
  • 为 IBC 代币动态生成预编译
  • 跟踪 ERC-20 allowance,以实现跨环境兼容
  • 单一代币表示可防止流动性碎片化
源代码:x/erc20 参数默认值:x/erc20/types/params.go

单一代币表示 v2(STRv2)

解决的问题

如果没有 STRv2,代币会存在于彼此分离的“世界”中:
  • Cosmos 侧:Bank 模块跟踪 ibc/ABC... 面额
  • EVM 侧:独立的 ERC-20 合约,具有不同地址
  • 结果:代币碎片化、流动性割裂、账务处理复杂

STRv2 解决方案

STRv2 确保代币只有一种表示:
  1. IBC 代币到达 → 自动在确定性地址获得 ERC-20 包装
  2. 用户需要 EVM 访问 → 将 bank 余额转换为 ERC-20(总供应量保持不变)
  3. 用户需要 Cosmos 访问 → 将 ERC-20 转回 bank 余额
  4. 结果:同一种代币,可在两个环境中访问,流动性统一
实现:token_pair.go:18-29

配置方式

ERC20 模块在链启动前通过 genesis.json 进行配置。

方法 1:直接编辑 JSON

直接编辑 ~/.evmd/config/genesis.json:
{
  "app_state": {
    "erc20": {
      "params": {
        "enable_erc20": true,
        "permissionless_registration": true
      },
      "token_pairs": [
        {
          "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
          "denom": "atest",
          "enabled": true,
          "contract_owner": 1
        }
      ],
      "native_precompiles": ["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"],
      "dynamic_precompiles": [],
      "allowances": []
    }
  }
}

方法 2:使用 jq 命令行工具

来自 local_node.sh:247-248:
GENESIS="$HOME/.evmd/config/genesis.json"
TMP="$HOME/.evmd/config/tmp_genesis.json"


# Set native precompile for native token
jq '.app_state.erc20.native_precompiles=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' \
  "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS"


# Create token pair for native token
jq '.app_state.erc20.token_pairs=[{
  contract_owner:1,
  erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  denom:"atest",
  enabled:true
}]' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS"

参数

enable_erc20

作用:启用或禁用整个 ERC20 模块功能的总开关。 类型:bool 有效值:
  • true - 启用 ERC-20 转换和 token pair(推荐)
  • false - 禁用所有 ERC-20 功能
默认值:true(params.go:26) 配置:
{
  "erc20": {
    "params": {
      "enable_erc20": true
    }
  }
}
影响:
  • 当为 true 时:用户可以在 Cosmos 表示与 ERC-20 表示之间转换
  • 当为 false 时:所有转换都会被禁用,代币保持原生格式
  • genesis 之后不能直接修改,必须通过治理提案
使用场景:
  • true - 兼容 EVM 的链的标准配置
  • false - 不提供 EVM 代币桥接的纯 Cosmos 链

permissionless_registration

作用:控制谁可以注册新的 token pair(为 Cosmos 代币创建 ERC-20 包装)。 类型:bool 有效值:
  • true - 任何人都可以注册 token pair(推荐用于公链)
  • false - 只有治理可以注册 token pair(许可式)
默认值:true(params.go:27) 配置:
{
  "erc20": {
    "params": {
      "permissionless_registration": true
    }
  }
}
影响: 当为 true(无许可)时:
  • 任意用户都可以调用 RegisterCoin 或 RegisterERC20 消息
  • IBC 代币会自动获得 ERC-20 包装
  • 对新代币提供最快的接入体验
  • 符合标准的类以太坊行为
当为 false(许可式)时:
  • 只有治理提案可以创建 token pair
  • 可以更严格地控制哪些代币拥有 ERC-20 表示
  • 新代币接入速度更慢
  • 适合精心筛选的代币列表
建议:公链使用 true,以匹配以太坊式用户体验

Genesis 状态

token_pairs

作用:定义 Cosmos 面额与 ERC-20 合约地址之间的映射关系。 类型:TokenPair 对象数组 结构:(token_pair.go:32-39)
message TokenPair {
  string erc20_address = 1;   // ERC-20 contract address (hex)
  string denom = 2;            // Cosmos denomination
  bool enabled = 3;            // Whether conversions are active
  Owner contract_owner = 4;    // Who owns the ERC-20 contract
}
合约所有者类型:
  • OWNER_MODULE = 1 - 由 erc20 模块持有(原生 Cosmos 代币)
  • OWNER_EXTERNAL = 2 - 由外部 EOA 持有(原生 ERC-20 代币)
配置:

# Using jq (from local_node.sh:248)
jq '.app_state.erc20.token_pairs=[{
  contract_owner:1,
  erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  denom:"atest",
  enabled:true
}]' genesis.json > tmp.json && mv tmp.json genesis.json
{
  "erc20": {
    "token_pairs": [
      {
        "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
        "denom": "atest",
        "enabled": true,
        "contract_owner": 1
      }
    ]
  }
}
特殊地址:0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE - 原生代币的标准地址(遵循以太坊惯例) 校验:(genesis.go:30-46)
  • 不允许重复的 ERC-20 地址
  • 不允许重复的面额
  • Cosmos denom 格式必须合法
  • Ethereum 地址格式必须合法

native_precompiles

作用:列出应作为有状态预编译暴露的 ERC-20 合约地址,用于原生 Cosmos 代币。 类型:十六进制地址数组(字符串) 目的:让 EVM 智能合约能够在确定性地址上与原生 Cosmos 代币交互 配置:

# Using jq (from local_node.sh:247)
jq '.app_state.erc20.native_precompiles=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' \
  genesis.json > tmp.json && mv tmp.json genesis.json
{
  "erc20": {
    "native_precompiles": [
      "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
    ]
  }
}
要求:
  • 每个预编译地址都必须有一个对应且已启用的 token pair(genesis.go:54-56)
  • 预编译提供由 bank 模块支持的 ERC-20 接口
使用场景:让 Solidity 合约可以对原生代币调用 IERC20(0xEeee...).transfer()

dynamic_precompiles

作用:与 native_precompiles 类似,但用于在 genesis 之后动态到达的 IBC 代币。 类型:十六进制地址数组(字符串) 默认值:[](genesis 时为空,在 IBC 转账过程中自动填充) 配置:
{
  "erc20": {
    "dynamic_precompiles": []
  }
}
工作机制:
  1. IBC 代币通过 ibc/transfer 模块到达
  2. ERC20 模块自动使用确定性地址创建 token pair
  3. 地址由 IBC denom 哈希派生(token_pair.go:18-29)
  4. 动态预编译地址被加入列表
  5. EVM 合约现在即可与该 IBC 代币交互
地址派生:使用 utils.GetIBCDenomAddress(denom) 根据 IBC denom 计算确定性地址 示例:
  • IBC denom:ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2
  • 派生得到的 ERC-20 地址:0x...(由 denom 哈希确定性生成)

allowances

作用:存储在 Cosmos 与 EVM 转换过程中需要保留的 ERC-20 allowance(approve/transferFrom)。 类型:Allowance 对象数组 结构:
message Allowance {
  string erc20_address = 1;  // ERC-20 contract
  string owner = 2;          // Token owner address
  string spender = 3;        // Approved spender address
  string amount = 4;         // Approved amount
}
默认值:[](genesis 时为空) 配置:
{
  "erc20": {
    "allowances": [
      {
        "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
        "owner": "0x1234...",
        "spender": "0x5678...",
        "amount": "1000000000000000000"
      }
    ]
  }
}
为什么需要:当用户在 Cosmos/EVM 之间转换代币时,allowance 必须被保留,DeFi 协议才能正确工作。 校验:(genesis.go:59-74)
  • 不允许重复的 allowance
  • allowance 必须引用已存在的 token pair
  • 地址和金额必须合法

完整配置示例

基于 local_node.sh:247-248:
#!/bin/bash

GENESIS="$HOME/.evmd/config/genesis.json"
TMP="$HOME/.evmd/config/tmp_genesis.json"


# Configure ERC20 module for native token (atest)
jq '.app_state.erc20.native_precompiles=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' \
  "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS"

jq '.app_state.erc20.token_pairs=[{
  contract_owner:1,
  erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  denom:"atest",
  enabled:true
}]' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS"


# Validate genesis
evmd genesis validate-genesis --home "$HOME/.evmd"
或者直接写入 genesis.json:
{
  "app_state": {
    "erc20": {
      "params": {
        "enable_erc20": true,
        "permissionless_registration": true
      },
      "token_pairs": [
        {
          "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
          "denom": "atest",
          "enabled": true,
          "contract_owner": 1
        }
      ],
      "native_precompiles": [
        "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
      ],
      "dynamic_precompiles": [],
      "allowances": []
    }
  }
}

运行时操作

查询 Token Pair


# List all token pairs
evmd query erc20 token-pairs


# Query specific token pair by denom
evmd query erc20 token-pair atest


# Query specific token pair by ERC-20 address
evmd query erc20 token-pair-by-address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE

转换代币


# Convert Cosmos coin to ERC-20
evmd tx erc20 convert-coin 1000000000000000000atest 0xRecipientAddress \
  --from mykey --chain-id mychain-1


# Convert ERC-20 back to Cosmos coin
evmd tx erc20 convert-erc20 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE \
  1000000000000000000 cosmos1recipient... --from mykey --chain-id mychain-1

注册新的 Token Pair


# Register Cosmos coin (if permissionless_registration=true)
evmd tx erc20 register-coin ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 \
  --from mykey --chain-id mychain-1

注册 ERC-20 代币(如果 permissionless_registration=true)

evmd tx erc20 register-erc20 0xTokenAddress —from mykey —chain-id mychain-1

---


## EVM 集成


### 在 Solidity 中使用原生代币

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract MyDeFiProtocol {
    // Native token precompile address
    IERC20 constant NATIVE_TOKEN = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);

    function depositNative(uint256 amount) external {
        // Transfer native Cosmos token (atest) using ERC-20 interface
        NATIVE_TOKEN.transferFrom(msg.sender, address(this), amount);

        // ... DeFi logic ...
    }

    function withdrawNative(uint256 amount) external {
        // Transfer back to user
        NATIVE_TOKEN.transfer(msg.sender, amount);
    }
}

在 Solidity 中使用 IBC 代币

// IBC token addresses are deterministic from denom
// Query the address: evmd query erc20 token-pair ibc/ABC...

IERC20 ibcToken = IERC20(0xDeterministicAddressFromIBCDenom);
ibcToken.transfer(recipient, amount);

常见问题与解决方案

问题:找不到代币对

现象:error: token pair not found for denom X 原因:该 Cosmos 代币不存在 ERC-20 包装器 解决方案:

# If permissionless_registration=true:
evmd tx erc20 register-coin <denom> --from mykey


# If permissionless_registration=false:

# Submit governance proposal to register token pair

问题:转换被禁用

现象:error: token pair is not enabled 原因:代币对已存在,但 enabled=false 解决方案:提交治理提案以启用该代币对

问题:无法访问预编译合约

现象:对预编译地址的 EVM 调用失败或返回空代码 原因:该地址未包含在 native_precompiles 或 dynamic_precompiles 中 解决方案:
  • 对于原生代币:在 genesis 中添加到 native_precompiles
  • 对于 IBC 代币:确保代币对已存在且已启用

问题:IBC 代币没有 ERC-20 地址

现象:IBC 代币已到账,但未创建 ERC-20 包装器 原因:enable_erc20=false 或模块未被正确初始化 解决方案:
  • 检查 enable_erc20 参数是否为 true
  • 检查模块是否已在 app.go 以及 BeginBlockers/EndBlockers 中注册

相关文档


源代码参考


The ERC20 module (x/erc20) implements Single Token Representation v2 (STRv2), ensuring seamless interoperability between Cosmos coins and EVM ERC-20 tokens. It automatically creates ERC-20 representations for IBC tokens and maintains bidirectional conversion.

Module Overview

Purpose: Provide unified token representation across Cosmos and EVM environments Key Functionality:
  • Automatic ERC-20 wrapper creation for Cosmos coins and IBC tokens
  • Bidirectional token conversion (Cosmos EVM)
  • Native token precompiles at deterministic addresses
  • Dynamic precompile generation for IBC tokens
  • ERC-20 allowance tracking for cross-environment compatibility
  • Single token representation prevents fragmentation
Source Code: x/erc20 Parameter Defaults: x/erc20/types/params.go

Single Token Representation v2 (STRv2)

Problem Solved

Without STRv2, tokens exist in separate “worlds”:
  • Cosmos side: Bank module tracks ibc/ABC... denominations
  • EVM side: Separate ERC-20 contracts with different addresses
  • Result: Token fragmentation, broken liquidity, complex accounting

STRv2 Solution

STRv2 ensures a single token representation:
  1. IBC token arrives → Automatically gets ERC-20 wrapper at deterministic address
  2. User wants EVM access → Convert bank balance to ERC-20 (same total supply)
  3. User wants Cosmos access → Convert ERC-20 back to bank balance
  4. Result: Same token, accessible from both environments, unified liquidity
Implementation: token_pair.go:18-29

Configuration Methods

The ERC20 module is configured through genesis.json before chain launch.

Method 1: Direct JSON Editing

Edit ~/.evmd/config/genesis.json directly:
{
  "app_state": {
    "erc20": {
      "params": {
        "enable_erc20": true,
        "permissionless_registration": true
      },
      "token_pairs": [
        {
          "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
          "denom": "atest",
          "enabled": true,
          "contract_owner": 1
        }
      ],
      "native_precompiles": ["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"],
      "dynamic_precompiles": [],
      "allowances": []
    }
  }
}

Method 2: Using jq Command-Line Tool

From local_node.sh:247-248:
GENESIS="$HOME/.evmd/config/genesis.json"
TMP="$HOME/.evmd/config/tmp_genesis.json"

# Set native precompile for native token
jq '.app_state.erc20.native_precompiles=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' \
  "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS"

# Create token pair for native token
jq '.app_state.erc20.token_pairs=[{
  contract_owner:1,
  erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  denom:"atest",
  enabled:true
}]' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS"

Parameters

enable_erc20

What It Does: Master switch to enable/disable the entire ERC20 module functionality. Type: bool Valid Values:
  • true - Enable ERC-20 conversions and token pairs (recommended)
  • false - Disable all ERC-20 functionality
Default: true (params.go:26) Configuration:
{
  "erc20": {
    "params": {
      "enable_erc20": true
    }
  }
}
Impact:
  • When true: Users can convert between Cosmos and ERC-20 representations
  • When false: All conversions disabled, tokens stay in their native format
  • Cannot be changed after genesis without governance proposal
Use Cases:
  • true - Standard for EVM-compatible chains
  • false - Pure Cosmos chain without EVM token bridging

permissionless_registration

What It Does: Controls who can register new token pairs (create ERC-20 wrappers for Cosmos tokens). Type: bool Valid Values:
  • true - Anyone can register token pairs (recommended for public chains)
  • false - Only governance can register token pairs (permissioned)
Default: true (params.go:27) Configuration:
{
  "erc20": {
    "params": {
      "permissionless_registration": true
    }
  }
}
Impact: When true (permissionless):
  • Any user can call RegisterCoin or RegisterERC20 messages
  • IBC tokens automatically get ERC-20 wrappers
  • Fastest UX for new tokens
  • Standard Ethereum-like behavior
When false (permissioned):
  • Only governance proposals can create token pairs
  • More control over which tokens get ERC-20 representations
  • Slower onboarding for new tokens
  • Useful for curated token lists
Recommendation: Use true for public chains to match Ethereum UX

Genesis State

token_pairs

What It Does: Defines the mappings between Cosmos denominations and ERC-20 contract addresses. Type: Array of TokenPair objects Structure: (token_pair.go:32-39)
message TokenPair {
  string erc20_address = 1;   // ERC-20 contract address (hex)
  string denom = 2;            // Cosmos denomination
  bool enabled = 3;            // Whether conversions are active
  Owner contract_owner = 4;    // Who owns the ERC-20 contract
}
Contract Owner Types:
  • OWNER_MODULE = 1 - Owned by erc20 module (native Cosmos token)
  • OWNER_EXTERNAL = 2 - Owned by external EOA (native ERC-20 token)
Configuration:
# Using jq (from local_node.sh:248)
jq '.app_state.erc20.token_pairs=[{
  contract_owner:1,
  erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  denom:"atest",
  enabled:true
}]' genesis.json > tmp.json && mv tmp.json genesis.json
{
  "erc20": {
    "token_pairs": [
      {
        "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
        "denom": "atest",
        "enabled": true,
        "contract_owner": 1
      }
    ]
  }
}
Special Address: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE - Standard address for native token (matches Ethereum convention) Validation: (genesis.go:30-46)
  • No duplicate ERC-20 addresses
  • No duplicate denominations
  • Valid Cosmos denom format
  • Valid Ethereum address format

native_precompiles

What It Does: List of ERC-20 contract addresses that should be accessible as stateful precompiles for native Cosmos tokens. Type: Array of hex addresses (strings) Purpose: Enable EVM smart contracts to interact with native Cosmos tokens at deterministic addresses Configuration:
# Using jq (from local_node.sh:247)
jq '.app_state.erc20.native_precompiles=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' \
  genesis.json > tmp.json && mv tmp.json genesis.json
{
  "erc20": {
    "native_precompiles": [
      "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
    ]
  }
}
Requirements:
  • Each precompile address MUST have a corresponding enabled token pair (genesis.go:54-56)
  • Precompile provides ERC-20 interface backed by bank module
Use Case: Enable Solidity contracts to call IERC20(0xEeee...).transfer() for native token

dynamic_precompiles

What It Does: Similar to native_precompiles but for IBC tokens that arrive dynamically after genesis. Type: Array of hex addresses (strings) Default: [] (empty at genesis, populated automatically during IBC transfers) Configuration:
{
  "erc20": {
    "dynamic_precompiles": []
  }
}
How It Works:
  1. IBC token arrives via ibc/transfer module
  2. ERC20 module automatically creates token pair with deterministic address
  3. Address is derived from IBC denom hash (token_pair.go:18-29)
  4. Dynamic precompile address added to list
  5. EVM contracts can now interact with IBC token
Address Derivation: Uses utils.GetIBCDenomAddress(denom) to compute deterministic address from IBC denom Example:
  • IBC denom: ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2
  • Derived ERC-20 address: 0x... (deterministic from denom hash)

allowances

What It Does: Stores ERC-20 allowances (approve/transferFrom) that need to persist across Cosmos-EVM conversions. Type: Array of Allowance objects Structure:
message Allowance {
  string erc20_address = 1;  // ERC-20 contract
  string owner = 2;          // Token owner address
  string spender = 3;        // Approved spender address
  string amount = 4;         // Approved amount
}
Default: [] (empty at genesis) Configuration:
{
  "erc20": {
    "allowances": [
      {
        "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
        "owner": "0x1234...",
        "spender": "0x5678...",
        "amount": "1000000000000000000"
      }
    ]
  }
}
Why Needed: When users convert tokens between Cosmos/EVM, allowances must be preserved for DeFi protocols to work correctly. Validation: (genesis.go:59-74)
  • No duplicate allowances
  • Allowance must reference existing token pair
  • Valid addresses and amounts

Complete Configuration Example

Based on local_node.sh:247-248:
#!/bin/bash

GENESIS="$HOME/.evmd/config/genesis.json"
TMP="$HOME/.evmd/config/tmp_genesis.json"

# Configure ERC20 module for native token (atest)
jq '.app_state.erc20.native_precompiles=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' \
  "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS"

jq '.app_state.erc20.token_pairs=[{
  contract_owner:1,
  erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  denom:"atest",
  enabled:true
}]' "$GENESIS" >"$TMP" && mv "$TMP" "$GENESIS"

# Validate genesis
evmd genesis validate-genesis --home "$HOME/.evmd"
Or in genesis.json directly:
{
  "app_state": {
    "erc20": {
      "params": {
        "enable_erc20": true,
        "permissionless_registration": true
      },
      "token_pairs": [
        {
          "erc20_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
          "denom": "atest",
          "enabled": true,
          "contract_owner": 1
        }
      ],
      "native_precompiles": [
        "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
      ],
      "dynamic_precompiles": [],
      "allowances": []
    }
  }
}

Runtime Operations

Query Token Pairs

# List all token pairs
evmd query erc20 token-pairs

# Query specific token pair by denom
evmd query erc20 token-pair atest

# Query specific token pair by ERC-20 address
evmd query erc20 token-pair-by-address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE

Convert Tokens

# Convert Cosmos coin to ERC-20
evmd tx erc20 convert-coin 1000000000000000000atest 0xRecipientAddress \
  --from mykey --chain-id mychain-1

# Convert ERC-20 back to Cosmos coin
evmd tx erc20 convert-erc20 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE \
  1000000000000000000 cosmos1recipient... --from mykey --chain-id mychain-1

Register New Token Pair

# Register Cosmos coin (if permissionless_registration=true)
evmd tx erc20 register-coin ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 \
  --from mykey --chain-id mychain-1

# Register ERC-20 token (if permissionless_registration=true)
evmd tx erc20 register-erc20 0xTokenAddress --from mykey --chain-id mychain-1

EVM Integration

Using Native Token in Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract MyDeFiProtocol {
    // Native token precompile address
    IERC20 constant NATIVE_TOKEN = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);

    function depositNative(uint256 amount) external {
        // Transfer native Cosmos token (atest) using ERC-20 interface
        NATIVE_TOKEN.transferFrom(msg.sender, address(this), amount);

        // ... DeFi logic ...
    }

    function withdrawNative(uint256 amount) external {
        // Transfer back to user
        NATIVE_TOKEN.transfer(msg.sender, amount);
    }
}

Using IBC Token in Solidity

// IBC token addresses are deterministic from denom
// Query the address: evmd query erc20 token-pair ibc/ABC...

IERC20 ibcToken = IERC20(0xDeterministicAddressFromIBCDenom);
ibcToken.transfer(recipient, amount);

Common Issues and Solutions

Issue: Token Pair Not Found

Symptom: error: token pair not found for denom X Cause: No ERC-20 wrapper exists for the Cosmos token Solution:
# If permissionless_registration=true:
evmd tx erc20 register-coin <denom> --from mykey

# If permissionless_registration=false:
# Submit governance proposal to register token pair

Issue: Conversion Disabled

Symptom: error: token pair is not enabled Cause: Token pair exists but enabled=false Solution: Submit governance proposal to enable token pair

Issue: Precompile Not Accessible

Symptom: EVM calls to precompile address fail or return no code Cause: Address not in native_precompiles or dynamic_precompiles Solution:
  • For native tokens: Add to native_precompiles in genesis
  • For IBC tokens: Ensure token pair exists and is enabled

Issue: IBC Token No ERC-20 Address

Symptom: IBC token arrives but no ERC-20 wrapper created Cause: enable_erc20=false or module not properly initialized Solution:
  • Check enable_erc20 parameter is true
  • Check module is in app.go and BeginBlockers/EndBlockers


Source Code References