1
0
mirror of https://github.com/oceanprotocol/react.git synced 2024-12-24 10:06:20 +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)
**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**
- [🏗 Installation](#-installation)
- [🏄 Usage](#-usage)
- [1. Providers](#1-providers)
- [2. Hooks](#2-hooks)
- [🏄 Quick Start](#-quick-start)
- [1. Add Provider](#1-add-provider)
- [2. Use Hooks](#2-use-hooks)
- [🦑 Development](#-development)
- [✨ Code Style](#-code-style)
- [👩‍🔬 Testing](#-testing)
@ -43,62 +43,25 @@
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
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
### 2. Use Hooks
Then within your component use the included hooks to interact with Ocean's functionality. Each hook can be used independently:
```tsx
import React from 'react'
import {
useWeb3,
useOcean,
useMetadata,
useConsume
} from '@oceanprotocol/react'
import { useOcean, useMetadata, useConsume } from '@oceanprotocol/react'
const did = 'did:op:0x000000000'
export default function MyComponent() {
// Get web3 from built-in Web3Provider context
const { web3 } = useWeb3()
// Get Ocean instance from built-in OceanProvider context
const { ocean, account } = useOcean()
const { ocean, web3, account } = useOcean()
// Get metadata for this asset
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": {
"-": "0.0.1",
"@oceanprotocol/react": "file:../",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"@toruslabs/torus-embed": "^1.7.3",
"@toruslabs/torus-embed": "^1.8.2",
"@types/jest": "^24.9.1",
"@types/node": "^12.12.47",
"@types/react": "^16.9.41",
"@types/react-dom": "^16.9.8",
"@types/shortid": "0.0.29",
"@walletconnect/web3-provider": "^1.0.14",
"@types/shortid": "^0.0.29",
"@walletconnect/web3-provider": "^1.1.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"shortid": "^2.2.15",
"typescript": "^3.7.5",
"web3-eth-contract": "^1.2.9"
"typescript": "^3.9.7",
"web3-eth-contract": "^1.2.11"
},
"scripts": {
"start": "react-scripts start",
@ -31,16 +28,10 @@
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"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"
/>
<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.
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 { 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
const config = {
metadataStoreUri: 'http://aquarius:5000',
@ -22,14 +24,41 @@ function App() {
nodeUri: `https://rinkeby.infura.io/a983b53583044593956054de049922fd`,
factoryAddress: '0xB9d406D24B310A7D821D0b782a36909e8c925471'
} 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 () => {}
useEffect(() => {
init()
}, [])
return (
<div className="app">
<OceanProvider config={config}>
<OceanProvider config={config} web3ModalOpts={web3ModalOpts}>
<div className="container">
<div>
<Wallet />

View File

@ -29,10 +29,8 @@ export function Publish() {
}
}
const marketAddress = '0x4D156A2ef69ffdDC55838176C6712C90f60a2285'
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: 'compute', cost: '1' }
])

View File

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

View File

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

View File

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

View File

@ -2,11 +2,11 @@
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.
2. Once Web3 becomes available, a connection to all Ocean Protocol network components is established.
3. Once Ocean becomes available, spits out some info about it.
1. On mount, setup [Web3Modal](https://github.com/Web3Modal/).
2. Once connection with Web3Modal is started, Web3 becomes available.
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
@ -18,36 +18,14 @@ import { OceanProvider, Config } from '@oceanprotocol/react'
const config: Config = {
nodeUri: '',
aquariusUri: '',
metadataStoreUri: '',
...
}
export default function MyApp({
children
}: {
children: ReactNode
}): 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: '',
...
const web3ModalOpts = {
network: 'mainnet', // optional
cacheProvider: true, // optional
providerOptions // required
}
export default function MyApp({
@ -56,30 +34,28 @@ export default function MyApp({
children: ReactNode
}): ReactNode {
return (
<Web3Provider>
{({ web3 }) => (
<OceanProvider config={config} web3={web3}>
<OceanProvider config={config} web3ModalOpts={web3ModalOpts}>
<h1>My App</h1>
{children}
</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:
```tsx
import { useOcean } from '@oceanprotocol/react'
function MyComponent() {
const { ocean, account } = useOcean()
const { ocean, accountId } = useOcean()
return (
<ul>
<li>Ocean available: {`${Boolean(ocean)}`}</li>
<li>Account: {account}</li>
<li>Account: {accountId}</li>
</ul>
)
}

View File

@ -25,3 +25,5 @@ export const publishFeedback: { [key in number]: string } = {
3: '3/4 Publishing asset ...',
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 }
}