1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-09-28 03:58:59 +02:00

Merge pull request #81 from oceanprotocol/feature/fees

Fees input and output
This commit is contained in:
Matthias Kretschmann 2020-09-22 13:36:19 +02:00 committed by GitHub
commit 8de48d88e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 308 additions and 156 deletions

View File

@ -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..."
}
}
}

View File

@ -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 ----

View File

@ -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;

View File

@ -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}
/>
)
}
}

View File

@ -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>

View File

@ -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

View File

@ -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>
)

View File

@ -49,14 +49,9 @@
}
.summary {
text-align: center;
margin-top: var(--spacer);
}
.summary input {
max-width: 5rem;
}
.alertArea {
margin-left: -2rem;
margin-right: -2rem;

View File

@ -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 && (

View 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
}

View 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;
}

View 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>
</>
)
}

View File

@ -2,6 +2,10 @@
composes: content from './index.module.css';
}
.form {
position: relative;
}
@media (min-width: 55rem) {
.form {
max-width: 12rem;

View File

@ -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>

View File

@ -20,6 +20,11 @@
margin-bottom: 0;
}
.content [class*='error'] {
text-align: left;
top: 100%;
}
.conversion {
width: 100%;
display: block;

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>}

View File

@ -10,7 +10,6 @@ export default function MetaSecondary({
}: {
metadata: MetadataMarket
}): ReactElement {
console.log(metadata)
return (
<aside className={styles.metaSecondary}>
{metadata?.additionalInformation?.tags?.length > 0 && (

View File

@ -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}>

View File

@ -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) {

View File

@ -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())

View File

@ -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
}

View File

@ -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',