Ethereum Wallet Development Series: Sending Tokens with ethers.js

·

In the world of decentralized applications, one of the most essential features of an Ethereum wallet is the ability to send tokens—especially ERC20-compliant ones. This article walks you through how to implement token transfer functionality in a web-based Ethereum wallet using ethers.js, a lightweight and powerful library for interacting with the Ethereum blockchain.

Whether you're building your own crypto wallet or integrating blockchain capabilities into a dApp, understanding how to interact with smart contracts to send tokens is a critical skill. We’ll explore core concepts like contract ABI, ERC20 standards, and transaction signing, all while maintaining clean, functional code.


Understanding Token Transfers on Ethereum

When you send a token such as USDT, DAI, or any other ERC20 token, you're not directly sending it like ETH. Instead, you're invoking a function on a smart contract that manages the token's ledger. This process hinges on knowing the Application Binary Interface (ABI) of the token contract.

What Is ABI?

The ABI is a JSON-formatted description of a smart contract’s methods and events. It tells your frontend application how to communicate with the contract—what functions are available, what parameters they accept, and what they return.

For example, when calling transfer(address to, uint256 amount), your app needs the ABI to encode this call correctly so the Ethereum Virtual Machine (Ethereum VM) can interpret it.

👉 Learn how to interact securely with Ethereum smart contracts today.


The ERC20 Token Standard

Most tokens on Ethereum follow the ERC20 standard, which defines a set of required functions and events:

Here’s a simplified version of the interface:

contract ERC20 {
    function totalSupply() public view returns (uint);
    function balanceOf(address tokenOwner) public view returns (uint balance);
    function transfer(address to, uint tokens) public returns (bool success);
    event Transfer(address indexed from, address indexed to, uint tokens);
}

This standardization allows wallets like MetaMask or your custom dApp to support thousands of tokens seamlessly.


Creating a Contract Instance with ethers.js

To interact with a token contract, you need three things:

  1. Contract Address – The deployed address of the token (e.g., 0xToken...).
  2. ABI – The interface definition.
  3. Provider or Signer – To read data or send transactions.

Using ethers.js, creating a contract instance is straightforward:

const abi = [ /* ABI array */ ];
const address = "0x..."; // Token contract address
const provider = new ethers.providers.Web3Provider(window.ethereum);

// Read-only contract (for querying data)
const contract = new ethers.Contract(address, abi, provider);

This allows you to call read-only functions like balanceOf() without spending gas.


Fetching Token Balance

Displaying a user's token balance enhances UX significantly. Here's how to do it:

HTML UI Example

<label>Token Balance:</label>
<input type="text" id="wallet-token-balance" readonly />

JavaScript Integration

const tokenBalanceEl = document.getElementById('wallet-token-balance');

// Assume `activeWallet` is the connected signer
contract.balanceOf(activeWallet.address)
  .then(balance => {
    // Format based on token decimals (e.g., 18 for DAI)
    const formatted = ethers.utils.formatUnits(balance, 18);
    tokenBalanceEl.value = formatted;
  })
  .catch(error => console.error("Failed to fetch balance:", error));

This retrieves the raw balance (in wei-like units) and formats it according to the token’s decimal precision.


Sending Tokens: Building the Transaction

Sending tokens involves more than just reading data—you must sign and broadcast a transaction.

UI for Token Transfer

<div>
  <label>Send To:</label>
  <input type="text" id="wallet-token-send-target-address" />
</div>
<div>
  <label>Amount:</label>
  <input type="number" id="wallet-token-send-amount" />
</div>
<button id="wallet-token-submit-send">Send</button>

Handling the Transfer Logic

const targetInput = document.getElementById('wallet-token-send-target-address');
const amountInput = document.getElementById('wallet-token-send-amount');
const submitBtn = document.getElementById('wallet-token-submit-send');

submitBtn.addEventListener('click', async () => {
  const targetAddress = ethers.utils.getAddress(targetInput.value);
  const amount = ethers.utils.parseUnits(amountInput.value, 18); // Adjust decimals as needed

  try {
    // Connect wallet to contract for signing
    const contractWithSigner = contract.connect(activeWallet);

    // Estimate gas limit
    const gasEstimate = await contractWithSigner.estimateGas.transfer(targetAddress, amount);

    // Send transaction
    const tx = await contractWithSigner.transfer(targetAddress, amount, {
      gasLimit: gasEstimate,
      gasPrice: ethers.utils.parseUnits("2", "gwei")
    });

    console.log("Transaction sent:", tx.hash);

    // Optional: Wait for confirmation
    await tx.wait();
    alert("Token transfer successful!");

    // Refresh balance and reset form
    location.reload();
  } catch (error) {
    console.error("Transfer failed:", error);
    alert("Transaction failed: " + error.message);
  }
});
🔐 Remember: Only functions that modify blockchain state require a signer. Read operations only need a provider.

Frequently Asked Questions

Q: What is the difference between a provider and a signer in ethers.js?

A: A provider reads data from the blockchain (e.g., balances), while a signer (like a wallet) can sign and send transactions that change state.

Q: Why do I need to use contract.connect(signer) before sending tokens?

A: Because sending tokens modifies the blockchain. The signer provides the private key to sign the transaction securely.

Q: How do I find a token’s ABI?

A: Most ERC20 tokens have publicly available ABIs. You can retrieve them from block explorers like Etherscan under the "Contract" tab.

Q: Can I send tokens without specifying gas price?

A: Yes. If omitted, ethers.js will automatically use network defaults via provider.getGasPrice().

Q: What happens if I enter an invalid address?

A: The transaction will likely fail or send tokens to an unrecoverable address. Always validate addresses using ethers.utils.getAddress().

Q: Are non-ERC20 tokens supported this way?

A: Not directly. Tokens like ERC721 (NFTs) have different interfaces and require their own ABI and logic.


Core Keywords for SEO

These keywords naturally appear throughout this guide, ensuring visibility for developers searching for practical implementation steps.

👉 Start building secure blockchain interactions now with trusted tools.


Final Notes

You’ve now implemented a fully functional token transfer system within a web-based Ethereum wallet using ethers.js. From fetching balances to securely sending tokens via signed transactions, each step builds toward creating a robust and user-friendly decentralized experience.

While this series has covered foundational aspects—from account creation to sending tokens—there’s always room to expand: adding support for multiple tokens, handling decimals dynamically, integrating with hardware wallets, or even supporting cross-chain transfers.

As blockchain technology evolves, mastering these fundamentals ensures you stay ahead in the rapidly growing Web3 ecosystem.

👉 Unlock advanced blockchain tools and APIs to power your next project.