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

merged multinetwork into poc-urql

This commit is contained in:
Bogdan Fazakas 2021-07-05 14:27:43 +03:00
commit 8d50eb92c0
45 changed files with 26442 additions and 293 deletions

View File

@ -1,15 +1,27 @@
# Default network, possible values: # Default network, possible values:
# "development", "ropsten", "rinkeby", "mainnet", "polygon", "moonbeamalpha" # "development", "ropsten", "rinkeby", "mainnet", "polygon", "moonbeamalpha",
# "gaiaxtestnet", "mumbai", "bsc"
GATSBY_NETWORK="rinkeby" GATSBY_NETWORK="rinkeby"
## Define a GATSBY_RBAC_URL to implement permission based restrictions
#GATSBY_RBAC_URL="http://localhost:3000"
#GATSBY_INFURA_PROJECT_ID="xxx" #GATSBY_INFURA_PROJECT_ID="xxx"
#GATSBY_MARKET_FEE_ADDRESS="0xxx" #GATSBY_MARKET_FEE_ADDRESS="0xxx"
#GATSBY_ANALYTICS_ID="xxx"
#GATSBY_PORTIS_ID="xxx" #GATSBY_PORTIS_ID="xxx"
#
# ADVANCED SETTINGS
#
# Toggle pricing options presented during price creation
#GATSBY_ALLOW_FIXED_PRICING="true" #GATSBY_ALLOW_FIXED_PRICING="true"
#GATSBY_ALLOW_DYNAMIC_PRICING="true" #GATSBY_ALLOW_DYNAMIC_PRICING="true"
#GATSBY_ALLOW_FREE_PRICING="false"
# Define RBAC server URL to implement permission based restrictions
#GATSBY_RBAC_URL="http://localhost:3000"
# Enables another asset editing button holder further advanced settings
#GATSBY_ALLOW_ADVANCED_SETTINGS="true" #GATSBY_ALLOW_ADVANCED_SETTINGS="true"
# Allow/Deny Lists
#GATSBY_CREDENTIAL_TYPE="address" #GATSBY_CREDENTIAL_TYPE="address"

View File

@ -26,6 +26,7 @@
- [⬆️ Deployment](#-deployment) - [⬆️ Deployment](#-deployment)
- [💖 Contributing](#-contributing) - [💖 Contributing](#-contributing)
- [🍴 Forking](#-forking) - [🍴 Forking](#-forking)
- [💻 Advanced Features](#-advanced-features)
- [🏛 License](#-license) - [🏛 License](#-license)
## 🏄 Get Started ## 🏄 Get Started
@ -374,6 +375,16 @@ Additionally, we would also advise that your retain the text saying "Powered by
Everything else is made open according to the apache2 license. We look forward to seeing your data marketplace! Everything else is made open according to the apache2 license. We look forward to seeing your data marketplace!
## 💻 Advanced Features
Ocean Market also includes a number of advanced features that are suitable for an enterprise data market, such as:
- Role based access control
- Allow and deny lists
- Free pricing
[See our seperate guide on advanced features](docs/advancedSettings.md)
## 🏛 License ## 🏛 License
```text ```text

View File

@ -8,7 +8,7 @@ module.exports = {
chainIds: [1, 137], chainIds: [1, 137],
// List of all supported chainIds. Used to populate the Chains user preferences list. // List of all supported chainIds. Used to populate the Chains user preferences list.
chainIdsSupported: [1, 3, 4, 137, 1287], chainIdsSupported: [1, 3, 4, 137, 1287, 56],
rbacUrl: process.env.GATSBY_RBAC_URL, rbacUrl: process.env.GATSBY_RBAC_URL,
@ -47,10 +47,11 @@ module.exports = {
// Wallets // Wallets
portisId: process.env.GATSBY_PORTIS_ID || 'xxx', portisId: process.env.GATSBY_PORTIS_ID || 'xxx',
// Used to show or hide the fixed and dynamic price options // Used to show or hide the fixed, dynamic or free price options
// tab to publishers during the price creation. // tab to publishers during the price creation.
allowFixedPricing: process.env.GATSBY_ALLOW_FIXED_PRICING || 'true', allowFixedPricing: process.env.GATSBY_ALLOW_FIXED_PRICING || 'true',
allowDynamicPricing: process.env.GATSBY_ALLOW_DYNAMIC_PRICING || 'true', allowDynamicPricing: process.env.GATSBY_ALLOW_DYNAMIC_PRICING || 'true',
allowFreePricing: process.env.GATSBY_ALLOW_FREE_PRICING || 'false',
// Used to show or hide advanced settings button in asset details page // Used to show or hide advanced settings button in asset details page
allowAdvancedSettings: process.env.GATSBY_ALLOW_ADVANCED_SETTINGS || 'false', allowAdvancedSettings: process.env.GATSBY_ALLOW_ADVANCED_SETTINGS || 'false',

View File

@ -21,6 +21,10 @@
"communityFee": "Explain community fee...", "communityFee": "Explain community fee...",
"marketplaceFee": "Explain marketplace fee..." "marketplaceFee": "Explain marketplace fee..."
} }
},
"free": {
"title": "Free",
"info": "Set your data set as free. The datatoken for this data set will be given for free via creating a faucet."
} }
}, },
"pool": { "pool": {

29
docs/advancedSettings.md Normal file
View File

@ -0,0 +1,29 @@
# Advanced Settings
**Table of Contents**
- [Role based Access Control](#rbac-settings)
- [Allow and Deny lists](#allow-and-deny-list-settings)
- [Free Pricing](#free-pricing-settings)
## RBAC settings
- Setup and host the Ocean role based access control (RBAC) server. Follow the instructions in the [RBAC repository](https://github.com/oceanprotocol/RBAC-Server)
- The RBAC server can store roles in [Keycloak](https://www.keycloak.org/) or a json file.
- In your .env file, set the value of the `GATSBY_RBAC_URL` environmental variable to the URL of the Ocean RBAC server that you have hosted, e.g. `GATSBY_RBAC_URL= "http://localhost:3000"`
- Users of your marketplace will now require the correct role ("user", "consumer", "publisher") to access features in your marketplace. The market will check the role that has been allocated to the user based on the address that they have connected to the market with.
- The following features have been wrapped in the `Permission` component and will be restricted once the `GATSBY_RBAC_URL` has been defined:
- Viewing or searching datasets requires the user to have permison to `browse`
- Purchasing or trading a datatoken, or adding liquidity to a pool require the user to have permison to `consume`
- Publishing a dataset requires the user to have permison to `publish`
- You can change the permission resrictions by either removing the `Permission` component or passing in a different eventType prop e.g. `<Permission eventType="browse">`.
## Allow and Deny List Settings
- To enable allow and deny lists you need to add the following environmental variable to your .env file: `GATSBY_ALLOW_ADVANCED_SETTINGS="true"`
- Publishers in your market will now have the ability to restrict who can consume their datasets.
## Free Pricing Settings
- To allow publishers to set pricing as "Free" you need to add the following environmental variable to your .env file: `GATSBY_ALLOW_FREE_PRICING="true"`
- This allocates the datatokens to the [dispenser contract](https://github.com/oceanprotocol/contracts/blob/main/contracts/dispenser/Dispenser.sol) which dispenses data tokens to users for free. Publishers in your market will now be able to offer their datasets to users for free (excluding gas costs).

25960
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@
"@coingecko/cryptoformat": "^0.4.2", "@coingecko/cryptoformat": "^0.4.2",
"@loadable/component": "^5.15.0", "@loadable/component": "^5.15.0",
"@oceanprotocol/art": "^3.0.0", "@oceanprotocol/art": "^3.0.0",
"@oceanprotocol/lib": "^0.15.1", "@oceanprotocol/lib": "^0.16.1",
"@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/typographies": "^0.1.0",
"@portis/web3": "^4.0.4", "@portis/web3": "^4.0.4",
"@sindresorhus/slugify": "^2.1.0", "@sindresorhus/slugify": "^2.1.0",
@ -60,7 +60,6 @@
"gatsby-transformer-remark": "^2.16.1", "gatsby-transformer-remark": "^2.16.1",
"gatsby-transformer-sharp": "^2.12.1", "gatsby-transformer-sharp": "^2.12.1",
"graphql": "14.7.0", "graphql": "14.7.0",
"graphql-schema-typescript": "^1.5.2",
"is-url-superb": "^6.0.0", "is-url-superb": "^6.0.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",

View File

@ -21,6 +21,8 @@ interface ButtonBuyProps {
onClick?: (e: FormEvent<HTMLButtonElement>) => void onClick?: (e: FormEvent<HTMLButtonElement>) => void
stepText?: string stepText?: string
type?: 'submit' type?: 'submit'
priceType?: string
algorithmPriceType?: string
} }
function getConsumeHelpText( function getConsumeHelpText(
@ -87,15 +89,21 @@ export default function ButtonBuy({
onClick, onClick,
stepText, stepText,
isLoading, isLoading,
type type,
priceType,
algorithmPriceType
}: ButtonBuyProps): ReactElement { }: ButtonBuyProps): ReactElement {
const buttonText = const buttonText =
action === 'download' action === 'download'
? hasPreviousOrder ? hasPreviousOrder
? 'Download' ? 'Download'
: priceType === 'free'
? 'Get'
: `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}` : `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}`
: hasPreviousOrder && hasPreviousOrderSelectedComputeAsset : hasPreviousOrder && hasPreviousOrderSelectedComputeAsset
? 'Start Compute Job' ? 'Start Compute Job'
: priceType === 'free' && algorithmPriceType === 'free'
? 'Order Compute Job'
: `Buy Compute Job` : `Buy Compute Job`
return ( return (

View File

@ -42,15 +42,20 @@ export default function PriceUnit({
return ( return (
<div className={styleClasses}> <div className={styleClasses}>
<div> {type && type === 'free' ? (
{Number.isNaN(Number(price)) ? '-' : formatPrice(price, locale)}{' '} <div> Free </div>
<span className={styles.symbol}>{symbol || 'OCEAN'}</span> ) : (
{type && type === 'pool' && ( <>
<Badge label="pool" className={styles.badge} /> <div>
)} {Number.isNaN(Number(price)) ? '-' : formatPrice(price, locale)}{' '}
</div> <span className={styles.symbol}>{symbol || 'OCEAN'}</span>
{type && type === 'pool' && (
{conversion && <Conversion price={price} />} <Badge label="pool" className={styles.badge} />
)}
</div>
{conversion && <Conversion price={price} />}
</>
)}
</div> </div>
) )
} }

View File

@ -16,7 +16,7 @@ export default function Price({
small?: boolean small?: boolean
conversion?: boolean conversion?: boolean
}): ReactElement { }): ReactElement {
return price?.value ? ( return price?.value || price?.type === 'free' ? (
<PriceUnit <PriceUnit
price={`${price.value}`} price={`${price.value}`}
className={className} className={className}

View File

@ -107,7 +107,12 @@ export default function AssetSelection({
</Dotdotdot> </Dotdotdot>
</label> </label>
<PriceUnit price={asset.price} small className={styles.price} /> <PriceUnit
price={asset.price}
type={asset.price === '0' ? 'free' : undefined}
small
className={styles.price}
/>
</div> </div>
)) ))
)} )}

View File

@ -23,7 +23,11 @@ export default function SearchBar({
e.preventDefault() e.preventDefault()
if (value === '') value = ' ' if (value === '') value = ' '
const urlEncodedValue = encodeURIComponent(value) const urlEncodedValue = encodeURIComponent(value)
const url = await addExistingParamsToUrl(location, 'text') const url = await addExistingParamsToUrl(location, [
'text',
'owner',
'tags'
])
navigate(`${url}&text=${urlEncodedValue}`) navigate(`${url}&text=${urlEncodedValue}`)
} }
@ -31,7 +35,11 @@ export default function SearchBar({
const searchParams = new URLSearchParams(window.location.href) const searchParams = new URLSearchParams(window.location.href)
const text = searchParams.get('text') const text = searchParams.get('text')
if (text !== ('' || undefined || null)) { if (text !== ('' || undefined || null)) {
const url = await addExistingParamsToUrl(location, 'text') const url = await addExistingParamsToUrl(location, [
'text',
'owner',
'tags'
])
navigate(`${url}&text=%20`) navigate(`${url}&text=%20`)
} }
} }

View File

@ -166,6 +166,8 @@ export default function FormStartCompute({
stepText={stepText} stepText={stepText}
isLoading={isLoading} isLoading={isLoading}
type="submit" type="submit"
priceType={price?.type}
algorithmPriceType={algorithmPrice?.type}
/> />
</Form> </Form>
) )

View File

@ -157,7 +157,9 @@ export default function Compute({
source.token source.token
) )
setDdoAlgorithmList(gueryResults.results) setDdoAlgorithmList(gueryResults.results)
const datasetComputeService = ddo.findServiceByType('compute')
algorithmSelectionList = await transformDDOToAssetSelection( algorithmSelectionList = await transformDDOToAssetSelection(
datasetComputeService?.serviceEndpoint,
gueryResults.results, gueryResults.results,
appConfig.metadataCacheUri, appConfig.metadataCacheUri,
[] []
@ -384,7 +386,10 @@ export default function Compute({
text="This algorithm has been set to private by the publisher and can't be downloaded. You can run it against any allowed data sets though!" text="This algorithm has been set to private by the publisher and can't be downloaded. You can run it against any allowed data sets though!"
state="info" state="info"
/> />
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} /> <AlgorithmDatasetsListForCompute
algorithmDid={ddo.id}
dataset={ddo}
/>
</> </>
) : ( ) : (
<Formik <Formik

View File

@ -12,6 +12,7 @@ import { useWeb3 } from '../../../providers/Web3'
import { usePricing } from '../../../hooks/usePricing' import { usePricing } from '../../../hooks/usePricing'
import { useConsume } from '../../../hooks/useConsume' import { useConsume } from '../../../hooks/useConsume'
import ButtonBuy from '../../atoms/ButtonBuy' import ButtonBuy from '../../atoms/ButtonBuy'
import { secondsToString } from '../../../utils/metadata'
import AlgorithmDatasetsListForCompute from '../AssetContent/AlgorithmDatasetsListForCompute' import AlgorithmDatasetsListForCompute from '../AssetContent/AlgorithmDatasetsListForCompute'
import styles from './Consume.module.css' import styles from './Consume.module.css'
@ -157,10 +158,11 @@ export default function Consume({
dtSymbol={ddo.dataTokenInfo?.symbol} dtSymbol={ddo.dataTokenInfo?.symbol}
dtBalance={dtBalance} dtBalance={dtBalance}
onClick={handleConsume} onClick={handleConsume}
assetTimeout={assetTimeout} assetTimeout={secondsToString(parseInt(assetTimeout))}
assetType={type} assetType={type}
stepText={consumeStepText || pricingStepText} stepText={consumeStepText || pricingStepText}
isLoading={pricingIsLoading || isLoading} isLoading={pricingIsLoading || isLoading}
priceType={price?.type}
/> />
) )
@ -176,7 +178,7 @@ export default function Consume({
</div> </div>
</div> </div>
{type === 'algorithm' && ( {type === 'algorithm' && (
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} /> <AlgorithmDatasetsListForCompute algorithmDid={ddo.id} dataset={ddo} />
)} )}
</aside> </aside>
) )

View File

@ -16,6 +16,10 @@ import {
} from '../../../../models/FormEditCredential' } from '../../../../models/FormEditCredential'
import DebugEditCredential from './DebugEditAdvancedSettings' import DebugEditCredential from './DebugEditAdvancedSettings'
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata' import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
import {
setMinterToDispenser,
setMinterToPublisher
} from '../../../../utils/freePrice'
const contentQuery = graphql` const contentQuery = graphql`
query EditAvanceSettingsQuery { query EditAvanceSettingsQuery {
@ -68,7 +72,7 @@ export default function EditAdvancedSettings({
const { debug } = useUserPreferences() const { debug } = useUserPreferences()
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { ocean } = useOcean() const { ocean } = useOcean()
const { metadata, ddo, refreshDdo } = useAsset() const { metadata, ddo, refreshDdo, price } = useAsset()
const [success, setSuccess] = useState<string>() const [success, setSuccess] = useState<string>()
const [error, setError] = useState<string>() const [error, setError] = useState<string>()
const { appConfig } = useSiteMetadata() const { appConfig } = useSiteMetadata()
@ -82,6 +86,16 @@ export default function EditAdvancedSettings({
resetForm: () => void resetForm: () => void
) { ) {
try { try {
if (price.type === 'free') {
const tx = await setMinterToPublisher(
ocean,
ddo.dataToken,
accountId,
setError
)
if (!tx) return
}
let newDdo: DDO let newDdo: DDO
newDdo = await ocean.assets.updateCredentials( newDdo = await ocean.assets.updateCredentials(
ddo, ddo,
@ -103,6 +117,15 @@ export default function EditAdvancedSettings({
Logger.error(content.form.error) Logger.error(content.form.error)
return return
} else { } else {
if (price.type === 'free') {
const tx = await setMinterToDispenser(
ocean,
ddo.dataToken,
accountId,
setError
)
if (!tx) return
}
setSuccess(content.form.success) setSuccess(content.form.success)
resetForm() resetForm()
} }

View File

@ -16,6 +16,10 @@ import { useUserPreferences } from '../../../../providers/UserPreferences'
import DebugEditCompute from './DebugEditCompute' import DebugEditCompute from './DebugEditCompute'
import styles from './index.module.css' import styles from './index.module.css'
import { transformComputeFormToServiceComputePrivacy } from '../../../../utils/compute' import { transformComputeFormToServiceComputePrivacy } from '../../../../utils/compute'
import {
setMinterToDispenser,
setMinterToPublisher
} from '../../../../utils/freePrice'
const contentQuery = graphql` const contentQuery = graphql`
query EditComputeDataQuery { query EditComputeDataQuery {
@ -62,7 +66,7 @@ export default function EditComputeDataset({
const { debug } = useUserPreferences() const { debug } = useUserPreferences()
const { ocean } = useOcean() const { ocean } = useOcean()
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { ddo, refreshDdo } = useAsset() const { ddo, refreshDdo, price } = useAsset()
const [success, setSuccess] = useState<string>() const [success, setSuccess] = useState<string>()
const [error, setError] = useState<string>() const [error, setError] = useState<string>()
@ -73,6 +77,15 @@ export default function EditComputeDataset({
resetForm: () => void resetForm: () => void
) { ) {
try { try {
if (price.type === 'free') {
const tx = await setMinterToPublisher(
ocean,
ddo.dataToken,
accountId,
setError
)
if (!tx) return
}
const privacy = await transformComputeFormToServiceComputePrivacy( const privacy = await transformComputeFormToServiceComputePrivacy(
values, values,
ocean ocean
@ -99,6 +112,15 @@ export default function EditComputeDataset({
Logger.error(content.form.error) Logger.error(content.form.error)
return return
} else { } else {
if (price.type === 'free') {
const tx = await setMinterToDispenser(
ocean,
ddo.dataToken,
accountId,
setError
)
if (!tx) return
}
// Edit succeeded // Edit succeeded
setSuccess(content.form.success) setSuccess(content.form.success)
resetForm() resetForm()

View File

@ -56,7 +56,9 @@ export default function FormEditComputeDataset({
appConfig.metadataCacheUri, appConfig.metadataCacheUri,
source.token source.token
) )
const datasetComputeService = ddo.findServiceByType('compute')
const algorithmSelectionList = await transformDDOToAssetSelection( const algorithmSelectionList = await transformDDOToAssetSelection(
datasetComputeService?.serviceEndpoint,
querryResult.results, querryResult.results,
appConfig.metadataCacheUri, appConfig.metadataCacheUri,
publisherTrustedAlgorithms publisherTrustedAlgorithms

View File

@ -18,6 +18,10 @@ import MetadataFeedback from '../../../molecules/MetadataFeedback'
import { graphql, useStaticQuery } from 'gatsby' import { graphql, useStaticQuery } from 'gatsby'
import { useWeb3 } from '../../../../providers/Web3' import { useWeb3 } from '../../../../providers/Web3'
import { useOcean } from '../../../../providers/Ocean' import { useOcean } from '../../../../providers/Ocean'
import {
setMinterToDispenser,
setMinterToPublisher
} from '../../../../utils/freePrice'
const contentQuery = graphql` const contentQuery = graphql`
query EditMetadataQuery { query EditMetadataQuery {
@ -88,6 +92,15 @@ export default function Edit({
resetForm: () => void resetForm: () => void
) { ) {
try { try {
if (price.type === 'free') {
const tx = await setMinterToPublisher(
ocean,
ddo.dataToken,
accountId,
setError
)
if (!tx) return
}
// Construct new DDO with new values // Construct new DDO with new values
const ddoEditedMetdata = await ocean.assets.editMetadata(ddo, { const ddoEditedMetdata = await ocean.assets.editMetadata(ddo, {
title: values.name, title: values.name,
@ -132,6 +145,15 @@ export default function Edit({
Logger.error(content.form.error) Logger.error(content.form.error)
return return
} else { } else {
if (price.type === 'free') {
const tx = await setMinterToDispenser(
ocean,
ddo.dataToken,
accountId,
setError
)
if (!tx) return
}
// Edit succeeded // Edit succeeded
setSuccess(content.form.success) setSuccess(content.form.success)
resetForm() resetForm()

View File

@ -122,11 +122,10 @@ export default function Graph(): ReactElement {
const { price } = useAsset() const { price } = useAsset()
const [lastBlock, setLastBlock] = useState(0) const [lastBlock, setLastBlock] = useState<number>(0)
const [priceHistory, setPriceHistory] = useState([]) const [priceHistory, setPriceHistory] = useState([])
const [liquidityHistory, setLiquidityHistory] = useState([]) const [liquidityHistory, setLiquidityHistory] = useState([])
const [timestamps, setTimestamps] = useState([]) const [timestamps, setTimestamps] = useState([])
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
const [graphData, setGraphData] = useState<ChartData>() const [graphData, setGraphData] = useState<ChartData>()
@ -157,7 +156,6 @@ export default function Graph(): ReactElement {
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}` return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
}) })
] ]
setTimestamps(latestTimestamps) setTimestamps(latestTimestamps)
const latestLiquidtyHistory = [ const latestLiquidtyHistory = [
@ -171,17 +169,20 @@ export default function Graph(): ReactElement {
...priceHistory, ...priceHistory,
...data.poolTransactions.map((item) => item.spotPrice) ...data.poolTransactions.map((item) => item.spotPrice)
] ]
setPriceHistory(latestPriceHistory) setPriceHistory(latestPriceHistory)
if (data.poolTransactions.length > 0) { if (data.poolTransactions.length > 0) {
const newBlock =
data.poolTransactions[data.poolTransactions.length - 1].block
if (newBlock === lastBlock) return
setLastBlock( setLastBlock(
data.poolTransactions[data.poolTransactions.length - 1].block data.poolTransactions[data.poolTransactions.length - 1].block
) )
refetch() refetch()
} else { } else {
setIsLoading(false)
setGraphData({ setGraphData({
labels: timestamps.slice(0), labels: latestTimestamps.slice(0),
datasets: [ datasets: [
{ {
...lineStyle, ...lineStyle,
@ -195,6 +196,7 @@ export default function Graph(): ReactElement {
} }
] ]
}) })
setIsLoading(false)
} }
}, [data, graphType]) }, [data, graphType])

View File

@ -12,7 +12,7 @@ import { useAsset } from '../../../providers/Asset'
import { useOcean } from '../../../providers/Ocean' import { useOcean } from '../../../providers/Ocean'
import { useWeb3 } from '../../../providers/Web3' import { useWeb3 } from '../../../providers/Web3'
import Web3Feedback from '../../molecules/Web3Feedback' import Web3Feedback from '../../molecules/Web3Feedback'
import { getFileInfo } from '../../../utils/provider' import { fileinfo, getFileInfo } from '../../../utils/provider'
import axios from 'axios' import axios from 'axios'
export default function AssetActions(): ReactElement { export default function AssetActions(): ReactElement {
@ -27,29 +27,27 @@ export default function AssetActions(): ReactElement {
const isCompute = Boolean(ddo?.findServiceByType('compute')) const isCompute = Boolean(ddo?.findServiceByType('compute'))
useEffect(() => { useEffect(() => {
if (!config) return const { attributes } = ddo.findServiceByType('metadata')
setFileMetadata(attributes.main.files[0])
const source = axios.CancelToken.source() // !!!!! do not remove this, we will enable this again after fileInfo endpoint is fixed !!!
async function initFileInfo() { // if (!config) return
setFileIsLoading(true) // const source = axios.CancelToken.source()
try { // async function initFileInfo() {
const fileInfo = await getFileInfo( // setFileIsLoading(true)
DID.parse(`${ddo.id}`), // try {
config.providerUri, // const fileInfo = await getFileInfo(
source.token // DID.parse(`${ddo.id}`),
) // config.providerUri,
setFileMetadata(fileInfo.data[0]) // source.token
} catch (error) { // )
Logger.error(error.message) // setFileMetadata(fileInfo.data[0])
} finally { // } catch (error) {
setFileIsLoading(false) // Logger.error(error.message)
} // } finally {
} // setFileIsLoading(false)
initFileInfo() // }
// }
return () => { // initFileInfo()
source.cancel()
}
}, [config, ddo.id]) }, [config, ddo.id])
// Get and set user DT balance // Get and set user DT balance
@ -72,6 +70,7 @@ export default function AssetActions(): ReactElement {
// Check user balance against price // Check user balance against price
useEffect(() => { useEffect(() => {
if (price?.type === 'free') setIsBalanceSufficient(true)
if (!price?.value || !account || !balance?.ocean || !dtBalance) return if (!price?.value || !account || !balance?.ocean || !dtBalance) return
setIsBalanceSufficient( setIsBalanceSufficient(

View File

@ -5,11 +5,14 @@ import { AssetSelectionAsset } from '../../molecules/FormFields/AssetSelection'
import AssetComputeList from '../../molecules/AssetComputeList' import AssetComputeList from '../../molecules/AssetComputeList'
import { useOcean } from '../../../providers/Ocean' import { useOcean } from '../../../providers/Ocean'
import { useAsset } from '../../../providers/Asset' import { useAsset } from '../../../providers/Asset'
import { DDO } from '@oceanprotocol/lib'
export default function AlgorithmDatasetsListForCompute({ export default function AlgorithmDatasetsListForCompute({
algorithmDid algorithmDid,
dataset
}: { }: {
algorithmDid: string algorithmDid: string
dataset: DDO
}): ReactElement { }): ReactElement {
const { config } = useOcean() const { config } = useOcean()
const { type } = useAsset() const { type } = useAsset()
@ -18,8 +21,10 @@ export default function AlgorithmDatasetsListForCompute({
useEffect(() => { useEffect(() => {
async function getDatasetsAllowedForCompute() { async function getDatasetsAllowedForCompute() {
const datasetComputeService = dataset.findServiceByType('compute')
const datasets = await getAlgorithmDatasetsForCompute( const datasets = await getAlgorithmDatasetsForCompute(
algorithmDid, algorithmDid,
datasetComputeService?.serviceEndpoint,
config.metadataCacheUri config.metadataCacheUri
) )
setDatasetsForCompute(datasets) setDatasetsForCompute(datasets)

View File

@ -0,0 +1,3 @@
.free {
composes: content from './index.module.css';
}

View File

@ -0,0 +1,21 @@
import React, { ReactElement } from 'react'
import stylesIndex from './index.module.css'
import styles from './Free.module.css'
import FormHelp from '../../../../atoms/Input/Help'
import { DDO } from '@oceanprotocol/lib'
import Price from './Price'
export default function Free({
ddo,
content
}: {
ddo: DDO
content: any
}): ReactElement {
return (
<div className={styles.free}>
<FormHelp className={stylesIndex.help}>{content.info}</FormHelp>
<Price ddo={ddo} free />
</div>
)
}

View File

@ -10,10 +10,12 @@ import usePricing from '../../../../../hooks/usePricing'
export default function Price({ export default function Price({
ddo, ddo,
firstPrice firstPrice,
free
}: { }: {
ddo: DDO ddo: DDO
firstPrice?: string firstPrice?: string
free?: boolean
}): ReactElement { }): ReactElement {
const [field, meta] = useField('price') const [field, meta] = useField('price')
const { getDTName, getDTSymbol } = usePricing() const { getDTName, getDTSymbol } = usePricing()
@ -38,17 +40,27 @@ export default function Price({
<div className={styles.price}> <div className={styles.price}>
<div className={styles.grid}> <div className={styles.grid}>
<div className={styles.form}> <div className={styles.form}>
<Input {free ? (
value={field.value} <Input
name="price" value="0"
type="number" name="price"
prefix="OCEAN" type="number"
min="1" prefix="OCEAN"
{...field} readOnly
additionalComponent={ />
<Conversion price={field.value} className={styles.conversion} /> ) : (
} <Input
/> value={field.value}
name="price"
type="number"
prefix="OCEAN"
min="1"
{...field}
additionalComponent={
<Conversion price={field.value} className={styles.conversion} />
}
/>
)}
<Error meta={meta} /> <Error meta={meta} />
</div> </div>
<div className={styles.datatoken}> <div className={styles.datatoken}>

View File

@ -45,3 +45,8 @@
padding-left: var(--spacer); padding-left: var(--spacer);
padding-right: var(--spacer); padding-right: var(--spacer);
} }
.free {
text-align: center;
margin-bottom: calc(var(--spacer) / 1.5);
}

View File

@ -3,6 +3,7 @@ import styles from './index.module.css'
import Tabs from '../../../../atoms/Tabs' import Tabs from '../../../../atoms/Tabs'
import Fixed from './Fixed' import Fixed from './Fixed'
import Dynamic from './Dynamic' import Dynamic from './Dynamic'
import Free from './Free'
import { useFormikContext } from 'formik' import { useFormikContext } from 'formik'
import { useUserPreferences } from '../../../../../providers/UserPreferences' import { useUserPreferences } from '../../../../../providers/UserPreferences'
import { PriceOptionsMarket } from '../../../../../@types/MetaData' import { PriceOptionsMarket } from '../../../../../@types/MetaData'
@ -33,6 +34,7 @@ export default function FormPricing({
const type = tabName.toLowerCase() const type = tabName.toLowerCase()
setFieldValue('type', type) setFieldValue('type', type)
type === 'fixed' && setFieldValue('dtAmount', 1000) type === 'fixed' && setFieldValue('dtAmount', 1000)
type === 'free' && price < 1 && setFieldValue('price', 1)
} }
// Always update everything when price value changes // Always update everything when price value changes
@ -57,6 +59,12 @@ export default function FormPricing({
title: content.dynamic.title, title: content.dynamic.title,
content: <Dynamic content={content.dynamic} ddo={ddo} /> content: <Dynamic content={content.dynamic} ddo={ddo} />
} }
: undefined,
appConfig.allowFreePricing === 'true'
? {
title: content.free.title,
content: <Free content={content.free} ddo={ddo} />
}
: undefined : undefined
].filter((tab) => tab !== undefined) ].filter((tab) => tab !== undefined)

View File

@ -40,6 +40,10 @@ const query = graphql`
marketplaceFee marketplaceFee
} }
} }
free {
title
info
}
} }
} }
} }

View File

@ -60,7 +60,11 @@ const columns = [
{ {
name: 'Finished', name: 'Finished',
selector: function getTimeRow(row: ComputeJobMetaData) { selector: function getTimeRow(row: ComputeJobMetaData) {
return <Time date={row.dateFinished} isUnix relative /> return row.dateFinished ? (
<Time date={row.dateFinished} isUnix relative />
) : (
''
)
} }
}, },
{ {
@ -117,6 +121,7 @@ export default function ComputeJobs(): ReactElement {
setIsLoading(true) setIsLoading(true)
// await refetch()
const dtList = [] const dtList = []
const computeJobs: ComputeJobMetaData[] = [] const computeJobs: ComputeJobMetaData[] = []
for (let i = 0; i < data.tokenOrders.length; i++) { for (let i = 0; i < data.tokenOrders.length; i++) {

View File

@ -85,7 +85,7 @@ function SectionQueryResult({
appConfig.metadataCacheUri, appConfig.metadataCacheUri,
source.token source.token
) )
if (result.totalResults <= 15) { if (queryData && result.totalResults > 0 && result.totalResults <= 15) {
const searchDIDs = queryData.split(' ') const searchDIDs = queryData.split(' ')
const sortedAssets = sortElements(result.results, searchDIDs) const sortedAssets = sortElements(result.results, searchDIDs)
// We take more assets than we need from the subgraph (to make sure // We take more assets than we need from the subgraph (to make sure
@ -95,7 +95,6 @@ function SectionQueryResult({
sortedAssets.splice(sortedAssets.length - overflow, overflow) sortedAssets.splice(sortedAssets.length - overflow, overflow)
result.results = sortedAssets result.results = sortedAssets
} }
if (result.results.length === 0) return
setResult(result) setResult(result)
setLoading(false) setLoading(false)
} catch (error) { } catch (error) {

View File

@ -25,7 +25,7 @@ export default function FilterPrice({
const [serviceSelections, setServiceSelections] = useState<string[]>([]) const [serviceSelections, setServiceSelections] = useState<string[]>([])
async function applyServiceFilter(filterBy: string) { async function applyServiceFilter(filterBy: string) {
let urlLocation = await addExistingParamsToUrl(location, 'serviceType') let urlLocation = await addExistingParamsToUrl(location, ['serviceType'])
if (filterBy && location.search.indexOf('&serviceType') === -1) { if (filterBy && location.search.indexOf('&serviceType') === -1) {
urlLocation = `${urlLocation}&serviceType=${filterBy}` urlLocation = `${urlLocation}&serviceType=${filterBy}`
} }
@ -59,7 +59,7 @@ export default function FilterPrice({
} }
async function applyClearFilter() { async function applyClearFilter() {
let urlLocation = await addExistingParamsToUrl(location, 'serviceType') let urlLocation = await addExistingParamsToUrl(location, ['serviceType'])
urlLocation = `${urlLocation}` urlLocation = `${urlLocation}`

View File

@ -32,7 +32,6 @@ export default function SearchPage({
useEffect(() => { useEffect(() => {
if (!appConfig.metadataCacheUri) return if (!appConfig.metadataCacheUri) return
async function initSearch() { async function initSearch() {
setLoading(true) setLoading(true)
setTotalResults(undefined) setTotalResults(undefined)
@ -66,7 +65,7 @@ export default function SearchPage({
<Permission eventType="browse"> <Permission eventType="browse">
<> <>
<div className={styles.search}> <div className={styles.search}>
{(text || owner) && ( {(text || owner || tags) && (
<SearchBar initialValue={(text || owner) as string} /> <SearchBar initialValue={(text || owner) as string} />
)} )}
<div className={styles.row}> <div className={styles.row}>

View File

@ -11,7 +11,10 @@ import classNames from 'classnames/bind'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
const sortItems = [{ display: 'Published', value: SortTermOptions.Created }] const sortItems = [
// { display: 'Relevance', value: SortTermOptions.Relevance },
{ display: 'Published', value: SortTermOptions.Created }
]
export default function Sort({ export default function Sort({
sortType, sortType,
@ -31,10 +34,11 @@ 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'])
urlLocation = `${urlLocation}&sort=${sortBy}` urlLocation = `${urlLocation}&sort=${sortBy}`
setSortType(sortBy) setSortType(sortBy)
} else if (direction) { } else if (direction) {
urlLocation = await addExistingParamsToUrl(location, 'sortOrder') urlLocation = await addExistingParamsToUrl(location, ['sortOrder'])
urlLocation = `${urlLocation}&sortOrder=${direction}` urlLocation = `${urlLocation}&sortOrder=${direction}`
setSortDirection(direction) setSortDirection(direction)
} }

View File

@ -1,12 +1,10 @@
import { import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
SearchQuery,
QueryResult
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import { MetadataCache, Logger } from '@oceanprotocol/lib' import { MetadataCache, Logger } from '@oceanprotocol/lib'
import queryString from 'query-string' import queryString from 'query-string'
export const SortTermOptions = { export const SortTermOptions = {
Created: 'created' Created: 'created',
Relevance: '_score'
} as const } as const
type SortTermOptions = typeof SortTermOptions[keyof typeof SortTermOptions] type SortTermOptions = typeof SortTermOptions[keyof typeof SortTermOptions]
@ -39,8 +37,11 @@ function addTypeFilterToQuery(sortTerm: string, typeFilter: string): string {
return sortTerm return sortTerm
} }
function getSortType(): string { function getSortType(sortParam: string): string {
const sortTerm = SortTermOptions.Created const sortTerm =
sortParam === SortTermOptions.Created
? SortTermOptions.Created
: SortTermOptions.Relevance
return sortTerm return sortTerm
} }
@ -54,9 +55,11 @@ export function getSearchQuery(
sort?: string, sort?: string,
sortOrder?: string, sortOrder?: string,
serviceType?: string serviceType?: string
): SearchQuery { ): any {
const sortTerm = getSortType() const sortTerm = getSortType(sort)
const sortValue = sortOrder === SortValueOptions.Ascending ? 1 : -1 const sortValue = sortOrder === SortValueOptions.Ascending ? 1 : -1
const emptySearchTerm = text === undefined || text === ''
let searchTerm = owner let searchTerm = owner
? `(publicKey.owner:${owner})` ? `(publicKey.owner:${owner})`
: tags : tags
@ -67,41 +70,84 @@ export function getSearchQuery(
`(service.attributes.additionalInformation.categories:\"${categories}\")` `(service.attributes.additionalInformation.categories:\"${categories}\")`
: text || '' : text || ''
// HACK: resolves the case sensitivity related to dataTokenInfo.symbol searchTerm = searchTerm.trim()
searchTerm = '*' + searchTerm.toUpperCase() + '*' let modifiedSearchTerm = searchTerm.split(' ').join(' OR ').trim()
modifiedSearchTerm = addTypeFilterToQuery(modifiedSearchTerm, serviceType)
searchTerm = addTypeFilterToQuery(searchTerm, serviceType) searchTerm = addTypeFilterToQuery(searchTerm, serviceType)
const prefixedSearchTerm =
emptySearchTerm && searchTerm
? searchTerm
: !emptySearchTerm && searchTerm
? '*' + searchTerm + '*'
: '**'
return { return {
page: Number(page) || 1, page: Number(page) || 1,
offset: Number(offset) || 21, offset: Number(offset) || 21,
query: { query: {
query_string: { bool: {
query: `${searchTerm} -isInPurgatory:true`, must: [
fields: [ {
'dataTokenInfo.name', bool: {
'dataTokenInfo.symbol', should: [
'service.attributes.main.name', {
'service.attributes.main.author', query_string: {
'service.attributes.additionalInformation.description' query: `${modifiedSearchTerm}`,
], fields: [
default_operator: 'AND' 'id',
'publicKey.owner',
'dataToken',
'dataTokenInfo.name',
'dataTokenInfo.symbol',
'service.attributes.main.name^10',
'service.attributes.main.author',
'service.attributes.additionalInformation.description',
'service.attributes.additionalInformation.tags'
],
minimum_should_match: '2<75%',
phrase_slop: 2,
boost: 5
}
},
{
match_phrase: {
content: {
query: `${searchTerm}`,
boost: 10
}
}
},
{
query_string: {
query: `${prefixedSearchTerm}`,
fields: [
'id',
'publicKey.owner',
'dataToken',
'dataTokenInfo.name',
'dataTokenInfo.symbol',
'service.attributes.main.name',
'service.attributes.main.author',
'service.attributes.additionalInformation.description',
'service.attributes.additionalInformation.tags'
],
default_operator: 'AND'
}
}
]
}
},
{
term: {
isInPurgatory: false
}
}
]
} }
// ...(owner && { 'publicKey.owner': [owner] }),
// ...(tags && { tags: [tags] }),
// ...(categories && { categories: [categories] })
}, },
sort: { sort: {
[sortTerm]: sortValue [sortTerm]: sortValue
} }
// Something in ocean.js is weird when using 'tags: [tag]'
// which is the only way the query actually returns desired results.
// But it doesn't follow 'SearchQuery' interface so we have to assign
// it here.
// } as SearchQuery
// And the next hack,
// nativeSearch is not implmeneted on ocean.js typings
} }
} }
@ -123,9 +169,9 @@ export async function getResults(
text, text,
owner, owner,
tags, tags,
categories,
page, page,
offset, offset,
categories,
sort, sort,
sortOrder, sortOrder,
serviceType serviceType
@ -143,25 +189,20 @@ export async function getResults(
sortOrder, sortOrder,
serviceType serviceType
) )
const queryResult = await metadataCache.queryMetadata(searchQuery) const queryResult = await metadataCache.queryMetadata(searchQuery)
return queryResult return queryResult
} }
export async function addExistingParamsToUrl( export async function addExistingParamsToUrl(
location: Location, location: Location,
excludedParam: string, excludedParams: string[]
secondExcludedParam?: string
): Promise<string> { ): Promise<string> {
const parsed = queryString.parse(location.search) const parsed = queryString.parse(location.search)
let urlLocation = '/search?' let urlLocation = '/search?'
if (Object.keys(parsed).length > 0) { if (Object.keys(parsed).length > 0) {
for (const querryParam in parsed) { for (const querryParam in parsed) {
if ( if (!excludedParams.includes(querryParam)) {
querryParam !== excludedParam && if (querryParam === 'page' && excludedParams.includes('text')) {
querryParam !== secondExcludedParam
) {
if (querryParam === 'page' && excludedParam === 'text') {
Logger.log('remove page when starting a new search') Logger.log('remove page when starting a new search')
} else { } else {
const value = parsed[querryParam] const value = parsed[querryParam]
@ -170,7 +211,10 @@ export async function addExistingParamsToUrl(
} }
} }
} else { } else {
urlLocation = `${urlLocation}sort=${SortTermOptions.Created}&sortOrder=${SortValueOptions.Descending}&` // sort should be relevance when fixed in aqua
urlLocation = `${urlLocation}sort=${encodeURIComponent(
SortTermOptions.Created
)}&sortOrder=${SortValueOptions.Descending}&`
} }
urlLocation = urlLocation.slice(0, -1) urlLocation = urlLocation.slice(0, -1)
return urlLocation return urlLocation

View File

@ -5,7 +5,9 @@ import { Decimal } from 'decimal.js'
import { import {
getCreatePricingPoolFeedback, getCreatePricingPoolFeedback,
getCreatePricingExchangeFeedback, getCreatePricingExchangeFeedback,
getBuyDTFeedback getBuyDTFeedback,
getCreateFreePricingFeedback,
getDispenseFeedback
} from '../utils/feedback' } from '../utils/feedback'
import { sleep } from '../utils' import { sleep } from '../utils'
@ -16,7 +18,7 @@ interface PriceOptions {
price: number price: number
dtAmount: number dtAmount: number
oceanAmount: number oceanAmount: number
type: 'fixed' | 'dynamic' | string type: 'fixed' | 'dynamic' | 'free' | string
weightOnDataToken: string weightOnDataToken: string
swapFee: string swapFee: string
} }
@ -68,7 +70,7 @@ function usePricing(): UsePricing {
// Helper for setting steps & feedback for all flows // Helper for setting steps & feedback for all flows
async function setStep( async function setStep(
index: number, index: number,
type: 'pool' | 'exchange' | 'buy', type: 'pool' | 'exchange' | 'free' | 'buy' | 'dispense',
ddo: DDO ddo: DDO
) { ) {
const dtSymbol = await getDTSymbol(ddo) const dtSymbol = await getDTSymbol(ddo)
@ -84,9 +86,15 @@ function usePricing(): UsePricing {
case 'exchange': case 'exchange':
messages = getCreatePricingExchangeFeedback(dtSymbol) messages = getCreatePricingExchangeFeedback(dtSymbol)
break break
case 'free':
messages = getCreateFreePricingFeedback(dtSymbol)
break
case 'buy': case 'buy':
messages = getBuyDTFeedback(dtSymbol) messages = getBuyDTFeedback(dtSymbol)
break break
case 'dispense':
messages = getDispenseFeedback(dtSymbol)
break
} }
setPricingStepText(messages[index]) setPricingStepText(messages[index])
@ -180,6 +188,28 @@ function usePricing(): UsePricing {
Logger.log('DT exchange buy response', tx) Logger.log('DT exchange buy response', tx)
break break
} }
case 'free': {
setStep(1, 'dispense', ddo)
const isDispensable = await ocean.OceanDispenser.isDispensable(
ddo.dataToken,
accountId,
'1'
)
if (!isDispensable) {
Logger.error(`Dispenser for ${ddo.dataToken} failed to dispense`)
return
}
tx = await ocean.OceanDispenser.dispense(
ddo.dataToken,
accountId,
'1'
)
setStep(2, 'dispense', ddo)
Logger.log('DT dispense response', tx)
break
}
} }
} catch (error) { } catch (error) {
setPricingError(error.message) setPricingError(error.message)
@ -219,9 +249,14 @@ function usePricing(): UsePricing {
setStep(99, 'pool', ddo) setStep(99, 'pool', ddo)
try { try {
// if fixedPrice set dt to max amount if (type === 'free') {
if (!isPool) dtAmount = 1000 setStep(99, 'free', ddo)
await mint(`${dtAmount}`, ddo) await ocean.OceanDispenser.activate(dataToken, '1', '1', accountId)
} else {
// if fixedPrice set dt to max amount
if (!isPool) dtAmount = 1000
await mint(`${dtAmount}`, ddo)
}
// dtAmount for fixed price is set to max // dtAmount for fixed price is set to max
const tx = isPool const tx = isPool
@ -235,9 +270,13 @@ function usePricing(): UsePricing {
swapFee swapFee
) )
.next((step: number) => setStep(step, 'pool', ddo)) .next((step: number) => setStep(step, 'pool', ddo))
: await ocean.fixedRateExchange : type === 'fixed'
? await ocean.fixedRateExchange
.create(dataToken, `${price}`, accountId, `${dtAmount}`) .create(dataToken, `${price}`, accountId, `${dtAmount}`)
.next((step: number) => setStep(step, 'exchange', ddo)) .next((step: number) => setStep(step, 'exchange', ddo))
: await ocean.OceanDispenser.makeMinter(dataToken, accountId).next(
(step: number) => setStep(step, 'free', ddo)
)
await sleep(20000) await sleep(20000)
return tx return tx
} catch (error) { } catch (error) {

View File

@ -29,6 +29,7 @@ interface UseSiteMetadata {
portisId: string portisId: string
allowFixedPricing: string allowFixedPricing: string
allowDynamicPricing: string allowDynamicPricing: string
allowFreePricing: string
allowAdvancedSettings: string allowAdvancedSettings: string
credentialType: string credentialType: string
} }
@ -65,6 +66,7 @@ const query = graphql`
portisId portisId
allowFixedPricing allowFixedPricing
allowDynamicPricing allowDynamicPricing
allowFreePricing
allowAdvancedSettings allowAdvancedSettings
credentialType credentialType
} }

View File

@ -27,7 +27,9 @@ function getCredentialList(
const credentialByType = credential.find( const credentialByType = credential.find(
(credential) => credential.type === credentialType (credential) => credential.type === credentialType
) )
return credentialByType.value && credentialByType.value.length > 0 return credentialByType &&
credentialByType.value &&
credentialByType.value.length > 0
? credentialByType.value ? credentialByType.value
: [] : []
} }

View File

@ -13,7 +13,7 @@ export const validationSchema: Yup.SchemaOf<PriceOptionsMarket> =
.min(21, (param) => `Must be more or equal to ${param.min}`) .min(21, (param) => `Must be more or equal to ${param.min}`)
.required('Required'), .required('Required'),
type: Yup.string() type: Yup.string()
.matches(/fixed|dynamic/g, { excludeEmptyString: true }) .matches(/fixed|dynamic|free/g, { excludeEmptyString: true })
.required('Required'), .required('Required'),
weightOnDataToken: Yup.string().required('Required'), weightOnDataToken: Yup.string().required('Required'),
weightOnOcean: Yup.string().required('Required'), weightOnOcean: Yup.string().required('Required'),

View File

@ -121,6 +121,7 @@ export async function getAssetsNames(
} }
export async function transformDDOToAssetSelection( export async function transformDDOToAssetSelection(
datasetProviderEndpoint: string,
ddoList: DDO[], ddoList: DDO[],
metadataCacheUri: string, metadataCacheUri: string,
selectedAlgorithms?: PublisherTrustedAlgorithm[] selectedAlgorithms?: PublisherTrustedAlgorithm[]
@ -129,14 +130,22 @@ export async function transformDDOToAssetSelection(
const didList: string[] = [] const didList: string[] = []
const priceList: PriceList = await getAssetsPriceList(ddoList) const priceList: PriceList = await getAssetsPriceList(ddoList)
const symbolList: any = {} const symbolList: any = {}
const didProviderEndpointMap: any = {}
for (const ddo of ddoList) { for (const ddo of ddoList) {
didList.push(ddo.id) didList.push(ddo.id)
symbolList[ddo.id] = ddo.dataTokenInfo.symbol symbolList[ddo.id] = ddo.dataTokenInfo.symbol
const algoComputeService = ddo.findServiceByType('compute')
algoComputeService?.serviceEndpoint &&
(didProviderEndpointMap[ddo.id] = algoComputeService?.serviceEndpoint)
} }
const ddoNames = await getAssetsNames(didList, metadataCacheUri, source.token) const ddoNames = await getAssetsNames(didList, metadataCacheUri, source.token)
const algorithmList: AssetSelectionAsset[] = [] const algorithmList: AssetSelectionAsset[] = []
didList?.forEach((did: string) => { didList?.forEach((did: string) => {
if (priceList[did]) { if (
priceList[did] &&
(!didProviderEndpointMap[did] ||
didProviderEndpointMap[did] === datasetProviderEndpoint)
) {
let selected = false let selected = false
selectedAlgorithms?.forEach((algorithm: PublisherTrustedAlgorithm) => { selectedAlgorithms?.forEach((algorithm: PublisherTrustedAlgorithm) => {
if (algorithm.did === did) { if (algorithm.did === did) {
@ -165,6 +174,7 @@ export async function transformDDOToAssetSelection(
export async function getAlgorithmDatasetsForCompute( export async function getAlgorithmDatasetsForCompute(
algorithmId: string, algorithmId: string,
datasetProviderUri: string,
metadataCacheUri: string metadataCacheUri: string
): Promise<AssetSelectionAsset[]> { ): Promise<AssetSelectionAsset[]> {
const source = axios.CancelToken.source() const source = axios.CancelToken.source()
@ -186,6 +196,7 @@ export async function getAlgorithmDatasetsForCompute(
return [] return []
} }
const datasets = await transformDDOToAssetSelection( const datasets = await transformDDOToAssetSelection(
datasetProviderUri,
computeDatasetsForCurrentAlgorithm, computeDatasetsForCurrentAlgorithm,
metadataCacheUri, metadataCacheUri,
[] []

View File

@ -1,6 +1,8 @@
const cleanupContentType = (contentType: string): string => { const cleanupContentType = (contentType: string): string => {
// strip away the `charset=utf-8`
const contentSplit = contentType.split(';')[0]
// strip away the 'application/' part // strip away the 'application/' part
const contentTypeSplit = contentType.split('/')[1] const contentTypeSplit = contentSplit.split('/')[1]
if (!contentTypeSplit) return contentType if (!contentTypeSplit) return contentType

View File

@ -52,6 +52,17 @@ export function getCreatePricingExchangeFeedback(dtSymbol: string): {
} }
} }
export function getCreateFreePricingFeedback(dtSymbol: string): {
[key: number]: string
} {
return {
99: `Creating ${dtSymbol} faucet...`,
0: 'Setting faucet as minter ...',
1: 'Approving minter...',
2: 'Faucet created.'
}
}
export function getBuyDTFeedback(dtSymbol: string): { [key: number]: string } { export function getBuyDTFeedback(dtSymbol: string): { [key: number]: string } {
return { return {
1: '1/3 Approving OCEAN ...', 1: '1/3 Approving OCEAN ...',
@ -67,3 +78,12 @@ export function getSellDTFeedback(dtSymbol: string): { [key: number]: string } {
3: `3/3 ${dtSymbol} sold.` 3: `3/3 ${dtSymbol} sold.`
} }
} }
export function getDispenseFeedback(dtSymbol: string): {
[key: number]: string
} {
return {
1: `1/2 Requesting ${dtSymbol}...`,
2: `2/2 Received ${dtSymbol}.`
}
}

37
src/utils/freePrice.ts Normal file
View File

@ -0,0 +1,37 @@
import { Logger, Ocean } from '@oceanprotocol/lib'
export async function setMinterToPublisher(
ocean: Ocean,
dataTokenAddress: string,
accountId: string,
setError: (msg: string) => void
): Promise<any> {
// free pricing v3 workaround part1
const response = await ocean.OceanDispenser.cancelMinter(
dataTokenAddress,
accountId
)
if (!response) {
setError('Updating DDO failed.')
Logger.error('Failed at cancelMinter')
}
return response
}
export async function setMinterToDispenser(
ocean: Ocean,
dataTokenAddress: string,
accountId: string,
setError: (msg: string) => void
): Promise<any> {
// free pricing v3 workaround part2
const response = await ocean.OceanDispenser.makeMinter(
dataTokenAddress,
accountId
)
if (!response) {
setError('Updating DDO failed.')
Logger.error('Failed at makeMinter')
}
return response
}

View File

@ -1,23 +1,18 @@
import { import { ConfigHelper, ConfigHelperConfig, Logger } from '@oceanprotocol/lib'
ConfigHelper,
ConfigHelperConfig,
ConfigHelperNetworkId,
ConfigHelperNetworkName,
Logger
} from '@oceanprotocol/lib'
import contractAddresses from '@oceanprotocol/contracts/artifacts/address.json' import contractAddresses from '@oceanprotocol/contracts/artifacts/address.json'
import { AbiItem } from 'web3-utils/types' import { AbiItem } from 'web3-utils/types'
import Web3 from 'web3' import Web3 from 'web3'
export function getOceanConfig( export function getOceanConfig(network: string | number): ConfigHelperConfig {
network: ConfigHelperNetworkName | ConfigHelperNetworkId
): ConfigHelperConfig {
const config = new ConfigHelper().getConfig( const config = new ConfigHelper().getConfig(
network, network,
network === 'polygon' || network === 'polygon' ||
network === 137 ||
network === 'moonbeamalpha' || network === 'moonbeamalpha' ||
network === 1287 network === 1287 ||
network === 'bsc' ||
network === 56 ||
network === 'gaiaxtestnet' ||
network === 2021000
? undefined ? undefined
: process.env.GATSBY_INFURA_PROJECT_ID : process.env.GATSBY_INFURA_PROJECT_ID
) )

View File

@ -4,6 +4,7 @@ import { getUrqlClientInstance } from '../providers/UrqlProvider'
import { getOceanConfig } from './ocean' import { getOceanConfig } from './ocean'
import web3 from 'web3' import web3 from 'web3'
import schema from '../../src/@types/schema.json' import schema from '../../src/@types/schema.json'
import { AssetsFreePrice } from '../@types/apollo/AssetsFreePrice'
export interface PriceList { export interface PriceList {
[key: string]: string [key: string]: string
@ -18,6 +19,36 @@ interface DidAndDatatokenMap {
[name: string]: string [name: string]: string
} }
const FreeQuery = gql`
query AssetsFreePrice($datatoken_in: [String!]) {
dispensers(orderBy: id, where: { datatoken_in: $datatoken_in }) {
datatoken {
id
address
}
}
}
`
const AssetFreeQuery = gql`
query AssetFreePrice($datatoken: String) {
dispensers(orderBy: id, where: { datatoken: $datatoken }) {
active
owner {
id
}
minterApproved
isTrueMinter
maxTokens
maxBalance
balance
datatoken {
id
}
}
}
`
const FreQuery = gql` const FreQuery = gql`
query AssetsFrePrice($datatoken_in: [String!]) { query AssetsFrePrice($datatoken_in: [String!]) {
fixedRateExchanges(orderBy: id, where: { datatoken_in: $datatoken_in }) { fixedRateExchanges(orderBy: id, where: { datatoken_in: $datatoken_in }) {
@ -141,7 +172,8 @@ export async function getPreviousOrders(
function transformPriceToBestPrice( function transformPriceToBestPrice(
frePrice: AssetsFrePriceFixedRateExchanges[], frePrice: AssetsFrePriceFixedRateExchanges[],
poolPrice: AssetsPoolPricePools[] poolPrice: AssetsPoolPricePools[],
freePrice: AssetFreePriceDispenser[]
) { ) {
if (poolPrice?.length > 0) { if (poolPrice?.length > 0) {
const price: BestPrice = { const price: BestPrice = {
@ -171,6 +203,18 @@ function transformPriceToBestPrice(
isConsumable: 'true' isConsumable: 'true'
} }
return price return price
} else if (freePrice?.length > 0) {
const price: BestPrice = {
type: 'free',
value: 0,
address: freePrice[0]?.datatoken.id,
exchange_id: '',
ocean: 0,
datatoken: 0,
pools: [],
isConsumable: 'true'
}
return price
} else { } else {
const price: BestPrice = { const price: BestPrice = {
type: '', type: '',
@ -190,8 +234,9 @@ async function getAssetsPoolsExchangesAndDatatokenMap(
assets: DDO[] assets: DDO[]
): Promise< ): Promise<
[ [
AssetsPoolPricePools[], ApolloQueryResult<AssetsPoolPrice>,
AssetsFrePriceFixedRateExchanges[], ApolloQueryResult<AssetsFrePrice>,
ApolloQueryResult<AssetsFreePrice>,
DidAndDatatokenMap DidAndDatatokenMap
] ]
> { > {
@ -210,6 +255,7 @@ async function getAssetsPoolsExchangesAndDatatokenMap(
} }
let poolPriceResponse: AssetsPoolPricePools[] = [] let poolPriceResponse: AssetsPoolPricePools[] = []
let frePriceResponse: AssetsFrePriceFixedRateExchanges[] = [] let frePriceResponse: AssetsFrePriceFixedRateExchanges[] = []
let freePriceResponse: AssetFreePriceDispenser[] = []
for (const chainKey in chainAssetLists) { for (const chainKey in chainAssetLists) {
const freVariables = { const freVariables = {
@ -218,7 +264,9 @@ async function getAssetsPoolsExchangesAndDatatokenMap(
const poolVariables = { const poolVariables = {
datatokenAddress_in: chainAssetLists[chainKey] datatokenAddress_in: chainAssetLists[chainKey]
} }
const freeVariables = {
datatoken_in: chainAssetLists[chainKey]
}
// harcoded until we have chainId on assets // harcoded until we have chainId on assets
const queryContext: OperationContext = { const queryContext: OperationContext = {
url: `${getSubgrahUri(1)}/subgraphs/name/oceanprotocol/ocean-subgraph`, url: `${getSubgrahUri(1)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
@ -236,22 +284,29 @@ async function getAssetsPoolsExchangesAndDatatokenMap(
frePriceResponse = frePriceResponse.concat( frePriceResponse = frePriceResponse.concat(
chainFrePriceResponse.data.fixedRateExchanges chainFrePriceResponse.data.fixedRateExchanges
) )
}
return [poolPriceResponse, frePriceResponse, didDTMap] const chainFreePriceResponse: OperationResult<AssetsFreePrice> =
await fetchData(FreeQuery, freeVariables, queryContext)
freePriceResponse = freePriceResponse.concat(
chainFreePriceResponse.data.dispensers
)
}
return [poolPriceResponse, frePriceResponse, freePriceResponse, didDTMap]
} }
export async function getAssetsPriceList(assets: DDO[]): Promise<PriceList> { export async function getAssetsPriceList(assets: DDO[]): Promise<PriceList> {
const priceList: PriceList = {} const priceList: PriceList = {}
const values: [ const values: [
AssetsPoolPricePools[], ApolloQueryResult<AssetsPoolPrice>,
AssetsFrePriceFixedRateExchanges[], ApolloQueryResult<AssetsFrePrice>,
ApolloQueryResult<AssetsFreePrice>,
DidAndDatatokenMap DidAndDatatokenMap
] = await getAssetsPoolsExchangesAndDatatokenMap(assets) ] = await getAssetsPoolsExchangesAndDatatokenMap(assets)
const poolPriceResponse = values[0] const poolPriceResponse = values[0]
const frePriceResponse = values[1] const frePriceResponse = values[1]
const didDTMap: DidAndDatatokenMap = values[2] const freePriceResponse = values[2]
const didDTMap: DidAndDatatokenMap = values[3]
for (const poolPrice of poolPriceResponse) { for (const poolPrice of poolPriceResponse) {
priceList[didDTMap[poolPrice.datatokenAddress]] = priceList[didDTMap[poolPrice.datatokenAddress]] =
@ -262,6 +317,9 @@ export async function getAssetsPriceList(assets: DDO[]): Promise<PriceList> {
for (const frePrice of frePriceResponse) { for (const frePrice of frePriceResponse) {
priceList[didDTMap[frePrice.datatoken?.address]] = frePrice.rate priceList[didDTMap[frePrice.datatoken?.address]] = frePrice.rate
} }
for (const freePrice of freePriceResponse.data?.dispensers) {
priceList[didDTMap[freePrice.datatoken?.address]] = '0'
}
return priceList return priceList
} }
@ -269,7 +327,9 @@ export async function getPrice(asset: DDO): Promise<BestPrice> {
const freVariables = { const freVariables = {
datatoken: asset?.dataToken.toLowerCase() datatoken: asset?.dataToken.toLowerCase()
} }
const freeVariables = {
datatoken: asset?.dataToken.toLowerCase()
}
const poolVariables = { const poolVariables = {
datatokenAddress: asset?.dataToken.toLowerCase() datatokenAddress: asset?.dataToken.toLowerCase()
} }
@ -289,10 +349,16 @@ export async function getPrice(asset: DDO): Promise<BestPrice> {
freVariables, freVariables,
queryContext queryContext
) )
const freePriceResponse: ApolloQueryResult<AssetsFreePrice> = await fetchData(
AssetFreeQuery,
freeVariables,
queryContext
)
const bestPrice: BestPrice = transformPriceToBestPrice( const bestPrice: BestPrice = transformPriceToBestPrice(
frePriceResponse.data.fixedRateExchanges, frePriceResponse.data.fixedRateExchanges,
poolPriceResponse.data.pools poolPriceResponse.data.pools,
freePriceResponse.data.dispensers
) )
return bestPrice return bestPrice
@ -304,26 +370,34 @@ export async function getAssetsBestPrices(
const assetsWithPrice: AssetListPrices[] = [] const assetsWithPrice: AssetListPrices[] = []
const values: [ const values: [
AssetsPoolPricePools[], ApolloQueryResult<AssetsPoolPrice>,
AssetsFrePriceFixedRateExchanges[], ApolloQueryResult<AssetsFrePrice>,
ApolloQueryResult<AssetsFreePrice>,
DidAndDatatokenMap DidAndDatatokenMap
] = await getAssetsPoolsExchangesAndDatatokenMap(assets) ] = await getAssetsPoolsExchangesAndDatatokenMap(assets)
const poolPriceResponse = values[0] const poolPriceResponse = values[0]
const frePriceResponse = values[1] const frePriceResponse = values[1]
const freePriceResponse = values[2]
for (const ddo of assets) { for (const ddo of assets) {
const dataToken = ddo.dataToken.toLowerCase() const dataToken = ddo.dataToken.toLowerCase()
const poolPrice: AssetsPoolPricePools[] = [] const poolPrice: AssetsPoolPricePools[] = []
const frePrice: AssetsFrePriceFixedRateExchanges[] = [] const frePrice: AssetsFrePriceFixedRateExchanges[] = []
const pool = poolPriceResponse.find( const freePrice: AssetFreePriceDispenser[] = []
const pool = poolPriceResponse.data?.pools.find(
(pool: any) => pool.datatokenAddress === dataToken (pool: any) => pool.datatokenAddress === dataToken
) )
pool && poolPrice.push(pool) pool && poolPrice.push(pool)
const fre = frePriceResponse.find( const fre = frePriceResponse.find(
(fre: any) => fre.datatoken.address === dataToken (fre: any) => fre.datatoken.address === dataToken
) )
fre && frePrice.push(fre) fre && frePrice.push(fre)
const bestPrice = transformPriceToBestPrice(frePrice, poolPrice) const free = freePriceResponse.data?.dispensers.find(
(free: any) => free.datatoken.address === dataToken
)
free && freePrice.push(free)
const bestPrice = transformPriceToBestPrice(frePrice, poolPrice, freePrice)
assetsWithPrice.push({ assetsWithPrice.push({
ddo: ddo, ddo: ddo,
price: bestPrice price: bestPrice

View File

@ -44,6 +44,9 @@ export function getNetworkDisplayName(
case 8996: case 8996:
displayName = 'Development' displayName = 'Development'
break break
case 2021000:
displayName = 'GAIA-X'
break
default: default:
displayName = `${data.chain} ${ displayName = `${data.chain} ${
data.network === 'mainnet' ? '' : data.network data.network === 'mainnet' ? '' : data.network