Gaia EVM Devnet
- Introduction
- Connect to the Network
- Run a Full Node
- Get Tokens
- Tooling & Resources
ERC20
Standard ERC20 token functionality for native Cosmos tokens
Overview
The ERC20 precompile provides standard ERC20 token functionality for native Cosmos tokens that have been registered for EVM compatibility. Each registered token pair gets its own unique precompile contract address. This allows for seamless interaction with native Cosmos assets through familiar ERC20 interfaces.
Address: Dynamic (assigned per token pair registration)
Related Module: x/erc20
Methods
totalSupply
Returns the total amount of tokens in existence.
import { ethers } from "ethers";
// The address of the ERC20 precompile is dynamic, assigned when a token pair is registered.
const erc20PrecompileAddress = "<ERC20_PRECOMPILE_ADDRESS>";
const provider = new ethers.JsonRpcProvider("<RPC_URL>");
// A generic ERC20 ABI is sufficient for read-only calls
const erc20Abi = ["function totalSupply() view returns (uint256)"];
const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider);
async function getTotalSupply() {
try {
const totalSupply = await contract.totalSupply();
console.log("Total Supply:", totalSupply.toString());
} catch (error) {
console.error("Error fetching total supply:", error);
}
}
getTotalSupply();
balanceOf
Returns the token balance of a specified account.
import { ethers } from "ethers";
const erc20PrecompileAddress = "<ERC20_PRECOMPILE_ADDRESS>";
const accountAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder
const provider = new ethers.JsonRpcProvider("<RPC_URL>");
const erc20Abi = ["function balanceOf(address account) view returns (uint256)"];
const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider);
async function getBalance() {
try {
const balance = await contract.balanceOf(accountAddress);
console.log(`Balance of ${accountAddress}:`, balance.toString());
} catch (error) {
console.error("Error fetching balance:", error);
}
}
getBalance();
transfer
Moves tokens from the caller’s account to a recipient.
transferFrom
Moves tokens from one account to another using an allowance.
approve
Sets the allowance of a spender over the caller’s tokens.
allowance
Returns the remaining number of tokens that a spender is allowed to spend on behalf of an owner.
import { ethers } from "ethers";
const erc20PrecompileAddress = "<ERC20_PRECOMPILE_ADDRESS>";
const ownerAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder
const spenderAddress = "0x27f320b7280911c7987a421a8138997a48d4b315"; // Placeholder
const provider = new ethers.JsonRpcProvider("<RPC_URL>");
const erc20Abi = ["function allowance(address owner, address spender) view returns (uint256)"];
const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider);
async function getAllowance() {
try {
const allowance = await contract.allowance(ownerAddress, spenderAddress);
console.log("Allowance:", allowance.toString());
} catch (error) {
console.error("Error fetching allowance:", error);
}
}
getAllowance();
name
Returns the name of the token.
import { ethers } from "ethers";
const erc20PrecompileAddress = "<ERC20_PRECOMPILE_ADDRESS>";
const provider = new ethers.JsonRpcProvider("<RPC_URL>");
const erc20Abi = ["function name() view returns (string)"];
const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider);
async function getName() {
try {
const name = await contract.name();
console.log("Token Name:", name);
} catch (error) {
console.error("Error fetching name:", error);
}
}
getName();
symbol
Returns the symbol of the token.
import { ethers } from "ethers";
const erc20PrecompileAddress = "<ERC20_PRECOMPILE_ADDRESS>";
const provider = new ethers.JsonRpcProvider("<RPC_URL>");
const erc20Abi = ["function symbol() view returns (string)"];
const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider);
async function getSymbol() {
try {
const symbol = await contract.symbol();
console.log("Token Symbol:", symbol);
} catch (error) {
console.error("Error fetching symbol:", error);
}
}
getSymbol();
decimals
Returns the number of decimals used for the token.
import { ethers } from "ethers";
const erc20PrecompileAddress = "<ERC20_PRECOMPILE_ADDRESS>";
const provider = new ethers.JsonRpcProvider("<RPC_URL>");
const erc20Abi = ["function decimals() view returns (uint8)"];
const contract = new ethers.Contract(erc20PrecompileAddress, erc20Abi, provider);
async function getDecimals() {
try {
const decimals = await contract.decimals();
console.log("Token Decimals:", decimals.toString());
} catch (error) {
console.error("Error fetching decimals:", error);
}
}
getDecimals();
Full Solidity Interface & ABI
// 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);
}
{
"_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" }
]
}