Building an Ethereum Web Wallet with ethers.js - Sending Tokens

·

Developing a decentralized Ethereum web wallet is a powerful way to engage with blockchain technology directly from the browser. This article, the fourth in a comprehensive series, focuses on one of the most essential features: sending ERC20 tokens. Using ethers.js, we'll explore how to interact with smart contracts to transfer digital assets securely and efficiently.

Whether you're building your own crypto wallet or expanding your blockchain development skills, understanding token transactions is crucial. We’ll walk through the core concepts—contract ABI, balance checking, and token transfers—with practical code examples that integrate seamlessly into your web application.


Understanding Contract ABI for Token Transactions

To send tokens on Ethereum, you're not just transferring value—you're invoking functions on a smart contract. Specifically, most tokens follow the ERC20 standard, which defines a set of rules for fungible tokens on the Ethereum network.

The key to interacting with any ERC20 contract is its ABI (Application Binary Interface). The ABI serves as a blueprint that describes all available functions and events in the contract, enabling your frontend to communicate with it correctly.

Here’s a simplified version of the ERC20 interface:

[
  {
    "constant": true,
    "inputs": [],
    "name": "totalSupply",
    "outputs": [{ "name": "", "type": "uint256" }],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [{ "name": "tokenOwner", "type": "address" }],
    "name": "balanceOf",
    "outputs": [{ "name": "balance", "type": "uint256" }],
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      { "name": "to", "type": "address" },
      { "name": "tokens", "type": "uint256" }
    ],
    "name": "transfer",
    "outputs": [{ "name": "success", "type": "bool" }],
    "type": "function"
  }
]

Each entry includes:

You can obtain the ABI by compiling your Solidity code in tools like Remix IDE, then copying the generated JSON output.

👉 Discover how to connect your wallet to real blockchain networks effortlessly.


Creating a Contract Instance with ethers.js

Once you have the ABI and the deployed contract address, creating a contract instance using ethers.js is straightforward.

const abi = [ /* ERC20 ABI JSON */ ];
const address = "0xYourTokenContractAddress";
const provider = new ethers.providers.Web3Provider(window.ethereum);

// Create contract instance
const contract = new ethers.Contract(address, abi, provider);

This contract object now allows you to call both read-only methods (like balanceOf) and state-changing methods (like transfer). However, to send transactions, you’ll need to connect a signer—an account capable of signing and authorizing changes on-chain.


Checking Token Balance

A functional wallet must display token balances. Using the balanceOf method from the ERC20 standard, we can fetch a user's token balance easily.

Assume this HTML structure:

<tr>
  <th>Token Balance:</th>
  <td>
    <input type="text" readonly class="readonly" id="wallet-token-balance" value="0.0" />
  </td>
</tr>

JavaScript logic using ethers.js:

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

// Fetch balance for connected wallet
contract.balanceOf(activeWallet.address)
  .then(balance => {
    // Format based on token decimals
    const formattedBalance = ethers.utils.formatUnits(balance, 18);
    tokenBalanceInput.value = formattedBalance;
  })
  .catch(error => {
    console.error("Failed to fetch balance:", error);
  });
Note: Always format token amounts according to their decimals property (usually 18). Use ethers.utils.formatUnits() for human-readable values.

Sending Tokens via Smart Contract

Transferring tokens involves sending a transaction to the contract’s transfer function. Unlike balance checks, this requires user authorization and gas fees.

UI for Token Transfer

<h3>Send Tokens</h3>
<table>
  <tr>
    <th>To Address:</th>
    <td><input type="text" placeholder="Recipient address" id="wallet-token-send-target-address" /></td>
  </tr>
  <tr>
    <th>Amount:</th>
    <td><input type="text" placeholder="Amount to send" id="wallet-token-send-amount" /></td>
  </tr>
  <tr>
    <td></td>
    <td><div id="wallet-token-submit-send" class="submit disable">Send</div></td>
  </tr>
</table>

Handling the Transfer Logic

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

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

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

  // Connect signer to contract
  const contractWithSigner = contract.connect(activeWallet);

  try {
    const tx = await contractWithSigner.transfer(targetAddress, amount, {
      gasLimit: gasEstimate,
      gasPrice: await provider.getGasPrice()
    });

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

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

    // Refresh balance and clear inputs
    location.reload();
  } catch (error) {
    console.error("Transaction failed:", error);
    alert("Transfer failed: " + error.message);
  }
});

🔑 Key Points:

👉 Learn how to securely manage private keys and sign transactions in your dApp.


Frequently Asked Questions (FAQ)

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, block info), while a signer (like a wallet) can sign and send transactions. To modify blockchain state—such as sending tokens—you must use a signer.

Q: Why do I need ABI to interact with a token contract?

A: The ABI tells your app what functions are available and how to encode/decode data when calling them. Without it, ethers.js wouldn't know how to format the transfer call correctly.

Q: How are gas fees calculated for token transfers?

A: Gas depends on computation complexity. Token transfers typically cost between 40,000–100,000 gas. Use contract.estimateGas.transfer() to get an accurate estimate before sending.

Q: Can I send tokens without holding ETH for gas?

A: No. Even when sending tokens, you need ETH in your wallet to pay for gas fees. This is a core limitation of Ethereum—users must hold ETH for transaction costs.

Q: Is the ERC20 transfer function synchronous?

A: No. Transactions are asynchronous. After calling transfer, you receive a transaction hash immediately, but must wait for mining and confirmation using .wait().

Q: How do I handle different token decimals?

A: Use ethers.utils.formatUnits(value, decimals) to convert raw amounts into user-friendly numbers. For example, 18 decimals means dividing by 10¹⁸.


Final Thoughts

Building a fully functional Ethereum web wallet with token sending capabilities brings together several critical blockchain development concepts: smart contract interaction, ABI usage, gas estimation, and secure signing.

With ethers.js, these processes become accessible and reliable, allowing developers to create intuitive, secure interfaces that empower users to manage their digital assets confidently.

As decentralized applications continue to grow, mastering these fundamentals ensures you’re equipped to build the next generation of Web3 tools.

👉 Start building real-world blockchain applications today with advanced tools and resources.