Connecting to frontends

Bridge your Solidity contract to a web frontend with ethers.js and wagmi — the same stack used by Doodledapp.com.

The frontend stack

Most DeFi frontends use a combination of:

  • ethers.js or viem — Low-level Ethereum interaction library
  • wagmi — React hooks for wallet connection and contract interaction
  • RainbowKit or ConnectKit — Pre-built wallet connection UI

The ABI

The ABI (Application Binary Interface) is the contract's public interface description — a JSON array that tells ethers.js how to encode function calls and decode responses. You get it from Hardhat's compilation output in artifacts/.

javascript
// Import ABI from compilation artifacts
import MyTokenABI from './artifacts/contracts/MyToken.sol/MyToken.json';

const abi = MyTokenABI.abi;

Reading from a contract (ethers.js)

javascript
import { ethers } from 'ethers';

const CONTRACT_ADDRESS = '0x...';

// Read-only provider — no wallet needed
const provider = new ethers.JsonRpcProvider('https://eth-mainnet.g.alchemy.com/v2/KEY');
const contract = new ethers.Contract(CONTRACT_ADDRESS, abi, provider);

// Call a view function
const balance = await contract.balanceOf('0x...');
console.log(ethers.formatEther(balance));  // converts from wei to ETH string

Writing to a contract (ethers.js)

javascript
// Connect to user's wallet
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const contract = new ethers.Contract(CONTRACT_ADDRESS, abi, signer);

// Call a state-changing function
const tx = await contract.transfer('0x...', ethers.parseEther('1.0'));
await tx.wait();  // wait for the transaction to be mined
console.log('Transfer confirmed in block', tx.blockNumber);

Using wagmi in React

javascript
// Read contract state
import { useReadContract } from 'wagmi';

function TokenBalance({ address }) {
  const { data: balance } = useReadContract({
    address: CONTRACT_ADDRESS,
    abi,
    functionName: 'balanceOf',
    args: [address],
  });

  return <div>{balance ? formatEther(balance) : '...'} tokens</div>;
}

// Write to contract
import { useWriteContract } from 'wagmi';

function TransferButton() {
  const { writeContract } = useWriteContract();

  return (
    <button onClick={() =>
      writeContract({
        address: CONTRACT_ADDRESS,
        abi,
        functionName: 'transfer',
        args: [recipient, parseEther('1.0')],
      })
    }>
      Transfer 1 Token
    </button>
  );
}

Listening for events

javascript
// Listen for Transfer events in real-time
contract.on('Transfer', (from, to, value, event) => {
  console.log(from + ' → ' + to + ': ' + ethers.formatEther(value) + ' tokens');
  console.log('Transaction hash:', event.transactionHash);
});

// Clean up listener
contract.off('Transfer', listener);

Handling errors

Always handle contract reverts gracefully in your frontend. ethers.js wraps revert reasons in the error message.

javascript
try {
  const tx = await contract.withdraw();
  await tx.wait();
} catch (err) {
  if (err.code === 'ACTION_REJECTED') {
    // User rejected the transaction in their wallet
  } else if (err.reason) {
    // Contract revert: err.reason contains the revert message
    console.error('Contract error:', err.reason);
  }
}
←   Verifying contractsAuditing your contracts   →