mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge pull request #967 from oceanprotocol/feature/publish
Publish flow changes
This commit is contained in:
commit
7d4d526d21
24
.eslintrc
24
.eslintrc
@ -2,11 +2,20 @@
|
|||||||
"extends": ["eslint:recommended", "prettier"],
|
"extends": ["eslint:recommended", "prettier"],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"ecmaFeatures": { "jsx": true }
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true,
|
||||||
|
"es2020": true,
|
||||||
|
"jest": true
|
||||||
},
|
},
|
||||||
"env": { "browser": true, "node": true, "es2020": true, "jest": true },
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"react": { "version": "detect" }
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
@ -30,7 +39,14 @@
|
|||||||
"react/jsx-no-bind": "off",
|
"react/jsx-no-bind": "off",
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"no-use-before-define": "off",
|
"no-use-before-define": "off",
|
||||||
"@typescript-eslint/no-use-before-define": "error"
|
"@typescript-eslint/no-use-before-define": "error",
|
||||||
|
"prefer-destructuring": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"object": true,
|
||||||
|
"array": false
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -128,7 +128,7 @@
|
|||||||
"label": "Access Type",
|
"label": "Access Type",
|
||||||
"help": "Choose how you want your files to be accessible for the specified price.",
|
"help": "Choose how you want your files to be accessible for the specified price.",
|
||||||
"type": "boxSelection",
|
"type": "boxSelection",
|
||||||
"options": ["Download", "Compute"],
|
"options": ["Access", "Compute"],
|
||||||
"required": true,
|
"required": true,
|
||||||
"disclaimer": "Please do not provide downloadable personal data without the consent of the data subjects.",
|
"disclaimer": "Please do not provide downloadable personal data without the consent of the data subjects.",
|
||||||
"disclaimerValues": ["Download"]
|
"disclaimerValues": ["Download"]
|
||||||
|
11
package-lock.json
generated
11
package-lock.json
generated
@ -35,7 +35,6 @@
|
|||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"is-url-superb": "^6.1.0",
|
"is-url-superb": "^6.1.0",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"js-sha256": "^0.9.0",
|
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.omit": "^4.5.0",
|
"lodash.omit": "^4.5.0",
|
||||||
@ -14512,11 +14511,6 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/js-sha256": {
|
|
||||||
"version": "0.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
|
|
||||||
"integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
|
|
||||||
},
|
|
||||||
"node_modules/js-sha3": {
|
"node_modules/js-sha3": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
|
||||||
@ -33620,11 +33614,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz",
|
||||||
"integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw=="
|
"integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw=="
|
||||||
},
|
},
|
||||||
"js-sha256": {
|
|
||||||
"version": "0.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
|
|
||||||
"integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
|
|
||||||
},
|
|
||||||
"js-sha3": {
|
"js-sha3": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
|
||||||
|
@ -45,7 +45,6 @@
|
|||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"is-url-superb": "^6.1.0",
|
"is-url-superb": "^6.1.0",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"js-sha256": "^0.9.0",
|
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.omit": "^4.5.0",
|
"lodash.omit": "^4.5.0",
|
||||||
|
20
src/@hooks/contracts/useNftFactory.ts
Normal file
20
src/@hooks/contracts/useNftFactory.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { NftFactory } from '@oceanprotocol/lib'
|
||||||
|
import { useWeb3 } from '@context/Web3'
|
||||||
|
import { getOceanConfig } from '@utils/ocean'
|
||||||
|
|
||||||
|
function useNftFactory(): NftFactory {
|
||||||
|
const { web3, chainId } = useWeb3()
|
||||||
|
const [nftFactory, setNftFactory] = useState<NftFactory>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!web3 || !chainId) return
|
||||||
|
const config = getOceanConfig(chainId)
|
||||||
|
const factory = new NftFactory(config.erc721FactoryAddress, web3)
|
||||||
|
setNftFactory(factory)
|
||||||
|
}, [web3, chainId])
|
||||||
|
|
||||||
|
return nftFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useNftFactory
|
@ -43,36 +43,36 @@ function useConsume(): UseConsume {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setStep(0)
|
setStep(0)
|
||||||
if (!orderId) {
|
// if (!orderId) {
|
||||||
// if we don't have a previous valid order, get one
|
// if we don't have a previous valid order, get one
|
||||||
// const userOwnedTokens = await ocean.accounts.getTokenBalance(
|
// const userOwnedTokens = await ocean.accounts.getTokenBalance(
|
||||||
// dataTokenAddress,
|
// dataTokenAddress,
|
||||||
// account
|
// account
|
||||||
// )
|
// )
|
||||||
// if (parseFloat(userOwnedTokens) < 1) {
|
// if (parseFloat(userOwnedTokens) < 1) {
|
||||||
// setConsumeError('Not enough datatokens')
|
// setConsumeError('Not enough datatokens')
|
||||||
// return 'Not enough datatokens'
|
// return 'Not enough datatokens'
|
||||||
// } else {
|
// } else {
|
||||||
// setStep(1)
|
// setStep(1)
|
||||||
// try {
|
// try {
|
||||||
// orderId = await ocean.assets.order(
|
// orderId = await ocean.assets.order(
|
||||||
// did as string,
|
// did as string,
|
||||||
// serviceType,
|
// serviceType,
|
||||||
// accountId,
|
// accountId,
|
||||||
// undefined,
|
// undefined,
|
||||||
// marketFeeAddress,
|
// marketFeeAddress,
|
||||||
// undefined,
|
// undefined,
|
||||||
// null,
|
// null,
|
||||||
// false
|
// false
|
||||||
// )
|
// )
|
||||||
// LoggerInstance.log('ordercreated', orderId)
|
// LoggerInstance.log('ordercreated', orderId)
|
||||||
// setStep(2)
|
// setStep(2)
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// setConsumeError(error.message)
|
// setConsumeError(error.message)
|
||||||
// return error.message
|
// return error.message
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
// }
|
||||||
setStep(3)
|
setStep(3)
|
||||||
// if (orderId)
|
// if (orderId)
|
||||||
// await ocean.assets.download(
|
// await ocean.assets.download(
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import { renderStaticWaves } from './oceanWaves'
|
import { renderStaticWaves } from './oceanWaves'
|
||||||
|
|
||||||
export interface NftOptions {
|
// https://docs.opensea.io/docs/metadata-standards
|
||||||
|
export interface NftMetadata {
|
||||||
name: string
|
name: string
|
||||||
symbol: string
|
symbol: string
|
||||||
description: string
|
description: string
|
||||||
image: string
|
image?: string
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
external_url?: string
|
||||||
|
image_data?: string
|
||||||
|
background_color?: string
|
||||||
|
/* eslint-enable camelcase */
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeSvg(svgString: string): string {
|
function encodeSvg(svgString: string): string {
|
||||||
@ -25,20 +31,23 @@ function encodeSvg(svgString: string): string {
|
|||||||
.replace(/\s+/g, ' ')
|
.replace(/\s+/g, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateNftOptions(): NftOptions {
|
export function generateNftMetadata(): NftMetadata {
|
||||||
// TODO: crop image properly in the end as generated SVG waves are a super-wide image,
|
// TODO: crop image properly in the end as generated SVG waves are a super-wide image,
|
||||||
// and add a filled background deciding on either black or white.
|
// and add a filled background deciding on either black or white.
|
||||||
const image = renderStaticWaves()
|
const image = renderStaticWaves()
|
||||||
// const image = new XMLSerializer().serializeToString(waves)
|
// const image = new XMLSerializer().serializeToString(waves)
|
||||||
// const image = `<svg><path d="M0 10.4304L16.3396 10.4304L8.88727 17.6833L10.2401 19L20 9.5L10.2401 0L8.88727 1.31491L16.3396 8.56959L0 8.56959V10.4304Z" /></svg>`
|
// const image = `<svg><path d="M0 10.4304L16.3396 10.4304L8.88727 17.6833L10.2401 19L20 9.5L10.2401 0L8.88727 1.31491L16.3396 8.56959L0 8.56959V10.4304Z" /></svg>`
|
||||||
|
|
||||||
const newNft: NftOptions = {
|
const newNft: NftMetadata = {
|
||||||
name: 'Ocean Asset v4 NFT',
|
name: 'Ocean Asset v4 NFT',
|
||||||
symbol: 'OCEAN-V4-NFT',
|
symbol: 'OCEAN-V4-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',
|
||||||
|
background_color: '141414', // dark background
|
||||||
// TODO: figure out if also image URI needs base64 encoding
|
// TODO: figure out if also image URI needs base64 encoding
|
||||||
// generated SVG embedded as 'data:image/svg+xml' and encoded characters
|
// generated SVG embedded as 'data:image/svg+xml' and encoded characters
|
||||||
image: `data:image/svg+xml,${encodeSvg(image)}`
|
image_data: `data:image/svg+xml,${encodeSvg(image)}`
|
||||||
// generated SVG embedded as 'data:image/svg+xml;base64'
|
// generated SVG embedded as 'data:image/svg+xml;base64'
|
||||||
// image: `data:image/svg+xml;base64,${window?.btoa(image)}`
|
// image: `data:image/svg+xml;base64,${window?.btoa(image)}`
|
||||||
// image: `data:image/svg+xml;base64,${Buffer.from(image).toString('base64')}`
|
// image: `data:image/svg+xml;base64,${Buffer.from(image).toString('base64')}`
|
||||||
@ -47,15 +56,15 @@ export function generateNftOptions(): NftOptions {
|
|||||||
return newNft
|
return newNft
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateNftCreateData(nftOptions: NftOptions): any {
|
export function generateNftCreateData(nftMetadata: NftMetadata): any {
|
||||||
const nftCreateData = {
|
const nftCreateData = {
|
||||||
name: nftOptions.name,
|
name: nftMetadata.name,
|
||||||
symbol: nftOptions.symbol,
|
symbol: nftMetadata.symbol,
|
||||||
templateIndex: 1,
|
templateIndex: 1,
|
||||||
// TODO: figure out if Buffer.from method is working in browser in final build
|
// TODO: figure out if Buffer.from method is working in browser in final build
|
||||||
// as BTOA is deprecated.
|
// as BTOA is deprecated.
|
||||||
tokenURI: window?.btoa(JSON.stringify(nftOptions))
|
tokenURI: window?.btoa(JSON.stringify(nftMetadata))
|
||||||
// tokenURI: Buffer.from(JSON.stringify(nftOptions)).toString('base64') // should end up as data:application/json;base64
|
// tokenURI: Buffer.from(JSON.stringify(nftMetadata)).toString('base64') // should end up as data:application/json;base64
|
||||||
}
|
}
|
||||||
|
|
||||||
return nftCreateData
|
return nftCreateData
|
||||||
|
@ -16,6 +16,10 @@ export function getOceanConfig(network: string | number): Config {
|
|||||||
? undefined
|
? undefined
|
||||||
: process.env.NEXT_PUBLIC_INFURA_PROJECT_ID
|
: process.env.NEXT_PUBLIC_INFURA_PROJECT_ID
|
||||||
) as Config
|
) as Config
|
||||||
|
|
||||||
|
// TODO: remove hack once address is fixed
|
||||||
|
if (network === 'rinkeby' || network === 4)
|
||||||
|
config.oceanTokenAddress = '0x8967bcf84170c91b0d24d4302c2376283b0b3a07'
|
||||||
return config as Config
|
return config as Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
import axios, { CancelToken, AxiosResponse } from 'axios'
|
import axios, { CancelToken, AxiosResponse, Method } from 'axios'
|
||||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
import {
|
||||||
|
FileMetadata,
|
||||||
|
LoggerInstance,
|
||||||
|
ProviderInstance
|
||||||
|
} from '@oceanprotocol/lib'
|
||||||
|
|
||||||
export interface FileMetadata {
|
export async function getEncryptedFiles(
|
||||||
index: number
|
files: FileMetadata[],
|
||||||
valid: boolean
|
providerUrl: string
|
||||||
contentType: string
|
|
||||||
contentLength: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getEncryptedFileUrls(
|
|
||||||
files: string[],
|
|
||||||
providerUrl: string,
|
|
||||||
did: string,
|
|
||||||
accountId: string
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
// https://github.com/oceanprotocol/provider/blob/v4main/API.md#encrypt-endpoint
|
// https://github.com/oceanprotocol/provider/blob/v4main/API.md#encrypt-endpoint
|
||||||
const url = `${providerUrl}/api/v1/services/encrypt`
|
console.log('start encr')
|
||||||
const response: AxiosResponse<{ encryptedDocument: string }> =
|
const response = await ProviderInstance.encrypt(
|
||||||
await axios.post(url, {
|
files,
|
||||||
documentId: did,
|
providerUrl,
|
||||||
signature: '', // TODO: add signature
|
(httpMethod: Method, url: string, body: string, headers: any) => {
|
||||||
publisherAddress: accountId,
|
return axios(url, {
|
||||||
document: files
|
method: httpMethod,
|
||||||
})
|
data: body,
|
||||||
return response?.data?.encryptedDocument
|
headers: headers
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
console.log('encr eres', response)
|
||||||
|
return response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing json: ' + error.message)
|
console.error('Error parsing json: ' + error.message)
|
||||||
}
|
}
|
||||||
@ -36,9 +36,12 @@ export async function getFileInfo(
|
|||||||
cancelToken: CancelToken
|
cancelToken: CancelToken
|
||||||
): Promise<FileMetadata[]> {
|
): Promise<FileMetadata[]> {
|
||||||
try {
|
try {
|
||||||
const postBody = { url }
|
// TODO: what was the point of this?
|
||||||
|
// if (url instanceof DID) postBody = { did: url.getDid() }
|
||||||
|
// else postBody = { url }
|
||||||
|
const postBody = { url, type: 'url' }
|
||||||
const response: AxiosResponse<FileMetadata[]> = await axios.post(
|
const response: AxiosResponse<FileMetadata[]> = await axios.post(
|
||||||
`${providerUrl}/api/v1/services/fileinfo`,
|
`${providerUrl}/api/services/fileinfo`,
|
||||||
postBody,
|
postBody,
|
||||||
{ cancelToken }
|
{ cancelToken }
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ import classNames from 'classnames/bind'
|
|||||||
import cleanupContentType from '@utils/cleanupContentType'
|
import cleanupContentType from '@utils/cleanupContentType'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import Loader from '@shared/atoms/Loader'
|
import Loader from '@shared/atoms/Loader'
|
||||||
import { FileMetadata } from '@utils/provider'
|
import { FileMetadata } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
const cx = classNames.bind(styles)
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ const cx = classNames.bind(styles)
|
|||||||
|
|
||||||
export interface BoxSelectionOption {
|
export interface BoxSelectionOption {
|
||||||
name: string
|
name: string
|
||||||
|
value?: string
|
||||||
checked: boolean
|
checked: boolean
|
||||||
title: JSX.Element | string
|
title: JSX.Element | string
|
||||||
icon?: JSX.Element
|
icon?: JSX.Element
|
||||||
@ -50,7 +51,7 @@ export default function BoxSelection({
|
|||||||
onChange={(event) => handleChange(event)}
|
onChange={(event) => handleChange(event)}
|
||||||
{...props}
|
{...props}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={option.name}
|
value={option.value ? option.value : option.name}
|
||||||
name={name}
|
name={name}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
|
@ -2,7 +2,7 @@ import React, { ReactElement } from 'react'
|
|||||||
import { prettySize } from './utils'
|
import { prettySize } from './utils'
|
||||||
import cleanupContentType from '@utils/cleanupContentType'
|
import cleanupContentType from '@utils/cleanupContentType'
|
||||||
import styles from './Info.module.css'
|
import styles from './Info.module.css'
|
||||||
import { FileMetadata } from '@utils/provider'
|
import { FileMetadata } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
export default function FileInfo({
|
export default function FileInfo({
|
||||||
file,
|
file,
|
||||||
@ -13,7 +13,7 @@ export default function FileInfo({
|
|||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return (
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info}>
|
||||||
<h3 className={styles.url}>{(file as any).url}</h3>
|
<h3 className={styles.url}>{file.url}</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li className={styles.success}>✓ URL confirmed</li>
|
<li className={styles.success}>✓ URL confirmed</li>
|
||||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Button from '@shared/atoms/Button'
|
import Button from '@shared/atoms/Button'
|
||||||
import { InputProps } from '@shared/FormInput'
|
import { InputProps } from '@shared/FormInput'
|
||||||
import { generateNftOptions } from '@utils/nft'
|
import { generateNftMetadata } from '@utils/nft'
|
||||||
import { useField } from 'formik'
|
import { useField } from 'formik'
|
||||||
import React, { ReactElement, useEffect } from 'react'
|
import React, { ReactElement, useEffect } from 'react'
|
||||||
import Refresh from '@images/refresh.svg'
|
import Refresh from '@images/refresh.svg'
|
||||||
@ -13,14 +13,14 @@ export default function Nft(props: InputProps): ReactElement {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (field.value?.name !== '') return
|
if (field.value?.name !== '') return
|
||||||
|
|
||||||
const nftOptions = generateNftOptions()
|
const nftOptions = generateNftMetadata()
|
||||||
helpers.setValue({ ...nftOptions })
|
helpers.setValue({ ...nftOptions })
|
||||||
}, [field.value?.name])
|
}, [field.value?.name])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.nft}>
|
<div className={styles.nft}>
|
||||||
<figure className={styles.image}>
|
<figure className={styles.image}>
|
||||||
<img src={field?.value?.image} width="128" height="128" />
|
<img src={field?.value?.image_data} width="128" height="128" />
|
||||||
<Button
|
<Button
|
||||||
style="text"
|
style="text"
|
||||||
size="small"
|
size="small"
|
||||||
@ -28,8 +28,8 @@ export default function Nft(props: InputProps): ReactElement {
|
|||||||
title="Generate new image"
|
title="Generate new image"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const nftOptions = generateNftOptions()
|
const nftMetadata = generateNftMetadata()
|
||||||
helpers.setValue({ ...nftOptions })
|
helpers.setValue({ ...nftMetadata })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Refresh />
|
<Refresh />
|
||||||
|
@ -5,7 +5,8 @@ import {
|
|||||||
ComputeOutput,
|
ComputeOutput,
|
||||||
Asset,
|
Asset,
|
||||||
DDO,
|
DDO,
|
||||||
PublisherTrustedAlgorithm
|
PublisherTrustedAlgorithm,
|
||||||
|
FileMetadata
|
||||||
} from '@oceanprotocol/lib'
|
} from '@oceanprotocol/lib'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import Price from '@shared/Price'
|
import Price from '@shared/Price'
|
||||||
@ -14,7 +15,6 @@ import Alert from '@shared/atoms/Alert'
|
|||||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import { usePricing } from '@hooks/usePricing'
|
import { usePricing } from '@hooks/usePricing'
|
||||||
import { useAsset } from '@context/Asset'
|
|
||||||
import {
|
import {
|
||||||
generateBaseQuery,
|
generateBaseQuery,
|
||||||
getFilterTerm,
|
getFilterTerm,
|
||||||
@ -36,7 +36,6 @@ import ComputeJobs from '../../../Profile/History/ComputeJobs'
|
|||||||
import { useCancelToken } from '@hooks/useCancelToken'
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
import { useIsMounted } from '@hooks/useIsMounted'
|
import { useIsMounted } from '@hooks/useIsMounted'
|
||||||
import { SortTermOptions } from '../../../../@types/aquarius/SearchQuery'
|
import { SortTermOptions } from '../../../../@types/aquarius/SearchQuery'
|
||||||
import { FileMetadata } from '@utils/provider'
|
|
||||||
|
|
||||||
export default function Compute({
|
export default function Compute({
|
||||||
ddo,
|
ddo,
|
||||||
|
@ -16,8 +16,7 @@ import { secondsToString } from '@utils/ddo'
|
|||||||
import AlgorithmDatasetsListForCompute from './Compute/AlgorithmDatasetsListForCompute'
|
import AlgorithmDatasetsListForCompute from './Compute/AlgorithmDatasetsListForCompute'
|
||||||
import styles from './Consume.module.css'
|
import styles from './Consume.module.css'
|
||||||
import { useIsMounted } from '@hooks/useIsMounted'
|
import { useIsMounted } from '@hooks/useIsMounted'
|
||||||
import { FileMetadata } from '@utils/provider'
|
import { Asset, FileMetadata } from '@oceanprotocol/lib'
|
||||||
import { Asset } from '@oceanprotocol/lib'
|
|
||||||
|
|
||||||
const previousOrderQuery = gql`
|
const previousOrderQuery = gql`
|
||||||
query PreviousOrder($id: String!, $account: String!) {
|
query PreviousOrder($id: String!, $account: String!) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { ReactElement, useState, useEffect } from 'react'
|
import React, { ReactElement, useState, useEffect } from 'react'
|
||||||
import Compute from './Compute'
|
import Compute from './Compute'
|
||||||
import Consume from './Consume'
|
import Consume from './Consume'
|
||||||
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
|
import { Asset, FileMetadata, LoggerInstance } from '@oceanprotocol/lib'
|
||||||
import Tabs, { TabsItem } from '@shared/atoms/Tabs'
|
import Tabs, { TabsItem } from '@shared/atoms/Tabs'
|
||||||
import { compareAsBN } from '@utils/numbers'
|
import { compareAsBN } from '@utils/numbers'
|
||||||
import Pool from './Pool'
|
import Pool from './Pool'
|
||||||
@ -9,7 +9,7 @@ import Trade from './Trade'
|
|||||||
import { useAsset } from '@context/Asset'
|
import { useAsset } from '@context/Asset'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import Web3Feedback from '@shared/Web3Feedback'
|
import Web3Feedback from '@shared/Web3Feedback'
|
||||||
import { FileMetadata, getFileInfo } from '@utils/provider'
|
import { getFileInfo } from '@utils/provider'
|
||||||
import { getOceanConfig } from '@utils/ocean'
|
import { getOceanConfig } from '@utils/ocean'
|
||||||
import { useCancelToken } from '@hooks/useCancelToken'
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
import { useIsMounted } from '@hooks/useIsMounted'
|
import { useIsMounted } from '@hooks/useIsMounted'
|
||||||
|
@ -39,7 +39,7 @@ export default function Actions({
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
style="primary"
|
style="primary"
|
||||||
disabled={values.user.accountId === '' || !isValid}
|
disabled={values.user.accountId === ''}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -90,5 +90,6 @@
|
|||||||
|
|
||||||
.success.current:before {
|
.success.current:before {
|
||||||
background: var(--font-color-heading);
|
background: var(--font-color-heading);
|
||||||
|
color: var(--brand-alert-green);
|
||||||
border-color: var(--brand-alert-green);
|
border-color: var(--brand-alert-green);
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,12 @@ export default function ServicesFields(): ReactElement {
|
|||||||
const { values, setFieldValue, touched, setTouched } =
|
const { values, setFieldValue, touched, setTouched } =
|
||||||
useFormikContext<FormPublishData>()
|
useFormikContext<FormPublishData>()
|
||||||
|
|
||||||
|
// name and title should be download, but option value should be access, probably the best way would be to change the component so that option is an object like {name,value}
|
||||||
const accessTypeOptions = [
|
const accessTypeOptions = [
|
||||||
{
|
{
|
||||||
name: accessTypeOptionsTitles[0].toLowerCase(),
|
name: 'download',
|
||||||
title: accessTypeOptionsTitles[0],
|
value: accessTypeOptionsTitles[0].toLowerCase(),
|
||||||
|
title: 'Download',
|
||||||
icon: <IconDownload />,
|
icon: <IconDownload />,
|
||||||
// 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.
|
||||||
@ -31,6 +33,7 @@ export default function ServicesFields(): ReactElement {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: accessTypeOptionsTitles[1].toLowerCase(),
|
name: accessTypeOptionsTitles[1].toLowerCase(),
|
||||||
|
value: accessTypeOptionsTitles[1].toLowerCase(),
|
||||||
title: accessTypeOptionsTitles[1],
|
title: accessTypeOptionsTitles[1],
|
||||||
icon: <IconCompute />,
|
icon: <IconCompute />,
|
||||||
checked:
|
checked:
|
||||||
@ -55,7 +58,7 @@ export default function ServicesFields(): ReactElement {
|
|||||||
|
|
||||||
setFieldValue(
|
setFieldValue(
|
||||||
'services[0].access',
|
'services[0].access',
|
||||||
values.services[0].algorithmPrivacy === true ? 'compute' : 'download'
|
values.services[0].algorithmPrivacy === true ? 'compute' : 'access'
|
||||||
)
|
)
|
||||||
}, [values.services[0].algorithmPrivacy, setFieldValue])
|
}, [values.services[0].algorithmPrivacy, setFieldValue])
|
||||||
|
|
||||||
|
@ -2,12 +2,17 @@ import { ReactElement, useEffect } from 'react'
|
|||||||
import { useFormikContext } from 'formik'
|
import { useFormikContext } from 'formik'
|
||||||
import { wizardSteps } from './_constants'
|
import { wizardSteps } from './_constants'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import { FormPublishData } from './_types'
|
import { FormPublishData, PublishFeedback } from './_types'
|
||||||
|
|
||||||
export function Steps(): ReactElement {
|
export function Steps({
|
||||||
|
feedback
|
||||||
|
}: {
|
||||||
|
feedback: PublishFeedback
|
||||||
|
}): ReactElement {
|
||||||
const { chainId, accountId } = useWeb3()
|
const { chainId, accountId } = useWeb3()
|
||||||
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
||||||
|
|
||||||
|
// auto-sync user chainId & account into form data values
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!chainId || !accountId) return
|
if (!chainId || !accountId) return
|
||||||
|
|
||||||
@ -15,6 +20,11 @@ export function Steps(): ReactElement {
|
|||||||
setFieldValue('user.accountId', accountId)
|
setFieldValue('user.accountId', accountId)
|
||||||
}, [chainId, accountId, setFieldValue])
|
}, [chainId, accountId, setFieldValue])
|
||||||
|
|
||||||
|
// auto-sync publish feedback into form data values
|
||||||
|
useEffect(() => {
|
||||||
|
setFieldValue('feedback', feedback)
|
||||||
|
}, [feedback, setFieldValue])
|
||||||
|
|
||||||
const { component } = wizardSteps.filter(
|
const { component } = wizardSteps.filter(
|
||||||
(stepContent) => stepContent.step === values.user.stepCurrent
|
(stepContent) => stepContent.step === values.user.stepCurrent
|
||||||
)[0]
|
)[0]
|
||||||
|
18
src/components/Publish/Submission/Feedback.tsx
Normal file
18
src/components/Publish/Submission/Feedback.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { ListItem } from '@shared/atoms/Lists'
|
||||||
|
import { useFormikContext } from 'formik'
|
||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
import { FormPublishData } from '../_types'
|
||||||
|
|
||||||
|
export function Feedback(): ReactElement {
|
||||||
|
const { values } = useFormikContext<FormPublishData>()
|
||||||
|
|
||||||
|
const items = Object.entries(values.feedback).map(([key, value], index) => {
|
||||||
|
return (
|
||||||
|
<ListItem ol key={index}>
|
||||||
|
{value.name}
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return <ol>{items}</ol>
|
||||||
|
}
|
@ -2,6 +2,7 @@ import React, { ReactElement } from 'react'
|
|||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import { FormPublishData } from '../_types'
|
import { FormPublishData } from '../_types'
|
||||||
import { useFormikContext } from 'formik'
|
import { useFormikContext } from 'formik'
|
||||||
|
import { Feedback } from './Feedback'
|
||||||
|
|
||||||
export default function Submission(): ReactElement {
|
export default function Submission(): ReactElement {
|
||||||
const { values, handleSubmit } = useFormikContext<FormPublishData>()
|
const { values, handleSubmit } = useFormikContext<FormPublishData>()
|
||||||
@ -12,6 +13,7 @@ export default function Submission(): ReactElement {
|
|||||||
Place to teach about what happens next, output all the steps in background
|
Place to teach about what happens next, output all the steps in background
|
||||||
in some list, after submission continously update this list with the
|
in some list, after submission continously update this list with the
|
||||||
status of the submission.
|
status of the submission.
|
||||||
|
<Feedback />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ export const initialValues: FormPublishData = {
|
|||||||
accountId: ''
|
accountId: ''
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
nft: { name: '', symbol: '', description: '', image: '' },
|
nft: { name: '', symbol: '', description: '', image_data: '' },
|
||||||
type: 'dataset',
|
type: 'dataset',
|
||||||
name: '',
|
name: '',
|
||||||
author: '',
|
author: '',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ServiceComputeOptions } from '@oceanprotocol/lib'
|
import { ServiceComputeOptions } from '@oceanprotocol/lib'
|
||||||
import { DataTokenOptions } from '@utils/datatokens'
|
import { DataTokenOptions } from '@utils/datatokens'
|
||||||
import { NftOptions } from '@utils/nft'
|
import { NftMetadata } from '@utils/nft'
|
||||||
import { ReactElement } from 'react'
|
import { ReactElement } from 'react'
|
||||||
|
|
||||||
interface FileMetadata {
|
interface FileMetadata {
|
||||||
@ -28,7 +28,7 @@ export interface FormPublishData {
|
|||||||
chainId: number
|
chainId: number
|
||||||
}
|
}
|
||||||
metadata: {
|
metadata: {
|
||||||
nft: NftOptions
|
nft: NftMetadata
|
||||||
type: 'dataset' | 'algorithm'
|
type: 'dataset' | 'algorithm'
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
@ -43,6 +43,7 @@ export interface FormPublishData {
|
|||||||
}
|
}
|
||||||
services: FormPublishService[]
|
services: FormPublishService[]
|
||||||
pricing: PriceOptions
|
pricing: PriceOptions
|
||||||
|
feedback?: PublishFeedback
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StepContent {
|
export interface StepContent {
|
||||||
@ -50,3 +51,11 @@ export interface StepContent {
|
|||||||
title: string
|
title: string
|
||||||
component: ReactElement
|
component: ReactElement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PublishFeedback {
|
||||||
|
[key: number]: {
|
||||||
|
name: string
|
||||||
|
status: 'success' | 'error' | 'pending'
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,8 +1,23 @@
|
|||||||
import { DDO, Metadata, Service } from '@oceanprotocol/lib'
|
import {
|
||||||
|
Config,
|
||||||
|
DDO,
|
||||||
|
Erc20CreateParams,
|
||||||
|
FreCreationParams,
|
||||||
|
generateDid,
|
||||||
|
getHash,
|
||||||
|
LoggerInstance,
|
||||||
|
Metadata,
|
||||||
|
NftCreateData,
|
||||||
|
NftFactory,
|
||||||
|
Pool,
|
||||||
|
PoolCreationParams,
|
||||||
|
Service,
|
||||||
|
ZERO_ADDRESS
|
||||||
|
} from '@oceanprotocol/lib'
|
||||||
import { mapTimeoutStringToSeconds } from '@utils/ddo'
|
import { mapTimeoutStringToSeconds } from '@utils/ddo'
|
||||||
import { getEncryptedFileUrls } from '@utils/provider'
|
import { getEncryptedFiles } from '@utils/provider'
|
||||||
import { sha256 } from 'js-sha256'
|
|
||||||
import slugify from 'slugify'
|
import slugify from 'slugify'
|
||||||
|
import Web3 from 'web3'
|
||||||
import {
|
import {
|
||||||
algorithmContainerPresets,
|
algorithmContainerPresets,
|
||||||
MetadataAlgorithmContainer
|
MetadataAlgorithmContainer
|
||||||
@ -66,9 +81,10 @@ export async function transformPublishFormToDdo(
|
|||||||
} = metadata
|
} = metadata
|
||||||
const { access, files, links, providerUrl, timeout } = services[0]
|
const { access, files, links, providerUrl, timeout } = services[0]
|
||||||
|
|
||||||
const did = nftAddress ? `0x${sha256(`${nftAddress}${chainId}`)}` : '0x...'
|
const did = nftAddress ? generateDid(nftAddress, chainId) : '0x...'
|
||||||
const currentTime = dateToStringNoMS(new Date())
|
const currentTime = dateToStringNoMS(new Date())
|
||||||
|
const isPreview = !datatokenAddress && !nftAddress
|
||||||
|
console.log('did', did, isPreview)
|
||||||
// Transform from files[0].url to string[] assuming only 1 file
|
// Transform from files[0].url to string[] assuming only 1 file
|
||||||
const filesTransformed = files?.length && files[0].valid && [files[0].url]
|
const filesTransformed = files?.length && files[0].valid && [files[0].url]
|
||||||
const linksTransformed = links?.length && links[0].valid && [links[0].url]
|
const linksTransformed = links?.length && links[0].valid && [links[0].url]
|
||||||
@ -114,21 +130,24 @@ export async function transformPublishFormToDdo(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
console.log('new meta', newMetadata)
|
||||||
|
|
||||||
// Encrypt just created string[] of urls
|
// this is the default format hardcoded
|
||||||
|
const file = [
|
||||||
|
{
|
||||||
|
type: 'url',
|
||||||
|
url: files[0].url,
|
||||||
|
method: 'GET'
|
||||||
|
}
|
||||||
|
]
|
||||||
const filesEncrypted =
|
const filesEncrypted =
|
||||||
|
!isPreview &&
|
||||||
files?.length &&
|
files?.length &&
|
||||||
files[0].valid &&
|
files[0].valid &&
|
||||||
(await getEncryptedFileUrls(
|
(await getEncryptedFiles(file, providerUrl.url))
|
||||||
filesTransformed,
|
|
||||||
providerUrl.url,
|
|
||||||
did,
|
|
||||||
accountId
|
|
||||||
))
|
|
||||||
|
|
||||||
const newService: Service = {
|
const newService: Service = {
|
||||||
// TODO: give some id
|
id: getHash(datatokenAddress + filesEncrypted),
|
||||||
id: '1',
|
|
||||||
type: access,
|
type: access,
|
||||||
files: filesEncrypted || '',
|
files: filesEncrypted || '',
|
||||||
datatokenAddress,
|
datatokenAddress,
|
||||||
@ -151,10 +170,12 @@ export async function transformPublishFormToDdo(
|
|||||||
// again, we can assume if `datatokenAddress` is not passed,
|
// again, we can assume if `datatokenAddress` is not passed,
|
||||||
// we are on preview.
|
// we are on preview.
|
||||||
...(!datatokenAddress && {
|
...(!datatokenAddress && {
|
||||||
dataTokenInfo: {
|
datatokens: [
|
||||||
name: values.services[0].dataTokenOptions.name,
|
{
|
||||||
symbol: values.services[0].dataTokenOptions.symbol
|
name: values.services[0].dataTokenOptions.name,
|
||||||
},
|
symbol: values.services[0].dataTokenOptions.symbol
|
||||||
|
}
|
||||||
|
],
|
||||||
nft: {
|
nft: {
|
||||||
owner: accountId
|
owner: accountId
|
||||||
}
|
}
|
||||||
@ -163,3 +184,132 @@ export async function transformPublishFormToDdo(
|
|||||||
|
|
||||||
return newDdo
|
return newDdo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createTokensAndPricing(
|
||||||
|
values: FormPublishData,
|
||||||
|
accountId: string,
|
||||||
|
marketFeeAddress: string,
|
||||||
|
config: Config,
|
||||||
|
nftFactory: NftFactory,
|
||||||
|
web3: Web3
|
||||||
|
) {
|
||||||
|
// image not included here for gas fees reasons. It is also an issue to reaserch how we add the image in the nft
|
||||||
|
const nftCreateData: NftCreateData = {
|
||||||
|
name: values.metadata.nft.name,
|
||||||
|
symbol: values.metadata.nft.symbol,
|
||||||
|
// tokenURI: values.metadata.nft.image_data,
|
||||||
|
tokenURI: '',
|
||||||
|
templateIndex: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: cap is hardcoded for now to 1000, this needs to be discussed at some point
|
||||||
|
// fee is default 0 for now
|
||||||
|
// TODO: templateIndex is hardcoded for now but this is incorrect, in the future it should be something like 1 for pools, and 2 for fre and free
|
||||||
|
const ercParams: Erc20CreateParams = {
|
||||||
|
templateIndex: values.pricing.type === 'dynamic' ? 1 : 2,
|
||||||
|
minter: accountId,
|
||||||
|
feeManager: accountId,
|
||||||
|
mpFeeAddress: marketFeeAddress,
|
||||||
|
feeToken: config.oceanTokenAddress,
|
||||||
|
feeAmount: `0`,
|
||||||
|
cap: '1000',
|
||||||
|
name: values.services[0].dataTokenOptions.name,
|
||||||
|
symbol: values.services[0].dataTokenOptions.symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
let erc721Address = ''
|
||||||
|
let datatokenAddress = ''
|
||||||
|
|
||||||
|
// TODO: cleaner code for this huge switch !??!?
|
||||||
|
switch (values.pricing.type) {
|
||||||
|
case 'dynamic': {
|
||||||
|
// no vesting in market by default, maybe at a later time , vestingAmount and vestedBlocks are hardcoded
|
||||||
|
// we use only ocean as basetoken
|
||||||
|
// TODO: discuss swapFeeLiquidityProvider, swapFeeMarketPlaceRunner
|
||||||
|
const poolParams: PoolCreationParams = {
|
||||||
|
ssContract: config.sideStakingAddress,
|
||||||
|
basetokenAddress: config.oceanTokenAddress,
|
||||||
|
basetokenSender: config.erc721FactoryAddress,
|
||||||
|
publisherAddress: accountId,
|
||||||
|
marketFeeCollector: marketFeeAddress,
|
||||||
|
poolTemplateAddress: config.poolTemplateAddress,
|
||||||
|
rate: values.pricing.price.toString(),
|
||||||
|
basetokenDecimals: 18,
|
||||||
|
vestingAmount: '0',
|
||||||
|
vestedBlocks: 2726000,
|
||||||
|
initialBasetokenLiquidity: values.pricing.amountOcean.toString(),
|
||||||
|
swapFeeLiquidityProvider: 1e15,
|
||||||
|
swapFeeMarketRunner: 1e15
|
||||||
|
}
|
||||||
|
// the spender in this case is the erc721Factory because we are delegating
|
||||||
|
const pool = new Pool(web3, LoggerInstance)
|
||||||
|
const txApp = await pool.approve(
|
||||||
|
accountId,
|
||||||
|
config.oceanTokenAddress,
|
||||||
|
config.erc721FactoryAddress,
|
||||||
|
'200',
|
||||||
|
false
|
||||||
|
)
|
||||||
|
console.log('aprove', txApp)
|
||||||
|
const result = await nftFactory.createNftErcWithPool(
|
||||||
|
accountId,
|
||||||
|
nftCreateData,
|
||||||
|
ercParams,
|
||||||
|
poolParams
|
||||||
|
)
|
||||||
|
|
||||||
|
erc721Address = result.events.NFTCreated.returnValues[0]
|
||||||
|
datatokenAddress = result.events.TokenCreated.returnValues[0]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'fixed': {
|
||||||
|
const freParams: FreCreationParams = {
|
||||||
|
fixedRateAddress: config.fixedRateExchangeAddress,
|
||||||
|
baseTokenAddress: config.oceanTokenAddress,
|
||||||
|
owner: accountId,
|
||||||
|
marketFeeCollector: marketFeeAddress,
|
||||||
|
baseTokenDecimals: 18,
|
||||||
|
dataTokenDecimals: 18,
|
||||||
|
fixedRate: values.pricing.price.toString(),
|
||||||
|
marketFee: 1e15,
|
||||||
|
withMint: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await nftFactory.createNftErcWithFixedRate(
|
||||||
|
accountId,
|
||||||
|
nftCreateData,
|
||||||
|
ercParams,
|
||||||
|
freParams
|
||||||
|
)
|
||||||
|
|
||||||
|
erc721Address = result.events.NFTCreated.returnValues[0]
|
||||||
|
datatokenAddress = result.events.TokenCreated.returnValues[0]
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'free': {
|
||||||
|
// maxTokens - how many tokens cand be dispensed when someone requests . If maxTokens=2 then someone can't request 3 in one tx
|
||||||
|
// maxBalance - how many dt the user has in it's wallet before the dispenser will not dispense dt
|
||||||
|
// both will be just 1 for the market
|
||||||
|
const dispenserParams = {
|
||||||
|
dispenserAddress: config.dispenserAddress,
|
||||||
|
maxTokens: web3.utils.toWei('1'),
|
||||||
|
maxBalance: web3.utils.toWei('1'),
|
||||||
|
withMint: true,
|
||||||
|
allowedSwapper: ZERO_ADDRESS
|
||||||
|
}
|
||||||
|
const result = await nftFactory.createNftErcWithDispenser(
|
||||||
|
accountId,
|
||||||
|
nftCreateData,
|
||||||
|
ercParams,
|
||||||
|
dispenserParams
|
||||||
|
)
|
||||||
|
erc721Address = result.events.NFTCreated.returnValues[0]
|
||||||
|
datatokenAddress = result.events.TokenCreated.returnValues[0]
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { erc721Address, datatokenAddress }
|
||||||
|
}
|
||||||
|
@ -46,7 +46,7 @@ const validationService = {
|
|||||||
}),
|
}),
|
||||||
timeout: Yup.string().required('Required'),
|
timeout: Yup.string().required('Required'),
|
||||||
access: Yup.string()
|
access: Yup.string()
|
||||||
.matches(/compute|download/g)
|
.matches(/compute|access/g)
|
||||||
.required('Required'),
|
.required('Required'),
|
||||||
providerUrl: Yup.object().shape({
|
providerUrl: Yup.object().shape({
|
||||||
url: Yup.string().url('Must be a valid URL.').required('Required'),
|
url: Yup.string().url('Must be a valid URL.').required('Required'),
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import React, { ReactElement, useState, useRef } from 'react'
|
import React, { ReactElement, useState, useRef } from 'react'
|
||||||
import { Form, Formik, validateYupSchema } from 'formik'
|
import { Form, Formik } from 'formik'
|
||||||
import { initialValues } from './_constants'
|
import { initialValues } from './_constants'
|
||||||
import { validationSchema } from './_validation'
|
|
||||||
import { useAccountPurgatory } from '@hooks/useAccountPurgatory'
|
import { useAccountPurgatory } from '@hooks/useAccountPurgatory'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import { transformPublishFormToDdo } from './_utils'
|
import { createTokensAndPricing, transformPublishFormToDdo } from './_utils'
|
||||||
import PageHeader from '@shared/Page/PageHeader'
|
import PageHeader from '@shared/Page/PageHeader'
|
||||||
import Title from './Title'
|
import Title from './Title'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
@ -12,10 +11,15 @@ 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 } from './_types'
|
import { FormPublishData, PublishFeedback } from './_types'
|
||||||
import { sha256 } from 'js-sha256'
|
|
||||||
import { generateNftCreateData } from '@utils/nft'
|
|
||||||
import { useUserPreferences } from '@context/UserPreferences'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
|
import useNftFactory from '@hooks/contracts/useNftFactory'
|
||||||
|
import { Nft, getHash, ProviderInstance } from '@oceanprotocol/lib'
|
||||||
|
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||||
|
import axios, { Method } from 'axios'
|
||||||
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
|
import { getOceanConfig } from '@utils/ocean'
|
||||||
|
import { validationSchema } from './_validation'
|
||||||
|
|
||||||
// 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'
|
||||||
@ -26,58 +30,211 @@ export default function PublishPage({
|
|||||||
content: { title: string; description: string; warning: string }
|
content: { title: string; description: string; warning: string }
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { debug } = useUserPreferences()
|
const { debug } = useUserPreferences()
|
||||||
const { accountId, chainId } = useWeb3()
|
const { accountId, web3, chainId } = useWeb3()
|
||||||
const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)
|
const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)
|
||||||
|
|
||||||
// TODO: success & error states need to be handled for each step we want to display
|
|
||||||
// most likely with a nested data structure.
|
|
||||||
const [success, setSuccess] = useState<string>()
|
|
||||||
const [error, setError] = useState<string>()
|
|
||||||
const scrollToRef = useRef()
|
const scrollToRef = useRef()
|
||||||
|
const { appConfig } = useSiteMetadata()
|
||||||
|
const nftFactory = useNftFactory()
|
||||||
|
const newCancelToken = useCancelToken()
|
||||||
|
|
||||||
|
const [feedback, setFeedback] = useState<PublishFeedback>({
|
||||||
|
1: {
|
||||||
|
name: 'Create Tokens & Pricing',
|
||||||
|
status: 'pending'
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
name: 'Encrypt DDO',
|
||||||
|
status: 'pending'
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
name: 'Publish DDO',
|
||||||
|
status: 'pending'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
async function handleSubmit(values: FormPublishData) {
|
async function handleSubmit(values: FormPublishData) {
|
||||||
|
let _erc721Address, _datatokenAddress, _ddo, _encryptedDdo
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 1. Create NFT & datatokens & create pricing schema
|
||||||
|
// --------------------------------------------------
|
||||||
try {
|
try {
|
||||||
// --------------------------------------------------
|
const config = getOceanConfig(chainId)
|
||||||
// 1. Mint NFT & datatokens & put in pool
|
console.log('config', config)
|
||||||
// --------------------------------------------------
|
|
||||||
// const nftOptions = values.metadata.nft
|
|
||||||
// const nftCreateData = generateNftCreateData(nftOptions)
|
|
||||||
|
|
||||||
// TODO: figure out syntax of ercParams we most likely need to pass
|
const { erc721Address, datatokenAddress } = await createTokensAndPricing(
|
||||||
// to createNftWithErc() as we need to pass options for the datatoken.
|
values,
|
||||||
// const ercParams = {}
|
accountId,
|
||||||
// const priceOptions = {
|
appConfig.marketFeeAddress,
|
||||||
// // swapFee is tricky: to get 0.1% you need to send 0.001 as value
|
config,
|
||||||
// swapFee: `${values.pricing.swapFee / 100}`
|
nftFactory,
|
||||||
// }
|
web3
|
||||||
// const txMint = await createNftWithErc(accountId, nftCreateData)
|
)
|
||||||
|
|
||||||
// TODO: figure out how to get nftAddress & datatokenAddress from tx log.
|
const isSuccess = erc721Address && datatokenAddress
|
||||||
// const { nftAddress, datatokenAddress } = txMint.logs[0].args
|
_erc721Address = erc721Address
|
||||||
// if (!nftAddress || !datatokenAddress) { throw new Error() }
|
_datatokenAddress = datatokenAddress
|
||||||
//
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 2. Construct and publish DDO
|
|
||||||
// --------------------------------------------------
|
|
||||||
// const did = sha256(`${nftAddress}${chainId}`)
|
|
||||||
// const ddo = transformPublishFormToDdo(values, datatokenAddress, nftAddress)
|
|
||||||
// const txPublish = await publish(ddo)
|
|
||||||
//
|
|
||||||
// --------------------------------------------------
|
|
||||||
// 3. Integrity check of DDO before & after publishing
|
|
||||||
// --------------------------------------------------
|
|
||||||
// const checksumBefore = sha256(ddo)
|
|
||||||
// const ddoFromChain = await getDdoFromChain(ddo.id)
|
|
||||||
// const ddoFromChainDecrypted = await decryptDdo(ddoFromChain)
|
|
||||||
// const checksumAfter = sha256(ddoFromChainDecrypted)
|
|
||||||
|
|
||||||
// if (checksumBefore !== checksumAfter) {
|
setFeedback({
|
||||||
// throw new Error('DDO integrity check failed!')
|
...feedback,
|
||||||
// }
|
1: {
|
||||||
setSuccess('Your DDO was published successfully!')
|
...feedback[1],
|
||||||
|
status: isSuccess ? 'success' : 'error'
|
||||||
|
}
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error.message)
|
console.error('error', error.message)
|
||||||
|
setFeedback({
|
||||||
|
...feedback,
|
||||||
|
1: {
|
||||||
|
...feedback[1],
|
||||||
|
status: 'error',
|
||||||
|
message: error.message
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 2. Construct and encypt DDO
|
||||||
|
// --------------------------------------------------
|
||||||
|
try {
|
||||||
|
const ddo = await transformPublishFormToDdo(
|
||||||
|
values,
|
||||||
|
_datatokenAddress,
|
||||||
|
_erc721Address
|
||||||
|
)
|
||||||
|
|
||||||
|
_ddo = ddo
|
||||||
|
|
||||||
|
const encryptedResponse = await ProviderInstance.encrypt(
|
||||||
|
ddo,
|
||||||
|
values.services[0].providerUrl.url,
|
||||||
|
(httpMethod: Method, url: string, body: string, headers: any) => {
|
||||||
|
return axios(url, {
|
||||||
|
method: httpMethod,
|
||||||
|
data: body,
|
||||||
|
headers: headers,
|
||||||
|
cancelToken: newCancelToken()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const encryptedDdo = encryptedResponse.data
|
||||||
|
|
||||||
|
_encryptedDdo = encryptedDdo
|
||||||
|
|
||||||
|
console.log('ddo', JSON.stringify(ddo))
|
||||||
|
|
||||||
|
setFeedback({
|
||||||
|
...feedback,
|
||||||
|
2: {
|
||||||
|
...feedback[2],
|
||||||
|
status: encryptedDdo ? 'success' : 'error'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('error', error.message)
|
||||||
|
setFeedback({
|
||||||
|
...feedback,
|
||||||
|
2: {
|
||||||
|
...feedback[2],
|
||||||
|
status: 'error',
|
||||||
|
message: error.message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 3. Publish DDO
|
||||||
|
// --------------------------------------------------
|
||||||
|
try {
|
||||||
|
// TODO: this whole setMetadata needs to go in a function ,too many hardcoded/calculated params
|
||||||
|
// TODO: hash generation : this needs to be moved in a function (probably on ocean.js) after we figure out what is going on in provider, leave it here for now
|
||||||
|
const metadataHash = getHash(JSON.stringify(_ddo))
|
||||||
|
const nft = new Nft(web3)
|
||||||
|
|
||||||
|
// theoretically used by aquarius or provider, not implemented yet, will remain hardcoded
|
||||||
|
const flags = '0x2'
|
||||||
|
|
||||||
|
const res = await nft.setMetadata(
|
||||||
|
_erc721Address,
|
||||||
|
accountId,
|
||||||
|
0,
|
||||||
|
values.services[0].providerUrl.url,
|
||||||
|
'',
|
||||||
|
flags,
|
||||||
|
_encryptedDdo,
|
||||||
|
'0x' + metadataHash
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('result', res)
|
||||||
|
|
||||||
|
setFeedback({
|
||||||
|
...feedback,
|
||||||
|
3: {
|
||||||
|
...feedback[3],
|
||||||
|
status: res ? 'success' : 'error'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('error', error.message)
|
||||||
|
setFeedback({
|
||||||
|
...feedback,
|
||||||
|
3: {
|
||||||
|
...feedback[3],
|
||||||
|
status: 'error',
|
||||||
|
message: error.message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 3. Integrity check of DDO before & after publishing
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
// TODO: not sure we want to do this at this step, seems overkill
|
||||||
|
|
||||||
|
// if we want to do this we just need to fetch it from aquarius. If we want to fetch from chain and decrypt, we would have more metamask pop-ups (not UX friendly)
|
||||||
|
// decrypt also validates the checksum
|
||||||
|
|
||||||
|
// TODO: remove the commented lines of code until `setSuccess`, didn't remove them yet because maybe i missed something
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 1. Mint NFT & datatokens & put in pool
|
||||||
|
// --------------------------------------------------
|
||||||
|
// const nftOptions = values.metadata.nft
|
||||||
|
// const nftCreateData = generateNftCreateData(nftOptions)
|
||||||
|
|
||||||
|
// figure out syntax of ercParams we most likely need to pass
|
||||||
|
// to createNftWithErc() as we need to pass options for the datatoken.
|
||||||
|
// const ercParams = {}
|
||||||
|
// const priceOptions = {
|
||||||
|
// // swapFee is tricky: to get 0.1% you need to send 0.001 as value
|
||||||
|
// swapFee: `${values.pricing.swapFee / 100}`
|
||||||
|
// }
|
||||||
|
// const txMint = await createNftWithErc(accountId, nftCreateData)
|
||||||
|
|
||||||
|
// figure out how to get nftAddress & datatokenAddress from tx log.
|
||||||
|
// const { nftAddress, datatokenAddress } = txMint.logs[0].args
|
||||||
|
// if (!nftAddress || !datatokenAddress) { throw new Error() }
|
||||||
|
//
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 2. Construct and publish DDO
|
||||||
|
// --------------------------------------------------
|
||||||
|
// const did = sha256(`${nftAddress}${chainId}`)
|
||||||
|
// const ddo = transformPublishFormToDdo(values, datatokenAddress, nftAddress)
|
||||||
|
// const txPublish = await publish(ddo)
|
||||||
|
//
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 3. Integrity check of DDO before & after publishing
|
||||||
|
// --------------------------------------------------
|
||||||
|
// const checksumBefore = sha256(ddo)
|
||||||
|
// const ddoFromChain = await getDdoFromChain(ddo.id)
|
||||||
|
// const ddoFromChainDecrypted = await decryptDdo(ddoFromChain)
|
||||||
|
// const checksumAfter = sha256(ddoFromChainDecrypted)
|
||||||
|
|
||||||
|
// if (checksumBefore !== checksumAfter) {
|
||||||
|
// throw new Error('DDO integrity check failed!')
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
return isInPurgatory && purgatoryData ? null : (
|
return isInPurgatory && purgatoryData ? null : (
|
||||||
@ -97,7 +254,7 @@ export default function PublishPage({
|
|||||||
/>
|
/>
|
||||||
<Form className={styles.form} ref={scrollToRef}>
|
<Form className={styles.form} ref={scrollToRef}>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
<Steps />
|
<Steps feedback={feedback} />
|
||||||
<Actions scrollToRef={scrollToRef} />
|
<Actions scrollToRef={scrollToRef} />
|
||||||
</Form>
|
</Form>
|
||||||
{debug && <Debug />}
|
{debug && <Debug />}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user