ERCs
ERC-20 — Fungible Token Standard
import {
balanceOf,
totalSupply,
transfer,
approve,
allowance,
transferFrom,
} from "@ethernauta/erc/20"; Core methods
| Method | Shape | Purpose |
|---|---|---|
name | Callable<string> | Token name. |
symbol | Callable<string> | Token symbol. |
decimals | Callable<number> | Decimals exponent. |
totalSupply | Callable<Uint256> | Total supply. |
balanceOf({ owner }) | Callable<Uint256> | Balance of an account. |
allowance({ owner, spender }) | Callable<Uint256> | Approval amount. |
transfer({ to, amount }) | Signable<Hash32> | Send tokens. |
approve({ spender, amount }) | Signable<Hash32> | Grant allowance. |
transferFrom({ from, to, amount }) | Signable<Hash32> | Transfer using allowance. |
Reading a balance
import {
contract,
create_reader,
encode_chain_id,
http,
} from "@ethernauta/transport";
import { balanceOf, decimals } from "@ethernauta/erc/20";
import { eth_call } from "@ethernauta/eth";
import { eip155_1 } from "@ethernauta/chain/eip155-1";
import { AddressSchema, type Bytes } from "@ethernauta/core";
import { format_unit } from "@ethernauta/utils";
import { parse } from "valibot";
const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const reader = create_reader([
{ chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);
const usdc_address = parse(AddressSchema, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
const holder = parse(AddressSchema, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
const ctx = contract({ chain_id: CHAIN_ID, to: usdc_address });
const balance_call = balanceOf([holder])(ctx);
const balance_bytes: Bytes = await eth_call([{ to: balance_call.to, input: balance_call.data }])(
reader({ chain_id: CHAIN_ID }),
);
const raw = balance_call.decode(balance_bytes);
const decimals_call = decimals()(ctx);
const decimals_bytes: Bytes = await eth_call([{ to: decimals_call.to, input: decimals_call.data }])(
reader({ chain_id: CHAIN_ID }),
);
const d = decimals_call.decode(decimals_bytes);
console.log(format_unit(BigInt(raw), Number(BigInt(d))), "USDC"); Transferring
import { transfer } from "@ethernauta/erc/20";
import type { Provider } from "@ethernauta/eip/1193";
import { create_provider, encode_chain_id } from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";
import { AddressSchema, Uint256Schema } from "@ethernauta/core";
import { parse_unit } from "@ethernauta/utils";
import { parse } from "valibot";
const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
declare const provider: Provider; // see /eips/6963 for discovery
const { signer } = create_provider(provider);
const usdc_address = parse(AddressSchema, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
const recipient = parse(AddressSchema, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
const value = parse(Uint256Schema, `0x${parse_unit("100", 6).toString(16)}`);
const signed = await transfer([recipient, value])(
signer({ chain_id: CHAIN_ID, to: usdc_address }),
);
void signed; Wallet handles nonce + gas; to is the token contract, not the recipient. The to recipient is in the call arguments.
Extensions
ERC-20 has several optional extensions, each at a sub-subpath:
| Subpath | Methods |
|---|---|
@ethernauta/erc/20/burnable | burn, burn_from |
@ethernauta/erc/20/capped | cap |
@ethernauta/erc/20/metadata | name, symbol, decimals (re-export) |
@ethernauta/erc/20/mintable | mint |
@ethernauta/erc/20/pausable | pause, unpause, paused |
@ethernauta/erc/20/wrapper | deposit_for, withdraw_to (wrapped-token pattern) |
Import only what you need; tree-shaking removes the rest.
See also
- ERC-2612 — permit (gasless approvals).
- Guide → calling contracts.