mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
merge v4 branch into c2d restore
This commit is contained in:
commit
ff1f953ffe
9307
package-lock.json
generated
9307
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@ -21,21 +21,21 @@
|
||||
"@coingecko/cryptoformat": "^0.4.4",
|
||||
"@loadable/component": "^5.15.2",
|
||||
"@oceanprotocol/art": "^3.2.0",
|
||||
"@oceanprotocol/lib": "^1.0.0-next.8",
|
||||
"@oceanprotocol/lib": "^1.0.0-next.10",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@portis/web3": "^4.0.6",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@urql/introspection": "^0.3.1",
|
||||
"@walletconnect/web3-provider": "^1.7.1",
|
||||
"axios": "^0.24.0",
|
||||
"axios": "^0.25.0",
|
||||
"bignumber.js": "^9.0.2",
|
||||
"chart.js": "^2.9.4",
|
||||
"chart.js": "^3.7.0",
|
||||
"classnames": "^2.3.1",
|
||||
"d3": "^7.3.0",
|
||||
"date-fns": "^2.28.0",
|
||||
"decimal.js": "^10.3.1",
|
||||
"dom-confetti": "^0.2.2",
|
||||
"dotenv": "^14.1.0",
|
||||
"dotenv": "^15.0.0",
|
||||
"ethereum-address": "0.0.4",
|
||||
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
||||
"filesize": "^8.0.6",
|
||||
@ -46,11 +46,11 @@
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"next": "^12.0.8",
|
||||
"next": "^12.0.9",
|
||||
"query-string": "^7.1.0",
|
||||
"querystring": "^0.2.1",
|
||||
"react": "^17.0.2",
|
||||
"react-chartjs-2": "^2.11.2",
|
||||
"react-chartjs-2": "^4.0.1",
|
||||
"react-clipboard.js": "^2.0.16",
|
||||
"react-data-table-component": "^6.11.7",
|
||||
"react-dom": "^17.0.2",
|
||||
@ -65,22 +65,22 @@
|
||||
"remark-html": "^13.0.1",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"slugify": "^1.6.5",
|
||||
"swr": "^1.1.2",
|
||||
"urql": "^2.0.6",
|
||||
"swr": "^1.2.0",
|
||||
"urql": "^2.1.1",
|
||||
"use-dark-mode": "^2.3.1",
|
||||
"web3": "^1.6.1",
|
||||
"web3modal": "^1.9.5",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/webpack": "^6.2.0",
|
||||
"@svgr/webpack": "^6.2.1",
|
||||
"@types/chart.js": "^2.9.35",
|
||||
"@types/d3": "^7.1.0",
|
||||
"@types/js-cookie": "^3.0.1",
|
||||
"@types/loadable__component": "^5.13.1",
|
||||
"@types/lodash.debounce": "^4.0.3",
|
||||
"@types/lodash.omit": "^4.5.6",
|
||||
"@types/node": "^17.0.8",
|
||||
"@types/node": "^17.0.13",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
|
@ -226,7 +226,7 @@ function ProfileProvider({
|
||||
|
||||
for (let i = 0; i < tokenOrders?.length; i++) {
|
||||
const did = web3.utils
|
||||
.toChecksumAddress(tokenOrders[i].token.address)
|
||||
.toChecksumAddress(tokenOrders[i].datatoken.address)
|
||||
.replace('0x', 'did:op:')
|
||||
didList.push(did)
|
||||
}
|
||||
|
@ -348,15 +348,15 @@ export async function getDownloadAssets(
|
||||
const downloadedAssets: DownloadedAsset[] = result.results
|
||||
.map((ddo) => {
|
||||
const order = tokenOrders.find(
|
||||
({ token }) =>
|
||||
token?.address.toLowerCase() ===
|
||||
({ datatoken }) =>
|
||||
datatoken?.address.toLowerCase() ===
|
||||
ddo.services[0].datatokenAddress.toLowerCase()
|
||||
)
|
||||
|
||||
return {
|
||||
ddo,
|
||||
networkId: ddo.chainId,
|
||||
dtSymbol: order?.token?.symbol,
|
||||
dtSymbol: order?.datatoken?.symbol,
|
||||
timestamp: order?.createdTimestamp
|
||||
}
|
||||
})
|
||||
|
@ -32,10 +32,9 @@ const getComputeOrders = gql`
|
||||
where: { payer: $user }
|
||||
) {
|
||||
id
|
||||
serviceId
|
||||
token {
|
||||
serviceIndex
|
||||
datatoken {
|
||||
address
|
||||
isDatatoken
|
||||
}
|
||||
tx
|
||||
createdTimestamp
|
||||
@ -51,13 +50,12 @@ const getComputeOrdersByDatatokenAddress = gql`
|
||||
orders(
|
||||
orderBy: createdTimestamp
|
||||
orderDirection: desc
|
||||
where: { payer: $user, token: $datatokenAddress }
|
||||
where: { payer: $user, datatoken: $datatokenAddress }
|
||||
) {
|
||||
id
|
||||
serviceId
|
||||
token {
|
||||
serviceIndex
|
||||
datatoken {
|
||||
address
|
||||
isDatatoken
|
||||
}
|
||||
tx
|
||||
createdTimestamp
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
} from '../@types/subgraph/PoolShares'
|
||||
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
||||
import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery'
|
||||
import { PoolLiquidity } from 'src/@types/subgraph/PoolLiquidity'
|
||||
import { PoolData } from 'src/@types/subgraph/PoolData'
|
||||
|
||||
export interface UserLiquidity {
|
||||
price: string
|
||||
@ -148,7 +148,7 @@ const PreviousOrderQuery = gql`
|
||||
query AssetPreviousOrder($id: String!, $account: String!) {
|
||||
orders(
|
||||
first: 1
|
||||
where: { token: $id, payer: $account }
|
||||
where: { datatoken: $id, payer: $account }
|
||||
orderBy: createdTimestamp
|
||||
orderDirection: desc
|
||||
) {
|
||||
@ -242,10 +242,13 @@ const UserTokenOrders = gql`
|
||||
orderDirection: desc
|
||||
where: { consumer: $user }
|
||||
) {
|
||||
token {
|
||||
datatoken {
|
||||
address
|
||||
symbol
|
||||
}
|
||||
consumerMarketToken {
|
||||
address
|
||||
symbol
|
||||
isDatatoken
|
||||
}
|
||||
createdTimestamp
|
||||
tx
|
||||
@ -282,9 +285,14 @@ const TopSalesQuery = gql`
|
||||
}
|
||||
`
|
||||
|
||||
const poolLiquidityQuery = gql`
|
||||
query PoolLiquidity($pool: ID!, $owner: String!) {
|
||||
pool(id: $pool) {
|
||||
const poolDataQuery = gql`
|
||||
query PoolData(
|
||||
$pool: ID!
|
||||
$poolAsString: String!
|
||||
$owner: String!
|
||||
$user: String
|
||||
) {
|
||||
poolData: pool(id: $pool) {
|
||||
id
|
||||
totalShares
|
||||
poolFee
|
||||
@ -307,16 +315,18 @@ const poolLiquidityQuery = gql`
|
||||
shares
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const userPoolShareQuery = gql`
|
||||
query PoolShare($pool: ID!, $user: String!) {
|
||||
pool(id: $pool) {
|
||||
poolDataUser: pool(id: $pool) {
|
||||
shares(where: { user: $user }) {
|
||||
shares
|
||||
}
|
||||
}
|
||||
poolSnapshots(first: 1000, where: { pool: $poolAsString }, orderBy: date) {
|
||||
date
|
||||
spotPrice
|
||||
baseTokenLiquidity
|
||||
datatokenLiquidity
|
||||
swapVolume
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -819,33 +829,22 @@ export async function getTopAssetsPublishers(
|
||||
export async function getPoolData(
|
||||
chainId: number,
|
||||
pool: string,
|
||||
owner: string
|
||||
owner: string,
|
||||
user: string
|
||||
) {
|
||||
const queryVariables = {
|
||||
// Using `pool` & `poolAsString` is a workaround to make the mega query work.
|
||||
// See https://github.com/oceanprotocol/ocean-subgraph/issues/301
|
||||
pool: pool.toLowerCase(),
|
||||
owner: owner.toLowerCase()
|
||||
poolAsString: pool.toLowerCase(),
|
||||
owner: owner.toLowerCase(),
|
||||
user: user.toLowerCase()
|
||||
}
|
||||
const response: OperationResult<PoolLiquidity> = await fetchData(
|
||||
poolLiquidityQuery,
|
||||
queryVariables,
|
||||
getQueryContext(chainId)
|
||||
)
|
||||
return response?.data?.pool
|
||||
}
|
||||
|
||||
export async function getUserPoolShareBalance(
|
||||
chainId: number,
|
||||
pool: string,
|
||||
accountId: string
|
||||
): Promise<string> {
|
||||
const queryVariables = {
|
||||
pool: pool.toLowerCase(),
|
||||
user: accountId.toLowerCase()
|
||||
}
|
||||
const response: OperationResult<PoolLiquidity> = await fetchData(
|
||||
userPoolShareQuery,
|
||||
const response: OperationResult<PoolData> = await fetchData(
|
||||
poolDataQuery,
|
||||
queryVariables,
|
||||
getQueryContext(chainId)
|
||||
)
|
||||
return response?.data?.pool?.shares[0]?.shares || '0'
|
||||
return response?.data
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ export default function DebugOutput({
|
||||
title,
|
||||
output
|
||||
}: {
|
||||
title: string
|
||||
title?: string
|
||||
output: any
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div style={{ marginTop: 'var(--spacer)' }}>
|
||||
<h5>{title}</h5>
|
||||
{title && <h5>{title}</h5>}
|
||||
<pre style={{ wordWrap: 'break-word' }}>
|
||||
<code>{JSON.stringify(output, null, 2)}</code>
|
||||
</pre>
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { ErrorMessage, useField } from 'formik'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import styles from './index.module.css'
|
||||
import InputGroup from '@shared/FormInput/InputGroup'
|
||||
import InputElement from '@shared/FormInput/InputElement'
|
||||
import isUrl from 'is-url-superb'
|
||||
|
||||
export default function URLInput({
|
||||
submitText,
|
||||
@ -21,7 +22,19 @@ export default function URLInput({
|
||||
hasError: boolean
|
||||
}): ReactElement {
|
||||
const [field, meta] = useField(name)
|
||||
const isButtonDisabled = !field?.value || field.value === ''
|
||||
const [isButtonDisabled, setIsButtonDisabled] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (!field?.value) return
|
||||
|
||||
setIsButtonDisabled(
|
||||
!field?.value ||
|
||||
field.value === '' ||
|
||||
!isUrl(field.value) ||
|
||||
field.value.includes('javascript:') ||
|
||||
meta?.error
|
||||
)
|
||||
}, [field?.value, meta?.error])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -30,10 +30,12 @@ const txHistoryQueryByPool = gql`
|
||||
symbol
|
||||
address
|
||||
}
|
||||
baseTokenValue
|
||||
datatoken {
|
||||
symbol
|
||||
address
|
||||
}
|
||||
datatokenValue
|
||||
type
|
||||
tx
|
||||
timestamp
|
||||
|
@ -7,19 +7,19 @@ import content from '../../../../content/price.json'
|
||||
|
||||
export function ButtonApprove({
|
||||
amount,
|
||||
coin,
|
||||
tokenSymbol,
|
||||
approveTokens,
|
||||
isLoading
|
||||
}: {
|
||||
amount: string
|
||||
coin: string
|
||||
tokenSymbol: string
|
||||
approveTokens: (amount: string) => void
|
||||
isLoading: boolean
|
||||
}): ReactElement {
|
||||
const { infiniteApproval } = useUserPreferences()
|
||||
|
||||
return isLoading ? (
|
||||
<Loader message={`Approving ${coin}...`} />
|
||||
<Loader message={`Approving ${tokenSymbol}...`} />
|
||||
) : infiniteApproval ? (
|
||||
<Button
|
||||
style="primary"
|
||||
@ -27,16 +27,22 @@ export function ButtonApprove({
|
||||
disabled={parseInt(amount) < 1}
|
||||
onClick={() => approveTokens(`${2 ** 53 - 1}`)}
|
||||
>
|
||||
Approve {coin}{' '}
|
||||
Approve {tokenSymbol}{' '}
|
||||
<Tooltip
|
||||
content={content.pool.tooltips.approveInfinite.replace('COIN', coin)}
|
||||
content={content.pool.tooltips.approveInfinite.replace(
|
||||
'COIN',
|
||||
tokenSymbol
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
) : (
|
||||
<Button style="primary" size="small" onClick={() => approveTokens(amount)}>
|
||||
Approve {amount} {coin}
|
||||
Approve {amount} {tokenSymbol}
|
||||
<Tooltip
|
||||
content={content.pool.tooltips.approveSpecific.replace('COIN', coin)}
|
||||
content={content.pool.tooltips.approveSpecific.replace(
|
||||
'COIN',
|
||||
tokenSymbol
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
|
@ -2,46 +2,47 @@ import React, { ReactElement, useCallback, useEffect, useState } from 'react'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import Decimal from 'decimal.js'
|
||||
import { getOceanConfig } from '@utils/ocean'
|
||||
import { ButtonApprove } from './ButtonApprove'
|
||||
import { allowance, approve, LoggerInstance } from '@oceanprotocol/lib'
|
||||
|
||||
export default function TokenApproval({
|
||||
actionButton,
|
||||
disabled,
|
||||
amount,
|
||||
coin
|
||||
tokenAddress,
|
||||
tokenSymbol
|
||||
}: {
|
||||
actionButton: JSX.Element
|
||||
disabled: boolean
|
||||
amount: string
|
||||
coin: string
|
||||
tokenAddress: string
|
||||
tokenSymbol: string
|
||||
}): ReactElement {
|
||||
const { ddo, price, isAssetNetwork } = useAsset()
|
||||
const { price, isAssetNetwork } = useAsset()
|
||||
const [tokenApproved, setTokenApproved] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { accountId } = useWeb3()
|
||||
const { web3, accountId } = useWeb3()
|
||||
|
||||
const config = getOceanConfig(ddo.chainId)
|
||||
|
||||
const tokenAddress =
|
||||
coin === 'OCEAN'
|
||||
? config.oceanTokenAddress
|
||||
: ddo.services[0].datatokenAddress
|
||||
const spender = price.address
|
||||
const spender = price?.address
|
||||
|
||||
const checkTokenApproval = useCallback(async () => {
|
||||
// if (!tokenAddress || !spender || !isAssetNetwork || !amount) return
|
||||
// const allowance = await ocean.datatokens.allowance(
|
||||
// tokenAddress,
|
||||
// accountId,
|
||||
// spender
|
||||
// )
|
||||
// amount &&
|
||||
// new Decimal(amount).greaterThan(new Decimal('0')) &&
|
||||
// setTokenApproved(
|
||||
// new Decimal(allowance).greaterThanOrEqualTo(new Decimal(amount))
|
||||
// )
|
||||
}, [tokenAddress, spender, accountId, amount, isAssetNetwork])
|
||||
if (!web3 || !tokenAddress || !spender || !isAssetNetwork || !amount) return
|
||||
|
||||
const allowanceValue = await allowance(
|
||||
web3,
|
||||
tokenAddress,
|
||||
accountId,
|
||||
spender
|
||||
)
|
||||
LoggerInstance.log(`[token approval] allowanceValue: ${allowanceValue}`)
|
||||
|
||||
if (!allowanceValue) return
|
||||
|
||||
new Decimal(amount).greaterThan(new Decimal('0')) &&
|
||||
setTokenApproved(
|
||||
new Decimal(allowanceValue).greaterThanOrEqualTo(new Decimal(amount))
|
||||
)
|
||||
}, [web3, tokenAddress, spender, accountId, amount, isAssetNetwork])
|
||||
|
||||
useEffect(() => {
|
||||
checkTokenApproval()
|
||||
@ -51,13 +52,17 @@ export default function TokenApproval({
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
// await ocean.datatokens.approve(tokenAddress, spender, amount, accountId)
|
||||
const tx = await approve(web3, accountId, tokenAddress, spender, amount)
|
||||
LoggerInstance.log(`[token approval] Approve tokens tx:`, tx)
|
||||
} catch (error) {
|
||||
LoggerInstance.error(
|
||||
`[token approval] Approve tokens tx failed:`,
|
||||
error.message
|
||||
)
|
||||
} finally {
|
||||
await checkTokenApproval()
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
await checkTokenApproval()
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -72,7 +77,7 @@ export default function TokenApproval({
|
||||
) : (
|
||||
<ButtonApprove
|
||||
amount={amount}
|
||||
coin={coin}
|
||||
tokenSymbol={tokenSymbol}
|
||||
approveTokens={approveTokens}
|
||||
isLoading={loading}
|
||||
/>
|
||||
|
@ -22,7 +22,7 @@ const previousOrderQuery = gql`
|
||||
query PreviousOrder($id: String!, $account: String!) {
|
||||
orders(
|
||||
first: 1
|
||||
where: { token: $id, payer: $account }
|
||||
where: { datatoken: $id, payer: $account }
|
||||
orderBy: createdTimestamp
|
||||
orderDirection: desc
|
||||
) {
|
||||
|
@ -14,9 +14,10 @@ export default function Actions({
|
||||
txId,
|
||||
actionName,
|
||||
amount,
|
||||
coin,
|
||||
action,
|
||||
isDisabled
|
||||
isDisabled,
|
||||
tokenAddress,
|
||||
tokenSymbol
|
||||
}: {
|
||||
isLoading: boolean
|
||||
loaderMessage: string
|
||||
@ -24,9 +25,10 @@ export default function Actions({
|
||||
txId: string
|
||||
actionName: string
|
||||
amount?: string
|
||||
coin?: string
|
||||
action: () => void
|
||||
isDisabled?: boolean
|
||||
tokenAddress: string
|
||||
tokenSymbol: string
|
||||
}): ReactElement {
|
||||
const { networkId } = useWeb3()
|
||||
|
||||
@ -50,7 +52,8 @@ export default function Actions({
|
||||
<TokenApproval
|
||||
actionButton={actionButton}
|
||||
amount={amount}
|
||||
coin={coin}
|
||||
tokenAddress={tokenAddress}
|
||||
tokenSymbol={tokenSymbol}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
) : (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ChangeEvent, ReactElement, useEffect } from 'react'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import styles from './FormAdd.module.css'
|
||||
import Input from '@shared/FormInput'
|
||||
import {
|
||||
@ -8,34 +8,27 @@ import {
|
||||
useFormikContext
|
||||
} from 'formik'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import CoinSelect from '../CoinSelect'
|
||||
import { FormAddLiquidity } from '.'
|
||||
import UserLiquidity from '../../UserLiquidity'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
|
||||
import { isValidNumber } from '@utils/numbers'
|
||||
import Decimal from 'decimal.js'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { LoggerInstance, Pool } from '@oceanprotocol/lib'
|
||||
|
||||
export default function FormAdd({
|
||||
coin,
|
||||
dtBalance,
|
||||
dtSymbol,
|
||||
tokenInAddress,
|
||||
tokenInSymbol,
|
||||
amountMax,
|
||||
setCoin,
|
||||
setAmount,
|
||||
totalPoolTokens,
|
||||
totalBalance,
|
||||
poolAddress,
|
||||
setNewPoolTokens,
|
||||
setNewPoolShare
|
||||
}: {
|
||||
coin: string
|
||||
dtBalance: string
|
||||
dtSymbol: string
|
||||
tokenInAddress: string
|
||||
tokenInSymbol: string
|
||||
amountMax: string
|
||||
setCoin: (value: string) => void
|
||||
setAmount: (value: string) => void
|
||||
totalPoolTokens: string
|
||||
totalBalance: PoolBalance
|
||||
poolAddress: string
|
||||
@ -46,75 +39,58 @@ export default function FormAdd({
|
||||
const { isAssetNetwork } = useAsset()
|
||||
|
||||
// Connect with form
|
||||
const {
|
||||
touched,
|
||||
setTouched,
|
||||
setFieldValue,
|
||||
validateField,
|
||||
values
|
||||
}: FormikContextType<FormAddLiquidity> = useFormikContext()
|
||||
|
||||
function handleFieldChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
// Workaround so validation kicks in on first touch
|
||||
!touched?.amount && setTouched({ amount: true })
|
||||
setAmount(e.target.value)
|
||||
|
||||
// Manually handle change events instead of using `handleChange` from Formik.
|
||||
// Solves bug where 0.0 can't be typed.
|
||||
validateField('amount')
|
||||
setFieldValue('amount', e.target.value)
|
||||
}
|
||||
const { setFieldValue, values }: FormikContextType<FormAddLiquidity> =
|
||||
useFormikContext()
|
||||
|
||||
useEffect(() => {
|
||||
async function calculatePoolShares() {
|
||||
// if (!web3) return
|
||||
// const tokenInAddress =
|
||||
// coin === 'OCEAN' ? ocean.pool.oceanAddress : ocean.pool.dtAddress
|
||||
// if (!values.amount || !tokenInAddress) {
|
||||
// setNewPoolTokens('0')
|
||||
// setNewPoolShare('0')
|
||||
// return
|
||||
// }
|
||||
// if (Number(values.amount) > Number(amountMax)) return
|
||||
// const poolTokens = await ocean.pool.calcPoolOutGivenSingleIn(
|
||||
// poolAddress,
|
||||
// tokenInAddress,
|
||||
// `${values.amount}`
|
||||
// )
|
||||
// setNewPoolTokens(poolTokens)
|
||||
// const newPoolShareDecimal =
|
||||
// isValidNumber(poolTokens) && isValidNumber(totalPoolTokens)
|
||||
// ? new Decimal(poolTokens)
|
||||
// .dividedBy(
|
||||
// new Decimal(totalPoolTokens).plus(new Decimal(poolTokens))
|
||||
// )
|
||||
// .mul(100)
|
||||
// .toString()
|
||||
// : '0'
|
||||
// totalBalance && setNewPoolShare(newPoolShareDecimal)
|
||||
if (!web3) return
|
||||
|
||||
if (!values.amount || !tokenInAddress) {
|
||||
setNewPoolTokens('0')
|
||||
setNewPoolShare('0')
|
||||
return
|
||||
}
|
||||
if (Number(values.amount) > Number(amountMax)) return
|
||||
|
||||
const poolInstance = new Pool(web3, LoggerInstance)
|
||||
|
||||
const poolTokens = await poolInstance.calcPoolOutGivenSingleIn(
|
||||
poolAddress,
|
||||
tokenInAddress,
|
||||
values.amount
|
||||
)
|
||||
setNewPoolTokens(poolTokens)
|
||||
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()
|
||||
}, [
|
||||
tokenInAddress,
|
||||
web3,
|
||||
values.amount,
|
||||
totalBalance,
|
||||
totalPoolTokens,
|
||||
amountMax,
|
||||
coin,
|
||||
poolAddress,
|
||||
setNewPoolTokens,
|
||||
setNewPoolShare
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue('amount', undefined)
|
||||
}, [coin])
|
||||
|
||||
return (
|
||||
<>
|
||||
<UserLiquidity
|
||||
amount={coin === 'OCEAN' ? balance.ocean : dtBalance}
|
||||
amount={balance.ocean}
|
||||
amountMax={amountMax}
|
||||
symbol={coin}
|
||||
symbol={tokenInSymbol}
|
||||
/>
|
||||
|
||||
<Field name="amount">
|
||||
@ -130,34 +106,24 @@ export default function FormAdd({
|
||||
name="amount"
|
||||
max={amountMax}
|
||||
min="0"
|
||||
value={`${values.amount}`}
|
||||
value={values.amount}
|
||||
step="any"
|
||||
prefix={
|
||||
<CoinSelect
|
||||
dtSymbol={dtSymbol}
|
||||
setCoin={setCoin}
|
||||
disabled={!web3 || !isAssetNetwork}
|
||||
/>
|
||||
}
|
||||
prefix={tokenInSymbol}
|
||||
placeholder="0"
|
||||
field={field}
|
||||
form={form}
|
||||
onChange={handleFieldChange}
|
||||
disabled={!isAssetNetwork}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
{(Number(balance.ocean) || dtBalance) > (values.amount || 0) && (
|
||||
{Number(balance.ocean) && (
|
||||
<Button
|
||||
className={styles.buttonMax}
|
||||
style="text"
|
||||
size="small"
|
||||
disabled={!web3}
|
||||
onClick={() => {
|
||||
setAmount(amountMax)
|
||||
setFieldValue('amount', amountMax)
|
||||
}}
|
||||
onClick={() => setFieldValue('amount', amountMax)}
|
||||
>
|
||||
Use Max
|
||||
</Button>
|
||||
|
@ -11,18 +11,16 @@ export default function Output({
|
||||
newPoolTokens,
|
||||
newPoolShare,
|
||||
swapFee,
|
||||
dtSymbol,
|
||||
datatokenSymbol,
|
||||
totalPoolTokens,
|
||||
totalBalance,
|
||||
coin
|
||||
totalBalance
|
||||
}: {
|
||||
newPoolTokens: string
|
||||
newPoolShare: string
|
||||
swapFee: string
|
||||
dtSymbol: string
|
||||
datatokenSymbol: string
|
||||
totalPoolTokens: string
|
||||
totalBalance: PoolBalance
|
||||
coin: string
|
||||
}): ReactElement {
|
||||
const { help, titleIn, titleOut } = content.pool.add.output
|
||||
|
||||
@ -37,21 +35,14 @@ export default function Output({
|
||||
return
|
||||
const newPoolSupply = new Decimal(totalPoolTokens).plus(newPoolTokens)
|
||||
const ratio = new Decimal(newPoolTokens).div(newPoolSupply)
|
||||
const newOceanReserve =
|
||||
coin === 'OCEAN'
|
||||
? new Decimal(totalBalance.ocean).plus(values.amount)
|
||||
: new Decimal(totalBalance.ocean)
|
||||
const newDtReserve =
|
||||
coin === 'OCEAN'
|
||||
? new Decimal(totalBalance.datatoken)
|
||||
: new Decimal(totalBalance.datatoken).plus(values.amount)
|
||||
const newOceanReserve = new Decimal(totalBalance.ocean).plus(values.amount)
|
||||
const newDtReserve = new Decimal(totalBalance.datatoken)
|
||||
const poolOcean = newOceanReserve.mul(ratio).toString()
|
||||
const poolDatatoken = newDtReserve.mul(ratio).toString()
|
||||
setPoolOcean(poolOcean)
|
||||
setPoolDatatoken(poolDatatoken)
|
||||
}, [
|
||||
values.amount,
|
||||
coin,
|
||||
totalBalance,
|
||||
totalPoolTokens,
|
||||
newPoolShare,
|
||||
@ -72,7 +63,7 @@ export default function Output({
|
||||
<div>
|
||||
<p>{titleOut}</p>
|
||||
<Token symbol="OCEAN" balance={poolOcean} />
|
||||
<Token symbol={dtSymbol} balance={poolDatatoken} />
|
||||
<Token symbol={datatokenSymbol} balance={poolDatatoken} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -13,14 +13,14 @@ import DebugOutput from '@shared/DebugOutput'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import content from '../../../../../../content/price.json'
|
||||
import { Datatoken } from '@oceanprotocol/lib'
|
||||
import { LoggerInstance, Pool } from '@oceanprotocol/lib'
|
||||
|
||||
export interface FormAddLiquidity {
|
||||
amount: number
|
||||
amount: string
|
||||
}
|
||||
|
||||
const initialValues: FormAddLiquidity = {
|
||||
amount: undefined
|
||||
amount: ''
|
||||
}
|
||||
|
||||
export default function Add({
|
||||
@ -29,8 +29,9 @@ export default function Add({
|
||||
totalPoolTokens,
|
||||
totalBalance,
|
||||
swapFee,
|
||||
dtSymbol,
|
||||
dtAddress,
|
||||
datatokenSymbol,
|
||||
tokenInSymbol,
|
||||
tokenInAddress,
|
||||
fetchAllData
|
||||
}: {
|
||||
setShowAdd: (show: boolean) => void
|
||||
@ -38,18 +39,16 @@ export default function Add({
|
||||
totalPoolTokens: string
|
||||
totalBalance: PoolBalance
|
||||
swapFee: string
|
||||
dtSymbol: string
|
||||
dtAddress: string
|
||||
datatokenSymbol: string
|
||||
tokenInSymbol: string
|
||||
tokenInAddress: string
|
||||
fetchAllData: () => void
|
||||
}): ReactElement {
|
||||
const { accountId, balance, web3 } = useWeb3()
|
||||
const { isAssetNetwork } = useAsset()
|
||||
const { debug } = useUserPreferences()
|
||||
const [txId, setTxId] = useState<string>()
|
||||
const [coin, setCoin] = useState('OCEAN')
|
||||
const [dtBalance, setDtBalance] = useState<string>()
|
||||
const [amountMax, setAmountMax] = useState<string>()
|
||||
const [amount, setAmount] = useState<string>('0')
|
||||
const [newPoolTokens, setNewPoolTokens] = useState('0')
|
||||
const [newPoolShare, setNewPoolShare] = useState('0')
|
||||
const [isWarningAccepted, setIsWarningAccepted] = useState(false)
|
||||
@ -57,65 +56,65 @@ export default function Add({
|
||||
// Live validation rules
|
||||
// https://github.com/jquense/yup#number
|
||||
const validationSchema: Yup.SchemaOf<FormAddLiquidity> = Yup.object().shape({
|
||||
amount: Yup.number()
|
||||
amount: Yup.string()
|
||||
.min(0.00001, (param) => `Must be more or equal to ${param.min}`)
|
||||
.max(
|
||||
Number(amountMax),
|
||||
`Maximum you can add is ${Number(amountMax).toFixed(2)} ${coin}`
|
||||
`Maximum you can add is ${Number(amountMax).toFixed(2)} OCEAN`
|
||||
)
|
||||
.required('Required')
|
||||
})
|
||||
|
||||
// Get datatoken balance when datatoken selected
|
||||
// Get maximum amount for OCEAN
|
||||
useEffect(() => {
|
||||
if (!web3 || !accountId || !isAssetNetwork || coin === 'OCEAN') return
|
||||
|
||||
async function getDtBalance() {
|
||||
const datatokenInstance = new Datatoken(web3)
|
||||
const dtBalance = await datatokenInstance.balance(dtAddress, accountId)
|
||||
setDtBalance(dtBalance)
|
||||
}
|
||||
getDtBalance()
|
||||
}, [web3, accountId, dtAddress, coin, isAssetNetwork])
|
||||
|
||||
// Get maximum amount for either OCEAN or datatoken
|
||||
useEffect(() => {
|
||||
if (!accountId || !isAssetNetwork || !poolAddress) return
|
||||
if (!web3 || !accountId || !isAssetNetwork || !poolAddress) return
|
||||
|
||||
async function getMaximum() {
|
||||
// const amountMaxPool =
|
||||
// coin === 'OCEAN'
|
||||
// ? await ocean.pool.getOceanMaxAddLiquidity(poolAddress)
|
||||
// : await ocean.pool.getDTMaxAddLiquidity(poolAddress)
|
||||
// const amountMax =
|
||||
// coin === 'OCEAN'
|
||||
// ? Number(balance.ocean) > Number(amountMaxPool)
|
||||
// ? amountMaxPool
|
||||
// : balance.ocean
|
||||
// : Number(dtBalance) > Number(amountMaxPool)
|
||||
// ? amountMaxPool
|
||||
// : dtBalance
|
||||
// setAmountMax(Number(amountMax).toFixed(3))
|
||||
try {
|
||||
const poolInstance = new Pool(web3, LoggerInstance)
|
||||
|
||||
const amountMaxPool = await poolInstance.getReserve(
|
||||
poolAddress,
|
||||
tokenInAddress
|
||||
)
|
||||
|
||||
const amountMax =
|
||||
Number(balance.ocean) > Number(amountMaxPool)
|
||||
? amountMaxPool
|
||||
: balance.ocean
|
||||
setAmountMax(Number(amountMax).toFixed(3))
|
||||
} catch (error) {
|
||||
LoggerInstance.error(error.message)
|
||||
}
|
||||
}
|
||||
getMaximum()
|
||||
}, [accountId, isAssetNetwork, poolAddress, coin, dtBalance, balance.ocean])
|
||||
}, [
|
||||
web3,
|
||||
accountId,
|
||||
isAssetNetwork,
|
||||
poolAddress,
|
||||
tokenInAddress,
|
||||
balance?.ocean
|
||||
])
|
||||
|
||||
// Submit
|
||||
async function handleAddLiquidity(amount: number, resetForm: () => void) {
|
||||
async function handleAddLiquidity(amount: string, resetForm: () => void) {
|
||||
const poolInstance = new Pool(web3, LoggerInstance)
|
||||
const minPoolAmountOut = '0' // ? TODO: how to get?
|
||||
|
||||
try {
|
||||
// const result =
|
||||
// coin === 'OCEAN'
|
||||
// ? await ocean.pool.addOceanLiquidity(
|
||||
// accountId,
|
||||
// poolAddress,
|
||||
// `${amount}`
|
||||
// )
|
||||
// : await ocean.pool.addDTLiquidity(accountId, poolAddress, `${amount}`)
|
||||
// setTxId(result?.transactionHash)
|
||||
// resetForm()
|
||||
const result = await poolInstance.joinswapExternAmountIn(
|
||||
accountId,
|
||||
poolAddress,
|
||||
tokenInAddress,
|
||||
amount,
|
||||
minPoolAmountOut
|
||||
)
|
||||
setTxId(result?.transactionHash)
|
||||
fetchAllData()
|
||||
resetForm()
|
||||
} catch (error) {
|
||||
console.error(error.message)
|
||||
LoggerInstance.error(error.message)
|
||||
toast.error(error.message)
|
||||
}
|
||||
}
|
||||
@ -139,12 +138,9 @@ export default function Add({
|
||||
<div className={styles.addInput}>
|
||||
{isWarningAccepted ? (
|
||||
<FormAdd
|
||||
coin={coin}
|
||||
dtBalance={dtBalance}
|
||||
dtSymbol={dtSymbol}
|
||||
tokenInAddress={tokenInAddress}
|
||||
tokenInSymbol={tokenInSymbol}
|
||||
amountMax={amountMax}
|
||||
setCoin={setCoin}
|
||||
setAmount={setAmount}
|
||||
totalPoolTokens={totalPoolTokens}
|
||||
totalBalance={totalBalance}
|
||||
poolAddress={poolAddress}
|
||||
@ -171,29 +167,30 @@ export default function Add({
|
||||
newPoolTokens={newPoolTokens}
|
||||
newPoolShare={newPoolShare}
|
||||
swapFee={swapFee}
|
||||
dtSymbol={dtSymbol}
|
||||
datatokenSymbol={datatokenSymbol}
|
||||
totalPoolTokens={totalPoolTokens}
|
||||
totalBalance={totalBalance}
|
||||
coin={coin}
|
||||
/>
|
||||
|
||||
<Actions
|
||||
isDisabled={
|
||||
!isValid ||
|
||||
!isWarningAccepted ||
|
||||
amount === '' ||
|
||||
amount === '0'
|
||||
!values.amount ||
|
||||
values.amount === '' ||
|
||||
values.amount === '0'
|
||||
}
|
||||
isLoading={isSubmitting}
|
||||
loaderMessage="Adding Liquidity..."
|
||||
successMessage="Successfully added liquidity."
|
||||
actionName={content.pool.add.action}
|
||||
action={submitForm}
|
||||
amount={amount}
|
||||
coin={coin}
|
||||
amount={values.amount}
|
||||
tokenAddress={tokenInAddress}
|
||||
tokenSymbol={tokenInSymbol}
|
||||
txId={txId}
|
||||
/>
|
||||
{debug && <DebugOutput title="Collected values" output={values} />}
|
||||
{debug && <DebugOutput output={values} />}
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
|
@ -1,26 +0,0 @@
|
||||
.coinSelect {
|
||||
composes: select from '@shared/FormInput/InputElement.module.css';
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-base);
|
||||
border: none;
|
||||
margin-left: -0.5rem;
|
||||
margin-right: -0.5rem;
|
||||
background-color: var(--background-highlight);
|
||||
width: auto;
|
||||
padding: 0 1.25rem 0 0.25rem;
|
||||
height: 41px;
|
||||
text-align: center;
|
||||
|
||||
/* custom arrow, without the divider line */
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
transparent 50%,
|
||||
var(--font-color-text) 50%
|
||||
),
|
||||
linear-gradient(135deg, var(--font-color-text) 50%, transparent 50%);
|
||||
background-position: calc(100% - 14px) 1.2rem, calc(100% - 9px) 1.2rem, 100% 0;
|
||||
}
|
||||
|
||||
.option {
|
||||
color: var(--font-color-heading);
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styles from './CoinSelect.module.css'
|
||||
|
||||
export default function CoinSelect({
|
||||
dtSymbol,
|
||||
disabled,
|
||||
setCoin
|
||||
}: {
|
||||
dtSymbol: string
|
||||
disabled: boolean
|
||||
setCoin: (coin: string) => void
|
||||
}): ReactElement {
|
||||
return (
|
||||
<select
|
||||
className={styles.coinSelect}
|
||||
onChange={(e) => setCoin(e.target.value)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<option className={styles.option} value="OCEAN">
|
||||
OCEAN
|
||||
</option>
|
||||
<option className={styles.option} value={dtSymbol}>
|
||||
{dtSymbol}
|
||||
</option>
|
||||
</select>
|
||||
)
|
||||
}
|
@ -1,241 +0,0 @@
|
||||
/* eslint-disable camelcase */
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
ReactElement,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState
|
||||
} from 'react'
|
||||
import { Line, defaults } from 'react-chartjs-2'
|
||||
import {
|
||||
ChartData,
|
||||
ChartDataSets,
|
||||
ChartOptions,
|
||||
ChartTooltipItem,
|
||||
ChartTooltipOptions
|
||||
} from 'chart.js'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import { formatPrice } from '@shared/Price/PriceUnit'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import useDarkMode from 'use-dark-mode'
|
||||
import { darkModeConfig } from '../../../../../app.config'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { gql, OperationResult } from 'urql'
|
||||
import { PoolHistory } from '../../../../@types/subgraph/PoolHistory'
|
||||
import { fetchData, getQueryContext } from '@utils/subgraph'
|
||||
import styles from './Graph.module.css'
|
||||
|
||||
declare type GraphType = 'liquidity' | 'price'
|
||||
|
||||
// Chart.js global defaults
|
||||
defaults.global.defaultFontFamily = `'Sharp Sans', -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', Helvetica, Arial, sans-serif`
|
||||
defaults.global.animation = { easing: 'easeInOutQuart', duration: 1000 }
|
||||
|
||||
const REFETCH_INTERVAL = 10000
|
||||
|
||||
const lineStyle: Partial<ChartDataSets> = {
|
||||
fill: false,
|
||||
lineTension: 0.1,
|
||||
borderWidth: 2,
|
||||
pointBorderWidth: 0,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBorderWidth: 0,
|
||||
pointHitRadius: 2,
|
||||
pointHoverBackgroundColor: '#ff4092'
|
||||
}
|
||||
|
||||
const tooltipOptions: Partial<ChartTooltipOptions> = {
|
||||
intersect: false,
|
||||
titleFontStyle: 'normal',
|
||||
titleFontSize: 10,
|
||||
bodyFontSize: 12,
|
||||
bodyFontStyle: 'bold',
|
||||
displayColors: false,
|
||||
xPadding: 10,
|
||||
yPadding: 10,
|
||||
cornerRadius: 3,
|
||||
borderWidth: 1,
|
||||
caretSize: 7
|
||||
}
|
||||
|
||||
function getOptions(locale: string, isDarkMode: boolean): ChartOptions {
|
||||
return {
|
||||
layout: {
|
||||
padding: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 10
|
||||
}
|
||||
},
|
||||
tooltips: {
|
||||
...tooltipOptions,
|
||||
backgroundColor: isDarkMode ? `#141414` : `#fff`,
|
||||
titleFontColor: isDarkMode ? `#e2e2e2` : `#303030`,
|
||||
bodyFontColor: isDarkMode ? `#fff` : `#141414`,
|
||||
borderColor: isDarkMode ? `#41474e` : `#e2e2e2`,
|
||||
callbacks: {
|
||||
label: (tooltipItem: ChartTooltipItem) =>
|
||||
`${formatPrice(`${tooltipItem.yLabel}`, locale)} OCEAN`
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
hover: {
|
||||
intersect: false,
|
||||
animationDuration: 0
|
||||
},
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
display: false
|
||||
// gridLines: {
|
||||
// drawBorder: false,
|
||||
// color: isDarkMode ? '#303030' : '#e2e2e2',
|
||||
// zeroLineColor: isDarkMode ? '#303030' : '#e2e2e2'
|
||||
// },
|
||||
// ticks: { display: false }
|
||||
}
|
||||
],
|
||||
xAxes: [{ display: false, gridLines: { display: true } }]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const graphTypes = ['Liquidity', 'Price']
|
||||
|
||||
const poolHistoryQuery = gql`
|
||||
query PoolHistory($id: String!) {
|
||||
poolSnapshots(first: 1000, where: { pool: $id }, orderBy: date) {
|
||||
date
|
||||
spotPrice
|
||||
baseTokenLiquidity
|
||||
datatokenLiquidity
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Graph(): ReactElement {
|
||||
const { locale } = useUserPreferences()
|
||||
const { price, ddo } = useAsset()
|
||||
const darkMode = useDarkMode(false, darkModeConfig)
|
||||
const [options, setOptions] = useState<ChartOptions>()
|
||||
const [graphType, setGraphType] = useState<GraphType>('liquidity')
|
||||
const [error, setError] = useState<Error>()
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [dataHistory, setDataHistory] = useState<PoolHistory>()
|
||||
const [graphData, setGraphData] = useState<ChartData>()
|
||||
const [graphFetchInterval, setGraphFetchInterval] = useState<NodeJS.Timeout>()
|
||||
|
||||
const getPoolHistory = useCallback(async () => {
|
||||
try {
|
||||
const queryResult: OperationResult<PoolHistory> = await fetchData(
|
||||
poolHistoryQuery,
|
||||
{ id: price.address.toLowerCase() },
|
||||
getQueryContext(ddo.chainId)
|
||||
)
|
||||
setDataHistory(queryResult?.data)
|
||||
} catch (error) {
|
||||
console.error('Error fetchData: ', error.message)
|
||||
setError(error)
|
||||
}
|
||||
}, [ddo?.chainId, price?.address])
|
||||
|
||||
const refetchGraph = useCallback(async () => {
|
||||
if (graphFetchInterval) return
|
||||
|
||||
const newInterval = setInterval(() => getPoolHistory(), REFETCH_INTERVAL)
|
||||
setGraphFetchInterval(newInterval)
|
||||
}, [getPoolHistory, graphFetchInterval])
|
||||
|
||||
useEffect(() => {
|
||||
LoggerInstance.log('Fired GraphOptions!')
|
||||
const options = getOptions(locale, darkMode.value)
|
||||
setOptions(options)
|
||||
}, [locale, darkMode.value])
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
if (!dataHistory) {
|
||||
await getPoolHistory()
|
||||
return
|
||||
}
|
||||
LoggerInstance.log('Fired GraphData!')
|
||||
|
||||
const latestTimestamps = [
|
||||
...dataHistory.poolSnapshots.map((item) => {
|
||||
const date = new Date(item.date * 1000)
|
||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
||||
})
|
||||
]
|
||||
|
||||
const latestLiquidityHistory = [
|
||||
...dataHistory.poolSnapshots.map((item) => item.baseTokenLiquidity)
|
||||
]
|
||||
|
||||
const latestPriceHistory = [
|
||||
...dataHistory.poolSnapshots.map((item) => item.datatokenLiquidity)
|
||||
]
|
||||
|
||||
setGraphData({
|
||||
labels: latestTimestamps.slice(0),
|
||||
datasets: [
|
||||
{
|
||||
...lineStyle,
|
||||
label: 'Liquidity (OCEAN)',
|
||||
data:
|
||||
graphType === 'liquidity'
|
||||
? latestLiquidityHistory.slice(0)
|
||||
: latestPriceHistory.slice(0),
|
||||
borderColor: `#8b98a9`,
|
||||
pointBackgroundColor: `#8b98a9`
|
||||
}
|
||||
]
|
||||
})
|
||||
setIsLoading(false)
|
||||
refetchGraph()
|
||||
}
|
||||
init()
|
||||
|
||||
return () => clearInterval(graphFetchInterval)
|
||||
}, [dataHistory, graphType, graphFetchInterval, getPoolHistory, refetchGraph])
|
||||
|
||||
function handleGraphTypeSwitch(e: ChangeEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
setGraphType(e.currentTarget.textContent.toLowerCase() as GraphType)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.graphWrap}>
|
||||
{isLoading ? (
|
||||
<Loader />
|
||||
) : error ? (
|
||||
<small>{error.message}</small>
|
||||
) : (
|
||||
<>
|
||||
<nav className={styles.type}>
|
||||
{graphTypes.map((type: GraphType) => (
|
||||
<Button
|
||||
key={type}
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleGraphTypeSwitch}
|
||||
className={`${styles.button} ${
|
||||
graphType === type.toLowerCase() ? styles.active : null
|
||||
}`}
|
||||
>
|
||||
{type}
|
||||
</Button>
|
||||
))}
|
||||
</nav>
|
||||
<Line height={70} data={graphData} options={options} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,24 +1,3 @@
|
||||
.graphWrap {
|
||||
min-height: 97px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: calc(var(--spacer) / 6) -1.35rem calc(var(--spacer) / 1.5) -1.35rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.graphWrap {
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.graphWrap canvas {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.type {
|
||||
position: absolute;
|
||||
bottom: -10px;
|
40
src/components/Asset/AssetActions/Pool/Graph/Nav.tsx
Normal file
40
src/components/Asset/AssetActions/Pool/Graph/Nav.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import Button from '@shared/atoms/Button'
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
Dispatch,
|
||||
ReactElement,
|
||||
SetStateAction
|
||||
} from 'react'
|
||||
import styles from './Nav.module.css'
|
||||
import { graphTypes, GraphType } from './_constants'
|
||||
|
||||
export default function Nav({
|
||||
graphType,
|
||||
setGraphType
|
||||
}: {
|
||||
graphType: GraphType
|
||||
setGraphType: Dispatch<SetStateAction<GraphType>>
|
||||
}): ReactElement {
|
||||
function handleGraphTypeSwitch(e: ChangeEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
setGraphType(e.currentTarget.textContent.toLowerCase() as GraphType)
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className={styles.type}>
|
||||
{graphTypes.map((type: GraphType) => (
|
||||
<Button
|
||||
key={type}
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleGraphTypeSwitch}
|
||||
className={`${styles.button} ${
|
||||
graphType === type.toLowerCase() ? styles.active : null
|
||||
}`}
|
||||
>
|
||||
{type}
|
||||
</Button>
|
||||
))}
|
||||
</nav>
|
||||
)
|
||||
}
|
67
src/components/Asset/AssetActions/Pool/Graph/_constants.ts
Normal file
67
src/components/Asset/AssetActions/Pool/Graph/_constants.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
LinearScale,
|
||||
CategoryScale,
|
||||
PointElement,
|
||||
Tooltip,
|
||||
BarElement,
|
||||
LineElement,
|
||||
LineController,
|
||||
BarController,
|
||||
ChartDataset,
|
||||
TooltipOptions,
|
||||
defaults
|
||||
} from 'chart.js'
|
||||
|
||||
export declare type GraphType = 'liquidity' | 'price' | 'volume'
|
||||
|
||||
export const graphTypes = ['Liquidity', 'Price', 'Volume']
|
||||
|
||||
// Chart.js global defaults
|
||||
ChartJS.register(
|
||||
LineElement,
|
||||
BarElement,
|
||||
PointElement,
|
||||
LinearScale,
|
||||
CategoryScale,
|
||||
Tooltip,
|
||||
LineController,
|
||||
BarController
|
||||
)
|
||||
|
||||
defaults.font.family = `'Sharp Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif`
|
||||
defaults.animation = { easing: 'easeInOutQuart', duration: 1000 }
|
||||
|
||||
export const lineStyle: Partial<ChartDataset> = {
|
||||
fill: false,
|
||||
borderWidth: 2,
|
||||
pointBorderWidth: 1,
|
||||
pointRadius: 2,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBorderWidth: 0,
|
||||
pointHitRadius: 2,
|
||||
pointHoverBackgroundColor: '#ff4092'
|
||||
}
|
||||
|
||||
export const tooltipOptions: Partial<TooltipOptions> = {
|
||||
intersect: false,
|
||||
displayColors: false,
|
||||
padding: 10,
|
||||
cornerRadius: 3,
|
||||
borderWidth: 1,
|
||||
caretSize: 7,
|
||||
bodyFont: {
|
||||
size: 13,
|
||||
weight: 'bold',
|
||||
lineHeight: 1,
|
||||
style: 'normal',
|
||||
family: defaults.font.family
|
||||
},
|
||||
titleFont: {
|
||||
size: 10,
|
||||
weight: 'normal',
|
||||
lineHeight: 1,
|
||||
style: 'normal',
|
||||
family: defaults.font.family
|
||||
}
|
||||
}
|
34
src/components/Asset/AssetActions/Pool/Graph/_utils.ts
Normal file
34
src/components/Asset/AssetActions/Pool/Graph/_utils.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { formatPrice } from '@shared/Price/PriceUnit'
|
||||
import { ChartOptions, TooltipItem } from 'chart.js'
|
||||
import { tooltipOptions } from './_constants'
|
||||
|
||||
export function getOptions(locale: string, isDarkMode: boolean): ChartOptions {
|
||||
return {
|
||||
layout: {
|
||||
padding: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 20
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
...tooltipOptions,
|
||||
backgroundColor: isDarkMode ? `#141414` : `#fff`,
|
||||
titleColor: isDarkMode ? `#e2e2e2` : `#303030`,
|
||||
bodyColor: isDarkMode ? `#fff` : `#141414`,
|
||||
borderColor: isDarkMode ? `#41474e` : `#e2e2e2`,
|
||||
callbacks: {
|
||||
label: (tooltipItem: TooltipItem<any>) =>
|
||||
`${formatPrice(`${tooltipItem.formattedValue}`, locale)} OCEAN`
|
||||
}
|
||||
}
|
||||
},
|
||||
hover: { intersect: false },
|
||||
scales: {
|
||||
y: { display: false, beginAtZero: true },
|
||||
x: { display: false, offset: true }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
.graphWrap {
|
||||
min-height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: calc(var(--spacer) / 6) -1.35rem calc(var(--spacer) / 1.5) -1.35rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.graphWrap {
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.graphWrap canvas {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
104
src/components/Asset/AssetActions/Pool/Graph/index.tsx
Normal file
104
src/components/Asset/AssetActions/Pool/Graph/index.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { ChartData, ChartOptions } from 'chart.js'
|
||||
import { Bar, Line } from 'react-chartjs-2'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import useDarkMode from 'use-dark-mode'
|
||||
import { darkModeConfig } from '../../../../../../app.config'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import styles from './index.module.css'
|
||||
import Decimal from 'decimal.js'
|
||||
import { lineStyle, GraphType } from './_constants'
|
||||
import Nav from './Nav'
|
||||
import { getOptions } from './_utils'
|
||||
import { PoolData_poolSnapshots as PoolDataPoolSnapshots } from 'src/@types/subgraph/PoolData'
|
||||
|
||||
export default function Graph({
|
||||
poolSnapshots
|
||||
}: {
|
||||
poolSnapshots: PoolDataPoolSnapshots[]
|
||||
}): ReactElement {
|
||||
const { locale } = useUserPreferences()
|
||||
const darkMode = useDarkMode(false, darkModeConfig)
|
||||
|
||||
const [options, setOptions] = useState<ChartOptions<any>>()
|
||||
const [graphType, setGraphType] = useState<GraphType>('liquidity')
|
||||
const [graphData, setGraphData] = useState<ChartData<any>>()
|
||||
|
||||
//
|
||||
// 0 Get Graph options
|
||||
//
|
||||
useEffect(() => {
|
||||
LoggerInstance.log('[pool graph] Fired getOptions().')
|
||||
const options = getOptions(locale, darkMode.value)
|
||||
setOptions(options)
|
||||
}, [locale, darkMode.value, graphType])
|
||||
|
||||
//
|
||||
// 1 Data manipulation
|
||||
//
|
||||
useEffect(() => {
|
||||
if (!poolSnapshots) return
|
||||
|
||||
const timestamps = poolSnapshots.map((item) => {
|
||||
const date = new Date(item.date * 1000)
|
||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
||||
})
|
||||
|
||||
let baseTokenLiquidityCumulative = '0'
|
||||
const liquidityHistory = poolSnapshots.map((item) => {
|
||||
baseTokenLiquidityCumulative = new Decimal(baseTokenLiquidityCumulative)
|
||||
.add(item.baseTokenLiquidity)
|
||||
.toString()
|
||||
return baseTokenLiquidityCumulative
|
||||
})
|
||||
|
||||
const priceHistory = poolSnapshots.map((item) => item.spotPrice)
|
||||
|
||||
let volumeCumulative = '0'
|
||||
const volumeHistory = poolSnapshots.map((item) => {
|
||||
volumeCumulative = new Decimal(volumeCumulative)
|
||||
.add(item.swapVolume)
|
||||
.toString()
|
||||
return volumeCumulative
|
||||
})
|
||||
|
||||
let data
|
||||
switch (graphType) {
|
||||
case 'price':
|
||||
data = priceHistory.slice(0)
|
||||
break
|
||||
case 'volume':
|
||||
data = volumeHistory.slice(0)
|
||||
break
|
||||
default:
|
||||
data = liquidityHistory.slice(0)
|
||||
break
|
||||
}
|
||||
|
||||
const newGraphData = {
|
||||
labels: timestamps.slice(0),
|
||||
datasets: [{ ...lineStyle, data, borderColor: `#8b98a9` }]
|
||||
}
|
||||
setGraphData(newGraphData)
|
||||
LoggerInstance.log('[pool graph] New graph data created:', newGraphData)
|
||||
}, [poolSnapshots, graphType])
|
||||
|
||||
return (
|
||||
<div className={styles.graphWrap}>
|
||||
{!graphData ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<>
|
||||
<Nav graphType={graphType} setGraphType={setGraphType} />
|
||||
|
||||
{graphType === 'volume' ? (
|
||||
<Bar width={416} height={120} data={graphData} options={options} />
|
||||
) : (
|
||||
<Line width={416} height={120} data={graphData} options={options} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -3,14 +3,13 @@ import React, {
|
||||
useState,
|
||||
ChangeEvent,
|
||||
useEffect,
|
||||
FormEvent,
|
||||
useRef
|
||||
} from 'react'
|
||||
import styles from './Remove.module.css'
|
||||
import Header from './Header'
|
||||
import { toast } from 'react-toastify'
|
||||
import Actions from './Actions'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import { LoggerInstance, Pool } from '@oceanprotocol/lib'
|
||||
import Token from './Token'
|
||||
import FormHelp from '@shared/FormInput/Help'
|
||||
import Button from '@shared/atoms/Button'
|
||||
@ -23,57 +22,53 @@ import Decimal from 'decimal.js'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import content from '../../../../../content/price.json'
|
||||
|
||||
const slippagePresets = ['5', '10', '15', '25', '50']
|
||||
|
||||
export default function Remove({
|
||||
setShowRemove,
|
||||
poolAddress,
|
||||
poolTokens,
|
||||
totalPoolTokens,
|
||||
dtSymbol,
|
||||
tokenOutAddress,
|
||||
tokenOutSymbol,
|
||||
fetchAllData
|
||||
}: {
|
||||
setShowRemove: (show: boolean) => void
|
||||
poolAddress: string
|
||||
poolTokens: string
|
||||
totalPoolTokens: string
|
||||
dtSymbol: string
|
||||
tokenOutAddress: string
|
||||
tokenOutSymbol: string
|
||||
fetchAllData: () => void
|
||||
}): ReactElement {
|
||||
const slippagePresets = ['5', '10', '15', '25', '50']
|
||||
const { accountId } = useWeb3()
|
||||
const { accountId, web3 } = useWeb3()
|
||||
const { isAssetNetwork } = useAsset()
|
||||
|
||||
const [amountPercent, setAmountPercent] = useState('0')
|
||||
const [amountMaxPercent, setAmountMaxPercent] = useState('100')
|
||||
const [amountPoolShares, setAmountPoolShares] = useState('0')
|
||||
const [amountOcean, setAmountOcean] = useState('0')
|
||||
const [amountDatatoken, setAmountDatatoken] = useState('0')
|
||||
const [isAdvanced, setIsAdvanced] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState<boolean>()
|
||||
const [txId, setTxId] = useState<string>()
|
||||
const [slippage, setSlippage] = useState<string>('5')
|
||||
const [minOceanAmount, setMinOceanAmount] = useState<string>('0')
|
||||
const [minDatatokenAmount, setMinDatatokenAmount] = useState<string>('0')
|
||||
|
||||
// TODO: precision needs to be set based on baseToken decimals
|
||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||
const poolInstance = new Pool(web3, LoggerInstance)
|
||||
|
||||
async function handleRemoveLiquidity() {
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
// const result =
|
||||
// isAdvanced === true
|
||||
// ? await ocean.pool.removePoolLiquidity(
|
||||
// accountId,
|
||||
// poolAddress,
|
||||
// amountPoolShares,
|
||||
// minDatatokenAmount,
|
||||
// minOceanAmount
|
||||
// )
|
||||
// : await ocean.pool.removeOceanLiquidityWithMinimum(
|
||||
// accountId,
|
||||
// poolAddress,
|
||||
// amountPoolShares,
|
||||
// minOceanAmount
|
||||
// )
|
||||
// setTxId(result?.transactionHash)
|
||||
const result = await poolInstance.exitswapPoolAmountIn(
|
||||
accountId,
|
||||
poolAddress,
|
||||
tokenOutAddress,
|
||||
amountPoolShares,
|
||||
minOceanAmount
|
||||
)
|
||||
setTxId(result?.transactionHash)
|
||||
fetchAllData()
|
||||
} catch (error) {
|
||||
LoggerInstance.error(error.message)
|
||||
@ -83,50 +78,33 @@ export default function Remove({
|
||||
}
|
||||
}
|
||||
|
||||
// Get and set max percentage
|
||||
// TODO: Get and set max percentage
|
||||
useEffect(() => {
|
||||
if (!accountId || !poolTokens) return
|
||||
|
||||
async function getMax() {
|
||||
// const amountMaxPercent =
|
||||
// isAdvanced === true
|
||||
// ? '100'
|
||||
// : await getMaxPercentRemove(poolAddress, poolTokens)
|
||||
// const amountMaxPercent = await getMaxPercentRemove(poolAddress, poolTokens)
|
||||
// setAmountMaxPercent(amountMaxPercent)
|
||||
}
|
||||
getMax()
|
||||
}, [accountId, isAdvanced, poolAddress, poolTokens])
|
||||
}, [accountId, poolAddress, poolTokens])
|
||||
|
||||
const getValues = useRef(
|
||||
debounce(async (newAmountPoolShares, isAdvanced) => {
|
||||
// if (isAdvanced === true) {
|
||||
// const tokens = await ocean.pool.getTokensRemovedforPoolShares(
|
||||
// poolAddress,
|
||||
// `${newAmountPoolShares}`
|
||||
// )
|
||||
// setAmountOcean(tokens?.oceanAmount)
|
||||
// setAmountDatatoken(tokens?.dtAmount)
|
||||
// return
|
||||
// }
|
||||
// const amountOcean = await ocean.pool.getOceanRemovedforPoolShares(
|
||||
// poolAddress,
|
||||
// newAmountPoolShares
|
||||
// )
|
||||
// setAmountOcean(amountOcean)
|
||||
debounce(async (newAmountPoolShares) => {
|
||||
const newAmountOcean = await poolInstance.calcSingleOutGivenPoolIn(
|
||||
poolAddress,
|
||||
tokenOutAddress,
|
||||
newAmountPoolShares
|
||||
)
|
||||
setAmountOcean(newAmountOcean)
|
||||
}, 150)
|
||||
)
|
||||
|
||||
// Check and set outputs when amountPoolShares changes
|
||||
useEffect(() => {
|
||||
if (!accountId || !poolTokens) return
|
||||
getValues.current(amountPoolShares, isAdvanced)
|
||||
}, [
|
||||
amountPoolShares,
|
||||
isAdvanced,
|
||||
accountId,
|
||||
poolTokens,
|
||||
poolAddress,
|
||||
totalPoolTokens
|
||||
])
|
||||
getValues.current(amountPoolShares)
|
||||
}, [amountPoolShares, accountId, poolTokens, poolAddress, totalPoolTokens])
|
||||
|
||||
useEffect(() => {
|
||||
const minOceanAmount = new Decimal(amountOcean)
|
||||
@ -134,14 +112,8 @@ export default function Remove({
|
||||
.dividedBy(100)
|
||||
.toString()
|
||||
|
||||
const minDatatokenAmount = new Decimal(amountDatatoken)
|
||||
.mul(new Decimal(100).minus(new Decimal(slippage)))
|
||||
.dividedBy(100)
|
||||
.toString()
|
||||
|
||||
setMinOceanAmount(minOceanAmount.slice(0, 18))
|
||||
setMinDatatokenAmount(minDatatokenAmount.slice(0, 18))
|
||||
}, [slippage, amountOcean, amountDatatoken, isAdvanced])
|
||||
}, [slippage, amountOcean])
|
||||
|
||||
// Set amountPoolShares based on set slider value
|
||||
function handleAmountPercentChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
@ -168,22 +140,6 @@ export default function Remove({
|
||||
setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`)
|
||||
}
|
||||
|
||||
function handleAdvancedButton(e: FormEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
setIsAdvanced(!isAdvanced)
|
||||
|
||||
setAmountPoolShares('0')
|
||||
setAmountPercent('0')
|
||||
setAmountOcean('0')
|
||||
setSlippage('5')
|
||||
setMinOceanAmount('0')
|
||||
setMinDatatokenAmount('0')
|
||||
|
||||
if (isAdvanced === true) {
|
||||
setAmountDatatoken('0')
|
||||
}
|
||||
}
|
||||
|
||||
function handleSlippageChange(e: ChangeEvent<HTMLSelectElement>) {
|
||||
setSlippage(e.target.value)
|
||||
}
|
||||
@ -219,20 +175,7 @@ export default function Remove({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<FormHelp>
|
||||
{isAdvanced === true
|
||||
? content.pool.remove.advanced
|
||||
: content.pool.remove.simple}
|
||||
</FormHelp>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleAdvancedButton}
|
||||
disabled={!isAssetNetwork}
|
||||
className={styles.toggle}
|
||||
>
|
||||
{isAdvanced === true ? 'Simple' : 'Advanced'}
|
||||
</Button>
|
||||
<FormHelp>{content.pool.remove.simple}</FormHelp>
|
||||
</div>
|
||||
</form>
|
||||
<div className={styles.output}>
|
||||
@ -242,14 +185,7 @@ export default function Remove({
|
||||
</div>
|
||||
<div>
|
||||
<p>{content.pool.remove.output.titleOut} minimum</p>
|
||||
{isAdvanced === true ? (
|
||||
<>
|
||||
<Token symbol="OCEAN" balance={minOceanAmount} />
|
||||
<Token symbol={dtSymbol} balance={minDatatokenAmount} />
|
||||
</>
|
||||
) : (
|
||||
<Token symbol="OCEAN" balance={minOceanAmount} />
|
||||
)}
|
||||
<Token symbol={tokenOutSymbol} balance={minOceanAmount} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.slippage}>
|
||||
@ -274,6 +210,8 @@ export default function Remove({
|
||||
successMessage="Successfully removed liquidity."
|
||||
isDisabled={!isAssetNetwork}
|
||||
txId={txId}
|
||||
tokenAddress={tokenOutAddress}
|
||||
tokenSymbol={tokenOutSymbol}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -7,10 +7,10 @@ import Decimal from 'decimal.js'
|
||||
export default function TokenList({
|
||||
title,
|
||||
children,
|
||||
ocean,
|
||||
oceanSymbol,
|
||||
dt,
|
||||
dtSymbol,
|
||||
baseTokenValue,
|
||||
baseTokenSymbol,
|
||||
datatokenValue,
|
||||
datatokenSymbol,
|
||||
poolShares,
|
||||
conversion,
|
||||
highlight,
|
||||
@ -18,10 +18,10 @@ export default function TokenList({
|
||||
}: {
|
||||
title: string | ReactNode
|
||||
children: ReactNode
|
||||
ocean: string
|
||||
oceanSymbol: string
|
||||
dt: string
|
||||
dtSymbol: string
|
||||
baseTokenValue: string
|
||||
baseTokenSymbol: string
|
||||
datatokenValue: string
|
||||
datatokenSymbol: string
|
||||
poolShares: string
|
||||
conversion: Decimal
|
||||
highlight?: boolean
|
||||
@ -32,8 +32,8 @@ export default function TokenList({
|
||||
<h3 className={styles.title}>{title}</h3>
|
||||
<div className={styles.tokens}>
|
||||
<div>
|
||||
<Token symbol={oceanSymbol} balance={ocean} />
|
||||
<Token symbol={dtSymbol} balance={dt} />
|
||||
<Token symbol={baseTokenSymbol} balance={baseTokenValue} />
|
||||
<Token symbol={datatokenSymbol} balance={datatokenValue} />
|
||||
{conversion.greaterThan(0) && (
|
||||
<Conversion
|
||||
price={conversion.toString()}
|
||||
@ -45,7 +45,6 @@ export default function TokenList({
|
||||
|
||||
<div>
|
||||
<Token symbol="pool shares" balance={poolShares} noIcon />
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,13 +13,16 @@ import TokenList from './TokenList'
|
||||
import AssetActionHistoryTable from '../AssetActionHistoryTable'
|
||||
import Graph from './Graph'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { PoolLiquidity_pool as PoolLiquidityData } from '../../../../@types/subgraph/PoolLiquidity'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import PoolTransactions from '@shared/PoolTransactions'
|
||||
import { isValidNumber } from '@utils/numbers'
|
||||
import Decimal from 'decimal.js'
|
||||
import content from '../../../../../content/price.json'
|
||||
import { getPoolData, getUserPoolShareBalance } from '@utils/subgraph'
|
||||
import { getPoolData } from '@utils/subgraph'
|
||||
import {
|
||||
PoolData_poolSnapshots as PoolDataPoolSnapshots,
|
||||
PoolData_poolData as PoolDataPoolData
|
||||
} from 'src/@types/subgraph/PoolData'
|
||||
|
||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||
|
||||
@ -29,10 +32,11 @@ function getWeight(weight: string) {
|
||||
|
||||
interface PoolInfo {
|
||||
poolFee: string
|
||||
weightOcean: string
|
||||
weightBaseToken: string
|
||||
weightDt: string
|
||||
dtSymbol: string
|
||||
oceanSymbol: string
|
||||
datatokenSymbol: string
|
||||
baseTokenSymbol: string
|
||||
baseTokenAddress: string
|
||||
totalPoolTokens: string
|
||||
totalLiquidityInOcean: Decimal
|
||||
}
|
||||
@ -59,7 +63,7 @@ export default function Pool(): ReactElement {
|
||||
const { isInPurgatory, ddo, owner, price, refreshInterval, isAssetNetwork } =
|
||||
useAsset()
|
||||
|
||||
const [poolData, setPoolData] = useState<PoolLiquidityData>()
|
||||
const [poolData, setPoolData] = useState<PoolDataPoolData>()
|
||||
const [poolInfo, setPoolInfo] = useState<PoolInfo>(
|
||||
initialPoolInfo as PoolInfo
|
||||
)
|
||||
@ -69,6 +73,7 @@ export default function Pool(): ReactElement {
|
||||
const [poolInfoUser, setPoolInfoUser] = useState<PoolInfoUser>(
|
||||
initialPoolInfoUser as PoolInfoUser
|
||||
)
|
||||
const [poolSnapshots, setPoolSnapshots] = useState<PoolDataPoolSnapshots[]>()
|
||||
|
||||
const [hasUserAddedLiquidity, setUserHasAddedLiquidity] = useState(false)
|
||||
const [showAdd, setShowAdd] = useState(false)
|
||||
@ -76,34 +81,27 @@ export default function Pool(): ReactElement {
|
||||
const [isRemoveDisabled, setIsRemoveDisabled] = useState(false)
|
||||
const [fetchInterval, setFetchInterval] = useState<NodeJS.Timeout>()
|
||||
|
||||
const fetchPoolData = useCallback(async () => {
|
||||
const fetchAllData = useCallback(async () => {
|
||||
if (!ddo?.chainId || !price?.address || !owner) return
|
||||
|
||||
const poolData = await getPoolData(ddo.chainId, price.address, owner)
|
||||
setPoolData(poolData)
|
||||
LoggerInstance.log('[pool] Fetched pool data:', poolData)
|
||||
}, [ddo?.chainId, price?.address, owner])
|
||||
|
||||
const fetchUserShares = useCallback(async () => {
|
||||
if (!ddo?.chainId || !price?.address || !accountId) return
|
||||
|
||||
const userShares = await getUserPoolShareBalance(
|
||||
const response = await getPoolData(
|
||||
ddo.chainId,
|
||||
price.address,
|
||||
accountId
|
||||
owner,
|
||||
accountId || ''
|
||||
)
|
||||
if (!response) return
|
||||
|
||||
setPoolData(response.poolData)
|
||||
setPoolInfoUser((prevState) => ({
|
||||
...prevState,
|
||||
poolShares: userShares
|
||||
poolShares: response.poolDataUser?.shares[0]?.shares
|
||||
}))
|
||||
LoggerInstance.log(`[pool] Fetched user shares: ${userShares}`)
|
||||
}, [ddo?.chainId, price?.address, accountId])
|
||||
|
||||
// Helper: fetch everything
|
||||
const fetchAllData = useCallback(() => {
|
||||
fetchPoolData()
|
||||
fetchUserShares()
|
||||
}, [fetchPoolData, fetchUserShares])
|
||||
setPoolSnapshots(response.poolSnapshots)
|
||||
LoggerInstance.log('[pool] Fetched pool data:', response.poolData)
|
||||
LoggerInstance.log('[pool] Fetched user data:', response.poolDataUser)
|
||||
LoggerInstance.log('[pool] Fetched pool snapshots:', response.poolSnapshots)
|
||||
}, [ddo?.chainId, price?.address, owner, accountId])
|
||||
|
||||
// Helper: start interval fetching
|
||||
const initFetchInterval = useCallback(() => {
|
||||
@ -158,10 +156,11 @@ export default function Pool(): ReactElement {
|
||||
|
||||
const newPoolInfo = {
|
||||
poolFee,
|
||||
weightOcean: getWeight(poolData.baseTokenWeight),
|
||||
weightBaseToken: getWeight(poolData.baseTokenWeight),
|
||||
weightDt: getWeight(poolData.datatokenWeight),
|
||||
dtSymbol: poolData.datatoken.symbol,
|
||||
oceanSymbol: poolData.baseToken.symbol,
|
||||
datatokenSymbol: poolData.datatoken.symbol,
|
||||
baseTokenSymbol: poolData.baseToken.symbol,
|
||||
baseTokenAddress: poolData.baseToken.address,
|
||||
totalPoolTokens: poolData.totalShares,
|
||||
totalLiquidityInOcean
|
||||
}
|
||||
@ -327,7 +326,6 @@ export default function Pool(): ReactElement {
|
||||
//
|
||||
useEffect(() => {
|
||||
if (!owner || !accountId) return
|
||||
|
||||
setIsRemoveDisabled(isInPurgatory && owner === accountId)
|
||||
}, [isInPurgatory, owner, accountId])
|
||||
|
||||
@ -337,32 +335,34 @@ export default function Pool(): ReactElement {
|
||||
<Add
|
||||
setShowAdd={setShowAdd}
|
||||
poolAddress={price?.address}
|
||||
totalPoolTokens={poolInfo.totalPoolTokens}
|
||||
totalPoolTokens={poolInfo?.totalPoolTokens}
|
||||
totalBalance={{
|
||||
ocean: new Decimal(price?.ocean).toString(),
|
||||
datatoken: new Decimal(price?.datatoken).toString()
|
||||
}}
|
||||
swapFee={poolInfo.poolFee}
|
||||
dtSymbol={poolInfo.dtSymbol}
|
||||
dtAddress={ddo?.services[0].datatokenAddress}
|
||||
swapFee={poolInfo?.poolFee}
|
||||
datatokenSymbol={poolInfo?.datatokenSymbol}
|
||||
tokenInAddress={poolInfo?.baseTokenAddress}
|
||||
tokenInSymbol={poolInfo?.baseTokenSymbol}
|
||||
fetchAllData={fetchAllData}
|
||||
/>
|
||||
) : showRemove ? (
|
||||
<Remove
|
||||
setShowRemove={setShowRemove}
|
||||
poolAddress={price?.address}
|
||||
poolTokens={poolInfoUser.poolShares}
|
||||
poolTokens={poolInfoUser?.poolShares}
|
||||
totalPoolTokens={poolInfo?.totalPoolTokens}
|
||||
dtSymbol={poolInfo?.dtSymbol}
|
||||
tokenOutAddress={poolInfo?.baseTokenAddress}
|
||||
tokenOutSymbol={poolInfo?.baseTokenSymbol}
|
||||
fetchAllData={fetchAllData}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.dataToken}>
|
||||
<PriceUnit price="1" symbol={poolInfo?.dtSymbol} /> ={' '}
|
||||
<PriceUnit price="1" symbol={poolInfo?.datatokenSymbol} /> ={' '}
|
||||
<PriceUnit
|
||||
price={`${price?.value}`}
|
||||
symbol={poolInfo?.oceanSymbol}
|
||||
symbol={poolInfo?.baseTokenSymbol}
|
||||
/>
|
||||
<Tooltip content={content.pool.tooltips.price} />
|
||||
<div className={styles.dataTokenLinks}>
|
||||
@ -397,10 +397,10 @@ export default function Pool(): ReactElement {
|
||||
/>
|
||||
</>
|
||||
}
|
||||
ocean={`${poolInfoUser?.liquidity?.ocean}`}
|
||||
oceanSymbol={poolInfo?.oceanSymbol}
|
||||
dt={`${poolInfoUser?.liquidity?.datatoken}`}
|
||||
dtSymbol={poolInfo?.dtSymbol}
|
||||
baseTokenValue={`${poolInfoUser?.liquidity?.ocean}`}
|
||||
baseTokenSymbol={poolInfo?.baseTokenSymbol}
|
||||
datatokenValue={`${poolInfoUser?.liquidity?.datatoken}`}
|
||||
datatokenSymbol={poolInfo?.datatokenSymbol}
|
||||
poolShares={poolInfoUser?.poolShares}
|
||||
conversion={poolInfoUser?.totalLiquidityInOcean}
|
||||
highlight
|
||||
@ -414,10 +414,10 @@ export default function Pool(): ReactElement {
|
||||
|
||||
<TokenList
|
||||
title="Pool Creator Statistics"
|
||||
ocean={`${poolInfoOwner?.liquidity?.ocean}`}
|
||||
oceanSymbol={poolInfo?.oceanSymbol}
|
||||
dt={`${poolInfoOwner?.liquidity?.datatoken}`}
|
||||
dtSymbol={poolInfo?.dtSymbol}
|
||||
baseTokenValue={`${poolInfoOwner?.liquidity?.ocean}`}
|
||||
baseTokenSymbol={poolInfo?.baseTokenSymbol}
|
||||
datatokenValue={`${poolInfoOwner?.liquidity?.datatoken}`}
|
||||
datatokenSymbol={poolInfo?.datatokenSymbol}
|
||||
poolShares={poolInfoOwner?.poolShares}
|
||||
conversion={poolInfoOwner?.totalLiquidityInOcean}
|
||||
>
|
||||
@ -435,18 +435,18 @@ export default function Pool(): ReactElement {
|
||||
{poolInfo?.weightDt && (
|
||||
<span
|
||||
className={styles.titleInfo}
|
||||
title={`Weight of ${poolInfo?.weightOcean}% OCEAN & ${poolInfo?.weightDt}% ${poolInfo?.dtSymbol}`}
|
||||
title={`Weight of ${poolInfo?.weightBaseToken}% ${poolInfo?.baseTokenSymbol} & ${poolInfo?.weightDt}% ${poolInfo?.datatokenSymbol}`}
|
||||
>
|
||||
{poolInfo?.weightOcean}/{poolInfo?.weightDt}
|
||||
{poolInfo?.weightBaseToken}/{poolInfo?.weightDt}
|
||||
</span>
|
||||
)}
|
||||
<Graph />
|
||||
<Graph poolSnapshots={poolSnapshots} />
|
||||
</>
|
||||
}
|
||||
ocean={`${price?.ocean}`}
|
||||
oceanSymbol={poolInfo?.oceanSymbol}
|
||||
dt={`${price?.datatoken}`}
|
||||
dtSymbol={poolInfo?.dtSymbol}
|
||||
baseTokenValue={`${price?.ocean}`}
|
||||
baseTokenSymbol={poolInfo?.baseTokenSymbol}
|
||||
datatokenValue={`${price?.datatoken}`}
|
||||
datatokenSymbol={poolInfo?.datatokenSymbol}
|
||||
poolShares={poolInfo?.totalPoolTokens}
|
||||
conversion={poolInfo?.totalLiquidityInOcean}
|
||||
showTVLLabel
|
||||
|
@ -38,6 +38,9 @@ export default function FormTrade({
|
||||
const [maximumDt, setMaximumDt] = useState(maxDt)
|
||||
const [isWarningAccepted, setIsWarningAccepted] = useState(false)
|
||||
|
||||
const tokenAddress = ''
|
||||
const tokenSymbol = ''
|
||||
|
||||
const validationSchema: Yup.SchemaOf<FormTradeData> = Yup.object()
|
||||
.shape({
|
||||
ocean: Yup.number()
|
||||
@ -152,8 +155,9 @@ export default function FormTrade({
|
||||
: undefined
|
||||
}
|
||||
action={submitForm}
|
||||
coin={coinFrom}
|
||||
txId={txId}
|
||||
tokenAddress={tokenAddress}
|
||||
tokenSymbol={tokenSymbol}
|
||||
/>
|
||||
|
||||
{debug && (
|
||||
|
@ -73,14 +73,15 @@ export default function AssetActions({
|
||||
|
||||
async function initFileInfo() {
|
||||
setFileIsLoading(true)
|
||||
|
||||
const asset = formikState?.values?.services?.[0].files?.[0].url || ddo.id
|
||||
const fileUrl =
|
||||
formikState?.values?.services?.[0].files?.[0].url ||
|
||||
(ddo.metadata?.links ? ddo.metadata?.links[0] : ' ')
|
||||
const providerUrl =
|
||||
formikState?.values?.services[0].providerUrl.url ||
|
||||
oceanConfig.providerUri
|
||||
|
||||
try {
|
||||
const fileInfoResponse = await getFileInfo(asset, providerUrl)
|
||||
const fileInfoResponse = await getFileInfo(fileUrl, providerUrl)
|
||||
fileInfoResponse && setFileMetadata(fileInfoResponse[0])
|
||||
setFileIsLoading(false)
|
||||
} catch (error) {
|
||||
|
@ -10,13 +10,17 @@ import styles from './EditHistory.module.css'
|
||||
const getReceipts = gql`
|
||||
query ReceiptData($address: ID!) {
|
||||
nftUpdates(
|
||||
where: { id: $address }
|
||||
where: { nft: $address }
|
||||
orderBy: timestamp
|
||||
orderDirection: desc
|
||||
) {
|
||||
id
|
||||
nft {
|
||||
address
|
||||
}
|
||||
tx
|
||||
timestamp
|
||||
type
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -24,6 +28,20 @@ const getReceipts = gql`
|
||||
export default function EditHistory(): ReactElement {
|
||||
const { ddo } = useAsset()
|
||||
|
||||
function getUpdateType(type: string): string {
|
||||
switch (type) {
|
||||
case 'METADATA_CREATED':
|
||||
return 'published'
|
||||
case 'METADATA_UPDATED':
|
||||
return 'updated'
|
||||
case 'STATE_UPDATED':
|
||||
return 'state updated'
|
||||
case 'TOKENURI_UPDATED':
|
||||
return 'NFT metadata updated'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
//
|
||||
// 1. Construct subgraph query based on DDO.
|
||||
// Need to wait for it to avoid infinite rerender loop with useQuery.
|
||||
@ -39,7 +57,7 @@ export default function EditHistory(): ReactElement {
|
||||
|
||||
const [result] = useQuery({
|
||||
query: getReceipts,
|
||||
variables: { address: ddo?.services[0].datatokenAddress.toLowerCase() },
|
||||
variables: { address: ddo?.nft.address.toLowerCase() },
|
||||
context: queryContext,
|
||||
pause: !ddo || !queryContext
|
||||
})
|
||||
@ -49,18 +67,10 @@ export default function EditHistory(): ReactElement {
|
||||
// 2. Construct display data based on fetched data.
|
||||
//
|
||||
const [receipts, setReceipts] = useState<ReceiptData[]>()
|
||||
const [creationTx, setCreationTx] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || data.nftUpdates.length === 0) return
|
||||
|
||||
const receiptCollectionLength = data.nftUpdates.length
|
||||
const creationData = data.nftUpdates[receiptCollectionLength - 1]
|
||||
setCreationTx(creationData.tx)
|
||||
|
||||
const receiptCollection = [...data.nftUpdates]
|
||||
receiptCollection.splice(-1, 1)
|
||||
|
||||
const receiptCollection = data.nftUpdates
|
||||
setReceipts(receiptCollection)
|
||||
}, [data])
|
||||
|
||||
@ -71,15 +81,11 @@ 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}`} relative isUnix />
|
||||
{getUpdateType(receipt.type)}{' '}
|
||||
<Time date={`${receipt.timestamp}`} relative isUnix />
|
||||
</ExplorerLink>
|
||||
</li>
|
||||
))}
|
||||
<li className={styles.item}>
|
||||
<ExplorerLink networkId={ddo?.chainId} path={`/tx/${creationTx}`}>
|
||||
published <Time date={ddo?.metadata?.created} relative />
|
||||
</ExplorerLink>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
|
@ -88,8 +88,10 @@ export async function transformPublishFormToDdo(
|
||||
const isPreview = !datatokenAddress && !nftAddress
|
||||
console.log('did', did, isPreview)
|
||||
// Transform from files[0].url to string[] assuming only 1 file
|
||||
const filesTransformed = files?.length && files[0].valid && [files[0].url]
|
||||
const linksTransformed = links?.length && links[0].valid && [links[0].url]
|
||||
const filesTransformed = files?.length &&
|
||||
files[0].valid && [files[0].url.replace('javascript:', '')]
|
||||
const linksTransformed = links?.length &&
|
||||
links[0].valid && [links[0].url.replace('javascript:', '')]
|
||||
|
||||
const newMetadata: Metadata = {
|
||||
created: currentTime,
|
||||
|
Loading…
Reference in New Issue
Block a user