26
loading...
This website collects cookies to deliver better user experience
This section requires you to have some basic understanding of react such as components, useState, useEffect, etc. There are a ton of good resources on react, so please check them out if you are not familiar with these concepts.
This project is hosted here.
Link to Github Repo of the project here. This will serve as a reference for you, if at some point you are not able to follow the tutorial.
npx create-react-app frontend
npm install @alch/alchemy-web3
npm install react-bootstrap [email protected]
npm install dotenv
npm install react-icons --save
Alchemy Web3 is a wrapper around Web3.js
which provides some enhanced API methods.
create-react-app
initiates with some boilerplate code when we can get rid off.App.test.js
logo.svg
reportWebVitals.js
, and setupTests.js
from the src folder.index.js
and App.js
file to look like this.import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
import './App.css'
function App() {
return (
<>
</>
)
}
export default App
frontend/public/index.html
and add bootstrap CDN into the head tag.<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
.env
file in the frontend directory and add your Alchemy API Key which we used in the previous section.REACT_APP_ALCHEMY_KEY = <YOUR_API_KEY>
SimpleBank/scripts/deploy.js
.const { ethers, artifacts } = require('hardhat')
async function main() {
const [deployer] = await ethers.getSigners()
console.log('Deploying contracts with the account: ', deployer.address)
const Bank = await ethers.getContractFactory('Bank')
const bank = await Bank.deploy()
console.log('Bank address: ', bank.address)
saveArtifacts(bank)
}
// save the address and artifact of the deployed contract in the frontend
const saveArtifacts = (bank) => {
const fs = require('fs')
const artifactDir = __dirname + '/../frontend/src/artifacts'
if (!fs.existsSync(artifactDir)) {
fs.mkdirSync(artifactDir)
}
const bankArtifact = artifacts.readArtifactSync('Bank')
const artifact = {
address: bank.address,
abi: bankArtifact.abi,
}
console.log('Saving artifacts to: ', artifactDir)
fs.writeFileSync(artifactDir + '/Bank.json', JSON.stringify(artifact))
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})
saveArtifacts()
and calling it at the end of main
will save the deployed contract address
and abi
to frontend/src/artifacts
when the contract is deployed.npx hardhat run .\scripts\deploy.js --network rinkeby
frontend/src
with a Bank.json
file.frontend/src
called utils
and add the following files into it. This is where we'll write all our functions.export const connectWallet = async () => {
if (window.ethereum) {
try {
const addresses = await window.ethereum.request({
method: 'eth_requestAccounts',
})
const obj = {
address: addresses[0],
connected: true,
status: '',
}
return obj
} catch (error) {
return {
address: '',
connected: false,
status: error.message,
}
}
} else {
return {
address: '',
connected: false,
status: (
<a href="https://metamask.io/" target="_blank" rel="noreferrer">
{' '}
You need to install Metamask
</a>
),
}
}
}
export const getWalletStatus = async () => {
if (window.ethereum) {
try {
const addresses = await window.ethereum.request({
method: 'eth_requestAccounts',
})
if (addresses.length > 0) {
return {
address: addresses[0],
connected: true,
status: '',
}
} else {
return {
address: '',
connected: false,
status: '🦊 Please connect to Metamask Wallet',
}
}
} catch (error) {
return {
address: '',
connected: false,
status: error.message,
}
}
} else {
return {
address: '',
connected: false,
status: (
<a href="https://metamask.io/" target="_blank" rel="noreferrer">
{' '}
You need to install Metamask
</a>
),
}
}
}
if (window.ethereum) {
//does someting
} else {
return {
address: '',
connected: false,
status: (
<a href="https://metamask.io/" target="_blank" rel="noreferrer">
{' '}
You need to install Metamask
</a>
),
}
}
window.ethereum
checks if the browser has a wallet. If yes, it proceeds else we return an instruction to install metamask wallet.try {
const addresses = await window.ethereum.request({
method: 'eth_requestAccounts',
})
const obj = {
address: addresses[0],
connected: true,
status: '',
}
return obj
} catch (error) {
return {
address: '',
connected: false,
status: error.message,
}
}
const addresses = await window.ethereum.request({ method: 'eth_requestAccounts', })
to request the wallet to connect an account to the app. If the connection was successful we get all the connected addresses and we return the first one, else we return the error message.getWalletStatus
function also works pretty much the same. It checks the accounts that are connected to the wallet, if none it responds with request to connect.const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require('@alch/alchemy-web3')
const web3 = createAlchemyWeb3(alchemyKey)
const { abi, address } = require('../artifacts/Bank.json')
export const depositEth = async (amount) => {
if (parseFloat(amount) <= 0) {
return {
status: 'Please enter a valid amount',
}
}
window.contract = await new web3.eth.Contract(abi, address)
const WeiAmount = web3.utils.toHex(web3.utils.toWei(amount, 'ether'))
const txParams = {
to: address,
from: window.ethereum.selectedAddress,
value: WeiAmount,
data: window.contract.methods.deposit().encodeABI(),
}
try {
await window.ethereum.request({
method: 'eth_sendTransaction',
params: [txParams],
})
return {
status: 'Transaction Successful. Refresh in a moment.',
}
} catch (error) {
return {
status: 'Transaction Failed' + error.message,
}
}
}
export const withdrawEth = async (amount) => {
window.contract = await new web3.eth.Contract(abi, address)
const WeiAmount = web3.utils.toWei(amount, 'ether')
const txParams = {
to: address,
from: window.ethereum.selectedAddress,
data: window.contract.methods.withdraw(WeiAmount).encodeABI(),
}
try {
await window.ethereum.request({
method: 'eth_sendTransaction',
params: [txParams],
})
return {
status: 'Transaction Successful. Refresh in a moment',
}
} catch (error) {
return {
status: 'Transaction Failed' + error.message,
}
}
}
export const getBalance = async () => {
window.contract = await new web3.eth.Contract(abi, address)
const reqParams = {
to: address,
from: window.ethereum.selectedAddress,
data: window.contract.methods.getBalance().encodeABI(),
}
try {
const response = await window.ethereum.request({
method: 'eth_call',
params: [reqParams],
})
const exhRate = await exchangeRate()
const balance = web3.utils.fromWei(response, 'ether')
return {
inr: balance * exhRate,
eth: balance,
exhRate: exhRate,
}
} catch (error) {
return {
status: 'Check Failed ' + error.message,
}
}
}
export const exchangeRate = async () => {
const response = await fetch(
'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=inr',
)
const data = await response.json()
return data.ethereum.inr
}
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require('@alch/alchemy-web3')
const web3 = createAlchemyWeb3(alchemyKey)
const { abi, address } = require('../artifacts/Bank.json')
address
and abi
from artifacts/Bank.json
.if (parseFloat(amount) <= 0) {
return {
status: 'Please enter a valid amount',
}
}
depositEth
function, which will be used to deposit amount. amount
parameter will be in string format, so we convert it to float and ensure it is greater than 0.window.contract = await new web3.eth.Contract(abi, address)
abi
and address
.const WeiAmount = web3.utils.toHex(web3.utils.toWei(amount, 'ether'))
const txParams = {
to: address,
from: window.ethereum.selectedAddress,
value: WeiAmount,
data: window.contract.methods.deposit().encodeABI(),
}
to: <contract_address>
, from: <address_of_account_connected_to_app>
, value: <amount_to_be_deposited>
, data: <call_to_contract_function>
.try {
await window.ethereum.request({
method: 'eth_sendTransaction',
params: [txParams],
})
return {
status: 'Transaction Successful. Refresh in a moment.',
}
} catch (error) {
return {
status: 'Transaction Failed' + error.message,
}
}
}
withdrawEth
function as the name suggests is for transferring back the amount from bank to wallet. Its almost same are earlier, just instead of sending amount as a transaction parameter we'll send it as a parameter to the call function.getBalance
function return the balance available for the amount which calls it. The major difference here is that we use method: 'eth_call'
since it is just a view function. We also use a exchangeRate
function which is a simple fetch
request to an api to get the current exchange rate of ETH
to INR
and return balance in both format.components
folder in frontend/src
and add these components to it.import { Container, Navbar } from 'react-bootstrap'
export default function NavBar() {
return (
<div>
<Navbar bg="dark" variant="dark">
<Container>
<Navbar.Brand>SimpleBank & Co.</Navbar.Brand>
</Container>
</Navbar>
</div>
)
}
import { Alert, Container, Row, Col } from 'react-bootstrap'
const StatusBox = ({ status }) => {
return (
<Container
className={status.length === 0 ? 'status-box-null' : 'status-box'}
>
<Row className="justify-content-center">
<Col lg="6">
<Alert variant="danger">{status}</Alert>
</Col>
</Row>
</Container>
)
}
export default StatusBox
import { IoIosRefresh } from 'react-icons/io'
import {
Button,
Container,
FormControl,
InputGroup,
Col,
Row,
Alert,
} from 'react-bootstrap'
import { useState, useEffect } from 'react'
import { getBalance, depositEth, withdrawEth } from '../utils/bankFunctions'
const BankInfo = ({ onAccoutChange }) => {
const [balanceINR, setBalanceINR] = useState(0)
const [balanceETH, setBalanceETH] = useState(0)
const [showDeposit, setShowDeposit] = useState(false)
const [showWithdraw, setShowWithdraw] = useState(false)
const [exhRate, setExhRate] = useState(0)
const [inputINR, setInputINR] = useState(null)
const [inputETH, setInputETH] = useState(null)
const [response, setResponse] = useState(null)
const handleShowDeposit = () => {
setShowDeposit(true)
}
const handleShowWithdraw = () => {
setShowWithdraw(true)
}
const handleClose = () => {
setShowDeposit(false)
setShowWithdraw(false)
setInputINR(null)
setInputETH(null)
setResponse(null)
}
const checkBalance = async () => {
const balance = await getBalance()
setBalanceETH(balance.eth)
setBalanceINR(balance.inr)
setExhRate(balance.exhRate)
}
const handleInoutINR = (e) => {
setInputINR(e.target.value)
setInputETH((e.target.value / exhRate).toFixed(18))
}
const handleDeposit = async () => {
setResponse(null)
const deposit = await depositEth(inputETH.toString())
setInputETH(null)
setInputINR(null)
setResponse(deposit.status)
}
const handleWithdraw = async () => {
if (inputINR > balanceINR) {
setResponse('Insufficient Balance')
} else {
setResponse(null)
const withdraw = await withdrawEth(inputETH.toString())
setInputETH(null)
setInputINR(null)
setResponse(withdraw.status)
}
}
useEffect(() => {
checkBalance()
}, [onAccoutChange])
return (
<>
<div className="balance-card">
<h1>
Your Balance
<IoIosRefresh className="refresh-icon" onClick={checkBalance} />
</h1>
<h3 className="balance-inr">{parseFloat(balanceINR).toFixed(2)} INR</h3>
<h3 className="balance-eth">{parseFloat(balanceETH).toFixed(4)} ETH</h3>
{!showDeposit && !showWithdraw && (
<div className="btn-grp">
<Button
className="deposit-btn"
variant="success"
onClick={handleShowDeposit}
>
Deposit
</Button>
<Button
className="withdraw-btn"
variant="warning"
onClick={handleShowWithdraw}
>
Withdraw
</Button>
</div>
)}
{showDeposit || showWithdraw ? (
<>
<Container>
<Row className="justify-content-center ">
<Col md="6">
<InputGroup className="amount-input">
<FormControl
placeholder="Enter Amount in INR"
type="number"
value={inputINR > 0 ? inputINR : ''}
onChange={handleInoutINR}
/>
<InputGroup.Text>INR</InputGroup.Text>
</InputGroup>
</Col>
</Row>
<Row className="justify-content-center">
<Col md="6">
<InputGroup className="amount-input">
<FormControl
placeholder="ETH Equivalent"
type="number"
value={inputETH > 0 ? inputETH : ''}
readOnly
/>
<InputGroup.Text>ETH</InputGroup.Text>
</InputGroup>
</Col>
</Row>
</Container>
<div className="btn-grp">
<Button
className="deposit-btn"
variant="success"
onClick={showDeposit ? handleDeposit : handleWithdraw}
>
{showDeposit ? 'Deposit' : 'Withdraw'}
</Button>
<Button
className="withdraw-btn"
variant="info"
onClick={handleClose}
>
Close
</Button>
</div>
{response && (
<Container>
<Row className="justify-content-center">
<Col md="6">
<Alert variant="info">{response}</Alert>
</Col>
</Row>
</Container>
)}
</>
) : null}
</div>
</>
)
}
export default BankInfo
import { useState, useEffect } from 'react'
import { Button } from 'react-bootstrap'
import { connectWallet, getWalletStatus } from '../utils/walletFunctions'
export const ConnectBtn = ({ setStatus, setConnected, setWallet }) => {
const [walletAddress, setWalletAddress] = useState('')
const handleConnect = async () => {
const walletResponse = await connectWallet()
setStatus(walletResponse.status)
setConnected(walletResponse.connected)
setWalletAddress(walletResponse.address)
setWallet(walletResponse.address)
}
useEffect(() => {
const checkWalletStatus = async () => {
const walletResponse = await getWalletStatus()
setStatus(walletResponse.status)
setConnected(walletResponse.connected)
setWalletAddress(walletResponse.address)
setWallet(walletResponse.address)
}
const walletListener = () => {
if (window.ethereum) {
window.ethereum.on('accountsChanged', (accounts) => {
checkWalletStatus()
})
}
}
checkWalletStatus()
walletListener()
}, [setConnected, setStatus, setWallet])
return (
<div className="connect-btn">
<Button variant="primary" onClick={handleConnect}>
{walletAddress.length === 0
? 'Connet Wallet'
: 'Connected: ' +
String(walletAddress).substring(0, 6) +
'...' +
String(walletAddress).substring(38)}
</Button>
</div>
)
}
export default ConnectBtn
ConnectBtn
component displays the status of wallet connection to the app and also used to request connection when not connected.import { IoIosInformationCircleOutline } from 'react-icons/io'
const Footer = () => {
return (
<footer className="footer">
<p className="footer-text">
<IoIosInformationCircleOutline className="info-icon" /> This application
in running on rinkeby test network. Please only use test Ethers.
</p>
</footer>
)
}
export default Footer
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.amount-input {
margin: 10px;
}
.balance-card {
margin-top: 20px;
text-align: center;
}
.balance-eth {
color: rgb(19, 202, 28);
}
.balance-inr {
color: rgb(25, 214, 214);
}
.btn-grp {
margin: 20px;
}
.deposit-btn {
margin-right: 20px;
}
.footer {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
padding: 5px;
background-color: teal;
color: white;
text-align: center;
}
.footer-text {
font-size: large;
}
.info-icon {
font-size: x-large;
}
.withdraw-btn {
margin-left: 20px;
}
.connect-btn {
text-align: center;
margin: 20px;
}
.refresh-icon {
margin-left: 10px;
font-size: 28px;
cursor: pointer;
}
.status-box {
margin-top: 20px;
text-align: center;
}
.status-box-null {
display: none;
}
import { useState } from 'react'
import NavBar from './components/NavBar'
import ConnectBtn from './components/ConnectBtn'
import StatusBox from './components/StatusBox'
import BankInfo from './components/BankInfo'
import Footer from './components/Footer'
function App() {
const [status, setStatus] = useState('')
const [connected, setConnected] = useState()
const [wallet, setWallet] = useState()
return (
<>
<NavBar />
<ConnectBtn
setStatus={setStatus}
setConnected={setConnected}
setWallet={setWallet}
/>
<StatusBox status={status} />
{connected && <BankInfo onAccoutChange={wallet} />}
<Footer />
</>
)
}
export default App
App.js
in order to render them in our app.npm start