1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

pull from origin main

This commit is contained in:
Jamie Hewitt 2021-05-26 17:31:58 +03:00
commit 20361578ca
49 changed files with 1119 additions and 765 deletions

View File

@ -25,6 +25,7 @@
- [🛳 Production](#-production) - [🛳 Production](#-production)
- [⬆️ Deployment](#-deployment) - [⬆️ Deployment](#-deployment)
- [💖 Contributing](#-contributing) - [💖 Contributing](#-contributing)
- [🍴 Forking](#-forking)
- [🏛 License](#-license) - [🏛 License](#-license)
## 🏄 Get Started ## 🏄 Get Started
@ -358,6 +359,18 @@ We welcome contributions in form of bug reports, feature requests, code changes,
- [Code of Conduct →](https://docs.oceanprotocol.com/concepts/code-of-conduct/) - [Code of Conduct →](https://docs.oceanprotocol.com/concepts/code-of-conduct/)
- [Reporting Vulnerabilities →](https://docs.oceanprotocol.com/concepts/vulnerabilities/) - [Reporting Vulnerabilities →](https://docs.oceanprotocol.com/concepts/vulnerabilities/)
## 🍴 Forking
We encourage you to fork this repository and create your own data marketplace. When you publish your forked version of this market there are a few elements that you are required to change for copyright reasons:
- The typeface is copyright protected and needs to be changed unless you purchase a license for it.
- The Ocean Protocol logo is a trademark of the Ocean Protocol Foundation and must be removed from forked versions of the market.
- The name "Ocean Market" is also copyright protected and should be changed to the name of your market.
Additionally, we would also advise that your retain the text saying "Powered by Ocean Protocol" on your forked version of the marketplace in order to give credit for the development work done by the Ocean Protocol team.
Everything else is made open according to the apache2 license. We look forward to seeing your data marketplace!
## 🏛 License ## 🏛 License
```text ```text

View File

@ -28,8 +28,8 @@
"label": "Docker Image", "label": "Docker Image",
"placeholder": "e.g. python3.7", "placeholder": "e.g. python3.7",
"help": "Please select an image to run your algorithm.", "help": "Please select an image to run your algorithm.",
"type": "select", "type": "boxSelection",
"options": ["node:latest", "python:latest", "custom image"], "options": [],
"required": true "required": true
}, },
{ {

View File

@ -34,7 +34,7 @@
"name": "access", "name": "access",
"label": "Access Type", "label": "Access Type",
"help": "Choose how you want your files to be accessible for the specified price.", "help": "Choose how you want your files to be accessible for the specified price.",
"type": "select", "type": "boxSelection",
"options": ["Download", "Compute"], "options": ["Download", "Compute"],
"required": true "required": true
}, },

2
package-lock.json generated
View File

@ -18101,7 +18101,7 @@
} }
}, },
"ethereumjs-abi": { "ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#1a27c59c15ab1e95ee8e5c4ed6ad814c49cc439e", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#1ce6a1d64235fabe2aaf827fd606def55693508f",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": { "requires": {
"bn.js": "^4.11.8", "bn.js": "^4.11.8",

View File

@ -0,0 +1,71 @@
.button {
display: inline-block;
position: relative;
min-width: auto;
}
.button:hover,
.button:focus {
transform: none;
}
.logoWrap {
position: relative;
display: inline-block;
z-index: 1;
}
.logoWrap::before {
content: '+';
color: var(--color-secondary);
font-family: var(--font-family-base);
font-weight: var(--font-weight-base);
font-size: 1.25em;
position: absolute;
right: 0.05em;
top: 0.05em;
line-height: 0;
}
.logo {
width: 1.6em;
height: 1.6em;
display: inline-block;
margin-bottom: -0.35em;
border-radius: 50%;
border: 0.065rem solid var(--color-secondary);
margin-right: calc(var(--spacer) / 10);
transition: 0.2s ease-out;
}
.button:hover .logo,
.button:focus .logo {
border-color: var(--color-primary);
}
.button:hover .logoWrap::before,
.button:focus .logoWrap::before {
color: var(--color-primary);
}
.text {
display: inline-block;
position: relative;
}
.minimal .text {
opacity: 0;
transform: translate3d(-1rem, 0, 0);
transition: 0.2s ease-out;
z-index: 0;
white-space: pre;
position: absolute;
left: 100%;
top: 0.15rem;
}
.minimal:hover .text,
.minimal:focus .text {
opacity: 1;
transform: translate3d(0, 0, 0);
}

View File

@ -0,0 +1,53 @@
import React, { ReactElement } from 'react'
import classNames from 'classnames/bind'
import { addTokenToWallet } from '../../utils/web3'
import { useWeb3 } from '../../providers/Web3'
import Button from './Button'
import styles from './AddToken.module.css'
const cx = classNames.bind(styles)
export default function AddToken({
address,
symbol,
logo,
text,
className,
minimal
}: {
address: string
symbol: string
logo: string // needs to be a remote image
text?: string
className?: string
minimal?: boolean
}): ReactElement {
const { web3Provider } = useWeb3()
const styleClasses = cx({
button: true,
minimal: minimal,
[className]: className
})
async function handleAddToken() {
if (!web3Provider) return
await addTokenToWallet(web3Provider, address, symbol, logo)
}
return (
<Button
className={styleClasses}
style="text"
size="small"
onClick={handleAddToken}
>
<span className={styles.logoWrap}>
<img src={logo} className={styles.logo} width="16" height="16" />
</span>
<span className={styles.text}>{text || `Add ${symbol}`}</span>
</Button>
)
}

View File

@ -1,20 +1,30 @@
import React, { ReactElement, ReactNode, useEffect, useState } from 'react' import React, { ReactElement, ReactNode, useEffect, useState } from 'react'
import { ReactComponent as External } from '../../images/external.svg' import { ReactComponent as External } from '../../images/external.svg'
import styles from './ExplorerLink.module.css' import classNames from 'classnames/bind'
import { ConfigHelperConfig } from '@oceanprotocol/lib' import { ConfigHelperConfig } from '@oceanprotocol/lib'
import { useOcean } from '../../providers/Ocean' import { useOcean } from '../../providers/Ocean'
import styles from './ExplorerLink.module.css'
const cx = classNames.bind(styles)
export default function ExplorerLink({ export default function ExplorerLink({
path, path,
children children,
className
}: { }: {
networkId: number networkId: number
path: string path: string
children: ReactNode children: ReactNode
className?: string
}): ReactElement { }): ReactElement {
const { config } = useOcean() const { config } = useOcean()
const [url, setUrl] = useState<string>() const [url, setUrl] = useState<string>()
const styleClasses = cx({
link: true,
[className]: className
})
useEffect(() => { useEffect(() => {
setUrl((config as ConfigHelperConfig).explorerUri) setUrl((config as ConfigHelperConfig).explorerUri)
}, [config]) }, [config])
@ -25,7 +35,7 @@ export default function ExplorerLink({
title={`View on ${(config as ConfigHelperConfig).explorerUri}`} title={`View on ${(config as ConfigHelperConfig).explorerUri}`}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className={styles.link} className={styleClasses}
> >
{children} <External /> {children} <External />
</a> </a>

View File

@ -4,6 +4,9 @@ import styles from './InputElement.module.css'
import { InputProps } from '.' import { InputProps } from '.'
import FilesInput from '../../molecules/FormFields/FilesInput' import FilesInput from '../../molecules/FormFields/FilesInput'
import Terms from '../../molecules/FormFields/Terms' import Terms from '../../molecules/FormFields/Terms'
import BoxSelection, {
BoxSelectionOption
} from '../../molecules/FormFields/BoxSelection'
import Datatoken from '../../molecules/FormFields/Datatoken' import Datatoken from '../../molecules/FormFields/Datatoken'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import AssetSelection, { import AssetSelection, {
@ -91,7 +94,7 @@ export default function InputElement({
id={slugify(option)} id={slugify(option)}
type={type} type={type}
name={name} name={name}
checked={props.defaultChecked} defaultChecked={props.defaultChecked}
{...props} {...props}
/> />
<label className={styles.radioLabel} htmlFor={slugify(option)}> <label className={styles.radioLabel} htmlFor={slugify(option)}>
@ -125,6 +128,15 @@ export default function InputElement({
return <Datatoken name={name} {...field} {...props} /> return <Datatoken name={name} {...field} {...props} />
case 'terms': case 'terms':
return <Terms name={name} options={options} {...field} {...props} /> return <Terms name={name} options={options} {...field} {...props} />
case 'boxSelection':
return (
<BoxSelection
name={name}
options={(options as unknown) as BoxSelectionOption[]}
{...field}
{...props}
/>
)
default: default:
return prefix || postfix ? ( return prefix || postfix ? (
<div className={`${prefix ? styles.prefixGroup : styles.postfixGroup}`}> <div className={`${prefix ? styles.prefixGroup : styles.postfixGroup}`}>

View File

@ -68,7 +68,6 @@ export default function Publisher({
> >
{name} {name}
</Link> </Link>
<div className={styles.links}> <div className={styles.links}>
{' — '} {' — '}
{profile && ( {profile && (

View File

@ -2,9 +2,10 @@ import AssetTeaser from '../molecules/AssetTeaser'
import * as React from 'react' import * as React from 'react'
import { DDO } from '@oceanprotocol/lib' import { DDO } from '@oceanprotocol/lib'
import ddo from '../../../tests/unit/__fixtures__/ddo' import ddo from '../../../tests/unit/__fixtures__/ddo'
import { AssetListPrices } from '../../utils/subgraph'
export default { export default {
title: 'Molecules/Asset Teaser' title: 'Molecules/Asset Teaser'
} }
export const Default = () => <AssetTeaser ddo={ddo as DDO} /> export const Default = () => <AssetTeaser ddo={ddo as DDO} price={undefined} />

View File

@ -3,7 +3,7 @@ import { Link } from 'gatsby'
import Dotdotdot from 'react-dotdotdot' import Dotdotdot from 'react-dotdotdot'
import Price from '../atoms/Price' import Price from '../atoms/Price'
import styles from './AssetTeaser.module.css' import styles from './AssetTeaser.module.css'
import { DDO } from '@oceanprotocol/lib' import { DDO, BestPrice } from '@oceanprotocol/lib'
import removeMarkdown from 'remove-markdown' import removeMarkdown from 'remove-markdown'
import Publisher from '../atoms/Publisher' import Publisher from '../atoms/Publisher'
import Time from '../atoms/Time' import Time from '../atoms/Time'
@ -11,9 +11,13 @@ import AssetType from '../atoms/AssetType'
declare type AssetTeaserProps = { declare type AssetTeaserProps = {
ddo: DDO ddo: DDO
price: BestPrice
} }
const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => { const AssetTeaser: React.FC<AssetTeaserProps> = ({
ddo,
price
}: AssetTeaserProps) => {
const { attributes } = ddo.findServiceByType('metadata') const { attributes } = ddo.findServiceByType('metadata')
const { name, type } = attributes.main const { name, type } = attributes.main
const { dataTokenInfo } = ddo const { dataTokenInfo } = ddo
@ -47,7 +51,7 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => {
</div> </div>
<footer className={styles.foot}> <footer className={styles.foot}>
<Price price={ddo.price} small /> <Price price={price} small />
<p className={styles.date}> <p className={styles.date}>
<Time date={ddo?.created} relative /> <Time date={ddo?.created} relative />
</p> </p>

View File

@ -0,0 +1,57 @@
.boxSelectionsWrapper {
display: flex;
justify-content: space-between;
gap: 0 calc(var(--spacer) / 4);
}
.boxSelectionsWrapper > div {
width: 100%;
}
.boxSelection {
display: block;
flex: 1 1 0px;
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2)
calc(var(--spacer) / 4) calc(var(--spacer) / 2) !important;
border-radius: var(--border-radius);
border: 1px solid var(--border-color);
color: var(--color-secondary);
cursor: pointer;
}
.disabled {
pointer-events: none;
opacity: 0.5;
}
.title {
font-weight: var(--font-weight-bold);
text-align: center;
}
.boxSelectionsWrapper input[type='radio'] {
position: fixed;
opacity: 0;
pointer-events: none;
}
input[type='radio']:checked + label {
color: var(--font-color-text);
border-color: var(--color-secondary);
}
.boxSelection svg {
width: var(--font-size-h4);
height: var(--font-size-h4);
fill: currentColor;
margin-bottom: calc(var(--spacer) / 5);
}
.boxSelectionsWrapper label {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
color: var(--color-secondary);
font-weight: normal;
}

View File

@ -0,0 +1,70 @@
import React, { ChangeEvent } from 'react'
import classNames from 'classnames/bind'
import Loader from '../../atoms/Loader'
import styles from './BoxSelection.module.css'
const cx = classNames.bind(styles)
export interface BoxSelectionOption {
name: string
checked: boolean
title: JSX.Element | string
icon?: JSX.Element
text?: JSX.Element | string
}
export default function BoxSelection({
name,
options,
disabled,
handleChange,
...props
}: {
name: string
options: BoxSelectionOption[]
disabled?: boolean
handleChange?: (event: ChangeEvent<HTMLInputElement>) => void
}): JSX.Element {
const styleClassesWrapper = cx({
boxSelectionsWrapper: true,
[styles.disabled]: disabled
})
const styleClassesInput = cx({
input: true,
radio: true
})
return (
<div className={styleClassesWrapper}>
{!options ? (
<Loader />
) : (
options.map((value: BoxSelectionOption) => (
<div key={value.name}>
<input
id={value.name}
type="radio"
className={styleClassesInput}
defaultChecked={value.checked}
onChange={(event) => handleChange(event)}
{...props}
disabled={disabled}
value={value.name}
name={name}
/>
<label
className={`${styles.boxSelection} ${styles.label}`}
htmlFor={value.name}
title={value.name}
>
{value.icon}
<span className={styles.title}>{value.title}</span>
{value.text}
</label>
</div>
))
)}
</div>
)
}

View File

@ -2,7 +2,6 @@ import React, { ReactElement, useEffect, useState } from 'react'
import { useWeb3 } from '../../providers/Web3' import { useWeb3 } from '../../providers/Web3'
import { addCustomNetwork, NetworkObject } from '../../utils/web3' import { addCustomNetwork, NetworkObject } from '../../utils/web3'
import { getOceanConfig } from '../../utils/ocean' import { getOceanConfig } from '../../utils/ocean'
import { getProviderInfo } from 'web3modal'
import { useOcean } from '../../providers/Ocean' import { useOcean } from '../../providers/Ocean'
import { useSiteMetadata } from '../../hooks/useSiteMetadata' import { useSiteMetadata } from '../../hooks/useSiteMetadata'
import AnnouncementBanner, { import AnnouncementBanner, {
@ -19,7 +18,7 @@ const networkMatic: NetworkObject = {
} }
export default function NetworkBanner(): ReactElement { export default function NetworkBanner(): ReactElement {
const { web3Provider } = useWeb3() const { web3Provider, web3ProviderInfo } = useWeb3()
const { config, connect } = useOcean() const { config, connect } = useOcean()
const { announcement } = useSiteMetadata() const { announcement } = useSiteMetadata()
@ -51,10 +50,9 @@ export default function NetworkBanner(): ReactElement {
} }
useEffect(() => { useEffect(() => {
if (!web3Provider && !config) return if (!web3ProviderInfo || (!web3Provider && !config)) return
const providerInfo = getProviderInfo(web3Provider) switch (web3ProviderInfo.name) {
switch (providerInfo?.name) {
case 'Web3': case 'Web3':
if (config.networkId !== 137) { if (config.networkId !== 137) {
setText(announcement.main) setText(announcement.main)
@ -80,7 +78,7 @@ export default function NetworkBanner(): ReactElement {
setAction(undefined) setAction(undefined)
} }
} }
}, [web3Provider, config, announcement]) }, [web3Provider, web3ProviderInfo, config, announcement])
return <AnnouncementBanner text={text} action={action} /> return <AnnouncementBanner text={text} action={action} />
} }

View File

@ -30,3 +30,7 @@
color: var(--font-color-text); color: var(--font-color-text);
border-color: var(--color-secondary); border-color: var(--color-secondary);
} }
.appearances div[class*='boxSelectionsWrapper'] {
padding-bottom: calc(var(--spacer) / 8);
}

View File

@ -1,42 +1,44 @@
import React, { ReactElement } from 'react' import React, { ReactElement, ChangeEvent } from 'react'
import { DarkMode } from 'use-dark-mode' import { DarkMode } from 'use-dark-mode'
import Button from '../../atoms/Button'
import FormHelp from '../../atoms/Input/Help' import FormHelp from '../../atoms/Input/Help'
import Label from '../../atoms/Input/Label' import Label from '../../atoms/Input/Label'
import styles from './Appearance.module.css'
import { ReactComponent as Moon } from '../../../images/moon.svg' import { ReactComponent as Moon } from '../../../images/moon.svg'
import { ReactComponent as Sun } from '../../../images/sun.svg' import { ReactComponent as Sun } from '../../../images/sun.svg'
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
const buttons = ['Light', 'Dark'] import styles from './Appearance.module.css'
export default function Appearance({ export default function Appearance({
darkMode darkMode
}: { }: {
darkMode: DarkMode darkMode: DarkMode
}): ReactElement { }): ReactElement {
return ( const options: BoxSelectionOption[] = [
<li> {
<Label htmlFor="">Appearance</Label> name: 'Light',
<div className={styles.buttons}> checked: !darkMode.value,
{buttons.map((button) => { title: 'Light',
const isDark = button === 'Dark' icon: <Sun />
const selected = },
(isDark && darkMode.value) || (!isDark && !darkMode.value) {
name: 'Dark',
checked: darkMode.value,
title: 'Dark',
icon: <Moon />
}
]
return ( function handleChange(event: ChangeEvent<HTMLInputElement>) {
<Button event.target.value === 'Dark' ? darkMode.enable() : darkMode.disable()
key={button} }
className={`${styles.button} ${selected ? styles.selected : ''}`}
size="small" return (
style="text" <li className={styles.appearances}>
onClick={() => (isDark ? darkMode.enable() : darkMode.disable())} <Label htmlFor="">Appearance</Label>
> <BoxSelection
{isDark ? <Moon /> : <Sun />} options={options}
{button} name="appearanceMode"
</Button> handleChange={handleChange}
) />
})}
</div>
<FormHelp>Defaults to your OS setting, select to override.</FormHelp> <FormHelp>Defaults to your OS setting, select to override.</FormHelp>
</li> </li>
) )

View File

@ -17,3 +17,14 @@
.selected { .selected {
composes: selected from './Appearance.module.css'; composes: selected from './Appearance.module.css';
} }
.chains div[class*='boxSelectionsWrapper'] {
display: grid;
gap: calc(var(--spacer) / 4);
grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
padding-bottom: calc(var(--spacer) / 8);
}
.chains label[class*='boxSelection'] {
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 4) !important;
}

View File

@ -1,53 +1,58 @@
import { ConfigHelperConfig } from '@oceanprotocol/lib' import { ConfigHelperConfig } from '@oceanprotocol/lib'
import React, { ReactElement } from 'react' import React, { ReactElement, ChangeEvent } from 'react'
import { useOcean } from '../../../providers/Ocean' import { useOcean } from '../../../providers/Ocean'
import { useWeb3 } from '../../../providers/Web3' import { useWeb3 } from '../../../providers/Web3'
import { getOceanConfig } from '../../../utils/ocean' import { getOceanConfig } from '../../../utils/ocean'
import Button from '../../atoms/Button'
import FormHelp from '../../atoms/Input/Help' import FormHelp from '../../atoms/Input/Help'
import Label from '../../atoms/Input/Label' import Label from '../../atoms/Input/Label'
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
import styles from './Chain.module.css' import styles from './Chain.module.css'
export default function Chain(): ReactElement { export default function Chain(): ReactElement {
const { web3 } = useWeb3() const { web3 } = useWeb3()
const { config, connect } = useOcean() const { config, connect } = useOcean()
async function connectOcean(networkName: string) { async function connectOcean(event: ChangeEvent<HTMLInputElement>) {
const config = getOceanConfig(networkName) const config = getOceanConfig(event.target.value)
await connect(config) await connect(config)
} }
const chains = [ function isNetworkSelected(oceanConfig: string) {
{ name: 'ETH', oceanConfig: 'mainnet', label: 'Mainnet' }, return (config as ConfigHelperConfig).network === oceanConfig
{ name: 'Polygon/Matic', oceanConfig: 'polygon', label: 'Mainnet' }, }
{ name: 'Moonbase Alpha', oceanConfig: 'moonbeamalpha', label: 'Testnet' }
const options: BoxSelectionOption[] = [
{
name: 'mainnet',
checked: isNetworkSelected('mainnet'),
title: 'ETH',
text: 'Mainnet'
},
{
name: 'polygon',
checked: isNetworkSelected('polygon'),
title: 'Polygon/Matic',
text: 'Mainnet'
},
{
name: 'moonbeamalpha',
checked: isNetworkSelected('moonbeamalpha'),
title: 'Moonbase Alpha',
text: 'Testnet'
}
] ]
// TODO: to fully solve https://github.com/oceanprotocol/market/issues/432 // TODO: to fully solve https://github.com/oceanprotocol/market/issues/432
// there are more considerations for users with a wallet connected (wallet network vs. setting network). // there are more considerations for users with a wallet connected (wallet network vs. setting network).
// For now, only show the setting for non-wallet users. // For now, only show the setting for non-wallet users.
return !web3 ? ( return !web3 ? (
<li> <li className={styles.chains}>
<Label htmlFor="">Chain</Label> <Label htmlFor="">Chain</Label>
<div className={styles.buttons}> <BoxSelection
{chains.map((button) => { options={options}
const selected = name="chain"
(config as ConfigHelperConfig).network === button.oceanConfig handleChange={connectOcean}
/>
return (
<Button
key={button.name}
className={`${styles.button} ${selected ? styles.selected : ''}`}
size="small"
style="text"
onClick={() => connectOcean(button.oceanConfig)}
>
{button.name}
<span>{button.label}</span>
</Button>
)
})}
</div>
<FormHelp>Switch the data source for the interface.</FormHelp> <FormHelp>Switch the data source for the interface.</FormHelp>
</li> </li>
) : null ) : null

View File

@ -42,7 +42,7 @@
justify-content: space-between; justify-content: space-between;
} }
.actions span { .walletLogoWrap {
display: block; display: block;
} }
@ -84,3 +84,7 @@
.walletInfo button { .walletInfo button {
margin-top: calc(var(--spacer) / 5) !important; margin-top: calc(var(--spacer) / 5) !important;
} }
.addToken {
margin-left: 0.3rem;
}

View File

@ -1,33 +1,29 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import Button from '../../atoms/Button'
import styles from './Details.module.css'
import { useOcean } from '../../../providers/Ocean'
import Web3Feedback from './Feedback'
import { getProviderInfo, IProviderInfo } from 'web3modal'
import Conversion from '../../atoms/Price/Conversion'
import { formatCurrency } from '@coingecko/cryptoformat' import { formatCurrency } from '@coingecko/cryptoformat'
import { useOcean } from '../../../providers/Ocean'
import { useUserPreferences } from '../../../providers/UserPreferences' import { useUserPreferences } from '../../../providers/UserPreferences'
import Button from '../../atoms/Button'
import AddToken from '../../atoms/AddToken'
import Conversion from '../../atoms/Price/Conversion'
import { useWeb3 } from '../../../providers/Web3' import { useWeb3 } from '../../../providers/Web3'
import { addOceanToWallet } from '../../../utils/web3'
import { Logger } from '@oceanprotocol/lib' import Web3Feedback from './Feedback'
import styles from './Details.module.css'
export default function Details(): ReactElement { export default function Details(): ReactElement {
const { web3Provider, connect, logout, networkData } = useWeb3() const {
web3Provider,
web3ProviderInfo,
connect,
logout,
networkData
} = useWeb3()
const { balance, config } = useOcean() const { balance, config } = useOcean()
const { locale } = useUserPreferences() const { locale } = useUserPreferences()
const [providerInfo, setProviderInfo] = useState<IProviderInfo>()
const [mainCurrency, setMainCurrency] = useState<string>() const [mainCurrency, setMainCurrency] = useState<string>()
// const [portisNetwork, setPortisNetwork] = useState<string>() // const [portisNetwork, setPortisNetwork] = useState<string>()
// Workaround cause getInjectedProviderName() always returns `MetaMask`
// https://github.com/oceanprotocol/market/issues/332
useEffect(() => {
if (!web3Provider) return
const providerInfo = getProviderInfo(web3Provider)
setProviderInfo(providerInfo)
}, [web3Provider])
useEffect(() => { useEffect(() => {
if (!networkData) return if (!networkData) return
@ -61,11 +57,11 @@ export default function Details(): ReactElement {
<li className={styles.actions}> <li className={styles.actions}>
<div title="Connected provider" className={styles.walletInfo}> <div title="Connected provider" className={styles.walletInfo}>
<span> <span className={styles.walletLogoWrap}>
<img className={styles.walletLogo} src={providerInfo?.logo} /> <img className={styles.walletLogo} src={web3ProviderInfo?.logo} />
{providerInfo?.name} {web3ProviderInfo?.name}
</span> </span>
{/* {providerInfo?.name === 'Portis' && ( {/* {web3ProviderInfo?.name === 'Portis' && (
<InputElement <InputElement
name="network" name="network"
type="select" type="select"
@ -75,20 +71,17 @@ export default function Details(): ReactElement {
onChange={handlePortisNetworkChange} onChange={handlePortisNetworkChange}
/> />
)} */} )} */}
{providerInfo?.name === 'MetaMask' && ( {web3ProviderInfo?.name === 'MetaMask' && (
<Button <AddToken
style="text" address={config.oceanTokenAddress}
size="small" symbol={config.oceanTokenSymbol}
onClick={() => { logo="https://raw.githubusercontent.com/oceanprotocol/art/main/logo/token.png"
addOceanToWallet(config, web3Provider) className={styles.addToken}
}} />
>
{`Add ${config.oceanTokenSymbol}`}
</Button>
)} )}
</div> </div>
<p> <p>
{providerInfo?.name === 'Portis' && ( {web3ProviderInfo?.name === 'Portis' && (
<Button <Button
style="text" style="text"
size="small" size="small"

View File

@ -3,7 +3,6 @@ import {
DDO, DDO,
File as FileMetadata, File as FileMetadata,
Logger, Logger,
ServiceType,
publisherTrustedAlgorithm, publisherTrustedAlgorithm,
BestPrice BestPrice
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
@ -37,11 +36,8 @@ import FormStartComputeDataset from './FormComputeDataset'
import styles from './index.module.css' import styles from './index.module.css'
import SuccessConfetti from '../../../atoms/SuccessConfetti' import SuccessConfetti from '../../../atoms/SuccessConfetti'
import Button from '../../../atoms/Button' import Button from '../../../atoms/Button'
import { gql, useQuery } from '@apollo/client'
import { FrePrice } from '../../../../@types/apollo/FrePrice'
import { PoolPrice } from '../../../../@types/apollo/PoolPrice'
import { secondsToString } from '../../../../utils/metadata' import { secondsToString } from '../../../../utils/metadata'
import { getPreviousOrders } from '../../../../utils/subgraph' import { getPreviousOrders, getPrice } from '../../../../utils/subgraph'
const SuccessAction = () => ( const SuccessAction = () => (
<Button style="text" to="/history" size="small"> <Button style="text" to="/history" size="small">
@ -49,23 +45,6 @@ const SuccessAction = () => (
</Button> </Button>
) )
const freQuery = gql`
query AlgorithmFrePrice($datatoken: String) {
fixedRateExchanges(orderBy: id, where: { datatoken: $datatoken }) {
rate
id
}
}
`
const poolQuery = gql`
query AlgorithmPoolPrice($datatoken: String) {
pools(where: { datatokenAddress: $datatoken }) {
spotPrice
consumePrice
}
}
`
export default function Compute({ export default function Compute({
isBalanceSufficient, isBalanceSufficient,
dtBalance, dtBalance,
@ -95,7 +74,6 @@ export default function Compute({
) )
const [algorithmDTBalance, setalgorithmDTBalance] = useState<string>() const [algorithmDTBalance, setalgorithmDTBalance] = useState<string>()
const [algorithmPrice, setAlgorithmPrice] = useState<BestPrice>() const [algorithmPrice, setAlgorithmPrice] = useState<BestPrice>()
const [variables, setVariables] = useState({})
const [ const [
previousAlgorithmOrderId, previousAlgorithmOrderId,
setPreviousAlgorithmOrderId setPreviousAlgorithmOrderId
@ -103,25 +81,6 @@ export default function Compute({
const [datasetTimeout, setDatasetTimeout] = useState<string>() const [datasetTimeout, setDatasetTimeout] = useState<string>()
const [algorithmTimeout, setAlgorithmTimeout] = useState<string>() const [algorithmTimeout, setAlgorithmTimeout] = useState<string>()
/* eslint-disable @typescript-eslint/no-unused-vars */
const {
refetch: refetchFre,
startPolling: startPollingFre,
data: frePrice
} = useQuery<FrePrice>(freQuery, {
variables,
skip: false
})
const {
refetch: refetchPool,
startPolling: startPollingPool,
data: poolPrice
} = useQuery<PoolPrice>(poolQuery, {
variables,
skip: false
})
/* eslint-enable @typescript-eslint/no-unused-vars */
const isComputeButtonDisabled = const isComputeButtonDisabled =
isJobStarting === true || file === null || !ocean || !isBalanceSufficient isJobStarting === true || file === null || !ocean || !isBalanceSufficient
const hasDatatoken = Number(dtBalance) >= 1 const hasDatatoken = Number(dtBalance) >= 1
@ -167,7 +126,7 @@ export default function Compute({
const algorithmQuery = const algorithmQuery =
trustedAlgorithmList.length > 0 ? `(${algoQuerry}) AND` : `` trustedAlgorithmList.length > 0 ? `(${algoQuerry}) AND` : ``
const query = { const query = {
page: 1, offset: 500,
query: { query: {
query_string: { query_string: {
query: `${algorithmQuery} service.attributes.main.type:algorithm -isInPurgatory:true` query: `${algorithmQuery} service.attributes.main.type:algorithm -isInPurgatory:true`
@ -215,40 +174,10 @@ export default function Compute({
setDatasetTimeout(secondsToString(timeout)) setDatasetTimeout(secondsToString(timeout))
}, [ddo]) }, [ddo])
useEffect(() => {
if (
!frePrice ||
frePrice.fixedRateExchanges.length === 0 ||
algorithmPrice.type !== 'exchange'
)
return
setAlgorithmPrice((prevState) => ({
...prevState,
value: frePrice.fixedRateExchanges[0].rate,
address: frePrice.fixedRateExchanges[0].id
}))
}, [frePrice])
useEffect(() => {
if (
!poolPrice ||
poolPrice.pools.length === 0 ||
algorithmPrice.type !== 'pool'
)
return
setAlgorithmPrice((prevState) => ({
...prevState,
value:
poolPrice.pools[0].consumePrice === '-1'
? poolPrice.pools[0].spotPrice
: poolPrice.pools[0].consumePrice
}))
}, [poolPrice])
const initMetadata = useCallback(async (ddo: DDO): Promise<void> => { const initMetadata = useCallback(async (ddo: DDO): Promise<void> => {
if (!ddo) return if (!ddo) return
setAlgorithmPrice(ddo.price) const price = await getPrice(ddo)
setVariables({ datatoken: ddo?.dataToken.toLowerCase() }) setAlgorithmPrice(price)
}, []) }, [])
useEffect(() => { useEffect(() => {
@ -430,7 +359,8 @@ export default function Compute({
Logger.log('[compute] Starting compute job response: ', response) Logger.log('[compute] Starting compute job response: ', response)
setHasPreviousDatasetOrder(true) await checkPreviousOrders(selectedAlgorithmAsset)
await checkPreviousOrders(ddo)
setIsPublished(true) setIsPublished(true)
} catch (error) { } catch (error) {
setError('Failed to start job!') setError('Failed to start job!')

View File

@ -44,7 +44,7 @@ export default function FormEditComputeDataset({
): Promise<AssetSelectionAsset[]> { ): Promise<AssetSelectionAsset[]> {
const source = axios.CancelToken.source() const source = axios.CancelToken.source()
const query = { const query = {
page: 1, offset: 500,
query: { query: {
query_string: { query_string: {
query: `service.attributes.main.type:algorithm -isInPurgatory:true` query: `service.attributes.main.type:algorithm -isInPurgatory:true`

View File

@ -81,6 +81,8 @@ export default function Remove({
const [minOceanAmount, setMinOceanAmount] = useState<string>('0') const [minOceanAmount, setMinOceanAmount] = useState<string>('0')
const [minDatatokenAmount, setMinDatatokenAmount] = useState<string>('0') const [minDatatokenAmount, setMinDatatokenAmount] = useState<string>('0')
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
async function handleRemoveLiquidity() { async function handleRemoveLiquidity() {
setIsLoading(true) setIsLoading(true)
try { try {
@ -157,12 +159,18 @@ export default function Remove({
]) ])
useEffect(() => { useEffect(() => {
const minOceanAmount = const minOceanAmount = new Decimal(amountOcean)
(Number(amountOcean) * (100 - Number(slippage))) / 100 .mul(new Decimal(100).minus(new Decimal(slippage)))
const minDatatokenAmount = .dividedBy(100)
(Number(amountDatatoken) * (100 - Number(slippage))) / 100 .toString()
setMinOceanAmount(`${minOceanAmount}`)
setMinDatatokenAmount(`${minDatatokenAmount}`) const minDatatokenAmount = new Decimal(amountDatatoken)
.mul(new Decimal(100).minus(new Decimal(slippage)))
.dividedBy(100)
.toString()
setMinOceanAmount(minOceanAmount.slice(0, 18))
setMinDatatokenAmount(minDatatokenAmount.slice(0, 18))
}, [slippage, amountOcean, amountDatatoken, isAdvanced]) }, [slippage, amountOcean, amountDatatoken, isAdvanced])
// Set amountPoolShares based on set slider value // Set amountPoolShares based on set slider value
@ -173,9 +181,9 @@ export default function Remove({
const amountPoolShares = new Decimal(e.target.value) const amountPoolShares = new Decimal(e.target.value)
.dividedBy(100) .dividedBy(100)
.mul(new Decimal(poolTokens)) .mul(new Decimal(poolTokens))
.toPrecision(18) // in some cases the returned value contain more than 18 digits which break conversion to wei .toString()
setAmountPoolShares(`${amountPoolShares}`) setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`)
} }
function handleMaxButton(e: ChangeEvent<HTMLInputElement>) { function handleMaxButton(e: ChangeEvent<HTMLInputElement>) {
@ -185,9 +193,9 @@ export default function Remove({
const amountPoolShares = new Decimal(amountMaxPercent) const amountPoolShares = new Decimal(amountMaxPercent)
.dividedBy(100) .dividedBy(100)
.mul(new Decimal(poolTokens)) .mul(new Decimal(poolTokens))
.toPrecision(18) .toString()
setAmountPoolShares(`${amountPoolShares}`) setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`)
} }
function handleAdvancedButton(e: FormEvent<HTMLButtonElement>) { function handleAdvancedButton(e: FormEvent<HTMLButtonElement>) {

View File

@ -7,7 +7,7 @@ import { useAsset } from '../../../../providers/Asset'
export default function Transactions(): ReactElement { export default function Transactions(): ReactElement {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const { ddo } = useAsset() const { price } = useAsset()
function handleClick() { function handleClick() {
setOpen(!open) setOpen(!open)
} }
@ -29,7 +29,7 @@ export default function Transactions(): ReactElement {
</Button> </Button>
</h3> </h3>
{open === true && ( {open === true && (
<PoolTransactions poolAddress={ddo.price?.address} minimal /> <PoolTransactions poolAddress={price?.address} minimal />
)} )}
</div> </div>
) )

View File

@ -95,8 +95,8 @@ export default function Pool(): ReactElement {
const [refreshPool, setRefreshPool] = useState(false) const [refreshPool, setRefreshPool] = useState(false)
const { data: dataLiquidity } = useQuery<PoolLiquidity>(poolLiquidityQuery, { const { data: dataLiquidity } = useQuery<PoolLiquidity>(poolLiquidityQuery, {
variables: { variables: {
id: ddo.price.address.toLowerCase(), id: price.address.toLowerCase(),
shareId: `${ddo.price.address.toLowerCase()}-${ddo.publicKey[0].owner.toLowerCase()}` shareId: `${price.address.toLowerCase()}-${ddo.publicKey[0].owner.toLowerCase()}`
}, },
pollInterval: 5000 pollInterval: 5000
}) })
@ -170,7 +170,8 @@ export default function Pool(): ReactElement {
const totalUserLiquidityInOcean = const totalUserLiquidityInOcean =
userLiquidity?.ocean + userLiquidity?.datatoken * price?.value userLiquidity?.ocean + userLiquidity?.datatoken * price?.value
setTotalUserLiquidityInOcean(totalUserLiquidityInOcean) setTotalUserLiquidityInOcean(totalUserLiquidityInOcean)
const totalLiquidityInOcean = price?.ocean + price?.datatoken * price?.value const totalLiquidityInOcean =
Number(price?.ocean) + Number(price?.datatoken) * Number(price?.value)
setTotalLiquidityInOcean(totalLiquidityInOcean) setTotalLiquidityInOcean(totalLiquidityInOcean)
}, [userLiquidity, price, poolTokens, totalPoolTokens]) }, [userLiquidity, price, poolTokens, totalPoolTokens])

View File

@ -19,7 +19,6 @@ export default function AssetActions(): ReactElement {
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>() const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
const [dtBalance, setDtBalance] = useState<string>() const [dtBalance, setDtBalance] = useState<string>()
const isCompute = Boolean(ddo?.findServiceByType('compute')) const isCompute = Boolean(ddo?.findServiceByType('compute'))
// Get and set user DT balance // Get and set user DT balance
@ -75,10 +74,7 @@ export default function AssetActions(): ReactElement {
} }
] ]
// Check from metadata, cause that is available earlier price?.type === 'pool' &&
const hasPool = ddo?.price?.type === 'pool'
hasPool &&
tabs.push( tabs.push(
{ {
title: 'Pool', title: 'Pool',

View File

@ -27,6 +27,11 @@
margin-right: calc(var(--spacer) / 4); margin-right: calc(var(--spacer) / 4);
} }
.datatoken {
white-space: pre;
margin-right: calc(var(--spacer) / 3);
}
.byline { .byline {
font-size: var(--font-size-small); font-size: var(--font-size-small);
} }
@ -34,3 +39,13 @@
.updated { .updated {
font-size: var(--font-size-mini); font-size: var(--font-size-mini);
} }
.addWrap {
padding-left: calc(var(--spacer) / 5);
border-left: 1px solid var(--border-color);
display: inline-block;
}
.add {
font-size: var(--font-size-mini);
}

View File

@ -3,13 +3,14 @@ import { useAsset } from '../../../providers/Asset'
import { useWeb3 } from '../../../providers/Web3' import { useWeb3 } from '../../../providers/Web3'
import ExplorerLink from '../../atoms/ExplorerLink' import ExplorerLink from '../../atoms/ExplorerLink'
import Publisher from '../../atoms/Publisher' import Publisher from '../../atoms/Publisher'
import AddToken from '../../atoms/AddToken'
import Time from '../../atoms/Time' import Time from '../../atoms/Time'
import styles from './MetaMain.module.css'
import AssetType from '../../atoms/AssetType' import AssetType from '../../atoms/AssetType'
import styles from './MetaMain.module.css'
export default function MetaMain(): ReactElement { export default function MetaMain(): ReactElement {
const { ddo, owner, type } = useAsset() const { ddo, owner, type } = useAsset()
const { networkId } = useWeb3() const { networkId, web3ProviderInfo } = useWeb3()
const isCompute = Boolean(ddo?.findServiceByType('compute')) const isCompute = Boolean(ddo?.findServiceByType('compute'))
const accessType = isCompute ? 'compute' : 'access' const accessType = isCompute ? 'compute' : 'access'
@ -22,6 +23,7 @@ export default function MetaMain(): ReactElement {
className={styles.assetType} className={styles.assetType}
/> />
<ExplorerLink <ExplorerLink
className={styles.datatoken}
networkId={networkId} networkId={networkId}
path={ path={
networkId === 137 || networkId === 1287 networkId === 137 || networkId === 1287
@ -31,6 +33,19 @@ export default function MetaMain(): ReactElement {
> >
{`${ddo?.dataTokenInfo.name}${ddo?.dataTokenInfo.symbol}`} {`${ddo?.dataTokenInfo.name}${ddo?.dataTokenInfo.symbol}`}
</ExplorerLink> </ExplorerLink>
{web3ProviderInfo?.name === 'MetaMask' && (
<span className={styles.addWrap}>
<AddToken
address={ddo?.dataTokenInfo.address}
symbol={ddo?.dataTokenInfo.symbol}
logo="https://raw.githubusercontent.com/oceanprotocol/art/main/logo/datatoken.png"
text={`Add ${ddo?.dataTokenInfo.symbol} to wallet`}
className={styles.add}
minimal
/>
</span>
)}
</header> </header>
<div className={styles.byline}> <div className={styles.byline}>

View File

@ -16,3 +16,9 @@
font-size: var(--font-size-small); font-size: var(--font-size-small);
font-style: italic; font-style: italic;
} }
.loaderWrap {
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -1,17 +1,28 @@
import AssetTeaser from '../molecules/AssetTeaser' import AssetTeaser from '../molecules/AssetTeaser'
import React from 'react' import React, { useEffect, useState } from 'react'
import Pagination from '../molecules/Pagination' import Pagination from '../molecules/Pagination'
import styles from './AssetList.module.css' import styles from './AssetList.module.css'
import { DDO } from '@oceanprotocol/lib' import { DDO } from '@oceanprotocol/lib'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import { getAssetsBestPrices, AssetListPrices } from '../../utils/subgraph'
import Loader from '../atoms/Loader'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
function LoaderArea() {
return (
<div className={styles.loaderWrap}>
<Loader />
</div>
)
}
declare type AssetListProps = { declare type AssetListProps = {
assets: DDO[] assets: DDO[]
showPagination: boolean showPagination: boolean
page?: number page?: number
totalPages?: number totalPages?: number
isLoading?: boolean
onPageChange?: React.Dispatch<React.SetStateAction<number>> onPageChange?: React.Dispatch<React.SetStateAction<number>>
className?: string className?: string
} }
@ -21,9 +32,22 @@ const AssetList: React.FC<AssetListProps> = ({
showPagination, showPagination,
page, page,
totalPages, totalPages,
isLoading,
onPageChange, onPageChange,
className className
}) => { }) => {
const [assetsWithPrices, setAssetWithPrices] = useState<AssetListPrices[]>()
const [loading, setLoading] = useState<boolean>(true)
useEffect(() => {
if (!assets) return
isLoading && setLoading(true)
getAssetsBestPrices(assets).then((asset) => {
setAssetWithPrices(asset)
setLoading(false)
})
}, [assets])
// // This changes the page field inside the query // // This changes the page field inside the query
function handlePageChange(selected: number) { function handlePageChange(selected: number) {
onPageChange(selected + 1) onPageChange(selected + 1)
@ -34,11 +58,19 @@ const AssetList: React.FC<AssetListProps> = ({
[className]: className [className]: className
}) })
return ( return assetsWithPrices &&
!loading &&
(isLoading === undefined || isLoading === false) ? (
<> <>
<div className={styleClasses}> <div className={styleClasses}>
{assets.length > 0 ? ( {assetsWithPrices.length > 0 ? (
assets.map((ddo) => <AssetTeaser ddo={ddo} key={ddo.id} />) assetsWithPrices.map((assetWithPrice) => (
<AssetTeaser
ddo={assetWithPrice.ddo}
price={assetWithPrice.price}
key={assetWithPrice.ddo.id}
/>
))
) : ( ) : (
<div className={styles.empty}>No results found.</div> <div className={styles.empty}>No results found.</div>
)} )}
@ -52,6 +84,8 @@ const AssetList: React.FC<AssetListProps> = ({
/> />
)} )}
</> </>
) : (
<LoaderArea />
) )
} }

View File

@ -2,3 +2,25 @@
text-transform: uppercase; text-transform: uppercase;
color: var(--color-secondary); color: var(--color-secondary);
} }
.computeTableContainer {
display: flex;
align-items: center;
}
.refresh,
.refresh:first-child {
margin-bottom: calc(var(--spacer) / 2);
margin-left: auto;
margin-right: auto;
display: block;
}
.refresh svg {
display: inline-block;
fill: currentColor;
width: 1em;
height: 1em;
margin-right: calc(var(--spacer) / 6);
margin-bottom: -0.1rem;
}

View File

@ -6,6 +6,7 @@ import { DDO, Logger, Service, Provider } from '@oceanprotocol/lib'
import { ComputeJobMetaData } from '../../../../@types/ComputeJobMetaData' import { ComputeJobMetaData } from '../../../../@types/ComputeJobMetaData'
import Dotdotdot from 'react-dotdotdot' import Dotdotdot from 'react-dotdotdot'
import Table from '../../../atoms/Table' import Table from '../../../atoms/Table'
import Button from '../../../atoms/Button'
import { useOcean } from '../../../../providers/Ocean' import { useOcean } from '../../../../providers/Ocean'
import { gql, useQuery } from '@apollo/client' import { gql, useQuery } from '@apollo/client'
import { useWeb3 } from '../../../../providers/Web3' import { useWeb3 } from '../../../../providers/Web3'
@ -13,8 +14,9 @@ import { queryMetadata } from '../../../../utils/aquarius'
import axios, { CancelToken } from 'axios' import axios, { CancelToken } from 'axios'
import { ComputeOrders } from '../../../../@types/apollo/ComputeOrders' import { ComputeOrders } from '../../../../@types/apollo/ComputeOrders'
import Details from './Details' import Details from './Details'
import styles from './index.module.css'
import { ComputeJob } from '@oceanprotocol/lib/dist/node/ocean/interfaces/Compute' import { ComputeJob } from '@oceanprotocol/lib/dist/node/ocean/interfaces/Compute'
import { ReactComponent as Refresh } from '../../../../images/refresh.svg'
import styles from './index.module.css'
const getComputeOrders = gql` const getComputeOrders = gql`
query ComputeOrders($user: String!) { query ComputeOrders($user: String!) {
@ -99,7 +101,7 @@ async function getAssetMetadata(
export default function ComputeJobs(): ReactElement { export default function ComputeJobs(): ReactElement {
const { ocean, account, config } = useOcean() const { ocean, account, config } = useOcean()
const { accountId } = useWeb3() const { accountId } = useWeb3()
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(true)
const [jobs, setJobs] = useState<ComputeJobMetaData[]>([]) const [jobs, setJobs] = useState<ComputeJobMetaData[]>([])
const { data } = useQuery<ComputeOrders>(getComputeOrders, { const { data } = useQuery<ComputeOrders>(getComputeOrders, {
variables: { variables: {
@ -107,139 +109,158 @@ export default function ComputeJobs(): ReactElement {
} }
}) })
useEffect(() => { async function getJobs() {
if (data === undefined || !config?.metadataCacheUri) return if (!ocean || !account) return
async function getJobs() { setIsLoading(true)
if (!ocean || !account) return
setIsLoading(true) const dtList = []
const computeJobs: ComputeJobMetaData[] = []
for (let i = 0; i < data.tokenOrders.length; i++) {
dtList.push(data.tokenOrders[i].datatokenId.address)
}
const queryDtList = JSON.stringify(dtList)
.replace(/,/g, ' ')
.replace(/"/g, '')
.replace(/(\[|\])/g, '')
const dtList = [] try {
const computeJobs: ComputeJobMetaData[] = [] const source = axios.CancelToken.source()
const assets = await getAssetMetadata(
queryDtList,
config.metadataCacheUri,
source.token
)
const providers: Provider[] = []
const serviceEndpoints: string[] = []
for (let i = 0; i < data.tokenOrders.length; i++) { for (let i = 0; i < data.tokenOrders.length; i++) {
dtList.push(data.tokenOrders[i].datatokenId.address) try {
const did = web3.utils
.toChecksumAddress(data.tokenOrders[i].datatokenId.address)
.replace('0x', 'did:op:')
const ddo = assets.filter((x) => x.id === did)[0]
if (!ddo) continue
const service = ddo.service.filter(
(x: Service) => x.index === data.tokenOrders[i].serviceId
)[0]
if (!service || service.type !== 'compute') continue
const { serviceEndpoint } = service
const wasProviderQueried =
serviceEndpoints.filter((x) => x === serviceEndpoint).length > 0
if (wasProviderQueried) continue
serviceEndpoints.push(serviceEndpoint)
} catch (err) {
Logger.error(err)
}
} }
const queryDtList = JSON.stringify(dtList)
.replace(/,/g, ' ')
.replace(/"/g, '')
.replace(/(\[|\])/g, '')
try { try {
const source = axios.CancelToken.source() for (let i = 0; i < serviceEndpoints.length; i++) {
const assets = await getAssetMetadata( const instanceConfig = {
queryDtList, config,
config.metadataCacheUri, web3: config.web3Provider,
source.token logger: Logger,
) ocean: ocean
const providers: Provider[] = [] }
const serviceEndpoints: string[] = [] const provider = await Provider.getInstance(instanceConfig)
for (let i = 0; i < data.tokenOrders.length; i++) { await provider.setBaseUrl(serviceEndpoints[i])
try { const hasSameCompute =
const did = web3.utils providers.filter(
.toChecksumAddress(data.tokenOrders[i].datatokenId.address) (x) => x.computeAddress === provider.computeAddress
.replace('0x', 'did:op:') ).length > 0
if (!hasSameCompute) providers.push(provider)
}
} catch (err) {
Logger.error(err)
}
for (let i = 0; i < providers.length; i++) {
try {
const providerComputeJobs = (await providers[i].computeStatus(
'',
account,
undefined,
undefined,
false
)) as ComputeJob[]
// means the provider uri is not good, so we ignore it and move on
if (!providerComputeJobs) continue
providerComputeJobs.sort((a, b) => {
if (a.dateCreated > b.dateCreated) {
return -1
}
if (a.dateCreated < b.dateCreated) {
return 1
}
return 0
})
for (let j = 0; j < providerComputeJobs.length; j++) {
const job = providerComputeJobs[j]
const did = job.inputDID[0]
const ddo = assets.filter((x) => x.id === did)[0] const ddo = assets.filter((x) => x.id === did)[0]
if (!ddo) continue if (!ddo) continue
const serviceMetadata = ddo.service.filter(
const service = ddo.service.filter( (x: Service) => x.type === 'metadata'
(x: Service) => x.index === data.tokenOrders[i].serviceId
)[0] )[0]
if (!service || service.type !== 'compute') continue const compJob: ComputeJobMetaData = {
const { serviceEndpoint } = service ...job,
assetName: serviceMetadata.attributes.main.name,
const wasProviderQueried = assetDtSymbol: ddo.dataTokenInfo.symbol
serviceEndpoints.filter((x) => x === serviceEndpoint).length > 0
if (wasProviderQueried) continue
serviceEndpoints.push(serviceEndpoint)
} catch (err) {
Logger.error(err)
}
}
try {
for (let i = 0; i < serviceEndpoints.length; i++) {
const instanceConfig = {
config,
web3: config.web3Provider,
logger: Logger,
ocean: ocean
} }
const provider = await Provider.getInstance(instanceConfig) computeJobs.push(compJob)
await provider.setBaseUrl(serviceEndpoints[i])
const hasSameCompute =
providers.filter(
(x) => x.computeAddress === provider.computeAddress
).length > 0
if (!hasSameCompute) providers.push(provider)
} }
} catch (err) { } catch (err) {
Logger.error(err) Logger.error(err)
} }
for (let i = 0; i < providers.length; i++) {
try {
const providerComputeJobs = (await providers[i].computeStatus(
'',
account,
undefined,
undefined,
false
)) as ComputeJob[]
// means the provider uri is not good, so we ignore it and move on
if (!providerComputeJobs) continue
providerComputeJobs.sort((a, b) => {
if (a.dateCreated > b.dateCreated) {
return -1
}
if (a.dateCreated < b.dateCreated) {
return 1
}
return 0
})
for (let j = 0; j < providerComputeJobs.length; j++) {
const job = providerComputeJobs[j]
const did = job.inputDID[0]
const ddo = assets.filter((x) => x.id === did)[0]
if (!ddo) continue
const serviceMetadata = ddo.service.filter(
(x: Service) => x.type === 'metadata'
)[0]
const compJob: ComputeJobMetaData = {
...job,
assetName: serviceMetadata.attributes.main.name,
assetDtSymbol: ddo.dataTokenInfo.symbol
}
computeJobs.push(compJob)
}
} catch (err) {
Logger.error(err)
}
}
setJobs(computeJobs)
} catch (error) {
Logger.log(error.message)
} finally {
setIsLoading(false)
} }
setJobs(computeJobs)
} catch (error) {
Logger.log(error.message)
} finally {
setIsLoading(false)
}
return true
}
useEffect(() => {
if (data === undefined || !config?.metadataCacheUri) {
setIsLoading(false)
return
} }
getJobs() getJobs()
}, [ocean, account, data, config?.metadataCacheUri]) }, [ocean, account, data, config?.metadataCacheUri])
return ( return (
<Table <>
columns={columns} {jobs.length > 0 && (
data={jobs} <Button
isLoading={isLoading} style="text"
defaultSortField="row.dateCreated" size="small"
defaultSortAsc={false} title="Refresh compute jobs"
/> onClick={() => getJobs()}
disabled={isLoading}
className={styles.refresh}
>
<Refresh />
Refresh
</Button>
)}
<Table
columns={columns}
data={jobs}
isLoading={isLoading}
defaultSortField="row.dateCreated"
defaultSortAsc={false}
/>
</>
) )
} }

View File

@ -1,7 +1,6 @@
import { Logger } from '@oceanprotocol/lib' import { Logger } from '@oceanprotocol/lib'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import Loader from '../../atoms/Loader'
import AssetList from '../../organisms/AssetList' import AssetList from '../../organisms/AssetList'
import axios from 'axios' import axios from 'axios'
import { queryMetadata } from '../../../utils/aquarius' import { queryMetadata } from '../../../utils/aquarius'
@ -48,14 +47,13 @@ export default function PublishedList(): ReactElement {
getPublished() getPublished()
}, [accountId, page, config.metadataCacheUri]) }, [accountId, page, config.metadataCacheUri])
return isLoading ? ( return accountId ? (
<Loader />
) : accountId && queryResult ? (
<AssetList <AssetList
assets={queryResult.results} assets={queryResult?.results}
isLoading={isLoading}
showPagination showPagination
page={queryResult.page} page={queryResult?.page}
totalPages={queryResult.totalPages} totalPages={queryResult?.totalPages}
onPageChange={(newPage) => { onPageChange={(newPage) => {
setPage(newPage) setPage(newPage)
}} }}

View File

@ -16,9 +16,3 @@
.section [class*='button'] { .section [class*='button'] {
margin-top: var(--spacer); margin-top: var(--spacer);
} }
.loaderWrap {
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -7,13 +7,13 @@ import {
SearchQuery SearchQuery
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import Container from '../atoms/Container' import Container from '../atoms/Container'
import Loader from '../atoms/Loader'
import { useOcean } from '../../providers/Ocean' import { useOcean } from '../../providers/Ocean'
import Button from '../atoms/Button' import Button from '../atoms/Button'
import Bookmarks from '../molecules/Bookmarks' import Bookmarks from '../molecules/Bookmarks'
import axios from 'axios' import axios from 'axios'
import { queryMetadata } from '../../utils/aquarius' import { queryMetadata } from '../../utils/aquarius'
import Permission from '../organisms/Permission' import Permission from '../organisms/Permission'
import { useWeb3 } from '../../providers/Web3'
const queryHighest = { const queryHighest = {
page: 1, page: 1,
@ -37,14 +37,6 @@ const queryLatest = {
sort: { created: -1 } sort: { created: -1 }
} }
function LoaderArea() {
return (
<div className={styles.loaderWrap}>
<Loader />
</div>
)
}
function SectionQueryResult({ function SectionQueryResult({
title, title,
query, query,
@ -56,11 +48,10 @@ function SectionQueryResult({
}) { }) {
const { config } = useOcean() const { config } = useOcean()
const [result, setResult] = useState<QueryResult>() const [result, setResult] = useState<QueryResult>()
const [loading, setLoading] = useState(true) const { web3Loading } = useWeb3()
useEffect(() => { useEffect(() => {
if (!config?.metadataCacheUri) return if (!config?.metadataCacheUri || web3Loading) return
const source = axios.CancelToken.source() const source = axios.CancelToken.source()
async function init() { async function init() {
@ -70,23 +61,18 @@ function SectionQueryResult({
source.token source.token
) )
setResult(result) setResult(result)
setLoading(false)
} }
init() init()
return () => { return () => {
source.cancel() source.cancel()
} }
}, [config?.metadataCacheUri, query]) }, [config?.metadataCacheUri, query, web3Loading])
return ( return (
<section className={styles.section}> <section className={styles.section}>
<h3>{title}</h3> <h3>{title}</h3>
{loading ? ( <AssetList assets={result?.results} showPagination={false} />
<LoaderArea />
) : (
result && <AssetList assets={result.results} showPagination={false} />
)}
{action && action} {action && action}
</section> </section>
) )

View File

@ -14,7 +14,6 @@ import Button from '../../atoms/Button'
import { FormContent, FormFieldProps } from '../../../@types/Form' import { FormContent, FormFieldProps } from '../../../@types/Form'
import { MetadataPublishFormAlgorithm } from '../../../@types/MetaData' import { MetadataPublishFormAlgorithm } from '../../../@types/MetaData'
import { initialValues as initialValuesAlgorithm } from '../../../models/FormAlgoPublish' import { initialValues as initialValuesAlgorithm } from '../../../models/FormAlgoPublish'
import stylesIndex from './index.module.css' import stylesIndex from './index.module.css'
const query = graphql` const query = graphql`
@ -63,6 +62,24 @@ export default function FormPublish(): ReactElement {
initialValues.dockerImage initialValues.dockerImage
) )
const dockerImageOptions = [
{
name: 'node:latest',
title: 'node:latest',
checked: true
},
{
name: 'python:latest',
title: 'python:latest',
checked: false
},
{
name: 'custom image',
title: 'custom image',
checked: false
}
]
// reset form validation on every mount // reset form validation on every mount
useEffect(() => { useEffect(() => {
setErrors({}) setErrors({})
@ -135,6 +152,11 @@ export default function FormPublish(): ReactElement {
<Field <Field
key={field.name} key={field.name}
{...field} {...field}
options={
field.type === 'boxSelection'
? dockerImageOptions
: field.options
}
component={Input} component={Input}
onChange={(e: ChangeEvent<HTMLInputElement>) => onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleFieldChange(e, field) handleFieldChange(e, field)

View File

@ -7,6 +7,8 @@ import { FormContent, FormFieldProps } from '../../../@types/Form'
import { MetadataPublishFormDataset } from '../../../@types/MetaData' import { MetadataPublishFormDataset } from '../../../@types/MetaData'
import { initialValues as initialValuesDataset } from '../../../models/FormAlgoPublish' import { initialValues as initialValuesDataset } from '../../../models/FormAlgoPublish'
import { useOcean } from '../../../providers/Ocean' import { useOcean } from '../../../providers/Ocean'
import { ReactComponent as Download } from '../../../images/download.svg'
import { ReactComponent as Compute } from '../../../images/compute.svg'
import stylesIndex from './index.module.css' import stylesIndex from './index.module.css'
import styles from './FormPublish.module.css' import styles from './FormPublish.module.css'
@ -61,6 +63,19 @@ export default function FormPublish(): ReactElement {
// setSubmitting(false) // setSubmitting(false)
}, [setErrors, setTouched]) }, [setErrors, setTouched])
const accessTypeOptions = [
{
name: 'Download',
title: 'Download',
icon: <Download />
},
{
name: 'Compute',
title: 'Compute',
icon: <Compute />
}
]
// Manually handle change events instead of using `handleChange` from Formik. // Manually handle change events instead of using `handleChange` from Formik.
// Workaround for default `validateOnChange` not kicking in // Workaround for default `validateOnChange` not kicking in
function handleFieldChange( function handleFieldChange(
@ -94,6 +109,9 @@ export default function FormPublish(): ReactElement {
<Field <Field
key={field.name} key={field.name}
{...field} {...field}
options={
field.type === 'boxSelection' ? accessTypeOptions : field.options
}
component={Input} component={Input}
onChange={(e: ChangeEvent<HTMLInputElement>) => onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleFieldChange(e, field) handleFieldChange(e, field)

View File

@ -23,7 +23,7 @@ export default function PageTemplateAssetDetails({
setPageTitle(isInPurgatory ? '' : title) setPageTitle(isInPurgatory ? '' : title)
}, [ddo, error, isInPurgatory, title]) }, [ddo, error, isInPurgatory, title])
return ddo ? ( return ddo && pageTitle ? (
<> <>
<Page title={pageTitle} uri={uri}> <Page title={pageTitle} uri={uri}>
<Router basepath="/asset"> <Router basepath="/asset">

View File

@ -1,188 +0,0 @@
import React, { ReactElement, useEffect, useState } from 'react'
import { useNavigate } from '@reach/router'
import styles from './filterPrice.module.css'
import classNames from 'classnames/bind'
import {
addExistingParamsToUrl,
FilterByPriceOptions,
FilterByTypeOptions
} from './utils'
import Button from '../../atoms/Button'
const cx = classNames.bind(styles)
const clearFilters = [{ display: 'Clear', value: '' }]
const priceFilterItems = [
{ display: 'fixed price', value: FilterByPriceOptions.Fixed },
{ display: 'dynamic price', value: FilterByPriceOptions.Dynamic }
]
const serviceFilterItems = [
{ display: 'data sets', value: FilterByTypeOptions.Data },
{ display: 'algorithms', value: FilterByTypeOptions.Algorithm }
]
export default function FilterPrice({
priceType,
serviceType,
setPriceType,
setServiceType
}: {
priceType: string
setPriceType: React.Dispatch<React.SetStateAction<string>>
serviceType: string
setServiceType: React.Dispatch<React.SetStateAction<string>>
}): ReactElement {
const navigate = useNavigate()
const [priceSelections, setPriceSelections] = useState<string[]>([])
const [serviceSelections, setServiceSelections] = useState<string[]>([])
async function applyPriceFilter(filterBy: string) {
let urlLocation = await addExistingParamsToUrl(location, 'priceType')
if (filterBy) {
urlLocation = `${urlLocation}&priceType=${filterBy}`
}
setPriceType(filterBy)
navigate(urlLocation)
}
async function applyServiceFilter(filterBy: string) {
let urlLocation = await addExistingParamsToUrl(location, 'serviceType')
if (filterBy && location.search.indexOf('&serviceType') === -1) {
urlLocation = `${urlLocation}&serviceType=${filterBy}`
}
setServiceType(filterBy)
navigate(urlLocation)
}
async function handleSelectedFilter(isSelected: boolean, value: string) {
if (
value === FilterByPriceOptions.Fixed ||
value === FilterByPriceOptions.Dynamic
) {
if (isSelected) {
if (priceSelections.length > 1) {
// both selected -> select the other one
const otherValue = priceFilterItems.find((p) => p.value !== value)
.value
await applyPriceFilter(otherValue)
} else {
// only the current one selected -> deselect it
await applyPriceFilter(undefined)
}
} else {
if (priceSelections.length > 0) {
// one already selected -> both selected
await applyPriceFilter(FilterByPriceOptions.All)
setPriceSelections(priceFilterItems.map((p) => p.value))
} else {
// none selected -> select
await applyPriceFilter(value)
setPriceSelections([value])
}
}
} else {
if (isSelected) {
if (serviceSelections.length > 1) {
const otherValue = serviceFilterItems.find((p) => p.value !== value)
.value
await applyServiceFilter(otherValue)
setServiceSelections([otherValue])
} else {
await applyServiceFilter(undefined)
}
} else {
if (serviceSelections.length) {
await applyServiceFilter(undefined)
setServiceSelections(serviceFilterItems.map((p) => p.value))
} else {
await applyServiceFilter(value)
setServiceSelections([value])
}
}
}
}
async function applyClearFilter() {
let urlLocation = await addExistingParamsToUrl(
location,
'priceType',
'serviceType'
)
urlLocation = `${urlLocation}`
setServiceSelections([])
setPriceSelections([])
setPriceType(undefined)
setServiceType(undefined)
navigate(urlLocation)
}
return (
<div className={styles.filterList}>
{priceFilterItems.map((e, index) => {
const isPriceSelected =
e.value === priceType || priceSelections.includes(e.value)
const selectFilter = cx({
[styles.selected]: isPriceSelected,
[styles.filter]: true
})
return (
<Button
size="small"
style="text"
key={index}
className={selectFilter}
onClick={async () => {
handleSelectedFilter(isPriceSelected, e.value)
}}
>
{e.display}
</Button>
)
})}
{serviceFilterItems.map((e, index) => {
const isServiceSelected =
e.value === serviceType || serviceSelections.includes(e.value)
const selectFilter = cx({
[styles.selected]: isServiceSelected,
[styles.filter]: true
})
return (
<Button
size="small"
style="text"
key={index}
className={selectFilter}
onClick={async () => {
handleSelectedFilter(isServiceSelected, e.value)
}}
>
{e.display}
</Button>
)
})}
{clearFilters.map((e, index) => {
const showClear =
priceSelections.length > 0 || serviceSelections.length > 0
return (
<Button
size="small"
style="text"
key={index}
className={showClear ? styles.showClear : styles.hideClear}
onClick={async () => {
applyClearFilter()
}}
>
{e.display}
</Button>
)
})}
</div>
)
}

View File

@ -47,6 +47,10 @@ button.filter,
margin-bottom: calc(var(--spacer) / 6); margin-bottom: calc(var(--spacer) / 6);
} }
.showClear:hover {
display: inline-flex;
color: var(--color-primary);
}
.showClear { .showClear {
display: inline-flex; display: inline-flex;
text-transform: capitalize; text-transform: capitalize;

View File

@ -0,0 +1,111 @@
import React, { ReactElement, useState } from 'react'
import { useNavigate } from '@reach/router'
import styles from './filterService.module.css'
import classNames from 'classnames/bind'
import { addExistingParamsToUrl, FilterByTypeOptions } from './utils'
import Button from '../../atoms/Button'
const cx = classNames.bind(styles)
const clearFilters = [{ display: 'Clear', value: '' }]
const serviceFilterItems = [
{ display: 'data sets', value: FilterByTypeOptions.Data },
{ display: 'algorithms', value: FilterByTypeOptions.Algorithm }
]
export default function FilterPrice({
serviceType,
setServiceType
}: {
serviceType: string
setServiceType: React.Dispatch<React.SetStateAction<string>>
}): ReactElement {
const navigate = useNavigate()
const [serviceSelections, setServiceSelections] = useState<string[]>([])
async function applyServiceFilter(filterBy: string) {
let urlLocation = await addExistingParamsToUrl(location, 'serviceType')
if (filterBy && location.search.indexOf('&serviceType') === -1) {
urlLocation = `${urlLocation}&serviceType=${filterBy}`
}
setServiceType(filterBy)
navigate(urlLocation)
}
async function handleSelectedFilter(isSelected: boolean, value: string) {
if (isSelected) {
if (serviceSelections.length > 1) {
const otherValue = serviceFilterItems.find((p) => p.value !== value)
.value
await applyServiceFilter(otherValue)
setServiceSelections([otherValue])
} else {
await applyServiceFilter(undefined)
if (serviceSelections.includes(value)) {
serviceSelections.pop()
}
}
} else {
if (serviceSelections.length) {
await applyServiceFilter(undefined)
setServiceSelections(serviceFilterItems.map((p) => p.value))
} else {
await applyServiceFilter(value)
setServiceSelections([value])
}
}
}
async function applyClearFilter() {
let urlLocation = await addExistingParamsToUrl(location, 'serviceType')
urlLocation = `${urlLocation}`
setServiceSelections([])
setServiceType(undefined)
navigate(urlLocation)
}
return (
<div className={styles.filterList}>
{serviceFilterItems.map((e, index) => {
const isServiceSelected =
e.value === serviceType || serviceSelections.includes(e.value)
const selectFilter = cx({
[styles.selected]: isServiceSelected,
[styles.filter]: true
})
return (
<Button
size="small"
style="text"
key={index}
className={selectFilter}
onClick={async () => {
handleSelectedFilter(isServiceSelected, e.value)
}}
>
{e.display}
</Button>
)
})}
{clearFilters.map((e, index) => {
const showClear = serviceSelections.length > 0
return (
<Button
size="small"
style="text"
key={index}
className={showClear ? styles.showClear : styles.hideClear}
onClick={async () => {
applyClearFilter()
}}
>
{e.display}
</Button>
)
})}
</div>
)
}

View File

@ -5,7 +5,7 @@ import SearchBar from '../../molecules/SearchBar'
import AssetList from '../../organisms/AssetList' import AssetList from '../../organisms/AssetList'
import styles from './index.module.css' import styles from './index.module.css'
import queryString from 'query-string' import queryString from 'query-string'
import PriceFilter from './filterPrice' import ServiceFilter from './filterService'
import Sort from './sort' import Sort from './sort'
import { getResults } from './utils' import { getResults } from './utils'
import { navigate } from 'gatsby' import { navigate } from 'gatsby'
@ -22,19 +22,9 @@ export default function SearchPage({
}): ReactElement { }): ReactElement {
const { config } = useOcean() const { config } = useOcean()
const parsed = queryString.parse(location.search) const parsed = queryString.parse(location.search)
const { const { text, owner, tags, page, sort, sortOrder, serviceType } = parsed
text,
owner,
tags,
page,
sort,
sortOrder,
priceType,
serviceType
} = parsed
const [queryResult, setQueryResult] = useState<QueryResult>() const [queryResult, setQueryResult] = useState<QueryResult>()
const [loading, setLoading] = useState<boolean>() const [loading, setLoading] = useState<boolean>()
const [price, setPriceType] = useState<string>(priceType as string)
const [service, setServiceType] = useState<string>(serviceType as string) const [service, setServiceType] = useState<string>(serviceType as string)
const [sortType, setSortType] = useState<string>(sort as string) const [sortType, setSortType] = useState<string>(sort as string)
const [sortDirection, setSortDirection] = useState<string>( const [sortDirection, setSortDirection] = useState<string>(
@ -59,7 +49,6 @@ export default function SearchPage({
tags, tags,
sort, sort,
page, page,
priceType,
serviceType, serviceType,
sortOrder, sortOrder,
config.metadataCacheUri config.metadataCacheUri
@ -82,10 +71,8 @@ export default function SearchPage({
<SearchBar initialValue={(text || owner) as string} /> <SearchBar initialValue={(text || owner) as string} />
)} )}
<div className={styles.row}> <div className={styles.row}>
<PriceFilter <ServiceFilter
priceType={price}
serviceType={service} serviceType={service}
setPriceType={setPriceType}
setServiceType={setServiceType} setServiceType={setServiceType}
/> />
<Sort <Sort
@ -93,25 +80,18 @@ export default function SearchPage({
sortDirection={sortDirection} sortDirection={sortDirection}
setSortType={setSortType} setSortType={setSortType}
setSortDirection={setSortDirection} setSortDirection={setSortDirection}
setPriceType={setPriceType}
setServiceType={setServiceType}
/> />
</div> </div>
</div> </div>
<div className={styles.results}> <div className={styles.results}>
{loading ? ( <AssetList
<Loader /> assets={queryResult?.results}
) : queryResult ? ( showPagination
<AssetList isLoading={loading}
assets={queryResult.results} page={queryResult?.page}
showPagination totalPages={queryResult?.totalPages}
page={queryResult.page} onPageChange={setPage}
totalPages={queryResult.totalPages} />
onPageChange={setPage}
/>
) : (
''
)}
</div> </div>
</> </>
</Permission> </Permission>

View File

@ -3,9 +3,7 @@ import { useNavigate } from '@reach/router'
import { import {
addExistingParamsToUrl, addExistingParamsToUrl,
SortTermOptions, SortTermOptions,
SortValueOptions, SortValueOptions
FilterByPriceOptions,
FilterByTypeOptions
} from './utils' } from './utils'
import Button from '../../atoms/Button' import Button from '../../atoms/Button'
import styles from './sort.module.css' import styles from './sort.module.css'
@ -13,26 +11,18 @@ import classNames from 'classnames/bind'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
const sortItems = [ const sortItems = [{ display: 'Published', value: SortTermOptions.Created }]
{ display: 'Published', value: SortTermOptions.Created },
{ display: 'Liquidity', value: SortTermOptions.Liquidity },
{ display: 'Price', value: SortTermOptions.Price }
]
export default function Sort({ export default function Sort({
sortType, sortType,
setSortType, setSortType,
sortDirection, sortDirection,
setSortDirection, setSortDirection
setPriceType,
setServiceType
}: { }: {
sortType: string sortType: string
setSortType: React.Dispatch<React.SetStateAction<string>> setSortType: React.Dispatch<React.SetStateAction<string>>
sortDirection: string sortDirection: string
setSortDirection: React.Dispatch<React.SetStateAction<string>> setSortDirection: React.Dispatch<React.SetStateAction<string>>
setPriceType: React.Dispatch<React.SetStateAction<string>>
setServiceType: React.Dispatch<React.SetStateAction<string>>
}): ReactElement { }): ReactElement {
const navigate = useNavigate() const navigate = useNavigate()
const directionArrow = String.fromCharCode( const directionArrow = String.fromCharCode(
@ -41,12 +31,7 @@ export default function Sort({
async function sortResults(sortBy?: string, direction?: string) { async function sortResults(sortBy?: string, direction?: string) {
let urlLocation: string let urlLocation: string
if (sortBy) { if (sortBy) {
urlLocation = await addExistingParamsToUrl(location, 'sort', 'priceType')
urlLocation = `${urlLocation}&sort=${sortBy}` urlLocation = `${urlLocation}&sort=${sortBy}`
if (sortBy === SortTermOptions.Liquidity) {
urlLocation = `${urlLocation}&priceType=${FilterByPriceOptions.Dynamic}`
setPriceType(FilterByPriceOptions.Dynamic)
}
setSortType(sortBy) setSortType(sortBy)
} else if (direction) { } else if (direction) {
urlLocation = await addExistingParamsToUrl(location, 'sortOrder') urlLocation = await addExistingParamsToUrl(location, 'sortOrder')

View File

@ -6,8 +6,6 @@ import { MetadataCache, Logger } from '@oceanprotocol/lib'
import queryString from 'query-string' import queryString from 'query-string'
export const SortTermOptions = { export const SortTermOptions = {
Liquidity: 'liquidity',
Price: 'price',
Created: 'created' Created: 'created'
} as const } as const
type SortTermOptions = typeof SortTermOptions[keyof typeof SortTermOptions] type SortTermOptions = typeof SortTermOptions[keyof typeof SortTermOptions]
@ -25,36 +23,12 @@ export const SortValueOptions = {
} as const } as const
type SortValueOptions = typeof SortValueOptions[keyof typeof SortValueOptions] type SortValueOptions = typeof SortValueOptions[keyof typeof SortValueOptions]
export const FilterByPriceOptions = {
Fixed: 'exchange',
Dynamic: 'pool',
All: 'all'
} as const
type FilterByPriceOptions = typeof FilterByPriceOptions[keyof typeof FilterByPriceOptions]
export const FilterByTypeOptions = { export const FilterByTypeOptions = {
Data: 'dataset', Data: 'dataset',
Algorithm: 'algorithm' Algorithm: 'algorithm'
} as const } as const
type FilterByTypeOptions = typeof FilterByTypeOptions[keyof typeof FilterByTypeOptions] type FilterByTypeOptions = typeof FilterByTypeOptions[keyof typeof FilterByTypeOptions]
function addPriceFilterToQuery(sortTerm: string, priceFilter: string): string {
if (priceFilter === FilterByPriceOptions.All) {
sortTerm = priceFilter
? sortTerm === ''
? `(price.type:${FilterByPriceOptions.Fixed} OR price.type:${FilterByPriceOptions.Dynamic})`
: `${sortTerm} AND (price.type:${FilterByPriceOptions.Dynamic} OR price.type:${FilterByPriceOptions.Fixed})`
: sortTerm
} else {
sortTerm = priceFilter
? sortTerm === ''
? `price.type:${priceFilter}`
: `${sortTerm} AND price.type:${priceFilter}`
: sortTerm
}
return sortTerm
}
function addTypeFilterToQuery(sortTerm: string, typeFilter: string): string { function addTypeFilterToQuery(sortTerm: string, typeFilter: string): string {
sortTerm = typeFilter sortTerm = typeFilter
? sortTerm === '' ? sortTerm === ''
@ -64,13 +38,8 @@ function addTypeFilterToQuery(sortTerm: string, typeFilter: string): string {
return sortTerm return sortTerm
} }
function getSortType(sortParam: string): string { function getSortType(): string {
const sortTerm = const sortTerm = SortTermOptions.Created
sortParam === SortTermOptions.Liquidity
? SortElasticTerm.Liquidity
: sortParam === SortTermOptions.Price
? SortElasticTerm.Price
: SortTermOptions.Created
return sortTerm return sortTerm
} }
@ -83,10 +52,9 @@ export function getSearchQuery(
offset?: string, offset?: string,
sort?: string, sort?: string,
sortOrder?: string, sortOrder?: string,
priceType?: string,
serviceType?: string serviceType?: string
): SearchQuery { ): SearchQuery {
const sortTerm = getSortType(sort) const sortTerm = getSortType()
const sortValue = sortOrder === SortValueOptions.Ascending ? 1 : -1 const sortValue = sortOrder === SortValueOptions.Ascending ? 1 : -1
let searchTerm = owner let searchTerm = owner
? `(publicKey.owner:${owner})` ? `(publicKey.owner:${owner})`
@ -97,15 +65,25 @@ export function getSearchQuery(
? // eslint-disable-next-line no-useless-escape ? // eslint-disable-next-line no-useless-escape
`(service.attributes.additionalInformation.categories:\"${categories}\")` `(service.attributes.additionalInformation.categories:\"${categories}\")`
: text || '' : text || ''
// HACK: resolves the case sensitivity related to dataTokenInfo.symbol
searchTerm = '*' + searchTerm.toUpperCase() + '*'
searchTerm = addTypeFilterToQuery(searchTerm, serviceType) searchTerm = addTypeFilterToQuery(searchTerm, serviceType)
searchTerm = addPriceFilterToQuery(searchTerm, priceType)
return { return {
page: Number(page) || 1, page: Number(page) || 1,
offset: Number(offset) || 21, offset: Number(offset) || 21,
query: { query: {
query_string: { query_string: {
query: `${searchTerm} -isInPurgatory:true` query: `${searchTerm} -isInPurgatory:true`,
fields: [
'dataTokenInfo.name',
'dataTokenInfo.symbol',
'service.attributes.main.name',
'service.attributes.main.author',
'service.attributes.additionalInformation.description'
],
default_operator: 'AND'
} }
// ...(owner && { 'publicKey.owner': [owner] }), // ...(owner && { 'publicKey.owner': [owner] }),
// ...(tags && { tags: [tags] }), // ...(tags && { tags: [tags] }),
@ -136,7 +114,6 @@ export async function getResults(
offset?: string offset?: string
sort?: string sort?: string
sortOrder?: string sortOrder?: string
priceType?: string
serviceType?: string serviceType?: string
}, },
metadataCacheUri: string metadataCacheUri: string
@ -150,10 +127,10 @@ export async function getResults(
categories, categories,
sort, sort,
sortOrder, sortOrder,
priceType,
serviceType serviceType
} = params } = params
const metadataCache = new MetadataCache(metadataCacheUri, Logger) const metadataCache = new MetadataCache(metadataCacheUri, Logger)
const searchQuery = getSearchQuery( const searchQuery = getSearchQuery(
text, text,
owner, owner,
@ -163,11 +140,10 @@ export async function getResults(
offset, offset,
sort, sort,
sortOrder, sortOrder,
priceType,
serviceType serviceType
) )
const queryResult = await metadataCache.queryMetadata(searchQuery)
const queryResult = await metadataCache.queryMetadata(searchQuery)
return queryResult return queryResult
} }

View File

@ -12,11 +12,9 @@ import { PurgatoryData } from '@oceanprotocol/lib/dist/node/ddo/interfaces/Purga
import getAssetPurgatoryData from '../utils/purgatory' import getAssetPurgatoryData from '../utils/purgatory'
import axios, { CancelToken } from 'axios' import axios, { CancelToken } from 'axios'
import { retrieveDDO } from '../utils/aquarius' import { retrieveDDO } from '../utils/aquarius'
import { getPrice } from '../utils/subgraph'
import { MetadataMarket } from '../@types/MetaData' import { MetadataMarket } from '../@types/MetaData'
import { useOcean } from './Ocean' import { useOcean } from './Ocean'
import { gql, useQuery } from '@apollo/client'
import { PoolPrice } from '../@types/apollo/PoolPrice'
import { FrePrice } from '../@types/apollo/FrePrice'
interface AssetProviderValue { interface AssetProviderValue {
isInPurgatory: boolean isInPurgatory: boolean
@ -33,26 +31,6 @@ interface AssetProviderValue {
refreshDdo: (token?: CancelToken) => Promise<void> refreshDdo: (token?: CancelToken) => Promise<void>
} }
const poolQuery = gql`
query PoolPrice($datatoken: String) {
pools(where: { datatokenAddress: $datatoken }) {
spotPrice
consumePrice
datatokenReserve
oceanReserve
}
}
`
const freQuery = gql`
query FrePrice($datatoken: String) {
fixedRateExchanges(orderBy: id, where: { datatoken: $datatoken }) {
rate
id
}
}
`
const AssetContext = createContext({} as AssetProviderValue) const AssetContext = createContext({} as AssetProviderValue)
const refreshInterval = 10000 // 10 sec. const refreshInterval = 10000 // 10 sec.
@ -75,68 +53,6 @@ function AssetProvider({
const [owner, setOwner] = useState<string>() const [owner, setOwner] = useState<string>()
const [error, setError] = useState<string>() const [error, setError] = useState<string>()
const [type, setType] = useState<MetadataMain['type']>() const [type, setType] = useState<MetadataMain['type']>()
const [variables, setVariables] = useState({})
/* eslint-disable @typescript-eslint/no-unused-vars */
const {
refetch: refetchFre,
startPolling: startPollingFre,
data: frePrice
} = useQuery<FrePrice>(freQuery, {
variables,
skip: false
})
const {
refetch: refetchPool,
startPolling: startPollingPool,
data: poolPrice
} = useQuery<PoolPrice>(poolQuery, {
variables,
skip: false
})
/* eslint-enable @typescript-eslint/no-unused-vars */
// this is not working as expected, thus we need to fetch both pool and fre
// useEffect(() => {
// if (!ddo || !variables || variables === '') return
// if (ddo.price.type === 'exchange') {
// refetchFre(variables)
// startPollingFre(refreshInterval)
// } else {
// refetchPool(variables)
// startPollingPool(refreshInterval)
// }
// }, [ddo, variables])
useEffect(() => {
if (
!frePrice ||
frePrice.fixedRateExchanges.length === 0 ||
price.type !== 'exchange'
)
return
setPrice((prevState) => ({
...prevState,
value: frePrice.fixedRateExchanges[0].rate,
address: frePrice.fixedRateExchanges[0].id
}))
}, [frePrice])
useEffect(() => {
if (!poolPrice || poolPrice.pools.length === 0 || price.type !== 'pool')
return
setPrice((prevState) => ({
...prevState,
value:
poolPrice.pools[0].consumePrice === '-1'
? poolPrice.pools[0].spotPrice
: poolPrice.pools[0].consumePrice,
ocean: poolPrice.pools[0].oceanReserve,
datatoken: poolPrice.pools[0].datatokenReserve,
isConsumable: poolPrice.pools[0].consumePrice === '-1' ? 'false' : 'true'
}))
}, [poolPrice])
const fetchDdo = async (token?: CancelToken) => { const fetchDdo = async (token?: CancelToken) => {
Logger.log('[asset] Init asset, get DDO') Logger.log('[asset] Init asset, get DDO')
@ -201,10 +117,8 @@ function AssetProvider({
const initMetadata = useCallback(async (ddo: DDO): Promise<void> => { const initMetadata = useCallback(async (ddo: DDO): Promise<void> => {
if (!ddo) return if (!ddo) return
// Set price & metadata from DDO first const returnedPrice = await getPrice(ddo)
// TODO Hacky hack, temporary™: set isConsumable to true by default since Aquarius can't be trusted. setPrice({ ...returnedPrice })
setPrice({ ...ddo.price, isConsumable: 'true' })
setVariables({ datatoken: ddo?.dataToken.toLowerCase() })
// Get metadata from DDO // Get metadata from DDO
const { attributes } = ddo.findServiceByType('metadata') const { attributes } = ddo.findServiceByType('metadata')

View File

@ -8,7 +8,7 @@ import React, {
useCallback useCallback
} from 'react' } from 'react'
import Web3 from 'web3' import Web3 from 'web3'
import Web3Modal from 'web3modal' import Web3Modal, { getProviderInfo, IProviderInfo } from 'web3modal'
import { infuraProjectId as infuraId, portisId } from '../../app.config' import { infuraProjectId as infuraId, portisId } from '../../app.config'
import WalletConnectProvider from '@walletconnect/web3-provider' import WalletConnectProvider from '@walletconnect/web3-provider'
import { Logger } from '@oceanprotocol/lib' import { Logger } from '@oceanprotocol/lib'
@ -24,6 +24,7 @@ interface Web3ProviderValue {
web3: Web3 web3: Web3
web3Provider: any web3Provider: any
web3Modal: Web3Modal web3Modal: Web3Modal
web3ProviderInfo: IProviderInfo
accountId: string accountId: string
networkId: number networkId: number
networkDisplayName: string networkDisplayName: string
@ -106,6 +107,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement {
const [web3, setWeb3] = useState<Web3>() const [web3, setWeb3] = useState<Web3>()
const [web3Provider, setWeb3Provider] = useState<any>() const [web3Provider, setWeb3Provider] = useState<any>()
const [web3Modal, setWeb3Modal] = useState<Web3Modal>() const [web3Modal, setWeb3Modal] = useState<Web3Modal>()
const [web3ProviderInfo, setWeb3ProviderInfo] = useState<IProviderInfo>()
const [networkId, setNetworkId] = useState<number>() const [networkId, setNetworkId] = useState<number>()
const [networkDisplayName, setNetworkDisplayName] = useState<string>() const [networkDisplayName, setNetworkDisplayName] = useState<string>()
const [networkData, setNetworkData] = useState<EthereumListsChain>() const [networkData, setNetworkData] = useState<EthereumListsChain>()
@ -148,7 +150,10 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement {
// Create initial Web3Modal instance // Create initial Web3Modal instance
// ----------------------------------- // -----------------------------------
useEffect(() => { useEffect(() => {
if (web3Modal) return if (web3Modal) {
setWeb3Loading(false)
return
}
async function init() { async function init() {
// note: needs artificial await here so the log message is reached and output // note: needs artificial await here so the log message is reached and output
@ -209,6 +214,18 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement {
getBlock() getBlock()
}, [web3, networkId]) }, [web3, networkId])
// -----------------------------------
// Get and set web3 provider info
// -----------------------------------
// Workaround cause getInjectedProviderName() always returns `MetaMask`
// https://github.com/oceanprotocol/market/issues/332
useEffect(() => {
if (!web3Provider) return
const providerInfo = getProviderInfo(web3Provider)
setWeb3ProviderInfo(providerInfo)
}, [web3Provider])
// ----------------------------------- // -----------------------------------
// Logout helper // Logout helper
// ----------------------------------- // -----------------------------------
@ -255,6 +272,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement {
web3, web3,
web3Provider, web3Provider,
web3Modal, web3Modal,
web3ProviderInfo,
accountId, accountId,
networkId, networkId,
networkDisplayName, networkDisplayName,

View File

@ -10,7 +10,7 @@ import {
SearchQuery SearchQuery
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSelection' import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSelection'
import { PriceList, getAssetPrices } from './subgraph' import { PriceList, getAssetsPriceList } from './subgraph'
import axios, { CancelToken, AxiosResponse } from 'axios' import axios, { CancelToken, AxiosResponse } from 'axios'
// TODO: import directly from ocean.js somehow. // TODO: import directly from ocean.js somehow.
@ -113,7 +113,7 @@ export async function transformDDOToAssetSelection(
): Promise<AssetSelectionAsset[]> { ): Promise<AssetSelectionAsset[]> {
const source = axios.CancelToken.source() const source = axios.CancelToken.source()
const didList: string[] = [] const didList: string[] = []
const priceList: PriceList = await getAssetPrices(ddoList) const priceList: PriceList = await getAssetsPriceList(ddoList)
const symbolList: any = {} const symbolList: any = {}
for (const ddo of ddoList) { for (const ddo of ddoList) {
didList.push(ddo.id) didList.push(ddo.id)

View File

@ -1,14 +1,32 @@
import { gql, DocumentNode, ApolloQueryResult } from '@apollo/client' import { gql, DocumentNode, ApolloQueryResult } from '@apollo/client'
import { DDO } from '@oceanprotocol/lib' import { DDO, BestPrice } from '@oceanprotocol/lib'
import { getApolloClientInstance } from '../providers/ApolloClientProvider' import { getApolloClientInstance } from '../providers/ApolloClientProvider'
import {
AssetsPoolPrice,
AssetsPoolPrice_pools as AssetsPoolPricePools
} from '../@types/apollo/AssetsPoolPrice'
import {
AssetsFrePrice,
AssetsFrePrice_fixedRateExchanges as AssetsFrePriceFixedRateExchanges
} from '../@types/apollo/AssetsFrePrice'
import { AssetPreviousOrder } from '../@types/apollo/AssetPreviousOrder'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
export interface PriceList { export interface PriceList {
[key: string]: string [key: string]: string
} }
const freQuery = gql` export interface AssetListPrices {
query AssetFrePrice($datatoken_in: [String!]) { ddo: DDO
price: BestPrice
}
interface DidAndDatatokenMap {
[name: string]: string
}
const FreQuery = gql`
query AssetsFrePrice($datatoken_in: [String!]) {
fixedRateExchanges(orderBy: id, where: { datatoken_in: $datatoken_in }) { fixedRateExchanges(orderBy: id, where: { datatoken_in: $datatoken_in }) {
rate rate
id id
@ -20,18 +38,42 @@ const freQuery = gql`
} }
` `
const poolQuery = gql` const AssetFreQuery = gql`
query AssetPoolPrice($datatokenAddress_in: [String!]) { query AssetFrePrice($datatoken: String) {
pools(where: { datatokenAddress_in: $datatokenAddress_in }) { fixedRateExchanges(orderBy: id, where: { datatoken: $datatoken }) {
spotPrice rate
consumePrice
id id
datatokenAddress
} }
} }
` `
const previousOrderQuery = gql` const PoolQuery = gql`
query AssetsPoolPrice($datatokenAddress_in: [String!]) {
pools(where: { datatokenAddress_in: $datatokenAddress_in }) {
id
spotPrice
consumePrice
datatokenAddress
datatokenReserve
oceanReserve
}
}
`
const AssetPoolPriceQuerry = gql`
query AssetPoolPrice($datatokenAddress: String) {
pools(where: { datatokenAddress: $datatokenAddress }) {
id
spotPrice
consumePrice
datatokenAddress
datatokenReserve
oceanReserve
}
}
`
const PreviousOrderQuery = gql`
query AssetPreviousOrder($id: String!, $account: String!) { query AssetPreviousOrder($id: String!, $account: String!) {
tokenOrders( tokenOrders(
first: 1 first: 1
@ -53,7 +95,8 @@ async function fetchData(
const client = getApolloClientInstance() const client = getApolloClientInstance()
const response = await client.query({ const response = await client.query({
query: query, query: query,
variables: variables variables: variables,
fetchPolicy: 'no-cache'
}) })
return response return response
} catch (error) { } catch (error) {
@ -70,19 +113,18 @@ export async function getPreviousOrders(
id: id, id: id,
account: account account: account
} }
const fetchedPreviousOrders: any = await fetchData( const fetchedPreviousOrders: ApolloQueryResult<AssetPreviousOrder> = await fetchData(
previousOrderQuery, PreviousOrderQuery,
variables variables
) )
if (fetchedPreviousOrders.data?.tokenOrders?.length === 0) return null if (fetchedPreviousOrders.data?.tokenOrders?.length === 0) return null
if (assetTimeout === '0') { if (assetTimeout === '0') {
return fetchedPreviousOrders?.data?.tokenOrders[0]?.tx return fetchedPreviousOrders?.data?.tokenOrders[0]?.tx
} else { } else {
const expiry = new BigNumber( const expiry =
fetchedPreviousOrders?.data?.tokenOrders[0]?.timestamp fetchedPreviousOrders?.data?.tokenOrders[0]?.timestamp * 1000 +
).plus(assetTimeout) Number(assetTimeout) * 1000
const unixTime = new BigNumber(Math.floor(Date.now() / 1000)) if (Date.now() <= expiry) {
if (unixTime.isLessThan(expiry)) {
return fetchedPreviousOrders?.data?.tokenOrders[0]?.tx return fetchedPreviousOrders?.data?.tokenOrders[0]?.tx
} else { } else {
return null return null
@ -90,9 +132,63 @@ export async function getPreviousOrders(
} }
} }
export async function getAssetPrices(assets: DDO[]): Promise<PriceList> { function transformPriceToBestPrice(
const priceList: PriceList = {} frePrice: AssetsFrePriceFixedRateExchanges[],
const didDTMap: any = {} poolPrice: AssetsPoolPricePools[]
) {
if (poolPrice?.length > 0) {
const price: BestPrice = {
type: 'pool',
address: poolPrice[0]?.id,
value:
poolPrice[0]?.consumePrice === '-1'
? poolPrice[0]?.spotPrice
: poolPrice[0]?.consumePrice,
ocean: poolPrice[0]?.oceanReserve,
datatoken: poolPrice[0]?.datatokenReserve,
pools: [poolPrice[0]?.id],
isConsumable: poolPrice[0]?.consumePrice === '-1' ? 'false' : 'true'
}
return price
} else if (frePrice?.length > 0) {
// TODO Hacky hack, temporary™: set isConsumable to true for fre assets.
// isConsumable: 'true'
const price: BestPrice = {
type: 'exchange',
value: frePrice[0]?.rate,
address: frePrice[0]?.id,
exchange_id: frePrice[0]?.id,
ocean: 0,
datatoken: 0,
pools: [],
isConsumable: 'true'
}
return price
} else {
const price: BestPrice = {
type: '',
value: 0,
address: '',
exchange_id: '',
ocean: 0,
datatoken: 0,
pools: [],
isConsumable: 'false'
}
return price
}
}
async function getAssetsPoolsExchangesAndDatatokenMap(
assets: DDO[]
): Promise<
[
ApolloQueryResult<AssetsPoolPrice>,
ApolloQueryResult<AssetsFrePrice>,
DidAndDatatokenMap
]
> {
const didDTMap: DidAndDatatokenMap = {}
const dataTokenList: string[] = [] const dataTokenList: string[] = []
for (const ddo of assets) { for (const ddo of assets) {
@ -105,16 +201,100 @@ export async function getAssetPrices(assets: DDO[]): Promise<PriceList> {
const poolVariables = { const poolVariables = {
datatokenAddress_in: dataTokenList datatokenAddress_in: dataTokenList
} }
const poolPriceResponse: any = await fetchData(poolQuery, poolVariables)
const poolPriceResponse: ApolloQueryResult<AssetsPoolPrice> = await fetchData(
PoolQuery,
poolVariables
)
const frePriceResponse: ApolloQueryResult<AssetsFrePrice> = await fetchData(
FreQuery,
freVariables
)
return [poolPriceResponse, frePriceResponse, didDTMap]
}
export async function getAssetsPriceList(assets: DDO[]): Promise<PriceList> {
const priceList: PriceList = {}
const values: [
ApolloQueryResult<AssetsPoolPrice>,
ApolloQueryResult<AssetsFrePrice>,
DidAndDatatokenMap
] = await getAssetsPoolsExchangesAndDatatokenMap(assets)
const poolPriceResponse = values[0]
const frePriceResponse = values[1]
const didDTMap: DidAndDatatokenMap = values[2]
for (const poolPrice of poolPriceResponse.data?.pools) { for (const poolPrice of poolPriceResponse.data?.pools) {
priceList[didDTMap[poolPrice.datatokenAddress]] = priceList[didDTMap[poolPrice.datatokenAddress]] =
poolPrice.consumePrice === '-1' poolPrice.consumePrice === '-1'
? poolPrice.spotPrice ? poolPrice.spotPrice
: poolPrice.consumePrice : poolPrice.consumePrice
} }
const frePriceResponse: any = await fetchData(freQuery, freVariables)
for (const frePrice of frePriceResponse.data?.fixedRateExchanges) { for (const frePrice of frePriceResponse.data?.fixedRateExchanges) {
priceList[didDTMap[frePrice.datatoken?.address]] = frePrice.rate priceList[didDTMap[frePrice.datatoken?.address]] = frePrice.rate
} }
return priceList return priceList
} }
export async function getPrice(asset: DDO): Promise<BestPrice> {
const freVariables = {
datatoken: asset?.dataToken.toLowerCase()
}
const poolVariables = {
datatokenAddress: asset?.dataToken.toLowerCase()
}
const poolPriceResponse: ApolloQueryResult<AssetsPoolPrice> = await fetchData(
AssetPoolPriceQuerry,
poolVariables
)
const frePriceResponse: ApolloQueryResult<AssetsFrePrice> = await fetchData(
AssetFreQuery,
freVariables
)
const bestPrice: BestPrice = transformPriceToBestPrice(
frePriceResponse.data.fixedRateExchanges,
poolPriceResponse.data.pools
)
return bestPrice
}
export async function getAssetsBestPrices(
assets: DDO[]
): Promise<AssetListPrices[]> {
const assetsWithPrice: AssetListPrices[] = []
const values: [
ApolloQueryResult<AssetsPoolPrice>,
ApolloQueryResult<AssetsFrePrice>,
DidAndDatatokenMap
] = await getAssetsPoolsExchangesAndDatatokenMap(assets)
const poolPriceResponse = values[0]
const frePriceResponse = values[1]
for (const ddo of assets) {
const dataToken = ddo.dataToken.toLowerCase()
const poolPrice: AssetsPoolPricePools[] = []
const frePrice: AssetsFrePriceFixedRateExchanges[] = []
const pool = poolPriceResponse.data?.pools.find(
(pool: any) => pool.datatokenAddress === dataToken
)
pool && poolPrice.push(pool)
const fre = frePriceResponse.data?.fixedRateExchanges.find(
(fre: any) => fre.datatoken.address === dataToken
)
fre && frePrice.push(fre)
const bestPrice = transformPriceToBestPrice(frePrice, poolPrice)
assetsWithPrice.push({
ddo: ddo,
price: bestPrice
})
}
return assetsWithPrice
}

View File

@ -1,4 +1,4 @@
import { Logger, ConfigHelperConfig } from '@oceanprotocol/lib' import { Logger } from '@oceanprotocol/lib'
export interface EthereumListsChain { export interface EthereumListsChain {
name: string name: string
@ -79,32 +79,33 @@ export function addCustomNetwork(
) )
} }
export function addOceanToWallet( export async function addTokenToWallet(
config: ConfigHelperConfig, web3Provider: any,
web3Provider: any address: string,
): void { symbol: string,
logo?: string
): Promise<void> {
const image =
logo ||
'https://raw.githubusercontent.com/oceanprotocol/art/main/logo/token.png'
const tokenMetadata = { const tokenMetadata = {
type: 'ERC20', type: 'ERC20',
options: { options: { address, symbol, image, decimals: 18 }
address: config.oceanTokenAddress,
symbol: config.oceanTokenSymbol,
decimals: 18,
image:
'https://raw.githubusercontent.com/oceanprotocol/art/main/logo/token.png'
}
} }
web3Provider.sendAsync( web3Provider.sendAsync(
{ {
method: 'wallet_watchAsset', method: 'wallet_watchAsset',
params: tokenMetadata, params: tokenMetadata,
id: Math.round(Math.random() * 100000) id: Math.round(Math.random() * 100000)
}, },
(err: string, added: any) => { (err: { code: number; message: string }, added: any) => {
if (err || 'error' in added) { if (err || 'error' in added) {
Logger.error( Logger.error(
`Couldn't add ${tokenMetadata.options.symbol} (${ `Couldn't add ${tokenMetadata.options.symbol} (${
tokenMetadata.options.address tokenMetadata.options.address
}) to MetaMask, error: ${err || added.error}` }) to MetaMask, error: ${err.message || added.error}`
) )
} else { } else {
Logger.log( Logger.log(