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

merge branch v4 into v4-c2d fixed conflicts

This commit is contained in:
Bogdan Fazakas 2022-03-17 11:29:02 +02:00
commit 08b90b3635
28 changed files with 1765 additions and 885 deletions

View File

@ -141,15 +141,6 @@
"options": ["Forever", "1 day", "1 week", "1 month", "1 year"], "options": ["Forever", "1 day", "1 week", "1 month", "1 year"],
"sortOptions": false, "sortOptions": false,
"required": true "required": true
},
{
"name": "computeOptions",
"label": "Compute Environment",
"type": "radio",
"options": [
"populated from computeEnvironmentDefaults in Publish/_constants & computeEnvironmentOptions in Publish/Services/"
],
"required": true
} }
] ]
}, },

1902
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,20 +21,20 @@
"@coingecko/cryptoformat": "^0.4.4", "@coingecko/cryptoformat": "^0.4.4",
"@loadable/component": "^5.15.2", "@loadable/component": "^5.15.2",
"@oceanprotocol/art": "^3.2.0", "@oceanprotocol/art": "^3.2.0",
"@oceanprotocol/lib": "^1.0.0-next.27", "@oceanprotocol/lib": "^1.0.0-next.28",
"@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/typographies": "^0.1.0",
"@portis/web3": "^4.0.6", "@portis/web3": "^4.0.7",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"@walletconnect/web3-provider": "^1.7.1", "@walletconnect/web3-provider": "^1.7.5",
"axios": "^0.25.0", "axios": "^0.26.1",
"bignumber.js": "^9.0.2", "bignumber.js": "^9.0.2",
"chart.js": "^3.7.0", "chart.js": "^3.7.1",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"decimal.js": "^10.3.1", "decimal.js": "^10.3.1",
"dom-confetti": "^0.2.2", "dom-confetti": "^0.2.2",
"dotenv": "^15.0.0", "dotenv": "^16.0.0",
"filesize": "^8.0.6", "filesize": "^8.0.7",
"formik": "^2.2.9", "formik": "^2.2.9",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"is-url-superb": "^6.1.0", "is-url-superb": "^6.1.0",
@ -44,7 +44,7 @@
"lodash.omit": "^4.5.0", "lodash.omit": "^4.5.0",
"myetherwallet-blockies": "^0.1.1", "myetherwallet-blockies": "^0.1.1",
"next": "^12.1.0", "next": "^12.1.0",
"query-string": "^7.1.0", "query-string": "^7.1.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-chartjs-2": "^4.0.1", "react-chartjs-2": "^4.0.1",
"react-clipboard.js": "^2.0.16", "react-clipboard.js": "^2.0.16",
@ -52,8 +52,8 @@
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-dotdotdot": "^1.3.1", "react-dotdotdot": "^1.3.1",
"react-modal": "^3.14.4", "react-modal": "^3.14.4",
"react-paginate": "^8.1.0", "react-paginate": "^8.1.2",
"react-spring": "^9.4.2", "react-spring": "^9.4.4",
"react-tabs": "^3.2.3", "react-tabs": "^3.2.3",
"react-toastify": "^8.1.0", "react-toastify": "^8.1.0",
"remark": "^13.0.0", "remark": "^13.0.0",
@ -61,10 +61,10 @@
"remark-html": "^13.0.1", "remark-html": "^13.0.1",
"remove-markdown": "^0.3.0", "remove-markdown": "^0.3.0",
"slugify": "^1.6.5", "slugify": "^1.6.5",
"swr": "^1.2.0", "swr": "^1.2.2",
"urql": "^2.1.1", "urql": "^2.2.0",
"use-dark-mode": "^2.3.1", "use-dark-mode": "^2.3.1",
"web3": "^1.6.1", "web3": "^1.7.1",
"web3modal": "^1.9.5", "web3modal": "^1.9.5",
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
@ -84,8 +84,8 @@
"@types/react-tabs": "^2.3.4", "@types/react-tabs": "^2.3.4",
"@types/remove-markdown": "^0.3.1", "@types/remove-markdown": "^0.3.1",
"@types/yup": "^0.29.11", "@types/yup": "^0.29.11",
"@typescript-eslint/eslint-plugin": "^5.9.1", "@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.9.1", "@typescript-eslint/parser": "^5.15.0",
"apollo": "^2.33.9", "apollo": "^2.33.9",
"eslint": "^7.27.0", "eslint": "^7.27.0",
"eslint-config-oceanprotocol": "^1.5.0", "eslint-config-oceanprotocol": "^1.5.0",
@ -96,11 +96,11 @@
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"https-browserify": "^1.0.0", "https-browserify": "^1.0.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"prettier": "^2.5.1", "prettier": "^2.6.0",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"process": "^0.11.10", "process": "^0.11.10",
"serve": "^13.0.2", "serve": "^13.0.2",
"stream-http": "^2.8.3", "stream-http": "^3.2.0",
"typescript": "^4.5.4" "typescript": "^4.5.4"
}, },
"repository": { "repository": {

View File

@ -1,12 +1,13 @@
import { SvgWaves } from './SvgWaves'
import { import {
Asset,
LoggerInstance, LoggerInstance,
Asset,
getHash, getHash,
Nft, Nft,
ProviderInstance, ProviderInstance,
DDO DDO,
MetadataAndTokenURI
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { SvgWaves } from './SvgWaves'
import Web3 from 'web3' import Web3 from 'web3'
import { TransactionReceipt } from 'web3-core' import { TransactionReceipt } from 'web3-core'
@ -45,17 +46,12 @@ function encodeSvg(svgString: string): string {
export function generateNftMetadata(): NftMetadata { export function generateNftMetadata(): NftMetadata {
const waves = new SvgWaves() const waves = new SvgWaves()
const svg = waves.generateSvg() const svg = waves.generateSvg()
// TODO: figure out if also image URI needs base64 encoding
// e.g. 'data:image/svg+xml;base64,'
// generated SVG embedded as 'data:image/svg+xml' and encoded characters
const imageData = `data:image/svg+xml,${encodeSvg(svg.outerHTML)}` const imageData = `data:image/svg+xml,${encodeSvg(svg.outerHTML)}`
const newNft: NftMetadata = { const newNft: NftMetadata = {
name: 'Ocean Asset NFT', name: 'Ocean Asset NFT',
symbol: 'OCEAN-NFT', symbol: 'OCEAN-NFT',
description: `This NFT represents an asset in the Ocean Protocol v4 ecosystem.`, description: `This NFT represents an asset in the Ocean Protocol v4 ecosystem.`,
// TODO: ideally this includes the final DID
external_url: 'https://market.oceanprotocol.com', external_url: 'https://market.oceanprotocol.com',
background_color: '141414', // dark background background_color: '141414', // dark background
image_data: imageData image_data: imageData
@ -64,24 +60,32 @@ export function generateNftMetadata(): NftMetadata {
return newNft return newNft
} }
export function generateNftCreateData(nftMetadata: NftMetadata): any { const tokenUriPrefix = 'data:application/json;base64,'
// TODO: figure out if Buffer.from method is working in browser in final build
// as BTOA is deprecated.
// tokenURI: window?.btoa(JSON.stringify(nftMetadata))
const encodedMetadata = Buffer.from(JSON.stringify(nftMetadata)).toString(
'base64'
)
export function generateNftCreateData(nftMetadata: NftMetadata): any {
const nftCreateData = { const nftCreateData = {
name: nftMetadata.name, name: nftMetadata.name,
symbol: nftMetadata.symbol, symbol: nftMetadata.symbol,
templateIndex: 1, templateIndex: 1,
tokenURI: `data:application/json;base64,${encodedMetadata}` tokenURI: ''
} }
return nftCreateData return nftCreateData
} }
export function decodeTokenURI(tokenURI: string): NftMetadata {
if (!tokenURI) return undefined
try {
const nftMeta = JSON.parse(
Buffer.from(tokenURI.replace(tokenUriPrefix, ''), 'base64').toString()
) as NftMetadata
return nftMeta
} catch (error) {
LoggerInstance.error(`[NFT] ${error.message}`)
}
}
export async function setNftMetadata( export async function setNftMetadata(
asset: Asset | DDO, asset: Asset | DDO,
accountId: string, accountId: string,
@ -125,3 +129,66 @@ export async function setNftMetadata(
return setMetadataTx return setMetadataTx
} }
export async function setNFTMetadataAndTokenURI(
asset: Asset | DDO,
accountId: string,
web3: Web3,
nftMetadata: NftMetadata,
signal: AbortSignal
): Promise<TransactionReceipt> {
const encryptedDdo = await ProviderInstance.encrypt(
asset,
asset.services[0].serviceEndpoint,
signal
)
LoggerInstance.log(
'[setNFTMetadataAndTokenURI] Got encrypted DDO',
encryptedDdo
)
const metadataHash = getHash(JSON.stringify(asset))
// add final did to external_url and asset link to description in nftMetadata before encoding
const externalUrl = `${nftMetadata.external_url}/asset/${asset.id}`
const encodedMetadata = Buffer.from(
JSON.stringify({
...nftMetadata,
description: `${nftMetadata.description}\n\nView on Ocean Market: ${externalUrl}`,
external_url: externalUrl
})
).toString('base64')
const nft = new Nft(web3)
// theoretically used by aquarius or provider, not implemented yet, will remain hardcoded
const flags = '0x02'
const metadataAndTokenURI: MetadataAndTokenURI = {
metaDataState: 0,
metaDataDecryptorUrl: asset.services[0].serviceEndpoint,
metaDataDecryptorAddress: '',
flags,
data: encryptedDdo,
metaDataHash: '0x' + metadataHash,
tokenId: 1,
tokenURI: `data:application/json;base64,${encodedMetadata}`,
metadataProofs: []
}
const estGasSetMetadataAndTokenURI = await nft.estGasSetMetadataAndTokenURI(
asset.nftAddress,
accountId,
metadataAndTokenURI
)
LoggerInstance.log(
'[setNFTMetadataAndTokenURI] est Gas set metadata and token uri --',
estGasSetMetadataAndTokenURI
)
const setMetadataAndTokenURITx = await nft.setMetadataAndTokenURI(
asset.nftAddress,
accountId,
metadataAndTokenURI
)
return setMetadataAndTokenURITx
}

View File

@ -1,6 +1,7 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import { usePrices } from '@context/Prices' import { usePrices } from '@context/Prices'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
import Web3 from 'web3'
import useNftFactory from '@hooks/contracts/useNftFactory' import useNftFactory from '@hooks/contracts/useNftFactory'
import { NftFactory } from '@oceanprotocol/lib' import { NftFactory } from '@oceanprotocol/lib'
import Conversion from '@shared/Price/Conversion' import Conversion from '@shared/Price/Conversion'
@ -20,7 +21,7 @@ const getEstGasFee = async (
const gasPrice = await web3.eth.getGasPrice() const gasPrice = await web3.eth.getGasPrice()
const gasLimit = await nftFactory?.estGasCreateNFT(address, nft) const gasLimit = await nftFactory?.estGasCreateNFT(address, nft)
const gasFeeEth = web3.utils.fromWei( const gasFeeEth = Web3.utils.fromWei(
(+gasPrice * +gasLimit).toString(), (+gasPrice * +gasLimit).toString(),
'ether' 'ether'
) )

View File

@ -6,7 +6,6 @@
background: none; background: none;
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
position: relative;
} }
.icon { .icon {
@ -25,11 +24,12 @@
fill: var(--brand-alert-green); fill: var(--brand-alert-green);
} }
.copied::after { .action {
content: 'Copied!'; display: flex;
position: absolute; gap: 5px;
top: -150%; }
left: -140%;
.feedback {
font-size: var(--font-size-mini); font-size: var(--font-size-mini);
color: var(--brand-alert-green); color: var(--brand-alert-green);
} }

View File

@ -27,7 +27,10 @@ export default function Copy({ text }: { text: string }): ReactElement {
onSuccess={() => setIsCopied(true)} onSuccess={() => setIsCopied(true)}
className={`${styles.button} ${isCopied ? styles.copied : ''}`} className={`${styles.button} ${isCopied ? styles.copied : ''}`}
> >
<IconCopy className={styles.icon} /> <div className={styles.action}>
<IconCopy className={styles.icon} />
{isCopied && <span className={styles.feedback}>Copied!</span>}
</div>
</Clipboard> </Clipboard>
) )
} }

View File

@ -85,11 +85,18 @@ export default function Graph({
const newGraphData = { const newGraphData = {
labels: timestamps, labels: timestamps,
datasets: [{ ...lineStyle, data, borderColor: `#8b98a9` }] datasets: [
{
...lineStyle,
data,
borderColor: `#8b98a9`,
backgroundColor: darkMode.value ? '#201f1f' : '#f7f7f7'
}
]
} }
setGraphData(newGraphData) setGraphData(newGraphData)
LoggerInstance.log('[pool graph] New graph data created:', newGraphData) LoggerInstance.log('[pool graph] New graph data created:', newGraphData)
}, [poolSnapshots, graphType, currency, prices, locale]) }, [poolSnapshots, graphType, currency, prices, locale, darkMode.value])
return ( return (
<div className={styles.graphWrap}> <div className={styles.graphWrap}>

View File

@ -17,6 +17,7 @@ const getReceipts = gql`
id id
nft { nft {
address address
owner
} }
tx tx
timestamp timestamp
@ -25,7 +26,13 @@ const getReceipts = gql`
} }
` `
export default function EditHistory(): ReactElement { export default function EditHistory({
receipts,
setReceipts
}: {
receipts: ReceiptData[]
setReceipts: (receipts: ReceiptData[]) => void
}): ReactElement {
const { asset } = useAsset() const { asset } = useAsset()
function getUpdateType(type: string): string { function getUpdateType(type: string): string {
@ -72,7 +79,7 @@ export default function EditHistory(): ReactElement {
if (!data || data.nftUpdates.length === 0) return if (!data || data.nftUpdates.length === 0) return
const receiptCollection = data.nftUpdates const receiptCollection = data.nftUpdates
setReceipts(receiptCollection) setReceipts(receiptCollection)
}, [data]) }, [data, setReceipts])
return ( return (
<> <>

View File

@ -1,51 +0,0 @@
.meta {
margin-bottom: calc(var(--spacer) / 1.5);
color: var(--color-secondary);
font-size: var(--font-size-small);
}
.asset {
margin-left: -2rem;
margin-right: -2rem;
padding-left: 2rem;
padding-right: 3rem;
border-bottom: 1px solid var(--border-color);
margin-bottom: calc(var(--spacer) / 1.5);
padding-bottom: calc(var(--spacer) / 1.75);
}
@media (min-width: 40rem) {
.asset {
margin-top: -0.65rem;
}
}
.assetType {
display: inline-block;
border-right: 1px solid var(--border-color);
padding-right: calc(var(--spacer) / 3.5);
margin-right: calc(var(--spacer) / 4);
}
.datatoken {
white-space: pre;
margin-right: calc(var(--spacer) / 3);
}
.byline {
font-size: var(--font-size-small);
}
.updated {
font-size: var(--font-size-mini);
}
.addWrap {
padding-left: calc(var(--spacer) / 5);
border-left: 1px solid var(--border-color);
display: inline-block;
}
.add {
font-size: var(--font-size-mini);
}

View File

@ -1,75 +0,0 @@
import React, { ReactElement } from 'react'
import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3'
import ExplorerLink from '@shared/ExplorerLink'
import Publisher from '@shared/Publisher'
import AddToken from '@shared/AddToken'
import Time from '@shared/atoms/Time'
import AssetType from '@shared/AssetType'
import styles from './MetaMain.module.css'
import { getServiceByName } from '@utils/ddo'
import { Asset } from '@oceanprotocol/lib'
export default function MetaMain({ ddo }: { ddo: Asset }): ReactElement {
const { isAssetNetwork } = useAsset()
const { web3ProviderInfo } = useWeb3()
const isCompute = Boolean(getServiceByName(ddo, 'compute'))
const accessType = isCompute ? 'compute' : 'access'
const blockscoutNetworks = [1287, 2021000, 2021001, 44787, 246, 1285]
const isBlockscoutExplorer = blockscoutNetworks.includes(ddo?.chainId)
const dataTokenName = ddo?.datatokens[0]?.name
const dataTokenSymbol = ddo?.datatokens[0]?.symbol
return (
<aside className={styles.meta}>
<header className={styles.asset}>
<AssetType
type={ddo?.metadata.type}
accessType={accessType}
className={styles.assetType}
/>
<ExplorerLink
className={styles.datatoken}
networkId={ddo?.chainId}
path={
isBlockscoutExplorer
? `tokens/${ddo?.services[0].datatokenAddress}`
: `token/${ddo?.services[0].datatokenAddress}`
}
>
{`${dataTokenName}${dataTokenSymbol}`}
</ExplorerLink>
{web3ProviderInfo?.name === 'MetaMask' && isAssetNetwork && (
<span className={styles.addWrap}>
<AddToken
address={ddo?.services[0].datatokenAddress}
symbol={(ddo as Asset)?.datatokens[0]?.symbol}
logo="https://raw.githubusercontent.com/oceanprotocol/art/main/logo/datatoken.png"
text={`Add ${(ddo as Asset)?.datatokens[0]?.symbol} to wallet`}
className={styles.add}
minimal
/>
</span>
)}
</header>
<div className={styles.byline}>
Published By <Publisher account={(ddo as Asset)?.nft?.owner} />
<p>
<Time date={ddo?.metadata.created} relative />
{ddo?.metadata.created !== ddo?.metadata.updated && (
<>
{' — '}
<span className={styles.updated}>
updated <Time date={ddo?.metadata.updated} relative />
</span>
</>
)}
</p>
</div>
</aside>
)
}

View File

@ -0,0 +1,26 @@
.wrapper {
display: flex;
flex-direction: column;
height: 100%;
padding-left: calc(var(--spacer) / 2);
justify-content: center;
}
.datatoken {
white-space: pre;
margin-right: calc(var(--spacer) / 3);
}
.owner {
display: block;
}
.addWrap {
padding-left: calc(var(--spacer) / 5);
border-left: 1px solid var(--border-color);
display: inline-block;
}
.add {
font-size: var(--font-size-mini);
}

View File

@ -0,0 +1,54 @@
import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3'
import { Asset } from '@oceanprotocol/lib'
import AddToken from '@shared/AddToken'
import ExplorerLink from '@shared/ExplorerLink'
import Publisher from '@shared/Publisher'
import React, { ReactElement } from 'react'
import styles from './MetaAsset.module.css'
export default function MetaAsset({
asset,
isBlockscoutExplorer
}: {
asset: Asset
isBlockscoutExplorer: boolean
}): ReactElement {
const { isAssetNetwork } = useAsset()
const { web3ProviderInfo } = useWeb3()
const dataTokenSymbol = asset?.datatokens[0]?.symbol
return (
<div className={styles.wrapper}>
<span className={styles.owner}>
Owned by <Publisher account={asset?.nft?.owner} />
</span>
<span>
<ExplorerLink
className={styles.datatoken}
networkId={asset?.chainId}
path={
isBlockscoutExplorer
? `tokens/${asset?.services[0].datatokenAddress}`
: `token/${asset?.services[0].datatokenAddress}`
}
>
{`Accessed with ${dataTokenSymbol}`}
</ExplorerLink>
{web3ProviderInfo?.name === 'MetaMask' && isAssetNetwork && (
<span className={styles.addWrap}>
<AddToken
address={asset?.services[0].datatokenAddress}
symbol={(asset as Asset)?.datatokens[0]?.symbol}
logo="https://raw.githubusercontent.com/oceanprotocol/art/main/logo/datatoken.png"
text={`Add ${(asset as Asset)?.datatokens[0]?.symbol} to wallet`}
className={styles.add}
minimal
/>
</span>
)}
</span>
</div>
)
}

View File

@ -0,0 +1,19 @@
.wrapper {
padding: calc(var(--spacer) / 2);
}
.assetType {
display: inline-block;
border-right: 1px solid var(--border-color);
padding-right: calc(var(--spacer) / 3.5);
margin-right: calc(var(--spacer) / 4);
}
.byline {
display: inline-block;
font-size: var(--font-size-small);
}
.updated {
font-size: var(--font-size-mini);
}

View File

@ -0,0 +1,47 @@
import { Asset } from '@oceanprotocol/lib'
import AssetType from '@shared/AssetType'
import Time from '@shared/atoms/Time'
import Publisher from '@shared/Publisher'
import { getServiceByName } from '@utils/ddo'
import React, { ReactElement } from 'react'
import styles from './MetaInfo.module.css'
export default function MetaInfo({
asset,
nftPublisher
}: {
asset: Asset
nftPublisher: string
}): ReactElement {
const isCompute = Boolean(getServiceByName(asset, 'compute'))
const accessType = isCompute ? 'compute' : 'access'
const nftOwner = asset?.nft?.owner
return (
<div className={styles.wrapper}>
<AssetType
type={asset?.metadata.type}
accessType={accessType}
className={styles.assetType}
/>
<div className={styles.byline}>
<p>
Published <Time date={asset?.metadata.created} relative />
{nftPublisher && nftPublisher !== nftOwner && (
<span>
{' by '} <Publisher account={nftPublisher} />
</span>
)}
{asset?.metadata.created !== asset?.metadata.updated && (
<>
{' — '}
<span className={styles.updated}>
updated <Time date={asset?.metadata.updated} relative />
</span>
</>
)}
</p>
</div>
</div>
)
}

View File

@ -0,0 +1,41 @@
.wrapper {
display: flex;
justify-content: flex-start;
align-items: center;
}
.wrapper img {
margin: 0;
width: 128px;
height: 128px;
}
.info {
padding: calc(var(--spacer) / 2);
color: var(--color-secondary);
}
.info h5 {
margin-bottom: 0;
}
.address {
word-break: break-all;
}
.address button::after {
word-break: normal;
}
.links {
margin-top: calc(var(--spacer) / 3);
}
.links a {
display: block;
}
.fallback {
padding-top: calc(var(--spacer) / 3);
font-style: italic;
}

View File

@ -0,0 +1,79 @@
import Copy from '@shared/atoms/Copy'
import External from '@images/external.svg'
import ExplorerLink from '@shared/ExplorerLink'
import { NftMetadata } from '@utils/nft'
import React, { ReactElement } from 'react'
import styles from './NftTooltip.module.css'
import explorerLinkStyles from '@shared/ExplorerLink/index.module.css'
import { accountTruncate } from '@utils/web3'
export default function NftTooltip({
nft,
address,
chainId,
isBlockscoutExplorer
}: {
nft: NftMetadata
address: string
chainId: number
isBlockscoutExplorer: boolean
}): ReactElement {
// Currently Ocean NFTs are not displayed correctly on OpenSea
// Code prepared to easily integrate this feature once this is fixed
//
// Supported OpeanSea networks:
// https://support.opensea.io/hc/en-us/articles/4404027708051-Which-blockchains-does-OpenSea-support-
const openseaNetworks = [1, 137]
const openseaTestNetworks = [4]
const openSeaSupported = openseaNetworks
.concat(openseaTestNetworks)
.includes(chainId)
const openSeaBaseUri = openSeaSupported
? openseaTestNetworks.includes(chainId)
? 'https://testnets.opensea.io'
: 'https://opensea.io'
: undefined
return (
<div className={styles.wrapper}>
{nft && <img src={nft.image_data} alt={nft?.name} />}
<div className={styles.info}>
{nft && <h5>{nft.name}</h5>}
{address && (
<span title={address} className={styles.address}>
{accountTruncate(address)} <Copy text={address} />
</span>
)}
<div className={styles.links}>
{address && (
<ExplorerLink
networkId={chainId}
path={
isBlockscoutExplorer ? `tokens/${address}` : `token/${address}`
}
>
View on explorer
</ExplorerLink>
)}
{openSeaSupported && nft && address && (
<a
href={`${openSeaBaseUri}/assets/${address}/1`}
target="_blank"
rel="noreferrer"
className={explorerLinkStyles.link}
>
View on OpeanSea <External />
</a>
)}
</div>
{!nft?.image_data && (
<p className={styles.fallback}>
This Data NFT was not created on Ocean Market
</p>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,44 @@
.meta {
margin-left: calc(var(--spacer) * -1);
margin-right: calc(var(--spacer) * -1);
margin-bottom: calc(var(--spacer) / 1.5);
color: var(--color-secondary);
font-size: var(--font-size-small);
}
.asset {
display: flex;
justify-content: flex-start;
align-items: flex-start;
height: calc(var(--spacer) * 2);
border-bottom: 1px solid var(--border-color);
}
.nftImage {
position: relative;
margin: 0;
border-right: 1px solid var(--border-color);
width: calc(var(--spacer) * 2);
height: calc(var(--spacer) * 2);
}
.nftImage img,
.nftImage > svg:first-of-type {
width: 100%;
height: 100%;
}
.nftImage > svg:first-of-type {
transform: scale(0.7);
}
.nftImage .tooltip {
position: absolute;
padding: 0;
left: 0;
bottom: 0;
}
.nftImage .tooltip svg {
fill: var(--font-color-text);
}

View File

@ -0,0 +1,53 @@
import React, { ReactElement } from 'react'
import styles from './index.module.css'
import { Asset } from '@oceanprotocol/lib'
import { decodeTokenURI } from '@utils/nft'
import MetaAsset from './MetaAsset'
import MetaInfo from './MetaInfo'
import Tooltip from '@shared/atoms/Tooltip'
import NftTooltip from './NftTooltip'
import Logo from '@shared/atoms/Logo'
export default function MetaMain({
asset,
nftPublisher
}: {
asset: Asset
nftPublisher: string
}): ReactElement {
const nftMetadata = decodeTokenURI(asset?.nft?.tokenURI)
const blockscoutNetworks = [1287, 2021000, 2021001, 44787, 246, 1285]
const isBlockscoutExplorer = blockscoutNetworks.includes(asset?.chainId)
return (
<aside className={styles.meta}>
<header className={styles.asset}>
<div className={styles.nftImage}>
{nftMetadata?.image_data ? (
<img src={nftMetadata?.image_data} alt={asset?.nft?.name} />
) : (
<Logo noWordmark />
)}
{(nftMetadata || asset?.nftAddress) && (
<Tooltip
className={styles.tooltip}
content={
<NftTooltip
nft={nftMetadata}
address={asset?.nftAddress}
chainId={asset?.chainId}
isBlockscoutExplorer={isBlockscoutExplorer}
/>
}
/>
)}
</div>
<MetaAsset asset={asset} isBlockscoutExplorer={isBlockscoutExplorer} />
</header>
<MetaInfo asset={asset} nftPublisher={nftPublisher} />
</aside>
)
}

View File

@ -16,6 +16,7 @@
.content { .content {
composes: box from '@shared/atoms/Box.module.css'; composes: box from '@shared/atoms/Box.module.css';
padding-top: 0;
margin-top: var(--spacer); margin-top: var(--spacer);
position: relative; position: relative;
} }

View File

@ -16,16 +16,27 @@ import NetworkName from '@shared/NetworkName'
import content from '../../../../content/purgatory.json' import content from '../../../../content/purgatory.json'
import { AssetExtended } from 'src/@types/AssetExtended' import { AssetExtended } from 'src/@types/AssetExtended'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
import Web3 from 'web3'
export default function AssetContent({ export default function AssetContent({
asset asset
}: { }: {
asset: AssetExtended asset: AssetExtended
}): ReactElement { }): ReactElement {
const { debug } = useUserPreferences()
const [isOwner, setIsOwner] = useState(false) const [isOwner, setIsOwner] = useState(false)
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { isInPurgatory, purgatoryData, owner, isAssetNetwork } = useAsset() const { isInPurgatory, purgatoryData, owner, isAssetNetwork } = useAsset()
const { debug } = useUserPreferences()
const [receipts, setReceipts] = useState([])
const [nftPublisher, setNftPublisher] = useState<string>()
useEffect(() => {
setNftPublisher(
Web3.utils.toChecksumAddress(
receipts?.find((e) => e.type === 'METADATA_CREATED')?.nft?.owner
)
)
}, [receipts])
useEffect(() => { useEffect(() => {
if (!accountId || !owner) return if (!accountId || !owner) return
@ -50,11 +61,10 @@ export default function AssetContent({
<article className={styles.grid}> <article className={styles.grid}>
<div> <div>
<div className={styles.content}> <div className={styles.content}>
<MetaMain ddo={asset} /> <MetaMain asset={asset} nftPublisher={nftPublisher} />
{asset?.accessDetails?.datatoken !== null && ( {asset?.accessDetails?.datatoken !== null && (
<Bookmark did={asset?.id} /> <Bookmark did={asset?.id} />
)} )}
{isInPurgatory === true ? ( {isInPurgatory === true ? (
<Alert <Alert
title={content.asset.title} title={content.asset.title}
@ -71,9 +81,8 @@ export default function AssetContent({
<MetaSecondary ddo={asset} /> <MetaSecondary ddo={asset} />
</> </>
)} )}
<MetaFull ddo={asset} /> <MetaFull ddo={asset} />
<EditHistory /> <EditHistory receipts={receipts} setReceipts={setReceipts} />
{debug === true && <DebugOutput title="DDO" output={asset} />} {debug === true && <DebugOutput title="DDO" output={asset} />}
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
import { BoxSelectionOption } from '@shared/FormFields/BoxSelection' import { BoxSelectionOption } from '@shared/FormFields/BoxSelection'
import Input from '@shared/FormInput' import Input from '@shared/FormInput'
import { Field, useFormikContext } from 'formik' import { Field, useFormikContext } from 'formik'
import React, { ReactElement } from 'react' import React, { ReactElement, useEffect } from 'react'
import content from '../../../../content/publish/form.json' import content from '../../../../content/publish/form.json'
import { FormPublishData } from '../_types' import { FormPublishData } from '../_types'
import { getFieldContent } from '../_utils' import { getFieldContent } from '../_utils'
@ -17,7 +17,7 @@ const assetTypeOptionsTitles = getFieldContent(
export default function MetadataFields(): ReactElement { export default function MetadataFields(): ReactElement {
// connect with Form state, use for conditional field rendering // connect with Form state, use for conditional field rendering
const { values } = useFormikContext<FormPublishData>() const { values, setFieldValue } = useFormikContext<FormPublishData>()
// BoxSelection component is not a Formik component // BoxSelection component is not a Formik component
// so we need to handle checked state manually. // so we need to handle checked state manually.
@ -45,6 +45,17 @@ export default function MetadataFields(): ReactElement {
checked: values.metadata.dockerImage === `${preset.image}:${preset.tag}` checked: values.metadata.dockerImage === `${preset.image}:${preset.tag}`
})) }))
useEffect(() => {
setFieldValue(
'services[0].access',
values.metadata.type === 'algorithm' ? 'compute' : 'access'
)
setFieldValue(
'services[0].algorithmPrivacy',
values.metadata.type === 'algorithm'
)
}, [values.metadata.type])
dockerImageOptions.push({ name: 'custom', title: 'Custom', checked: false }) dockerImageOptions.push({ name: 'custom', title: 'Custom', checked: false })
return ( return (

View File

@ -20,7 +20,8 @@ export default function Navigation(): ReactElement {
const isSuccessMetadata = errors.metadata === undefined const isSuccessMetadata = errors.metadata === undefined
const isSuccessServices = errors.services === undefined const isSuccessServices = errors.services === undefined
const isSuccessPricing = const isSuccessPricing =
errors.pricing === undefined && touched.pricing?.price errors.pricing === undefined &&
(touched.pricing?.price || touched.pricing?.freeAgreement)
const isSuccessPreview = const isSuccessPreview =
isSuccessMetadata && isSuccessServices && isSuccessPricing isSuccessMetadata && isSuccessServices && isSuccessPricing

View File

@ -43,7 +43,11 @@ export default function ServicesFields(): ReactElement {
// Auto-change access type based on algo privacy boolean. // Auto-change access type based on algo privacy boolean.
// Could be also done later in transformPublishFormToDdo(). // Could be also done later in transformPublishFormToDdo().
useEffect(() => { useEffect(() => {
if (!values.services[0].algorithmPrivacy) return if (
values.services[0].algorithmPrivacy === null ||
values.services[0].algorithmPrivacy === undefined
)
return
setFieldValue( setFieldValue(
'services[0].access', 'services[0].access',

View File

@ -1,6 +1,6 @@
import { ReactElement, useEffect } from 'react' import { ReactElement, useEffect } from 'react'
import { useFormikContext } from 'formik' import { useFormikContext } from 'formik'
import { wizardSteps } from './_constants' import { wizardSteps, initialPublishFeedback } from './_constants'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
import { FormPublishData, PublishFeedback } from './_types' import { FormPublishData, PublishFeedback } from './_types'
@ -38,7 +38,7 @@ export function Steps({
'a single transaction', 'a single transaction',
'a single transaction, after an initial approve transaction' 'a single transaction, after an initial approve transaction'
) )
: feedback['1'].description : initialPublishFeedback['1'].description
} }
}) })
}, [values.pricing.type, setFieldValue]) }, [values.pricing.type, setFieldValue])

View File

@ -70,7 +70,7 @@ export const initialValues: FormPublishData = {
links: [{ url: '' }], links: [{ url: '' }],
dataTokenOptions: { name: '', symbol: '' }, dataTokenOptions: { name: '', symbol: '' },
timeout: '', timeout: '',
access: '', access: 'access',
providerUrl: { providerUrl: {
url: 'https://provider.mainnet.oceanprotocol.com', url: 'https://provider.mainnet.oceanprotocol.com',
valid: true valid: true

View File

@ -181,6 +181,7 @@ export async function transformPublishFormToDdo(
} }
], ],
nft: { nft: {
...generateNftCreateData(values?.metadata.nft),
owner: accountId owner: accountId
} }
}) })

View File

@ -24,7 +24,7 @@ import {
import { getOceanConfig } from '@utils/ocean' import { getOceanConfig } from '@utils/ocean'
import { validationSchema } from './_validation' import { validationSchema } from './_validation'
import { useAbortController } from '@hooks/useAbortController' import { useAbortController } from '@hooks/useAbortController'
import { setNftMetadata } from '@utils/nft' import { setNFTMetadataAndTokenURI } from '@utils/nft'
// TODO: restore FormikPersist, add back clear form action // TODO: restore FormikPersist, add back clear form action
const formName = 'ocean-publish-form' const formName = 'ocean-publish-form'
@ -63,7 +63,15 @@ export default function PublishPage({
...prevState, ...prevState,
'1': { '1': {
...prevState['1'], ...prevState['1'],
status: 'active' status: 'active',
txCount: values.pricing.type === 'dynamic' ? 2 : 1,
description:
values.pricing.type === 'dynamic'
? prevState['1'].description.replace(
'a single transaction',
'a single transaction, after an initial approve transaction'
)
: prevState['1'].description
} }
})) }))
@ -102,7 +110,14 @@ export default function PublishPage({
'1': { '1': {
...prevState['1'], ...prevState['1'],
status: 'error', status: 'error',
errorMessage: error.message errorMessage: error.message,
description:
values.pricing.type === 'dynamic'
? prevState['1'].description.replace(
'a single transaction',
'a single transaction, after an initial approve transaction'
)
: prevState['1'].description
} }
})) }))
} }
@ -173,10 +188,11 @@ export default function PublishPage({
if (!_ddo || !_encryptedDdo) throw new Error('No DDO received.') if (!_ddo || !_encryptedDdo) throw new Error('No DDO received.')
const res = await setNftMetadata( const res = await setNFTMetadataAndTokenURI(
_ddo, _ddo,
accountId, accountId,
web3, web3,
values.metadata.nft,
newAbortController() newAbortController()
) )