1
0
mirror of https://github.com/oceanprotocol/react.git synced 2024-12-25 02:26:25 +01:00

Merge pull request #66 from oceanprotocol/feature/cache-connect

refactor web3 connection
This commit is contained in:
Matthias Kretschmann 2020-07-31 11:49:21 +02:00 committed by GitHub
commit 612fafc26b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2877 additions and 3843 deletions

View File

@ -15,16 +15,16 @@
![iu](https://user-images.githubusercontent.com/90316/80356686-1650c080-887a-11ea-854e-bdc2bbdb0c20.jpeg) ![iu](https://user-images.githubusercontent.com/90316/80356686-1650c080-887a-11ea-854e-bdc2bbdb0c20.jpeg)
**WE ARE IN HARDWARE MODE. This project is in a conceptual phase and most stuff does not work yet.** **WE ARE IN HARDWARE MODE. This project is in a conceptual phase and most stuff changes weekly. More importantly, it only works against bleeding edge v3 components of Ocean Protocol which are not completely public yet.**
--- ---
**Table of Contents** **Table of Contents**
- [🏗 Installation](#-installation) - [🏗 Installation](#-installation)
- [🏄 Usage](#-usage) - [🏄 Quick Start](#-quick-start)
- [1. Providers](#1-providers) - [1. Add Provider](#1-add-provider)
- [2. Hooks](#2-hooks) - [2. Use Hooks](#2-use-hooks)
- [🦑 Development](#-development) - [🦑 Development](#-development)
- [✨ Code Style](#-code-style) - [✨ Code Style](#-code-style)
- [👩‍🔬 Testing](#-testing) - [👩‍🔬 Testing](#-testing)
@ -43,62 +43,25 @@
npm install @oceanprotocol/react npm install @oceanprotocol/react
``` ```
## 🏄 Usage ## 🏄 Quick Start
First, wrap your whole app with the [`Web3Provider`](src/providers/Web3Provider) and the [`OceanProvider`](src/providers/OceanProvider). ### 1. Add Provider
### 1. Providers First, wrap your whole app with the [`<OceanProvider />`](src/providers/OceanProvider).
```tsx ### 2. Use Hooks
import React, { ReactNode } from 'react'
import { Web3Provider, OceanProvider, Config } from '@oceanprotocol/react'
const config: Config = {
nodeUri: '',
aquariusUri: '',
...
}
export default function MyApp({
children
}: {
children: ReactNode
}): ReactNode {
return (
<Web3Provider>
<OceanProvider config={config}>
<h1>My App</h1>
{children}
</OceanProvider>
)}
</Web3Provider>
)
}
```
The `OceanProvider` requires a Web3 instance to be passed as prop so you can replace the basic [`Web3Provider`](src/providers/Web3Provider) with whatever component/library/provider returning a Web3 instance.
### 2. Hooks
Then within your component use the included hooks to interact with Ocean's functionality. Each hook can be used independently: Then within your component use the included hooks to interact with Ocean's functionality. Each hook can be used independently:
```tsx ```tsx
import React from 'react' import React from 'react'
import { import { useOcean, useMetadata, useConsume } from '@oceanprotocol/react'
useWeb3,
useOcean,
useMetadata,
useConsume
} from '@oceanprotocol/react'
const did = 'did:op:0x000000000' const did = 'did:op:0x000000000'
export default function MyComponent() { export default function MyComponent() {
// Get web3 from built-in Web3Provider context
const { web3 } = useWeb3()
// Get Ocean instance from built-in OceanProvider context // Get Ocean instance from built-in OceanProvider context
const { ocean, account } = useOcean() const { ocean, web3, account } = useOcean()
// Get metadata for this asset // Get metadata for this asset
const { title, metadata } = useMetadata(did) const { title, metadata } = useMetadata(did)

23
example/.gitignore vendored
View File

@ -1,23 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1,8 @@
# Example
Simple example app based on Create React App.
```bash
npm install
npm start
```

6279
example/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,22 +5,19 @@
"dependencies": { "dependencies": {
"-": "0.0.1", "-": "0.0.1",
"@oceanprotocol/react": "file:../", "@oceanprotocol/react": "file:../",
"@testing-library/jest-dom": "^4.2.4", "@toruslabs/torus-embed": "^1.8.2",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"@toruslabs/torus-embed": "^1.7.3",
"@types/jest": "^24.9.1", "@types/jest": "^24.9.1",
"@types/node": "^12.12.47", "@types/node": "^12.12.47",
"@types/react": "^16.9.41", "@types/react": "^16.9.41",
"@types/react-dom": "^16.9.8", "@types/react-dom": "^16.9.8",
"@types/shortid": "0.0.29", "@types/shortid": "^0.0.29",
"@walletconnect/web3-provider": "^1.0.14", "@walletconnect/web3-provider": "^1.1.0",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-scripts": "3.4.1", "react-scripts": "3.4.1",
"shortid": "^2.2.15", "shortid": "^2.2.15",
"typescript": "^3.7.5", "typescript": "^3.9.7",
"web3-eth-contract": "^1.2.9" "web3-eth-contract": "^1.2.11"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
@ -31,16 +28,10 @@
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
}, },
"browserslist": { "browserslist": [
"production": [
">0.2%", ">0.2%",
"not dead", "not dead",
"not ie <= 11",
"not op_mini all" "not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
] ]
} }
}

View File

@ -10,11 +10,6 @@
content="Web site created using create-react-app" content="Web site created using create-react-app"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.

View File

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -7,7 +7,9 @@ import { Config } from '@oceanprotocol/lib'
import { AllDdos } from './AllDdos' import { AllDdos } from './AllDdos'
import { ConsumeDdo } from './ConsumeDdo' import { ConsumeDdo } from './ConsumeDdo'
function App() { import WalletConnectProvider from '@walletconnect/web3-provider'
import Torus from '@toruslabs/torus-embed'
// factory Address needs to be updated each time you deploy the contract on local network // factory Address needs to be updated each time you deploy the contract on local network
const config = { const config = {
metadataStoreUri: 'http://aquarius:5000', metadataStoreUri: 'http://aquarius:5000',
@ -22,14 +24,41 @@ function App() {
nodeUri: `https://rinkeby.infura.io/a983b53583044593956054de049922fd`, nodeUri: `https://rinkeby.infura.io/a983b53583044593956054de049922fd`,
factoryAddress: '0xB9d406D24B310A7D821D0b782a36909e8c925471' factoryAddress: '0xB9d406D24B310A7D821D0b782a36909e8c925471'
} as Config } as Config
const providerOptions = {
walletconnect: {
package: WalletConnectProvider,
options: {
infuraId: ''
}
},
torus: {
package: Torus,
options: {
networkParams: {
host: config.nodeUri // optional
// chainId: 1337, // optional
// networkId: 1337 // optional
}
}
}
}
export const web3ModalOpts = {
cacheProvider: true,
providerOptions
}
function App() {
const init = async () => {} const init = async () => {}
useEffect(() => { useEffect(() => {
init() init()
}, []) }, [])
return ( return (
<div className="app"> <div className="app">
<OceanProvider config={config}> <OceanProvider config={config} web3ModalOpts={web3ModalOpts}>
<div className="container"> <div className="container">
<div> <div>
<Wallet /> <Wallet />

View File

@ -29,10 +29,8 @@ export function Publish() {
} }
} }
const marketAddress = '0x4D156A2ef69ffdDC55838176C6712C90f60a2285'
const publishAsset = async () => { const publishAsset = async () => {
const ddo = await publish(asset as Metadata, '4', marketAddress, [ const ddo = await publish(asset as Metadata, '4', [
{ serviceType: 'access', cost: '1' }, { serviceType: 'access', cost: '1' },
{ serviceType: 'compute', cost: '1' } { serviceType: 'compute', cost: '1' }
]) ])

View File

@ -3,29 +3,19 @@ import { useOcean } from '@oceanprotocol/react'
import { useEffect } from 'react' import { useEffect } from 'react'
export function Wallet() { export function Wallet() {
const { web3, ocean, connect, logout, accountId } = useOcean() const { ocean, connect, logout, accountId } = useOcean()
const conn = async () => { const conn = async () => {
const { default: WalletConnectProvider } = await import( await connect()
'@walletconnect/web3-provider'
)
const providerOptions = {
/* See Provider Options Section */
walletconnect: {
package: WalletConnectProvider, // required
options: {
infuraId: 'INFURA_ID' // required
}
}
} }
await connect({ cacheProvider: true, providerOptions })
}
const init = async () => { const init = async () => {
if (ocean === undefined || accountId === undefined) return if (ocean === undefined || accountId === undefined) return
const assets = await ocean.assets.ownerAssets(accountId) const assets = await ocean.assets.ownerAssets(accountId)
console.log(assets) console.log(assets)
} }
useEffect(() => { useEffect(() => {
init() init()
}, [ocean, accountId]) }, [ocean, accountId])
@ -34,6 +24,7 @@ export function Wallet() {
await logout() await logout()
await conn() await conn()
} }
return ( return (
<> <>
<div>wallet</div> <div>wallet</div>

View File

@ -27,7 +27,6 @@
"dependencies": { "dependencies": {
"@oceanprotocol/lib": "^0.1.10", "@oceanprotocol/lib": "^0.1.10",
"axios": "^0.19.2", "axios": "^0.19.2",
"react": "^16.13.1",
"web3": "^1.2.11", "web3": "^1.2.11",
"web3modal": "^1.9.0" "web3modal": "^1.9.0"
}, },

View File

@ -10,6 +10,7 @@ import ProviderStatus from './ProviderStatus'
import { Ocean, Logger, Account, Config } from '@oceanprotocol/lib' import { Ocean, Logger, Account, Config } from '@oceanprotocol/lib'
import Web3Modal, { ICoreOptions } from 'web3modal' import Web3Modal, { ICoreOptions } from 'web3modal'
import { getDefaultProviders } from './getDefaultProviders' import { getDefaultProviders } from './getDefaultProviders'
import { getAccountId, getBalance } from '../../utils'
interface Balance { interface Balance {
eth: string | undefined eth: string | undefined
@ -35,9 +36,11 @@ const OceanContext = createContext(null)
function OceanProvider({ function OceanProvider({
config, config,
web3ModalOpts,
children children
}: { }: {
config: Config config: Config
web3ModalOpts?: Partial<ICoreOptions>
children: any children: any
}): ReactElement { }): ReactElement {
const [web3, setWeb3] = useState<Web3 | undefined>() const [web3, setWeb3] = useState<Web3 | undefined>()
@ -54,13 +57,20 @@ function OceanProvider({
const [status, setStatus] = useState<ProviderStatus>( const [status, setStatus] = useState<ProviderStatus>(
ProviderStatus.NOT_AVAILABLE ProviderStatus.NOT_AVAILABLE
) )
const [web3ModalOpts, setWeb3ModalOpts] = useState<Partial<ICoreOptions>>()
function init() { async function init() {
Logger.log('Ocean Provider init') Logger.log('Ocean Provider init')
window && window &&
window.ethereum && window.ethereum &&
(window.ethereum.autoRefreshOnNetworkChange = false) (window.ethereum.autoRefreshOnNetworkChange = false)
Logger.log('Web3Modal init.')
if (web3ModalOpts === undefined) {
web3ModalOpts = await getDefaultProviders()
}
const web3ModalInstance = new Web3Modal(web3ModalOpts)
setWeb3Modal(web3ModalInstance)
Logger.log('Web3Modal instance created.', web3ModalInstance)
} }
// On mount setup Web3Modal instance // On mount setup Web3Modal instance
@ -68,18 +78,17 @@ function OceanProvider({
init() init()
}, []) }, [])
async function connect(opts?: Partial<ICoreOptions>) { // Connect automatically to cached provider if present
Logger.log('Connecting ....') useEffect(() => {
if (!web3Modal) return
web3Modal.cachedProvider && connect()
}, [web3Modal])
if (opts === undefined) { async function connect() {
opts = await getDefaultProviders() try {
} Logger.log('Connecting ...')
const instance = web3Modal || new Web3Modal(opts) const provider = await web3Modal.connect()
setWeb3Modal(instance)
Logger.log('Web3Modal instance created.', instance)
const provider = web3Provider || (await instance.connect())
setWeb3Provider(provider) setWeb3Provider(provider)
const web3 = new Web3(provider) const web3 = new Web3(provider)
@ -108,62 +117,51 @@ function OceanProvider({
const balance = await getBalance(account) const balance = await getBalance(account)
setBalance(balance) setBalance(balance)
Logger.log('balance', JSON.stringify(balance)) Logger.log('balance', JSON.stringify(balance))
} catch (error) {
Logger.error(error)
}
} }
async function logout() { async function logout() {
// ToDo check how is the proper way to logout // TODO: check how is the proper way to logout
web3Modal.clearCachedProvider() web3Modal.clearCachedProvider()
} }
async function getAccountId(web3: Web3) {
const accounts = await web3.eth.getAccounts()
return accounts[0]
}
async function getBalance(account: Account) {
const eth = await account.getEtherBalance()
const ocean = await account.getOceanBalance()
return { eth, ocean }
}
// //
// Listen for provider, account & network changes // Listen for provider, account & network changes
// and react to it // and react to it
// //
const handleConnect = async (provider: any) => {
Logger.debug("Handling 'connect' event with payload", provider) // const handleConnect = async (provider: any) => {
} // Logger.debug("Handling 'connect' event with payload", provider)
// }
const handleAccountsChanged = async (accounts: string[]) => { const handleAccountsChanged = async (accounts: string[]) => {
console.debug("Handling 'accountsChanged' event with payload", accounts) Logger.debug("Handling 'accountsChanged' event with payload", accounts)
// if (status === ProviderStatus.CONNECTED) { connect()
connect(web3ModalOpts)
// }
} }
// ToDo need to handle this, it's not implemented, need to update chainId and reinitialize ocean lib
const handleNetworkChanged = async (networkId: string | number) => { const handleNetworkChanged = async (networkId: string | number) => {
console.debug( Logger.debug(
"Handling 'networkChanged' event with payload", "Handling 'chainChanged' event with payload",
networkId, networkId,
status status
) )
// if (status === ProviderStatus.CONNECTED) { connect()
connect(web3ModalOpts)
// }
} }
// TODO: Refetch balance periodically, or figure out some event to subscribe to
useEffect(() => { useEffect(() => {
web3Modal && web3Modal.on('connect', handleConnect) // web3Modal && web3Modal.on('connect', handleConnect)
if (web3Provider !== undefined && web3Provider !== null) { if (web3Provider !== undefined && web3Provider !== null) {
web3Provider.on('accountsChanged', handleAccountsChanged) web3Provider.on('accountsChanged', handleAccountsChanged)
web3Provider.on('networkChanged', handleNetworkChanged) web3Provider.on('chainChanged', handleNetworkChanged)
return () => { return () => {
web3Provider.removeListener('accountsChanged', handleAccountsChanged) web3Provider.removeListener('accountsChanged', handleAccountsChanged)
web3Provider.removeListener('networkChanged', handleNetworkChanged) web3Provider.removeListener('chainChanged', handleNetworkChanged)
} }
} }
}, [web3Modal, web3Provider]) }, [web3Modal, web3Provider])

View File

@ -2,11 +2,11 @@
The `OceanProvider` maintains a connection to the Ocean Protocol network in multiple steps: The `OceanProvider` maintains a connection to the Ocean Protocol network in multiple steps:
1. On mount, connect to Aquarius instance right away so any asset metadata can be retrieved before, and independent of any Web3 connections. 1. On mount, setup [Web3Modal](https://github.com/Web3Modal/).
2. Once Web3 becomes available, a connection to all Ocean Protocol network components is established. 2. Once connection with Web3Modal is started, Web3 becomes available.
3. Once Ocean becomes available, spits out some info about it. 3. Once Web3 becomes available, connection to Ocean Protocol components are initiated, all available under the `ocean` object.
Also provides a `useOcean` helper hook to access its context values from any component. With the included `useOcean` helper hook you can access all context values from any component.
## Usage ## Usage
@ -18,36 +18,14 @@ import { OceanProvider, Config } from '@oceanprotocol/react'
const config: Config = { const config: Config = {
nodeUri: '', nodeUri: '',
aquariusUri: '', metadataStoreUri: '',
... ...
} }
export default function MyApp({ const web3ModalOpts = {
children network: 'mainnet', // optional
}: { cacheProvider: true, // optional
children: ReactNode providerOptions // required
}): ReactNode {
const web3 = await getWeb3()
return (
<OceanProvider config={config} web3={web3}>
<h1>My App</h1>
{children}
</OceanProvider>
)
}
```
The `OceanProvider` requires a Web3 instance to be passed as prop so you can either handle this with your own `getWeb3()`, or use the basic [`Web3Provider`](../Web3Provider):
```tsx
import React, { ReactNode } from 'react'
import { Web3Provider, OceanProvider, Config } from '@oceanprotocol/react'
const config: Config = {
nodeUri: '',
aquariusUri: '',
...
} }
export default function MyApp({ export default function MyApp({
@ -56,30 +34,28 @@ export default function MyApp({
children: ReactNode children: ReactNode
}): ReactNode { }): ReactNode {
return ( return (
<Web3Provider> <OceanProvider config={config} web3ModalOpts={web3ModalOpts}>
{({ web3 }) => (
<OceanProvider config={config} web3={web3}>
<h1>My App</h1> <h1>My App</h1>
{children} {children}
</OceanProvider> </OceanProvider>
)}
</Web3Provider>
) )
} }
``` ```
The `OceanProvider` uses [Web3Modal](https://github.com/Web3Modal/) to make its initial wallet connection. If you do not pass `web3ModalOpts` as a prop, only the default injected provider will be available. Adding more providers requires you to add them as dependencies to your project and pass them as `providerOptions`. See all the available [Provider Options](https://github.com/Web3Modal/web3modal#provider-options).
You can then access the provider context values with the `useOcean` hook: You can then access the provider context values with the `useOcean` hook:
```tsx ```tsx
import { useOcean } from '@oceanprotocol/react' import { useOcean } from '@oceanprotocol/react'
function MyComponent() { function MyComponent() {
const { ocean, account } = useOcean() const { ocean, accountId } = useOcean()
return ( return (
<ul> <ul>
<li>Ocean available: {`${Boolean(ocean)}`}</li> <li>Ocean available: {`${Boolean(ocean)}`}</li>
<li>Account: {account}</li> <li>Account: {accountId}</li>
</ul> </ul>
) )
} }

View File

@ -25,3 +25,5 @@ export const publishFeedback: { [key in number]: string } = {
3: '3/4 Publishing asset ...', 3: '3/4 Publishing asset ...',
4: '4/4 Asset published succesfully' 4: '4/4 Asset published succesfully'
} }
export * from './web3'

15
src/utils/web3.ts Normal file
View File

@ -0,0 +1,15 @@
import Web3 from 'web3'
import { Account } from '@oceanprotocol/lib'
import { Balance } from '../providers'
export async function getAccountId(web3: Web3): Promise<string> {
const accounts = await web3.eth.getAccounts()
return accounts[0]
}
export async function getBalance(account: Account): Promise<Balance> {
const eth = await account.getEtherBalance()
const ocean = await account.getOceanBalance()
return { eth, ocean }
}