1
0
mirror of https://github.com/kremalicious/blog.git synced 2024-11-15 01:25:28 +01:00

Merge pull request #195 from kremalicious/feature/web3-refactor

web3 refactor
This commit is contained in:
Matthias Kretschmann 2019-11-23 22:44:12 +01:00 committed by GitHub
commit a9c1e30e6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 2158 additions and 1961 deletions

View File

@ -1,39 +0,0 @@
name: Test
on: [push]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['10', '12']
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Cache node modules
uses: actions/cache@v1
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-${{ matrix.node-version }}
- name: Cache Gatsby build output
uses: actions/cache@v1
with:
path: public
key: ${{ runner.os }}-public
- run: npm ci
- run: npm test
- run: npm run build
env:
CI: true
GATSBY_GITHUB_TOKEN: ${{ secrets.GATSBY_GITHUB_TOKEN }}

View File

@ -121,6 +121,12 @@ exports.onPostBuild = async ({ graphql }) => {
// https://github.com/ethereum/web3.js/issues/1105#issuecomment-446039296
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
resolve: {
alias: {
// replace native `scrypt` module with pure js `js-scrypt`
scrypt: 'js-scrypt'
}
},
plugins: [
// ignore these plugins completely
new webpack.IgnorePlugin(/^(?:electron|ws)$/)

2983
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -29,10 +29,15 @@
"not op_mini all"
],
"dependencies": {
"@ethersproject/providers": "^5.0.0-beta.146",
"@ethersproject/units": "^5.0.0-beta.132",
"@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",
"date-fns": "^2.8.1",
"dms2dec": "^1.1.0",
"ethereum-blockies": "github:MyEtherWallet/blockies",
"fast-exif": "^1.0.1",
"feather-icons": "^4.24.1",
"fraction.js": "^4.0.12",
@ -58,36 +63,33 @@
"gatsby-remark-copy-linked-files": "^2.1.30",
"gatsby-remark-images": "^3.1.33",
"gatsby-remark-smartypants": "^2.1.16",
"gatsby-remark-vscode": "^1.3.0",
"gatsby-remark-vscode": "^1.4.0",
"gatsby-source-filesystem": "^2.1.38",
"gatsby-source-graphql": "^2.1.24",
"gatsby-transformer-remark": "^2.6.37",
"gatsby-transformer-sharp": "^2.3.5",
"graphql": "^14.5.8",
"intersection-observer": "^0.7.0",
"load-script": "^1.0.0",
"node-fetch": "^2.6.0",
"pigeon-maps": "^0.14.0",
"pigeon-marker": "^0.3.4",
"react": "^16.12.0",
"react-blockies": "^1.4.1",
"react-clipboard.js": "^2.0.13",
"react-clipboard.js": "^2.0.16",
"react-dom": "^16.12.0",
"react-feather": "^2.0.3",
"react-helmet": "^5.2.1",
"react-modal": "^3.11.1",
"react-pose": "^4.0.9",
"react-pose": "^4.0.10",
"react-qr-svg": "^2.2.1",
"react-transition-group": "^4.3.0",
"remark": "^11.0.1",
"remark": "^11.0.2",
"remark-react": "^6.0.0",
"slugify": "^1.3.6",
"use-dark-mode": "^2.3.1",
"web3": "^1.2.4"
"use-dark-mode": "^2.3.1"
},
"devDependencies": {
"@babel/node": "^7.7.0",
"@babel/preset-env": "^7.7.1",
"@babel/preset-typescript": "^7.7.0",
"@babel/node": "^7.7.4",
"@babel/preset-env": "^7.7.4",
"@babel/preset-typescript": "^7.7.4",
"@svgr/webpack": "^4.3.3",
"@testing-library/jest-dom": "^4.2.3",
"@testing-library/react": "^9.3.2",
@ -95,11 +97,11 @@
"@types/jest": "^24.0.21",
"@types/loadable__component": "^5.10.0",
"@types/lunr": "^2.3.2",
"@types/node": "^12.12.11",
"@types/react": "^16.9.11",
"@types/node": "^12.12.12",
"@types/node-fetch": "^2.5.4",
"@types/react": "^16.9.12",
"@types/react-dom": "^16.9.3",
"@types/react-helmet": "^5.0.14",
"@types/react-modal": "^3.10.0",
"@types/react-transition-group": "^4.2.3",
"@types/shortid": "0.0.29",
"@typescript-eslint/eslint-plugin": "^2.8.0",
@ -107,7 +109,7 @@
"@welldone-software/why-did-you-render": "^3.3.9",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"eslint": "^6.6.0",
"eslint": "^6.7.0",
"eslint-config-prettier": "^6.7.0",
"eslint-loader": "^3.0.2",
"eslint-plugin-graphql": "^3.1.0",

View File

@ -2,3 +2,7 @@ declare module 'pigeon-maps'
declare module 'pigeon-marker'
declare module 'react-blockies'
declare module 'remark-react'
declare module 'ethereum-blockies' {
export function toDataUrl(address: string): string
}

View File

@ -28,7 +28,6 @@ function FeaturedPure({
)
}
export default function Featured() {
const query = graphql`
query {
allMarkdownRemark(
@ -55,6 +54,7 @@ export default function Featured() {
}
`
export default function Featured() {
const data = useStaticQuery(query)
return <FeaturedPure data={data} />
}

View File

@ -1,19 +1,24 @@
@import 'variables';
@import 'mixins';
.account {
font-size: $font-size-mini;
.accountWrap {
font-size: $font-size-small;
color: $brand-grey-light;
max-width: 8rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: $spacer;
display: flex;
justify-content: space-between;
}
.identicon {
.blockies {
width: 1.2rem;
height: 1.2rem;
border-radius: 50%;
overflow: hidden;
display: inline-block;
vertical-align: middle;
margin-right: $spacer / 8;
margin-right: $spacer / 4;
}
.balance {
margin-left: $spacer;
}

View File

@ -0,0 +1,14 @@
import React from 'react'
import { render, waitForElement } from '@testing-library/react'
import Account from './Account'
describe('Account', () => {
it('renders without crashing', async () => {
const { container } = render(<Account />)
const lazyElement = await waitForElement(() =>
container.querySelector('.balance')
)
expect(lazyElement).toBeInTheDocument()
})
})

View File

@ -1,12 +1,27 @@
import React from 'react'
import Blockies from 'react-blockies'
import { toDataUrl } from 'ethereum-blockies'
import { formatEther } from '@ethersproject/units'
import styles from './Account.module.scss'
import useWeb3, { getBalance } from '../../../hooks/use-web3'
const Account = ({ account }: { account: string }) => (
<div className={styles.account} title={account}>
<Blockies seed={account} scale={2} size={8} className={styles.identicon} />
{account}
export default function Account() {
const { library, account } = useWeb3()
const ethBalance = account && getBalance(account, library)
const blockies = account && toDataUrl(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}>
<img className={styles.blockies} src={blockies} alt="Blockies" />
{accountDisplay}
</span>
<span className={styles.balance}>{balanceDisplay}</span>
</div>
)
export default Account
}

View 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 /><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)}`
}}
/>
)
}

View File

@ -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)}`
}}
/>
)
}

View File

@ -3,7 +3,10 @@
.conversion {
font-size: $font-size-mini;
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 {
margin-left: $spacer / 2;

View File

@ -0,0 +1,12 @@
import React from 'react'
import { render, waitForElement } from '@testing-library/react'
import Conversion from './Conversion'
describe('Conversion', () => {
it('renders without crashing', async () => {
const { getByText } = render(<Conversion amount={1} />)
const lazyElement = await waitForElement(() => getByText(/= €/))
expect(lazyElement).toBeInTheDocument()
})
})

View File

@ -1,39 +1,40 @@
import React, { PureComponent } from 'react'
import { getFiat, Logger } from './utils'
import React, { useState, useEffect } from 'react'
import fetch from 'node-fetch'
import styles from './Conversion.module.scss'
export default class Conversion extends PureComponent<
{ amount: number },
{ euro: string; dollar: string }
> {
state = {
export async function getFiat(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 }
}
export default function Conversion({ amount }: { amount: number }) {
const [conversion, setConversion] = useState({
euro: '0.00',
dollar: '0.00'
}
})
const { dollar, euro } = conversion
componentDidMount() {
this.getFiatResponse()
}
componentDidUpdate(prevProps: any) {
const { amount } = this.props
if (amount !== prevProps.amount) {
this.getFiatResponse()
}
}
async getFiatResponse() {
async function getFiatResponse() {
try {
const { dollar, euro } = await getFiat(this.props.amount)
this.setState({ euro, dollar })
const { dollar, euro } = await getFiat(amount)
setConversion({ euro, dollar })
} catch (error) {
Logger.error(error.message)
console.error(error.message)
}
}
render() {
const { dollar, euro } = this.state
useEffect(() => {
getFiatResponse()
}, [amount])
return (
<div className={styles.conversion}>
@ -42,4 +43,3 @@ export default class Conversion extends PureComponent<
</div>
)
}
}

View File

@ -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 {
composes: message from './index.module.scss';
}

View File

@ -1,21 +1,23 @@
import React from 'react'
import React, { useState } from 'react'
import Input from '../../atoms/Input'
import Account from './Account'
import Conversion from './Conversion'
import styles from './InputGroup.module.scss'
export default function InputGroup({
amount,
onAmountChange,
sendTransaction,
selectedAccount
sendTransaction
}: {
amount: number
onAmountChange(target: any): void
sendTransaction(): void
selectedAccount?: string | null
sendTransaction(amount: number): void
}) {
const [amount, setAmount] = useState(0.03)
const onAmountChange = ({ target }: { target: any }) => {
setAmount(target.value)
}
return (
<div>
<Account />
<div className={styles.inputGroup}>
<div className={styles.input}>
<Input
@ -29,13 +31,14 @@ export default function InputGroup({
<span>ETH</span>
</div>
</div>
<button className="btn btn-primary" onClick={sendTransaction}>
<button
className="btn btn-primary"
onClick={() => sendTransaction(amount)}
>
Make it rain
</button>
<div className={styles.infoline}>
<Conversion amount={amount} />
{selectedAccount && <Account account={selectedAccount} />}
</div>
<Conversion amount={amount} />
</div>
)
}

View File

@ -1,20 +1,6 @@
@import 'variables';
.web3 {
width: 100%;
text-align: center;
margin-top: $spacer / 2;
margin-bottom: $spacer;
padding-bottom: $spacer;
small {
color: darken($alert-info, 60%);
margin-top: -($spacer / 2);
display: block;
}
}
.web3Row {
min-height: 77px;
display: flex;
align-items: center;

View File

@ -0,0 +1,26 @@
import React from 'react'
import { render, waitForElement, fireEvent } from '@testing-library/react'
import { Web3ReactProvider } from '@web3-react/core'
import { getLibrary } from '../../../hooks/use-web3'
import Web3Donation from '.'
describe('Web3Donation', () => {
it('renders without crashing', async () => {
const { container, getByText } = render(
<Web3ReactProvider getLibrary={getLibrary}>
<Web3Donation address="xxx" />
</Web3ReactProvider>
)
const lazyElement = await waitForElement(() =>
container.querySelector('button')
)
expect(lazyElement).toBeInTheDocument()
fireEvent.click(lazyElement)
const message = await waitForElement(() =>
getByText(/No Ethereum browser extension detected/)
)
expect(message).toBeInTheDocument()
})
})

View File

@ -1,210 +1,70 @@
import React, { PureComponent } from 'react'
import Web3 from 'web3'
import React, { useState, useEffect } from 'react'
import useWeb3, { connectors, getErrorMessage } from '../../../hooks/use-web3'
import InputGroup from './InputGroup'
import Alerts, { alertMessages } from './Alerts'
import Alert, { getTransactionMessage } from './Alert'
import styles from './index.module.scss'
import { getWeb3, getAccounts, getNetwork } from './utils'
const ONE_SECOND = 1000
const ONE_MINUTE = ONE_SECOND * 60
const correctNetwork = 1
interface Web3DonationState {
netId: number
networkName: string
accounts: string[]
selectedAccount: string
amount: number
transactionHash: string
receipt: string
message: {
status?: string
text?: string
}
inTransaction: boolean
}
export default class Web3Donation extends PureComponent<
{ address: string },
Web3DonationState
> {
state = {
netId: 0,
networkName: '',
accounts: [''],
selectedAccount: '',
amount: 0.01,
transactionHash: '',
receipt: '',
message: {},
inTransaction: false
}
web3: Web3 = null
interval: any = null
networkInterval: any = null
componentDidMount() {
this.initWeb3()
}
componentWillUnmount() {
this.resetAllTheThings()
}
initWeb3 = async () => {
this.setState({ message: { text: 'Checking' } })
try {
this.web3 = await getWeb3()
this.web3
? this.initAllTheTings()
: this.setState({
message: {
status: 'error',
text: alertMessages().noWeb3
}
})
} catch (error) {
this.setState({
message: { status: 'error', text: error }
})
}
}
async initAllTheTings() {
this.fetchAccounts()
this.fetchNetwork()
this.initAccountsPoll()
this.initNetworkPoll()
}
resetAllTheThings() {
clearInterval(this.interval)
clearInterval(this.networkInterval)
}
initAccountsPoll() {
if (!this.interval) {
this.interval = setInterval(this.fetchAccounts, ONE_SECOND * 10)
}
}
initNetworkPoll() {
if (!this.networkInterval) {
this.networkInterval = setInterval(this.fetchNetwork, ONE_MINUTE)
}
}
fetchNetwork = async () => {
const { web3 } = this
const { netId, networkName } = await getNetwork(web3)
if (netId === correctNetwork) {
this.setState({ netId, networkName })
} else {
this.setState({
message: {
status: 'error',
text: alertMessages(networkName).noCorrectNetwork
}
})
}
}
fetchAccounts = async () => {
const { web3 } = this
const accounts = await getAccounts(web3)
if (accounts[0]) {
this.setState({
accounts,
selectedAccount: accounts[0].toLowerCase()
})
} else {
this.setState({
message: {
status: 'error',
text: alertMessages().noAccount
}
})
}
}
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
}
})
})
}
onAmountChange = ({ target }: { target: any }) => {
this.setState({ amount: target.value })
}
render() {
export default function Web3Donation({ address }: { address: string }) {
const {
selectedAccount,
amount,
transactionHash,
message,
inTransaction
} = this.state
connector,
library,
chainId,
account,
activate,
active,
error
} = useWeb3()
const [message, setMessage] = useState()
useEffect(() => {
setMessage(undefined)
error &&
setMessage({
status: 'error',
text: getErrorMessage(error, chainId)
})
}, [connector, account, library, chainId, active, error])
const [transactionHash, setTransactionHash] = useState(undefined)
async function sendTransaction(amount: number) {
const signer = library.getSigner()
setMessage({
status: 'loading',
text: getTransactionMessage().waitingForUser
})
const tx = await signer.sendTransaction({
to: address,
value: amount * 1e18 // ETH -> Wei
})
setTransactionHash(tx.hash)
setMessage({
status: 'loading',
text: getTransactionMessage().waitingConfirmation
})
await tx.wait()
setMessage({
status: 'success',
text: getTransactionMessage().success
})
}
return (
<div className={styles.web3}>
<header>
<h4>Web3 Wallet</h4>
<p>Send Ether with MetaMask or Brave.</p>
</header>
<div className={styles.web3Row}>
{selectedAccount &&
this.state.netId === correctNetwork &&
!inTransaction ? (
<InputGroup
selectedAccount={selectedAccount}
amount={amount}
onAmountChange={this.onAmountChange}
sendTransaction={this.sendTransaction}
/>
{!active && !message ? (
<button className="link" onClick={() => activate(connectors.MetaMask)}>
Activate Web3
</button>
) : library && account && !message ? (
<InputGroup sendTransaction={sendTransaction} />
) : (
message && (
<Alerts message={message} transactionHash={transactionHash} />
)
message && <Alert message={message} transactionHash={transactionHash} />
)}
</div>
</div>
)
}
}

View File

@ -1,113 +0,0 @@
import Web3 from 'web3'
declare global {
interface Window {
ethereum: any
web3: Web3
}
interface Console {
[key: string]: any
}
}
export class Logger {
static dispatch(verb: any, ...args: any) {
console[verb](...args)
}
static log(...args: any) {
Logger.dispatch('log', ...args)
}
static debug(...args: any) {
Logger.dispatch('debug', ...args)
}
static error(...args: any) {
Logger.dispatch('error', ...args)
}
}
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 getWeb3 = async () => {
let web3
// Modern dapp browsers...
if (window.ethereum) {
web3 = new Web3(window.ethereum)
try {
// Request account access
await window.ethereum.enable()
return web3
} catch (error) {
// User denied account access...
Logger.error(error)
return error
}
}
// Legacy dapp browsers...
else if (window.web3) {
web3 = new Web3(window.web3.currentProvider)
return web3
}
// Non-dapp browsers...
else {
return
}
}
export const getAccounts = async (web3: Web3) => {
const ethAccounts = await web3.eth.getAccounts()
return ethAccounts
}
export const getNetwork = async (web3: Web3) => {
const netId = await web3.eth.net.getId()
const networkName = getNetworkName(netId)
return { netId, 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) Logger.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 }
}

View 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 })

View 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
}

View 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
}

View File

@ -61,6 +61,20 @@
font-size: $font-size-h2;
}
.web3 {
width: 100%;
text-align: center;
margin-top: $spacer / 2;
margin-bottom: $spacer * 2;
padding-bottom: $spacer;
small {
color: darken($alert-info, 60%);
margin-top: -($spacer / 2);
display: block;
}
}
.coins {
width: 100%;

View File

@ -2,13 +2,15 @@ import React from 'react'
import loadable from '@loadable/component'
import shortid from 'shortid'
import Helmet from 'react-helmet'
import { Web3ReactProvider } from '@web3-react/core'
import { Author } from '../@types/Site'
import { useSiteMetadata } from '../hooks/use-site-metadata'
import { getLibrary } from '../hooks/use-web3'
import Qr from '../components/atoms/Qr'
import Icon from '../components/atoms/Icon'
import styles from './thanks.module.scss'
const Web3Donation = loadable(() =>
const LazyWeb3Donation = loadable(() =>
import('../components/molecules/Web3Donation')
)
@ -46,10 +48,19 @@ export default function Thanks() {
<h1 className={styles.title}>Say Thanks</h1>
</header>
<Web3Donation
<div className={styles.web3}>
<header>
<h4>Web3 Wallet</h4>
<p>Send Ether with MetaMask or Brave.</p>
</header>
<Web3ReactProvider getLibrary={getLibrary}>
<LazyWeb3Donation
fallback={<div className={styles.loading}>Loading...</div>}
address={author.ether}
/>
</Web3ReactProvider>
</div>
<div className={styles.coins}>
<header>