> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cosmos.network/llms.txt
> Use this file to discover all available pages before exploring further.

# WERC20

> Single token representation: An ERC20 interface for any token

## Overview

The WERC20 precompile provides a standard ERC20 interface to native Cosmos tokens through Cosmos EVM's **[Single Token Representation](/evm/v0.4.x/documentation/concepts/single-token-representation)** architecture.
Unlike traditional wrapped tokens that are functionally two separate tokens with unique individual properties and behaviors, Cosmos EVM's WERC20 logic gives smart contracts direct access to native bank module balances through familiar ERC20 methods.

**Key Concept**: ATOM and WATOM are not separate tokens—they are two different interfaces to the same token stored in the bank module.
Native Cosmos tokens (including ATOM and all IBC tokens) exist in both wrapped and unwrapped states at all times, allowing developers to choose the interaction method that best fits their use case:

* Use it normally through Cosmos bank send (unwrapped state)
* Use it like you would normally use ether or 'wei' on the EVM (native value transfers)
* Use it as ERC20 WATOM with the contract address below (wrapped state)

**WATOM Contract Address**: `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`
**Precompile Type**: Dynamic (unique address per wrapped token)
**Related Module**: x/bank (via ERC20 module integration)

## Gas Costs

<Info>
  Gas costs are approximated and may vary based on token complexity and chain settings.
</Info>

| Method                                  | Gas 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         |
| `deposit()`                             | \~23,000 gas (no-op) |
| `withdraw(uint256)`                     | \~9,000 gas (no-op)  |

<Note>
  For a comprehensive understanding of how single token representation works and its benefits over traditional wrapping, see the [Single Token Representation](/evm/v0.4.x/documentation/concepts/single-token-representation) documentation.
</Note>

## Technical Implementation

### Architecture Deep Dive

The ERC20 module creates a **unified token representation** that bridges native Cosmos tokens with ERC20 interfaces:

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// Simplified conceptual flow (not actual implementation)
func (k Keeper) ERC20Transfer(from, to common.Address, amount *big.Int) error {
    // Convert EVM addresses to Cosmos addresses
    cosmosFrom := sdk.AccAddress(from.Bytes())
    cosmosTo := sdk.AccAddress(to.Bytes())

    // Use bank module directly - no separate ERC20 state
    coin := sdk.NewCoin(k.denom, sdk.NewIntFromBigInt(amount))
    return k.bankKeeper.SendCoins(ctx, cosmosFrom, cosmosTo, sdk.Coins{coin})
}

func (k Keeper) ERC20BalanceOf(account common.Address) *big.Int {
    // Query bank module directly
    cosmosAddr := sdk.AccAddress(account.Bytes())
    balance := k.bankKeeper.GetBalance(ctx, cosmosAddr, k.denom)
    return balance.Amount.BigInt()
}
```

### Why Deposit/Withdraw are No-Ops

Since ATOM and WATOM represent the same underlying token, traditional deposit/withdraw operations are meaningless:

```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// These functions exist for interface compatibility but do nothing
function deposit() external payable {
    // No-op: msg.value automatically increases your bank balance
    // Your WATOM balance is identical to your ATOM balance
}

function withdraw(uint256 amount) external {
    // No-op: your ATOM balance is already accessible
    // No conversion needed as they're the same token
}
```

<Info>
  **Understanding the Deposit/Withdraw Pattern**

  Unlike traditional WETH implementations where the contract holds wrapped tokens:

  * **Traditional WETH**: Contract receives ETH and mints WETH tokens that it holds
  * **WERC20**: Contract never holds tokens - all balances remain in the bank module
  * **Result**: The precompile contract address has no balance; tokens stay with users

  This is why `deposit()` and `withdraw()` are no-ops - there's no separate wrapped token state to manage.
</Info>

### Real-World Example

```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// User starts with 100 ATOM in bank module
const atomBalance = await bankPrecompile.balances(userAddress);
// Returns: [{denom: "test", amount: "100000000"}] // 100 ATOM (6 decimals)

const wtestBalance = await wtest.balanceOf(userAddress);
// Returns: "100000000" // Same 100 ATOM, accessed via ERC20 interface

// User transfers 50 WATOM via ERC20
await wtest.transfer(recipientAddress, "50000000");

// Check balances again
const newAtomBalance = await bankPrecompile.balances(userAddress);
// Returns: [{denom: "test", amount: "50000000"}] // 50 ATOM remaining

const newWatomBalance = await wtest.balanceOf(userAddress);
// Returns: "50000000" // Same 50 ATOM, both queries return identical values
```

## Methods

### Standard ERC20 Interface

All standard ERC20 methods are available and operate on the underlying bank balance:

### `balanceOf`

Returns the native token balance for a specific account (same as bank module balance).

<CodeGroup>
  ```javascript Ethers.js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  import { ethers } from "ethers";

  const provider = new ethers.JsonRpcProvider("<RPC_URL>");
  const wtestAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
  const werc20Abi = ["function balanceOf(address account) view returns (uint256)"];

  const wtest = new ethers.Contract(wtestAddress, werc20Abi, provider);

  async function getBalance() {
    try {
      const userAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
      const balance = await wtest.balanceOf(userAddress);
      console.log("Balance (both ATOM and WATOM):", balance.toString());
    } catch (error) {
      console.error("Error:", error);
    }
  }
  ```

  ```bash cURL theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  curl -X POST <RPC_URL> \
    -H "Content-Type: application/json" \
    --data '{
      "jsonrpc": "2.0",
      "method": "eth_call",
      "params": [
          {
              "to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
              "data": "0x70a08231000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
          },
          "latest"
      ],
      "id": 1
  }'
  ```
</CodeGroup>

### `transfer`

Transfers tokens using the bank module (identical to native Cosmos transfer).

<CodeGroup>
  ```javascript Ethers.js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  import { ethers } from "ethers";

  const provider = new ethers.JsonRpcProvider("<RPC_URL>");
  const signer = new ethers.Wallet("<PRIVATE_KEY>", provider);
  const wtestAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
  const werc20Abi = ["function transfer(address to, uint256 amount) returns (bool)"];

  const wtest = new ethers.Contract(wtestAddress, werc20Abi, signer);

  async function transferTokens() {
    try {
      const recipientAddress = "0x742d35Cc6634C0532925a3b844Bc9e7595f5b899";
      const amount = ethers.parseUnits("10.0", 6); // 10 ATOM (6 decimals)

      const tx = await wtest.transfer(recipientAddress, amount);
      const receipt = await tx.wait();

      console.log("Transfer successful:", receipt.hash);
    } catch (error) {
      console.error("Error:", error);
    }
  }
  ```

  ```bash cURL theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  # First, sign the transaction offline, then:
  curl -X POST <RPC_URL> \
    -H "Content-Type: application/json" \
    --data '{
      "jsonrpc": "2.0",
      "method": "eth_sendRawTransaction",
      "params": ["<SIGNED_TRANSACTION_DATA>"],
      "id": 1
  }'
  ```
</CodeGroup>

### `totalSupply`

Returns the total supply from the bank module.

<CodeGroup>
  ```javascript Ethers.js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  import { ethers } from "ethers";

  const provider = new ethers.JsonRpcProvider("<RPC_URL>");
  const wtestAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
  const werc20Abi = ["function totalSupply() view returns (uint256)"];

  const wtest = new ethers.Contract(wtestAddress, werc20Abi, provider);

  async function getTotalSupply() {
    try {
      const supply = await wtest.totalSupply();
      console.log("Total Supply:", supply.toString());
    } catch (error) {
      console.error("Error:", error);
    }
  }
  ```

  ```bash cURL theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  curl -X POST <RPC_URL> \
    -H "Content-Type: application/json" \
    --data '{
      "jsonrpc": "2.0",
      "method": "eth_call",
      "params": [
          {
              "to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
              "data": "0x18160ddd"
          },
          "latest"
      ],
      "id": 1
  }'
  ```
</CodeGroup>

### `approve` / `allowance` / `transferFrom`

Standard ERC20 approval mechanisms for delegated transfers.

<CodeGroup>
  ```javascript Ethers.js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  import { ethers } from "ethers";

  const provider = new ethers.JsonRpcProvider("<RPC_URL>");
  const signer = new ethers.Wallet("<PRIVATE_KEY>", provider);
  const wtestAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
  const werc20Abi = [
    "function approve(address spender, uint256 amount) returns (bool)",
    "function allowance(address owner, address spender) view returns (uint256)",
    "function transferFrom(address from, address to, uint256 amount) returns (bool)"
  ];

  const wtest = new ethers.Contract(wtestAddress, werc20Abi, signer);

  async function approveAndTransfer() {
    try {
      const spenderAddress = "0x742d35Cc6634C0532925a3b844Bc9e7595f5b899";
      const amount = ethers.parseUnits("50.0", 6); // 50 ATOM

      // Approve spending
      const approveTx = await wtest.approve(spenderAddress, amount);
      await approveTx.wait();

      // Check allowance
      const allowance = await wtest.allowance(signer.address, spenderAddress);
      console.log("Allowance:", allowance.toString());

      // Transfer from (would be called by spender)
      // const transferTx = await wtest.transferFrom(ownerAddress, recipientAddress, amount);
    } catch (error) {
      console.error("Error:", error);
    }
  }
  ```
</CodeGroup>

### `name` / `symbol` / `decimals`

Token metadata (e.g., "Wrapped ATOM", "WATOM", 6).

<CodeGroup>
  ```javascript Ethers.js theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  import { ethers } from "ethers";

  const provider = new ethers.JsonRpcProvider("<RPC_URL>");
  const wtestAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
  const werc20Abi = [
    "function name() view returns (string)",
    "function symbol() view returns (string)",
    "function decimals() view returns (uint8)"
  ];

  const wtest = new ethers.Contract(wtestAddress, werc20Abi, provider);

  async function getTokenInfo() {
    try {
      const [name, symbol, decimals] = await Promise.all([
        wtest.name(),
        wtest.symbol(),
        wtest.decimals()
      ]);
      console.log(`Token: ${name} (${symbol}) - ${decimals} decimals`);
    } catch (error) {
      console.error("Error:", error);
    }
  }
  ```

  ```bash cURL theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  # Get token name
  curl -X POST <RPC_URL> \
    -H "Content-Type: application/json" \
    --data '{
      "jsonrpc": "2.0",
      "method": "eth_call",
      "params": [
          {"to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "data": "0x06fdde03"},
          "latest"
      ],
      "id": 1
  }'

  # Get token symbol
  curl -X POST <RPC_URL> \
    -H "Content-Type: application/json" \
    --data '{
      "jsonrpc": "2.0",
      "method": "eth_call",
      "params": [
          {"to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "data": "0x95d89b41"},
          "latest"
      ],
      "id": 1
  }'

  # Get decimals
  curl -X POST <RPC_URL> \
    -H "Content-Type: application/json" \
    --data '{
      "jsonrpc": "2.0",
      "method": "eth_call",
      "params": [
          {"to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "data": "0x313ce567"},
          "latest"
      ],
      "id": 1
  }'
  ```
</CodeGroup>

### Legacy Methods (Interface Compatibility)

These methods exist for WETH compatibility but are no-ops:

### `deposit`

**No-op function** - Included for interface compatibility with WETH contracts.

<Warning>
  This function does nothing because ATOM and WATOM are the same token. Sending native tokens to this function will increase your balance, but the function itself performs no conversion logic.
</Warning>

### `withdraw`

**No-op function** - Included for interface compatibility with WETH contracts.

<Warning>
  This function does nothing because ATOM and WATOM are the same token. Your native token balance is always directly accessible without any unwrapping process.
</Warning>

## Usage Examples

### DeFi Integration Example

```solidity theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
contract LiquidityPool {
    IERC20 public immutable WATOM;

    constructor(address _wtest) {
        WATOM = IERC20(_wtest);
    }

    function addLiquidity(uint256 amount) external {
        // This transfers from the user's bank balance
        WATOM.transferFrom(msg.sender, address(this), amount);

        // Pool now has tokens in its bank balance
        // No wrapping/unwrapping needed - it's all the same token!
    }

    function removeLiquidity(uint256 amount) external {
        // This transfers back to user's bank balance
        WATOM.transfer(msg.sender, amount);

        // User can now use these tokens as native ATOM
        // or continue using WATOM interface - both access same balance
    }
}
```

### Cross-Interface Balance Verification

```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// Verify that both interfaces show the same balance
async function verifyBalanceConsistency(userAddress) {
    // Query via bank precompile (native interface)
    const bankBalance = await bankContract.balances(userAddress);
    const atomAmount = bankBalance.find(b => b.denom === "test")?.amount || "0";

    // Query via WERC20 precompile (ERC20 interface)
    const wtestAmount = await wtest.balanceOf(userAddress);

    // These will always be equal since the ERC20 balance is just
    // an abstracted bank module balance query
    console.log(`Consistent balance: ${atomAmount} (both ATOM and WATOM)`);
}
```

### Working with IBC Tokens

```javascript theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// IBC tokens work exactly the same way
const ibcTokenAddress = "0x..."; // Each IBC token gets its own WERC20 address
const ibcToken = new ethers.Contract(ibcTokenAddress, werc20Abi, signer);

// Check balance (same as bank module balance)
const balance = await ibcToken.balanceOf(userAddress);

// Transfer IBC tokens via ERC20 interface
await ibcToken.transfer(recipientAddress, amount);

// Use in DeFi protocols just like any ERC20 token
await defiProtocol.stake(ibcTokenAddress, amount);
```

## Solidity Interface & ABI

```solidity title="WERC20 Solidity Interface" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.18;

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

/// @title WERC20 Precompile Contract
/// @dev Provides ERC20 interface to native Cosmos tokens via bank module
/// @notice This is NOT a traditional wrapped token - both native and ERC20 interfaces access the same balance
interface IWERC20 is IERC20 {
    /// @dev Emitted when deposit() is called (no-op for compatibility)
    /// @param dst The address that called deposit
    /// @param wad The amount specified (though no conversion occurs)
    event Deposit(address indexed dst, uint256 wad);

    /// @dev Emitted when withdraw() is called (no-op for compatibility)
    /// @param src The address that called withdraw
    /// @param wad The amount specified (though no conversion occurs)
    event Withdrawal(address indexed src, uint256 wad);

    /// @dev No-op function for WETH compatibility - native tokens automatically update balance
    /// @notice This function exists for interface compatibility but performs no conversion
    function deposit() external payable;

    /// @dev No-op function for WETH compatibility - native tokens always accessible
    /// @param wad Amount to "withdraw" (no conversion performed)
    /// @notice This function exists for interface compatibility but performs no conversion
    function withdraw(uint256 wad) external;
}
```

```json title="WERC20 ABI" lines expandable theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
{
  "_format": "hh-sol-artifact-1",
  "contractName": "IWERC20",
  "sourceName": "solidity/precompiles/werc20/IWERC20.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": "dst", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "wad", "type": "uint256" } ], "name": "Deposit", "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" },
    { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "src", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "wad", "type": "uint256" } ], "name": "Withdrawal", "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": "deposit", "outputs": [], "stateMutability": "payable", "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" },
    { "inputs": [ { "internalType": "uint256", "name": "wad", "type": "uint256" } ], "name": "withdraw", "outputs": [], "stateMutability": "nonpayable", "type": "function" }
  ]
}
```
