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

Merge branch main into issue-1787-multichain-provider

This commit is contained in:
Bogdan Fazakas 2023-02-02 12:05:23 +02:00
commit 359ff0b488
56 changed files with 8182 additions and 753 deletions

View File

@ -0,0 +1,91 @@
import { Asset } from '@oceanprotocol/lib'
export const algorithmAquarius: Asset = {
'@context': ['https://w3id.org/did/v1'],
id: 'did:op:6654b0793765b269696cec8d2f0d077d9bbcdd3c4f033d941ab9684e8ad06630',
nftAddress: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
version: '4.1.0',
chainId: 1,
metadata: {
created: '2022-09-29T11:30:26Z',
updated: '2022-09-29T11:30:26Z',
type: 'algorithm',
name: 'algorithmTestitest',
description: 'This is an algorithm test.',
links: ['https://www.oceanprotocol.com/sample'],
tags: [
'trading',
'defi',
'algorithm',
'algorithmic-crypto-trading',
'algo-trading',
'trading-strategy',
'cryptocurrency',
'crypto'
],
author: 'Test User',
license: 'https://market.oceanprotocol.com/terms',
additionalInformation: {
termsAndConditions: true
},
algorithm: {
language: 'json',
version: '0.1',
container: {
entrypoint: 'python $algo',
tag: 'latest',
image: 'https://docker.com/test.img',
checksum: ''
}
}
},
services: [
{
id: 'dbc42f4c62d2452f8731fd023eacfae74e9c7a42fbd12ce84310f13342e4aab1',
type: 'access',
files:
'0x04022ef1afafe340f41b261ef721b8dd55dee094975cc70330803d760beef38871948ce572ff1c533d56cda2665749ed2eb8283e243ec5ee19011f510b6b263b2da0af537e3f1fdff7ddd90fa26c7a4761a6d26928bc1348a302634012aac7998e92c84456ab73e9a847120c44ebda15781787e8c382391b2eaefc8b8d36998f3998d1c4647f4f7bb28f4278093c1d231f66e78f81452049443b9e540aeb42ebbdc1b748c024eb10218532814736e241efa1c2a687685b4e2ea7a877685aa0ea325d1a8cf765d1b423b32d81ec3c3e22fc9c15c6b9b71f2862edaec4e4cf7c3a638ffc0ecb88ede3cabb511d4780543a53c001a95f42de1877796e13c997b57bc671507e92198934b4ea7c2e6554993388421253e8c2f10458dec872a7ebfa71b6e77ed359222c93261ba252028c5da06ccf8defcd529885b2125816325a47e23728b513',
datatokenAddress: '0x067e1E6ec580F3F0f6781679A4A5AB07A6464b08',
serviceEndpoint: 'https://v4.provider.goerli.oceanprotocol.com',
timeout: 604800
}
],
event: {
tx: '0x3e07a75c1cc5d4146222a93ab4319144e60ecca3ebfb8b15f1ff339d6f479dc9',
block: 7680195,
from: '0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7',
contract: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
datetime: '2022-09-29T11:31:12'
},
nft: {
address: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
name: 'Ocean Data NFT',
symbol: 'OCEAN-NFT',
state: 0,
tokenURI:
'data:application/json;base64,eyJuYW1lIjoiT2NlYW4gRGF0YSBORlQiLCJzeW1ib2wiOiJPQ0VBTi1ORlQiLCJkZXNjcmlwdGlvbiI6IlRoaXMgTkZUIHJlcHJlc2VudHMgYW4gYXNzZXQgaW4gdGhlIE9jZWFuIFByb3RvY29sIHY0IGVjb3N5c3RlbS5cblxuVmlldyBvbiBPY2VhbiBNYXJrZXQ6IGh0dHBzOi8vbWFya2V0Lm9jZWFucHJvdG9jb2wuY29tL2Fzc2V0L2RpZDpvcDo2NjU0YjA3OTM3NjViMjY5Njk2Y2VjOGQyZjBkMDc3ZDliYmNkZDNjNGYwMzNkOTQxYWI5Njg0ZThhZDA2NjMwIiwiZXh0ZXJuYWxfdXJsIjoiaHR0cHM6Ly9tYXJrZXQub2NlYW5wcm90b2NvbC5jb20vYXNzZXQvZGlkOm9wOjY2NTRiMDc5Mzc2NWIyNjk2OTZjZWM4ZDJmMGQwNzdkOWJiY2RkM2M0ZjAzM2Q5NDFhYjk2ODRlOGFkMDY2MzAiLCJiYWNrZ3JvdW5kX2NvbG9yIjoiMTQxNDE0IiwiaW1hZ2VfZGF0YSI6ImRhdGE6aW1hZ2Uvc3ZnK3htbCwlM0Nzdmcgdmlld0JveD0nMCAwIDk5IDk5JyBmaWxsPSd1bmRlZmluZWQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyclM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5Mjc3JyBkPSdNMCw5OUwwLDI5QzksMjUgMTksMjIgMjksMjFDMzgsMTkgNDksMTkgNjEsMjFDNzIsMjIgODUsMjUgOTksMjlMOTksOTlaJy8lM0UlM0NwYXRoIGZpbGw9JyUyM2ZmNDA5MmJiJyBkPSdNMCw5OUwwLDU1QzgsNDkgMTcsNDQgMjgsNDNDMzgsNDEgNTAsNDIgNjMsNDNDNzUsNDMgODcsNDIgOTksNDJMOTksOTlaJyUzRSUzQy9wYXRoJTNFJTNDcGF0aCBmaWxsPSclMjNmZjQwOTJmZicgZD0nTTAsOTlMMCw2OEMxMSw2NiAyMiw2NSAzMiw2N0M0MSw2OCA1MCw3MyA2MSw3NkM3MSw3OCA4NSw3OCA5OSw3OUw5OSw5OVonJTNFJTNDL3BhdGglM0UlM0Mvc3ZnJTNFIn0=',
owner: '0x99840Df5Cb42faBE0Feb8811Aaa4BC99cA6C84e0',
created: '2022-09-29T11:31:12'
},
datatokens: [
{
address: '0x067e1E6ec580F3F0f6781679A4A5AB07A6464b08',
name: 'Stupendous Orca Token',
symbol: 'STUORC-59',
serviceId:
'dbc42f4c62d2452f8731fd023eacfae74e9c7a42fbd12ce84310f13342e4aab1'
}
],
stats: {
orders: 22,
price: {
value: 3231343254,
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
tokenSymbol: 'OCEAN'
}
},
purgatory: {
state: false,
reason: ''
}
}

View File

@ -1,6 +1,6 @@
import { Asset } from '@oceanprotocol/lib' import { Asset } from '@oceanprotocol/lib'
export const assetAquarius: Asset = { export const datasetAquarius: Asset = {
'@context': ['https://w3id.org/did/v1'], '@context': ['https://w3id.org/did/v1'],
id: 'did:op:6654b0793765b269696cec8d2f0d077d9bbcdd3c4f033d941ab9684e8ad06630', id: 'did:op:6654b0793765b269696cec8d2f0d077d9bbcdd3c4f033d941ab9684e8ad06630',
nftAddress: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d', nftAddress: '0xbA5BA7B09e2FA1eb0258f647503F81D2Af5cb07d',
@ -12,7 +12,16 @@ export const assetAquarius: Asset = {
type: 'dataset', type: 'dataset',
name: 'Testitest', name: 'Testitest',
description: 'This is a test.', description: 'This is a test.',
tags: [], tags: [
'trading',
'defi',
'algorithm',
'algorithmic-crypto-trading',
'algo-trading',
'trading-strategy',
'cryptocurrency',
'crypto'
],
author: 'Test User', author: 'Test User',
license: 'https://market.oceanprotocol.com/terms', license: 'https://market.oceanprotocol.com/terms',
additionalInformation: { additionalInformation: {

View File

@ -1,7 +1,7 @@
import { assetAquarius } from './assetAquarius' import { datasetAquarius } from './datasetAquarius'
export const asset: AssetExtended = { export const asset: AssetExtended = {
...assetAquarius, ...datasetAquarius,
accessDetails: { accessDetails: {
templateId: 1, templateId: 1,
publisherMarketOrderFee: '0', publisherMarketOrderFee: '0',

View File

@ -1574,4 +1574,4 @@ export const assets: AssetExtended[] = [
validOrderTx: null validOrderTx: null
} }
} }
] ]

View File

@ -5,6 +5,5 @@ export default {
chainIds: [5, 1, 137, 56, 1285, 246], chainIds: [5, 1, 137, 56, 1285, 246],
bookmarks: [], bookmarks: [],
privacyPolicySlug: '/privacy/en', privacyPolicySlug: '/privacy/en',
showPPC: true, showPPC: true
infiniteApproval: false
} }

View File

@ -1,3 +1,3 @@
import { assets } from '../../__fixtures__/assetsWithAccessDetails' import { assets } from '../../__fixtures__/datasetsWithAccessDetails'
export const getAccessDetailsForAssets = jest.fn().mockResolvedValue(assets) export const getAccessDetailsForAssets = jest.fn().mockResolvedValue(assets)

View File

@ -1,7 +1,7 @@
import marketMetadata from '../__fixtures__/marketMetadata' import marketMetadata from '../__fixtures__/marketMetadata'
import userPreferences from '../__fixtures__/userPreferences' import userPreferences from '../__fixtures__/userPreferences'
import web3 from '../__fixtures__/web3' import web3 from '../__fixtures__/web3'
import { asset } from '../__fixtures__/assetWithAccessDetails' import { asset } from '../__fixtures__/datasetWithAccessDetails'
jest.mock('../../src/@context/MarketMetadata', () => ({ jest.mock('../../src/@context/MarketMetadata', () => ({
useMarketMetadata: () => marketMetadata useMarketMetadata: () => marketMetadata

View File

@ -30,44 +30,47 @@
{ {
"name": "files", "name": "files",
"label": "File", "label": "File",
"prominentHelp": false, "prominentHelp": false,
"type": "tabs", "type": "tabs",
"fields": [{ "fields": [
"value": "ipfs", {
"title": "IPFS", "value": "ipfs",
"label": "CID", "title": "IPFS",
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq", "label": "CID",
"help": "This CID will be stored encrypted after publishing.", "placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ", "help": "This CID will be stored encrypted after publishing.",
"prominentHelp": true, "computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"type": "files", "prominentHelp": true,
"required": true "type": "files",
}, "required": true
{ },
"value": "arweave", {
"title": "Arweave", "value": "arweave",
"label": "Transaction ID", "title": "Arweave",
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd", "label": "Transaction ID",
"help": "This Transaction ID will be stored encrypted after publishing.", "placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ", "help": "This Transaction ID will be stored encrypted after publishing.",
"prominentHelp": true, "computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"type": "files", "prominentHelp": true,
"required": true "type": "files",
}, "required": true
{ },
"value": "url", {
"title": "URL", "value": "url",
"label": "File", "title": "URL",
"placeholder": "e.g. https://file.com/file.json", "label": "File",
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**", "placeholder": "e.g. https://file.com/file.json",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ", "help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
"prominentHelp": true, "computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"type": "files", "prominentHelp": true,
"required": true "type": "files",
}], "required": true
}
],
"sortOptions": false, "sortOptions": false,
"required": true "required": true
},{ },
{
"name": "links", "name": "links",
"label": "Sample file", "label": "Sample file",
"prominentHelp": false, "prominentHelp": false,
@ -83,7 +86,8 @@
"prominentHelp": true, "prominentHelp": true,
"type": "files", "type": "files",
"required": false "required": false
}], }
],
"required": false "required": false
}, },
@ -116,6 +120,20 @@
"placeholder": "e.g. 0X123ABC...", "placeholder": "e.g. 0X123ABC...",
"help": "This address will receive the revenue from all sales. More info available in our [docs](https://docs.oceanprotocol.com/core-concepts/datanft-and-datatoken#revenue).", "help": "This address will receive the revenue from all sales. More info available in our [docs](https://docs.oceanprotocol.com/core-concepts/datanft-and-datatoken#revenue).",
"required": false "required": false
},
{
"name": "assetState",
"label": "Asset Status",
"help": "This asset will no longer be visible to other users and it won't be possible to purchase it. More info available in our [docs](https://docs.oceanprotocol.com/core-concepts/did-ddo#state).",
"type": "select",
"options": [
"Active",
"Revoked by publisher",
"Ordering is temporary disabled",
"Asset unlisted"
],
"sortOptions": false,
"required": false
} }
] ]
} }

View File

@ -24,8 +24,7 @@
}, },
"approval": { "approval": {
"tooltips": { "tooltips": {
"approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences.", "approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences."
"approveInfinite": "Give the smart contract permission to spend infinte amounts of your COIN so you have to do this only once. You can disable allowing infinite amounts in your user preferences."
} }
} }
} }

View File

@ -104,60 +104,64 @@
{ {
"name": "files", "name": "files",
"label": "File", "label": "File",
"prominentHelp": false, "prominentHelp": false,
"type": "tabs", "type": "tabs",
"fields": [{ "fields": [
"value": "ipfs", {
"title": "IPFS", "value": "ipfs",
"label": "CID", "title": "IPFS",
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq", "label": "CID",
"help": "This CID will be stored encrypted after publishing.", "placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ", "help": "This CID will be stored encrypted after publishing.",
"prominentHelp": true, "computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"type": "files", "prominentHelp": true,
"required": true "type": "files",
}, "required": true
{ },
"value": "arweave", {
"title": "Arweave", "value": "arweave",
"label": "Transaction ID", "title": "Arweave",
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd", "label": "Transaction ID",
"help": "This Transaction ID will be stored encrypted after publishing.", "placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ", "help": "This Transaction ID will be stored encrypted after publishing.",
"prominentHelp": true, "computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"type": "files", "prominentHelp": true,
"required": true "type": "files",
}, "required": true
{ },
"value": "url", {
"title": "URL", "value": "url",
"label": "File", "title": "URL",
"placeholder": "e.g. https://file.com/file.json", "label": "File",
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**", "placeholder": "e.g. https://file.com/file.json",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ", "help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
"prominentHelp": true, "computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"type": "files", "prominentHelp": true,
"required": true "type": "files",
}], "required": true
}
],
"sortOptions": false, "sortOptions": false,
"required": true "required": true
},{ },
{
"name": "links", "name": "links",
"label": "Sample file", "label": "Sample file",
"prominentHelp": false, "prominentHelp": false,
"type": "tabs", "type": "tabs",
"fields": [ "fields": [
{ {
"value": "url", "value": "url",
"title": "URL", "title": "URL",
"label": "File", "label": "File",
"placeholder": "e.g. https://file.com/file.json", "placeholder": "e.g. https://file.com/file.json",
"help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**", "help": "This URL will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ", "computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true, "prominentHelp": true,
"type": "files", "type": "files",
"required": false "required": false
}], }
],
"required": false "required": false
}, },
{ {

7987
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@
"@coingecko/cryptoformat": "^0.5.4", "@coingecko/cryptoformat": "^0.5.4",
"@loadable/component": "^5.15.2", "@loadable/component": "^5.15.2",
"@oceanprotocol/art": "^3.2.0", "@oceanprotocol/art": "^3.2.0",
"@oceanprotocol/lib": "^2.5.2", "@oceanprotocol/lib": "^2.6.0",
"@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/typographies": "^0.1.0",
"@oceanprotocol/use-dark-mode": "^2.4.3", "@oceanprotocol/use-dark-mode": "^2.4.3",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
@ -55,9 +55,9 @@
"react-dotdotdot": "^1.3.1", "react-dotdotdot": "^1.3.1",
"react-modal": "^3.16.1", "react-modal": "^3.16.1",
"react-paginate": "^8.1.4", "react-paginate": "^8.1.4",
"react-select": "^5.6.1", "react-select": "^5.7.0",
"react-spring": "^9.5.5", "react-spring": "^9.5.5",
"react-tabs": "^5.1.0", "react-tabs": "^6.0.0",
"react-toastify": "^9.1.1", "react-toastify": "^9.1.1",
"remark": "^14.0.2", "remark": "^14.0.2",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
@ -71,19 +71,19 @@
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-essentials": "^6.5.13", "@storybook/addon-essentials": "^6.5.15",
"@storybook/builder-webpack5": "^6.5.13", "@storybook/builder-webpack5": "^6.5.13",
"@storybook/manager-webpack5": "^6.5.13", "@storybook/manager-webpack5": "^6.5.13",
"@storybook/react": "^6.5.13", "@storybook/react": "^6.5.13",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@types/jest": "^29.2.3", "@types/jest": "^29.2.5",
"@types/js-cookie": "^3.0.2", "@types/js-cookie": "^3.0.2",
"@types/loadable__component": "^5.13.4", "@types/loadable__component": "^5.13.4",
"@types/node": "^18.8.5", "@types/node": "^18.8.5",
"@types/react": "^18.0.25", "@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9", "@types/react-dom": "^18.0.10",
"@types/react-modal": "^3.13.1", "@types/react-modal": "^3.13.1",
"@types/react-paginate": "^7.1.1", "@types/react-paginate": "^7.1.1",
"@types/remove-markdown": "^0.3.1", "@types/remove-markdown": "^0.3.1",
@ -98,12 +98,12 @@
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.11", "eslint-plugin-react": "^7.31.11",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-testing-library": "^5.9.1", "eslint-plugin-testing-library": "^5.10.0",
"https-browserify": "^1.0.0", "https-browserify": "^1.0.0",
"husky": "^8.0.2", "husky": "^8.0.2",
"jest": "^29.3.1", "jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1", "jest-environment-jsdom": "^29.3.1",
"prettier": "^2.8.0", "prettier": "^2.8.3",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"process": "^0.11.10", "process": "^0.11.10",
"serve": "^14.1.2", "serve": "^14.1.2",

View File

@ -16,6 +16,8 @@ import { getOceanConfig, getDevelopmentConfig } from '@utils/ocean'
import { getAccessDetails } from '@utils/accessDetailsAndPricing' import { getAccessDetails } from '@utils/accessDetailsAndPricing'
import { useIsMounted } from '@hooks/useIsMounted' import { useIsMounted } from '@hooks/useIsMounted'
import { useMarketMetadata } from './MarketMetadata' import { useMarketMetadata } from './MarketMetadata'
import { assetStateToString } from '@utils/assetState'
import { isValidDid } from '@utils/ddo'
export interface AssetProviderValue { export interface AssetProviderValue {
isInPurgatory: boolean isInPurgatory: boolean
@ -28,6 +30,7 @@ export interface AssetProviderValue {
isOwner: boolean isOwner: boolean
oceanConfig: Config oceanConfig: Config
loading: boolean loading: boolean
assetState: string
fetchAsset: (token?: CancelToken) => Promise<void> fetchAsset: (token?: CancelToken) => Promise<void>
} }
@ -53,6 +56,7 @@ function AssetProvider({
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [isAssetNetwork, setIsAssetNetwork] = useState<boolean>() const [isAssetNetwork, setIsAssetNetwork] = useState<boolean>()
const [oceanConfig, setOceanConfig] = useState<Config>() const [oceanConfig, setOceanConfig] = useState<Config>()
const [assetState, setAssetState] = useState<string>()
const newCancelToken = useCancelToken() const newCancelToken = useCancelToken()
const isMounted = useIsMounted() const isMounted = useIsMounted()
@ -63,6 +67,13 @@ function AssetProvider({
const fetchAsset = useCallback( const fetchAsset = useCallback(
async (token?: CancelToken) => { async (token?: CancelToken) => {
if (!did) return if (!did) return
const isDid = isValidDid(did)
if (!isDid) {
setError(`The url is not for a valid DID`)
LoggerInstance.error(`[asset] Not a valid DID`)
return
}
LoggerInstance.log('[asset] Fetching asset...') LoggerInstance.log('[asset] Fetching asset...')
setLoading(true) setLoading(true)
@ -77,27 +88,16 @@ function AssetProvider({
return return
} }
if ([1, 2, 3].includes(asset.nft.state)) { if (asset.nft.state === (1 | 2 | 3)) {
// handle nft states as documented in https://docs.oceanprotocol.com/core-concepts/did-ddo/#state setTitle(
let state `This asset has been set as "${assetStateToString(
switch (asset.nft.state) { asset.nft.state
case 1: )}" by the publisher`
state = 'end-of-life' )
break
case 2:
state = 'deprecated'
break
case 3:
state = 'revoked'
break
}
setTitle(`This asset has been flagged as "${state}" by the publisher`)
setError(`\`${did}\`` + `\n\nPublisher Address: ${asset.nft.owner}`) setError(`\`${did}\`` + `\n\nPublisher Address: ${asset.nft.owner}`)
LoggerInstance.error(`[asset] Failed getting asset for ${did}`, asset) LoggerInstance.error(`[asset] Failed getting asset for ${did}`, asset)
return return
} }
if (asset) { if (asset) {
setError(undefined) setError(undefined)
setAsset((prevState) => ({ setAsset((prevState) => ({
@ -108,12 +108,13 @@ function AssetProvider({
setOwner(asset.nft?.owner) setOwner(asset.nft?.owner)
setIsInPurgatory(asset.purgatory?.state) setIsInPurgatory(asset.purgatory?.state)
setPurgatoryData(asset.purgatory) setPurgatoryData(asset.purgatory)
setAssetState(assetStateToString(asset.nft.state))
LoggerInstance.log('[asset] Got asset', asset) LoggerInstance.log('[asset] Got asset', asset)
} }
setLoading(false) setLoading(false)
}, },
[did] [did, accountId]
) )
// ----------------------------------- // -----------------------------------
@ -190,6 +191,14 @@ function AssetProvider({
setOceanConfig(oceanConfig) setOceanConfig(oceanConfig)
}, [asset?.chainId]) }, [asset?.chainId])
// -----------------------------------
// Set Asset State as a string
// -----------------------------------
useEffect(() => {
if (!asset?.nft) return
setAssetState(assetStateToString(asset.nft.state))
}, [asset])
return ( return (
<AssetContext.Provider <AssetContext.Provider
value={ value={
@ -205,7 +214,8 @@ function AssetProvider({
fetchAsset, fetchAsset,
isAssetNetwork, isAssetNetwork,
isOwner, isOwner,
oceanConfig oceanConfig,
assetState
} as AssetProviderValue } as AssetProviderValue
} }
> >

View File

@ -24,8 +24,6 @@ interface UserPreferencesValue {
removeBookmark: (did: string) => void removeBookmark: (did: string) => void
setPrivacyPolicySlug: (slug: string) => void setPrivacyPolicySlug: (slug: string) => void
setShowPPC: (value: boolean) => void setShowPPC: (value: boolean) => void
infiniteApproval: boolean
setInfiniteApproval: (value: boolean) => void
locale: string locale: string
} }
@ -73,10 +71,6 @@ function UserPreferencesProvider({
localStorage?.showPPC !== false localStorage?.showPPC !== false
) )
const [infiniteApproval, setInfiniteApproval] = useState(
localStorage?.infiniteApproval || false
)
// Write values to localStorage on change // Write values to localStorage on change
useEffect(() => { useEffect(() => {
setLocalStorage({ setLocalStorage({
@ -85,18 +79,9 @@ function UserPreferencesProvider({
currency, currency,
bookmarks, bookmarks,
privacyPolicySlug, privacyPolicySlug,
showPPC, showPPC
infiniteApproval
}) })
}, [ }, [chainIds, debug, currency, bookmarks, privacyPolicySlug, showPPC])
chainIds,
debug,
currency,
bookmarks,
privacyPolicySlug,
showPPC,
infiniteApproval
])
// Set ocean.js log levels, default: Error // Set ocean.js log levels, default: Error
useEffect(() => { useEffect(() => {
@ -152,8 +137,6 @@ function UserPreferencesProvider({
bookmarks, bookmarks,
privacyPolicySlug, privacyPolicySlug,
showPPC, showPPC,
infiniteApproval,
setInfiniteApproval,
setChainIds, setChainIds,
setDebug, setDebug,
setCurrency, setCurrency,

View File

@ -22,6 +22,7 @@ export function getNetworkType(network: EthereumListsChain): string {
export function getNetworkDisplayName(data: EthereumListsChain): string { export function getNetworkDisplayName(data: EthereumListsChain): string {
let displayName let displayName
if (!data) return 'Unknown'
switch (data.chainId) { switch (data.chainId) {
case 137: case 137:

View File

@ -46,6 +46,7 @@ declare global {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
query: any query: any
sort?: { [jsonPath: string]: SortDirectionOptions } sort?: { [jsonPath: string]: SortDirectionOptions }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
aggs?: any aggs?: any
} }
} }

View File

@ -9,8 +9,8 @@ const defaultBaseQueryReturn = {
query: { query: {
bool: { bool: {
filter: [ filter: [
{ terms: { chainId: [1, 3] } },
{ term: { _index: 'aquarius' } }, { term: { _index: 'aquarius' } },
{ terms: { chainId: [1, 3] } },
{ term: { 'purgatory.state': false } }, { term: { 'purgatory.state': false } },
{ bool: { must_not: [{ term: { 'nft.state': 5 } }] } } { bool: { must_not: [{ term: { 'nft.state': 5 } }] } }
] ]

View File

@ -42,6 +42,24 @@ export function getFilterTerm(
export function generateBaseQuery( export function generateBaseQuery(
baseQueryParams: BaseQueryParams baseQueryParams: BaseQueryParams
): SearchQuery { ): SearchQuery {
const filters: unknown[] = [getFilterTerm('_index', 'aquarius')]
baseQueryParams.filters && filters.push(...baseQueryParams.filters)
baseQueryParams.chainIds &&
filters.push(getFilterTerm('chainId', baseQueryParams.chainIds))
!baseQueryParams.ignorePurgatory &&
filters.push(getFilterTerm('purgatory.state', false))
!baseQueryParams.ignoreState &&
filters.push({
bool: {
must_not: [
{
term: {
'nft.state': 5
}
}
]
}
})
const generatedQuery = { const generatedQuery = {
from: baseQueryParams.esPaginationOptions?.from || 0, from: baseQueryParams.esPaginationOptions?.from || 0,
size: size:
@ -51,31 +69,7 @@ export function generateBaseQuery(
query: { query: {
bool: { bool: {
...baseQueryParams.nestedQuery, ...baseQueryParams.nestedQuery,
filter: [ filter: filters
...(baseQueryParams.filters || []),
baseQueryParams.chainIds
? getFilterTerm('chainId', baseQueryParams.chainIds)
: [],
getFilterTerm('_index', 'aquarius'),
...(baseQueryParams.ignorePurgatory
? []
: [getFilterTerm('purgatory.state', false)]),
...(baseQueryParams.ignoreState
? []
: [
{
bool: {
must_not: [
{
term: {
'nft.state': 5
}
}
]
}
}
])
]
} }
} }
} as SearchQuery } as SearchQuery
@ -90,7 +84,6 @@ export function generateBaseQuery(
baseQueryParams.sortOptions.sortDirection || baseQueryParams.sortOptions.sortDirection ||
SortDirectionOptions.Descending SortDirectionOptions.Descending
} }
return generatedQuery return generatedQuery
} }
@ -260,6 +253,7 @@ export async function getPublishedAssets(
const filters: FilterTerm[] = [] const filters: FilterTerm[] = []
filters.push(getFilterTerm('nft.state', [0, 4, 5]))
filters.push(getFilterTerm('nft.owner', accountId.toLowerCase())) filters.push(getFilterTerm('nft.owner', accountId.toLowerCase()))
accesType !== undefined && accesType !== undefined &&
filters.push(getFilterTerm('services.type', accesType)) filters.push(getFilterTerm('services.type', accesType))

39
src/@utils/assetState.ts Normal file
View File

@ -0,0 +1,39 @@
export function assetStateToString(state: number): string {
switch (state) {
case 0:
return 'Active'
case 1:
return 'End-of-life'
case 2:
return 'Deprecated (by another asset)'
case 3:
return 'Revoked by publisher'
case 4:
return 'Ordering is temporary disabled'
case 5:
return 'Asset unlisted'
default:
break
}
}
export function assetStateToNumber(state: string): number {
switch (state) {
case 'Active':
return 0
case 'End-of-life':
return 1
case 'Deprecated (by another asset)':
return 2
case 'Revoked by publisher':
return 3
case 'Ordering is temporary disabled':
return 4
case 'Asset unlisted':
return 5
default:
break
}
}

View File

@ -1,5 +1,10 @@
import { Asset, DDO, Service } from '@oceanprotocol/lib' import { Asset, DDO, Service } from '@oceanprotocol/lib'
export function isValidDid(did: string): boolean {
const regex = /did:op:[A-Za-z0-9]{64}/
return regex.test(did)
}
export function getServiceByName( export function getServiceByName(
ddo: Asset | DDO, ddo: Asset | DDO,
name: 'access' | 'compute' name: 'access' | 'compute'

View File

@ -147,3 +147,14 @@ export async function downloadFile(
) )
await downloadFileBrowser(downloadUrl) await downloadFileBrowser(downloadUrl)
} }
export async function checkValidProvider(
providerUrl: string
): Promise<boolean> {
try {
const response = await ProviderInstance.isValidProvider(providerUrl)
return response
} catch (error) {
LoggerInstance.error(error.message)
}
}

View File

@ -1,4 +1,4 @@
import { sanitizeUrl } from '.' import { sanitizeUrl, isGoogleUrl } from '.'
describe('@utils/url', () => { describe('@utils/url', () => {
test('sanitizeUrl', () => { test('sanitizeUrl', () => {
@ -7,3 +7,20 @@ describe('@utils/url', () => {
expect(sanitizeUrl('ftp://example.com')).toBe('about:blank') expect(sanitizeUrl('ftp://example.com')).toBe('about:blank')
}) })
}) })
describe('isGoogleUrl', () => {
it('should return true if the url is a google domain', () => {
expect(isGoogleUrl('https://google.com')).toBe(true)
expect(isGoogleUrl('https://drive.google.com')).toBe(true)
expect(isGoogleUrl('https://docs.google.com')).toBe(true)
expect(isGoogleUrl('https://sheets.google.com')).toBe(true)
expect(isGoogleUrl('https://meet.google.com')).toBe(true)
expect(isGoogleUrl('https://calendar.google.com')).toBe(true)
})
it('should return false if the url is not a google domain', () => {
expect(isGoogleUrl('https://google.test.com')).toBe(false)
expect(isGoogleUrl('https://drive.gloogle.com')).toBe(false)
expect(isGoogleUrl('https://drive.google.test.com')).toBe(false)
expect(isGoogleUrl('https://google.com.test.com')).toBe(false)
})
})

View File

@ -1,5 +1,14 @@
import isUrl from 'is-url-superb'
export function sanitizeUrl(url: string) { export function sanitizeUrl(url: string) {
const u = decodeURI(url).trim().toLowerCase() const u = decodeURI(url).trim().toLowerCase()
const isAllowedUrlScheme = u.startsWith('http://') || u.startsWith('https://') const isAllowedUrlScheme = u.startsWith('http://') || u.startsWith('https://')
return isAllowedUrlScheme ? url : 'about:blank' return isAllowedUrlScheme ? url : 'about:blank'
} }
// check if the url is a google domain
export const isGoogleUrl = (url: string): boolean => {
if (!url || !isUrl(url)) return
const googleUrl = new URL(url)
return googleUrl.hostname.endsWith('google.com')
}

View File

@ -1,6 +1,7 @@
import { isCID } from '@utils/ipfs' import { isCID } from '@utils/ipfs'
import isUrl from 'is-url-superb' import isUrl from 'is-url-superb'
import * as Yup from 'yup' import * as Yup from 'yup'
import { isGoogleUrl } from './url/index'
export function testLinks(isEdit?: boolean) { export function testLinks(isEdit?: boolean) {
return Yup.string().test((value, context) => { return Yup.string().test((value, context) => {
@ -28,7 +29,7 @@ export function testLinks(isEdit?: boolean) {
validField = true validField = true
} }
// if the url has google drive, we need to block the user from submit // if the url has google drive, we need to block the user from submit
if (value?.toString().includes('drive.google')) { if (isGoogleUrl(value?.toString())) {
validField = false validField = false
errorMessage = errorMessage =
'Google Drive is not a supported hosting service. Please use an alternative.' 'Google Drive is not a supported hosting service. Please use an alternative.'

View File

@ -3,7 +3,7 @@ import { ComponentStory, ComponentMeta } from '@storybook/react'
import MarketMetadataProvider from '@context/MarketMetadata' import MarketMetadataProvider from '@context/MarketMetadata'
import { UserPreferencesProvider } from '@context/UserPreferences' import { UserPreferencesProvider } from '@context/UserPreferences'
import AssetList, { AssetListProps } from '.' import AssetList, { AssetListProps } from '.'
import { assets } from '../../../../.jest/__fixtures__/assetsWithAccessDetails' import { assets } from '../../../../.jest/__fixtures__/datasetsWithAccessDetails'
export default { export default {
title: 'Component/@shared/AssetList', title: 'Component/@shared/AssetList',

View File

@ -1,7 +1,7 @@
import { render, screen, fireEvent } from '@testing-library/react' import { render, screen, fireEvent } from '@testing-library/react'
import React from 'react' import React from 'react'
import AssetList from './index' import AssetList from './index'
import { assetAquarius } from '../../../../.jest/__fixtures__/assetAquarius' import { datasetAquarius } from '../../../../.jest/__fixtures__/datasetAquarius'
describe('@shared/AssetList', () => { describe('@shared/AssetList', () => {
it('renders without crashing', async () => { it('renders without crashing', async () => {
@ -9,7 +9,7 @@ describe('@shared/AssetList', () => {
render( render(
<AssetList <AssetList
assets={[assetAquarius]} assets={[datasetAquarius]}
showPagination showPagination
page={1} page={1}
totalPages={10} totalPages={10}

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import testRender from '../../../../.jest/testRender' import testRender from '../../../../.jest/testRender'
import AssetTeaser from './index' import AssetTeaser from './index'
import { asset } from '../../../../.jest/__fixtures__/assetWithAccessDetails' import { asset } from '../../../../.jest/__fixtures__/datasetWithAccessDetails'
describe('@shared/AssetTeaser', () => { describe('@shared/AssetTeaser', () => {
testRender(<AssetTeaser asset={asset} />) testRender(<AssetTeaser asset={asset} />)

View File

@ -40,6 +40,9 @@ export default function FileIcon({
? filesize(Number(file.contentLength)).toString() ? filesize(Number(file.contentLength)).toString()
: ''} : ''}
</li> </li>
<li>
{file.type === 'smartcontract' ? 'smart\ncontract' : file.type}
</li>
</> </>
) : ( ) : (
<li className={styles.empty}>No file info available</li> <li className={styles.empty}>No file info available</li>

View File

@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react' import React from 'react'
import FilesInput from './index' import FilesInput from './index'
import { useField } from 'formik' import { useField } from 'formik'
import { getFileInfo } from '@utils/provider' import { getFileInfo, checkValidProvider } from '@utils/provider'
jest.mock('formik') jest.mock('formik')
jest.mock('@utils/provider') jest.mock('@utils/provider')
@ -48,6 +48,7 @@ const mockForm = {
describe('@shared/FormInput/InputElement/FilesInput', () => { describe('@shared/FormInput/InputElement/FilesInput', () => {
it('renders without crashing', async () => { it('renders without crashing', async () => {
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers]) ;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers])
;(checkValidProvider as jest.Mock).mockReturnValue(true)
;(getFileInfo as jest.Mock).mockReturnValue([ ;(getFileInfo as jest.Mock).mockReturnValue([
{ {
valid: true, valid: true,

View File

@ -3,9 +3,10 @@ import { useField } from 'formik'
import FileInfo from './Info' import FileInfo from './Info'
import UrlInput from '../URLInput' import UrlInput from '../URLInput'
import { InputProps } from '@shared/FormInput' import { InputProps } from '@shared/FormInput'
import { getFileInfo } from '@utils/provider' import { getFileInfo, checkValidProvider } from '@utils/provider'
import { LoggerInstance } from '@oceanprotocol/lib' import { LoggerInstance } from '@oceanprotocol/lib'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import { isGoogleUrl } from '@utils/url/index'
export default function FilesInput(props: InputProps): ReactElement { export default function FilesInput(props: InputProps): ReactElement {
const [field, meta, helpers] = useField(props.name) const [field, meta, helpers] = useField(props.name)
@ -26,12 +27,19 @@ export default function FilesInput(props: InputProps): ReactElement {
setIsLoading(true) setIsLoading(true)
// TODO: handled on provider // TODO: handled on provider
if (url.includes('drive.google')) { if (isGoogleUrl(url)) {
throw Error( throw Error(
'Google Drive is not a supported hosting service. Please use an alternative.' 'Google Drive is not a supported hosting service. Please use an alternative.'
) )
} }
// Check if provider is a valid provider
const isValid = await checkValidProvider(providerUrl)
if (!isValid)
throw Error(
'✗ Provider cannot be reached, please check status.oceanprotocol.com and try again later.'
)
const checkedFile = await getFileInfo(url, providerUrl, storageType) const checkedFile = await getFileInfo(url, providerUrl, storageType)
// error if something's not right from response // error if something's not right from response

View File

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
import React from 'react' import React from 'react'
import Price from './index' import Price from './index'
import { asset } from '../../../../.jest/__fixtures__/assetWithAccessDetails' import { asset } from '../../../../.jest/__fixtures__/datasetWithAccessDetails'
import prices from '../../../../.jest/__fixtures__/prices' import prices from '../../../../.jest/__fixtures__/prices'
jest.mock('../../../@context/Prices', () => ({ jest.mock('../../../@context/Prices', () => ({

View File

@ -15,17 +15,18 @@ export default function Price({
size?: 'small' | 'mini' | 'large' size?: 'small' | 'mini' | 'large'
}): ReactElement { }): ReactElement {
const isSupported = const isSupported =
accessDetails?.type === 'fixed' || accessDetails?.type === 'free' (accessDetails?.type === 'fixed' || accessDetails?.type === 'free') &&
accessDetails?.baseToken?.symbol
const price = `${orderPriceAndFees?.price || accessDetails?.price}` const price = `${orderPriceAndFees?.price || accessDetails?.price}`
return isSupported ? ( return isSupported ? (
<PriceUnit <PriceUnit
price={Number(price)} price={Number(price)}
symbol={accessDetails.baseToken?.symbol} symbol={accessDetails?.baseToken?.symbol}
className={className} className={className}
size={size} size={size}
conversion={conversion} conversion={conversion}
type={accessDetails.type} type={accessDetails?.type}
/> />
) : null ) : null
} }

View File

@ -43,7 +43,7 @@ export default function Tooltip(props: TippyProps): ReactElement {
<Tippy <Tippy
interactive interactive
interactiveBorder={5} interactiveBorder={5}
zIndex={1} zIndex={3}
trigger={trigger || 'mouseenter focus'} trigger={trigger || 'mouseenter focus'}
disabled={disabled || null} disabled={disabled || null}
placement={placement || 'auto'} placement={placement || 'auto'}

View File

@ -39,9 +39,9 @@ export default function AssetStats() {
</span> </span>
) : null} ) : null}
{!asset?.stats || asset?.stats?.orders < 0 ? ( {!asset?.stats || asset?.stats?.orders < 0 ? (
'N/A' <span className={styles.stat}>N/A</span>
) : asset?.stats?.orders === 0 ? ( ) : asset?.stats?.orders === 0 ? (
'No sales yet' <span className={styles.stat}>No sales yet</span>
) : ( ) : (
<span className={styles.stat}> <span className={styles.stat}>
<span className={styles.number}>{asset.stats.orders}</span> sale <span className={styles.number}>{asset.stats.orders}</span> sale

View File

@ -23,14 +23,14 @@ const computeProps: ButtonBuyProps = {
action: 'compute', action: 'compute',
disabled: false, disabled: false,
hasPreviousOrder: false, hasPreviousOrder: false,
hasDatatoken: true, hasDatatoken: false,
btSymbol: 'btSymbol', btSymbol: 'btSymbol',
dtSymbol: 'dtSymbol', dtSymbol: 'dtSymbol',
dtBalance: '100000000000', dtBalance: '100000000000',
assetTimeout: '1 day', assetTimeout: '1 day',
assetType: 'algorithm', assetType: 'algorithm',
hasPreviousOrderSelectedComputeAsset: false, hasPreviousOrderSelectedComputeAsset: false,
hasDatatokenSelectedComputeAsset: true, hasDatatokenSelectedComputeAsset: false,
dtSymbolSelectedComputeAsset: 'dtSymbol', dtSymbolSelectedComputeAsset: 'dtSymbol',
dtBalanceSelectedComputeAsset: 'dtBalance', dtBalanceSelectedComputeAsset: 'dtBalance',
selectedComputeAssetType: 'selectedComputeAssetType', selectedComputeAssetType: 'selectedComputeAssetType',
@ -78,6 +78,11 @@ describe('Asset/AssetActions/ButtonBuy', () => {
render(<ButtonBuy {...downloadProps} priceType="free" hasPreviousOrder />) render(<ButtonBuy {...downloadProps} priceType="free" hasPreviousOrder />)
const button = screen.getByText('Download') const button = screen.getByText('Download')
expect(button).toContainHTML('<button') expect(button).toContainHTML('<button')
expect(
screen.getByText(
'This Dataset is free to use. Please note that network gas fees still apply, even when using free assets.'
)
).toBeInTheDocument()
}) })
it('Renders "Get" button for free assets without crashing', () => { it('Renders "Get" button for free assets without crashing', () => {
@ -105,6 +110,33 @@ describe('Asset/AssetActions/ButtonBuy', () => {
expect(button).toContainHTML('<button') expect(button).toContainHTML('<button')
}) })
it('Renders correct message for fixed-priced compute asset with free algorithm', () => {
render(<ButtonBuy {...computeProps} />)
expect(
screen.getByText(
'To use this algorithm, you will buy 1 dtSymbol and immediately send it back to the publisher. Connect to the correct network to interact with this asset. The C2D resources required to start the job are available, no payment is required for them. Please note that network gas fees still apply, even when using free assets.'
)
).toBeInTheDocument()
})
it('Renders correct message for free compute asset with free algorithm', () => {
render(<ButtonBuy {...computeProps} priceType="free" />)
expect(
screen.getByText(
'This algorithm is free to use. Connect to the correct network to interact with this asset. The C2D resources required to start the job are available, no payment is required for them. Please note that network gas fees still apply, even when using free assets.'
)
).toBeInTheDocument()
})
it('Renders correct message for free compute asset with free algorithm', () => {
render(<ButtonBuy {...computeProps} algorithmPriceType="fixed" />)
expect(
screen.getByText(
'To use this algorithm, you will buy 1 dtSymbol and immediately send it back to the publisher. Connect to the correct network to interact with this asset. The C2D resources required to start the job are available, no payment is required for them.'
)
).toBeInTheDocument()
})
it('Renders "Buy Compute Job" button for compute without crashing', () => { it('Renders "Buy Compute Job" button for compute without crashing', () => {
render(<ButtonBuy {...computeProps} hasDatatokenSelectedComputeAsset />) render(<ButtonBuy {...computeProps} hasDatatokenSelectedComputeAsset />)
const button = screen.getByText('Buy Compute Job') const button = screen.getByText('Buy Compute Job')

View File

@ -46,7 +46,8 @@ function getConsumeHelpText(
isBalanceSufficient: boolean, isBalanceSufficient: boolean,
consumableFeedback: string, consumableFeedback: string,
isSupportedOceanNetwork: boolean, isSupportedOceanNetwork: boolean,
web3: Web3 web3: Web3,
priceType: string
) { ) {
const text = const text =
isConsumable === false isConsumable === false
@ -57,7 +58,9 @@ function getConsumeHelpText(
? `You own ${dtBalance} ${dtSymbol} allowing you to use this dataset by spending 1 ${dtSymbol}, but without paying ${btSymbol} again.` ? `You own ${dtBalance} ${dtSymbol} allowing you to use this dataset by spending 1 ${dtSymbol}, but without paying ${btSymbol} again.`
: isBalanceSufficient === false : isBalanceSufficient === false
? `You do not have enough ${btSymbol} in your wallet to purchase this asset.` ? `You do not have enough ${btSymbol} in your wallet to purchase this asset.`
: `For using this ${assetType}, you will buy 1 ${dtSymbol} and immediately spend it back to the publisher.` : priceType === 'free'
? `This ${assetType} is free to use.`
: `To use this ${assetType}, you will buy 1 ${dtSymbol} and immediately send it back to the publisher.`
return text return text
} }
@ -71,7 +74,8 @@ function getAlgoHelpText(
hasDatatokenSelectedComputeAsset: boolean, hasDatatokenSelectedComputeAsset: boolean,
isBalanceSufficient: boolean, isBalanceSufficient: boolean,
isSupportedOceanNetwork: boolean, isSupportedOceanNetwork: boolean,
web3: Web3 web3: Web3,
algorithmPriceType: string
) { ) {
const text = const text =
(!dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset) || (!dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset) ||
@ -86,7 +90,9 @@ function getAlgoHelpText(
? `Connect to the correct network to interact with this asset.` ? `Connect to the correct network to interact with this asset.`
: isBalanceSufficient === false : isBalanceSufficient === false
? '' ? ''
: `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and spend it back to its publisher and pool.` : algorithmPriceType === 'free'
? `Additionally, the selected ${selectedComputeAssetType} is free to use.`
: `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and send it back to the publisher.`
return text return text
} }
@ -99,6 +105,8 @@ function getComputeAssetHelpText(
isConsumable: boolean, isConsumable: boolean,
consumableFeedback: string, consumableFeedback: string,
isBalanceSufficient: boolean, isBalanceSufficient: boolean,
algorithmPriceType: string,
priceType: string,
hasPreviousOrderSelectedComputeAsset?: boolean, hasPreviousOrderSelectedComputeAsset?: boolean,
hasDatatokenSelectedComputeAsset?: boolean, hasDatatokenSelectedComputeAsset?: boolean,
assetType?: string, assetType?: string,
@ -121,7 +129,8 @@ function getComputeAssetHelpText(
isBalanceSufficient, isBalanceSufficient,
consumableFeedback, consumableFeedback,
isSupportedOceanNetwork, isSupportedOceanNetwork,
web3 web3,
priceType
) )
const computeAlgoHelpText = getAlgoHelpText( const computeAlgoHelpText = getAlgoHelpText(
@ -134,13 +143,16 @@ function getComputeAssetHelpText(
hasDatatokenSelectedComputeAsset, hasDatatokenSelectedComputeAsset,
isBalanceSufficient, isBalanceSufficient,
isSupportedOceanNetwork, isSupportedOceanNetwork,
web3 web3,
algorithmPriceType
) )
const providerFeeHelpText = hasProviderFee const providerFeeHelpText = hasProviderFee
? 'In order to start the job you also need to pay the fees for renting the c2d resources.' ? 'In order to start the job you also need to pay the fees for renting the c2d resources.'
: 'C2D resources required to start the job are available, no payment required for those fees.' : 'The C2D resources required to start the job are available, no payment is required for them.'
const computeHelpText = `${computeAssetHelpText} ${computeAlgoHelpText} ${providerFeeHelpText}` let computeHelpText = `${computeAssetHelpText} ${computeAlgoHelpText} ${providerFeeHelpText}`
computeHelpText = computeHelpText.replace(/^\s+/, '')
return computeHelpText return computeHelpText
} }
@ -190,6 +202,53 @@ export default function ButtonBuy({
? 'Order Compute Job' ? 'Order Compute Job'
: `Buy Compute Job` : `Buy Compute Job`
function message(): string {
let message = ''
if (action === 'download') {
message = getConsumeHelpText(
btSymbol,
dtBalance,
dtSymbol,
hasDatatoken,
hasPreviousOrder,
assetType,
isConsumable,
isBalanceSufficient,
consumableFeedback,
isSupportedOceanNetwork,
web3,
priceType
)
} else {
message = getComputeAssetHelpText(
hasPreviousOrder,
hasDatatoken,
btSymbol,
dtSymbol,
dtBalance,
isConsumable,
consumableFeedback,
isBalanceSufficient,
algorithmPriceType,
priceType,
hasPreviousOrderSelectedComputeAsset,
hasDatatokenSelectedComputeAsset,
assetType,
dtSymbolSelectedComputeAsset,
dtBalanceSelectedComputeAsset,
selectedComputeAssetType,
isAlgorithmConsumable,
isSupportedOceanNetwork,
web3,
hasProviderFee
)
}
if (priceType === 'free' || algorithmPriceType === 'free') {
message +=
' Please note that network gas fees still apply, even when using free assets.'
}
return message
}
return ( return (
<div className={styles.actions}> <div className={styles.actions}>
{isLoading ? ( {isLoading ? (
@ -205,42 +264,7 @@ export default function ButtonBuy({
> >
{buttonText} {buttonText}
</Button> </Button>
<div className={styles.help}> <div className={styles.help}>{message()}</div>
{action === 'download'
? getConsumeHelpText(
btSymbol,
dtBalance,
dtSymbol,
hasDatatoken,
hasPreviousOrder,
assetType,
isConsumable,
isBalanceSufficient,
consumableFeedback,
isSupportedOceanNetwork,
web3
)
: getComputeAssetHelpText(
hasPreviousOrder,
hasDatatoken,
btSymbol,
dtSymbol,
dtBalance,
isConsumable,
consumableFeedback,
isBalanceSufficient,
hasPreviousOrderSelectedComputeAsset,
hasDatatokenSelectedComputeAsset,
assetType,
dtSymbolSelectedComputeAsset,
dtBalanceSelectedComputeAsset,
selectedComputeAssetType,
isAlgorithmConsumable,
isSupportedOceanNetwork,
web3,
hasProviderFee
)}
</div>
</> </>
)} )}
</div> </div>

View File

@ -33,6 +33,7 @@ export default function FormStartCompute({
hasDatatokenSelectedComputeAsset, hasDatatokenSelectedComputeAsset,
datasetSymbol, datasetSymbol,
algorithmSymbol, algorithmSymbol,
providerFeesSymbol,
dtSymbolSelectedComputeAsset, dtSymbolSelectedComputeAsset,
dtBalanceSelectedComputeAsset, dtBalanceSelectedComputeAsset,
selectedComputeAssetType, selectedComputeAssetType,
@ -61,6 +62,7 @@ export default function FormStartCompute({
hasDatatokenSelectedComputeAsset?: boolean hasDatatokenSelectedComputeAsset?: boolean
datasetSymbol?: string datasetSymbol?: string
algorithmSymbol?: string algorithmSymbol?: string
providerFeesSymbol?: string
dtSymbolSelectedComputeAsset?: string dtSymbolSelectedComputeAsset?: string
dtBalanceSelectedComputeAsset?: string dtBalanceSelectedComputeAsset?: string
selectedComputeAssetType?: string selectedComputeAssetType?: string
@ -145,7 +147,7 @@ export default function FormStartCompute({
? new Decimal(providerFeeAmount).toDecimalPlaces(MAX_DECIMALS) ? new Decimal(providerFeeAmount).toDecimalPlaces(MAX_DECIMALS)
: new Decimal(0) : new Decimal(0)
if (algorithmSymbol === 'OCEAN') { if (algorithmSymbol === providerFeesSymbol) {
let sum = providerFees.add(priceAlgo) let sum = providerFees.add(priceAlgo)
totalPrices.push({ totalPrices.push({
value: sum.toDecimalPlaces(MAX_DECIMALS).toString(), value: sum.toDecimalPlaces(MAX_DECIMALS).toString(),
@ -161,7 +163,7 @@ export default function FormStartCompute({
}) })
} }
} else { } else {
if (datasetSymbol === 'OCEAN') { if (datasetSymbol === providerFeesSymbol) {
const sum = providerFees.add(priceDataset) const sum = providerFees.add(priceDataset)
totalPrices.push({ totalPrices.push({
value: sum.toDecimalPlaces(MAX_DECIMALS).toString(), value: sum.toDecimalPlaces(MAX_DECIMALS).toString(),
@ -179,7 +181,7 @@ export default function FormStartCompute({
}) })
totalPrices.push({ totalPrices.push({
value: providerFees.toDecimalPlaces(MAX_DECIMALS).toString(), value: providerFees.toDecimalPlaces(MAX_DECIMALS).toString(),
symbol: 'OCEAN' symbol: providerFeesSymbol
}) })
} else { } else {
totalPrices.push({ totalPrices.push({
@ -188,7 +190,7 @@ export default function FormStartCompute({
}) })
totalPrices.push({ totalPrices.push({
value: providerFees.toDecimalPlaces(MAX_DECIMALS).toString(), value: providerFees.toDecimalPlaces(MAX_DECIMALS).toString(),
symbol: 'OCEAN' symbol: providerFeesSymbol
}) })
totalPrices.push({ totalPrices.push({
value: priceAlgo.toDecimalPlaces(MAX_DECIMALS).toString(), value: priceAlgo.toDecimalPlaces(MAX_DECIMALS).toString(),
@ -211,7 +213,8 @@ export default function FormStartCompute({
datasetOrderPrice, datasetOrderPrice,
algoOrderPrice, algoOrderPrice,
algorithmSymbol, algorithmSymbol,
datasetSymbol datasetSymbol,
providerFeesSymbol
]) ])
useEffect(() => { useEffect(() => {
@ -263,6 +266,7 @@ export default function FormStartCompute({
datasetOrderPrice={datasetOrderPrice} datasetOrderPrice={datasetOrderPrice}
algoOrderPrice={algoOrderPrice} algoOrderPrice={algoOrderPrice}
providerFeeAmount={providerFeeAmount} providerFeeAmount={providerFeeAmount}
providerFeesSymbol={providerFeesSymbol}
validUntil={validUntil} validUntil={validUntil}
totalPrices={totalPrices} totalPrices={totalPrices}
/> />

View File

@ -20,6 +20,7 @@ interface PriceOutputProps {
datasetOrderPrice?: string datasetOrderPrice?: string
algoOrderPrice?: string algoOrderPrice?: string
providerFeeAmount?: string providerFeeAmount?: string
providerFeesSymbol?: string
validUntil?: string validUntil?: string
totalPrices?: totalPriceMap[] totalPrices?: totalPriceMap[]
} }
@ -84,6 +85,7 @@ export default function PriceOutput({
datasetOrderPrice, datasetOrderPrice,
algoOrderPrice, algoOrderPrice,
providerFeeAmount, providerFeeAmount,
providerFeesSymbol,
validUntil, validUntil,
totalPrices totalPrices
}: PriceOutputProps): ReactElement { }: PriceOutputProps): ReactElement {
@ -134,7 +136,7 @@ export default function PriceOutput({
<Row <Row
price={providerFeeAmount} // initializeCompute.provider fee amount price={providerFeeAmount} // initializeCompute.provider fee amount
timeout={`${validUntil} seconds`} // valid until value timeout={`${validUntil} seconds`} // valid until value
symbol={'OCEAN'} // we assume that provider fees will always be in OCEAN token symbol={providerFeesSymbol} // we assume that provider fees will always be in OCEAN token
sign="+" sign="+"
type="C2D RESOURCES" type="C2D RESOURCES"
/> />

View File

@ -12,7 +12,8 @@ import {
ComputeAlgorithm, ComputeAlgorithm,
ComputeOutput, ComputeOutput,
ProviderComputeInitializeResults, ProviderComputeInitializeResults,
unitsToAmount unitsToAmount,
minAbi
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import Price from '@shared/Price' import Price from '@shared/Price'
@ -61,7 +62,7 @@ export default function Compute({
fileIsLoading?: boolean fileIsLoading?: boolean
consumableFeedback?: string consumableFeedback?: string
}): ReactElement { }): ReactElement {
const { accountId, web3, isSupportedOceanNetwork } = useWeb3() const { accountId, web3, isSupportedOceanNetwork, networkId } = useWeb3()
const { chainIds } = useUserPreferences() const { chainIds } = useUserPreferences()
const { isAssetNetwork } = useAsset() const { isAssetNetwork } = useAsset()
@ -90,6 +91,7 @@ export default function Compute({
const [initializedProviderResponse, setInitializedProviderResponse] = const [initializedProviderResponse, setInitializedProviderResponse] =
useState<ProviderComputeInitializeResults>() useState<ProviderComputeInitializeResults>()
const [providerFeeAmount, setProviderFeeAmount] = useState<string>('0') const [providerFeeAmount, setProviderFeeAmount] = useState<string>('0')
const [providerFeesSymbol, setProviderFeesSymbol] = useState<string>('OCEAN')
const [computeValidUntil, setComputeValidUntil] = useState<string>('0') const [computeValidUntil, setComputeValidUntil] = useState<string>('0')
const [datasetOrderPriceAndFees, setDatasetOrderPriceAndFees] = const [datasetOrderPriceAndFees, setDatasetOrderPriceAndFees] =
useState<OrderPriceAndFees>() useState<OrderPriceAndFees>()
@ -160,6 +162,18 @@ export default function Compute({
setProviderFeeAmount(feeAmount) setProviderFeeAmount(feeAmount)
const datatoken = new Datatoken(
await getDummyWeb3(asset?.chainId),
null,
null,
minAbi
)
setProviderFeesSymbol(
await datatoken.getSymbol(
initializedProvider?.datasets?.[0]?.providerFee?.providerFeeToken
)
)
const computeDuration = ( const computeDuration = (
parseInt(initializedProvider?.datasets?.[0]?.providerFee?.validUntil) - parseInt(initializedProvider?.datasets?.[0]?.providerFee?.validUntil) -
Math.floor(Date.now() / 1000) Math.floor(Date.now() / 1000)
@ -467,11 +481,15 @@ export default function Compute({
assetTimeout={secondsToString(asset?.services[0].timeout)} assetTimeout={secondsToString(asset?.services[0].timeout)}
hasPreviousOrderSelectedComputeAsset={!!validAlgorithmOrderTx} hasPreviousOrderSelectedComputeAsset={!!validAlgorithmOrderTx}
hasDatatokenSelectedComputeAsset={hasAlgoAssetDatatoken} hasDatatokenSelectedComputeAsset={hasAlgoAssetDatatoken}
datasetSymbol={asset?.accessDetails?.baseToken?.symbol || 'OCEAN'} datasetSymbol={
asset?.accessDetails?.baseToken?.symbol ||
(asset?.chainId === 137 ? 'mOCEAN' : 'OCEAN')
}
algorithmSymbol={ algorithmSymbol={
selectedAlgorithmAsset?.accessDetails?.baseToken?.symbol || selectedAlgorithmAsset?.accessDetails?.baseToken?.symbol ||
'OCEAN' (selectedAlgorithmAsset?.chainId === 137 ? 'mOCEAN' : 'OCEAN')
} }
providerFeesSymbol={providerFeesSymbol}
dtSymbolSelectedComputeAsset={ dtSymbolSelectedComputeAsset={
selectedAlgorithmAsset?.datatokens[0]?.symbol selectedAlgorithmAsset?.datatokens[0]?.symbol
} }

View File

@ -50,17 +50,20 @@ export default function Download({
useState<OrderPriceAndFees>() useState<OrderPriceAndFees>()
const [retry, setRetry] = useState<boolean>(false) const [retry, setRetry] = useState<boolean>(false)
const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED' const isUnsupportedPricing =
asset?.accessDetails?.type === 'NOT_SUPPORTED' ||
(asset?.accessDetails?.type === 'fixed' &&
!asset?.accessDetails?.baseToken?.symbol)
useEffect(() => { useEffect(() => {
Number(asset?.nft.state) === 4 && setIsOrderDisabled(true) Number(asset?.nft.state) === 4 && setIsOrderDisabled(true)
}, [asset?.nft.state]) }, [asset?.nft.state])
useEffect(() => { useEffect(() => {
if (!asset?.accessDetails || isUnsupportedPricing) return if (!asset?.accessDetails || isUnsupportedPricing) return
asset.accessDetails.isOwned && setIsOwned(asset?.accessDetails?.isOwned) setIsOwned(asset?.accessDetails?.isOwned || false)
asset.accessDetails.validOrderTx && setValidOrderTx(asset?.accessDetails?.validOrderTx || '')
setValidOrderTx(asset?.accessDetails?.validOrderTx)
// get full price and fees // get full price and fees
async function init() { async function init() {

View File

@ -0,0 +1,11 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import Bookmark from './Bookmark'
import { datasetAquarius } from '../../../../.jest/__fixtures__/datasetAquarius'
describe('src/components/Asset/AssetContent/Bookmark.tsx', () => {
it('renders Add Bookmark button', () => {
render(<Bookmark did={datasetAquarius.id} />)
expect(screen.getByTitle('Add Bookmark')).toBeInTheDocument()
})
})

View File

@ -0,0 +1,18 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import MetaFull from './MetaFull'
import { datasetAquarius } from '../../../../.jest/__fixtures__/datasetAquarius'
import { algorithmAquarius } from '../../../../.jest/__fixtures__/algorithmAquarius'
describe('src/components/Asset/AssetContent/MetaFull.tsx', () => {
it('renders metadata', () => {
render(<MetaFull ddo={datasetAquarius} />)
expect(screen.getByText('Owner')).toBeInTheDocument()
})
it('renders metadata for an algorithm', () => {
render(<MetaFull ddo={algorithmAquarius} />)
expect(screen.getByText('Docker Image')).toBeInTheDocument()
expect(screen.getByText('DID')).toBeInTheDocument()
})
})

View File

@ -3,17 +3,18 @@ import MetaItem from './MetaItem'
import styles from './MetaFull.module.css' import styles from './MetaFull.module.css'
import Publisher from '@shared/Publisher' import Publisher from '@shared/Publisher'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3' import { getDummyWeb3 } from '@utils/web3'
import { Asset, Datatoken, LoggerInstance } from '@oceanprotocol/lib' import { Asset, Datatoken, LoggerInstance } from '@oceanprotocol/lib'
export default function MetaFull({ ddo }: { ddo: Asset }): ReactElement { export default function MetaFull({ ddo }: { ddo: Asset }): ReactElement {
const [paymentCollector, setPaymentCollector] = useState<string>() const [paymentCollector, setPaymentCollector] = useState<string>()
const { isInPurgatory } = useAsset() const { isInPurgatory, assetState } = useAsset()
const { web3 } = useWeb3()
useEffect(() => { useEffect(() => {
async function getInitialPaymentCollector() { async function getInitialPaymentCollector() {
try { try {
if (!ddo) return
const web3 = await getDummyWeb3(ddo.chainId)
const datatoken = new Datatoken(web3) const datatoken = new Datatoken(web3)
setPaymentCollector( setPaymentCollector(
await datatoken.getPaymentCollector(ddo.datatokens[0].address) await datatoken.getPaymentCollector(ddo.datatokens[0].address)
@ -23,7 +24,7 @@ export default function MetaFull({ ddo }: { ddo: Asset }): ReactElement {
} }
} }
getInitialPaymentCollector() getInitialPaymentCollector()
}, [ddo, web3]) }, [ddo])
function DockerImage() { function DockerImage() {
const containerInfo = ddo?.metadata?.algorithm?.container const containerInfo = ddo?.metadata?.algorithm?.container
@ -40,6 +41,9 @@ export default function MetaFull({ ddo }: { ddo: Asset }): ReactElement {
title="Owner" title="Owner"
content={<Publisher account={ddo?.nft?.owner} />} content={<Publisher account={ddo?.nft?.owner} />}
/> />
{assetState !== 'Active' && (
<MetaItem title="Asset State" content={assetState} />
)}
{paymentCollector && paymentCollector !== ddo?.nft?.owner && ( {paymentCollector && paymentCollector !== ddo?.nft?.owner && (
<MetaItem <MetaItem
title="Revenue Sent To" title="Revenue Sent To"

View File

@ -0,0 +1,18 @@
import { render, screen } from '@testing-library/react'
import React from 'react'
import MetaSecondary from './MetaSecondary'
import { datasetAquarius } from '../../../../.jest/__fixtures__/datasetAquarius'
import { algorithmAquarius } from '../../../../.jest/__fixtures__/algorithmAquarius'
describe('src/components/Asset/AssetContent/MetaSecondary.tsx', () => {
it('renders tags', () => {
render(<MetaSecondary ddo={datasetAquarius} />)
expect(
screen.getByText(datasetAquarius.metadata.tags[0])
).toBeInTheDocument()
})
it('renders download sample button', () => {
render(<MetaSecondary ddo={algorithmAquarius} />)
expect(screen.getByText('Sample Data')).toBeInTheDocument()
})
})

View File

@ -6,7 +6,8 @@ import {
FixedRateExchange, FixedRateExchange,
Asset, Asset,
Service, Service,
Datatoken Datatoken,
Nft
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { validationSchema } from './_validation' import { validationSchema } from './_validation'
import { getInitialValues } from './_constants' import { getInitialValues } from './_constants'
@ -26,6 +27,7 @@ import { useAsset } from '@context/Asset'
import { setNftMetadata } from '@utils/nft' import { setNftMetadata } from '@utils/nft'
import { sanitizeUrl } from '@utils/url' import { sanitizeUrl } from '@utils/url'
import { getEncryptedFiles } from '@utils/provider' import { getEncryptedFiles } from '@utils/provider'
import { assetStateToNumber } from '@utils/assetState'
export default function Edit({ export default function Edit({
asset asset
@ -33,7 +35,7 @@ export default function Edit({
asset: AssetExtended asset: AssetExtended
}): ReactElement { }): ReactElement {
const { debug } = useUserPreferences() const { debug } = useUserPreferences()
const { fetchAsset, isAssetNetwork } = useAsset() const { fetchAsset, isAssetNetwork, assetState } = useAsset()
const { accountId, web3 } = useWeb3() const { accountId, web3 } = useWeb3()
const newAbortController = useAbortController() const newAbortController = useAbortController()
const [success, setSuccess] = useState<string>() const [success, setSuccess] = useState<string>()
@ -157,6 +159,16 @@ export default function Edit({
newAbortController() newAbortController()
) )
if (values.assetState !== assetState) {
const nft = new Nft(web3)
await nft.setMetadataState(
asset?.nftAddress,
accountId,
assetStateToNumber(values.assetState)
)
}
LoggerInstance.log('[edit] setMetadata result', setMetadataTx) LoggerInstance.log('[edit] setMetadata result', setMetadataTx)
if (!setMetadataTx) { if (!setMetadataTx) {
@ -179,8 +191,9 @@ export default function Edit({
initialValues={getInitialValues( initialValues={getInitialValues(
asset?.metadata, asset?.metadata,
asset?.services[0]?.timeout, asset?.services[0]?.timeout,
asset?.accessDetails?.price, asset?.accessDetails?.price || '0',
paymentCollector paymentCollector,
assetState
)} )}
validationSchema={validationSchema} validationSchema={validationSchema}
onSubmit={async (values, { resetForm }) => { onSubmit={async (values, { resetForm }) => {

View File

@ -6,6 +6,7 @@ import { useAsset } from '@context/Asset'
import { FormPublishData } from '@components/Publish/_types' import { FormPublishData } from '@components/Publish/_types'
import { getFileInfo } from '@utils/provider' import { getFileInfo } from '@utils/provider'
import { getFieldContent } from '@utils/form' import { getFieldContent } from '@utils/form'
import { isGoogleUrl } from '@utils/url'
export function checkIfTimeoutInPredefinedValues( export function checkIfTimeoutInPredefinedValues(
timeout: string, timeout: string,
@ -65,7 +66,7 @@ export default function FormEditMetadata({
getFileInfo(asset.metadata.links[0], providerUrl, 'url').then( getFileInfo(asset.metadata.links[0], providerUrl, 'url').then(
(checkedFile) => { (checkedFile) => {
// set valid false if url is using google drive // set valid false if url is using google drive
if (asset.metadata.links[0].includes('drive.google')) { if (isGoogleUrl(asset.metadata.links[0])) {
setFieldValue('links', [ setFieldValue('links', [
{ {
url: asset.metadata.links[0], url: asset.metadata.links[0],
@ -134,6 +135,11 @@ export default function FormEditMetadata({
component={Input} component={Input}
name="paymentCollector" name="paymentCollector"
/> />
<Field
{...getFieldContent('assetState', data)}
component={Input}
name="assetState"
/>
<FormActions /> <FormActions />
</Form> </Form>

View File

@ -6,7 +6,8 @@ export function getInitialValues(
metadata: Metadata, metadata: Metadata,
timeout: number, timeout: number,
price: string, price: string,
paymentCollector: string paymentCollector: string,
assetState: string
): Partial<MetadataEditForm> { ): Partial<MetadataEditForm> {
return { return {
name: metadata?.name, name: metadata?.name,
@ -17,7 +18,8 @@ export function getInitialValues(
timeout: secondsToString(timeout), timeout: secondsToString(timeout),
author: metadata?.author, author: metadata?.author,
tags: metadata?.tags, tags: metadata?.tags,
paymentCollector paymentCollector,
assetState
} }
} }

View File

@ -9,6 +9,7 @@ export interface MetadataEditForm {
links?: FileInfo[] links?: FileInfo[]
author?: string author?: string
tags?: string[] tags?: string[]
assetState?: string
} }
export interface ComputeEditForm { export interface ComputeEditForm {

View File

@ -43,7 +43,8 @@ export const validationSchema = Yup.object().shape({
(value) => { (value) => {
return web3.utils.isAddress(value) return web3.utils.isAddress(value)
} }
) ),
retireAsset: Yup.string()
}) })
export const computeSettingsValidationSchema = Yup.object().shape({ export const computeSettingsValidationSchema = Yup.object().shape({

View File

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react' import { render, screen } from '@testing-library/react'
import React from 'react' import React from 'react'
import RelatedAssets from '.' import RelatedAssets from '.'
import { assets } from '../../../../.jest/__fixtures__/assetsWithAccessDetails' import { assets } from '../../../../.jest/__fixtures__/datasetsWithAccessDetails'
import { queryMetadata } from '../../../@utils/aquarius' import { queryMetadata } from '../../../@utils/aquarius'
// import * as userPreferencesMock from '../../../@context/UserPreferences' // import * as userPreferencesMock from '../../../@context/UserPreferences'

View File

@ -1,21 +0,0 @@
import React, { ReactElement } from 'react'
import { useUserPreferences } from '@context/UserPreferences'
import Input from '@shared/FormInput'
export default function TokenApproval(): ReactElement {
const { infiniteApproval, setInfiniteApproval } = useUserPreferences()
return (
<li>
<Input
label="Token Approvals"
help="Use infinite amount when approving tokens in _Use_."
name="infiniteApproval"
type="checkbox"
options={['Allow infinite amount']}
defaultChecked={infiniteApproval === true}
onChange={() => setInfiniteApproval(!infiniteApproval)}
/>
</li>
)
}

View File

@ -7,7 +7,6 @@ import Debug from './Debug'
import Caret from '@images/caret.svg' import Caret from '@images/caret.svg'
import useDarkMode from '@oceanprotocol/use-dark-mode' import useDarkMode from '@oceanprotocol/use-dark-mode'
import Appearance from './Appearance' import Appearance from './Appearance'
import TokenApproval from './TokenApproval'
import { useMarketMetadata } from '@context/MarketMetadata' import { useMarketMetadata } from '@context/MarketMetadata'
export default function UserPreferences(): ReactElement { export default function UserPreferences(): ReactElement {
@ -20,7 +19,6 @@ export default function UserPreferences(): ReactElement {
content={ content={
<ul className={styles.preferencesDetails}> <ul className={styles.preferencesDetails}>
<Currency /> <Currency />
<TokenApproval />
<Appearance darkMode={darkMode} /> <Appearance darkMode={darkMode} />
<Debug /> <Debug />
</ul> </ul>

View File

@ -3,7 +3,7 @@ import React from 'react'
import MostViews from '.' import MostViews from '.'
import axios from 'axios' import axios from 'axios'
import { queryMetadata } from '@utils/aquarius' import { queryMetadata } from '@utils/aquarius'
import { assetAquarius } from '../../../../.jest/__fixtures__/assetAquarius' import { datasetAquarius } from '../../../../.jest/__fixtures__/datasetAquarius'
jest.mock('axios') jest.mock('axios')
jest.mock('@utils/aquarius') jest.mock('@utils/aquarius')
@ -12,7 +12,7 @@ const axiosMock = axios as jest.Mocked<typeof axios>
const queryMetadataMock = queryMetadata as jest.Mock const queryMetadataMock = queryMetadata as jest.Mock
const queryMetadataBaseReturn: PagedAssets = { const queryMetadataBaseReturn: PagedAssets = {
results: [assetAquarius], results: [datasetAquarius],
page: 1, page: 1,
totalPages: 1, totalPages: 1,
totalResults: 1, totalResults: 1,
@ -27,7 +27,7 @@ describe('components/Home/MostViews', () => {
it('renders without crashing', async () => { it('renders without crashing', async () => {
axiosMock.get.mockImplementation(() => axiosMock.get.mockImplementation(() =>
Promise.resolve({ Promise.resolve({
data: [{ count: 666, did: assetAquarius.id }] data: [{ count: 666, did: datasetAquarius.id }]
}) })
) )
queryMetadataMock.mockResolvedValue(queryMetadataBaseReturn) queryMetadataMock.mockResolvedValue(queryMetadataBaseReturn)

View File

@ -22,23 +22,6 @@ export default function PricingFields(): ReactElement {
token.name.toLowerCase().includes('ocean') token.name.toLowerCase().includes('ocean')
) || approvedBaseTokens?.[0] ) || approvedBaseTokens?.[0]
const isBaseTokenSet = !!approvedBaseTokens?.find(
(token) => token?.address === values?.pricing?.baseToken?.address
)
useEffect(() => {
if (!approvedBaseTokens?.length) return
if (isBaseTokenSet) return
setFieldValue('pricing.baseToken', defaultBaseToken)
}, [
approvedBaseTokens,
chainId,
defaultBaseToken,
isBaseTokenSet,
setFieldValue,
values.pricing.baseToken
])
// Switch type value upon tab change // Switch type value upon tab change
function handleTabChange(tabName: string) { function handleTabChange(tabName: string) {
const type = tabName.toLowerCase() const type = tabName.toLowerCase()

View File

@ -10,7 +10,7 @@ export function Steps({
}: { }: {
feedback: PublishFeedback feedback: PublishFeedback
}): ReactElement { }): ReactElement {
const { chainId, accountId } = useWeb3() const { chainId, accountId, approvedBaseTokens } = useWeb3()
const { values, setFieldValue, touched, setTouched } = const { values, setFieldValue, touched, setTouched } =
useFormikContext<FormPublishData>() useFormikContext<FormPublishData>()
@ -24,6 +24,21 @@ export function Steps({
setFieldValue('user.accountId', accountId) setFieldValue('user.accountId', accountId)
}, [chainId, accountId, setFieldValue]) }, [chainId, accountId, setFieldValue])
useEffect(() => {
if (!approvedBaseTokens?.length) return
const defaultBaseToken =
approvedBaseTokens?.find((token) =>
token.name.toLowerCase().includes('ocean')
) || approvedBaseTokens?.[0]
const isBaseTokenSet = !!approvedBaseTokens?.find(
(token) => token?.address === values?.pricing?.baseToken?.address
)
if (isBaseTokenSet) return
setFieldValue('pricing.baseToken', defaultBaseToken)
}, [approvedBaseTokens])
// auto-sync publish feedback into form data values // auto-sync publish feedback into form data values
useEffect(() => { useEffect(() => {
setFieldValue('feedback', feedback) setFieldValue('feedback', feedback)

View File

@ -14,25 +14,9 @@ export default function Page404(): ReactElement {
<> <>
<Head> <Head>
<style type="text/css">{` <style type="text/css">{`
body {
background: transparent url(${fishfail}) center bottom no-repeat !important;
background-size: cover !important;
min-height: 100vh;
}
main { main {
text-align: center; text-align: center;
} }
header *,
footer *,
main * {
color: var(--brand-white) !important;
}
header svg path {
fill: var(--brand-white) !important;
}
`}</style> `}</style>
</Head> </Head>
<Page <Page