diff --git a/src/components/@shared/FormFields/FilesInput/index.tsx b/src/components/@shared/FormFields/FilesInput/index.tsx index 5098993e5..527fb1f38 100644 --- a/src/components/@shared/FormFields/FilesInput/index.tsx +++ b/src/components/@shared/FormFields/FilesInput/index.tsx @@ -6,18 +6,22 @@ import { InputProps } from '@shared/FormInput' import { getFileUrlInfo } from '@utils/provider' import { FormPublishData } from 'src/components/Publish/_types' import { LoggerInstance } from '@oceanprotocol/lib' +import { useAsset } from '@context/Asset' export default function FilesInput(props: InputProps): ReactElement { const [field, meta, helpers] = useField(props.name) const [isLoading, setIsLoading] = useState(false) const { values, setFieldError } = useFormikContext() + const { asset } = useAsset() async function handleValidation(e: React.SyntheticEvent, url: string) { // File example 'https://oceanprotocol.com/tech-whitepaper.pdf' e.preventDefault() try { - const providerUrl = values?.services[0].providerUrl.url + const providerUrl = values?.services + ? values?.services[0].providerUrl.url + : asset.services[0].serviceEndpoint setIsLoading(true) const checkedFile = await getFileUrlInfo(url, providerUrl) diff --git a/src/components/@shared/FormFields/Provider/index.tsx b/src/components/@shared/FormFields/Provider/index.tsx index 7678daf76..a3874c422 100644 --- a/src/components/@shared/FormFields/Provider/index.tsx +++ b/src/components/@shared/FormFields/Provider/index.tsx @@ -5,23 +5,29 @@ import { InputProps } from '@shared/FormInput' import FileInfo from '../FilesInput/Info' import styles from './index.module.css' import Button from '@shared/atoms/Button' -import { initialValues } from 'src/components/Publish/_constants' import { LoggerInstance, ProviderInstance } from '@oceanprotocol/lib' import { FormPublishData } from 'src/components/Publish/_types' +import { getOceanConfig } from '@utils/ocean' +import { useWeb3 } from '@context/Web3' +import axios from 'axios' +import { useCancelToken } from '@hooks/useCancelToken' export default function CustomProvider(props: InputProps): ReactElement { + const { chainId } = useWeb3() + const newCancelToken = useCancelToken() + const { initialValues, setFieldError } = useFormikContext() const [field, meta, helpers] = useField(props.name) const [isLoading, setIsLoading] = useState(false) - const { setFieldError } = useFormikContext() async function handleValidation(e: React.SyntheticEvent) { e.preventDefault() try { setIsLoading(true) + + // Check if provider is a valid provider const isValid = await ProviderInstance.isValidProvider(field.value.url) - // error if something's not right from response // No way to detect a failed request with ProviderInstance.isValidProvider, // making this error show up for multiple cases it shouldn't, like network // down. @@ -30,8 +36,20 @@ export default function CustomProvider(props: InputProps): ReactElement { '✗ No valid provider detected. Check your network, your URL and try again.' ) + // Check if valid provider is for same chain user is on + const providerResponse = await axios.get(field.value.url, { + cancelToken: newCancelToken() + }) + const providerChainId = providerResponse?.data?.chainId + const userChainId = chainId || 1 + + if (providerChainId !== userChainId) + throw Error( + '✗ This provider is incompatible with the network your wallet is connected to.' + ) + // if all good, add provider to formik state - helpers.setValue({ url: field.value.url, valid: isValid }) + helpers.setValue({ url: field.value.url, valid: isValid, custom: true }) } catch (error) { setFieldError(`${field.name}.url`, error.message) LoggerInstance.error(error.message) @@ -41,13 +59,18 @@ export default function CustomProvider(props: InputProps): ReactElement { } function handleFileInfoClose() { - helpers.setValue({ url: '', valid: false }) + helpers.setValue({ url: '', valid: false, custom: true }) helpers.setTouched(false) } function handleDefault(e: React.SyntheticEvent) { e.preventDefault() - helpers.setValue(initialValues.services[0].providerUrl) + + const oceanConfig = getOceanConfig(chainId) + const providerUrl = + oceanConfig?.providerUri || initialValues.services[0].providerUrl.url + + helpers.setValue({ url: providerUrl, valid: true, custom: false }) } return field?.value?.valid === true ? ( diff --git a/src/components/Publish/Services/index.tsx b/src/components/Publish/Services/index.tsx index 2262eaa31..279c34b4f 100644 --- a/src/components/Publish/Services/index.tsx +++ b/src/components/Publish/Services/index.tsx @@ -6,7 +6,6 @@ import IconCompute from '@images/compute.svg' import content from '../../../../content/publish/form.json' import { getFieldContent } from '../_utils' import { FormPublishData } from '../_types' -import { getOceanConfig } from '@utils/ocean' const accessTypeOptionsTitles = getFieldContent( 'access', @@ -15,8 +14,7 @@ const accessTypeOptionsTitles = getFieldContent( export default function ServicesFields(): ReactElement { // connect with Form state, use for conditional field rendering - const { values, setFieldValue, touched, setTouched } = - useFormikContext() + const { values, setFieldValue } = useFormikContext() // 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 = [ @@ -55,15 +53,6 @@ export default function ServicesFields(): ReactElement { ) }, [values.services[0].algorithmPrivacy, setFieldValue]) - // Auto-change default providerUrl on user network change - useEffect(() => { - if (!values?.user?.chainId) return - - const config = getOceanConfig(values.user.chainId) - config && setFieldValue('services[0].providerUrl.url', config.providerUri) - setTouched({ ...touched, services: [{ providerUrl: { url: true } }] }) - }, [values.user.chainId, setFieldValue, setTouched]) - return ( <> () + const { values, setFieldValue, touched, setTouched } = + useFormikContext() // auto-sync user chainId & account into form data values useEffect(() => { @@ -41,7 +43,32 @@ export function Steps({ : initialPublishFeedback['1'].description } }) - }, [values.pricing.type, setFieldValue]) + }, [values.pricing.type, feedback, setFieldValue]) + + // Auto-change default providerUrl on user network change + useEffect(() => { + if ( + !values?.user?.chainId || + values?.services[0]?.providerUrl.custom === true + ) + return + + const config = getOceanConfig(values.user.chainId) + if (config) { + setFieldValue('services[0].providerUrl', { + url: config.providerUri, + valid: true, + custom: false + }) + } + + setTouched({ ...touched, services: [{ providerUrl: { url: true } }] }) + }, [ + values?.user?.chainId, + values?.services[0]?.providerUrl.custom, + setFieldValue, + setTouched + ]) const { component } = wizardSteps.filter( (stepContent) => stepContent.step === values.user.stepCurrent diff --git a/src/components/Publish/_constants.tsx b/src/components/Publish/_constants.tsx index 2ea897630..15460e440 100644 --- a/src/components/Publish/_constants.tsx +++ b/src/components/Publish/_constants.tsx @@ -74,7 +74,8 @@ export const initialValues: FormPublishData = { access: 'access', providerUrl: { url: 'https://provider.mainnet.oceanprotocol.com', - valid: true + valid: true, + custom: false }, computeOptions } diff --git a/src/components/Publish/_types.ts b/src/components/Publish/_types.ts index 1df06f1c1..95c7cfed8 100644 --- a/src/components/Publish/_types.ts +++ b/src/components/Publish/_types.ts @@ -16,7 +16,7 @@ export interface FormPublishService { timeout: string dataTokenOptions: { name: string; symbol: string } access: 'Download' | 'Compute' | string - providerUrl?: { url: string; valid: boolean } + providerUrl: { url: string; valid: boolean; custom: boolean } algorithmPrivacy?: boolean computeOptions?: ServiceComputeOptions } diff --git a/src/components/Publish/_validation.ts b/src/components/Publish/_validation.ts index 93e285ab6..8bf123b63 100644 --- a/src/components/Publish/_validation.ts +++ b/src/components/Publish/_validation.ts @@ -16,6 +16,10 @@ const validationMetadata = { .required('Required'), description: Yup.string() .min(10, (param) => `Description must be at least ${param.min} characters`) + .max( + 5000, + (param) => `Description must have maximum ${param.max} characters` + ) .required('Required'), author: Yup.string().required('Required'), tags: Yup.string().nullable(), @@ -54,7 +58,8 @@ const validationService = { .required('Required'), providerUrl: Yup.object().shape({ url: Yup.string().url('Must be a valid URL.').required('Required'), - valid: Yup.boolean().isTrue().required('Valid Provider is required.') + valid: Yup.boolean().isTrue().required('Valid Provider is required.'), + custom: Yup.boolean() }) } diff --git a/src/components/Publish/index.tsx b/src/components/Publish/index.tsx index 932930151..fbf8e92b4 100644 --- a/src/components/Publish/index.tsx +++ b/src/components/Publish/index.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useState, useRef, useEffect } from 'react' +import React, { ReactElement, useState, useRef } from 'react' import { Form, Formik } from 'formik' import { initialPublishFeedback, initialValues } from './_constants' import { useAccountPurgatory } from '@hooks/useAccountPurgatory' @@ -14,20 +14,14 @@ import { Steps } from './Steps' import { FormPublishData, PublishFeedback } from './_types' import { useUserPreferences } from '@context/UserPreferences' import useNftFactory from '@hooks/contracts/useNftFactory' -import { - Nft, - getHash, - ProviderInstance, - LoggerInstance, - DDO -} from '@oceanprotocol/lib' +import { ProviderInstance, LoggerInstance, DDO } from '@oceanprotocol/lib' import { getOceanConfig } from '@utils/ocean' import { validationSchema } from './_validation' import { useAbortController } from '@hooks/useAbortController' import { setNFTMetadataAndTokenURI } from '@utils/nft' // TODO: restore FormikPersist, add back clear form action -const formName = 'ocean-publish-form' +// const formName = 'ocean-publish-form' export default function PublishPage({ content @@ -225,62 +219,13 @@ export default function PublishPage({ } })) } - - // -------------------------------------------------- - // 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 : ( { + onSubmit={async (values) => { // kick off publishing await handleSubmit(values) }}