Skip to content

Instantly share code, notes, and snippets.

@bbminhtri
Created July 18, 2022 08:41
Show Gist options
  • Save bbminhtri/e575eb2b8a571f1cf5cb1ea4f1af1c48 to your computer and use it in GitHub Desktop.
Save bbminhtri/e575eb2b8a571f1cf5cb1ea4f1af1c48 to your computer and use it in GitHub Desktop.
Basic Web3JS - Metamask interaction demo
import {
Button,
Card,
CardContent,
Container,
Stack,
TextField,
Typography,
} from "@mui/material";
import React, { useEffect, useState } from "react";
import { css, cx } from "@emotion/css";
import Web3 from "web3";
import "./App.css";
import CONTRACT_ABI from "./contracts/TestERC20.json";
function App() {
const [currentAccount, setCurrentAccount] = useState(null);
const [web3, setWeb3] = useState(null);
const [contractAddress, setContractAddress] = useState("");
const [erc20Contract, setErc20Contract] = useState(null);
const [tokenBalance, setTokenBalance] = useState("0");
const [targetAddress, setTargetAddress] = useState("");
const [targetAmount, setTargetAmount] = useState(0);
const checkWalletIsConnected = async () => {
const { ethereum } = window;
if (!ethereum) {
console.log("Make sure you have Metamask installed!");
return;
} else {
console.log("Wallet exists! We're ready to go!");
setWeb3(new Web3(ethereum));
}
const accounts = await ethereum.request({ method: "eth_accounts" });
if (accounts.length !== 0) {
const account = accounts[0];
console.log("Found an authorized account: ", account);
setCurrentAccount(account);
} else {
console.log("No authorized account found");
}
window.ethereum.on("accountsChanged", function (accounts) {
// Time to reload your interface with accounts[0]!
if (accounts.length !== 0) {
const account = accounts[0];
console.log("Found an authorized account: ", account);
setCurrentAccount(account);
} else {
console.log("No authorized account found");
}
});
};
const connectWalletHandler = async () => {
const { ethereum } = window;
if (!ethereum) {
alert("Please install Metamask!");
}
try {
const accounts = await ethereum.request({
method: "eth_requestAccounts",
});
console.log("Found an account! Address: ", accounts[0]);
setCurrentAccount(accounts[0]);
} catch (err) {
console.log(err);
}
};
const connectWalletButton = () => {
return (
<Button onClick={connectWalletHandler} variant="contained">
Connect Wallet
</Button>
);
};
useEffect(() => {
checkWalletIsConnected();
}, []);
useEffect(() => {
if (currentAccount && erc20Contract) {
erc20Contract.methods
.balanceOf(currentAccount)
.call()
.then((balance) => setTokenBalance(balance.toString()));
}
}, [currentAccount, erc20Contract]);
const reloadContract = () => {
if (web3) {
setErc20Contract(new web3.eth.Contract(CONTRACT_ABI, contractAddress));
}
};
const [isSending, setIsSending] = useState(false);
const [isDone, setIsDone] = useState(false);
const [hash, setHash] = useState(null);
const [confirmNum, setConfirmNum] = useState(0);
const handleSendToken = () => {
setIsSending(true);
erc20Contract.methods
.transfer(targetAddress, new Web3().utils.toWei(targetAmount.toString()))
.send({
from: currentAccount,
gas: 200000,
type: "0x2",
})
.on("transactionHash", (hash) => {
setHash(hash);
})
.on("confirmation", (confirmationNumber, receipt) => {
setConfirmNum(confirmationNumber);
if (confirmationNumber >= 5) {
setIsSending(false);
setIsDone(true);
erc20Contract.methods
.balanceOf(currentAccount)
.call()
.then((balance) => setTokenBalance(balance.toString()));
}
});
};
const [target1, setTarget1] = useState("");
const [target2, setTarget2] = useState("");
const [targetAmount2, setTargetAmount2] = useState(0);
const [isSending2, setIsSending2] = useState(false);
const [isDone2, setIsDone2] = useState(false);
const [hash2, setHash2] = useState(null);
const [confirmNum2, setConfirmNum2] = useState(0);
const handleSendMultipleToken = () => {
setIsSending2(true);
setIsDone2(false);
erc20Contract.methods
.transferMultiple(
target1,
target2,
new Web3().utils.toWei(targetAmount2.toString())
)
.send({
from: currentAccount,
gas: 200000,
type: "0x2",
})
.on("transactionHash", (hash) => {
setHash2(hash);
})
.on("confirmation", (confirmationNumber, receipt) => {
setConfirmNum2(confirmationNumber);
if (confirmationNumber >= 5) {
setIsSending2(false);
setIsDone2(true);
erc20Contract.methods
.balanceOf(currentAccount)
.call()
.then((balance) => setTokenBalance(balance.toString()));
}
});
};
const [txnData, setTxnData] = useState("");
const [decoded, setDecoded] = useState(null);
const decodeTxnData = () => {
const multitransferABI = [
{ internalType: "address", name: "receiver1", type: "address" },
{ internalType: "address", name: "receiver2", type: "address" },
{ internalType: "uint256", name: "numTokens", type: "uint256" },
];
const decoded = web3.eth.abi.decodeParameters(
multitransferABI,
txnData.slice(10)
);
setDecoded(decoded);
};
return (
<Container
className={css`
background-color: lightgrey !important;
min-height: 1000px;
`}
>
<Typography align="center" variant="h2">
{" "}
Web3 Demo{" "}
</Typography>
<Card
className={css`
padding: 30px;
margin-top: 30px;
margin-bottom: 30px;
`}
>
<CardContent
className={css`
justify-content: center;
`}
>
<Typography align="center" variant="h5">
{" "}
Active account: {(currentAccount && currentAccount) || "None"}
</Typography>
{!currentAccount && connectWalletButton()}
</CardContent>
</Card>
<Card
className={css`
padding: 30px;
margin-bottom: 30px;
`}
>
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
<TextField
type="text"
label="Address"
size="small"
placeholder="Contract Address"
value={contractAddress}
onChange={(e) => setContractAddress(e.target.value)}
style={{ width: 500 }}
/>
<Button variant="contained" onClick={reloadContract}>
Reload Contract
</Button>
</Stack>
{!erc20Contract ? (
"Contract not loaded!"
) : (
<React.Fragment>
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
Token Balance: {new Web3().utils.fromWei(tokenBalance)}
</Stack>
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
<TextField
type="text"
label="Target"
size="small"
placeholder="Target Address"
value={targetAddress}
onChange={(e) => setTargetAddress(e.target.value)}
style={{ width: 500 }}
/>
<TextField
type="text"
label="Amount"
size="small"
placeholder="Amount to Send"
value={targetAmount}
onChange={(e) => setTargetAmount(e.target.value)}
style={{ width: 200 }}
/>
<Button variant="contained" onClick={handleSendToken}>
Send
</Button>
</Stack>
{isSending && (
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
Transaction Hash: {hash}
</Stack>
)}
{isSending && (
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
Confirmation Number: {confirmNum}
</Stack>
)}
{isDone && (
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
Sending Done!!!
</Stack>
)}
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
<TextField
type="text"
label="Target 1"
size="small"
placeholder="Target Address"
value={target1}
onChange={(e) => setTarget1(e.target.value)}
style={{ width: 400 }}
/>
<TextField
type="text"
label="Target 2"
size="small"
placeholder="Target Address"
value={target2}
onChange={(e) => setTarget2(e.target.value)}
style={{ width: 400 }}
/>
<TextField
type="text"
label="Amount"
size="small"
placeholder="Amount to Send"
value={targetAmount2}
onChange={(e) => setTargetAmount2(e.target.value)}
style={{ width: 200 }}
/>
<Button variant="contained" onClick={handleSendMultipleToken}>
Send
</Button>
</Stack>
{isSending2 && (
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
Transaction Hash: {hash2}
</Stack>
)}
{isSending2 && (
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
Confirmation Number: {confirmNum2}
</Stack>
)}
{isDone2 && (
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
Sending Done!!!
</Stack>
)}
</React.Fragment>
)}
</Card>
<Card
className={css`
padding: 30px;
margin-bottom: 30px;
`}
>
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
mt={5}
spacing={3}
maxWidth={800}
>
<TextField
type="text"
label="Transaction Data"
size="small"
placeholder="Transaction Data"
value={txnData}
onChange={(e) => setTxnData(e.target.value)}
style={{ width: 500 }}
/>
<Button variant="contained" onClick={decodeTxnData}>
Decode
</Button>
</Stack>
{decoded && (
<React.Fragment>
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
Receiver 1: {decoded.receiver1}
</Stack>
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
Receiver 2: {decoded.receiver2}
</Stack>
<Stack
direction="row"
alignItems="left"
justifyContent="start"
mb={3}
spacing={3}
maxWidth={800}
>
Number of Tokens:{" "}
{new Web3().utils.fromWei(decoded.numTokens.toString())}
</Stack>
</React.Fragment>
)}
</Card>
</Container>
);
}
export default App;
[
{ "inputs": [], "stateMutability": "nonpayable", "type": "constructor" },
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "address", "name": "delegate", "type": "address" }
],
"name": "allowance",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "delegate", "type": "address" },
{ "internalType": "uint256", "name": "numTokens", "type": "uint256" }
],
"name": "approve",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "tokenOwner", "type": "address" }
],
"name": "balanceOf",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "receiver", "type": "address" },
{ "internalType": "uint256", "name": "numTokens", "type": "uint256" }
],
"name": "transfer",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "address", "name": "buyer", "type": "address" },
{ "internalType": "uint256", "name": "numTokens", "type": "uint256" }
],
"name": "transferFrom",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "receiver1", "type": "address" },
{ "internalType": "address", "name": "receiver2", "type": "address" },
{ "internalType": "uint256", "name": "numTokens", "type": "uint256" }
],
"name": "transferMultiple",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment