Scholarpeak

Contract Interaction

Learn how to interact with smart contracts using Ethers.js.

What is Contract Interaction?

Smart contracts are programs on the Ethereum blockchain. You interact with them by calling their functions. Ethers.js abstracts away the complexity of encoding function calls and decoding return values.

Creating a Contract Instance

To interact with a contract, you need the contract address and its ABI (Application Binary Interface).

typescript
import { ethers } from 'ethers';

// Contract ABI (defines function signatures)
const ABI = [
  "function name() public view returns (string)",
  "function symbol() public view returns (string)",
  "function balanceOf(address) public view returns (uint256)",
  "function transfer(address to, uint256 amount) public returns (bool)"
];

// USDC token on mainnet
const contractAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';

// Create read-only contract instance (with provider)
const provider = new ethers.JsonRpcProvider('https://eth.llamarpc.com');
const contract = new ethers.Contract(
  contractAddress,
  ABI,
  provider
);

// Get token name
const name = await contract.name();
console.log('Token name:', name); // "USD Coin"

// Get user balance
const balance = await contract.balanceOf('0x...');
console.log('Balance:', ethers.formatUnits(balance, 6)); // USDC has 6 decimals

Reading Contract Data (View Functions)

View functions don't change state and don't require transactions. They're free to call.

typescript
import { ethers } from 'ethers';

const ABI = [
  "function balanceOf(address account) public view returns (uint256)",
  "function totalSupply() public view returns (uint256)",
  "function allowance(address owner, address spender) public view returns (uint256)"
];

const contract = new ethers.Contract(
  '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  ABI,
  provider
);

// Read-only calls don't need a signer
const totalSupply = await contract.totalSupply();
console.log('Total supply:', ethers.formatUnits(totalSupply, 6));

const balance = await contract.balanceOf('0x...');
const allowance = await contract.allowance('0x...', '0x...');

Writing to Contracts (State-Changing Functions)

Functions that modify state require a signer and cost gas.

typescript
import { ethers } from 'ethers';

const ABI = [
  "function transfer(address to, uint256 amount) public returns (bool)",
  "function approve(address spender, uint256 amount) public returns (bool)"
];

// Connect with signer to call write functions
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();

const contract = new ethers.Contract(
  '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  ABI,
  signer // Pass signer to enable writes
);

// Transfer tokens
try {
  const tx = await contract.transfer(
    '0x742d35Cc6634C0532925a3b844Bc9e7595f42171',
    ethers.parseUnits('100', 6) // 100 USDC
  );

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

  // Wait for transaction
  const receipt = await tx.wait();
  console.log('Transaction mined!');

} catch (error) {
  console.error('Transfer failed:', error);
}

Listening to Contract Events

Contracts emit events when certain actions happen. You can listen for these events.

typescript
import { ethers } from 'ethers';

const ABI = [
  "event Transfer(address indexed from, address indexed to, uint256 value)",
  "function transfer(address to, uint256 amount) public returns (bool)"
];

const contract = new ethers.Contract(
  '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  ABI,
  provider
);

// Listen for Transfer events
contract.on('Transfer', (from, to, value, event) => {
  console.log(`Transfer from ${from} to ${to}`);
  console.log('Amount:', ethers.formatUnits(value, 6));
});

// Listen for events from past blocks
const pastEvents = await contract.queryFilter(
  contract.filters.Transfer(userAddress, null),
  -1000 // Last 1000 blocks
);

pastEvents.forEach(event => {
  console.log(event.args);
});

// Remove listeners
contract.removeAllListeners('Transfer');

Complete Example: Token Swap Approval

typescript
import { ethers } from 'ethers';

async function approveAndSwap() {
  // Setup
  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();

  const usdcAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
  const swapRouterAddress = '0xE592427A0AEce92De3Edee1F18E0157C05861564';

  const ABI = [
    "function approve(address spender, uint256 amount) public returns (bool)",
    "function balanceOf(address account) public view returns (uint256)"
  ];

  const usdc = new ethers.Contract(usdcAddress, ABI, signer);

  // 1. Check balance
  const balance = await usdc.balanceOf(await signer.getAddress());
  console.log('Balance:', ethers.formatUnits(balance, 6), 'USDC');

  // 2. Approve router to spend USDC
  const approveTx = await usdc.approve(
    swapRouterAddress,
    ethers.parseUnits('1000', 6) // Approve 1000 USDC
  );

  console.log('Approval sent:', approveTx.hash);

  // 3. Wait for approval
  await approveTx.wait();
  console.log('Approval confirmed!');

  // Now ready to swap...
}