mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge pull request #81 from oceanprotocol/feature/fees
Fees input and output
This commit is contained in:
commit
8de48d88e6
@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
16
src/@types/MetaData.d.ts
vendored
16
src/@types/MetaData.d.ts
vendored
@ -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 ----
|
||||
|
@ -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;
|
||||
|
@ -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) => (
|
||||
<input className={styles.input} id={props.name} {...props} />
|
||||
const DefaultInput = ({ small, ...props }: InputProps) => (
|
||||
<input
|
||||
className={`${styles.input} ${small ? styles.small : null}`}
|
||||
id={props.name}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
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 ? (
|
||||
<div className={`${prefix ? styles.prefixGroup : styles.postfixGroup}`}>
|
||||
{prefix && <div className={styles.prefix}>{prefix}</div>}
|
||||
<DefaultInput name={name} type={type || 'text'} {...props} />
|
||||
{postfix && <div className={styles.postfix}>{postfix}</div>}
|
||||
{prefix && (
|
||||
<div className={`${styles.prefix} ${small ? styles.small : ''}`}>
|
||||
{prefix}
|
||||
</div>
|
||||
)}
|
||||
<DefaultInput
|
||||
name={name}
|
||||
type={type || 'text'}
|
||||
small={small}
|
||||
{...props}
|
||||
/>
|
||||
{postfix && (
|
||||
<div className={`${styles.postfix} ${small ? styles.small : ''}`}>
|
||||
{postfix}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<DefaultInput name={name} type={type || 'text'} {...props} />
|
||||
<DefaultInput
|
||||
name={name}
|
||||
type={type || 'text'}
|
||||
small={small}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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<InputProps>): 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<InputProps>): ReactElement {
|
||||
</Label>
|
||||
<InputElement small={small} {...field} {...props} />
|
||||
|
||||
{field && (
|
||||
{field && field.name !== 'price' && (
|
||||
<div className={styles.error}>
|
||||
<ErrorMessage name={field.name} />
|
||||
</div>
|
||||
|
@ -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' ? (
|
||||
<FileInfo file={field.value[0]} removeItem={removeItem} />
|
||||
) : (
|
||||
<FileInput
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ReactElement, ChangeEvent } from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './Coin.module.css'
|
||||
import InputElement from '../../../atoms/Input/InputElement'
|
||||
@ -6,24 +6,24 @@ import { ReactComponent as Logo } from '../../../../images/logo.svg'
|
||||
import Conversion from '../../../atoms/Price/Conversion'
|
||||
import { DataTokenOptions } from '@oceanprotocol/react'
|
||||
import RefreshName from './RefreshName'
|
||||
import { useField } from 'formik'
|
||||
import Error from './Error'
|
||||
|
||||
export default function Coin({
|
||||
datatokenOptions,
|
||||
name,
|
||||
value,
|
||||
weight,
|
||||
onOceanChange,
|
||||
generateName,
|
||||
readOnly
|
||||
}: {
|
||||
datatokenOptions: DataTokenOptions
|
||||
name: string
|
||||
value: string
|
||||
weight: string
|
||||
onOceanChange?: (event: ChangeEvent<HTMLInputElement>) => void
|
||||
generateName?: () => void
|
||||
readOnly?: boolean
|
||||
}): ReactElement {
|
||||
const [field, meta] = useField(name)
|
||||
|
||||
return (
|
||||
<div className={styles.coin}>
|
||||
<figure className={styles.icon}>
|
||||
@ -43,16 +43,16 @@ export default function Coin({
|
||||
|
||||
<div className={styles.data}>
|
||||
<InputElement
|
||||
value={value}
|
||||
name={name}
|
||||
type="number"
|
||||
onChange={onOceanChange}
|
||||
readOnly={readOnly}
|
||||
prefix={datatokenOptions?.symbol || 'DT'}
|
||||
min="1"
|
||||
{...field}
|
||||
/>
|
||||
{datatokenOptions?.symbol === 'OCEAN' && (
|
||||
<Conversion price={value} className={stylesIndex.conversion} />
|
||||
<Conversion price={field.value} className={stylesIndex.conversion} />
|
||||
)}
|
||||
<Error meta={meta} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -49,14 +49,9 @@
|
||||
}
|
||||
|
||||
.summary {
|
||||
text-align: center;
|
||||
margin-top: var(--spacer);
|
||||
}
|
||||
|
||||
.summary input {
|
||||
max-width: 5rem;
|
||||
}
|
||||
|
||||
.alertArea {
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
|
@ -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<HTMLInputElement>) => 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<string>()
|
||||
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({
|
||||
</aside>
|
||||
|
||||
<h4 className={styles.title}>
|
||||
Data Token Liquidity Pool{' '}
|
||||
<Tooltip content={content.tooltips.poolInfo} />
|
||||
Datatoken Liquidity Pool <Tooltip content={content.tooltips.poolInfo} />
|
||||
</h4>
|
||||
|
||||
<div className={styles.tokens}>
|
||||
<Coin
|
||||
name="ocean"
|
||||
name="price.price"
|
||||
datatokenOptions={{ symbol: 'OCEAN', name: 'Ocean Token' }}
|
||||
value={ocean}
|
||||
weight={`${100 - Number(Number(weightOnDataToken) * 10)}%`}
|
||||
onOceanChange={onOceanChange}
|
||||
/>
|
||||
<Coin
|
||||
name="tokensToMint"
|
||||
name="price.tokensToMint"
|
||||
datatokenOptions={datatokenOptions}
|
||||
value={tokensToMint.toString()}
|
||||
weight={`${Number(weightOnDataToken) * 10}%`}
|
||||
generateName={generateName}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Fees tooltips={content.tooltips} />
|
||||
|
||||
<footer className={styles.summary}>
|
||||
<Label htmlFor="liquidityProviderFee">
|
||||
Liquidity Provider Fee{' '}
|
||||
<Tooltip content={content.tooltips.liquidityProviderFee} />
|
||||
</Label>
|
||||
<InputElement
|
||||
value={liquidityProviderFee}
|
||||
name="liquidityProviderFee"
|
||||
readOnly
|
||||
postfix="%"
|
||||
/>
|
||||
You will get: <br />
|
||||
100% share of pool
|
||||
</footer>
|
||||
|
||||
{error && (
|
||||
|
13
src/components/molecules/FormFields/Price/Error.tsx
Normal file
13
src/components/molecules/FormFields/Price/Error.tsx
Normal file
@ -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<any>
|
||||
}): ReactElement {
|
||||
return meta.error ? (
|
||||
<div className={stylesInput.error}>{meta.error}</div>
|
||||
) : null
|
||||
}
|
22
src/components/molecules/FormFields/Price/Fees.module.css
Normal file
22
src/components/molecules/FormFields/Price/Fees.module.css
Normal file
@ -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;
|
||||
}
|
67
src/components/molecules/FormFields/Price/Fees.tsx
Normal file
67
src/components/molecules/FormFields/Price/Fees.tsx
Normal file
@ -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 (
|
||||
<>
|
||||
<div className={styles.fees}>
|
||||
<Input
|
||||
label={
|
||||
<>
|
||||
Liquidity Provider Fee
|
||||
<Tooltip content={tooltips.liquidityProviderFee} />
|
||||
</>
|
||||
}
|
||||
type="number"
|
||||
postfix="%"
|
||||
min="0.1"
|
||||
max="0.9"
|
||||
step="0.1"
|
||||
small
|
||||
{...field}
|
||||
additionalComponent={<Error meta={meta} />}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label={
|
||||
<>
|
||||
Community Fee
|
||||
<Tooltip content={tooltips.communityFee} />
|
||||
</>
|
||||
}
|
||||
value="0.1"
|
||||
name="communityFee"
|
||||
postfix="%"
|
||||
readOnly
|
||||
small
|
||||
/>
|
||||
|
||||
<Input
|
||||
label={
|
||||
<>
|
||||
Marketplace Fee
|
||||
<Tooltip content={tooltips.marketplaceFee} />
|
||||
</>
|
||||
}
|
||||
value={appConfig.marketFeeAmount}
|
||||
name="marketplaceFee"
|
||||
postfix="%"
|
||||
readOnly
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
@ -2,6 +2,10 @@
|
||||
composes: content from './index.module.css';
|
||||
}
|
||||
|
||||
.form {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 55rem) {
|
||||
.form {
|
||||
max-width: 12rem;
|
||||
|
@ -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<HTMLInputElement>) => void
|
||||
generateName: () => void
|
||||
content: any
|
||||
}): ReactElement {
|
||||
const [field, meta] = useField('price.price')
|
||||
|
||||
return (
|
||||
<div className={styles.fixed}>
|
||||
<FormHelp className={stylesIndex.help}>{content.info}</FormHelp>
|
||||
|
||||
<div className={styles.grid}>
|
||||
<div className={styles.form}>
|
||||
<Label htmlFor="ocean">Ocean Token</Label>
|
||||
<InputElement
|
||||
value={ocean}
|
||||
name="ocean"
|
||||
<Input
|
||||
label="Ocean Token"
|
||||
value={field.value}
|
||||
name="price.price"
|
||||
type="number"
|
||||
prefix="OCEAN"
|
||||
onChange={onChange}
|
||||
min="1"
|
||||
{...field}
|
||||
additionalComponent={
|
||||
<Conversion
|
||||
price={field.value}
|
||||
className={stylesIndex.conversion}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Conversion price={ocean} className={stylesIndex.conversion} />
|
||||
<Error meta={meta} />
|
||||
</div>
|
||||
|
||||
{datatokenOptions && (
|
||||
<div className={styles.datatoken}>
|
||||
<h4>
|
||||
|
@ -20,6 +20,11 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.content [class*='error'] {
|
||||
text-align: left;
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.conversion {
|
||||
width: 100%;
|
||||
display: block;
|
||||
|
@ -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<number>()
|
||||
const [datatokenOptions, setDatatokenOptions] = useState<DataTokenOptions>()
|
||||
|
||||
function handleOceanChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
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: (
|
||||
<Fixed
|
||||
ocean={amountOcean}
|
||||
datatokenOptions={datatokenOptions}
|
||||
onChange={handleOceanChange}
|
||||
datatokenOptions={field.value.datatoken}
|
||||
generateName={generateName}
|
||||
content={content.fixed}
|
||||
/>
|
||||
@ -95,10 +88,9 @@ export default function Price(props: InputProps): ReactElement {
|
||||
title: content.dynamic.title,
|
||||
content: (
|
||||
<Dynamic
|
||||
ocean={amountOcean}
|
||||
priceOptions={{ ...priceOptions, tokensToMint }}
|
||||
datatokenOptions={datatokenOptions}
|
||||
onOceanChange={handleOceanChange}
|
||||
ocean={price}
|
||||
priceOptions={{ ...field.value, tokensToMint }}
|
||||
datatokenOptions={field.value.datatoken}
|
||||
generateName={generateName}
|
||||
content={content.dynamic}
|
||||
/>
|
||||
@ -111,7 +103,7 @@ export default function Price(props: InputProps): ReactElement {
|
||||
<Tabs items={tabs} handleTabChange={handleTabChange} />
|
||||
{debug === true && (
|
||||
<pre>
|
||||
<code>{JSON.stringify(field.value)}</code>
|
||||
<code>{JSON.stringify(field.value, null, 2)}</code>
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
|
@ -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<string>()
|
||||
const [txId, setTxId] = useState<string>('')
|
||||
const [isLoading, setIsLoading] = useState<boolean>()
|
||||
|
||||
@ -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({
|
||||
</div>
|
||||
<div>
|
||||
<p>You will earn</p>
|
||||
<Token symbol="%" balance={swapFee} />
|
||||
<Token symbol="%" balance={liquidityProviderFee} />
|
||||
of each pool transaction
|
||||
</div>
|
||||
</div>
|
||||
|
@ -33,6 +33,7 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
||||
const [totalBalance, setTotalBalance] = useState<Balance>()
|
||||
const [dtSymbol, setDtSymbol] = useState<string>()
|
||||
const [userBalance, setUserBalance] = useState<Balance>()
|
||||
const [liquidityProviderFee, setLiquidityProviderFee] = useState<string>()
|
||||
|
||||
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 ? (
|
||||
<Remove
|
||||
@ -144,7 +153,7 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
||||
Pool
|
||||
</EtherscanLink>
|
||||
<EtherscanLink network="rinkeby" path={`token/${ddo.dataToken}`}>
|
||||
Data Token
|
||||
Datatoken
|
||||
</EtherscanLink>
|
||||
</div>
|
||||
</div>
|
||||
@ -168,6 +177,10 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
||||
{debug === true && (
|
||||
<Token symbol="BPT" balance={totalPoolTokens} />
|
||||
)}
|
||||
<Token
|
||||
symbol="% liquidity provider fee"
|
||||
balance={liquidityProviderFee}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -56,7 +56,7 @@ export default function MetaFull({
|
||||
/>
|
||||
|
||||
<MetaItem
|
||||
title="Data Token"
|
||||
title="Datatoken"
|
||||
content={
|
||||
<EtherscanLink network="rinkeby" path={`token/${dataToken}`}>
|
||||
{dtName ? `${dtName} - ${dtSymbol}` : <code>{dataToken}</code>}
|
||||
|
@ -10,7 +10,6 @@ export default function MetaSecondary({
|
||||
}: {
|
||||
metadata: MetadataMarket
|
||||
}): ReactElement {
|
||||
console.log(metadata)
|
||||
return (
|
||||
<aside className={styles.metaSecondary}>
|
||||
{metadata?.additionalInformation?.tags?.length > 0 && (
|
||||
|
@ -11,7 +11,7 @@ import Button from '../../atoms/Button'
|
||||
export default function Preview({
|
||||
values
|
||||
}: {
|
||||
values: MetadataPublishForm
|
||||
values: Partial<MetadataPublishForm>
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className={styles.preview}>
|
||||
|
@ -2,7 +2,7 @@ import React, { ReactElement } from 'react'
|
||||
import { useNavigate } from '@reach/router'
|
||||
import { toast } from 'react-toastify'
|
||||
import { Formik } from 'formik'
|
||||
import { usePublish, DataTokenOptions } from '@oceanprotocol/react'
|
||||
import { usePublish } from '@oceanprotocol/react'
|
||||
import styles from './index.module.css'
|
||||
import PublishForm from './PublishForm'
|
||||
import Web3Feedback from '../../molecules/Wallet/Feedback'
|
||||
@ -11,7 +11,6 @@ import { initialValues, validationSchema } from '../../../models/FormPublish'
|
||||
import { transformPublishFormToMetadata } from './utils'
|
||||
import Preview from './Preview'
|
||||
import { MetadataPublishForm } from '../../../@types/MetaData'
|
||||
// import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
|
||||
@ -20,44 +19,38 @@ export default function PublishPage({
|
||||
}: {
|
||||
content: { form: FormContent }
|
||||
}): ReactElement {
|
||||
// TODO: implement marketFee
|
||||
// const { marketFeeAddress, marketFeeAmount } = useSiteMetadata()
|
||||
const { debug } = useUserPreferences()
|
||||
const { publish, publishError, isLoading, publishStepText } = usePublish()
|
||||
const navigate = useNavigate()
|
||||
|
||||
async function handleSubmit(
|
||||
values: MetadataPublishForm,
|
||||
values: Partial<MetadataPublishForm>,
|
||||
resetForm: () => void
|
||||
): Promise<void> {
|
||||
const metadata = transformPublishFormToMetadata(values)
|
||||
const priceOptions = values.price
|
||||
const { price } = values
|
||||
const serviceType = values.access === 'Download' ? 'access' : 'compute'
|
||||
let datatokenOptions: DataTokenOptions
|
||||
|
||||
try {
|
||||
Logger.log('Publish with ', priceOptions, serviceType, datatokenOptions)
|
||||
Logger.log('Publish with ', price, serviceType, price.datatoken)
|
||||
|
||||
const ddo = await publish(
|
||||
metadata as any,
|
||||
priceOptions,
|
||||
{
|
||||
...price,
|
||||
liquidityProviderFee: `${price.liquidityProviderFee}`
|
||||
},
|
||||
serviceType,
|
||||
datatokenOptions
|
||||
price.datatoken
|
||||
)
|
||||
|
||||
if (publishError) {
|
||||
toast.error(publishError)
|
||||
console.error(publishError)
|
||||
toast.error(publishError) && console.error(publishError)
|
||||
return null
|
||||
}
|
||||
|
||||
// User feedback and redirect to new asset detail page
|
||||
ddo && toast.success('Asset created successfully.')
|
||||
|
||||
// reset form state
|
||||
// TODO: verify persistant form in localStorage is cleared with it too
|
||||
resetForm()
|
||||
|
||||
ddo && toast.success('Asset created successfully.') && resetForm()
|
||||
// Go to new asset detail page
|
||||
navigate(`/asset/${ddo.id}`)
|
||||
} catch (error) {
|
||||
|
@ -3,7 +3,7 @@ import { toStringNoMS } from '../../../utils'
|
||||
import AssetModel from '../../../models/Asset'
|
||||
|
||||
export function transformPublishFormToMetadata(
|
||||
data: MetadataPublishForm
|
||||
data: Partial<MetadataPublishForm>
|
||||
): MetadataMarket {
|
||||
const currentTime = toStringNoMS(new Date())
|
||||
|
||||
|
@ -6,14 +6,28 @@ export const validationSchema = Yup.object().shape<MetadataPublishForm>({
|
||||
// ---- required fields ----
|
||||
name: Yup.string().required('Required'),
|
||||
author: Yup.string().required('Required'),
|
||||
price: Yup.object().shape({
|
||||
tokensToMint: Yup.number().required('Required'),
|
||||
type: Yup.string()
|
||||
.matches(/fixed|dynamic/g)
|
||||
.required('Required'),
|
||||
weightOnDataToken: Yup.string().required('Required'),
|
||||
liquidityProviderFee: Yup.string()
|
||||
}),
|
||||
price: Yup.object()
|
||||
.shape({
|
||||
price: Yup.number().min(1, 'Must be greater than 0').required('Required'),
|
||||
tokensToMint: Yup.number()
|
||||
.min(1, 'Must be greater than 0')
|
||||
.required('Required'),
|
||||
type: Yup.string()
|
||||
.matches(/fixed|dynamic/g)
|
||||
.required('Required'),
|
||||
weightOnDataToken: Yup.string().required('Required'),
|
||||
liquidityProviderFee: Yup.number()
|
||||
.min(0.1, 'Must be more or equal to 0.1')
|
||||
.max(0.9, 'Must be less or equal to 0.9')
|
||||
.required('Required'),
|
||||
datatoken: Yup.object()
|
||||
.shape({
|
||||
name: Yup.string(),
|
||||
symbol: Yup.string()
|
||||
})
|
||||
.nullable()
|
||||
})
|
||||
.required('Required'),
|
||||
files: Yup.array<FileMetadata>().required('Required').nullable(),
|
||||
description: Yup.string().required('Required'),
|
||||
license: Yup.string().required('Required'),
|
||||
@ -23,27 +37,24 @@ export const validationSchema = Yup.object().shape<MetadataPublishForm>({
|
||||
termsAndConditions: Yup.boolean().required('Required'),
|
||||
|
||||
// ---- optional fields ----
|
||||
copyrightHolder: Yup.string(),
|
||||
tags: Yup.string(),
|
||||
copyrightHolder: Yup.string().nullable(),
|
||||
tags: Yup.string().nullable(),
|
||||
links: Yup.object<FileMetadata[]>().nullable()
|
||||
})
|
||||
|
||||
export const initialValues: MetadataPublishForm = {
|
||||
export const initialValues: Partial<MetadataPublishForm> = {
|
||||
name: '',
|
||||
author: '',
|
||||
price: {
|
||||
price: 1,
|
||||
type: 'fixed',
|
||||
tokensToMint: 1,
|
||||
weightOnDataToken: '9', // 90% on data token
|
||||
liquidityProviderFee: '0.1', // in %
|
||||
price: 1
|
||||
liquidityProviderFee: 0.1 // in %
|
||||
},
|
||||
files: '',
|
||||
description: '',
|
||||
license: '',
|
||||
access: '',
|
||||
termsAndConditions: false,
|
||||
copyrightHolder: '',
|
||||
tags: '',
|
||||
links: ''
|
||||
termsAndConditions: false
|
||||
}
|
||||
|
@ -5,10 +5,11 @@ const testFormData: MetadataPublishForm = {
|
||||
files: [],
|
||||
license: '',
|
||||
price: {
|
||||
tokensToMint: 1,
|
||||
type: 'simple',
|
||||
price: 1,
|
||||
tokensToMint: 9,
|
||||
type: 'fixed',
|
||||
weightOnDataToken: '1',
|
||||
liquidityProviderFee: '0.1'
|
||||
liquidityProviderFee: 0.1
|
||||
},
|
||||
name: '',
|
||||
description: 'description',
|
||||
|
Loading…
Reference in New Issue
Block a user