Skip to content

Commit

Permalink
⏱️ Readonly provider (#39)
Browse files Browse the repository at this point in the history
* Background provider

* Remove dep

* Lint

* Create silent-flowers-shop.md

* Add disconnect button

* Introduce Falsy type

Co-authored-by: Piotr Szlachciak <szlachciak.piotr@gmail.com>

* Move Falsy type to its own file

* Rename BackgroundProvider -> ReadOnly*

* Introduce Config, rename Providers to DAppProvider, extract ReadOnlyProviderActivator to a seperate file

* Update README to include latest example

Co-authored-by: Marek Kirejczyk <marekkirejczyk@users.noreply.github.com>
Co-authored-by: Piotr Szlachciak <szlachciak.piotr@gmail.com>
Co-authored-by: Kirejczyk Marek <marek.kirejczyk@gmail.com>
  • Loading branch information
4 people committed Feb 22, 2021
1 parent 4b5cefc commit 9506ad8
Show file tree
Hide file tree
Showing 17 changed files with 116 additions and 37 deletions.
6 changes: 6 additions & 0 deletions .changeset/silent-flowers-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@usedapp/core": patch
"@usedapp/example": patch
---

Add ability to specify a background chain that will be used before wallet is connected.
27 changes: 19 additions & 8 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,29 @@ These requirements used to make writing quality DApps somewhat challenging, but
## Example

```tsx
const config: Config = {
readOnlyChain: ChainId.Mainnet,
readOnlyUrls: {
[ChainId.Mainnet]: 'https://mainnet.infura.io/v3/62687d1a985d4508b2b7a24827551934',
},
}

ReactDOM.render(
<EthersProvider>
<App />
<EthersProvider/>,
document.getElementById('app')
<React.StrictMode>
<DAppProvider config={config}>
<App />
</DAppProvider>
</React.StrictMode>,
document.getElementById('root')
)

function App() {
const balance = useEtherBalance();
export function App() {
const { activate, account } = useEthers()
return (
<div>
Your balance is: <span> {balance} </span>
<div>
<button onClick={() => activate(injected)}>Connect</button>
</div>
{account && <p>Account: {account}</p>}
</div>
)
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@types/react-dom": "^17.0.0",
"@web3-react/core": "^6.1.1",
"@web3-react/injected-connector": "^6.0.7",
"@web3-react/network-connector": "^6.1.5",
"ethereum-waffle": "^3.2.2",
"react": "^17.0.1",
"react-dom": "^17.0.1"
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/hooks/useBlockMeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ const GET_CURRENT_BLOCK_DIFFICULTY_CALL = MultiCallABI.encodeFunctionData('getCu

export function useBlockMeta() {
const address = useMulticallAddress()
const timestamp = useChainCall({ address, data: GET_CURRENT_BLOCK_TIMESTAMP_CALL })
const difficulty = useChainCall({ address, data: GET_CURRENT_BLOCK_DIFFICULTY_CALL })
const timestamp = useChainCall(address && { address, data: GET_CURRENT_BLOCK_TIMESTAMP_CALL })
const difficulty = useChainCall(address && { address, data: GET_CURRENT_BLOCK_DIFFICULTY_CALL })

return {
timestamp: timestamp !== undefined ? new Date(BigNumber.from(timestamp).mul(1000).toNumber()) : undefined,
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/hooks/useChainCalls.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useContext, useEffect } from 'react'
import { ChainCall } from '../providers/chainState/callsReducer'
import { ChainStateContext } from '../providers/chainState/context'
import { Falsy } from '../model/types'

export function useChainCalls(calls: ChainCall[]) {
const { addCalls, removeCalls, value } = useContext(ChainStateContext)
Expand All @@ -15,7 +16,7 @@ export function useChainCalls(calls: ChainCall[]) {
})
}

export function useChainCall(call: ChainCall | false | undefined) {
export function useChainCall(call: ChainCall | Falsy) {
const [result] = useChainCalls(call ? [call] : [])
return result
}
2 changes: 1 addition & 1 deletion packages/core/src/hooks/useMulticallAddress.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useContext } from 'react'
import { ChainStateContext } from '../providers/chainState/context'

export function useMulticallAddress() {
export function useMulticallAddress(): string | undefined {
return useContext(ChainStateContext).multicallAddress
}
15 changes: 15 additions & 0 deletions packages/core/src/model/Config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ChainId } from '../constants'

export type NodeUrls = {
[chainId: number]: string
}

export type MulticallAddresses = {
[chainId: number]: string
}

export type Config = {
readOnlyChain?: ChainId
readOnlyUrls?: NodeUrls
multicallAddresses?: MulticallAddresses
}
1 change: 1 addition & 0 deletions packages/core/src/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Falsy = false | 0 | '' | null | undefined
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { ReactNode } from 'react'
import { Web3ReactProvider } from '@web3-react/core'
import { Web3Provider } from '@ethersproject/providers'
import { Web3ReactProvider } from '@web3-react/core'
import { ReactNode } from 'react'
import { MULTICALL_ADDRESSES } from '../constants'
import { Config } from '../model/Config'
import { BlockNumberProvider } from './blockNumber/provider'
import { ChainStateProvider } from './chainState'
import { MULTICALL_ADDRESSES } from '../constants'
import { ReadOnlyProviderActivator } from './ReadOnlyProviderActivator'

interface Props {
interface DAppProviderProps {
children: ReactNode
multicallAddresses?: {
[chainId: number]: string
}
config: Config
}

export function Providers(props: Props) {
const multicallAddresses = { ...MULTICALL_ADDRESSES, ...props.multicallAddresses }
export function DAppProvider({ config, children }: DAppProviderProps) {
const multicallAddresses = { ...MULTICALL_ADDRESSES, ...config.multicallAddresses }

return (
<Web3ReactProvider getLibrary={getLibrary}>
<BlockNumberProvider>
<ChainStateProvider multicallAddresses={multicallAddresses}>{props.children}</ChainStateProvider>
{config.readOnlyChain && config.readOnlyUrls && (
<ReadOnlyProviderActivator chainId={config.readOnlyChain} nodeUrls={config.readOnlyUrls} />
)}
<ChainStateProvider multicallAddresses={multicallAddresses}>{children}</ChainStateProvider>
</BlockNumberProvider>
</Web3ReactProvider>
)
Expand Down
22 changes: 22 additions & 0 deletions packages/core/src/providers/ReadOnlyProviderActivator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useEffect } from 'react'
import { NetworkConnector } from '@web3-react/network-connector'
import { ChainId } from '../constants'
import { useEthers } from '../hooks'
import { NodeUrls } from '../model/Config'

interface ReadOnlyProviderActivatorProps {
chainId: ChainId
nodeUrls: NodeUrls
}

export function ReadOnlyProviderActivator({ chainId, nodeUrls }: ReadOnlyProviderActivatorProps) {
const { activate, account, chainId: connectedChainId, active, connector } = useEthers()

useEffect(() => {
if (!active || (connector instanceof NetworkConnector && connectedChainId !== chainId)) {
activate(new NetworkConnector({ defaultChainId: chainId, urls: nodeUrls }))
}
}, [chainId, active, account, connectedChainId, connector])

return null
}
2 changes: 1 addition & 1 deletion packages/core/src/providers/chainState/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const ChainStateContext = createContext<{
state?: ChainState
error?: unknown
}
multicallAddress: string
multicallAddress: string | undefined
addCalls(calls: ChainCall[]): void
removeCalls(calls: ChainCall[]): void
}>({
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/providers/chainState/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function ChainStateProvider({ children, multicallAddresses }: Props) {
const [debouncedCalls, debouncedId] = useDebouncePair(calls, chainId, 50)
const uniqueCalls = debouncedId === chainId ? getUnique(debouncedCalls) : []

const multicallAddress = chainId !== undefined ? multicallAddresses[chainId] : ''
const multicallAddress = chainId !== undefined ? multicallAddresses[chainId] : undefined

useEffect(() => {
if (library && blockNumber !== undefined && chainId !== undefined) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './Providers'
export * from './DAppProvider'
export * from './blockNumber'
export * from './chainState'
16 changes: 8 additions & 8 deletions packages/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,26 @@
"lint:prettier": "yarn prettier './{src, test}/**/*.{ts,tsx}'"
},
"dependencies": {
"@usedapp/core": "*",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"@usedapp/core": "*"
"react-dom": "^17.0.0"
},
"devDependencies": {
"webpack": "^4.44.2",
"webpack-cli": "^4.1.0",
"webpack-dev-server": "^3.11.0",
"@testing-library/react": "^11.0.0",
"@types/chai": "^4.2.13",
"@types/mocha": "^8.2.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/snowpack-env": "^2.3.2",
"chai": "^4.2.0",
"prettier": "^2.0.5",
"html-webpack-plugin": "^4.5.0",
"clean-webpack-plugin": "^3.0.0",
"eslint": "7.19.0",
"html-webpack-plugin": "^4.5.0",
"prettier": "^2.0.5",
"ts-loader": "^8.0.7",
"typescript": "^4.0.0",
"eslint": "7.19.0"
"webpack": "^4.44.2",
"webpack-cli": "^4.1.0",
"webpack-dev-server": "^3.11.0"
}
}
4 changes: 3 additions & 1 deletion packages/example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const injected = new InjectedConnector({ supportedChainIds: [1, 3, 4, 42] })

export function App() {
const blockNumber = useBlockNumber()
const { chainId, activate } = useEthers()
const { chainId, activate, deactivate, account } = useEthers()
const { timestamp, difficulty } = useBlockMeta()

return (
Expand All @@ -17,7 +17,9 @@ export function App() {
{timestamp && <p>Current block timestamp: {timestamp.toLocaleString()}</p>}
<div>
<button onClick={() => activate(injected)}>Connect</button>
<button onClick={() => deactivate()}>Disconnect</button>
</div>
{account && <p>Account: {account}</p>}
</div>
)
}
14 changes: 11 additions & 3 deletions packages/example/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Providers } from '@usedapp/core'
import { ChainId, DAppProvider } from '@usedapp/core'
import { App } from './App'
import type { Config } from '@usedapp/core/dist/src/model/Config'

const config: Config = {
readOnlyChain: ChainId.Mainnet,
readOnlyUrls: {
[ChainId.Mainnet]: 'https://mainnet.infura.io/v3/62687d1a985d4508b2b7a24827551934',
},
}

ReactDOM.render(
<React.StrictMode>
<Providers>
<DAppProvider config={config}>
<App />
</Providers>
</DAppProvider>
</React.StrictMode>,
document.getElementById('root')
)
9 changes: 9 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,15 @@
"@web3-react/types" "^6.0.7"
tiny-warning "^1.0.3"

"@web3-react/network-connector@^6.1.5":
version "6.1.5"
resolved "https://registry.npmjs.org/@web3-react/network-connector/-/network-connector-6.1.5.tgz#1fcce1dc7b03dac23fcc01ad0b0c870cb0e39e0b"
integrity sha512-Uwk8iMG8YCnTeKmyXt3Q7QJN28qTs0YTTW8/aes2R26KmYWCk3GdL2eal0QcXUixJy/IjrhXzbwzHgpneJqrWg==
dependencies:
"@web3-react/abstract-connector" "^6.0.7"
"@web3-react/types" "^6.0.7"
tiny-invariant "^1.0.6"

"@web3-react/types@^6.0.7":
version "6.0.7"
resolved "https://registry.yarnpkg.com/@web3-react/types/-/types-6.0.7.tgz#34a6204224467eedc6123abaf55fbb6baeb2809f"
Expand Down

0 comments on commit 9506ad8

Please sign in to comment.