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.