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:
commit
08b90b3635
@ -141,15 +141,6 @@
|
||||
"options": ["Forever", "1 day", "1 week", "1 month", "1 year"],
|
||||
"sortOptions": false,
|
||||
"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
1902
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@ -21,20 +21,20 @@
|
||||
"@coingecko/cryptoformat": "^0.4.4",
|
||||
"@loadable/component": "^5.15.2",
|
||||
"@oceanprotocol/art": "^3.2.0",
|
||||
"@oceanprotocol/lib": "^1.0.0-next.27",
|
||||
"@oceanprotocol/lib": "^1.0.0-next.28",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@portis/web3": "^4.0.6",
|
||||
"@portis/web3": "^4.0.7",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@walletconnect/web3-provider": "^1.7.1",
|
||||
"axios": "^0.25.0",
|
||||
"@walletconnect/web3-provider": "^1.7.5",
|
||||
"axios": "^0.26.1",
|
||||
"bignumber.js": "^9.0.2",
|
||||
"chart.js": "^3.7.0",
|
||||
"chart.js": "^3.7.1",
|
||||
"classnames": "^2.3.1",
|
||||
"date-fns": "^2.28.0",
|
||||
"decimal.js": "^10.3.1",
|
||||
"dom-confetti": "^0.2.2",
|
||||
"dotenv": "^15.0.0",
|
||||
"filesize": "^8.0.6",
|
||||
"dotenv": "^16.0.0",
|
||||
"filesize": "^8.0.7",
|
||||
"formik": "^2.2.9",
|
||||
"gray-matter": "^4.0.3",
|
||||
"is-url-superb": "^6.1.0",
|
||||
@ -44,7 +44,7 @@
|
||||
"lodash.omit": "^4.5.0",
|
||||
"myetherwallet-blockies": "^0.1.1",
|
||||
"next": "^12.1.0",
|
||||
"query-string": "^7.1.0",
|
||||
"query-string": "^7.1.1",
|
||||
"react": "^17.0.2",
|
||||
"react-chartjs-2": "^4.0.1",
|
||||
"react-clipboard.js": "^2.0.16",
|
||||
@ -52,8 +52,8 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dotdotdot": "^1.3.1",
|
||||
"react-modal": "^3.14.4",
|
||||
"react-paginate": "^8.1.0",
|
||||
"react-spring": "^9.4.2",
|
||||
"react-paginate": "^8.1.2",
|
||||
"react-spring": "^9.4.4",
|
||||
"react-tabs": "^3.2.3",
|
||||
"react-toastify": "^8.1.0",
|
||||
"remark": "^13.0.0",
|
||||
@ -61,10 +61,10 @@
|
||||
"remark-html": "^13.0.1",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"slugify": "^1.6.5",
|
||||
"swr": "^1.2.0",
|
||||
"urql": "^2.1.1",
|
||||
"swr": "^1.2.2",
|
||||
"urql": "^2.2.0",
|
||||
"use-dark-mode": "^2.3.1",
|
||||
"web3": "^1.6.1",
|
||||
"web3": "^1.7.1",
|
||||
"web3modal": "^1.9.5",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
@ -84,8 +84,8 @@
|
||||
"@types/react-tabs": "^2.3.4",
|
||||
"@types/remove-markdown": "^0.3.1",
|
||||
"@types/yup": "^0.29.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||
"@typescript-eslint/parser": "^5.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.15.0",
|
||||
"@typescript-eslint/parser": "^5.15.0",
|
||||
"apollo": "^2.33.9",
|
||||
"eslint": "^7.27.0",
|
||||
"eslint-config-oceanprotocol": "^1.5.0",
|
||||
@ -96,11 +96,11 @@
|
||||
"file-loader": "^6.2.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"husky": "^7.0.4",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier": "^2.6.0",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"process": "^0.11.10",
|
||||
"serve": "^13.0.2",
|
||||
"stream-http": "^2.8.3",
|
||||
"stream-http": "^3.2.0",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { SvgWaves } from './SvgWaves'
|
||||
import {
|
||||
Asset,
|
||||
LoggerInstance,
|
||||
Asset,
|
||||
getHash,
|
||||
Nft,
|
||||
ProviderInstance,
|
||||
DDO
|
||||
DDO,
|
||||
MetadataAndTokenURI
|
||||
} from '@oceanprotocol/lib'
|
||||
import { SvgWaves } from './SvgWaves'
|
||||
import Web3 from 'web3'
|
||||
import { TransactionReceipt } from 'web3-core'
|
||||
|
||||
@ -45,17 +46,12 @@ function encodeSvg(svgString: string): string {
|
||||
export function generateNftMetadata(): NftMetadata {
|
||||
const waves = new SvgWaves()
|
||||
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 newNft: NftMetadata = {
|
||||
name: 'Ocean Asset NFT',
|
||||
symbol: 'OCEAN-NFT',
|
||||
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',
|
||||
background_color: '141414', // dark background
|
||||
image_data: imageData
|
||||
@ -64,24 +60,32 @@ export function generateNftMetadata(): NftMetadata {
|
||||
return newNft
|
||||
}
|
||||
|
||||
export function generateNftCreateData(nftMetadata: NftMetadata): any {
|
||||
// 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'
|
||||
)
|
||||
const tokenUriPrefix = 'data:application/json;base64,'
|
||||
|
||||
export function generateNftCreateData(nftMetadata: NftMetadata): any {
|
||||
const nftCreateData = {
|
||||
name: nftMetadata.name,
|
||||
symbol: nftMetadata.symbol,
|
||||
templateIndex: 1,
|
||||
tokenURI: `data:application/json;base64,${encodedMetadata}`
|
||||
tokenURI: ''
|
||||
}
|
||||
|
||||
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(
|
||||
asset: Asset | DDO,
|
||||
accountId: string,
|
||||
@ -125,3 +129,66 @@ export async function setNftMetadata(
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { usePrices } from '@context/Prices'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import Web3 from 'web3'
|
||||
import useNftFactory from '@hooks/contracts/useNftFactory'
|
||||
import { NftFactory } from '@oceanprotocol/lib'
|
||||
import Conversion from '@shared/Price/Conversion'
|
||||
@ -20,7 +21,7 @@ const getEstGasFee = async (
|
||||
|
||||
const gasPrice = await web3.eth.getGasPrice()
|
||||
const gasLimit = await nftFactory?.estGasCreateNFT(address, nft)
|
||||
const gasFeeEth = web3.utils.fromWei(
|
||||
const gasFeeEth = Web3.utils.fromWei(
|
||||
(+gasPrice * +gasLimit).toString(),
|
||||
'ether'
|
||||
)
|
||||
|
@ -6,7 +6,6 @@
|
||||
background: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@ -25,11 +24,12 @@
|
||||
fill: var(--brand-alert-green);
|
||||
}
|
||||
|
||||
.copied::after {
|
||||
content: 'Copied!';
|
||||
position: absolute;
|
||||
top: -150%;
|
||||
left: -140%;
|
||||
.action {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.feedback {
|
||||
font-size: var(--font-size-mini);
|
||||
color: var(--brand-alert-green);
|
||||
}
|
||||
|
@ -27,7 +27,10 @@ export default function Copy({ text }: { text: string }): ReactElement {
|
||||
onSuccess={() => setIsCopied(true)}
|
||||
className={`${styles.button} ${isCopied ? styles.copied : ''}`}
|
||||
>
|
||||
<div className={styles.action}>
|
||||
<IconCopy className={styles.icon} />
|
||||
{isCopied && <span className={styles.feedback}>Copied!</span>}
|
||||
</div>
|
||||
</Clipboard>
|
||||
)
|
||||
}
|
||||
|
@ -85,11 +85,18 @@ export default function Graph({
|
||||
|
||||
const newGraphData = {
|
||||
labels: timestamps,
|
||||
datasets: [{ ...lineStyle, data, borderColor: `#8b98a9` }]
|
||||
datasets: [
|
||||
{
|
||||
...lineStyle,
|
||||
data,
|
||||
borderColor: `#8b98a9`,
|
||||
backgroundColor: darkMode.value ? '#201f1f' : '#f7f7f7'
|
||||
}
|
||||
]
|
||||
}
|
||||
setGraphData(newGraphData)
|
||||
LoggerInstance.log('[pool graph] New graph data created:', newGraphData)
|
||||
}, [poolSnapshots, graphType, currency, prices, locale])
|
||||
}, [poolSnapshots, graphType, currency, prices, locale, darkMode.value])
|
||||
|
||||
return (
|
||||
<div className={styles.graphWrap}>
|
||||
|
@ -17,6 +17,7 @@ const getReceipts = gql`
|
||||
id
|
||||
nft {
|
||||
address
|
||||
owner
|
||||
}
|
||||
tx
|
||||
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()
|
||||
|
||||
function getUpdateType(type: string): string {
|
||||
@ -72,7 +79,7 @@ export default function EditHistory(): ReactElement {
|
||||
if (!data || data.nftUpdates.length === 0) return
|
||||
const receiptCollection = data.nftUpdates
|
||||
setReceipts(receiptCollection)
|
||||
}, [data])
|
||||
}, [data, setReceipts])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -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);
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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);
|
||||
}
|
54
src/components/Asset/AssetContent/MetaMain/MetaAsset.tsx
Normal file
54
src/components/Asset/AssetContent/MetaMain/MetaAsset.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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);
|
||||
}
|
47
src/components/Asset/AssetContent/MetaMain/MetaInfo.tsx
Normal file
47
src/components/Asset/AssetContent/MetaMain/MetaInfo.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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;
|
||||
}
|
79
src/components/Asset/AssetContent/MetaMain/NftTooltip.tsx
Normal file
79
src/components/Asset/AssetContent/MetaMain/NftTooltip.tsx
Normal 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>
|
||||
)
|
||||
}
|
44
src/components/Asset/AssetContent/MetaMain/index.module.css
Normal file
44
src/components/Asset/AssetContent/MetaMain/index.module.css
Normal 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);
|
||||
}
|
53
src/components/Asset/AssetContent/MetaMain/index.tsx
Normal file
53
src/components/Asset/AssetContent/MetaMain/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
|
||||
.content {
|
||||
composes: box from '@shared/atoms/Box.module.css';
|
||||
padding-top: 0;
|
||||
margin-top: var(--spacer);
|
||||
position: relative;
|
||||
}
|
||||
|
@ -16,16 +16,27 @@ import NetworkName from '@shared/NetworkName'
|
||||
import content from '../../../../content/purgatory.json'
|
||||
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import Web3 from 'web3'
|
||||
|
||||
export default function AssetContent({
|
||||
asset
|
||||
}: {
|
||||
asset: AssetExtended
|
||||
}): ReactElement {
|
||||
const { debug } = useUserPreferences()
|
||||
const [isOwner, setIsOwner] = useState(false)
|
||||
const { accountId } = useWeb3()
|
||||
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(() => {
|
||||
if (!accountId || !owner) return
|
||||
@ -50,11 +61,10 @@ export default function AssetContent({
|
||||
<article className={styles.grid}>
|
||||
<div>
|
||||
<div className={styles.content}>
|
||||
<MetaMain ddo={asset} />
|
||||
<MetaMain asset={asset} nftPublisher={nftPublisher} />
|
||||
{asset?.accessDetails?.datatoken !== null && (
|
||||
<Bookmark did={asset?.id} />
|
||||
)}
|
||||
|
||||
{isInPurgatory === true ? (
|
||||
<Alert
|
||||
title={content.asset.title}
|
||||
@ -71,9 +81,8 @@ export default function AssetContent({
|
||||
<MetaSecondary ddo={asset} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<MetaFull ddo={asset} />
|
||||
<EditHistory />
|
||||
<EditHistory receipts={receipts} setReceipts={setReceipts} />
|
||||
{debug === true && <DebugOutput title="DDO" output={asset} />}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { BoxSelectionOption } from '@shared/FormFields/BoxSelection'
|
||||
import Input from '@shared/FormInput'
|
||||
import { Field, useFormikContext } from 'formik'
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import content from '../../../../content/publish/form.json'
|
||||
import { FormPublishData } from '../_types'
|
||||
import { getFieldContent } from '../_utils'
|
||||
@ -17,7 +17,7 @@ const assetTypeOptionsTitles = getFieldContent(
|
||||
|
||||
export default function MetadataFields(): ReactElement {
|
||||
// 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
|
||||
// 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}`
|
||||
}))
|
||||
|
||||
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 })
|
||||
|
||||
return (
|
||||
|
@ -20,7 +20,8 @@ export default function Navigation(): ReactElement {
|
||||
const isSuccessMetadata = errors.metadata === undefined
|
||||
const isSuccessServices = errors.services === undefined
|
||||
const isSuccessPricing =
|
||||
errors.pricing === undefined && touched.pricing?.price
|
||||
errors.pricing === undefined &&
|
||||
(touched.pricing?.price || touched.pricing?.freeAgreement)
|
||||
const isSuccessPreview =
|
||||
isSuccessMetadata && isSuccessServices && isSuccessPricing
|
||||
|
||||
|
@ -43,7 +43,11 @@ export default function ServicesFields(): ReactElement {
|
||||
// Auto-change access type based on algo privacy boolean.
|
||||
// Could be also done later in transformPublishFormToDdo().
|
||||
useEffect(() => {
|
||||
if (!values.services[0].algorithmPrivacy) return
|
||||
if (
|
||||
values.services[0].algorithmPrivacy === null ||
|
||||
values.services[0].algorithmPrivacy === undefined
|
||||
)
|
||||
return
|
||||
|
||||
setFieldValue(
|
||||
'services[0].access',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ReactElement, useEffect } from 'react'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { wizardSteps } from './_constants'
|
||||
import { wizardSteps, initialPublishFeedback } from './_constants'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { FormPublishData, PublishFeedback } from './_types'
|
||||
|
||||
@ -38,7 +38,7 @@ export function Steps({
|
||||
'a single transaction',
|
||||
'a single transaction, after an initial approve transaction'
|
||||
)
|
||||
: feedback['1'].description
|
||||
: initialPublishFeedback['1'].description
|
||||
}
|
||||
})
|
||||
}, [values.pricing.type, setFieldValue])
|
||||
|
@ -70,7 +70,7 @@ export const initialValues: FormPublishData = {
|
||||
links: [{ url: '' }],
|
||||
dataTokenOptions: { name: '', symbol: '' },
|
||||
timeout: '',
|
||||
access: '',
|
||||
access: 'access',
|
||||
providerUrl: {
|
||||
url: 'https://provider.mainnet.oceanprotocol.com',
|
||||
valid: true
|
||||
|
@ -181,6 +181,7 @@ export async function transformPublishFormToDdo(
|
||||
}
|
||||
],
|
||||
nft: {
|
||||
...generateNftCreateData(values?.metadata.nft),
|
||||
owner: accountId
|
||||
}
|
||||
})
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
import { getOceanConfig } from '@utils/ocean'
|
||||
import { validationSchema } from './_validation'
|
||||
import { useAbortController } from '@hooks/useAbortController'
|
||||
import { setNftMetadata } from '@utils/nft'
|
||||
import { setNFTMetadataAndTokenURI } from '@utils/nft'
|
||||
|
||||
// TODO: restore FormikPersist, add back clear form action
|
||||
const formName = 'ocean-publish-form'
|
||||
@ -63,7 +63,15 @@ export default function PublishPage({
|
||||
...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': {
|
||||
...prevState['1'],
|
||||
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.')
|
||||
|
||||
const res = await setNftMetadata(
|
||||
const res = await setNFTMetadataAndTokenURI(
|
||||
_ddo,
|
||||
accountId,
|
||||
web3,
|
||||
values.metadata.nft,
|
||||
newAbortController()
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user