Web3 App: Consuming Smart Contract with MetaMask

Your smart contract is deployed on Sepolia. The next step is a web UI that lets users interact with it through MetaMask: connecting wallets, reading contract state, and submitting transactions.

TL;DR: Build a Web3 frontend that connects to MetaMask, reads from a deployed smart contract, and submits transactions on Sepolia.
Stack: Web3.js, MetaMask, Solidity, Sepolia, React
Level: Intermediate
Reading time: ~10 min

Full repo: github.com/ferrerallan/web3-ethereum-deploy-sepolia

Setup

npx create-react-app .
npm i web3
npm i @mui/material @emotion/react @emotion/styled

Web3Service.js

import Web3 from 'web3';
import ABI from '../ABI.json';

const CONTRACT_ADDRESS = '0xYourDeployedContractAddress';
export const web3 = new Web3(window.ethereum);

export function getContract() {
    if (!window.ethereum) throw new Error('No MetaMask installed!');
    const from = localStorage.getItem('wallet');
    return new web3.eth.Contract(ABI, CONTRACT_ADDRESS, { from });
}

export async function doLogin() {
    if (!window.ethereum) throw new Error('No MetaMask installed!');
    const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
    if (!accounts.length) throw new Error('No accounts found!');
    localStorage.setItem('wallet', accounts[0].toLowerCase());
    return accounts[0];
}

export async function deposit(amount) {
    const contract = getContract();
    const weiAmount = web3.utils.toWei(amount, 'ether');
    const accounts = await web3.eth.getAccounts();
    return contract.methods.deposit().send({ from: accounts[0], value: weiAmount });
}

export async function getBalance(address) {
    const contract = getContract();
    const balance = await contract.methods.getBalance(address).call();
    return web3.utils.fromWei(balance, 'ether');
}

DepositEther component

import React, { useState } from 'react';
import { deposit } from '../services/Web3Service';
import { TextField, Button, Container, Typography } from '@mui/material';

const DepositEther = () => {
    const [amount, setAmount] = useState('');

    const handleDeposit = async () => {
        try {
            await deposit(amount);
            alert('Deposit successful');
            setAmount('');
        } catch (error) {
            console.error('Error depositing Ether:', error);
        }
    };

    return (
        <Container maxWidth="sm">
            <Typography variant="h4">Deposit Ether</Typography>
            <TextField fullWidth type="text" value={amount} onChange={(e) => setAmount(e.target.value)} placeholder="Amount in Ether" />
            <Button variant="contained" onClick={handleDeposit}>Deposit</Button>
        </Container>
    );
};

export default DepositEther;

What you’ve built

A Web3 frontend that detects MetaMask, connects to the user’s wallet, reads contract state, and calls contract functions that prompt MetaMask for transaction confirmation.

Next steps

  • Always handle the case where MetaMask is not installed. Check typeof window.ethereum !== “undefined” before doing anything else.
  • Keep the contract ABI in a separate file so it’s easy to update when the contract changes.
  • Test all user-facing error states: rejected transaction, insufficient gas, wrong network. These are common and your UI should handle them gracefully.

Questions or feedback? Find me on LinkedIn or GitHub.

Leave a Comment