diff --git a/content/pages/publish.json b/content/pages/publish.json index 11300e99d..1f397cd7a 100644 --- a/content/pages/publish.json +++ b/content/pages/publish.json @@ -100,14 +100,16 @@ "price": { "fixed": { "title": "Fixed", - "info": "Set your price for accessing this data set. A Data Token for this data set, worth the entered amount of OCEAN, will be created." + "info": "Set your price for accessing this data set. A Datatoken for this data set, worth the entered amount of OCEAN, will be created." }, "dynamic": { "title": "Dynamic", - "info": "Let's create a decentralized, automated market for your data set. A Data Token for this data set, worth the entered amount of OCEAN, will be created. Additionally, you will provide liquidity into a Data Token/OCEAN liquidity pool with Balancer.", + "info": "Let's create a decentralized, automated market for your data set. A Datatoken for this data set, worth the entered amount of OCEAN, will be created. Additionally, you will provide liquidity into a OCEAN/Datatoken liquidity pool with Balancer.", "tooltips": { - "poolInfo": "Help me", - "liquidityProviderFee": "Help me" + "poolInfo": "Explain what is going on here...", + "liquidityProviderFee": "Explain liquidity provider fee...", + "communityFee": "Explain community fee...", + "marketplaceFee": "Explain marketplace fee..." } } } diff --git a/src/@types/MetaData.d.ts b/src/@types/MetaData.d.ts index df787238c..8edf13e62 100644 --- a/src/@types/MetaData.d.ts +++ b/src/@types/MetaData.d.ts @@ -4,7 +4,7 @@ import { AdditionalInformation, ServiceMetadata } from '@oceanprotocol/lib' -import { PriceOptions } from '@oceanprotocol/react' +import { PriceOptions, DataTokenOptions } from '@oceanprotocol/react' export interface AdditionalInformationMarket extends AdditionalInformation { links?: File[] @@ -13,7 +13,17 @@ export interface AdditionalInformationMarket extends AdditionalInformation { } export interface MetadataMarket extends Metadata { - additionalInformation: AdditionalInformationMarket + // While required for this market, Aquarius/Plecos will keep this as optional + // allowing external pushes of assets without `additionalInformation`. + // Making it optional here helps safeguarding against those assets. + additionalInformation?: AdditionalInformationMarket +} + +export interface PriceOptionsMarket extends PriceOptions { + // easier to keep this as number for Yup input validation + liquidityProviderFee: number + // collect datatoken info for publishing + datatoken?: DataTokenOptions } export interface MetadataPublishForm { @@ -23,7 +33,7 @@ export interface MetadataPublishForm { files: string | File[] author: string license: string - price: PriceOptions + price: PriceOptionsMarket access: 'Download' | 'Compute' | string termsAndConditions: boolean // ---- optional fields ---- diff --git a/src/components/atoms/Input/InputElement.module.css b/src/components/atoms/Input/InputElement.module.css index 132db0897..902d55ef7 100644 --- a/src/components/atoms/Input/InputElement.module.css +++ b/src/components/atoms/Input/InputElement.module.css @@ -38,6 +38,15 @@ color: var(--brand-grey-light); cursor: not-allowed; pointer-events: none; + /* for hiding spin buttons in Firefox */ + -moz-appearance: textfield; +} + +.input[readonly]::-webkit-inner-spin-button, +.input[disabled]::-webkit-inner-spin-button, +.input[readonly]::-webkit-outer-spin-button, +.input[disabled]::-webkit-outer-spin-button { + display: none; } .select { @@ -207,6 +216,12 @@ font-size: var(--font-size-small); } +.prefix.small, +.postfix.small { + min-height: 34px; + font-size: var(--font-size-mini); +} + .selectSmall { composes: small; height: 34px; diff --git a/src/components/atoms/Input/InputElement.tsx b/src/components/atoms/Input/InputElement.tsx index eeb986242..8e106d005 100644 --- a/src/components/atoms/Input/InputElement.tsx +++ b/src/components/atoms/Input/InputElement.tsx @@ -6,8 +6,12 @@ import FilesInput from '../../molecules/FormFields/FilesInput' import Terms from '../../molecules/FormFields/Terms' import Price from '../../molecules/FormFields/Price' -const DefaultInput = (props: InputProps) => ( - +const DefaultInput = ({ small, ...props }: InputProps) => ( + ) export default function InputElement({ @@ -18,6 +22,10 @@ export default function InputElement({ postfix, small, field, + label, + help, + form, + additionalComponent, ...props }: InputProps): ReactElement { switch (type) { @@ -75,12 +83,30 @@ export default function InputElement({ default: return prefix || postfix ? (
- {prefix &&
{prefix}
} - - {postfix &&
{postfix}
} + {prefix && ( +
+ {prefix} +
+ )} + + {postfix && ( +
+ {postfix} +
+ )}
) : ( - + ) } } diff --git a/src/components/atoms/Input/index.tsx b/src/components/atoms/Input/index.tsx index 7ff57eb36..5e5ad85b6 100644 --- a/src/components/atoms/Input/index.tsx +++ b/src/components/atoms/Input/index.tsx @@ -10,7 +10,7 @@ const cx = classNames.bind(styles) export interface InputProps { name: string - label?: string + label?: string | ReactNode placeholder?: string required?: boolean help?: string @@ -30,6 +30,7 @@ export interface InputProps { multiple?: boolean pattern?: string min?: string + max?: string disabled?: boolean readOnly?: boolean field?: any @@ -53,8 +54,7 @@ export default function Input(props: Partial): ReactElement { } = props const hasError = - props.form?.touched[field.name] && - typeof props.form.errors[field.name] === 'string' + props.form?.touched[field.name] && props.form?.errors[field.name] const styleClasses = cx({ field: true, @@ -71,7 +71,7 @@ export default function Input(props: Partial): ReactElement { - {field && ( + {field && field.name !== 'price' && (
diff --git a/src/components/molecules/FormFields/FilesInput/index.tsx b/src/components/molecules/FormFields/FilesInput/index.tsx index cb0a7259a..9c07727ba 100644 --- a/src/components/molecules/FormFields/FilesInput/index.tsx +++ b/src/components/molecules/FormFields/FilesInput/index.tsx @@ -6,12 +6,8 @@ import FileInput from './Input' import { getFileInfo } from '../../../../utils' import { InputProps } from '../../../atoms/Input' -interface Values { - url: string -} - export default function FilesInput(props: InputProps): ReactElement { - const [field, meta, helpers] = useField(props) + const [field, meta, helpers] = useField(props.name) const [isLoading, setIsLoading] = useState(false) async function handleButtonClick(e: React.SyntheticEvent, url: string) { @@ -36,7 +32,7 @@ export default function FilesInput(props: InputProps): ReactElement { return ( <> - {field && typeof field.value === 'object' ? ( + {field?.value && field.value[0] && typeof field.value === 'object' ? ( ) : ( ) => void generateName?: () => void readOnly?: boolean }): ReactElement { + const [field, meta] = useField(name) + return (
@@ -43,16 +43,16 @@ export default function Coin({
{datatokenOptions?.symbol === 'OCEAN' && ( - + )} +
) diff --git a/src/components/molecules/FormFields/Price/Dynamic.module.css b/src/components/molecules/FormFields/Price/Dynamic.module.css index bc493ef6b..9eb4d843b 100644 --- a/src/components/molecules/FormFields/Price/Dynamic.module.css +++ b/src/components/molecules/FormFields/Price/Dynamic.module.css @@ -49,14 +49,9 @@ } .summary { - text-align: center; margin-top: var(--spacer); } -.summary input { - max-width: 5rem; -} - .alertArea { margin-left: -2rem; margin-right: -2rem; diff --git a/src/components/molecules/FormFields/Price/Dynamic.tsx b/src/components/molecules/FormFields/Price/Dynamic.tsx index a53c01994..fc5b52d89 100644 --- a/src/components/molecules/FormFields/Price/Dynamic.tsx +++ b/src/components/molecules/FormFields/Price/Dynamic.tsx @@ -1,35 +1,33 @@ -import React, { ReactElement, useState, ChangeEvent, useEffect } from 'react' +import React, { ReactElement, useState, useEffect } from 'react' import stylesIndex from './index.module.css' import styles from './Dynamic.module.css' import FormHelp from '../../../atoms/Input/Help' import Wallet from '../../Wallet' -import { DataTokenOptions, PriceOptions, useOcean } from '@oceanprotocol/react' +import { DataTokenOptions, useOcean } from '@oceanprotocol/react' import Alert from '../../../atoms/Alert' import Coin from './Coin' import { isCorrectNetwork } from '../../../../utils/wallet' import { useSiteMetadata } from '../../../../hooks/useSiteMetadata' -import InputElement from '../../../atoms/Input/InputElement' -import Label from '../../../atoms/Input/Label' import Tooltip from '../../../atoms/Tooltip' +import Fees from './Fees' +import { PriceOptionsMarket } from '../../../../@types/MetaData' export default function Dynamic({ ocean, priceOptions, datatokenOptions, - onOceanChange, generateName, content }: { - ocean: string - priceOptions: PriceOptions + ocean: number + priceOptions: PriceOptionsMarket datatokenOptions: DataTokenOptions - onOceanChange: (event: ChangeEvent) => void generateName: () => void content: any }): ReactElement { const { appConfig } = useSiteMetadata() const { account, balance, chainId, refreshBalance } = useOcean() - const { weightOnDataToken, tokensToMint, liquidityProviderFee } = priceOptions + const { weightOnDataToken } = priceOptions const [error, setError] = useState() const correctNetwork = isCorrectNetwork(chainId) @@ -37,14 +35,14 @@ export default function Dynamic({ c.toUpperCase() ) - // Check: account, network & insuffciant balance + // Check: account, network & insufficient balance useEffect(() => { if (!account) { setError(`No account connected. Please connect your Web3 wallet.`) } else if (!correctNetwork) { setError(`Wrong Network. Please connect to ${desiredNetworkName}.`) } else if (Number(balance.ocean) < Number(ocean)) { - setError(`Insufficiant balance. You need at least ${ocean} OCEAN`) + setError(`Insufficient balance. You need at least ${ocean} OCEAN`) } else { setError(undefined) } @@ -76,39 +74,29 @@ export default function Dynamic({

- Data Token Liquidity Pool{' '} - + Datatoken Liquidity Pool

+ +
- - + You will get:
+ 100% share of pool
{error && ( diff --git a/src/components/molecules/FormFields/Price/Error.tsx b/src/components/molecules/FormFields/Price/Error.tsx new file mode 100644 index 000000000..2d25d0816 --- /dev/null +++ b/src/components/molecules/FormFields/Price/Error.tsx @@ -0,0 +1,13 @@ +import { FieldMetaProps } from 'formik' +import React, { ReactElement } from 'react' +import stylesInput from '../../../atoms/Input/index.module.css' + +export default function Error({ + meta +}: { + meta: FieldMetaProps +}): ReactElement { + return meta.error ? ( +
{meta.error}
+ ) : null +} diff --git a/src/components/molecules/FormFields/Price/Fees.module.css b/src/components/molecules/FormFields/Price/Fees.module.css new file mode 100644 index 000000000..5564da7ca --- /dev/null +++ b/src/components/molecules/FormFields/Price/Fees.module.css @@ -0,0 +1,22 @@ +.fees { + display: grid; + gap: var(--spacer); + grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); + margin-left: -2rem; + margin-right: -2rem; + border-bottom: 1px solid var(--brand-grey-lighter); + margin-top: var(--spacer); + padding: 0 var(--spacer) calc(var(--spacer) / 2) var(--spacer); + + justify-content: center; + text-align: center; + border-bottom: 1px solid var(--brand-grey-lighter); +} + +.fees label { + white-space: nowrap; +} + +.fees input { + max-width: 5rem; +} diff --git a/src/components/molecules/FormFields/Price/Fees.tsx b/src/components/molecules/FormFields/Price/Fees.tsx new file mode 100644 index 000000000..2e19db493 --- /dev/null +++ b/src/components/molecules/FormFields/Price/Fees.tsx @@ -0,0 +1,67 @@ +import React, { ReactElement } from 'react' +import Tooltip from '../../../atoms/Tooltip' +import styles from './Fees.module.css' +import { useSiteMetadata } from '../../../../hooks/useSiteMetadata' +import { useField } from 'formik' +import Input from '../../../atoms/Input' +import Error from './Error' + +export default function Fees({ + tooltips +}: { + tooltips: { [key: string]: string } +}): ReactElement { + const { appConfig } = useSiteMetadata() + const [field, meta] = useField('price.liquidityProviderFee') + + return ( + <> +
+ + Liquidity Provider Fee + + + } + type="number" + postfix="%" + min="0.1" + max="0.9" + step="0.1" + small + {...field} + additionalComponent={} + /> + + + Community Fee + + + } + value="0.1" + name="communityFee" + postfix="%" + readOnly + small + /> + + + Marketplace Fee + + + } + value={appConfig.marketFeeAmount} + name="marketplaceFee" + postfix="%" + readOnly + small + /> +
+ + ) +} diff --git a/src/components/molecules/FormFields/Price/Fixed.module.css b/src/components/molecules/FormFields/Price/Fixed.module.css index 4eef6bea7..9d7ca5a15 100644 --- a/src/components/molecules/FormFields/Price/Fixed.module.css +++ b/src/components/molecules/FormFields/Price/Fixed.module.css @@ -2,6 +2,10 @@ composes: content from './index.module.css'; } +.form { + position: relative; +} + @media (min-width: 55rem) { .form { max-width: 12rem; diff --git a/src/components/molecules/FormFields/Price/Fixed.tsx b/src/components/molecules/FormFields/Price/Fixed.tsx index bfffa4112..4ae9454cc 100644 --- a/src/components/molecules/FormFields/Price/Fixed.tsx +++ b/src/components/molecules/FormFields/Price/Fixed.tsx @@ -1,42 +1,49 @@ -import React, { ReactElement, ChangeEvent } from 'react' +import React, { ReactElement } from 'react' import stylesIndex from './index.module.css' import styles from './Fixed.module.css' import FormHelp from '../../../atoms/Input/Help' -import Label from '../../../atoms/Input/Label' -import InputElement from '../../../atoms/Input/InputElement' import Conversion from '../../../atoms/Price/Conversion' import { DataTokenOptions } from '@oceanprotocol/react' import RefreshName from './RefreshName' +import { useField } from 'formik' +import Input from '../../../atoms/Input' +import Error from './Error' export default function Fixed({ - ocean, datatokenOptions, - onChange, generateName, content }: { - ocean: string datatokenOptions: DataTokenOptions - onChange: (event: ChangeEvent) => void generateName: () => void content: any }): ReactElement { + const [field, meta] = useField('price.price') + return (
{content.info}
- - + } /> - +
+ {datatokenOptions && (

diff --git a/src/components/molecules/FormFields/Price/index.module.css b/src/components/molecules/FormFields/Price/index.module.css index 7655b3bbc..22182cdad 100644 --- a/src/components/molecules/FormFields/Price/index.module.css +++ b/src/components/molecules/FormFields/Price/index.module.css @@ -20,6 +20,11 @@ margin-bottom: 0; } +.content [class*='error'] { + text-align: left; + top: 100%; +} + .conversion { width: 100%; display: block; diff --git a/src/components/molecules/FormFields/Price/index.tsx b/src/components/molecules/FormFields/Price/index.tsx index 89cafa62b..e4116fc86 100644 --- a/src/components/molecules/FormFields/Price/index.tsx +++ b/src/components/molecules/FormFields/Price/index.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useState, ChangeEvent, useEffect } from 'react' +import React, { ReactElement, useState, useEffect } from 'react' import { graphql, useStaticQuery } from 'gatsby' import { InputProps } from '../../../atoms/Input' import styles from './index.module.css' @@ -7,7 +7,8 @@ import Fixed from './Fixed' import Dynamic from './Dynamic' import { useField } from 'formik' import { useUserPreferences } from '../../../../providers/UserPreferences' -import { DataTokenOptions, PriceOptions, useOcean } from '@oceanprotocol/react' +import { useOcean } from '@oceanprotocol/react' +import { PriceOptionsMarket } from '../../../../@types/MetaData' const query = graphql` query PriceFieldQuery { @@ -26,6 +27,8 @@ const query = graphql` tooltips { poolInfo liquidityProviderFee + communityFee + marketplaceFee } } } @@ -42,17 +45,10 @@ export default function Price(props: InputProps): ReactElement { const content = data.content.edges[0].node.childPagesJson.price const { ocean } = useOcean() - const [field, meta, helpers] = useField(props) - const priceOptions: PriceOptions = field.value + const [field, meta, helpers] = useField(props.name) + const { price }: PriceOptionsMarket = field.value - const [amountOcean, setAmountOcean] = useState('1') const [tokensToMint, setTokensToMint] = useState() - const [datatokenOptions, setDatatokenOptions] = useState() - - function handleOceanChange(event: ChangeEvent) { - setAmountOcean(event.target.value) - helpers.setValue({ ...field.value, price: event.target.value }) - } function handleTabChange(tabName: string) { const type = tabName.toLowerCase() @@ -61,17 +57,16 @@ export default function Price(props: InputProps): ReactElement { function generateName() { if (!ocean) return - const newDatatokenOptions = ocean.datatokens.generateDtName() - setDatatokenOptions(newDatatokenOptions) + const datatoken = ocean.datatokens.generateDtName() + helpers.setValue({ ...field.value, datatoken }) } // Always update everything when amountOcean changes useEffect(() => { - const tokensToMint = - Number(amountOcean) * Number(priceOptions.weightOnDataToken) + const tokensToMint = Number(price) * Number(field.value.weightOnDataToken) setTokensToMint(tokensToMint) helpers.setValue({ ...field.value, tokensToMint }) - }, [amountOcean]) + }, [price]) // Generate new DT name & symbol useEffect(() => { @@ -83,9 +78,7 @@ export default function Price(props: InputProps): ReactElement { title: content.fixed.title, content: ( @@ -95,10 +88,9 @@ export default function Price(props: InputProps): ReactElement { title: content.dynamic.title, content: ( @@ -111,7 +103,7 @@ export default function Price(props: InputProps): ReactElement { {debug === true && (
-          {JSON.stringify(field.value)}
+          {JSON.stringify(field.value, null, 2)}
         
)}

diff --git a/src/components/organisms/AssetActions/Pool/Add.tsx b/src/components/organisms/AssetActions/Pool/Add.tsx index abdec6aab..cfe9e1bea 100644 --- a/src/components/organisms/AssetActions/Pool/Add.tsx +++ b/src/components/organisms/AssetActions/Pool/Add.tsx @@ -16,17 +16,18 @@ export default function Add({ setShowAdd, poolAddress, totalPoolTokens, - totalBalance + totalBalance, + liquidityProviderFee }: { setShowAdd: (show: boolean) => void poolAddress: string totalPoolTokens: string totalBalance: Balance + liquidityProviderFee: string }): ReactElement { const { debug } = useUserPreferences() const { ocean, accountId, balance } = useOcean() const [amount, setAmount] = useState('') - const [swapFee, setSwapFee] = useState() const [txId, setTxId] = useState('') const [isLoading, setIsLoading] = useState() @@ -38,14 +39,6 @@ export default function Add({ totalBalance && ((Number(newPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(2) - useEffect(() => { - async function getFee() { - const swapFee = await ocean.pool.getSwapFee(accountId, poolAddress) - setSwapFee(swapFee) - } - getFee() - }, []) - async function handleAddLiquidity() { setIsLoading(true) @@ -96,7 +89,7 @@ export default function Add({

You will earn

- + of each pool transaction
diff --git a/src/components/organisms/AssetActions/Pool/index.tsx b/src/components/organisms/AssetActions/Pool/index.tsx index 02197a1d9..57b753ac7 100644 --- a/src/components/organisms/AssetActions/Pool/index.tsx +++ b/src/components/organisms/AssetActions/Pool/index.tsx @@ -33,6 +33,7 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement { const [totalBalance, setTotalBalance] = useState() const [dtSymbol, setDtSymbol] = useState() const [userBalance, setUserBalance] = useState() + const [liquidityProviderFee, setLiquidityProviderFee] = useState() const [showAdd, setShowAdd] = useState(false) const [showRemove, setShowRemove] = useState(false) @@ -103,6 +104,13 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement { } setUserBalance(userBalance) + + // Get liquidity provider fee + const liquidityProviderFee = await ocean.pool.getSwapFee( + accountId, + price.address + ) + setLiquidityProviderFee(liquidityProviderFee) } catch (error) { console.error(error.message) } finally { @@ -122,6 +130,7 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement { poolAddress={price.address} totalPoolTokens={totalPoolTokens} totalBalance={totalBalance} + liquidityProviderFee={liquidityProviderFee} /> ) : showRemove ? ( - Data Token + Datatoken @@ -168,6 +177,10 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement { {debug === true && ( )} + diff --git a/src/components/organisms/AssetContent/MetaFull.tsx b/src/components/organisms/AssetContent/MetaFull.tsx index c6cb7c1fb..707f3eeca 100644 --- a/src/components/organisms/AssetContent/MetaFull.tsx +++ b/src/components/organisms/AssetContent/MetaFull.tsx @@ -56,7 +56,7 @@ export default function MetaFull({ /> {dtName ? `${dtName} - ${dtSymbol}` : {dataToken}} diff --git a/src/components/organisms/AssetContent/MetaSecondary.tsx b/src/components/organisms/AssetContent/MetaSecondary.tsx index cf4619628..152333fa0 100644 --- a/src/components/organisms/AssetContent/MetaSecondary.tsx +++ b/src/components/organisms/AssetContent/MetaSecondary.tsx @@ -10,7 +10,6 @@ export default function MetaSecondary({ }: { metadata: MetadataMarket }): ReactElement { - console.log(metadata) return (