Skip to content

Instantly share code, notes, and snippets.

@rashgaroth
Created January 11, 2022 15:56
Show Gist options
  • Save rashgaroth/af7b99eef284ccfdfa37e35309964ae1 to your computer and use it in GitHub Desktop.
Save rashgaroth/af7b99eef284ccfdfa37e35309964ae1 to your computer and use it in GitHub Desktop.
Web3 hooks for communicate with smart contract
// =============================================
// Dwiyan Putra (nefti.dwiyan@gmail.com)
// Jan 11 2022
// =============================================
import * as _aP from 'storage/profile/actions' //your actions (redux)
import * as _aW from 'storage/wallet/walletActions' //your actions (redux)
import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux' // im using redux for state management
import Web3 from 'web3'
import Web3Modal from 'web3modal'
import { providerOptions } from 'common/provider/Web3Connectors' // your providerOptions for web3 connect :)
import { toast } from 'react-hot-toast' // for giving informations for users
// =============================================
// Smart contract address list, please use your own smart contract address for this
// =============================================
let web3Modal
const scMarketplace = process.env.CONTRACT_ADDRESS_MARKETPLACE
const scWallet = process.env.CONTRACT_ADDRESS_WALLET
const scGallery = process.env.CONTRACT_ADDRESS_OWN_GALLERY
const abiWallet = process.env.ABI_WALLET
const abiGallery = process.env.ABI_GALLERY
const abiMarketplace = process.env.ABI_MARKETPLACE
// =============================================
// Done
// =============================================
if (typeof window !== 'undefined') {
web3Modal = new Web3Modal({
cacheProvider: true,
providerOptions: providerOptions,
})
}
const initialWeb3State = {
provider: null,
web3Provider: null,
address: '',
balance: '0',
decimal: ''
}
const web3Reducer = (state = initialWeb3State, action) => {
switch(action.type){
case 'SET_PROVIDER':
return {
...state,
provider: action.provider.provider,
address: action.provider.address,
balance: action.provider.balance,
web3Provider: action.provider.web3Provider,
decimal: action.provider.decimal,
}
case 'SET_ADDR':
return {
...state,
address: action.data,
}
}
}
export const Web3Context = React.createContext(null)
export const Web3Provider = ({ children }) => {
const dispatcherRedux = useDispatch()
const [state, dispatch] = useReducer(web3Reducer, initialWeb3State)
const [provider, setProvider] = useState(null)
const [account, setAccount] = useState(null)
const [walletContract, setWalletContract] = useState(null)
const [galleryContract, setGalleryContract] = useState(null)
const [marketplaceContract, setMarketplaceContract] = useState(null)
const [isConnected, setIsConnected] = useState(false)
const [withWeb3, setWithWeb3] = useState(null)
// Connect wallet function
const web3Connect = () => new Promise(async(resolve, reject) => {
try{
const prov = await web3Modal.connect()
if(prov){
console.log(prov, "@prov")
resolve({
provider: prov
})
}else{
resolve({
provider: null
})
}
}catch(e){
if (typeof e !== 'undefined') {
if (e.message === 'Already processing eth_requestAccounts. Please wait.') {
toast.error('Already processing. Please go to your wallet provider and sync with Neftipedia')
resolve({
provider: null
})
} else {
if (e.message.includes("Returned values aren't valid")) {
toast.error("Please change your network")
resolve({
provider: null
})
} else {
toast.error(e.message)
resolve({
provider: null
})
}
}
}
resolve({
provider: null
})
}
})
// Connect wallet function
const connect = async () => {
console.log("@connecting")
try {
web3Connect().then(async(data) => {
if(data.provider){
const prov = data.provider
const web3 = new Web3(prov)
const account = await web3.eth.getAccounts()
const walletContract = new web3.eth.Contract(abiWallet, scWallet)
const galleryContract = new web3.eth.Contract(abiGallery, scGallery)
const marketplaceContract = new web3.eth.Contract(abiMarketplace, scMarketplace)
const tokenBalance = await walletContract.methods.balanceOf(account[0]).call()
console.info("@Connecting to web3... ")
await setWithWeb3(web3)
await setProvider(prov)
setWalletContract(walletContract)
setGalleryContract(galleryContract)
setMarketplaceContract(marketplaceContract)
setAccount(account[0])
setIsConnected(true)
console.info("@Connected")
dispatcherRedux(_aP.auth({
wallet: account[0].toLocaleLowerCase()
}))
await dispatch(
{
type: 'SET_PROVIDER',
provider: {
...state,
address: account[0],
provider: prov,
web3Provider: web3,
balance: tokenBalance,
}
},
)
await dispatch(
{
type: 'SET_ADDR',
data: account[0]
}
)
}
}).catch((e) => {
toast.error(e.message)
})
} catch (e) {
if (typeof e !== 'undefined') {
toast.error(e.message)
}
}
}
// Disconnect wallet function
const disconnect = async () => {
console.log(provider, state.provider, "@state.provider")
await web3Modal.clearCachedProvider()
if (provider.disconnect && typeof provider.disconnect === 'function') {
await provider.disconnect()
}
setIsConnected(false)
setProvider(null)
setWithWeb3(null)
await dispatch(
{
type: 'SET_PROVIDER',
provider: {
...state,
provider: null,
web3Provider: null,
address: '',
balance: '0',
}
}
)
await dispatcherRedux(
_aP.successLogin({
jwt: '',
name: 'No Account',
profile_cover: '',
profile_picture: '',
status: '',
verified_at: '',
wallet_address: '',
id: '',
email: '',
badge: '',
}),
)
}
// React lifecycle
let temp = true
useEffect(async () => {
if (typeof state.provider !== 'undefined' && state.provider !== null) {
if (state.provider.on) {
const handleAccountsChanged = async (accountCallback = []) => {
temp = true
try{
if(accountCallback){
if(temp){
console.info("@fromAccountChanged")
const web3 = new Web3(state.provider)
const tokenBalance = await walletContract.methods.balanceOf(accountCallback[0]).call()
await setWithWeb3(web3)
await setProvider(state.provider)
setAccount(accountCallback[0])
setIsConnected(true)
dispatcherRedux(_aP.auth({
wallet: accountCallback[0]
}))
dispatch({
type: 'SET_PROVIDER',
provider: {
...state,
web3Provider: web3,
balance: tokenBalance,
address: accountCallback[0],
}
})
}else{
console.info("@account changed with:", accountCallback[0])
}
temp = true
}
}catch(e){
toast.error(e.message)
}
}
//https://docs.ethers.io/v5/concepts/best-practices/#best-practices--network-changes
const handleChainChanged = (_hexChainId = '') => {
console.log("on chain changed")
window.location.reload()
}
const handleDisconnect = () => {
disconnect()
}
state.provider.on('accountsChanged', handleAccountsChanged)
state.provider.on('chainChanged', handleChainChanged)
state.provider.on('disconnect', handleDisconnect)
}
}
return async () => {
console.info("@on cleaning handler")
dispatch(
{
type: 'SET_PROVIDER',
provider: {
...state,
provider: null,
web3Provider: null,
address: '',
balance: '0',
}
}
)
await dispatcherRedux(
_aP.successLogin({
jwt: '',
name: 'Neftipedia Account',
profile_cover: '',
profile_picture: '',
status: '',
verified_at: '',
wallet_address: '',
id: '',
email: '',
badge: '',
}),
)
}
}, [state.provider])
useEffect(() => {
if (web3Modal.cachedProvider) {
connect()
setIsConnected(true)
}else{
setIsConnected(false)
}
}, [])
const toTokenHex = (num) => {
const { toBN, toHex } = withWeb3.utils
const isFloat = n => {
return Number(n) === n && n % 1 !== 0
}
const toToken = val => {
let numb = isFloat(val) ? val * 100 : val
let res = toBN(10).pow(toBN(16)).mul(toBN(numb))
if (isFloat(val)) res = res.div(toBN(100))
return res
}
return toHex(toToken(num))
}
// used for global approval :)
// another function for communicate with smart contract
const approval = (sc, amount) => new Promise((resolve, reject) => {
try{
walletContract.methods.approve(sc, amount)
.send({ from: account })
.on('confirmation', (confirmationNumber, receipt) => {
console.info(receipt, "@approvalReceipt")
resolve({
success: true,
data: receipt
})
})
.on('error', (error, receipt) => {
console.error(error, receipt)
reject({
success: false,
data: error.message
})
})
}catch(e){
reject({
success: false,
data: e.message
})
}
})
const allowance = (sc) => new Promise((resolve, reject) => {
try{
walletContract.methods.allowance(account, sc)
.send({ from: account })
.on('confirmation', (confirmationNumber, receipt) => {
console.info(receipt, "@allowedReceipt")
resolve({
success: true,
data: receipt
})
})
.on('error', (error, receipt) => {
console.error(error, receipt)
reject({
success: false,
data: error.message
})
})
}catch(e){
reject({
success: false,
data: e.message
})
}
})
const txDirectBuy = (saleId, purchaseId, amount) => new Promise((resolve, reject) => {
try{
marketplaceContract.methods.txDirectBuy(saleId, purchaseId, amount)
.send({ from: account })
.on('confirmation', (confirmationNumber, receipt) => {
console.info(receipt, "@txDirectBuyReceipt")
resolve({
success: true,
data: receipt
})
})
.on('error', (error, receipt) => {
console.error(error, receipt)
reject({
success: false,
data: error.message
})
})
}catch(e){
resolve({
success: false,
data: {
message: e.message
}
})
}
})
const refreshBalance = () => new Promise(async(resolve, reject) => {
try{
if(state.provider !== null){
const web3 = new Web3(state.provider)
web3.eth.getAccounts()
.then(async(account) => {
if(account[0]){
await walletContract.methods.balanceOf(account[0]).call({ from: account[0] }, (error, res) => {
if(res){
console.info(res, "@balanceOfResult")
dispatch({
type: 'SET_PROVIDER',
provider: {
...state,
web3Provider: web3,
balance: res,
address: account[0],
}
})
resolve({
success: true,
data: res
})
}else{
console.error(error, "@errorgetBalance")
reject({
success: false,
data: error.message
})
}
})
}
})
}
}catch(e){
reject({
success: false,
data: e.message
})
}
})
const values = useMemo(
() => ({
connect,
disconnect,
toTokenHex,
approval,
allowance,
txDirectBuy,
refreshBalance,
isConnected,
provider,
account,
walletContract,
galleryContract,
marketplaceContract,
state,
withWeb3,
}),
[
isConnected,
account,
provider,
state,
withWeb3
],
)
return <Web3Context.Provider value={values}>{children}</Web3Context.Provider>
}
export default function useWeb3() {
const context = React.useContext(Web3Context)
if (context === undefined) {
throw new Error('useWeb3 hook must be with a useWeb3Provider')
}
return context
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment