mirror of
https://github.com/kremalicious/blog.git
synced 2025-01-05 03:15:07 +01:00
use new web3-react
This commit is contained in:
parent
32faba15eb
commit
ab14272675
@ -121,6 +121,12 @@ exports.onPostBuild = async ({ graphql }) => {
|
|||||||
// https://github.com/ethereum/web3.js/issues/1105#issuecomment-446039296
|
// https://github.com/ethereum/web3.js/issues/1105#issuecomment-446039296
|
||||||
exports.onCreateWebpackConfig = ({ actions }) => {
|
exports.onCreateWebpackConfig = ({ actions }) => {
|
||||||
actions.setWebpackConfig({
|
actions.setWebpackConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
// replace native `scrypt` module with pure js `js-scrypt`
|
||||||
|
scrypt: 'js-scrypt'
|
||||||
|
}
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
// ignore these plugins completely
|
// ignore these plugins completely
|
||||||
new webpack.IgnorePlugin(/^(?:electron|ws)$/)
|
new webpack.IgnorePlugin(/^(?:electron|ws)$/)
|
||||||
|
15564
package-lock.json
generated
15564
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -29,7 +29,11 @@
|
|||||||
"not op_mini all"
|
"not op_mini all"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ethersproject/providers": "^5.0.0-beta.145",
|
||||||
|
"@ethersproject/units": "^5.0.0-beta.131",
|
||||||
"@loadable/component": "^5.10.3",
|
"@loadable/component": "^5.10.3",
|
||||||
|
"@web3-react/core": "^6.0.0-beta.15",
|
||||||
|
"@web3-react/injected-connector": "^6.0.0-beta.17",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"date-fns": "^2.8.1",
|
"date-fns": "^2.8.1",
|
||||||
"dms2dec": "^1.1.0",
|
"dms2dec": "^1.1.0",
|
||||||
@ -81,8 +85,7 @@
|
|||||||
"remark": "^11.0.1",
|
"remark": "^11.0.1",
|
||||||
"remark-react": "^6.0.0",
|
"remark-react": "^6.0.0",
|
||||||
"slugify": "^1.3.6",
|
"slugify": "^1.3.6",
|
||||||
"use-dark-mode": "^2.3.1",
|
"use-dark-mode": "^2.3.1"
|
||||||
"web3-react": "^5.0.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/node": "^7.7.0",
|
"@babel/node": "^7.7.0",
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
@import 'variables';
|
@import 'variables';
|
||||||
|
|
||||||
.account {
|
.accountWrap {
|
||||||
font-size: $font-size-mini;
|
font-size: $font-size-small;
|
||||||
color: $brand-grey-light;
|
color: $brand-grey-light;
|
||||||
max-width: 8rem;
|
margin-bottom: $spacer;
|
||||||
white-space: nowrap;
|
display: flex;
|
||||||
overflow: hidden;
|
justify-content: space-between;
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.identicon {
|
.identicon {
|
||||||
@ -15,5 +14,8 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: $spacer / 8;
|
margin-right: $spacer / 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance {
|
||||||
margin-left: $spacer;
|
margin-left: $spacer;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,31 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Blockies from 'react-blockies'
|
import Blockies from 'react-blockies'
|
||||||
|
import { formatEther } from '@ethersproject/units'
|
||||||
import styles from './Account.module.scss'
|
import styles from './Account.module.scss'
|
||||||
|
import useWeb3, { getBalance } from '../../../hooks/use-web3'
|
||||||
|
|
||||||
const Account = ({ account }: { account: string }) => (
|
export default function Account() {
|
||||||
<div className={styles.account} title={account}>
|
const { library, account } = useWeb3()
|
||||||
<Blockies seed={account} scale={2} size={8} className={styles.identicon} />
|
const ethBalance = account && getBalance(account, library)
|
||||||
{account}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default Account
|
const accountDisplay =
|
||||||
|
account &&
|
||||||
|
`${account.substring(0, 8)}...${account.substring(account.length - 4)}`
|
||||||
|
const balanceDisplay =
|
||||||
|
ethBalance && `Ξ${parseFloat(formatEther(ethBalance)).toPrecision(4)}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.accountWrap} title={account}>
|
||||||
|
<span className={styles.account}>
|
||||||
|
<Blockies
|
||||||
|
seed={account}
|
||||||
|
scale={2}
|
||||||
|
size={8}
|
||||||
|
className={styles.identicon}
|
||||||
|
/>
|
||||||
|
{accountDisplay}
|
||||||
|
</span>
|
||||||
|
<span className={styles.balance}>{balanceDisplay}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
46
src/components/molecules/Web3Donation/Alert.tsx
Normal file
46
src/components/molecules/Web3Donation/Alert.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import styles from './Alert.module.scss'
|
||||||
|
|
||||||
|
export function getTransactionMessage(transactionHash?: string) {
|
||||||
|
return {
|
||||||
|
transaction: `<a href="https://etherscan.io/tx/${transactionHash}" target="_blank">See your transaction on etherscan.io.</a>`,
|
||||||
|
waitingForUser: 'Waiting for your confirmation',
|
||||||
|
waitingConfirmation: 'Waiting for network confirmation, hang on',
|
||||||
|
success: 'Confirmed. You are awesome, thanks!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const constructMessage = (
|
||||||
|
transactionHash: string,
|
||||||
|
message?: { text?: string }
|
||||||
|
) =>
|
||||||
|
transactionHash
|
||||||
|
? message &&
|
||||||
|
message.text +
|
||||||
|
'<br />' +
|
||||||
|
getTransactionMessage(transactionHash).transaction
|
||||||
|
: message && message.text
|
||||||
|
|
||||||
|
const classes = (status: string) =>
|
||||||
|
status === 'success'
|
||||||
|
? styles.success
|
||||||
|
: status === 'error'
|
||||||
|
? styles.error
|
||||||
|
: styles.alert
|
||||||
|
|
||||||
|
export default function Alert({
|
||||||
|
transactionHash,
|
||||||
|
message
|
||||||
|
}: {
|
||||||
|
transactionHash: string
|
||||||
|
message?: { text?: string; status?: string }
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classes(message.status)}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `${constructMessage(transactionHash, message)}`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -1,49 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import styles from './Alerts.module.scss'
|
|
||||||
|
|
||||||
export const alertMessages = (
|
|
||||||
networkName?: string,
|
|
||||||
transactionHash?: string
|
|
||||||
) => ({
|
|
||||||
noAccount:
|
|
||||||
'Web3 detected, but no account. Are you logged into your MetaMask account?',
|
|
||||||
noCorrectNetwork: `Please connect to <strong>Main</strong> network. You are on <strong>${networkName}</strong> right now.`,
|
|
||||||
noWeb3:
|
|
||||||
'No Web3 detected. Install <a href="https://metamask.io">MetaMask</a> or <a href="https://brave.com">Brave</a>.',
|
|
||||||
transaction: `<a href="https://etherscan.io/tx/${transactionHash}" target="_blank">See your transaction on etherscan.io.</a>`,
|
|
||||||
waitingForUser: 'Waiting for your confirmation',
|
|
||||||
waitingConfirmation: 'Waiting for network confirmation, hang on',
|
|
||||||
success: 'Confirmed. You are awesome, thanks!'
|
|
||||||
})
|
|
||||||
|
|
||||||
interface AlertProps {
|
|
||||||
transactionHash: string
|
|
||||||
message?: { text?: string; status?: string }
|
|
||||||
}
|
|
||||||
|
|
||||||
const constructMessage = (
|
|
||||||
transactionHash: string,
|
|
||||||
message?: { text?: string }
|
|
||||||
) =>
|
|
||||||
transactionHash
|
|
||||||
? message &&
|
|
||||||
message.text + '<br />' + alertMessages(null, transactionHash).transaction
|
|
||||||
: message && message.text
|
|
||||||
|
|
||||||
const classes = (status: string) =>
|
|
||||||
status === 'success'
|
|
||||||
? styles.success
|
|
||||||
: status === 'error'
|
|
||||||
? styles.error
|
|
||||||
: styles.alert
|
|
||||||
|
|
||||||
export default function Alerts({ transactionHash, message }: AlertProps) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classes(message.status)}
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: `${constructMessage(transactionHash, message)}`
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
@ -3,7 +3,10 @@
|
|||||||
.conversion {
|
.conversion {
|
||||||
font-size: $font-size-mini;
|
font-size: $font-size-mini;
|
||||||
color: $brand-grey-light;
|
color: $brand-grey-light;
|
||||||
text-align: center;
|
text-align: left;
|
||||||
|
margin-top: $spacer / 4;
|
||||||
|
margin-left: $spacer * 1.4;
|
||||||
|
animation: fadeIn 0.5s 0.8s ease-out backwards;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
margin-left: $spacer / 2;
|
margin-left: $spacer / 2;
|
||||||
|
@ -1,45 +1,44 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { getFiat } from './utils'
|
|
||||||
import styles from './Conversion.module.scss'
|
import styles from './Conversion.module.scss'
|
||||||
|
|
||||||
export default class Conversion extends PureComponent<
|
export async function getFiat(amount: number) {
|
||||||
{ amount: number },
|
const url = 'https://api.coinmarketcap.com/v1/ticker/ethereum/?convert=EUR'
|
||||||
{ euro: string; dollar: string }
|
const response = await fetch(url)
|
||||||
> {
|
if (!response.ok) console.error(response.statusText)
|
||||||
state = {
|
const data = await response.json()
|
||||||
|
/* eslint-disable @typescript-eslint/camelcase */
|
||||||
|
const { price_usd, price_eur } = data[0]
|
||||||
|
const dollar = (amount * price_usd).toFixed(2)
|
||||||
|
const euro = (amount * price_eur).toFixed(2)
|
||||||
|
/* eslint-enable @typescript-eslint/camelcase */
|
||||||
|
|
||||||
|
return { dollar, euro }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Conversion({ amount }: { amount: number }) {
|
||||||
|
const [conversion, setConversion] = useState({
|
||||||
euro: '0.00',
|
euro: '0.00',
|
||||||
dollar: '0.00'
|
dollar: '0.00'
|
||||||
}
|
})
|
||||||
|
const { dollar, euro } = conversion
|
||||||
|
|
||||||
componentDidMount() {
|
async function getFiatResponse() {
|
||||||
this.getFiatResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: any) {
|
|
||||||
const { amount } = this.props
|
|
||||||
|
|
||||||
if (amount !== prevProps.amount) {
|
|
||||||
this.getFiatResponse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFiatResponse() {
|
|
||||||
try {
|
try {
|
||||||
const { dollar, euro } = await getFiat(this.props.amount)
|
const { dollar, euro } = await getFiat(amount)
|
||||||
this.setState({ euro, dollar })
|
setConversion({ euro, dollar })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.message)
|
console.error(error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
useEffect(() => {
|
||||||
const { dollar, euro } = this.state
|
getFiatResponse()
|
||||||
|
}, [amount])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.conversion}>
|
<div className={styles.conversion}>
|
||||||
<span>{dollar !== '0.00' && `= $ ${dollar}`}</span>
|
<span>{dollar !== '0.00' && `= $ ${dollar}`}</span>
|
||||||
<span>{euro !== '0.00' && `= € ${euro}`}</span>
|
<span>{euro !== '0.00' && `= € ${euro}`}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -88,15 +88,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoline {
|
|
||||||
flex-basis: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: $spacer / 4;
|
|
||||||
animation: fadeIn 0.5s 0.8s ease-out backwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
composes: message from './index.module.scss';
|
composes: message from './index.module.scss';
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,44 @@
|
|||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import Input from '../../atoms/Input'
|
import Input from '../../atoms/Input'
|
||||||
import Account from './Account'
|
import Account from './Account'
|
||||||
import Conversion from './Conversion'
|
import Conversion from './Conversion'
|
||||||
import styles from './InputGroup.module.scss'
|
import styles from './InputGroup.module.scss'
|
||||||
|
|
||||||
export default function InputGroup({
|
export default function InputGroup({
|
||||||
amount,
|
sendTransaction
|
||||||
onAmountChange,
|
|
||||||
sendTransaction,
|
|
||||||
selectedAccount
|
|
||||||
}: {
|
}: {
|
||||||
amount: number
|
sendTransaction(amount: number): void
|
||||||
onAmountChange(target: any): void
|
|
||||||
sendTransaction(): void
|
|
||||||
selectedAccount?: string | null
|
|
||||||
}) {
|
}) {
|
||||||
|
const [amount, setAmount] = useState(0.03)
|
||||||
|
|
||||||
|
const onAmountChange = ({ target }: { target: any }) => {
|
||||||
|
setAmount(target.value)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.inputGroup}>
|
<div>
|
||||||
<div className={styles.input}>
|
<Account />
|
||||||
<Input
|
<div className={styles.inputGroup}>
|
||||||
type="number"
|
<div className={styles.input}>
|
||||||
value={amount}
|
<Input
|
||||||
onChange={onAmountChange}
|
type="number"
|
||||||
min="0"
|
value={amount}
|
||||||
step="0.01"
|
onChange={onAmountChange}
|
||||||
/>
|
min="0"
|
||||||
<div className={styles.currency}>
|
step="0.01"
|
||||||
<span>ETH</span>
|
/>
|
||||||
|
<div className={styles.currency}>
|
||||||
|
<span>ETH</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => sendTransaction(amount)}
|
||||||
|
>
|
||||||
|
Make it rain
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-primary" onClick={() => sendTransaction()}>
|
<Conversion amount={amount} />
|
||||||
Make it rain
|
|
||||||
</button>
|
|
||||||
<div className={styles.infoline}>
|
|
||||||
<Conversion amount={amount} />
|
|
||||||
{selectedAccount && <Account account={selectedAccount} />}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
import { Connectors } from 'web3-react'
|
|
||||||
// import TrezorApi from 'trezor-connect'
|
|
||||||
// import WalletConnectApi from '@walletconnect/web3-subprovider'
|
|
||||||
// import FortmaticApi from 'fortmatic'
|
|
||||||
// import PortisApi from '@portis/web3'
|
|
||||||
|
|
||||||
const {
|
|
||||||
InjectedConnector
|
|
||||||
// NetworkOnlyConnector,
|
|
||||||
// TrezorConnector,
|
|
||||||
// LedgerConnector,
|
|
||||||
// WalletConnectConnector,
|
|
||||||
// FortmaticConnector,
|
|
||||||
// PortisConnector
|
|
||||||
} = Connectors
|
|
||||||
|
|
||||||
// const supportedNetworkURLs = {
|
|
||||||
// 1: 'https://mainnet.infura.io/v3/60ab76e16df54c808e50a79975b4779f',
|
|
||||||
// 4: 'https://rinkeby.infura.io/v3/60ab76e16df54c808e50a79975b4779f'
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const defaultNetwork = 1
|
|
||||||
|
|
||||||
const MetaMask = new InjectedConnector({
|
|
||||||
supportedNetworks: [1]
|
|
||||||
})
|
|
||||||
|
|
||||||
// const Network = new NetworkOnlyConnector({
|
|
||||||
// providerURL: supportedNetworkURLs[1]
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const Trezor = new TrezorConnector({
|
|
||||||
// api: TrezorApi,
|
|
||||||
// supportedNetworkURLs,
|
|
||||||
// defaultNetwork,
|
|
||||||
// manifestEmail: 'noahwz@gmail.com',
|
|
||||||
// manifestAppUrl: 'https://codesandbox.io/s/6v5nrq2nqw'
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const Ledger = new LedgerConnector({
|
|
||||||
// supportedNetworkURLs,
|
|
||||||
// defaultNetwork
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const WalletConnect = new WalletConnectConnector({
|
|
||||||
// api: WalletConnectApi,
|
|
||||||
// bridge: 'https://bridge.walletconnect.org',
|
|
||||||
// supportedNetworkURLs,
|
|
||||||
// defaultNetwork
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const Fortmatic = new FortmaticConnector({
|
|
||||||
// api: FortmaticApi,
|
|
||||||
// apiKey: 'pk_live_F95FEECB1BE324B5',
|
|
||||||
// logoutOnDeactivation: false
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const Portis = new PortisConnector({
|
|
||||||
// api: PortisApi,
|
|
||||||
// dAppId: '211b48db-e8cc-4b68-82ad-bf781727ea9e',
|
|
||||||
// network: 'mainnet'
|
|
||||||
// })
|
|
||||||
|
|
||||||
export default {
|
|
||||||
MetaMask
|
|
||||||
// Network,
|
|
||||||
// Trezor,
|
|
||||||
// Ledger,
|
|
||||||
// WalletConnect,
|
|
||||||
// Fortmatic,
|
|
||||||
// Portis
|
|
||||||
}
|
|
@ -1,38 +1,39 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { useWeb3Context } from 'web3-react'
|
import useWeb3, { connectors, getErrorMessage } from '../../../hooks/use-web3'
|
||||||
import InputGroup from './InputGroup'
|
import InputGroup from './InputGroup'
|
||||||
import Alerts, { alertMessages } from './Alerts'
|
import Alert, { getTransactionMessage } from './Alert'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
export default function Web3Donation({ address }: { address: string }) {
|
export default function Web3Donation({ address }: { address: string }) {
|
||||||
const {
|
const {
|
||||||
setFirstValidConnector,
|
connector,
|
||||||
error,
|
|
||||||
library,
|
library,
|
||||||
|
chainId,
|
||||||
|
account,
|
||||||
|
activate,
|
||||||
active,
|
active,
|
||||||
account
|
error
|
||||||
} = useWeb3Context()
|
} = useWeb3()
|
||||||
const [amount, setAmount] = useState(0.03)
|
|
||||||
const [message, setMessage] = useState()
|
const [message, setMessage] = useState()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFirstValidConnector(['MetaMask'])
|
setMessage(undefined)
|
||||||
}, [])
|
|
||||||
|
|
||||||
error &&
|
error &&
|
||||||
setMessage({
|
setMessage({
|
||||||
status: 'error',
|
status: 'error',
|
||||||
text: error.message
|
text: getErrorMessage(error, chainId)
|
||||||
})
|
})
|
||||||
|
}, [connector, account, library, chainId, active, error])
|
||||||
|
|
||||||
const [transactionHash, setTransactionHash] = useState(undefined)
|
const [transactionHash, setTransactionHash] = useState(undefined)
|
||||||
|
|
||||||
async function sendTransaction() {
|
async function sendTransaction(amount: number) {
|
||||||
const signer = library.getSigner()
|
const signer = library.getSigner()
|
||||||
|
|
||||||
setMessage({
|
setMessage({
|
||||||
status: 'loading',
|
status: 'loading',
|
||||||
text: alertMessages().waitingForUser
|
text: getTransactionMessage().waitingForUser
|
||||||
})
|
})
|
||||||
|
|
||||||
const tx = await signer.sendTransaction({
|
const tx = await signer.sendTransaction({
|
||||||
@ -42,68 +43,26 @@ export default function Web3Donation({ address }: { address: string }) {
|
|||||||
setTransactionHash(tx.hash)
|
setTransactionHash(tx.hash)
|
||||||
setMessage({
|
setMessage({
|
||||||
status: 'loading',
|
status: 'loading',
|
||||||
text: alertMessages().waitingConfirmation
|
text: getTransactionMessage().waitingConfirmation
|
||||||
})
|
})
|
||||||
|
|
||||||
// setMessage({
|
// setMessage({
|
||||||
// status: 'success',
|
// status: 'success',
|
||||||
// text: alertMessages().success
|
// text: getTransactionMessage().success
|
||||||
// })
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
const onAmountChange = ({ target }: { target: any }) => {
|
|
||||||
setAmount(target.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.web3}>
|
<div className={styles.web3}>
|
||||||
{active && !message ? (
|
{!active && !message ? (
|
||||||
<InputGroup
|
<button className="link" onClick={() => activate(connectors.MetaMask)}>
|
||||||
selectedAccount={account}
|
Activate Web3
|
||||||
amount={amount}
|
</button>
|
||||||
onAmountChange={onAmountChange}
|
) : library && account && !message ? (
|
||||||
sendTransaction={sendTransaction}
|
<InputGroup sendTransaction={sendTransaction} />
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
message && (
|
message && <Alert message={message} transactionHash={transactionHash} />
|
||||||
<Alerts message={message} transactionHash={transactionHash} />
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendTransaction = () => {
|
|
||||||
// const { web3 } = this
|
|
||||||
|
|
||||||
// this.setState({
|
|
||||||
// inTransaction: true,
|
|
||||||
// message: { text: alertMessages().waitingForUser }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// web3.eth
|
|
||||||
// .sendTransaction({
|
|
||||||
// from: this.state.selectedAccount,
|
|
||||||
// to: this.props.address,
|
|
||||||
// value: this.state.amount * 1e18 // ETH -> Wei
|
|
||||||
// })
|
|
||||||
// .once('transactionHash', transactionHash => {
|
|
||||||
// this.setState({
|
|
||||||
// transactionHash,
|
|
||||||
// message: { text: alertMessages().waitingConfirmation }
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// .on('error', error =>
|
|
||||||
// this.setState({
|
|
||||||
// message: { status: 'error', text: error.message }
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
// .then(() => {
|
|
||||||
// this.setState({
|
|
||||||
// message: {
|
|
||||||
// status: 'success',
|
|
||||||
// text: alertMessages().success
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
export const getNetworkName = (netId: number) => {
|
|
||||||
let networkName
|
|
||||||
|
|
||||||
switch (netId) {
|
|
||||||
case 1:
|
|
||||||
networkName = 'Main'
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
networkName = 'Morden'
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
networkName = 'Ropsten'
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
networkName = 'Rinkeby'
|
|
||||||
break
|
|
||||||
case 42:
|
|
||||||
networkName = 'Kovan'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
networkName = 'Private'
|
|
||||||
}
|
|
||||||
|
|
||||||
return networkName
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getFiat = async (amount: number) => {
|
|
||||||
const url = 'https://api.coinmarketcap.com/v1/ticker/ethereum/?convert=EUR'
|
|
||||||
const response = await fetch(url)
|
|
||||||
if (!response.ok) console.error(response.statusText)
|
|
||||||
const data = await response.json()
|
|
||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
const { price_usd, price_eur } = data[0]
|
|
||||||
const dollar = (amount * price_usd).toFixed(2)
|
|
||||||
const euro = (amount * price_eur).toFixed(2)
|
|
||||||
/* eslint-enable @typescript-eslint/camelcase */
|
|
||||||
|
|
||||||
return { dollar, euro }
|
|
||||||
}
|
|
75
src/hooks/use-web3/connectors.tsx
Normal file
75
src/hooks/use-web3/connectors.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { InjectedConnector } from '@web3-react/injected-connector'
|
||||||
|
// import { NetworkConnector } from '@web3-react/network-connector'
|
||||||
|
// import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
|
||||||
|
// import { WalletLinkConnector } from '@web3-react/walletlink-connector'
|
||||||
|
// import { LedgerConnector } from '@web3-react/ledger-connector'
|
||||||
|
// import { TrezorConnector } from '@web3-react/trezor-connector'
|
||||||
|
// import { FrameConnector } from '@web3-react/frame-connector'
|
||||||
|
// import { AuthereumConnector } from '@web3-react/authereum-connector'
|
||||||
|
// import { FortmaticConnector } from '@web3-react/fortmatic-connector'
|
||||||
|
// import { PortisConnector } from '@web3-react/portis-connector'
|
||||||
|
// import { SquarelinkConnector } from '@web3-react/squarelink-connector'
|
||||||
|
// import { TorusConnector } from '@web3-react/torus-connector'
|
||||||
|
|
||||||
|
// const POLLING_INTERVAL = 8000
|
||||||
|
// const RPC_URLS: { [chainId: number]: string } = {
|
||||||
|
// 1: process.env.RPC_URL_1 as string,
|
||||||
|
// 4: process.env.RPC_URL_4 as string
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const MetaMask = new InjectedConnector({
|
||||||
|
supportedChainIds: [1]
|
||||||
|
})
|
||||||
|
|
||||||
|
// export const network = new NetworkConnector({
|
||||||
|
// urls: { 1: RPC_URLS[1], 4: RPC_URLS[4] },
|
||||||
|
// defaultChainId: 1,
|
||||||
|
// pollingInterval: POLLING_INTERVAL
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const walletconnect = new WalletConnectConnector({
|
||||||
|
// rpc: { 1: RPC_URLS[1] },
|
||||||
|
// bridge: 'https://bridge.walletconnect.org',
|
||||||
|
// qrcode: true,
|
||||||
|
// pollingInterval: POLLING_INTERVAL
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const walletlink = new WalletLinkConnector({
|
||||||
|
// url: RPC_URLS[1],
|
||||||
|
// appName: 'web3-react example'
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const ledger = new LedgerConnector({
|
||||||
|
// chainId: 1,
|
||||||
|
// url: RPC_URLS[1],
|
||||||
|
// pollingInterval: POLLING_INTERVAL
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const trezor = new TrezorConnector({
|
||||||
|
// chainId: 1,
|
||||||
|
// url: RPC_URLS[1],
|
||||||
|
// pollingInterval: POLLING_INTERVAL,
|
||||||
|
// manifestEmail: 'dummy@abc.xyz',
|
||||||
|
// manifestAppUrl: 'http://localhost:1234'
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const frame = new FrameConnector({ supportedChainIds: [1] })
|
||||||
|
|
||||||
|
// export const authereum = new AuthereumConnector({ chainId: 42 })
|
||||||
|
|
||||||
|
// export const fortmatic = new FortmaticConnector({
|
||||||
|
// apiKey: process.env.FORTMATIC_API_KEY as string,
|
||||||
|
// chainId: 4
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const portis = new PortisConnector({
|
||||||
|
// dAppId: process.env.PORTIS_DAPP_ID as string,
|
||||||
|
// networks: [1, 100]
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const squarelink = new SquarelinkConnector({
|
||||||
|
// clientId: process.env.SQUARELINK_CLIENT_ID as string,
|
||||||
|
// networks: [1, 100]
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const torus = new TorusConnector({ chainId: 1 })
|
99
src/hooks/use-web3/index.tsx
Normal file
99
src/hooks/use-web3/index.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { useWeb3React } from '@web3-react/core'
|
||||||
|
import { Web3ReactContextInterface } from '@web3-react/core/dist/types'
|
||||||
|
import * as connectors from './connectors'
|
||||||
|
import {
|
||||||
|
getLibrary,
|
||||||
|
getNetworkName,
|
||||||
|
getErrorMessage,
|
||||||
|
getBalance
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
export { connectors, getLibrary, getNetworkName, getErrorMessage, getBalance }
|
||||||
|
|
||||||
|
export function useEagerConnect() {
|
||||||
|
const { MetaMask } = connectors
|
||||||
|
const { activate, active } = useWeb3React()
|
||||||
|
const [tried, setTried] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
MetaMask.isAuthorized().then(isAuthorized => {
|
||||||
|
if (isAuthorized) {
|
||||||
|
activate(MetaMask, undefined, true).catch(() => {
|
||||||
|
setTried(true)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setTried(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// if the connection worked, wait until we get confirmation of that to flip the flag
|
||||||
|
useEffect(() => {
|
||||||
|
if (!tried && active) {
|
||||||
|
setTried(true)
|
||||||
|
}
|
||||||
|
}, [tried, active])
|
||||||
|
|
||||||
|
return tried
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useInactiveListener(suppress = false) {
|
||||||
|
const { active, error, activate } = useWeb3React()
|
||||||
|
const { MetaMask } = connectors
|
||||||
|
|
||||||
|
useEffect((): any => {
|
||||||
|
const { ethereum } = window as any
|
||||||
|
if (ethereum && !active && !error && !suppress) {
|
||||||
|
const handleConnect = () => {
|
||||||
|
console.log("Handling 'connect' event")
|
||||||
|
activate(MetaMask)
|
||||||
|
}
|
||||||
|
const handleChainChanged = (chainId: string | number) => {
|
||||||
|
console.log("Handling 'chainChanged' event with payload", chainId)
|
||||||
|
activate(MetaMask)
|
||||||
|
}
|
||||||
|
const handleNetworkChanged = (networkId: string | number) => {
|
||||||
|
console.log("Handling 'networkChanged' event with payload", networkId)
|
||||||
|
activate(MetaMask)
|
||||||
|
}
|
||||||
|
const handleAccountsChanged = (accounts: string[]) => {
|
||||||
|
console.log("Handling 'accountsChanged' event with payload", accounts)
|
||||||
|
if (accounts.length > 0) {
|
||||||
|
activate(MetaMask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ethereum.on('connect', handleConnect)
|
||||||
|
ethereum.on('chainChanged', handleChainChanged)
|
||||||
|
ethereum.on('networkChanged', handleNetworkChanged)
|
||||||
|
ethereum.on('accountsChanged', handleAccountsChanged)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ethereum.removeListener('networkChanged', handleNetworkChanged)
|
||||||
|
ethereum.removeListener('accountsChanged', handleAccountsChanged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [active, error, suppress, activate])
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useWeb3(): Web3ReactContextInterface {
|
||||||
|
const context = useWeb3React()
|
||||||
|
|
||||||
|
// handle logic to recognize the connector currently being activated
|
||||||
|
const [activatingConnector, setActivatingConnector] = useState()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activatingConnector && activatingConnector === context.connector) {
|
||||||
|
setActivatingConnector(undefined)
|
||||||
|
}
|
||||||
|
}, [activatingConnector, context.connector])
|
||||||
|
|
||||||
|
// handle logic to eagerly connect to the injected ethereum provider, if it exists and has granted access already
|
||||||
|
const triedEager = useEagerConnect()
|
||||||
|
|
||||||
|
// handle logic to connect in reaction to certain events on the injected ethereum provider, if it exists
|
||||||
|
useInactiveListener(!triedEager || !!activatingConnector)
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
83
src/hooks/use-web3/utils.tsx
Normal file
83
src/hooks/use-web3/utils.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { UnsupportedChainIdError } from '@web3-react/core'
|
||||||
|
import {
|
||||||
|
NoEthereumProviderError,
|
||||||
|
UserRejectedRequestError
|
||||||
|
} from '@web3-react/injected-connector'
|
||||||
|
import { Web3Provider } from '@ethersproject/providers'
|
||||||
|
|
||||||
|
export function getLibrary(provider: any): Web3Provider {
|
||||||
|
const library = new Web3Provider(provider)
|
||||||
|
library.pollingInterval = 10000
|
||||||
|
return library
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNetworkName(netId: number) {
|
||||||
|
let networkName
|
||||||
|
|
||||||
|
switch (netId) {
|
||||||
|
case 1:
|
||||||
|
networkName = 'Main'
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
networkName = 'Morden'
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
networkName = 'Ropsten'
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
networkName = 'Rinkeby'
|
||||||
|
break
|
||||||
|
case 42:
|
||||||
|
networkName = 'Kovan'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
networkName = 'Private'
|
||||||
|
}
|
||||||
|
|
||||||
|
return networkName
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getErrorMessage(error: Error, chainId: number) {
|
||||||
|
if (error instanceof NoEthereumProviderError) {
|
||||||
|
return 'No Ethereum browser extension detected, install <a href="https://metamask.io">MetaMask</a> or <a href="https://brave.com">Brave</a>.'
|
||||||
|
} else if (error instanceof UnsupportedChainIdError) {
|
||||||
|
const networkName = getNetworkName(chainId)
|
||||||
|
return `Please connect to <strong>Main</strong> network. You are on <strong>${networkName}</strong> right now.`
|
||||||
|
} else if (error instanceof UserRejectedRequestError) {
|
||||||
|
return 'Please authorize this website to access your Ethereum account.'
|
||||||
|
} else {
|
||||||
|
console.error(error)
|
||||||
|
return 'An unknown error occurred. Check the console for more details.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBalance(account: string, library: any) {
|
||||||
|
const [ethBalance, setEthBalance] = useState()
|
||||||
|
|
||||||
|
useEffect((): any => {
|
||||||
|
if (library && account) {
|
||||||
|
let stale = false
|
||||||
|
|
||||||
|
library
|
||||||
|
.getBalance(account)
|
||||||
|
.then((balance: any) => {
|
||||||
|
if (!stale) {
|
||||||
|
setEthBalance(balance)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (!stale) {
|
||||||
|
setEthBalance(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
stale = true
|
||||||
|
setEthBalance(undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [library, account])
|
||||||
|
|
||||||
|
return ethBalance
|
||||||
|
}
|
@ -2,15 +2,15 @@ import React from 'react'
|
|||||||
import loadable from '@loadable/component'
|
import loadable from '@loadable/component'
|
||||||
import shortid from 'shortid'
|
import shortid from 'shortid'
|
||||||
import Helmet from 'react-helmet'
|
import Helmet from 'react-helmet'
|
||||||
|
import { Web3ReactProvider } from '@web3-react/core'
|
||||||
import { Author } from '../@types/Site'
|
import { Author } from '../@types/Site'
|
||||||
import { useSiteMetadata } from '../hooks/use-site-metadata'
|
import { useSiteMetadata } from '../hooks/use-site-metadata'
|
||||||
|
import { getLibrary } from '../hooks/use-web3'
|
||||||
import Qr from '../components/atoms/Qr'
|
import Qr from '../components/atoms/Qr'
|
||||||
import Icon from '../components/atoms/Icon'
|
import Icon from '../components/atoms/Icon'
|
||||||
import connectors from '../components/molecules/Web3Donation/connectors'
|
|
||||||
import styles from './thanks.module.scss'
|
import styles from './thanks.module.scss'
|
||||||
|
|
||||||
const Web3Provider = loadable(() => import('web3-react'))
|
const LazyWeb3Donation = loadable(() =>
|
||||||
const Web3Donation = loadable(() =>
|
|
||||||
import('../components/molecules/Web3Donation')
|
import('../components/molecules/Web3Donation')
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ export default function Thanks() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Web3ReactProvider getLibrary={getLibrary}>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>Say thanks</title>
|
<title>Say thanks</title>
|
||||||
<meta name="robots" content="noindex,nofollow" />
|
<meta name="robots" content="noindex,nofollow" />
|
||||||
@ -47,33 +47,30 @@ export default function Thanks() {
|
|||||||
<header>
|
<header>
|
||||||
<h1 className={styles.title}>Say Thanks</h1>
|
<h1 className={styles.title}>Say Thanks</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<Web3Provider connectors={connectors} libraryName={'ethers.js'}>
|
|
||||||
<div className={styles.web3}>
|
|
||||||
<header>
|
|
||||||
<h4>Web3 Wallet</h4>
|
|
||||||
<p>Send Ether with MetaMask or Brave.</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<Web3Donation fallback={<div className={styles.loading}>Loading...</div>} address={author.ether} />
|
<div className={styles.web3}>
|
||||||
</div>
|
<header>
|
||||||
|
<h4>Web3 Wallet</h4>
|
||||||
|
<p>Send Ether with MetaMask or Brave.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
<div className={styles.coins}>
|
<LazyWeb3Donation
|
||||||
<header>
|
fallback={<div className={styles.loading}>Loading...</div>}
|
||||||
<h4>Any other wallets</h4>
|
address={author.ether}
|
||||||
<p>Send Bitcoin or Ether from any wallet.</p>
|
/>
|
||||||
</header>
|
</div>
|
||||||
|
|
||||||
{coins.map((address: string) => (
|
<div className={styles.coins}>
|
||||||
<Coin
|
<header>
|
||||||
key={shortid.generate()}
|
<h4>Any other wallets</h4>
|
||||||
address={address}
|
<p>Send Bitcoin or Ether from any wallet.</p>
|
||||||
author={author}
|
</header>
|
||||||
/>
|
|
||||||
))}
|
{coins.map((address: string) => (
|
||||||
</div>
|
<Coin key={shortid.generate()} address={address} author={author} />
|
||||||
</Web3Provider>
|
))}
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</>
|
</Web3ReactProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import loadable from '@loadable/component'
|
|
||||||
import shortid from 'shortid'
|
|
||||||
import Helmet from 'react-helmet'
|
|
||||||
import { Author } from '../@types/Site'
|
|
||||||
import { useSiteMetadata } from '../hooks/use-site-metadata'
|
|
||||||
import Qr from '../components/atoms/Qr'
|
|
||||||
import Icon from '../components/atoms/Icon'
|
|
||||||
import connectors from '../components/molecules/Web3Donation/connectors'
|
|
||||||
import styles from './thanks.module.scss'
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
const Web3Donation = loadable(() =>
|
|
||||||
import('../components/molecules/Web3Donation')
|
|
||||||
)
|
|
||||||
=======
|
|
||||||
const Web3Provider = lazy(() => import('web3-react'))
|
|
||||||
const Web3Donation = lazy(() => import('../components/molecules/Web3Donation'))
|
|
||||||
>>>>>>> refactor web3
|
|
||||||
|
|
||||||
const Coin = ({ address, author }: { address: string; author: Author }) => (
|
|
||||||
<div className={styles.coin}>
|
|
||||||
<Qr title={address} address={(author as any)[address]} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
const BackButton = () => (
|
|
||||||
<button
|
|
||||||
className={`link ${styles.buttonBack}`}
|
|
||||||
onClick={() => window.history.back()}
|
|
||||||
>
|
|
||||||
<Icon name="ChevronLeft" /> Go Back
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default function Thanks() {
|
|
||||||
const { author } = useSiteMetadata()
|
|
||||||
const coins = Object.keys(author).filter(
|
|
||||||
key => key === 'bitcoin' || key === 'ether'
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Helmet>
|
|
||||||
<title>Say thanks</title>
|
|
||||||
<meta name="robots" content="noindex,nofollow" />
|
|
||||||
</Helmet>
|
|
||||||
|
|
||||||
<article className={styles.thanks}>
|
|
||||||
<BackButton />
|
|
||||||
<header>
|
|
||||||
<h1 className={styles.title}>Say Thanks</h1>
|
|
||||||
</header>
|
|
||||||
<<<<<<< HEAD
|
|
||||||
|
|
||||||
<Web3Donation
|
|
||||||
fallback={<div className={styles.loading}>Loading...</div>}
|
|
||||||
address={author.ether}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={styles.coins}>
|
|
||||||
<header>
|
|
||||||
<h4>Any other wallets</h4>
|
|
||||||
<p>Send Bitcoin or Ether from any wallet.</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{coins.map((address: string) => (
|
|
||||||
<Coin key={shortid.generate()} address={address} author={author} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
=======
|
|
||||||
{!isSSR && (
|
|
||||||
<Suspense fallback={<div className={styles.loading}>Loading...</div>}>
|
|
||||||
<Web3Provider connectors={connectors} libraryName={'ethers.js'}>
|
|
||||||
<div className={styles.web3}>
|
|
||||||
<header>
|
|
||||||
<h4>Web3 Wallet</h4>
|
|
||||||
<p>Send Ether with MetaMask or Brave.</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<Web3Donation address={author.ether} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.coins}>
|
|
||||||
<header>
|
|
||||||
<h4>Any other wallets</h4>
|
|
||||||
<p>Send Bitcoin or Ether from any wallet.</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{coins.map((address: string) => (
|
|
||||||
<Coin
|
|
||||||
key={shortid.generate()}
|
|
||||||
address={address}
|
|
||||||
author={author}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Web3Provider>
|
|
||||||
</Suspense>
|
|
||||||
)}
|
|
||||||
>>>>>>> refactor web3
|
|
||||||
</article>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user