mirror of
https://github.com/oceanprotocol/commons.git
synced 2023-03-15 18:03:00 +01:00
allow login to wallets
This commit is contained in:
parent
d545bcaa30
commit
adf52ceb77
54
client/package-lock.json
generated
54
client/package-lock.json
generated
@ -2986,6 +2986,32 @@
|
|||||||
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
|
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"bindings": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||||
|
"requires": {
|
||||||
|
"file-uri-to-path": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bip39": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "11.11.6",
|
||||||
|
"create-hash": "^1.1.0",
|
||||||
|
"pbkdf2": "^3.0.9",
|
||||||
|
"randombytes": "^2.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": {
|
||||||
|
"version": "11.11.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz",
|
||||||
|
"integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"bl": {
|
"bl": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
|
||||||
@ -6515,6 +6541,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz",
|
||||||
"integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY="
|
"integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY="
|
||||||
},
|
},
|
||||||
|
"file-uri-to-path": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||||
|
},
|
||||||
"filesize": {
|
"filesize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/filesize/-/filesize-4.1.2.tgz",
|
||||||
@ -15531,6 +15562,29 @@
|
|||||||
"glob": "^7.1.2"
|
"glob": "^7.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"truffle-hdwallet-provider": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/truffle-hdwallet-provider/-/truffle-hdwallet-provider-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-T9qNm7b6MD0UPWVmmJEhgzW1DdR6mkMDijGBSbdJqYaaBLufoycE+qH3dsV+m1mLTE+ebM5RcJ4gF4oXgDW67w==",
|
||||||
|
"requires": {
|
||||||
|
"any-promise": "^1.3.0",
|
||||||
|
"bindings": "^1.3.1",
|
||||||
|
"websocket": "^1.0.28"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"websocket": {
|
||||||
|
"version": "1.0.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.28.tgz",
|
||||||
|
"integrity": "sha512-00y/20/80P7H4bCYkzuuvvfDvh+dgtXi5kzDf3UcZwN6boTYaKvsrtZ5lIYm1Gsg48siMErd9M4zjSYfYFHTrA==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "^2.2.0",
|
||||||
|
"nan": "^2.11.0",
|
||||||
|
"typedarray-to-buffer": "^3.1.5",
|
||||||
|
"yaeti": "^0.0.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ts-pnp": {
|
"ts-pnp": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.2.tgz",
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"@oceanprotocol/squid": "0.6.2",
|
"@oceanprotocol/squid": "0.6.2",
|
||||||
"@oceanprotocol/typographies": "^0.1.0",
|
"@oceanprotocol/typographies": "^0.1.0",
|
||||||
"@sindresorhus/slugify": "^0.9.1",
|
"@sindresorhus/slugify": "^0.9.1",
|
||||||
|
"bip39": "^3.0.2",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
||||||
@ -38,6 +39,7 @@
|
|||||||
"react-popper": "^1.3.3",
|
"react-popper": "^1.3.3",
|
||||||
"react-router-dom": "^5.0.1",
|
"react-router-dom": "^5.0.1",
|
||||||
"react-transition-group": "^4.1.1",
|
"react-transition-group": "^4.1.1",
|
||||||
|
"truffle-hdwallet-provider": "1.0.5",
|
||||||
"web3": "1.0.0-beta.37"
|
"web3": "1.0.0-beta.37"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -33,6 +33,8 @@ export default class Header extends PureComponent {
|
|||||||
{menu.map(item => (
|
{menu.map(item => (
|
||||||
<MenuItem key={item.title} item={item} />
|
<MenuItem key={item.title} item={item} />
|
||||||
))}
|
))}
|
||||||
|
<button onClick={this.context.loginMetamask}>login Metamask</button>
|
||||||
|
<button onClick={this.context.loginZeroWallet}>login ZWallet</button>
|
||||||
<AccountStatus className={styles.accountStatus} />
|
<AccountStatus className={styles.accountStatus} />
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
42
client/src/context/MetamaskProvider.ts
Normal file
42
client/src/context/MetamaskProvider.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import Web3 from 'web3'
|
||||||
|
|
||||||
|
export class MetamaskProvider {
|
||||||
|
web3: Web3
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Default
|
||||||
|
this.web3 = null as any
|
||||||
|
// Modern dapp browsers
|
||||||
|
if (window.ethereum) {
|
||||||
|
this.web3 = new Web3(window.ethereum)
|
||||||
|
}
|
||||||
|
// Legacy dapp browsers
|
||||||
|
else if (window.web3) {
|
||||||
|
this.web3 = new Web3(window.web3.currentProvider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async isAvaliable() {
|
||||||
|
return this.web3 !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
async isLogged() {
|
||||||
|
if(this.web3 === null) return false
|
||||||
|
if((await this.web3.eth.getAccounts()).length > 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async startLogin() {
|
||||||
|
try {
|
||||||
|
await window.ethereum.enable()
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getProvider() {
|
||||||
|
return this.web3
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
import React, { PureComponent } from 'react'
|
import React, { PureComponent } from 'react'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import { Logger, Ocean, Account } from '@oceanprotocol/squid'
|
import { Ocean, Account } from '@oceanprotocol/squid'
|
||||||
import { User } from '.'
|
import { User } from '.'
|
||||||
import { provideOcean, requestFromFaucet, FaucetResponse } from '../ocean'
|
import { provideOcean, requestFromFaucet, FaucetResponse } from '../ocean'
|
||||||
import { nodeUri } from '../config'
|
import { nodeUri } from '../config'
|
||||||
import MarketProvider from './MarketProvider'
|
import MarketProvider from './MarketProvider'
|
||||||
|
import { MetamaskProvider } from './MetamaskProvider'
|
||||||
|
import { ZeroWalletProvider } from './ZeroWalletProvider'
|
||||||
|
|
||||||
const POLL_ACCOUNTS = 1000 // every 1s
|
const POLL_ACCOUNTS = 1000 // every 1s
|
||||||
const POLL_NETWORK = POLL_ACCOUNTS * 60 // every 1 min
|
const POLL_NETWORK = POLL_ACCOUNTS * 60 // every 1 min
|
||||||
@ -58,23 +60,57 @@ interface UserProviderState {
|
|||||||
ocean: Ocean
|
ocean: Ocean
|
||||||
requestFromFaucet(account: string): Promise<FaucetResponse>
|
requestFromFaucet(account: string): Promise<FaucetResponse>
|
||||||
unlockAccounts(): Promise<any>
|
unlockAccounts(): Promise<any>
|
||||||
|
loginMetamask(): Promise<any>
|
||||||
|
loginZeroWallet(): Promise<any>
|
||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
||||||
private unlockAccounts = async () => {
|
private unlockAccounts = async () => {
|
||||||
try {
|
try {
|
||||||
await window.ethereum.enable()
|
window.ethereum.enable()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// User denied account access...
|
// User denied account access...
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loginMetamask = async () => {
|
||||||
|
const metamaskProvider = new MetamaskProvider()
|
||||||
|
await metamaskProvider.startLogin()
|
||||||
|
localStorage.setItem('logType', 'Metamask')
|
||||||
|
const web3 = metamaskProvider.getProvider()
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
isLogged: true,
|
||||||
|
web3
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.loadOcean()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private loginZeroWallet = async () => {
|
||||||
|
const zerowalletProvider = new ZeroWalletProvider()
|
||||||
|
await zerowalletProvider.createLogin()
|
||||||
|
localStorage.setItem('logType', 'ZeroWallet')
|
||||||
|
const web3 = zerowalletProvider.getProvider()
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
isLogged: true,
|
||||||
|
web3
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.loadOcean()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
public state = {
|
public state = {
|
||||||
isLogged: false,
|
isLogged: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
isWeb3: false,
|
isWeb3: true,
|
||||||
isOceanNetwork: false,
|
isOceanNetwork: false,
|
||||||
balance: {
|
balance: {
|
||||||
eth: 0,
|
eth: 0,
|
||||||
@ -86,6 +122,8 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
ocean: {} as any,
|
ocean: {} as any,
|
||||||
requestFromFaucet: () => requestFromFaucet(''),
|
requestFromFaucet: () => requestFromFaucet(''),
|
||||||
unlockAccounts: () => this.unlockAccounts(),
|
unlockAccounts: () => this.unlockAccounts(),
|
||||||
|
loginMetamask: () => this.loginMetamask(),
|
||||||
|
loginZeroWallet: () => this.loginZeroWallet(),
|
||||||
message: 'Connecting to Ocean...'
|
message: 'Connecting to Ocean...'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,9 +132,6 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
await this.bootstrap()
|
await this.bootstrap()
|
||||||
|
|
||||||
this.initAccountsPoll()
|
|
||||||
this.initNetworkPoll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initAccountsPoll() {
|
private initAccountsPoll() {
|
||||||
@ -114,105 +149,66 @@ export default class UserProvider extends PureComponent<{}, UserProviderState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getWeb3 = () => {
|
private loadOcean = async () => {
|
||||||
// Modern dapp browsers
|
const { ocean } = await provideOcean(this.state.web3)
|
||||||
if (window.ethereum) {
|
this.setState({ ocean, isLoading: false }, () => {
|
||||||
window.web3 = new Web3(window.ethereum)
|
this.initNetworkPoll()
|
||||||
return window.web3
|
this.initAccountsPoll()
|
||||||
}
|
this.fetchNetwork()
|
||||||
// Legacy dapp browsers
|
this.fetchAccounts()
|
||||||
else if (window.web3) {
|
})
|
||||||
window.web3 = new Web3(window.web3.currentProvider)
|
|
||||||
return window.web3
|
|
||||||
}
|
|
||||||
// Non-dapp browsers
|
|
||||||
else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bootstrap = async () => {
|
private bootstrap = async () => {
|
||||||
try {
|
const logType = localStorage.getItem('logType')
|
||||||
//
|
switch (logType) {
|
||||||
// Start with Web3 detection only
|
case 'Metamask':
|
||||||
//
|
const metamaskProvider = new MetamaskProvider()
|
||||||
this.setState({ message: 'Setting up Web3...' })
|
|
||||||
let web3 = await this.getWeb3()
|
|
||||||
|
|
||||||
web3
|
|
||||||
? this.setState({ isWeb3: true })
|
|
||||||
: this.setState({ isWeb3: false })
|
|
||||||
|
|
||||||
// Modern & legacy dapp browsers
|
|
||||||
if (web3 && this.state.isWeb3) {
|
|
||||||
//
|
|
||||||
// Detecting network with window.web3
|
|
||||||
//
|
|
||||||
let isOceanNetwork
|
|
||||||
|
|
||||||
await window.web3.eth.net.getId((err, netId) => {
|
|
||||||
if (err) return
|
|
||||||
|
|
||||||
const isPacific = netId === 0xcea11
|
|
||||||
const isNile = netId === 8995
|
|
||||||
const isDuero = netId === 2199
|
|
||||||
const isSpree = netId === 8996
|
|
||||||
|
|
||||||
isOceanNetwork = isPacific || isNile || isDuero || isSpree
|
|
||||||
|
|
||||||
const network = isPacific
|
|
||||||
? 'Pacific'
|
|
||||||
: isNile
|
|
||||||
? 'Nile'
|
|
||||||
: isDuero
|
|
||||||
? 'Duero'
|
|
||||||
: netId.toString()
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isOceanNetwork !== this.state.isOceanNetwork ||
|
(await metamaskProvider.isAvaliable()) &&
|
||||||
network !== this.state.network
|
(await metamaskProvider.isLogged())
|
||||||
) {
|
) {
|
||||||
this.setState({ isOceanNetwork, network })
|
const web3 = metamaskProvider.getProvider()
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
isLogged: true,
|
||||||
|
web3
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.loadOcean()
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
} else {
|
||||||
if (!isOceanNetwork) {
|
this.loadOcean()
|
||||||
web3 = this.state.web3 // eslint-disable-line
|
|
||||||
}
|
}
|
||||||
|
break
|
||||||
//
|
case 'ZeroWallet':
|
||||||
// Provide the Ocean
|
const zerowalletProvider = new ZeroWalletProvider()
|
||||||
//
|
if (await zerowalletProvider.isLogged()) {
|
||||||
this.setState({ message: 'Connecting to Ocean...' })
|
await zerowalletProvider.restoreStoredLogin()
|
||||||
|
this.setState(
|
||||||
const { ocean } = await provideOcean(web3)
|
{
|
||||||
this.setState({ ocean, message: 'Getting accounts...' })
|
isLogged: true,
|
||||||
|
web3: zerowalletProvider.getProvider()
|
||||||
// Get accounts
|
},
|
||||||
await this.fetchAccounts()
|
() => {
|
||||||
|
this.loadOcean()
|
||||||
this.setState({ isLoading: false, message: '' })
|
|
||||||
}
|
}
|
||||||
// Non-dapp browsers
|
)
|
||||||
else {
|
} else {
|
||||||
this.setState({ message: 'Connecting to Ocean...' })
|
this.loadOcean()
|
||||||
const { ocean } = await provideOcean(this.state.web3)
|
|
||||||
this.setState({ ocean, isLoading: false })
|
|
||||||
|
|
||||||
this.fetchNetwork()
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
break
|
||||||
// error in bootstrap process
|
default:
|
||||||
// show error connecting to ocean
|
this.loadOcean()
|
||||||
Logger.error('web3 error', e.message)
|
break
|
||||||
this.setState({ isLoading: false })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchAccounts = async () => {
|
private fetchAccounts = async () => {
|
||||||
const { ocean, isWeb3, isLogged, isOceanNetwork } = this.state
|
const { ocean, isLogged, isOceanNetwork } = this.state
|
||||||
|
|
||||||
if (isWeb3) {
|
if (isLogged) {
|
||||||
let accounts
|
let accounts
|
||||||
|
|
||||||
// Modern dapp browsers
|
// Modern dapp browsers
|
||||||
|
44
client/src/context/ZeroWalletProvider.ts
Normal file
44
client/src/context/ZeroWalletProvider.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import Web3 from 'web3'
|
||||||
|
import { nodeHost, nodePort, nodeScheme } from '../config'
|
||||||
|
import bip39 from 'bip39'
|
||||||
|
const HDWalletProvider = require('truffle-hdwallet-provider')
|
||||||
|
|
||||||
|
export class ZeroWalletProvider {
|
||||||
|
web3: Web3
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Default
|
||||||
|
this.web3 = null as any
|
||||||
|
}
|
||||||
|
|
||||||
|
async isLogged() {
|
||||||
|
if (localStorage.getItem('seedphrase') !== null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async restoreStoredLogin() {
|
||||||
|
const mnemonic = localStorage.getItem('seedphrase') as string
|
||||||
|
localStorage.setItem('seedphrase', mnemonic)
|
||||||
|
const provider = new HDWalletProvider(mnemonic, `${nodeScheme}://${nodeHost}:${nodePort}`, 0, 1);
|
||||||
|
this.web3 = new Web3(provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createLogin() {
|
||||||
|
const mnemonic = bip39.generateMnemonic()
|
||||||
|
localStorage.setItem('seedphrase', mnemonic)
|
||||||
|
const provider = new HDWalletProvider(mnemonic, `${nodeScheme}://${nodeHost}:${nodePort}`, 0, 1);
|
||||||
|
this.web3 = new Web3(provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
async restoreLogin(mnemonic: string) {
|
||||||
|
localStorage.setItem('seedphrase', mnemonic)
|
||||||
|
const provider = new HDWalletProvider(mnemonic, `${nodeScheme}://${nodeHost}:${nodePort}`, 0, 1);
|
||||||
|
this.web3 = new Web3(provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
getProvider() {
|
||||||
|
return this.web3
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,12 @@ export const User = React.createContext({
|
|||||||
unlockAccounts: () => {
|
unlockAccounts: () => {
|
||||||
/* empty */
|
/* empty */
|
||||||
},
|
},
|
||||||
|
loginMetamask: () => {
|
||||||
|
/* empty */
|
||||||
|
},
|
||||||
|
loginZeroWallet: () => {
|
||||||
|
/* empty */
|
||||||
|
},
|
||||||
message: ''
|
message: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -22,7 +22,11 @@ export async function provideOcean(web3provider: Web3) {
|
|||||||
verbose
|
verbose
|
||||||
}
|
}
|
||||||
|
|
||||||
const ocean: Ocean = await Ocean.getInstance(config)
|
console.log('accs web3:', (await web3provider.eth.getAccounts())[0])
|
||||||
|
|
||||||
|
const ocean: any = await Ocean.getInstance(config)
|
||||||
|
|
||||||
|
console.log('accs ocean:', (await ocean.accounts.list())[0].id)
|
||||||
|
|
||||||
return { ocean }
|
return { ocean }
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user