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

Merge remote-tracking branch 'origin/main' into feature/1400-shared-components-stories

This commit is contained in:
ClaudiaHolhos 2022-06-16 12:38:23 +03:00
commit e71695fed5
20 changed files with 373 additions and 299 deletions

View File

@ -18,7 +18,7 @@ import { getAccessDetails } from '@utils/accessDetailsAndPricing'
import { useIsMounted } from '@hooks/useIsMounted' import { useIsMounted } from '@hooks/useIsMounted'
import { useMarketMetadata } from './MarketMetadata' import { useMarketMetadata } from './MarketMetadata'
interface AssetProviderValue { export interface AssetProviderValue {
isInPurgatory: boolean isInPurgatory: boolean
purgatoryData: Purgatory purgatoryData: Purgatory
asset: AssetExtended asset: AssetExtended

View File

@ -5,7 +5,9 @@ export const opcQuery = gql`
opc(id: 1) { opc(id: 1) {
swapOceanFee swapOceanFee
swapNonOceanFee swapNonOceanFee
approvedTokens approvedTokens {
id
}
id id
} }
} }

View File

@ -36,7 +36,7 @@ function MarketMetadataProvider({
opcData.push({ opcData.push({
chainId: appConfig.chainIdsSupported[i], chainId: appConfig.chainIdsSupported[i],
approvedTokens: response.data?.opc.approvedTokens, approvedTokens: response.data?.opc.approvedTokens?.map((x) => x.id),
swapApprovedFee: response.data?.opc.swapOceanFee, swapApprovedFee: response.data?.opc.swapOceanFee,
swapNotApprovedFee: response.data?.opc.swapNonOceanFee swapNotApprovedFee: response.data?.opc.swapNonOceanFee
} as OpcFee) } as OpcFee)

View File

@ -9,7 +9,7 @@ import Web3 from 'web3'
// TODO: Why do we have these one line functions ?!?!?! // TODO: Why do we have these one line functions ?!?!?!
export async function getEncryptedFiles( export async function getEncryptedFiles(
files: FileMetadata[], files: any,
providerUrl: string providerUrl: string
): Promise<string> { ): Promise<string> {
try { try {

View File

@ -20,15 +20,6 @@
text-align: center; text-align: center;
} }
.button:first-child {
margin-left: 0;
}
.button:last-child {
margin-right: 0;
min-width: auto;
}
.button:hover, .button:hover,
.button:focus { .button:focus {
color: var(--brand-white); color: var(--brand-white);

View File

@ -23,6 +23,11 @@
margin-left: calc(var(--spacer) / 4); margin-left: calc(var(--spacer) / 4);
} }
.loader.white {
border-color: rgba(255 255 255 / 0.3);
border-top-color: var(--brand-white);
}
@keyframes loader { @keyframes loader {
to { to {
transform: rotate(360deg); transform: rotate(360deg);

View File

@ -3,12 +3,13 @@ import styles from './index.module.css'
export interface LoaderProps { export interface LoaderProps {
message?: string message?: string
white?: boolean
} }
export default function Loader({ message }: LoaderProps): ReactElement { export default function Loader({ message, white }: LoaderProps): ReactElement {
return ( return (
<div className={styles.loaderWrap}> <div className={styles.loaderWrap}>
<span className={styles.loader} /> <span className={`${styles.loader} ${white ? styles.white : ''}`} />
{message && <span className={styles.message}>{message}</span>} {message && <span className={styles.message}>{message}</span>}
</div> </div>
) )

View File

@ -5,13 +5,14 @@ import AddToken from '@shared/AddToken'
import ExplorerLink from '@shared/ExplorerLink' import ExplorerLink from '@shared/ExplorerLink'
import Publisher from '@shared/Publisher' import Publisher from '@shared/Publisher'
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import { AssetExtended } from 'src/@types/AssetExtended'
import styles from './MetaAsset.module.css' import styles from './MetaAsset.module.css'
export default function MetaAsset({ export default function MetaAsset({
asset, asset,
isBlockscoutExplorer isBlockscoutExplorer
}: { }: {
asset: Asset asset: AssetExtended
isBlockscoutExplorer: boolean isBlockscoutExplorer: boolean
}): ReactElement { }): ReactElement {
const { isAssetNetwork } = useAsset() const { isAssetNetwork } = useAsset()

View File

@ -1,16 +1,17 @@
import { Asset } from '@oceanprotocol/lib' import { useAsset } from '@context/Asset'
import AssetType from '@shared/AssetType' import AssetType from '@shared/AssetType'
import Time from '@shared/atoms/Time' import Time from '@shared/atoms/Time'
import Publisher from '@shared/Publisher' import Publisher from '@shared/Publisher'
import { getServiceByName } from '@utils/ddo' import { getServiceByName } from '@utils/ddo'
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import { AssetExtended } from 'src/@types/AssetExtended'
import styles from './MetaInfo.module.css' import styles from './MetaInfo.module.css'
export default function MetaInfo({ export default function MetaInfo({
asset, asset,
nftPublisher nftPublisher
}: { }: {
asset: Asset asset: AssetExtended
nftPublisher: string nftPublisher: string
}): ReactElement { }): ReactElement {
const isCompute = Boolean(getServiceByName(asset, 'compute')) const isCompute = Boolean(getServiceByName(asset, 'compute'))

View File

@ -13,32 +13,3 @@
height: calc(var(--spacer) * 2); height: calc(var(--spacer) * 2);
border-bottom: 1px solid var(--border-color); 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

@ -1,14 +1,11 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styles from './index.module.css' import styles from './index.module.css'
import { AssetExtended } from 'src/@types/AssetExtended'
import { decodeTokenURI } from '@utils/nft'
import MetaAsset from './MetaAsset' import MetaAsset from './MetaAsset'
import MetaInfo from './MetaInfo' import MetaInfo from './MetaInfo'
import Tooltip from '@shared/atoms/Tooltip' import Nft from '../Nft'
import NftTooltip from './NftTooltip' import { AssetExtended } from 'src/@types/AssetExtended'
import Logo from '@shared/atoms/Logo'
import { FormPublishData } from '../../../Publish/_types' const blockscoutNetworks = [1287, 2021000, 2021001, 44787, 246, 1285]
import { useFormikContext } from 'formik'
export default function MetaMain({ export default function MetaMain({
asset, asset,
@ -17,51 +14,12 @@ export default function MetaMain({
asset: AssetExtended asset: AssetExtended
nftPublisher: string nftPublisher: string
}): ReactElement { }): ReactElement {
const nftMetadata = decodeTokenURI(asset?.nft?.tokenURI)
const blockscoutNetworks = [1287, 2021000, 2021001, 44787, 246, 1285]
const isBlockscoutExplorer = blockscoutNetworks.includes(asset?.chainId) const isBlockscoutExplorer = blockscoutNetworks.includes(asset?.chainId)
// TODO: using this for the publish preview works fine, but produces a console warning
// on asset details page as there is no formik context there:
// Warning: Formik context is undefined, please verify you are calling useFormikContext()
// as child of a <Formik> component.
const formikState = useFormikContext<FormPublishData>()
// checking if the NFT has an image associated (tokenURI)
// if tokenURI is undefined, then we are in Preview
// for Preview we need to show accessDetails.dataImage
// as this is where the NFT's SVG (during publish) is stored
const nftImage = nftMetadata?.image_data
? nftMetadata.image_data
: formikState?.values?.metadata?.nft?.image_data
? formikState.values.metadata.nft.image_data
: null
return ( return (
<aside className={styles.meta}> <aside className={styles.meta}>
<header className={styles.asset}> <header className={styles.asset}>
<div className={styles.nftImage}> <Nft isBlockscoutExplorer={isBlockscoutExplorer} />
{nftImage ? (
<img src={nftImage} 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} /> <MetaAsset asset={asset} isBlockscoutExplorer={isBlockscoutExplorer} />
</header> </header>

View File

@ -7,6 +7,11 @@ import styles from './NftTooltip.module.css'
import explorerLinkStyles from '@shared/ExplorerLink/index.module.css' import explorerLinkStyles from '@shared/ExplorerLink/index.module.css'
import { accountTruncate } from '@utils/web3' import { accountTruncate } from '@utils/web3'
// Supported OpenSea networks:
// https://support.opensea.io/hc/en-us/articles/4404027708051-Which-blockchains-does-OpenSea-support-
const openSeaNetworks = [1, 137]
const openSeaTestNetworks = [4]
export default function NftTooltip({ export default function NftTooltip({
nft, nft,
address, address,
@ -18,26 +23,23 @@ export default function NftTooltip({
chainId: number chainId: number
isBlockscoutExplorer: boolean isBlockscoutExplorer: boolean
}): ReactElement { }): ReactElement {
// Currently Ocean NFTs are not displayed correctly on OpenSea const openSeaSupported = openSeaNetworks
// Code prepared to easily integrate this feature once this is fixed .concat(openSeaTestNetworks)
//
// 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) .includes(chainId)
const openSeaBaseUri = openSeaSupported const openSeaBaseUri = openSeaSupported
? openseaTestNetworks.includes(chainId) ? openSeaTestNetworks.includes(chainId)
? 'https://testnets.opensea.io' ? 'https://testnets.opensea.io'
: 'https://opensea.io' : 'https://opensea.io'
: undefined : undefined
const openSeaUrl = `${openSeaBaseUri}/assets/${
chainId === 137 ? 'matic' : ''
}/${address}/1`
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
{nft && <img src={nft.image_data} alt={nft?.name} />} {nft && <img src={nft.image_data || nft.image} alt={nft?.name} />}
<div className={styles.info}> <div className={styles.info}>
{nft && <h5>{nft.name}</h5>} {nft && <h5>{nft.name}</h5>}
{address && ( {address && (
@ -53,25 +55,23 @@ export default function NftTooltip({
isBlockscoutExplorer ? `tokens/${address}` : `token/${address}` isBlockscoutExplorer ? `tokens/${address}` : `token/${address}`
} }
> >
View on explorer View on Explorer
</ExplorerLink> </ExplorerLink>
)} )}
{openSeaSupported && nft && address && ( {openSeaSupported && address && (
<a <a
href={`${openSeaBaseUri}/assets/${address}/1`} href={openSeaUrl}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className={explorerLinkStyles.link} className={explorerLinkStyles.link}
> >
View on OpeanSea <External /> View on OpenSea <External />
</a> </a>
)} )}
</div> </div>
{!nft?.image_data && ( {!nft?.image_data && !nft?.image && (
<p className={styles.fallback}> <p className={styles.fallback}>This Data NFT has no image set.</p>
This Data NFT was not created on Ocean Market
</p>
)} )}
</div> </div>
</div> </div>

View File

@ -0,0 +1,28 @@
.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,60 @@
import { useAsset } from '@context/Asset'
import Tooltip from '@shared/atoms/Tooltip'
import { decodeTokenURI } from '@utils/nft'
import { useFormikContext } from 'formik'
import React from 'react'
import { FormPublishData } from 'src/components/Publish/_types'
import Logo from '@shared/atoms/Logo'
import NftTooltip from './NftTooltip'
import styles from './index.module.css'
export default function Nft({
isBlockscoutExplorer
}: {
isBlockscoutExplorer: boolean
}) {
const { asset } = useAsset()
const nftMetadata = decodeTokenURI(asset?.nft?.tokenURI)
// TODO: using this for the publish preview works fine, but produces a console warning
// on asset details page as there is no formik context there:
// Warning: Formik context is undefined, please verify you are calling useFormikContext()
// as child of a <Formik> component.
const formikState = useFormikContext<FormPublishData>()
// checking if the NFT has an image associated (tokenURI)
// if tokenURI is undefined, then we are in Preview
// for Preview we need to show accessDetails.dataImage
// as this is where the NFT's SVG (during publish) is stored
const nftImage = nftMetadata?.image_data
? nftMetadata.image_data
: nftMetadata?.image
? nftMetadata.image
: formikState?.values?.metadata?.nft?.image_data
? formikState.values.metadata.nft.image_data
: null
return (
<div className={styles.nftImage}>
{nftImage ? (
<img src={nftImage} 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>
)
}

View File

@ -61,7 +61,9 @@ export default function MarketStats(): ReactElement {
// //
const getMarketStats = useCallback(async () => { const getMarketStats = useCallback(async () => {
if (!mainChainIds?.length) return if (!mainChainIds?.length) return
const newData: {
[chainId: number]: FooterStatsValuesGlobalStatistics
} = {}
for (const chainId of mainChainIds) { for (const chainId of mainChainIds) {
const context: OperationContext = { const context: OperationContext = {
url: `${getSubgraphUri( url: `${getSubgraphUri(
@ -73,15 +75,12 @@ export default function MarketStats(): ReactElement {
try { try {
const response = await fetchData(queryGlobalStatistics, null, context) const response = await fetchData(queryGlobalStatistics, null, context)
if (!response?.data?.globalStatistics) return if (!response?.data?.globalStatistics) return
newData[chainId] = response.data.globalStatistics[0]
setData((prevState) => ({
...prevState,
[chainId]: response.data.globalStatistics[0]
}))
} catch (error) { } catch (error) {
LoggerInstance.error('Error fetching global stats: ', error.message) LoggerInstance.error('Error fetching global stats: ', error.message)
} }
} }
setData(newData)
}, [mainChainIds]) }, [mainChainIds])
// //
@ -96,10 +95,12 @@ export default function MarketStats(): ReactElement {
// //
useEffect(() => { useEffect(() => {
if (!data || !mainChainIds?.length) return if (!data || !mainChainIds?.length) return
const newTotal: StatsTotal = { const newTotal: StatsTotal = {
...initialTotal // always start calculating beginning from initial 0 values ...initialTotal // always start calculating beginning from initial 0 values
} }
const newTVLInOcean: StatsValue = {}
const newTotalLiquidity: StatsValue = {}
const newPoolCount: StatsValue = {}
for (const chainId of mainChainIds) { for (const chainId of mainChainIds) {
const baseTokenValue = data[chainId]?.totalLiquidity[0]?.value const baseTokenValue = data[chainId]?.totalLiquidity[0]?.value
@ -109,25 +110,15 @@ export default function MarketStats(): ReactElement {
? new Decimal(baseTokenValue).mul(2) ? new Decimal(baseTokenValue).mul(2)
: new Decimal(0) : new Decimal(0)
setTotalValueLockedInOcean((prevState) => ({ newTVLInOcean[chainId] = `${totalValueLockedInOcean}`
...prevState,
[chainId]: `${totalValueLockedInOcean}`
}))
const totalOceanLiquidity = Number(baseTokenValue) || 0 const totalOceanLiquidity = Number(baseTokenValue) || 0
setTotalOceanLiquidity((prevState) => ({ newTotalLiquidity[chainId] = `${totalOceanLiquidity}`
...prevState,
[chainId]: `${totalOceanLiquidity}`
}))
const poolCount = data[chainId]?.poolCount || 0 const poolCount = data[chainId]?.poolCount || 0
setPoolCount((prevState) => ({ newPoolCount[chainId] = `${poolCount}`
...prevState,
[chainId]: `${poolCount}`
}))
const nftCount = data[chainId]?.nftCount || 0 const nftCount = data[chainId]?.nftCount || 0
const datatokenCount = data[chainId]?.datatokenCount || 0 const datatokenCount = data[chainId]?.datatokenCount || 0
const orderCount = data[chainId]?.orderCount || 0 const orderCount = data[chainId]?.orderCount || 0
@ -142,7 +133,9 @@ export default function MarketStats(): ReactElement {
LoggerInstance.error('Error data manipulation: ', error.message) LoggerInstance.error('Error data manipulation: ', error.message)
} }
} }
setTotalValueLockedInOcean(newTVLInOcean)
setTotalOceanLiquidity(newTotalLiquidity)
setPoolCount(newPoolCount)
setTotal(newTotal) setTotal(newTotal)
}, [data, mainChainIds, prices, currency]) }, [data, mainChainIds, prices, currency])

View File

@ -7,6 +7,7 @@
.actions button { .actions button {
margin: 0 calc(var(--spacer) / 2); margin: 0 calc(var(--spacer) / 2);
min-height: 40px;
} }
.infoIcon { .infoIcon {

View File

@ -10,6 +10,7 @@ import { useRouter } from 'next/router'
import Tooltip from '@shared/atoms/Tooltip' import Tooltip from '@shared/atoms/Tooltip'
import AvailableNetworks from 'src/components/Publish/AvailableNetworks' import AvailableNetworks from 'src/components/Publish/AvailableNetworks'
import Info from '@images/info.svg' import Info from '@images/info.svg'
import Loader from '@shared/atoms/Loader'
export default function Actions({ export default function Actions({
scrollToRef, scrollToRef,
@ -59,6 +60,11 @@ export default function Actions({
(values.user.stepCurrent === 2 && errors.services !== undefined) || (values.user.stepCurrent === 2 && errors.services !== undefined) ||
(values.user.stepCurrent === 3 && errors.pricing !== undefined) (values.user.stepCurrent === 3 && errors.pricing !== undefined)
const hasSubmitError =
values.feedback?.[1].status === 'error' ||
values.feedback?.[2].status === 'error' ||
values.feedback?.[3].status === 'error'
return ( return (
<footer className={styles.actions}> <footer className={styles.actions}>
{did ? ( {did ? (
@ -107,7 +113,13 @@ export default function Actions({
style="primary" style="primary"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid}
> >
Submit {isSubmitting ? (
<Loader white />
) : hasSubmitError ? (
'Retry'
) : (
'Submit'
)}
</Button> </Button>
)} )}
</> </>

View File

@ -143,13 +143,18 @@ export async function transformPublishFormToDdo(
} }
// this is the default format hardcoded // this is the default format hardcoded
const file = [ const file = {
{ nftAddress,
type: 'url', datatokenAddress,
url: files[0].url, files: [
method: 'GET' {
} type: 'url',
] index: 0,
url: files[0].url,
method: 'GET'
}
]
}
const filesEncrypted = const filesEncrypted =
!isPreview && !isPreview &&
files?.length && files?.length &&
@ -172,7 +177,7 @@ export async function transformPublishFormToDdo(
'@context': ['https://w3id.org/did/v1'], '@context': ['https://w3id.org/did/v1'],
id: did, id: did,
nftAddress, nftAddress,
version: '4.0.0', version: '4.1.0',
chainId, chainId,
metadata: newMetadata, metadata: newMetadata,
services: [newService], services: [newService],

View File

@ -11,7 +11,7 @@ import Actions from './Actions'
import Debug from './Debug' import Debug from './Debug'
import Navigation from './Navigation' import Navigation from './Navigation'
import { Steps } from './Steps' import { Steps } from './Steps'
import { FormPublishData, PublishFeedback } from './_types' import { FormPublishData } from './_types'
import { useUserPreferences } from '@context/UserPreferences' import { useUserPreferences } from '@context/UserPreferences'
import useNftFactory from '@hooks/contracts/useNftFactory' import useNftFactory from '@hooks/contracts/useNftFactory'
import { ProviderInstance, LoggerInstance, DDO } from '@oceanprotocol/lib' import { ProviderInstance, LoggerInstance, DDO } from '@oceanprotocol/lib'
@ -35,157 +35,166 @@ export default function PublishPage({
const nftFactory = useNftFactory() const nftFactory = useNftFactory()
const newAbortController = useAbortController() const newAbortController = useAbortController()
const [feedback, setFeedback] = useState<PublishFeedback>( // This `feedback` state is auto-synced into Formik context under `values.feedback`
initialPublishFeedback // for use in other components. Syncing defined in ./Steps.tsx child component.
) const [feedback, setFeedback] = useState(initialPublishFeedback)
// Collecting output of each publish step, enabling retry of failed steps
const [erc721Address, setErc721Address] = useState<string>() const [erc721Address, setErc721Address] = useState<string>()
const [datatokenAddress, setDatatokenAddress] = useState<string>() const [datatokenAddress, setDatatokenAddress] = useState<string>()
const [ddo, setDdo] = useState<DDO>() const [ddo, setDdo] = useState<DDO>()
const [ddoEncrypted, setDdoEncrypted] = useState<string>() const [ddoEncrypted, setDdoEncrypted] = useState<string>()
const [did, setDid] = useState<string>() const [did, setDid] = useState<string>()
async function handleSubmit(values: FormPublishData) { // --------------------------------------------------
// reset all feedback state // 1. Create NFT & datatokens & create pricing schema
setFeedback(initialPublishFeedback) // --------------------------------------------------
async function create(values: FormPublishData): Promise<{
// -------------------------------------------------- erc721Address: string
// 1. Create NFT & datatokens & create pricing schema datatokenAddress: string
// Wrapped in conditional allowing method to run }> {
// multiple times. setFeedback((prevState) => ({
// -------------------------------------------------- ...prevState,
if (!erc721Address && !datatokenAddress) { '1': {
try { ...prevState['1'],
setFeedback((prevState) => ({ status: 'active',
...prevState, errorMessage: null
'1': {
...prevState['1'],
status: 'active',
txCount: values.pricing.type === 'dynamic' ? 2 : 1,
description: prevState['1'].description
}
}))
const config = getOceanConfig(chainId)
LoggerInstance.log('[publish] using config: ', config)
const { erc721Address, datatokenAddress, txHash } =
await createTokensAndPricing(
values,
accountId,
config,
nftFactory,
web3
)
const isSuccess = Boolean(erc721Address && datatokenAddress && txHash)
setErc721Address(erc721Address)
setDatatokenAddress(datatokenAddress)
LoggerInstance.log('[publish] createTokensAndPricing tx', txHash)
LoggerInstance.log('[publish] erc721Address', erc721Address)
LoggerInstance.log('[publish] datatokenAddress', datatokenAddress)
setFeedback((prevState) => ({
...prevState,
'1': {
...prevState['1'],
status: isSuccess ? 'success' : 'error',
txHash
}
}))
} catch (error) {
LoggerInstance.error('[publish] error', error.message)
if (error.message.length > 65) {
error.message = 'No Token created.'
}
setFeedback((prevState) => ({
...prevState,
'1': {
...prevState['1'],
status: 'error',
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
}
}))
} }
} }))
// --------------------------------------------------
// 2. Construct and encrypt DDO
// Wrapped in conditional allowing method to run
// multiple times.
// --------------------------------------------------
if (!ddoEncrypted) {
try {
setFeedback((prevState) => ({
...prevState,
'2': {
...prevState['2'],
status: 'active'
}
}))
if (!datatokenAddress || !erc721Address)
throw new Error('No NFT or Datatoken received.')
const ddo = await transformPublishFormToDdo(
values,
datatokenAddress,
erc721Address
)
setDdo(ddo)
LoggerInstance.log('[publish] Got new DDO', ddo)
const encryptedResponse = await ProviderInstance.encrypt(
ddo,
values.services[0].providerUrl.url,
newAbortController()
)
const ddoEncrypted = encryptedResponse
setDdoEncrypted(ddoEncrypted)
LoggerInstance.log('[publish] Got encrypted DDO', ddoEncrypted)
setFeedback((prevState) => ({
...prevState,
'2': {
...prevState['2'],
status: ddoEncrypted ? 'success' : 'error'
}
}))
} catch (error) {
LoggerInstance.error('[publish] error', error.message)
setFeedback((prevState) => ({
...prevState,
'2': {
...prevState['2'],
status: 'error',
errorMessage: error.message
}
}))
}
}
// --------------------------------------------------
// 3. Write DDO into NFT metadata
// --------------------------------------------------
try { try {
const config = getOceanConfig(chainId)
LoggerInstance.log('[publish] using config: ', config)
const { erc721Address, datatokenAddress, txHash } =
await createTokensAndPricing(
values,
accountId,
config,
nftFactory,
web3
)
const isSuccess = Boolean(erc721Address && datatokenAddress && txHash)
if (!isSuccess) throw new Error('No Token created. Please try again.')
LoggerInstance.log('[publish] createTokensAndPricing tx', txHash)
LoggerInstance.log('[publish] erc721Address', erc721Address)
LoggerInstance.log('[publish] datatokenAddress', datatokenAddress)
setFeedback((prevState) => ({ setFeedback((prevState) => ({
...prevState, ...prevState,
'3': { '1': {
...prevState['3'], ...prevState['1'],
status: 'active' status: 'success',
txHash
} }
})) }))
if (!ddo || !ddoEncrypted) throw new Error('No DDO received.') return { erc721Address, datatokenAddress }
} catch (error) {
LoggerInstance.error('[publish] error', error.message)
if (error.message.length > 65) {
error.message = 'No Token created. Please try again.'
}
setFeedback((prevState) => ({
...prevState,
'1': {
...prevState['1'],
status: 'error',
errorMessage: error.message
}
}))
}
}
// --------------------------------------------------
// 2. Construct and encrypt DDO
// --------------------------------------------------
async function encrypt(
values: FormPublishData,
erc721Address: string,
datatokenAddress: string
): Promise<{ ddo: DDO; ddoEncrypted: string }> {
setFeedback((prevState) => ({
...prevState,
'2': {
...prevState['2'],
status: 'active',
errorMessage: null
}
}))
try {
if (!datatokenAddress || !erc721Address)
throw new Error('No NFT or Datatoken received. Please try again.')
const ddo = await transformPublishFormToDdo(
values,
datatokenAddress,
erc721Address
)
if (!ddo) throw new Error('No DDO received. Please try again.')
setDdo(ddo)
LoggerInstance.log('[publish] Got new DDO', ddo)
const ddoEncrypted = await ProviderInstance.encrypt(
ddo,
values.services[0].providerUrl.url,
newAbortController()
)
if (!ddoEncrypted)
throw new Error('No encrypted DDO received. Please try again.')
setDdoEncrypted(ddoEncrypted)
LoggerInstance.log('[publish] Got encrypted DDO', ddoEncrypted)
setFeedback((prevState) => ({
...prevState,
'2': {
...prevState['2'],
status: 'success'
}
}))
return { ddo, ddoEncrypted }
} catch (error) {
LoggerInstance.error('[publish] error', error.message)
setFeedback((prevState) => ({
...prevState,
'2': {
...prevState['2'],
status: 'error',
errorMessage: error.message
}
}))
}
}
// --------------------------------------------------
// 3. Write DDO into NFT metadata
// --------------------------------------------------
async function publish(
values: FormPublishData,
ddo: DDO,
ddoEncrypted: string
): Promise<{ did: string }> {
setFeedback((prevState) => ({
...prevState,
'3': {
...prevState['3'],
status: 'active',
errorMessage: null
}
}))
try {
if (!ddo || !ddoEncrypted)
throw new Error('No DDO received. Please try again.')
const res = await setNFTMetadataAndTokenURI( const res = await setNFTMetadataAndTokenURI(
ddo, ddo,
@ -196,7 +205,7 @@ export default function PublishPage({
) )
if (!res?.transactionHash) if (!res?.transactionHash)
throw new Error( throw new Error(
'Metadata could not be written into the NFT. Please hit Submit again to retry.' 'Metadata could not be written into the NFT. Please try again.'
) )
LoggerInstance.log('[publish] setMetadata result', res) LoggerInstance.log('[publish] setMetadata result', res)
@ -210,7 +219,7 @@ export default function PublishPage({
} }
})) }))
setDid(ddo.id) return { did: ddo.id }
} catch (error) { } catch (error) {
LoggerInstance.error('[publish] error', error.message) LoggerInstance.error('[publish] error', error.message)
setFeedback((prevState) => ({ setFeedback((prevState) => ({
@ -224,6 +233,44 @@ export default function PublishPage({
} }
} }
// --------------------------------------------------
// Orchestrate publishing
// --------------------------------------------------
async function handleSubmit(values: FormPublishData) {
// Syncing variables with state, enabling retry of failed steps
let _erc721Address = erc721Address
let _datatokenAddress = datatokenAddress
let _ddo = ddo
let _ddoEncrypted = ddoEncrypted
let _did = did
if (!_erc721Address || !_datatokenAddress) {
const { erc721Address, datatokenAddress } = await create(values)
_erc721Address = erc721Address
_datatokenAddress = datatokenAddress
setErc721Address(erc721Address)
setDatatokenAddress(datatokenAddress)
}
if (!_ddo || !_ddoEncrypted) {
const { ddo, ddoEncrypted } = await encrypt(
values,
_erc721Address,
_datatokenAddress
)
_ddo = ddo
_ddoEncrypted = ddoEncrypted
setDdo(ddo)
setDdoEncrypted(ddoEncrypted)
}
if (!_did) {
const { did } = await publish(values, _ddo, _ddoEncrypted)
_did = did
setDid(did)
}
}
return isInPurgatory && purgatoryData ? null : ( return isInPurgatory && purgatoryData ? null : (
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
@ -233,22 +280,20 @@ export default function PublishPage({
await handleSubmit(values) await handleSubmit(values)
}} }}
> >
{({ values }) => { {({ values }) => (
return ( <>
<> <PageHeader
<PageHeader title={<Title networkId={values.user.chainId} />}
title={<Title networkId={values.user.chainId} />} description={content.description}
description={content.description} />
/> <Form className={styles.form} ref={scrollToRef}>
<Form className={styles.form} ref={scrollToRef}> <Navigation />
<Navigation /> <Steps feedback={feedback} />
<Steps feedback={feedback} /> <Actions scrollToRef={scrollToRef} did={did} />
<Actions scrollToRef={scrollToRef} did={did} /> </Form>
</Form> {debug && <Debug />}
{debug && <Debug />} </>
</> )}
)
}}
</Formik> </Formik>
) )
} }