Frontend Integration with Ethers.js

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Frontend Integration with Ethers.js
Simple
~2-3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1051
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    827
  • image_logo-aider_0.jpg
    AIDER company logo development
    762
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    850

Ethers.js Frontend Integration

Ethers.js v6 broke backward compatibility with v5 in several critical places: BigNumber replaced by native bigint, providers.Web3Provider renamed to BrowserProvider, signer API changed. If connecting the library to an existing project — check the version in package.json before starting work.

Wallet Connection

Modern approach — wagmi + viem for React projects, or rainbowkit/web3modal for ready UI. But if the task is "pure ethers.js without abstractions":

import { BrowserProvider, Contract, parseEther, formatEther } from 'ethers';

async function connectWallet() {
  if (!window.ethereum) throw new Error('No wallet detected');
  
  const provider = new BrowserProvider(window.ethereum);
  await provider.send('eth_requestAccounts', []);
  const signer = await provider.getSigner();
  const address = await signer.getAddress();
  const network = await provider.getNetwork();
  
  return { provider, signer, address, chainId: network.chainId };
}

Handling account and network change events:

window.ethereum.on('accountsChanged', (accounts: string[]) => {
  if (accounts.length === 0) {
    // User disconnected wallet
    setConnected(false);
  } else {
    setAddress(accounts[0]);
  }
});

window.ethereum.on('chainChanged', (chainId: string) => {
  // chainId comes as hex string
  window.location.reload(); // simplest way to recreate provider
});

Working with Contracts

const ERC20_ABI = [
  'function balanceOf(address owner) view returns (uint256)',
  'function transfer(address to, uint256 amount) returns (bool)',
  'function approve(address spender, uint256 amount) returns (bool)',
  'function allowance(address owner, address spender) view returns (uint256)',
  'event Transfer(address indexed from, address indexed to, uint256 value)',
];

const contract = new Contract(TOKEN_ADDRESS, ERC20_ABI, signer);

// Reading (free)
const balance = await contract.balanceOf(userAddress);
console.log(formatEther(balance)); // for 18 decimals

// Writing (transaction)
const tx = await contract.transfer(recipientAddress, parseEther('1.0'));
const receipt = await tx.wait(); // wait for confirmation
console.log('Mined in block:', receipt.blockNumber);

Gas Estimation and Override

// Estimate gas before sending
const gasEstimate = await contract.transfer.estimateGas(recipient, amount);
const gasLimit = gasEstimate * 120n / 100n; // +20% buffer

// Send with custom gas parameters
const tx = await contract.transfer(recipient, amount, {
  gasLimit,
  maxFeePerGas: parseUnits('30', 'gwei'),
  maxPriorityFeePerGas: parseUnits('2', 'gwei'),
});

Signing Messages

EIP-712 (typed data) — for signatures with human-readable context:

const domain = {
  name: 'MyDapp',
  version: '1',
  chainId: 1,
  verifyingContract: CONTRACT_ADDRESS,
};

const types = {
  Order: [
    { name: 'seller', type: 'address' },
    { name: 'tokenId', type: 'uint256' },
    { name: 'price', type: 'uint256' },
    { name: 'deadline', type: 'uint256' },
  ],
};

const value = { seller: address, tokenId: 42n, price: parseEther('1'), deadline: BigInt(Math.floor(Date.now()/1000) + 3600) };

const signature = await signer.signTypedData(domain, types, value);

Backend verification via ethers.verifyTypedData(domain, types, value, signature).

Typical Problems

bigint JSON serialization: native bigint doesn't serialize via JSON.stringify. Convert to string before sending to backend or state: balance.toString().

Multiple injected providers: if MetaMask and Rabby are installed simultaneously, window.ethereum can be either. EIP-6963 solves this via window.addEventListener('eip6963:announceProvider', ...) — all wallets announce themselves, user chooses explicitly.

Read-only provider: for reading data without wallet use JsonRpcProvider with Alchemy/Infura key instead of BrowserProvider. Don't require wallet connection for viewing data.

Timeline Guidelines

Basic integration (connect/disconnect, read contract, send tx) — 1 day. With EIP-712 signatures, multi-chain support and handling all edge cases — 2-3 days.