mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
merge branch main into feature/account-page and fixed merge conflicts
This commit is contained in:
commit
69f02afd59
@ -18,6 +18,7 @@
|
||||
|
||||
# Enables another asset editing button holder further advanced settings
|
||||
#GATSBY_ALLOW_ADVANCED_SETTINGS="true"
|
||||
#GATSBY_ALLOW_ADVANCED_PUBLISH_SETTINGS="true"
|
||||
|
||||
# Allow/Deny Lists
|
||||
#GATSBY_CREDENTIAL_TYPE="address"
|
||||
|
@ -81,7 +81,7 @@ npm start
|
||||
|
||||
To use the app together with MetaMask, importing one of the accounts auto-generated by the Ganache container is the easiest way to have test ETH available. All of them have 100 ETH by default. Upon start, the `ocean_ganache_1` container will print out the private keys of multiple accounts in its logs. Pick one of them and import into MetaMask.
|
||||
|
||||
To fully test all [The Graph](https://thegraph.com) integrations, you have to run your own local Graph node with our [`ocean-subgraph`](https://github.com/oceanprotocol/ocean-subgraph) deployed to it. Barge does not include a local subgraph so by default, the `config.subgraphUri` is hardcoded to the Rinkeby subgraph in our [`NetworkMonitor` component](https://github.com/oceanprotocol/market/blob/main/src/helpers/NetworkMonitor.tsx).
|
||||
To fully test all [The Graph](https://thegraph.com) integrations, you have to run your own local Graph node with our [`ocean-subgraph`](https://github.com/oceanprotocol/ocean-subgraph) deployed to it. Barge does not include a local subgraph so by default, the `subgraphUri` is hardcoded to the Rinkeby subgraph in our [`getDevelopmentConfig` function](https://github.com/oceanprotocol/market/blob/d0b1534d105e5dcb3790c65d4bb04ff1d2dbc575/src/utils/ocean.ts#L31).
|
||||
|
||||
> Cleaning all Docker images so they are fetched freshly is often a good idea to make sure no issues are caused by old or stale images: `docker system prune --all --volumes`
|
||||
|
||||
|
@ -60,5 +60,7 @@ module.exports = {
|
||||
|
||||
// Used to show or hide advanced settings button in asset details page
|
||||
allowAdvancedSettings: process.env.GATSBY_ALLOW_ADVANCED_SETTINGS || 'false',
|
||||
allowAdvancedPublishSettings:
|
||||
process.env.GATSBY_ALLOW_ADVANCED_PUBLISH_SETTINGS || 'false',
|
||||
credentialType: process.env.GATSBY_CREDENTIAL_TYPE || 'address'
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ documents:
|
||||
- './src/components/pages/History/PoolShares.tsx'
|
||||
- './src/components/pages/History/Downloads.tsx'
|
||||
- './src/components/pages/History/ComputeJobs/index.tsx'
|
||||
- './src/ //Users/bogdanfazakas/Sites/ocean-market/src/components/organisms/AssetContent/EditHistory.tsx'
|
||||
- './src/components/organisms/AssetContent/EditHistory.tsx'
|
||||
# - './src/components/organisms/AssetActions/Pool/index.tsx'
|
||||
- './src/components/organisms/AssetActions/Pool/Graph.tsx'
|
||||
- './src/components/organisms/AssetActions/Consume.tsx'
|
||||
|
@ -91,6 +91,14 @@
|
||||
"placeholder": "e.g. logistics, ai",
|
||||
"help": "Separate tags with comma."
|
||||
},
|
||||
{
|
||||
"name": "providerUri",
|
||||
"label": "Custom Provider URL",
|
||||
"type": "providerUri",
|
||||
"help": "Enter the URL for your custom provider or leave blank to use the default provider. [Learn more](https://github.com/oceanprotocol/provider/).",
|
||||
"placeholder": "https://provider.polygon.oceanprotocol.com/",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "termsAndConditions",
|
||||
"label": "Terms & Conditions",
|
||||
|
@ -38,6 +38,14 @@
|
||||
"options": ["Download", "Compute"],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "providerUri",
|
||||
"label": "Custom Provider URL",
|
||||
"type": "providerUri",
|
||||
"help": "Enter the URL for your custom provider or leave blank to use the default provider. [Learn more](https://github.com/oceanprotocol/provider/).",
|
||||
"placeholder": "https://provider.polygon.oceanprotocol.com/",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "timeout",
|
||||
"label": "Timeout",
|
||||
|
@ -12,18 +12,18 @@
|
||||
"title": "Fixed",
|
||||
"info": "Set your price for accessing this data set. The datatoken for this data set will be worth the entered amount of OCEAN.",
|
||||
"tooltips": {
|
||||
"communityFee": "Explain community fee...",
|
||||
"marketplaceFee": "Explain marketplace fee..."
|
||||
"communityFee": "Goes to Ocean DAO for teams to improve the tools, build apps, do outreach, and more. A small fraction is used to burn OCEAN. This fee is collected when downloading or using an asset in a compute job.",
|
||||
"marketplaceFee": "Goes to the marketplace owner that is hosting and providing the marketplace and is collected when downloading or using an asset in a compute job. In Ocean Market, it is treated as network revenue that goes to the Ocean community."
|
||||
}
|
||||
},
|
||||
"dynamic": {
|
||||
"title": "Dynamic",
|
||||
"info": "Let's create a decentralized, automated market for your data set. The datatoken for this data set will be worth the entered amount of OCEAN. Additionally, you will provide liquidity into a Datatoken/OCEAN liquidity pool with Balancer.",
|
||||
"tooltips": {
|
||||
"poolInfo": "Explain what is going on here...",
|
||||
"swapFee": "Explain liquidity provider fee...",
|
||||
"communityFee": "Explain community fee...",
|
||||
"marketplaceFee": "Explain marketplace fee..."
|
||||
"poolInfo": "The liquidity pool provides the funds for traders to trade against. The price of the asset is determined by the ratio of OCEAN to datatokens.",
|
||||
"swapFee": "Liquidity providers earn this fee on all pool trades, proportionally to their share of the pool. The fee is set by the creator of the pool and is used to incentivize liquidity providers to join the pool.",
|
||||
"communityFee": "Goes to Ocean DAO for teams to improve the tools, build apps, do outreach, and more. A small fraction is used to burn OCEAN. This fee is collected when downloading or using an asset in a compute job.",
|
||||
"marketplaceFee": "Goes to the marketplace owner that is hosting and providing the marketplace and is collected when downloading or using an asset in a compute job. In Ocean Market, it is treated as network revenue that goes to the Ocean community."
|
||||
}
|
||||
},
|
||||
"free": {
|
||||
@ -33,7 +33,7 @@
|
||||
},
|
||||
"pool": {
|
||||
"tooltips": {
|
||||
"price": "Explain how this price is determined...",
|
||||
"price": "The price is determined by an automated market maker, which is a type of decentralized exchange protocol that relies on a mathematical formula. It is an alternative to a traditional order book.",
|
||||
"liquidity": "Providing liquidity will earn you SWAPFEE% on every transaction in this pool, proportionally to your share of the pool."
|
||||
},
|
||||
"add": {
|
||||
|
1
src/@types/Form.d.ts
vendored
1
src/@types/Form.d.ts
vendored
@ -13,6 +13,7 @@ export interface FormFieldProps {
|
||||
placeholder?: string
|
||||
pattern?: string
|
||||
min?: string
|
||||
advanced?: boolean
|
||||
}
|
||||
|
||||
export interface FormContent {
|
||||
|
2
src/@types/MetaData.d.ts
vendored
2
src/@types/MetaData.d.ts
vendored
@ -38,6 +38,7 @@ export interface MetadataPublishFormDataset {
|
||||
// ---- optional fields ----
|
||||
tags?: string
|
||||
links?: string | EditableMetadataLinks[]
|
||||
providerUri?: string
|
||||
}
|
||||
|
||||
export interface MetadataPublishFormAlgorithm {
|
||||
@ -56,6 +57,7 @@ export interface MetadataPublishFormAlgorithm {
|
||||
containerTag: string
|
||||
entrypoint: string
|
||||
tags?: string
|
||||
providerUri?: string
|
||||
}
|
||||
|
||||
export interface MetadataEditForm {
|
||||
|
4
src/@types/TokenBalance.d.ts
vendored
4
src/@types/TokenBalance.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
export interface PoolBalance {
|
||||
ocean: number
|
||||
datatoken: number
|
||||
ocean: string
|
||||
datatoken: string
|
||||
}
|
||||
|
||||
export interface UserBalance {
|
||||
|
@ -8,7 +8,6 @@ import { useWeb3 } from '../providers/Web3'
|
||||
import { useSiteMetadata } from '../hooks/useSiteMetadata'
|
||||
import { useAccountPurgatory } from '../hooks/useAccountPurgatory'
|
||||
import AnnouncementBanner from './atoms/AnnouncementBanner'
|
||||
import { useGraphSyncStatus } from '../hooks/useGraphSyncStatus'
|
||||
import styles from './App.module.css'
|
||||
|
||||
const contentQuery = graphql`
|
||||
@ -40,7 +39,6 @@ export default function App({
|
||||
const { warning } = useSiteMetadata()
|
||||
const { accountId } = useWeb3()
|
||||
const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)
|
||||
// const { isGraphSynced, blockHead, blockGraph } = useGraphSyncStatus()
|
||||
|
||||
return (
|
||||
<Styles>
|
||||
|
@ -3,6 +3,7 @@ import slugify from '@sindresorhus/slugify'
|
||||
import styles from './InputElement.module.css'
|
||||
import { InputProps } from '.'
|
||||
import FilesInput from '../../molecules/FormFields/FilesInput'
|
||||
import CustomProvider from '../../molecules/FormFields/CustomProvider'
|
||||
import Terms from '../../molecules/FormFields/Terms'
|
||||
import BoxSelection, {
|
||||
BoxSelectionOption
|
||||
@ -125,6 +126,8 @@ export default function InputElement({
|
||||
)
|
||||
case 'files':
|
||||
return <FilesInput name={name} {...field} {...props} />
|
||||
case 'providerUri':
|
||||
return <CustomProvider name={name} {...field} {...props} />
|
||||
case 'datatoken':
|
||||
return <Datatoken name={name} {...field} {...props} />
|
||||
case 'terms':
|
||||
|
@ -63,7 +63,7 @@ export default function Publisher({
|
||||
) : (
|
||||
<>
|
||||
<Link
|
||||
to={`/search/?owner=${account}&sort=created&sortOrder=desc`}
|
||||
to={`/search?sort=created&sortOrder=desc&text=${account}`}
|
||||
title="Show all data sets created by this account."
|
||||
>
|
||||
{name}
|
||||
|
@ -0,0 +1,3 @@
|
||||
.advancedBtn {
|
||||
margin-bottom: 2rem;
|
||||
}
|
53
src/components/molecules/FormFields/AdvancedSettings.tsx
Normal file
53
src/components/molecules/FormFields/AdvancedSettings.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { ReactElement, useState, FormEvent, ChangeEvent } from 'react'
|
||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||
import Input from '../../atoms/Input'
|
||||
import Button from '../../atoms/Button'
|
||||
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||
import { Field } from 'formik'
|
||||
import styles from './AdvancedSettings.module.css'
|
||||
|
||||
export default function AdvancedSettings(prop: {
|
||||
content: FormContent
|
||||
handleFieldChange: (
|
||||
e: ChangeEvent<HTMLInputElement>,
|
||||
field: FormFieldProps
|
||||
) => void
|
||||
}): ReactElement {
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const [advancedSettings, setAdvancedSettings] = useState<boolean>(false)
|
||||
function toggleAdvancedSettings(e: FormEvent<Element>) {
|
||||
e.preventDefault()
|
||||
advancedSettings === true
|
||||
? setAdvancedSettings(false)
|
||||
: setAdvancedSettings(true)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{appConfig.allowAdvancedPublishSettings === 'true' && (
|
||||
<Button
|
||||
className={styles.advancedBtn}
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={toggleAdvancedSettings}
|
||||
>
|
||||
Advanced Settings
|
||||
</Button>
|
||||
)}
|
||||
{prop.content.data.map(
|
||||
(field: FormFieldProps) =>
|
||||
advancedSettings === true &&
|
||||
field.advanced === true && (
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={field.options}
|
||||
component={Input}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
prop.handleFieldChange(e, field)
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
60
src/components/molecules/FormFields/CustomProvider.tsx
Normal file
60
src/components/molecules/FormFields/CustomProvider.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import { useField } from 'formik'
|
||||
import { toast } from 'react-toastify'
|
||||
import CustomInput from './URLInput/Input'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { InputProps } from '../../atoms/Input'
|
||||
|
||||
export default function CustomProvider(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [providerUrl, setProviderUrl] = useState<string>()
|
||||
const { ocean, config } = useOcean()
|
||||
|
||||
function loadProvider() {
|
||||
if (!providerUrl) return
|
||||
async function validateProvider() {
|
||||
let valid: boolean
|
||||
try {
|
||||
setIsLoading(true)
|
||||
valid = await ocean.provider.isValidProvider(providerUrl)
|
||||
} catch (error) {
|
||||
valid = false
|
||||
console.error(error.message)
|
||||
} finally {
|
||||
valid
|
||||
? toast.success('Perfect! That provider URL looks good 🐳')
|
||||
: toast.error(
|
||||
'Could not validate provider. Please check URL and try again'
|
||||
)
|
||||
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
validateProvider()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadProvider()
|
||||
}, [providerUrl, config.providerUri])
|
||||
|
||||
async function handleButtonClick(e: React.SyntheticEvent, url: string) {
|
||||
helpers.setTouched(false)
|
||||
e.preventDefault()
|
||||
if (providerUrl === url) {
|
||||
loadProvider()
|
||||
}
|
||||
|
||||
setProviderUrl(url)
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomInput
|
||||
submitText="Validate"
|
||||
{...props}
|
||||
{...field}
|
||||
isLoading={isLoading}
|
||||
handleButtonClick={handleButtonClick}
|
||||
/>
|
||||
)
|
||||
}
|
@ -3,7 +3,7 @@ import axios from 'axios'
|
||||
import { useField } from 'formik'
|
||||
import { toast } from 'react-toastify'
|
||||
import FileInfo from './Info'
|
||||
import FileInput from './Input'
|
||||
import CustomInput from '../URLInput/Input'
|
||||
import { InputProps } from '../../../atoms/Input'
|
||||
import { fileinfo } from '../../../../utils/provider'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
@ -68,7 +68,8 @@ export default function FilesInput(props: InputProps): ReactElement {
|
||||
{field?.value && field.value[0] && typeof field.value === 'object' ? (
|
||||
<FileInfo name={props.name} file={field.value[0]} />
|
||||
) : (
|
||||
<FileInput
|
||||
<CustomInput
|
||||
submitText="Add File"
|
||||
{...props}
|
||||
{...field}
|
||||
isLoading={isLoading}
|
||||
|
@ -5,11 +5,13 @@ import Loader from '../../../atoms/Loader'
|
||||
import styles from './Input.module.css'
|
||||
import InputGroup from '../../../atoms/Input/InputGroup'
|
||||
|
||||
export default function FileInput({
|
||||
export default function URLInput({
|
||||
submitText,
|
||||
handleButtonClick,
|
||||
isLoading,
|
||||
...props
|
||||
}: {
|
||||
submitText: string
|
||||
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
||||
isLoading: boolean
|
||||
}): ReactElement {
|
||||
@ -30,7 +32,7 @@ export default function FileInput({
|
||||
onClick={(e: React.SyntheticEvent) => e.preventDefault()}
|
||||
disabled={!field.value}
|
||||
>
|
||||
{isLoading ? <Loader /> : 'Add File'}
|
||||
{isLoading ? <Loader /> : submitText}
|
||||
</Button>
|
||||
</InputGroup>
|
||||
)
|
@ -4,7 +4,7 @@ import Conversion from '../atoms/Price/Conversion'
|
||||
import PriceUnit from '../atoms/Price/PriceUnit'
|
||||
import Tooltip from '../atoms/Tooltip'
|
||||
import NetworkName from '../atoms/NetworkName'
|
||||
import { fetchData, getSubgrahUri } from '../../utils/subgraph'
|
||||
import { fetchData, getSubgraphUri } from '../../utils/subgraph'
|
||||
import { filterNetworksByType } from './UserPreferences/Networks/index'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
import useNetworkMetadata from '../../hooks/useNetworkMetadata'
|
||||
@ -113,7 +113,7 @@ export default function MarketStats(): ReactElement {
|
||||
|
||||
for (const chainId of mainChainIdsList) {
|
||||
const context: OperationContext = {
|
||||
url: `${getSubgrahUri(
|
||||
url: `${getSubgraphUri(
|
||||
chainId
|
||||
)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
|
||||
requestPolicy: 'network-only'
|
||||
|
@ -38,3 +38,6 @@
|
||||
.action {
|
||||
margin-top: calc(var(--spacer) / 1.5);
|
||||
}
|
||||
.moreInfo {
|
||||
padding: calc(var(--spacer) / 4) calc(var(--spacer) / 2) !important;
|
||||
}
|
||||
|
@ -70,7 +70,12 @@ export default function MetadataFeedback({
|
||||
<>
|
||||
<p>Sorry, something went wrong. Please try again.</p>
|
||||
{moreInfo && <Alert text={error} state="error" />}
|
||||
<Button style="text" size="small" onClick={toggleMoreInfo}>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={toggleMoreInfo}
|
||||
className={styles.moreInfo}
|
||||
>
|
||||
{moreInfo === false ? 'More Info' : 'Hide error'}
|
||||
</Button>
|
||||
<ActionError setError={setError} />
|
||||
|
@ -129,10 +129,12 @@ const columnsMinimal = [columns[0], columns[3]]
|
||||
|
||||
export default function PoolTransactions({
|
||||
poolAddress,
|
||||
poolChainId,
|
||||
minimal,
|
||||
accountId
|
||||
}: {
|
||||
poolAddress?: string
|
||||
poolChainId?: number[]
|
||||
minimal?: boolean
|
||||
accountId: string
|
||||
}): ReactElement {
|
||||
@ -144,13 +146,17 @@ export default function PoolTransactions({
|
||||
const [data, setData] = useState<PoolTransaction[]>()
|
||||
|
||||
async function fetchPoolTransactionData() {
|
||||
const variables = { user: accountId?.toLowerCase() }
|
||||
const variables = {
|
||||
user: accountId?.toLowerCase(),
|
||||
pool: poolAddress?.toLowerCase()
|
||||
}
|
||||
const transactions: PoolTransaction[] = []
|
||||
const result = await fetchDataForMultipleChains(
|
||||
poolAddress ? txHistoryQueryByPool : txHistoryQuery,
|
||||
variables,
|
||||
chainIds
|
||||
poolAddress ? poolChainId : chainIds
|
||||
)
|
||||
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
result[i].poolTransactions.forEach((poolTransaction: PoolTransaction) => {
|
||||
transactions.push(poolTransaction)
|
||||
|
@ -41,19 +41,20 @@ function handleTimeoutCustomOption(
|
||||
data[timeoutInputIndex].options[5] = values.timeout
|
||||
}
|
||||
}
|
||||
|
||||
export default function FormEditMetadata({
|
||||
data,
|
||||
setShowEdit,
|
||||
setTimeoutStringValue,
|
||||
values,
|
||||
showPrice
|
||||
showPrice,
|
||||
isComputeDataset
|
||||
}: {
|
||||
data: FormFieldProps[]
|
||||
setShowEdit: (show: boolean) => void
|
||||
setTimeoutStringValue: (value: string) => void
|
||||
values: Partial<MetadataPublishFormDataset>
|
||||
showPrice: boolean
|
||||
isComputeDataset: boolean
|
||||
}): ReactElement {
|
||||
const { config } = useOcean()
|
||||
const {
|
||||
@ -70,12 +71,22 @@ export default function FormEditMetadata({
|
||||
validateField(field.name)
|
||||
setFieldValue(field.name, e.target.value)
|
||||
}
|
||||
|
||||
// This component is handled by Formik so it's not rendered like a "normal" react component,
|
||||
// so handleTimeoutCustomOption is called only once.
|
||||
// https://github.com/oceanprotocol/market/pull/324#discussion_r561132310
|
||||
if (data && values) handleTimeoutCustomOption(data, values)
|
||||
|
||||
const timeoutOptionsArray = data.filter(
|
||||
(field) => field.name === 'timeout'
|
||||
)[0].options
|
||||
|
||||
if (isComputeDataset && timeoutOptionsArray.includes('Forever')) {
|
||||
const foreverOptionIndex = timeoutOptionsArray.indexOf('Forever')
|
||||
timeoutOptionsArray.splice(foreverOptionIndex, 1)
|
||||
} else if (!isComputeDataset && !timeoutOptionsArray.includes('Forever')) {
|
||||
timeoutOptionsArray.push('Forever')
|
||||
}
|
||||
|
||||
return (
|
||||
<Form className={styles.form}>
|
||||
{data.map(
|
||||
@ -83,6 +94,11 @@ export default function FormEditMetadata({
|
||||
(!showPrice && field.name === 'price') || (
|
||||
<Field
|
||||
key={field.name}
|
||||
options={
|
||||
field.name === 'timeout' && isComputeDataset === true
|
||||
? timeoutOptionsArray
|
||||
: field.options
|
||||
}
|
||||
{...field}
|
||||
component={Input}
|
||||
prefix={field.name === 'price' && config.oceanTokenSymbol}
|
||||
|
@ -55,9 +55,11 @@ const contentQuery = graphql`
|
||||
`
|
||||
|
||||
export default function Edit({
|
||||
setShowEdit
|
||||
setShowEdit,
|
||||
isComputeType
|
||||
}: {
|
||||
setShowEdit: (show: boolean) => void
|
||||
isComputeType?: boolean
|
||||
}): ReactElement {
|
||||
const data = useStaticQuery(contentQuery)
|
||||
const content = data.content.edges[0].node.childPagesJson
|
||||
@ -201,6 +203,7 @@ export default function Edit({
|
||||
setTimeoutStringValue={setTimeoutStringValue}
|
||||
values={initialValues}
|
||||
showPrice={price.type === 'exchange'}
|
||||
isComputeDataset={isComputeType}
|
||||
/>
|
||||
|
||||
<aside>
|
||||
|
@ -15,6 +15,9 @@ import UserLiquidity from '../../../../atoms/UserLiquidity'
|
||||
import { useOcean } from '../../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../../providers/Web3'
|
||||
|
||||
import { isValidNumber } from './../../../../../utils/numberValidations'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
export default function FormAdd({
|
||||
coin,
|
||||
dtBalance,
|
||||
@ -67,6 +70,7 @@ export default function FormAdd({
|
||||
setNewPoolShare('0')
|
||||
return
|
||||
}
|
||||
|
||||
if (Number(values.amount) > Number(amountMax)) return
|
||||
|
||||
const poolTokens = await ocean.pool.calcPoolOutGivenSingleIn(
|
||||
@ -74,15 +78,20 @@ export default function FormAdd({
|
||||
coin === 'OCEAN' ? ocean.pool.oceanAddress : ocean.pool.dtAddress,
|
||||
`${values.amount}`
|
||||
)
|
||||
|
||||
setNewPoolTokens(poolTokens)
|
||||
totalBalance &&
|
||||
setNewPoolShare(
|
||||
`${
|
||||
(Number(poolTokens) /
|
||||
(Number(totalPoolTokens) + Number(poolTokens))) *
|
||||
100
|
||||
}`
|
||||
)
|
||||
|
||||
const newPoolShareDecimal =
|
||||
isValidNumber(poolTokens) && isValidNumber(totalPoolTokens)
|
||||
? new Decimal(poolTokens)
|
||||
.dividedBy(
|
||||
new Decimal(totalPoolTokens).plus(new Decimal(poolTokens))
|
||||
)
|
||||
.mul(100)
|
||||
.toString()
|
||||
: '0'
|
||||
|
||||
totalBalance && setNewPoolShare(newPoolShareDecimal)
|
||||
}
|
||||
calculatePoolShares()
|
||||
}, [
|
||||
|
@ -2,6 +2,7 @@ import Conversion from '../../../atoms/Price/Conversion'
|
||||
import React, { ReactElement, ReactNode } from 'react'
|
||||
import Token from './Token'
|
||||
import styles from './TokenList.module.css'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
export default function TokenList({
|
||||
title,
|
||||
@ -20,7 +21,7 @@ export default function TokenList({
|
||||
dt: string
|
||||
dtSymbol: string
|
||||
poolShares: string
|
||||
conversion: number
|
||||
conversion: Decimal
|
||||
highlight?: boolean
|
||||
showTVLLabel?: boolean
|
||||
}): ReactElement {
|
||||
@ -31,9 +32,9 @@ export default function TokenList({
|
||||
<div>
|
||||
<Token symbol="OCEAN" balance={ocean} />
|
||||
<Token symbol={dtSymbol} balance={dt} />
|
||||
{conversion > 0 && (
|
||||
{conversion.greaterThan(0) && (
|
||||
<Conversion
|
||||
price={`${conversion}`}
|
||||
price={conversion.toString()}
|
||||
className={styles.totalLiquidity}
|
||||
showTVLLabel={showTVLLabel}
|
||||
/>
|
||||
|
@ -22,8 +22,13 @@ import { useWeb3 } from '../../../../providers/Web3'
|
||||
import PoolTransactions from '../../../molecules/PoolTransactions'
|
||||
import { fetchData, getQueryContext } from '../../../../utils/subgraph'
|
||||
|
||||
import { isValidNumber } from './../../../../utils/numberValidations'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
const REFETCH_INTERVAL = 5000
|
||||
|
||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||
|
||||
const contentQuery = graphql`
|
||||
query PoolQuery {
|
||||
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
|
||||
@ -85,11 +90,15 @@ export default function Pool(): ReactElement {
|
||||
|
||||
const [hasAddedLiquidity, setHasAddedLiquidity] = useState(false)
|
||||
const [poolShare, setPoolShare] = useState<string>()
|
||||
const [totalUserLiquidityInOcean, setTotalUserLiquidityInOcean] = useState(0)
|
||||
const [totalLiquidityInOcean, setTotalLiquidityInOcean] = useState(0)
|
||||
const [totalUserLiquidityInOcean, setTotalUserLiquidityInOcean] = useState(
|
||||
new Decimal(0)
|
||||
)
|
||||
const [totalLiquidityInOcean, setTotalLiquidityInOcean] = useState(
|
||||
new Decimal(0)
|
||||
)
|
||||
|
||||
const [creatorTotalLiquidityInOcean, setCreatorTotalLiquidityInOcean] =
|
||||
useState(0)
|
||||
useState(new Decimal(0))
|
||||
const [creatorLiquidity, setCreatorLiquidity] = useState<PoolBalance>()
|
||||
const [creatorPoolTokens, setCreatorPoolTokens] = useState<string>()
|
||||
const [creatorPoolShare, setCreatorPoolShare] = useState<string>()
|
||||
@ -144,15 +153,28 @@ export default function Pool(): ReactElement {
|
||||
|
||||
// Get swap fee
|
||||
// swapFee is tricky: to get 0.1% you need to convert from 0.001
|
||||
setSwapFee(`${Number(dataLiquidity.pool.swapFee) * 100}`)
|
||||
const swapFee = isValidNumber(dataLiquidity.pool.swapFee)
|
||||
? new Decimal(dataLiquidity.pool.swapFee).mul(100).toString()
|
||||
: '0'
|
||||
|
||||
setSwapFee(swapFee)
|
||||
|
||||
// Get weights
|
||||
const weightDt = dataLiquidity.pool.tokens.filter(
|
||||
(token: any) => token.tokenAddress === ddo.dataToken.toLowerCase()
|
||||
)[0].denormWeight
|
||||
|
||||
setWeightDt(`${Number(weightDt) * 10}`)
|
||||
setWeightOcean(`${100 - Number(weightDt) * 10}`)
|
||||
const weightDtDecimal = isValidNumber(weightDt)
|
||||
? new Decimal(weightDt).mul(10).toString()
|
||||
: '0'
|
||||
|
||||
setWeightDt(weightDtDecimal)
|
||||
|
||||
const weightOceanDecimal = isValidNumber(weightDt)
|
||||
? new Decimal(100).minus(new Decimal(weightDt).mul(10)).toString()
|
||||
: '0'
|
||||
|
||||
setWeightOcean(weightOceanDecimal)
|
||||
|
||||
//
|
||||
// Get everything the creator put into the pool
|
||||
@ -161,12 +183,25 @@ export default function Pool(): ReactElement {
|
||||
const creatorPoolTokens = dataLiquidity.pool.shares[0].balance
|
||||
setCreatorPoolTokens(creatorPoolTokens)
|
||||
|
||||
// Calculate creator's provided liquidity based on pool tokens
|
||||
const creatorOceanBalance =
|
||||
(Number(creatorPoolTokens) / Number(totalPoolTokens)) * price.ocean
|
||||
isValidNumber(creatorPoolTokens) &&
|
||||
isValidNumber(totalPoolTokens) &&
|
||||
isValidNumber(price.ocean)
|
||||
? new Decimal(creatorPoolTokens)
|
||||
.dividedBy(new Decimal(totalPoolTokens))
|
||||
.mul(price.ocean)
|
||||
.toString()
|
||||
: '0'
|
||||
|
||||
const creatorDtBalance =
|
||||
(Number(creatorPoolTokens) / Number(totalPoolTokens)) * price.datatoken
|
||||
isValidNumber(creatorPoolTokens) &&
|
||||
isValidNumber(totalPoolTokens) &&
|
||||
isValidNumber(price.datatoken)
|
||||
? new Decimal(creatorPoolTokens)
|
||||
.dividedBy(new Decimal(totalPoolTokens))
|
||||
.mul(price.datatoken)
|
||||
.toString()
|
||||
: '0'
|
||||
|
||||
const creatorLiquidity = {
|
||||
ocean: creatorOceanBalance,
|
||||
@ -175,14 +210,30 @@ export default function Pool(): ReactElement {
|
||||
setCreatorLiquidity(creatorLiquidity)
|
||||
|
||||
const totalCreatorLiquidityInOcean =
|
||||
creatorLiquidity?.ocean +
|
||||
creatorLiquidity?.datatoken * dataLiquidity.pool.spotPrice
|
||||
isValidNumber(creatorLiquidity?.ocean) &&
|
||||
isValidNumber(creatorLiquidity?.datatoken) &&
|
||||
isValidNumber(dataLiquidity.pool.spotPrice)
|
||||
? new Decimal(creatorLiquidity?.ocean).add(
|
||||
new Decimal(creatorLiquidity?.datatoken).mul(
|
||||
new Decimal(dataLiquidity.pool.spotPrice)
|
||||
)
|
||||
)
|
||||
: new Decimal(0)
|
||||
|
||||
setCreatorTotalLiquidityInOcean(totalCreatorLiquidityInOcean)
|
||||
|
||||
const creatorPoolShare =
|
||||
price?.ocean &&
|
||||
price?.datatoken &&
|
||||
creatorLiquidity &&
|
||||
((Number(creatorPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
|
||||
isValidNumber(creatorPoolTokens) &&
|
||||
isValidNumber(totalPoolTokens)
|
||||
? new Decimal(creatorPoolTokens)
|
||||
.dividedBy(new Decimal(totalPoolTokens))
|
||||
.mul(100)
|
||||
.toFixed(2)
|
||||
: '0'
|
||||
|
||||
setCreatorPoolShare(creatorPoolShare)
|
||||
refetchLiquidity()
|
||||
}
|
||||
@ -195,17 +246,38 @@ export default function Pool(): ReactElement {
|
||||
|
||||
useEffect(() => {
|
||||
const poolShare =
|
||||
isValidNumber(poolTokens) &&
|
||||
isValidNumber(totalPoolTokens) &&
|
||||
price?.ocean &&
|
||||
price?.datatoken &&
|
||||
((Number(poolTokens) / Number(totalPoolTokens)) * 100).toFixed(5)
|
||||
new Decimal(poolTokens)
|
||||
.dividedBy(new Decimal(totalPoolTokens))
|
||||
.mul(100)
|
||||
.toFixed(5)
|
||||
|
||||
setPoolShare(poolShare)
|
||||
setHasAddedLiquidity(Number(poolShare) > 0)
|
||||
|
||||
const totalUserLiquidityInOcean =
|
||||
userLiquidity?.ocean + userLiquidity?.datatoken * price?.value
|
||||
isValidNumber(userLiquidity?.ocean) &&
|
||||
isValidNumber(userLiquidity?.datatoken) &&
|
||||
isValidNumber(price?.value)
|
||||
? new Decimal(userLiquidity?.ocean).add(
|
||||
new Decimal(userLiquidity?.datatoken).mul(price?.value)
|
||||
)
|
||||
: new Decimal(0)
|
||||
|
||||
setTotalUserLiquidityInOcean(totalUserLiquidityInOcean)
|
||||
|
||||
const totalLiquidityInOcean =
|
||||
Number(price?.ocean) + Number(price?.datatoken) * Number(price?.value)
|
||||
isValidNumber(price?.ocean) &&
|
||||
isValidNumber(price?.datatoken) &&
|
||||
isValidNumber(price?.value)
|
||||
? new Decimal(price?.ocean).add(
|
||||
new Decimal(price?.datatoken).mul(price?.value)
|
||||
)
|
||||
: new Decimal(0)
|
||||
|
||||
setTotalLiquidityInOcean(totalLiquidityInOcean)
|
||||
}, [userLiquidity, price, poolTokens, totalPoolTokens])
|
||||
|
||||
@ -221,11 +293,28 @@ export default function Pool(): ReactElement {
|
||||
price.address
|
||||
)
|
||||
setPoolTokens(poolTokens)
|
||||
|
||||
// calculate user's provided liquidity based on pool tokens
|
||||
const userOceanBalance =
|
||||
(Number(poolTokens) / Number(totalPoolTokens)) * price.ocean
|
||||
isValidNumber(poolTokens) &&
|
||||
isValidNumber(totalPoolTokens) &&
|
||||
isValidNumber(price.ocean)
|
||||
? new Decimal(poolTokens)
|
||||
.dividedBy(new Decimal(totalPoolTokens))
|
||||
.mul(price.ocean)
|
||||
.toString()
|
||||
: '0'
|
||||
|
||||
const userDtBalance =
|
||||
(Number(poolTokens) / Number(totalPoolTokens)) * price.datatoken
|
||||
isValidNumber(poolTokens) &&
|
||||
isValidNumber(totalPoolTokens) &&
|
||||
isValidNumber(price.datatoken)
|
||||
? new Decimal(poolTokens)
|
||||
.dividedBy(new Decimal(totalPoolTokens))
|
||||
.mul(price.datatoken)
|
||||
.toString()
|
||||
: '0'
|
||||
|
||||
const userLiquidity = {
|
||||
ocean: userOceanBalance,
|
||||
datatoken: userDtBalance
|
||||
@ -255,8 +344,8 @@ export default function Pool(): ReactElement {
|
||||
poolAddress={price.address}
|
||||
totalPoolTokens={totalPoolTokens}
|
||||
totalBalance={{
|
||||
ocean: price.ocean,
|
||||
datatoken: price.datatoken
|
||||
ocean: new Decimal(price.ocean).toString(),
|
||||
datatoken: new Decimal(price.datatoken).toString()
|
||||
}}
|
||||
swapFee={swapFee}
|
||||
dtSymbol={dtSymbol}
|
||||
@ -367,7 +456,7 @@ export default function Pool(): ReactElement {
|
||||
style="primary"
|
||||
size="small"
|
||||
onClick={() => setShowAdd(true)}
|
||||
disabled={isInPurgatory || !isAssetNetwork}
|
||||
disabled={isInPurgatory}
|
||||
>
|
||||
Add Liquidity
|
||||
</Button>
|
||||
@ -389,6 +478,7 @@ export default function Pool(): ReactElement {
|
||||
<PoolTransactions
|
||||
accountId={accountId}
|
||||
poolAddress={price?.address}
|
||||
poolChainId={[ddo.chainId]}
|
||||
minimal
|
||||
/>
|
||||
</AssetActionHistoryTable>
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { Ocean } from '@oceanprotocol/lib'
|
||||
|
||||
import { isValidNumber } from './../../../../utils/numberValidations'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||
|
||||
export async function getMaxPercentRemove(
|
||||
ocean: Ocean,
|
||||
poolAddress: string,
|
||||
@ -15,9 +20,15 @@ export async function getMaxPercentRemove(
|
||||
amountMaxOcean
|
||||
)
|
||||
|
||||
let amountMaxPercent = `${Math.floor(
|
||||
(Number(amountMaxPoolShares) / Number(poolTokens)) * 100
|
||||
)}`
|
||||
let amountMaxPercent =
|
||||
isValidNumber(amountMaxPoolShares) && isValidNumber(poolTokens)
|
||||
? new Decimal(amountMaxPoolShares)
|
||||
.dividedBy(new Decimal(poolTokens))
|
||||
.mul(100)
|
||||
.floor()
|
||||
.toString()
|
||||
: '0'
|
||||
|
||||
if (Number(amountMaxPercent) > 100) {
|
||||
amountMaxPercent = '100'
|
||||
}
|
||||
|
@ -42,8 +42,8 @@ export default function FormTrade({
|
||||
}: {
|
||||
ddo: DDO
|
||||
balance: PoolBalance
|
||||
maxDt: number
|
||||
maxOcean: number
|
||||
maxDt: string
|
||||
maxOcean: string
|
||||
price: BestPrice
|
||||
}): ReactElement {
|
||||
const data = useStaticQuery(contentQuery)
|
||||
@ -61,12 +61,18 @@ export default function FormTrade({
|
||||
const validationSchema: Yup.SchemaOf<FormTradeData> = Yup.object()
|
||||
.shape({
|
||||
ocean: Yup.number()
|
||||
.max(maximumOcean, (param) => `Must be more or equal to ${param.max}`)
|
||||
.max(
|
||||
Number(maximumOcean),
|
||||
(param) => `Must be more or equal to ${param.max}`
|
||||
)
|
||||
.min(0.001, (param) => `Must be more or equal to ${param.min}`)
|
||||
.required('Required')
|
||||
.nullable(),
|
||||
datatoken: Yup.number()
|
||||
.max(maximumDt, (param) => `Must be less or equal than ${param.max}`)
|
||||
.max(
|
||||
Number(maximumDt),
|
||||
(param) => `Must be less or equal than ${param.max}`
|
||||
)
|
||||
.min(0.00001, (param) => `Must be more or equal to ${param.min}`)
|
||||
.required('Required')
|
||||
.nullable(),
|
||||
@ -77,7 +83,9 @@ export default function FormTrade({
|
||||
|
||||
async function handleTrade(values: FormTradeData) {
|
||||
try {
|
||||
const impact = new Decimal(100 - Number(values.slippage)).div(100)
|
||||
const impact = new Decimal(
|
||||
new Decimal(100).sub(new Decimal(values.slippage))
|
||||
).div(100)
|
||||
const precision = 15
|
||||
const tx =
|
||||
values.type === 'buy'
|
||||
|
@ -5,6 +5,11 @@ import { useOcean } from '../../../../providers/Ocean'
|
||||
import Token from '../Pool/Token'
|
||||
import styles from './Output.module.css'
|
||||
|
||||
import { isValidNumber } from './../../../../utils/numberValidations'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||
|
||||
export default function Output({
|
||||
dtSymbol,
|
||||
poolAddress
|
||||
@ -25,12 +30,20 @@ export default function Output({
|
||||
|
||||
async function getSwapFee() {
|
||||
const swapFee = await ocean.pool.getSwapFee(poolAddress)
|
||||
|
||||
// swapFee is tricky: to get 0.1% you need to convert from 0.001
|
||||
setSwapFee(`${Number(swapFee) * 100}`)
|
||||
setSwapFee(
|
||||
isValidNumber(swapFee) ? new Decimal(swapFee).mul(100).toString() : '0'
|
||||
)
|
||||
|
||||
const value =
|
||||
values.type === 'buy'
|
||||
? Number(swapFee) * values.ocean
|
||||
: Number(swapFee) * values.datatoken
|
||||
? isValidNumber(swapFee) && isValidNumber(values.ocean)
|
||||
? new Decimal(swapFee).mul(new Decimal(values.ocean))
|
||||
: 0
|
||||
: isValidNumber(swapFee) && isValidNumber(values.datatoken)
|
||||
? new Decimal(swapFee).mul(new Decimal(values.datatoken))
|
||||
: 0
|
||||
setSwapFeeValue(value.toString())
|
||||
}
|
||||
getSwapFee()
|
||||
@ -46,8 +59,14 @@ export default function Output({
|
||||
const maxImpact = 1 - Number(values.slippage) / 100
|
||||
const maxPrice =
|
||||
values.type === 'buy'
|
||||
? (values.datatoken * maxImpact).toString()
|
||||
: (values.ocean * maxImpact).toString()
|
||||
? isValidNumber(values.datatoken) && isValidNumber(maxImpact)
|
||||
? new Decimal(values.datatoken)
|
||||
.mul(new Decimal(maxImpact))
|
||||
.toString()
|
||||
: '0'
|
||||
: isValidNumber(values.ocean) && isValidNumber(maxImpact)
|
||||
? new Decimal(values.ocean).mul(new Decimal(maxImpact)).toString()
|
||||
: '0'
|
||||
|
||||
setMaxOutput(maxPrice)
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
.priceImpact {
|
||||
font-size: var(--font-size-small);
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
padding: calc(var(--spacer) / 4) var(--spacer);
|
||||
color: var(--color-secondary);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
.priceImpact strong {
|
||||
font-weight: var(--font-weight-base);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.alert {
|
||||
color: var(--brand-alert-red);
|
||||
}
|
||||
|
||||
.number {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
65
src/components/organisms/AssetActions/Trade/PriceImpact.tsx
Normal file
65
src/components/organisms/AssetActions/Trade/PriceImpact.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Decimal from 'decimal.js'
|
||||
import Tooltip from '../../../atoms/Tooltip'
|
||||
import styles from './PriceImpact.module.css'
|
||||
|
||||
export default function PriceImpact({
|
||||
totalValue,
|
||||
tokenAmount,
|
||||
spotPrice
|
||||
}: {
|
||||
/// how much the user actually pays (doesn't matter witch token it is)
|
||||
totalValue: string
|
||||
/// how many tokens the user trades (doesn't matter witch token it is)
|
||||
tokenAmount: string
|
||||
/// the spot price of the traded token (doesn't matter witch token it is))
|
||||
spotPrice: string
|
||||
}): ReactElement {
|
||||
const [priceImpact, setPriceImpact] = useState<string>('0')
|
||||
|
||||
async function getPriceImpact(
|
||||
totalValue: string,
|
||||
tokenAmount: string,
|
||||
spotPrice: string
|
||||
) {
|
||||
const dtotalValue = new Decimal(totalValue)
|
||||
const dTokenAmount = new Decimal(tokenAmount)
|
||||
const dSpotPrice = new Decimal(spotPrice)
|
||||
let priceImpact = Decimal.abs(
|
||||
dtotalValue.div(dTokenAmount.times(dSpotPrice)).minus(1)
|
||||
).mul(100)
|
||||
if (priceImpact.isNaN()) priceImpact = new Decimal(0)
|
||||
return priceImpact.toDecimalPlaces(2, Decimal.ROUND_DOWN)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!totalValue || !tokenAmount || !spotPrice) {
|
||||
setPriceImpact('0')
|
||||
return
|
||||
}
|
||||
|
||||
async function init() {
|
||||
const newPriceImpact = await getPriceImpact(
|
||||
totalValue,
|
||||
tokenAmount,
|
||||
spotPrice
|
||||
)
|
||||
setPriceImpact(newPriceImpact.toString())
|
||||
}
|
||||
init()
|
||||
}, [totalValue, tokenAmount, spotPrice])
|
||||
|
||||
return (
|
||||
<div className={styles.priceImpact}>
|
||||
<strong>Price impact</strong>
|
||||
<div>
|
||||
<span
|
||||
className={`${styles.number} ${
|
||||
parseInt(priceImpact) > 5 && styles.alert
|
||||
}`}
|
||||
>{`${priceImpact}%`}</span>
|
||||
<Tooltip content="The difference between the market price and estimated price due to trade size." />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -4,13 +4,15 @@
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
padding: calc(var(--spacer) / 4) var(--spacer);
|
||||
text-align: center;
|
||||
color: var(--color-secondary);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
.slippage strong {
|
||||
font-weight: var(--font-weight-base);
|
||||
color: var(--font-color-heading);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.title {
|
||||
@ -25,6 +27,4 @@
|
||||
.slippage select {
|
||||
width: fit-content;
|
||||
display: inline-block;
|
||||
margin-left: calc(var(--spacer) / 4);
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { FormikContextType, useFormikContext } from 'formik'
|
||||
import React, { ChangeEvent, ReactElement, useEffect, useState } from 'react'
|
||||
import React, { ChangeEvent, ReactElement } from 'react'
|
||||
import { FormTradeData, slippagePresets } from '../../../../models/FormTrade'
|
||||
import InputElement from '../../../atoms/Input/InputElement'
|
||||
import Tooltip from '../../../atoms/Tooltip'
|
||||
import styles from './Slippage.module.css'
|
||||
|
||||
export default function Slippage(): ReactElement {
|
||||
@ -14,9 +15,9 @@ export default function Slippage(): ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.slippage}>
|
||||
<strong>Expected price impact</strong>
|
||||
<div className={styles.slippage}>
|
||||
<strong>Slippage Tolerance</strong>
|
||||
<div>
|
||||
<InputElement
|
||||
name="slippage"
|
||||
type="select"
|
||||
@ -27,7 +28,8 @@ export default function Slippage(): ReactElement {
|
||||
value={values.slippage}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Tooltip content="Your transaction will revert if the price changes unfavorably by more than this percentage." />
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -10,6 +10,11 @@ import Output from './Output'
|
||||
import Slippage from './Slippage'
|
||||
import { FormTradeData, TradeItem } from '../../../../models/FormTrade'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import PriceImpact from './PriceImpact'
|
||||
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||
|
||||
export default function Swap({
|
||||
ddo,
|
||||
@ -21,23 +26,23 @@ export default function Swap({
|
||||
setMaximumOcean
|
||||
}: {
|
||||
ddo: DDO
|
||||
maxDt: number
|
||||
maxOcean: number
|
||||
maxDt: string
|
||||
maxOcean: string
|
||||
balance: PoolBalance
|
||||
price: BestPrice
|
||||
setMaximumDt: (value: number) => void
|
||||
setMaximumOcean: (value: number) => void
|
||||
setMaximumDt: (value: string) => void
|
||||
setMaximumOcean: (value: string) => void
|
||||
}): ReactElement {
|
||||
const { ocean } = useOcean()
|
||||
const [oceanItem, setOceanItem] = useState<TradeItem>({
|
||||
amount: 0,
|
||||
amount: '0',
|
||||
token: 'OCEAN',
|
||||
maxAmount: 0
|
||||
maxAmount: '0'
|
||||
})
|
||||
const [dtItem, setDtItem] = useState<TradeItem>({
|
||||
amount: 0,
|
||||
amount: '0',
|
||||
token: ddo.dataTokenInfo.symbol,
|
||||
maxAmount: 0
|
||||
maxAmount: '0'
|
||||
})
|
||||
|
||||
const {
|
||||
@ -47,6 +52,11 @@ export default function Swap({
|
||||
validateForm
|
||||
}: FormikContextType<FormTradeData> = useFormikContext()
|
||||
|
||||
/// Values used for calculation of price impact
|
||||
const [spotPrice, setSpotPrice] = useState<string>()
|
||||
const [totalValue, setTotalValue] = useState<string>()
|
||||
const [tokenAmount, setTokenAmount] = useState<string>()
|
||||
///
|
||||
useEffect(() => {
|
||||
if (!ddo || !balance || !values || !price) return
|
||||
|
||||
@ -66,32 +76,32 @@ export default function Swap({
|
||||
const maximumDt =
|
||||
values.type === 'buy'
|
||||
? Number(dtAmount) > Number(maxBuyDt)
|
||||
? Number(maxBuyDt)
|
||||
: Number(dtAmount)
|
||||
: Number(dtAmount) > balance.datatoken
|
||||
? balance.datatoken
|
||||
: Number(dtAmount)
|
||||
? new Decimal(maxBuyDt)
|
||||
: new Decimal(dtAmount)
|
||||
: Number(dtAmount) > Number(balance.datatoken)
|
||||
? new Decimal(balance.datatoken)
|
||||
: new Decimal(dtAmount)
|
||||
|
||||
const maximumOcean =
|
||||
values.type === 'sell'
|
||||
? Number(oceanAmount) > Number(maxBuyOcean)
|
||||
? Number(maxBuyOcean)
|
||||
: Number(oceanAmount)
|
||||
: Number(oceanAmount) > balance.ocean
|
||||
? balance.ocean
|
||||
: Number(oceanAmount)
|
||||
? new Decimal(maxBuyOcean)
|
||||
: new Decimal(oceanAmount)
|
||||
: Number(oceanAmount) > Number(balance.ocean)
|
||||
? new Decimal(balance.ocean)
|
||||
: new Decimal(oceanAmount)
|
||||
|
||||
setMaximumDt(maximumDt)
|
||||
setMaximumOcean(maximumOcean)
|
||||
setMaximumDt(maximumDt.toString())
|
||||
setMaximumOcean(maximumOcean.toString())
|
||||
setOceanItem({
|
||||
...oceanItem,
|
||||
amount: oceanAmount,
|
||||
maxAmount: maximumOcean
|
||||
amount: oceanAmount.toString(),
|
||||
maxAmount: maximumOcean.toString()
|
||||
})
|
||||
setDtItem({
|
||||
...dtItem,
|
||||
amount: dtAmount,
|
||||
maxAmount: maximumDt
|
||||
amount: dtAmount.toString(),
|
||||
maxAmount: maximumDt.toString()
|
||||
})
|
||||
}
|
||||
calculateMaximum()
|
||||
@ -106,16 +116,63 @@ export default function Swap({
|
||||
}
|
||||
|
||||
const handleValueChange = async (name: string, value: number) => {
|
||||
const newValue =
|
||||
name === 'ocean'
|
||||
? values.type === 'sell'
|
||||
? await ocean.pool.getDTNeeded(price.address, value.toString())
|
||||
: await ocean.pool.getDTReceived(price.address, value.toString())
|
||||
: values.type === 'sell'
|
||||
? await ocean.pool.getOceanReceived(price.address, value.toString())
|
||||
: await ocean.pool.getOceanNeeded(price.address, value.toString())
|
||||
let tokenIn = ''
|
||||
let tokenOut = ''
|
||||
let newValue
|
||||
|
||||
setFieldValue(name === 'ocean' ? 'datatoken' : 'ocean', newValue)
|
||||
if (name === 'ocean') {
|
||||
if (values.type === 'sell') {
|
||||
newValue = await ocean.pool.getDTNeeded(price.address, value.toString())
|
||||
|
||||
setTotalValue(newValue)
|
||||
setTokenAmount(value.toString())
|
||||
|
||||
tokenIn = ddo.dataToken
|
||||
tokenOut = ocean.pool.oceanAddress
|
||||
} else {
|
||||
newValue = await ocean.pool.getDTReceived(
|
||||
price.address,
|
||||
value.toString()
|
||||
)
|
||||
|
||||
setTotalValue(value.toString())
|
||||
setTokenAmount(newValue)
|
||||
tokenIn = ocean.pool.oceanAddress
|
||||
tokenOut = ddo.dataToken
|
||||
}
|
||||
} else {
|
||||
if (values.type === 'sell') {
|
||||
newValue = await ocean.pool.getOceanReceived(
|
||||
price.address,
|
||||
value.toString()
|
||||
)
|
||||
|
||||
setTotalValue(value.toString())
|
||||
setTokenAmount(newValue)
|
||||
tokenIn = ddo.dataToken
|
||||
tokenOut = ocean.pool.oceanAddress
|
||||
} else {
|
||||
newValue = await ocean.pool.getOceanNeeded(
|
||||
price.address,
|
||||
value.toString()
|
||||
)
|
||||
|
||||
setTotalValue(newValue)
|
||||
setTokenAmount(value.toString())
|
||||
tokenIn = ocean.pool.oceanAddress
|
||||
tokenOut = ddo.dataToken
|
||||
}
|
||||
}
|
||||
|
||||
await setFieldValue(name === 'ocean' ? 'datatoken' : 'ocean', newValue)
|
||||
|
||||
const spotPrice = await ocean.pool.getSpotPrice(
|
||||
price.address,
|
||||
tokenIn,
|
||||
tokenOut
|
||||
)
|
||||
|
||||
setSpotPrice(spotPrice)
|
||||
validateForm()
|
||||
}
|
||||
|
||||
@ -139,6 +196,11 @@ export default function Swap({
|
||||
|
||||
<Output dtSymbol={dtItem.token} poolAddress={price?.address} />
|
||||
|
||||
<PriceImpact
|
||||
totalValue={totalValue}
|
||||
tokenAmount={tokenAmount}
|
||||
spotPrice={spotPrice}
|
||||
/>
|
||||
<Slippage />
|
||||
</div>
|
||||
)
|
||||
|
@ -73,7 +73,7 @@ export default function TradeInput({
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setFieldValue(name, item?.maxAmount)
|
||||
handleValueChange(name, item?.maxAmount)
|
||||
handleValueChange(name, Number(item?.maxAmount))
|
||||
}}
|
||||
>
|
||||
Use Max
|
||||
|
@ -5,13 +5,18 @@ import { useAsset } from '../../../../providers/Asset'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
|
||||
import { isValidNumber } from './../../../../utils/numberValidations'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||
|
||||
export default function Trade(): ReactElement {
|
||||
const { accountId, balance } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
const [tokenBalance, setTokenBalance] = useState<PoolBalance>()
|
||||
const { price, ddo } = useAsset()
|
||||
const [maxDt, setMaxDt] = useState(0)
|
||||
const [maxOcean, setMaxOcean] = useState(0)
|
||||
const [maxDt, setMaxDt] = useState('0')
|
||||
const [maxOcean, setMaxOcean] = useState('0')
|
||||
|
||||
// Get datatoken balance, and combine with OCEAN balance from hooks into one object
|
||||
useEffect(() => {
|
||||
@ -20,8 +25,8 @@ export default function Trade(): ReactElement {
|
||||
async function getTokenBalance() {
|
||||
const dtBalance = await ocean.datatokens.balance(ddo.dataToken, accountId)
|
||||
setTokenBalance({
|
||||
ocean: Number(balance.ocean),
|
||||
datatoken: Number(dtBalance)
|
||||
ocean: new Decimal(balance.ocean).toString(),
|
||||
datatoken: new Decimal(dtBalance).toString()
|
||||
})
|
||||
}
|
||||
getTokenBalance()
|
||||
@ -35,12 +40,20 @@ export default function Trade(): ReactElement {
|
||||
const maxTokensInPool = await ocean.pool.getDTMaxBuyQuantity(
|
||||
price.address
|
||||
)
|
||||
setMaxDt(Number(maxTokensInPool))
|
||||
setMaxDt(
|
||||
isValidNumber(maxTokensInPool)
|
||||
? new Decimal(maxTokensInPool).toString()
|
||||
: '0'
|
||||
)
|
||||
|
||||
const maxOceanInPool = await ocean.pool.getOceanMaxBuyQuantity(
|
||||
price.address
|
||||
)
|
||||
setMaxOcean(Number(maxOceanInPool))
|
||||
setMaxOcean(
|
||||
isValidNumber(maxOceanInPool)
|
||||
? new Decimal(maxOceanInPool).toString()
|
||||
: '0'
|
||||
)
|
||||
}
|
||||
getMaximum()
|
||||
}, [ocean, balance.ocean, price])
|
||||
|
@ -14,10 +14,11 @@ import { useWeb3 } from '../../../providers/Web3'
|
||||
import Web3Feedback from '../../molecules/Web3Feedback'
|
||||
import { getFileInfo } from '../../../utils/provider'
|
||||
import axios from 'axios'
|
||||
import { getOceanConfig } from '../../../utils/ocean'
|
||||
|
||||
export default function AssetActions(): ReactElement {
|
||||
const { accountId, balance } = useWeb3()
|
||||
const { ocean, config, account } = useOcean()
|
||||
const { ocean, account } = useOcean()
|
||||
const { price, ddo, isAssetNetwork } = useAsset()
|
||||
|
||||
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
|
||||
@ -30,9 +31,10 @@ export default function AssetActions(): ReactElement {
|
||||
const [consumableFeedback, setConsumableFeedback] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
if (!ddo || !accountId) return
|
||||
if (!ddo || !accountId || !ocean || !isAssetNetwork) return
|
||||
|
||||
async function checkIsConsumable() {
|
||||
const consumable: any = await ocean.assets.isConsumable(
|
||||
const consumable = await ocean.assets.isConsumable(
|
||||
ddo,
|
||||
accountId.toLowerCase()
|
||||
)
|
||||
@ -42,17 +44,20 @@ export default function AssetActions(): ReactElement {
|
||||
}
|
||||
}
|
||||
checkIsConsumable()
|
||||
}, [accountId, ddo])
|
||||
}, [accountId, isAssetNetwork, ddo, ocean])
|
||||
|
||||
useEffect(() => {
|
||||
if (!config) return
|
||||
const oceanConfig = getOceanConfig(ddo.chainId)
|
||||
if (!oceanConfig) return
|
||||
|
||||
const source = axios.CancelToken.source()
|
||||
|
||||
async function initFileInfo() {
|
||||
setFileIsLoading(true)
|
||||
try {
|
||||
const fileInfo = await getFileInfo(
|
||||
DID.parse(`${ddo.id}`),
|
||||
config.providerUri,
|
||||
oceanConfig.providerUri,
|
||||
source.token
|
||||
)
|
||||
|
||||
@ -60,15 +65,16 @@ export default function AssetActions(): ReactElement {
|
||||
} catch (error) {
|
||||
Logger.error(error.message)
|
||||
} finally {
|
||||
// this triggers a memory leak warrning, no idea how to fix
|
||||
// this triggers a memory leak warning, no idea how to fix
|
||||
setFileIsLoading(false)
|
||||
}
|
||||
}
|
||||
initFileInfo()
|
||||
|
||||
return () => {
|
||||
source.cancel()
|
||||
}
|
||||
}, [config, ddo])
|
||||
}, [ddo])
|
||||
|
||||
// Get and set user DT balance
|
||||
useEffect(() => {
|
||||
|
@ -2,10 +2,10 @@ import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { useAsset } from '../../../providers/Asset'
|
||||
import ExplorerLink from '../../atoms/ExplorerLink'
|
||||
import Time from '../../atoms/Time'
|
||||
import styles from './EditHistory.module.css'
|
||||
import { gql, useQuery } from 'urql'
|
||||
import { gql, OperationContext, useQuery } from 'urql'
|
||||
import { ReceiptData_datatokens_updates as ReceiptData } from '../../../@types/apollo/ReceiptData'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import { getQueryContext } from '../../../utils/subgraph'
|
||||
import styles from './EditHistory.module.css'
|
||||
|
||||
const getReceipts = gql`
|
||||
query ReceiptData($address: ID!) {
|
||||
@ -20,14 +20,32 @@ const getReceipts = gql`
|
||||
`
|
||||
|
||||
export default function EditHistory(): ReactElement {
|
||||
const { networkId } = useWeb3()
|
||||
const { ddo } = useAsset()
|
||||
|
||||
//
|
||||
// 1. Construct subgraph query based on DDO.
|
||||
// Need to wait for it to avoid infinite rerender loop with useQuery.
|
||||
//
|
||||
const [queryContext, setQueryContext] = useState<OperationContext>()
|
||||
|
||||
useEffect(() => {
|
||||
if (!ddo) return
|
||||
|
||||
const queryContext = getQueryContext(ddo.chainId)
|
||||
setQueryContext(queryContext)
|
||||
}, [ddo])
|
||||
|
||||
const [result] = useQuery({
|
||||
query: getReceipts,
|
||||
variables: { address: ddo?.dataToken.toLowerCase() }
|
||||
variables: { address: ddo?.dataToken.toLowerCase() },
|
||||
context: queryContext,
|
||||
pause: !ddo || !queryContext
|
||||
})
|
||||
const { data } = result
|
||||
|
||||
//
|
||||
// 2. Construct display data based on fetched data.
|
||||
//
|
||||
const [receipts, setReceipts] = useState<ReceiptData[]>()
|
||||
const [creationTx, setCreationTx] = useState<string>()
|
||||
|
||||
@ -51,8 +69,7 @@ export default function EditHistory(): ReactElement {
|
||||
{receipts?.map((receipt) => (
|
||||
<li key={receipt.id} className={styles.item}>
|
||||
<ExplorerLink networkId={ddo.chainId} path={`/tx/${receipt.tx}`}>
|
||||
edited{' '}
|
||||
<Time date={receipt.timestamp.toString()} relative isUnix />
|
||||
edited <Time date={`${receipt.timestamp}`} relative isUnix />
|
||||
</ExplorerLink>
|
||||
</li>
|
||||
))}
|
||||
|
@ -12,6 +12,11 @@ import { DDO } from '@oceanprotocol/lib'
|
||||
import FormHelp from '../../../../atoms/Input/Help'
|
||||
import { useSiteMetadata } from '../../../../../hooks/useSiteMetadata'
|
||||
|
||||
import { isValidNumber } from './../../../../../utils/numberValidations'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||
|
||||
export default function FormPricing({
|
||||
ddo,
|
||||
setShowPricing,
|
||||
@ -41,8 +46,15 @@ export default function FormPricing({
|
||||
useEffect(() => {
|
||||
if (type === 'fixed') return
|
||||
const dtAmount =
|
||||
(Number(oceanAmount) / Number(weightOnOcean) / price) *
|
||||
Number(weightOnDataToken)
|
||||
isValidNumber(oceanAmount) &&
|
||||
isValidNumber(weightOnOcean) &&
|
||||
isValidNumber(price) &&
|
||||
isValidNumber(weightOnDataToken)
|
||||
? new Decimal(oceanAmount)
|
||||
.dividedBy(new Decimal(weightOnOcean))
|
||||
.dividedBy(new Decimal(price))
|
||||
.mul(new Decimal(weightOnDataToken))
|
||||
: 0
|
||||
|
||||
setFieldValue('dtAmount', dtAmount)
|
||||
}, [price, oceanAmount, weightOnOcean, weightOnDataToken, type])
|
||||
|
@ -50,6 +50,7 @@ export default function AssetContent(props: AssetContentProps): ReactElement {
|
||||
const { owner, isInPurgatory, purgatoryData, isAssetNetwork } = useAsset()
|
||||
const [showPricing, setShowPricing] = useState(false)
|
||||
const [showEdit, setShowEdit] = useState<boolean>()
|
||||
const [isComputeType, setIsComputeType] = useState<boolean>(false)
|
||||
const [showEditCompute, setShowEditCompute] = useState<boolean>()
|
||||
const [showEditAdvancedSettings, setShowEditAdvancedSettings] =
|
||||
useState<boolean>()
|
||||
@ -63,7 +64,8 @@ export default function AssetContent(props: AssetContentProps): ReactElement {
|
||||
const isOwner = accountId.toLowerCase() === owner.toLowerCase()
|
||||
setIsOwner(isOwner)
|
||||
setShowPricing(isOwner && price.type === '')
|
||||
}, [accountId, price, owner])
|
||||
setIsComputeType(Boolean(ddo.findServiceByType('compute')))
|
||||
}, [accountId, price, owner, ddo])
|
||||
|
||||
function handleEditButton() {
|
||||
// move user's focus to top of screen
|
||||
@ -82,7 +84,7 @@ export default function AssetContent(props: AssetContentProps): ReactElement {
|
||||
}
|
||||
|
||||
return showEdit ? (
|
||||
<Edit setShowEdit={setShowEdit} />
|
||||
<Edit setShowEdit={setShowEdit} isComputeType={isComputeType} />
|
||||
) : showEditCompute ? (
|
||||
<EditComputeDataset setShowEdit={setShowEditCompute} />
|
||||
) : showEditAdvancedSettings ? (
|
||||
|
@ -6,6 +6,7 @@ import { DDO } from '@oceanprotocol/lib'
|
||||
import classNames from 'classnames/bind'
|
||||
import { getAssetsBestPrices, AssetListPrices } from '../../utils/subgraph'
|
||||
import Loader from '../atoms/Loader'
|
||||
import { useUserPreferences } from '../../providers/UserPreferences'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
@ -36,6 +37,7 @@ const AssetList: React.FC<AssetListProps> = ({
|
||||
onPageChange,
|
||||
className
|
||||
}) => {
|
||||
const { chainIds } = useUserPreferences()
|
||||
const [assetsWithPrices, setAssetWithPrices] = useState<AssetListPrices[]>()
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
|
||||
@ -71,6 +73,8 @@ const AssetList: React.FC<AssetListProps> = ({
|
||||
key={assetWithPrice.ddo.id}
|
||||
/>
|
||||
))
|
||||
) : chainIds.length === 0 ? (
|
||||
<div className={styles.empty}>No network selected.</div>
|
||||
) : (
|
||||
<div className={styles.empty}>No results found.</div>
|
||||
)}
|
||||
|
@ -17,6 +17,10 @@ import { fetchDataForMultipleChains } from '../../../../utils/subgraph'
|
||||
import NetworkName from '../../../atoms/NetworkName'
|
||||
import axios from 'axios'
|
||||
import { retrieveDDO } from '../../../../utils/aquarius'
|
||||
import { isValidNumber } from '../../../../utils/numberValidations'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||
|
||||
const REFETCH_INTERVAL = 20000
|
||||
|
||||
@ -90,11 +94,16 @@ function Liquidity({ row, type }: { row: Asset; type: string }) {
|
||||
).toString()
|
||||
}
|
||||
if (type === 'pool') {
|
||||
price = `${
|
||||
Number(row.poolShare.poolId.oceanReserve) +
|
||||
Number(row.poolShare.poolId.datatokenReserve) *
|
||||
row.poolShare.poolId.consumePrice
|
||||
}`
|
||||
price =
|
||||
isValidNumber(row.poolShare.poolId.oceanReserve) &&
|
||||
isValidNumber(row.poolShare.poolId.datatokenReserve) &&
|
||||
isValidNumber(row.poolShare.poolId.consumePrice)
|
||||
? new Decimal(row.poolShare.poolId.datatokenReserve)
|
||||
.mul(new Decimal(row.poolShare.poolId.consumePrice))
|
||||
.plus(row.poolShare.poolId.oceanReserve)
|
||||
.toString()
|
||||
: '0'
|
||||
|
||||
oceanTokenBalance = row.poolShare.poolId.oceanReserve.toString()
|
||||
dataTokenBalance = row.poolShare.poolId.datatokenReserve.toString()
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import SearchBar from '../molecules/SearchBar'
|
||||
import styles from './Home.module.css'
|
||||
import AssetList from '../organisms/AssetList'
|
||||
import {
|
||||
QueryResult,
|
||||
SearchQuery
|
||||
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import Container from '../atoms/Container'
|
||||
import Button from '../atoms/Button'
|
||||
import Bookmarks from '../molecules/Bookmarks'
|
||||
import axios from 'axios'
|
||||
@ -19,14 +16,15 @@ import { getHighestLiquidityDIDs } from '../../utils/subgraph'
|
||||
import { DDO, Logger } from '@oceanprotocol/lib'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
import { useUserPreferences } from '../../providers/UserPreferences'
|
||||
import styles from './Home.module.css'
|
||||
|
||||
async function getQueryHighest(
|
||||
chainIds: number[]
|
||||
): Promise<[SearchQuery, string]> {
|
||||
const dids = await getHighestLiquidityDIDs(chainIds)
|
||||
const [dids, didsLength] = await getHighestLiquidityDIDs(chainIds)
|
||||
const queryHighest = {
|
||||
page: 1,
|
||||
offset: dids.length,
|
||||
offset: didsLength,
|
||||
query: {
|
||||
query_string: {
|
||||
query: `(${dids}) AND (${transformChainIdsListToQuery(
|
||||
@ -74,29 +72,40 @@ function SectionQueryResult({
|
||||
queryData?: string
|
||||
}) {
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const { chainIds } = useUserPreferences()
|
||||
const [result, setResult] = useState<QueryResult>()
|
||||
const [loading, setLoading] = useState<boolean>()
|
||||
const { chainIds } = useUserPreferences()
|
||||
|
||||
useEffect(() => {
|
||||
if (!appConfig.metadataCacheUri) return
|
||||
const source = axios.CancelToken.source()
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const result = await queryMetadata(query, source.token)
|
||||
if (queryData && result.totalResults > 0) {
|
||||
const searchDIDs = queryData.split(' ')
|
||||
const sortedAssets = sortElements(result.results, searchDIDs)
|
||||
const overflow = sortedAssets.length - 9
|
||||
sortedAssets.splice(sortedAssets.length - overflow, overflow)
|
||||
result.results = sortedAssets
|
||||
if (chainIds.length === 0) {
|
||||
const result: QueryResult = {
|
||||
results: [],
|
||||
page: 0,
|
||||
totalPages: 0,
|
||||
totalResults: 0
|
||||
}
|
||||
setResult(result)
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
Logger.log(error.message)
|
||||
} else {
|
||||
try {
|
||||
setLoading(true)
|
||||
const result = await queryMetadata(query, source.token)
|
||||
if (queryData && result.totalResults > 0) {
|
||||
const searchDIDs = queryData.split(' ')
|
||||
const sortedAssets = sortElements(result.results, searchDIDs)
|
||||
const overflow = sortedAssets.length - 9
|
||||
sortedAssets.splice(sortedAssets.length - overflow, overflow)
|
||||
result.results = sortedAssets
|
||||
}
|
||||
setResult(result)
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
Logger.error(error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
init()
|
||||
|
@ -23,6 +23,7 @@ export default function Debug({
|
||||
{
|
||||
index: 1,
|
||||
type: values.access,
|
||||
serviceEndpoint: values.providerUri,
|
||||
attributes: {}
|
||||
}
|
||||
]
|
||||
|
@ -11,6 +11,7 @@ import Input from '../../atoms/Input'
|
||||
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||
import { MetadataPublishFormAlgorithm } from '../../../@types/MetaData'
|
||||
import { initialValues as initialValuesAlgorithm } from '../../../models/FormAlgoPublish'
|
||||
import AdvancedSettings from '../../molecules/FormFields/AdvancedSettings'
|
||||
import FormTitle from './FormTitle'
|
||||
import FormActions from './FormActions'
|
||||
import styles from './FormPublish.module.css'
|
||||
@ -33,6 +34,7 @@ const query = graphql`
|
||||
required
|
||||
sortOptions
|
||||
options
|
||||
advanced
|
||||
}
|
||||
warning
|
||||
}
|
||||
@ -145,6 +147,7 @@ export default function FormPublish(): ReactElement {
|
||||
|
||||
{content.data.map(
|
||||
(field: FormFieldProps) =>
|
||||
field.advanced !== true &&
|
||||
((field.name !== 'entrypoint' &&
|
||||
field.name !== 'image' &&
|
||||
field.name !== 'containerTag') ||
|
||||
@ -164,7 +167,10 @@ export default function FormPublish(): ReactElement {
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
<AdvancedSettings
|
||||
content={content}
|
||||
handleFieldChange={handleFieldChange}
|
||||
/>
|
||||
<FormActions
|
||||
isValid={isValid}
|
||||
resetFormAndClearStorage={resetFormAndClearStorage}
|
||||
|
@ -1,16 +1,22 @@
|
||||
import React, { ReactElement, useEffect, FormEvent, ChangeEvent } from 'react'
|
||||
import React, {
|
||||
ReactElement,
|
||||
useEffect,
|
||||
FormEvent,
|
||||
ChangeEvent,
|
||||
useState
|
||||
} from 'react'
|
||||
import { useStaticQuery, graphql } from 'gatsby'
|
||||
import { useFormikContext, Field, Form, FormikContextType } from 'formik'
|
||||
import Input from '../../atoms/Input'
|
||||
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||
import { MetadataPublishFormDataset } from '../../../@types/MetaData'
|
||||
import { initialValues as initialValuesDataset } from '../../../models/FormAlgoPublish'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { ReactComponent as Download } from '../../../images/download.svg'
|
||||
import { ReactComponent as Compute } from '../../../images/compute.svg'
|
||||
import FormTitle from './FormTitle'
|
||||
import FormActions from './FormActions'
|
||||
import styles from './FormPublish.module.css'
|
||||
import AdvancedSettings from '../../molecules/FormFields/AdvancedSettings'
|
||||
|
||||
const query = graphql`
|
||||
query {
|
||||
@ -30,6 +36,7 @@ const query = graphql`
|
||||
required
|
||||
sortOptions
|
||||
options
|
||||
advanced
|
||||
}
|
||||
warning
|
||||
}
|
||||
@ -47,6 +54,7 @@ export default function FormPublish(): ReactElement {
|
||||
status,
|
||||
setStatus,
|
||||
isValid,
|
||||
values,
|
||||
setErrors,
|
||||
setTouched,
|
||||
resetForm,
|
||||
@ -54,6 +62,8 @@ export default function FormPublish(): ReactElement {
|
||||
setFieldValue
|
||||
}: FormikContextType<MetadataPublishFormDataset> = useFormikContext()
|
||||
|
||||
const [computeTypeSelected, setComputeTypeSelected] = useState<boolean>(false)
|
||||
|
||||
// reset form validation on every mount
|
||||
useEffect(() => {
|
||||
setErrors({})
|
||||
@ -75,6 +85,8 @@ export default function FormPublish(): ReactElement {
|
||||
}
|
||||
]
|
||||
|
||||
const computeTypeOptions = ['1 day', '1 week', '1 month', '1 year']
|
||||
|
||||
// Manually handle change events instead of using `handleChange` from Formik.
|
||||
// Workaround for default `validateOnChange` not kicking in
|
||||
function handleFieldChange(
|
||||
@ -84,6 +96,16 @@ export default function FormPublish(): ReactElement {
|
||||
const value =
|
||||
field.type === 'terms' ? !JSON.parse(e.target.value) : e.target.value
|
||||
|
||||
if (field.name === 'access' && value === 'Compute') {
|
||||
setComputeTypeSelected(true)
|
||||
if (values.timeout === 'Forever')
|
||||
setFieldValue('timeout', computeTypeOptions[0])
|
||||
} else {
|
||||
if (field.name === 'access' && value === 'Download') {
|
||||
setComputeTypeSelected(false)
|
||||
}
|
||||
}
|
||||
|
||||
validateField(field.name)
|
||||
setFieldValue(field.name, value)
|
||||
}
|
||||
@ -105,19 +127,30 @@ export default function FormPublish(): ReactElement {
|
||||
>
|
||||
<FormTitle title={content.title} />
|
||||
|
||||
{content.data.map((field: FormFieldProps) => (
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.type === 'boxSelection' ? accessTypeOptions : field.options
|
||||
}
|
||||
component={Input}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleFieldChange(e, field)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{content.data.map(
|
||||
(field: FormFieldProps) =>
|
||||
field.advanced !== true && (
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.type === 'boxSelection'
|
||||
? accessTypeOptions
|
||||
: field.name === 'timeout' && computeTypeSelected === true
|
||||
? computeTypeOptions
|
||||
: field.options
|
||||
}
|
||||
component={Input}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleFieldChange(e, field)
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<AdvancedSettings
|
||||
content={content}
|
||||
handleFieldChange={handleFieldChange}
|
||||
/>
|
||||
|
||||
<FormActions
|
||||
isValid={isValid}
|
||||
|
@ -132,14 +132,16 @@ export default function PublishPage({
|
||||
'Publish with ',
|
||||
metadata,
|
||||
serviceType,
|
||||
values.dataTokenOptions
|
||||
values.dataTokenOptions,
|
||||
values.providerUri
|
||||
)
|
||||
|
||||
const ddo = await publish(
|
||||
metadata as unknown as Metadata,
|
||||
serviceType,
|
||||
values.dataTokenOptions,
|
||||
timeout
|
||||
timeout,
|
||||
values.providerUri
|
||||
)
|
||||
|
||||
// Publish failed
|
||||
|
@ -32,6 +32,7 @@ interface UseSiteMetadata {
|
||||
allowFreePricing: string
|
||||
allowAdvancedSettings: string
|
||||
credentialType: string
|
||||
allowAdvancedPublishSettings: string
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,6 +69,7 @@ const query = graphql`
|
||||
allowDynamicPricing
|
||||
allowFreePricing
|
||||
allowAdvancedSettings
|
||||
allowAdvancedPublishSettings
|
||||
credentialType
|
||||
}
|
||||
}
|
||||
|
@ -51,5 +51,6 @@ export const initialValues: Partial<MetadataPublishFormAlgorithm> = {
|
||||
algorithmPrivacy: false,
|
||||
termsAndConditions: false,
|
||||
tags: '',
|
||||
timeout: 'Forever'
|
||||
timeout: 'Forever',
|
||||
providerUri: ''
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ export const validationSchema: Yup.SchemaOf<MetadataPublishFormDataset> =
|
||||
.matches(/Compute|Download/g, { excludeEmptyString: true })
|
||||
.required('Required'),
|
||||
termsAndConditions: Yup.boolean().required('Required'),
|
||||
|
||||
// ---- optional fields ----
|
||||
tags: Yup.string().nullable(),
|
||||
links: Yup.array<FileMetadata[]>().nullable()
|
||||
links: Yup.array<FileMetadata[]>().nullable(),
|
||||
providerUri: Yup.string().url().nullable()
|
||||
})
|
||||
.defined()
|
||||
|
||||
@ -44,5 +44,6 @@ export const initialValues: Partial<MetadataPublishFormDataset> = {
|
||||
timeout: 'Forever',
|
||||
access: '',
|
||||
termsAndConditions: false,
|
||||
tags: ''
|
||||
tags: '',
|
||||
providerUri: ''
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ export interface FormTradeData extends PoolBalance {
|
||||
}
|
||||
|
||||
export interface TradeItem {
|
||||
amount: number
|
||||
amount: string
|
||||
token: string
|
||||
maxAmount: number
|
||||
maxAmount: string
|
||||
}
|
||||
|
||||
export const initialValues: FormTradeData = {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { createClient, Provider, Client } from 'urql'
|
||||
import React, { useState, useEffect, ReactNode, ReactElement } from 'react'
|
||||
import { useWeb3 } from './Web3'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { getOceanConfig } from '../utils/ocean'
|
||||
|
||||
@ -22,11 +21,16 @@ export default function UrqlClientProvider({
|
||||
}: {
|
||||
children: ReactNode
|
||||
}): ReactElement {
|
||||
const { networkId } = useWeb3()
|
||||
//
|
||||
// Set a default client here based on ETH Mainnet, as that's required for
|
||||
// urql to work.
|
||||
// Throughout code base this client is then used and altered by passing
|
||||
// a new queryContext holding different subgraph URLs.
|
||||
//
|
||||
const [client, setClient] = useState<Client>()
|
||||
|
||||
useEffect(() => {
|
||||
const oceanConfig = getOceanConfig(networkId || 1)
|
||||
const oceanConfig = getOceanConfig(1)
|
||||
|
||||
if (!oceanConfig?.subgraphUri) {
|
||||
Logger.error(
|
||||
@ -39,8 +43,9 @@ export default function UrqlClientProvider({
|
||||
urqlClient = newClient
|
||||
setClient(newClient)
|
||||
Logger.log(`[URQL] Client connected to ${oceanConfig.subgraphUri}`)
|
||||
}, [networkId])
|
||||
}, [])
|
||||
|
||||
return client ? <Provider value={client}>{children}</Provider> : <></>
|
||||
}
|
||||
|
||||
export { UrqlClientProvider }
|
||||
|
@ -8,7 +8,8 @@ export default function compareAsBN(balance: string, price: string): boolean {
|
||||
const compare = aBN.comparedTo(bBN)
|
||||
|
||||
switch (compare) {
|
||||
case 1 || 0: // balance is greater or equal to price
|
||||
case 1: // balance is greater than price
|
||||
case 0: // balance is equal to price
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
@ -42,6 +42,7 @@ export function secondsToString(numberOfSeconds: number): string {
|
||||
if (numberOfSeconds === 0) return 'Forever'
|
||||
|
||||
const years = Math.floor(numberOfSeconds / 31536000)
|
||||
const months = Math.floor((numberOfSeconds %= 31536000) / 2630000)
|
||||
const weeks = Math.floor((numberOfSeconds %= 31536000) / 604800)
|
||||
const days = Math.floor((numberOfSeconds %= 604800) / 86400)
|
||||
const hours = Math.floor((numberOfSeconds %= 86400) / 3600)
|
||||
@ -50,6 +51,8 @@ export function secondsToString(numberOfSeconds: number): string {
|
||||
|
||||
return years
|
||||
? `${years} year${numberEnding(years)}`
|
||||
: months
|
||||
? `${months} month${numberEnding(months)}`
|
||||
: weeks
|
||||
? `${weeks} week${numberEnding(weeks)}`
|
||||
: days
|
||||
|
8
src/utils/numberValidations.ts
Normal file
8
src/utils/numberValidations.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export function isValidNumber(value: any): boolean {
|
||||
const isUndefinedValue = typeof value === 'undefined'
|
||||
const isNullValue = value === null
|
||||
const isNaNValue = isNaN(Number(value))
|
||||
const isEmptyString = value === ''
|
||||
|
||||
return !isUndefinedValue && !isNullValue && !isNaNValue && !isEmptyString
|
||||
}
|
@ -141,17 +141,17 @@ const HighestLiquidityAssets = gql`
|
||||
}
|
||||
`
|
||||
|
||||
export function getSubgrahUri(chainId: number): string {
|
||||
export function getSubgraphUri(chainId: number): string {
|
||||
const config = getOceanConfig(chainId)
|
||||
return config.subgraphUri
|
||||
}
|
||||
|
||||
export function getQueryContext(chainId: number): OperationContext {
|
||||
const queryContext: OperationContext = {
|
||||
url: `${getSubgrahUri(
|
||||
url: `${getSubgraphUri(
|
||||
Number(chainId)
|
||||
)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
|
||||
requestPolicy: 'network-only'
|
||||
requestPolicy: 'cache-and-network'
|
||||
}
|
||||
|
||||
return queryContext
|
||||
@ -180,7 +180,7 @@ export async function fetchDataForMultipleChains(
|
||||
let datas: any[] = []
|
||||
for (const chainId of chainIds) {
|
||||
const context: OperationContext = {
|
||||
url: `${getSubgrahUri(
|
||||
url: `${getSubgraphUri(
|
||||
chainId
|
||||
)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
|
||||
requestPolicy: 'network-only'
|
||||
@ -410,6 +410,21 @@ export async function getPrice(asset: DDO): Promise<BestPrice> {
|
||||
return bestPrice
|
||||
}
|
||||
|
||||
export async function getSpotPrice(asset: DDO): Promise<number> {
|
||||
const poolVariables = {
|
||||
datatokenAddress: asset?.dataToken.toLowerCase()
|
||||
}
|
||||
const queryContext = getQueryContext(Number(asset.chainId))
|
||||
|
||||
const poolPriceResponse: OperationResult<AssetsPoolPrice> = await fetchData(
|
||||
AssetPoolPriceQuerry,
|
||||
poolVariables,
|
||||
queryContext
|
||||
)
|
||||
|
||||
return poolPriceResponse.data.pools[0].spotPrice
|
||||
}
|
||||
|
||||
export async function getAssetsBestPrices(
|
||||
assets: DDO[]
|
||||
): Promise<AssetListPrices[]> {
|
||||
@ -454,7 +469,7 @@ export async function getAssetsBestPrices(
|
||||
|
||||
export async function getHighestLiquidityDIDs(
|
||||
chainIds: number[]
|
||||
): Promise<string> {
|
||||
): Promise<[string, number]> {
|
||||
const didList: string[] = []
|
||||
let highestLiquidiyAssets: HighestLiquidityAssetsPools[] = []
|
||||
for (const chain of chainIds) {
|
||||
@ -480,5 +495,5 @@ export async function getHighestLiquidityDIDs(
|
||||
.replace(/"/g, '')
|
||||
.replace(/(\[|\])/g, '')
|
||||
.replace(/(did:op:)/g, '0x')
|
||||
return searchDids
|
||||
return [searchDids, didList.length]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user