Query and transfer ERC-20 tokens using Nethereum's built-in typed services (.NET/C#). Use this skill whenever the user asks about ERC-20 tokens, token balances, token transfers, token approvals, allowances, token metadata (name, symbol, decimals), or any fungible token interaction with C# or .NET.
Nethereum has a built-in typed service for ERC-20 — no ABI needed, no code generation. Just get the service for any token contract address and call methods directly. This is the simplest way to interact with fungible tokens.
NuGet: Nethereum.Web3
dotnet add package Nethereum.Web3
All ERC-20 operations start by getting a typed service for the token's contract address:
var erc20 = web3.Eth.ERC20.GetContractService(contractAddress);
Every ERC-20 token exposes metadata. These are read-only calls — no gas, no signing needed:
var name = await erc20.NameQueryAsync();
var symbol = await erc20.SymbolQueryAsync();
var decimals = await erc20.DecimalsQueryAsync();
var totalSupply = await erc20.TotalSupplyQueryAsync();
The decimals value matters — ERC-20 tokens store balances as integers in the smallest unit. Most tokens use 18 decimals, but stablecoins like USDC use 6. Always use Web3.Convert.FromWei(value, decimals) to display human-readable amounts.
Balances are returned in the token's smallest unit. Use FromWei with the token's decimals for a readable value:
var balance = await erc20.BalanceOfQueryAsync(myAddress);
Console.WriteLine($"Balance: {Web3.Convert.FromWei(balance, decimals)} {symbol}");
Transfers require a Web3 instance connected with a funded account. The amount must be in the smallest unit — use ToWei with the token's decimals:
var receipt = await erc20.TransferRequestAndWaitForReceiptAsync(
recipientAddress,
Web3.Convert.ToWei(100, decimals));
Gas estimation, nonce, and EIP-1559 fees are handled automatically.
The approve/transferFrom pattern lets another address (like a DEX or smart contract) spend your tokens. First approve a spending limit, then the spender calls transferFrom:
// Approve the spender
var approveReceipt = await erc20.ApproveRequestAndWaitForReceiptAsync(
spenderAddress,
Web3.Convert.ToWei(1000, decimals));
// Check allowance
var allowance = await erc20.AllowanceQueryAsync(myAddress, spenderAddress);
Be careful with approvals — approving a large amount gives the spender permission to transfer up to that amount at any time.
ERC-20 transfers emit a Transfer event. Nethereum ships a built-in TransferEventDTO so you don't need to define your own:
using Nethereum.Contracts.Standards.ERC20.ContractDefinition;
var transferEvent = web3.Eth.GetEvent<TransferEventDTO>(contractAddress);
var filter = transferEvent.CreateFilterInput(
BlockParameter.CreateEarliest(),
BlockParameter.CreateLatest());
var transfers = await transferEvent.GetAllChangesAsync(filter);
foreach (var t in transfers)
{
Console.WriteLine($"{t.Event.From} -> {t.Event.To}: {Web3.Convert.FromWei(t.Event.Value, decimals)}");
}
All query methods accept an optional BlockParameter to read state at a specific block. The node must have archive state for the requested block:
var historicalBalance = await erc20.BalanceOfQueryAsync(
myAddress,
new BlockParameter(15_000_000));
| Method | Returns | Description |
|---|---|---|
NameQueryAsync() | string | Token name |
SymbolQueryAsync() | string | Token symbol |
DecimalsQueryAsync() | byte | Decimal places |
TotalSupplyQueryAsync() | BigInteger | Total supply in wei |
BalanceOfQueryAsync(address) | BigInteger | Balance in wei |
AllowanceQueryAsync(owner, spender) | BigInteger | Approved amount |
TransferRequestAndWaitForReceiptAsync(to, amount) | TransactionReceipt | Transfer tokens |
ApproveRequestAndWaitForReceiptAsync(spender, amount) | TransactionReceipt | Approve spending |
TransferFromRequestAndWaitForReceiptAsync(from, to, amount) | TransactionReceipt | Transfer on behalf |
For full documentation, see: https://docs.nethereum.com/docs/smart-contracts/erc20