1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

merge v4 into v4-c2d

This commit is contained in:
Bogdan Fazakas 2022-04-15 12:04:49 +03:00
commit 0b1d9cc436
52 changed files with 723 additions and 632 deletions

16
content/footer.json Normal file
View File

@ -0,0 +1,16 @@
{
"links": [
{
"name": "Docs",
"url": "https://docs.oceanprotocol.com"
},
{
"name": "GitHub",
"url": "https://github.com/oceanprotocol"
},
{
"name": "Discord",
"url": "https://discord.gg/TnXjkR5"
}
]
}

View File

@ -3,7 +3,7 @@
"siteTagline": "A marketplace to find, publish and trade data sets in the Ocean Network.", "siteTagline": "A marketplace to find, publish and trade data sets in the Ocean Network.",
"siteUrl": "https://v4.market.oceanprotocol.com", "siteUrl": "https://v4.market.oceanprotocol.com",
"siteImage": "/share.png", "siteImage": "/share.png",
"copyright": "All Rights Reserved. Powered by [Ocean Protocol](https://oceanprotocol.com)", "copyright": "All Rights Reserved. Powered by ",
"menu": [ "menu": [
{ {
"name": "Publish", "name": "Publish",

View File

@ -59,13 +59,7 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
const [userPoolShares, setUserPoolShares] = useState('0') const [userPoolShares, setUserPoolShares] = useState('0')
const fetchAllData = useCallback(async () => { const fetchAllData = useCallback(async () => {
if ( if (!asset?.chainId || !asset?.accessDetails?.addressOrId || !owner) return
!accountId ||
!asset?.chainId ||
!asset?.accessDetails?.addressOrId ||
!owner
)
return
const response = await getPoolData( const response = await getPoolData(
asset.chainId, asset.chainId,

View File

@ -50,7 +50,7 @@ export function generateNftMetadata(): NftMetadata {
const imageData = `data:image/svg+xml,${encodeSvg(svg.outerHTML)}` const imageData = `data:image/svg+xml,${encodeSvg(svg.outerHTML)}`
const newNft: NftMetadata = { const newNft: NftMetadata = {
name: 'Ocean Asset NFT', name: 'Ocean Data NFT',
symbol: 'OCEAN-NFT', symbol: 'OCEAN-NFT',
description: `This NFT represents an asset in the Ocean Protocol v4 ecosystem.`, description: `This NFT represents an asset in the Ocean Protocol v4 ecosystem.`,
external_url: 'https://market.oceanprotocol.com', external_url: 'https://market.oceanprotocol.com',

View File

@ -65,9 +65,7 @@ export default function InputElement({
disabled={disabled} disabled={disabled}
multiple={multiple} multiple={multiple}
> >
{field !== undefined && field.value === '' && ( {field !== undefined && field.value === '' && <option value="" />}
<option value="">---</option>
)}
{sortedOptions && {sortedOptions &&
(sortedOptions as string[]).map((option: string, index: number) => ( (sortedOptions as string[]).map((option: string, index: number) => (
<option key={index} value={option}> <option key={index} value={option}>

View File

@ -29,7 +29,7 @@ export interface InputProps {
options?: string[] options?: string[]
sortOptions?: boolean sortOptions?: boolean
additionalComponent?: ReactElement additionalComponent?: ReactElement
value?: string value?: string | number
onChange?( onChange?(
e: e:
| FormEvent<HTMLInputElement> | FormEvent<HTMLInputElement>

View File

@ -1,6 +1,5 @@
.token { .token {
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
margin-bottom: calc(var(--spacer) / 4);
white-space: nowrap; white-space: nowrap;
} }
@ -26,6 +25,21 @@
height: var(--font-size-base); height: var(--font-size-base);
} }
.conversion {
composes: token;
margin-bottom: 0;
font-weight: var(--font-weight-base) !important;
font-size: var(--font-size-small);
padding-left: var(--font-size-base);
padding-top: calc(var(--spacer) / 10);
}
.conversion strong {
font-size: var(--font-size-base);
color: var(--font-color-heading);
line-height: 1;
}
/* Data Token Icon Style */ /* Data Token Icon Style */
.icon:not([class*='OCEAN']) path { .icon:not([class*='OCEAN']) path {
fill: var(--brand-violet); fill: var(--brand-violet);

View File

@ -0,0 +1,40 @@
import React, { ReactElement } from 'react'
import styles from './index.module.css'
import PriceUnit from '@shared/Price/PriceUnit'
import Logo from '@shared/atoms/Logo'
import Decimal from 'decimal.js'
import Conversion from '@shared/Price/Conversion'
import { MAX_DECIMALS } from '@utils/constants'
export default function Token({
symbol,
balance,
conversion,
noIcon,
size
}: {
symbol: string
balance: string
conversion?: Decimal
noIcon?: boolean
size?: 'small' | 'mini'
}): ReactElement {
return (
<>
<div className={`${styles.token} ${size ? styles[size] : ''}`}>
<figure
className={`${styles.icon} ${symbol} ${noIcon ? styles.noIcon : ''}`}
>
<Logo noWordmark />
</figure>
<PriceUnit price={balance} symbol={symbol} size={size} />
</div>
{conversion?.greaterThan(0) && (
<Conversion
price={conversion.toDecimalPlaces(MAX_DECIMALS).toString()}
className={styles.conversion}
/>
)}
</>
)
}

View File

@ -15,5 +15,5 @@
} }
.container.narrow { .container.narrow {
max-width: 42rem; max-width: 62rem;
} }

View File

@ -1,6 +1,5 @@
.actions { .actions {
composes: container from './Pool/index.module.css'; composes: section from './Pool/Section/index.module.css';
border-top: 1px solid var(--border-color);
margin-top: calc(var(--spacer) / 1.5); margin-top: calc(var(--spacer) / 1.5);
padding: calc(var(--spacer) / 1.5); padding: calc(var(--spacer) / 1.5);
background: var(--background-highlight); background: var(--background-highlight);
@ -12,7 +11,7 @@
} }
.title { .title {
composes: title from './Pool/index.module.css'; composes: title from './Pool/Section/Title.module.css';
margin-bottom: 0; margin-bottom: 0;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -1,7 +1,7 @@
import React, { ReactElement, useState } from 'react' import React, { ReactElement, useState } from 'react'
import Loader from '@shared/atoms/Loader' import Loader from '@shared/atoms/Loader'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import styles from './Actions.module.css' import styles from './index.module.css'
import ExplorerLink from '@shared/ExplorerLink' import ExplorerLink from '@shared/ExplorerLink'
import SuccessConfetti from '@shared/SuccessConfetti' import SuccessConfetti from '@shared/SuccessConfetti'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'

View File

@ -14,29 +14,21 @@ import { useWeb3 } from '@context/Web3'
import { isValidNumber } from '@utils/numbers' import { isValidNumber } from '@utils/numbers'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import { LoggerInstance, Pool } from '@oceanprotocol/lib' import { Pool } from '@oceanprotocol/lib'
import { usePool } from '@context/Pool'
export default function FormAdd({ export default function FormAdd({
tokenInAddress,
tokenInSymbol,
amountMax, amountMax,
totalPoolTokens,
totalBalance,
poolAddress,
setNewPoolTokens, setNewPoolTokens,
setNewPoolShare setNewPoolShare
}: { }: {
tokenInAddress: string
tokenInSymbol: string
amountMax: string amountMax: string
totalPoolTokens: string
totalBalance: PoolBalance
poolAddress: string
setNewPoolTokens: (value: string) => void setNewPoolTokens: (value: string) => void
setNewPoolShare: (value: string) => void setNewPoolShare: (value: string) => void
}): ReactElement { }): ReactElement {
const { balance, web3 } = useWeb3() const { balance, web3 } = useWeb3()
const { isAssetNetwork } = useAsset() const { isAssetNetwork } = useAsset()
const { poolData, poolInfo } = usePool()
// Connect with form // Connect with form
const { const {
@ -47,9 +39,9 @@ export default function FormAdd({
useEffect(() => { useEffect(() => {
async function calculatePoolShares() { async function calculatePoolShares() {
if (!web3) return if (!web3 || !poolData?.id || !poolInfo?.totalPoolTokens) return
if (!values.amount || !tokenInAddress) { if (!values.amount || !poolInfo?.baseTokenAddress) {
setNewPoolTokens('0') setNewPoolTokens('0')
setNewPoolShare('0') setNewPoolShare('0')
return return
@ -59,31 +51,32 @@ export default function FormAdd({
const poolInstance = new Pool(web3) const poolInstance = new Pool(web3)
const poolTokens = await poolInstance.calcPoolOutGivenSingleIn( const poolTokens = await poolInstance.calcPoolOutGivenSingleIn(
poolAddress, poolData.id,
tokenInAddress, poolInfo.baseTokenAddress,
values.amount values.amount
) )
setNewPoolTokens(poolTokens) setNewPoolTokens(poolTokens)
const newPoolShareDecimal = const newPoolShareDecimal =
isValidNumber(poolTokens) && isValidNumber(totalPoolTokens) isValidNumber(poolTokens) && isValidNumber(poolInfo.totalPoolTokens)
? new Decimal(poolTokens) ? new Decimal(poolTokens)
.dividedBy( .dividedBy(
new Decimal(totalPoolTokens).plus(new Decimal(poolTokens)) new Decimal(poolInfo.totalPoolTokens).plus(
new Decimal(poolTokens)
)
) )
.mul(100) .mul(100)
.toString() .toString()
: '0' : '0'
totalBalance && setNewPoolShare(newPoolShareDecimal) setNewPoolShare(newPoolShareDecimal)
} }
calculatePoolShares() calculatePoolShares()
}, [ }, [
tokenInAddress, poolInfo?.baseTokenAddress,
web3, web3,
values.amount, values.amount,
totalBalance, poolInfo?.totalPoolTokens,
totalPoolTokens,
amountMax, amountMax,
poolAddress, poolData?.id,
setNewPoolTokens, setNewPoolTokens,
setNewPoolShare setNewPoolShare
]) ])
@ -93,7 +86,7 @@ export default function FormAdd({
<UserLiquidity <UserLiquidity
amount={balance.ocean} amount={balance.ocean}
amountMax={amountMax} amountMax={amountMax}
symbol={tokenInSymbol} symbol={poolInfo?.baseTokenSymbol}
/> />
<Field name="amount"> <Field name="amount">
@ -111,7 +104,7 @@ export default function FormAdd({
min="0" min="0"
value={values.amount} value={values.amount}
step="any" step="any"
prefix={tokenInSymbol} prefix={poolInfo?.baseTokenSymbol}
placeholder="0" placeholder="0"
field={field} field={field}
form={form} form={form}

View File

@ -1,60 +1,24 @@
import { FormikContextType, useFormikContext } from 'formik' import React, { ReactElement } from 'react'
import React, { ReactElement, useEffect, useState } from 'react'
import { FormAddLiquidity } from '.'
import FormHelp from '@shared/FormInput/Help' import FormHelp from '@shared/FormInput/Help'
import Token from '../Token' import Token from '../../../../@shared/Token'
import styles from './Output.module.css' import styles from './Output.module.css'
import Decimal from 'decimal.js'
import content from '../../../../../../content/price.json' import content from '../../../../../../content/price.json'
import { usePool } from '@context/Pool'
export default function Output({ export default function Output({
newPoolTokens, newPoolTokens,
newPoolShare, newPoolShare
swapFee,
datatokenSymbol,
totalPoolTokens,
totalBalance
}: { }: {
newPoolTokens: string newPoolTokens: string
newPoolShare: string newPoolShare: string
swapFee: string
datatokenSymbol: string
totalPoolTokens: string
totalBalance: PoolBalance
}): ReactElement { }): ReactElement {
const { help, titleIn, titleOut } = content.pool.add.output const { help, titleIn } = content.pool.add.output
const { poolInfo } = usePool()
// Connect with form
const { values }: FormikContextType<FormAddLiquidity> = useFormikContext()
const [poolOcean, setPoolOcean] = useState('0')
const [poolDatatoken, setPoolDatatoken] = useState('0')
useEffect(() => {
if (!values.amount || !totalBalance || !totalPoolTokens || !newPoolTokens)
return
const newPoolSupply = new Decimal(totalPoolTokens).plus(newPoolTokens)
const ratio = new Decimal(newPoolTokens).div(newPoolSupply)
const newOceanReserve = new Decimal(totalBalance.baseToken).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,
totalBalance,
totalPoolTokens,
newPoolShare,
newPoolTokens
])
return ( return (
<> <>
<FormHelp className={styles.help}> <FormHelp className={styles.help}>
{help.replace('SWAPFEE', swapFee)} {help.replace('SWAPFEE', poolInfo?.liquidityProviderSwapFee)}
</FormHelp> </FormHelp>
<div className={styles.output}> <div className={styles.output}>
<p>{titleIn}</p> <p>{titleIn}</p>

View File

@ -1,5 +1,5 @@
import React, { ReactElement, useState, useEffect } from 'react' import React, { ReactElement, useState, useEffect } from 'react'
import Header from '../Header' import Header from '../Actions/Header'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import Actions from '../Actions' import Actions from '../Actions'
import * as Yup from 'yup' import * as Yup from 'yup'
@ -25,28 +25,15 @@ const initialValues: FormAddLiquidity = {
} }
export default function Add({ export default function Add({
setShowAdd, setShowAdd
poolAddress,
totalPoolTokens,
totalBalance,
swapFee,
datatokenSymbol,
tokenInSymbol,
tokenInAddress
}: { }: {
setShowAdd: (show: boolean) => void setShowAdd: (show: boolean) => void
poolAddress: string
totalPoolTokens: string
totalBalance: PoolBalance
swapFee: string
datatokenSymbol: string
tokenInSymbol: string
tokenInAddress: string
}): ReactElement { }): ReactElement {
const { accountId, balance, web3 } = useWeb3() const { accountId, balance, web3 } = useWeb3()
const { isAssetNetwork } = useAsset() const { isAssetNetwork } = useAsset()
const { fetchAllData } = usePool() const { poolData, poolInfo, fetchAllData } = usePool()
const { debug } = useUserPreferences() const { debug } = useUserPreferences()
const [txId, setTxId] = useState<string>() const [txId, setTxId] = useState<string>()
const [amountMax, setAmountMax] = useState<string>() const [amountMax, setAmountMax] = useState<string>()
const [newPoolTokens, setNewPoolTokens] = useState('0') const [newPoolTokens, setNewPoolTokens] = useState('0')
@ -67,15 +54,22 @@ export default function Add({
// Get maximum amount for OCEAN // Get maximum amount for OCEAN
useEffect(() => { useEffect(() => {
if (!web3 || !accountId || !isAssetNetwork || !poolAddress) return if (
!web3 ||
!accountId ||
!isAssetNetwork ||
!poolData?.id ||
!poolInfo?.baseTokenAddress
)
return
async function getMaximum() { async function getMaximum() {
try { try {
const poolInstance = new Pool(web3) const poolInstance = new Pool(web3)
const poolReserve = await poolInstance.getReserve( const poolReserve = await poolInstance.getReserve(
poolAddress, poolData.id,
tokenInAddress poolInfo.baseTokenAddress
) )
const amountMaxPool = calcMaxExactIn(poolReserve) const amountMaxPool = calcMaxExactIn(poolReserve)
@ -93,8 +87,8 @@ export default function Add({
web3, web3,
accountId, accountId,
isAssetNetwork, isAssetNetwork,
poolAddress, poolData?.id,
tokenInAddress, poolInfo?.baseTokenAddress,
balance?.ocean balance?.ocean
]) ])
@ -106,7 +100,7 @@ export default function Add({
try { try {
const result = await poolInstance.joinswapExternAmountIn( const result = await poolInstance.joinswapExternAmountIn(
accountId, accountId,
poolAddress, poolData?.id,
amount, amount,
minPoolAmountOut minPoolAmountOut
) )
@ -141,12 +135,7 @@ export default function Add({
<div className={styles.addInput}> <div className={styles.addInput}>
{isWarningAccepted ? ( {isWarningAccepted ? (
<FormAdd <FormAdd
tokenInAddress={tokenInAddress}
tokenInSymbol={tokenInSymbol}
amountMax={amountMax} amountMax={amountMax}
totalPoolTokens={totalPoolTokens}
totalBalance={totalBalance}
poolAddress={poolAddress}
setNewPoolTokens={setNewPoolTokens} setNewPoolTokens={setNewPoolTokens}
setNewPoolShare={setNewPoolShare} setNewPoolShare={setNewPoolShare}
/> />
@ -166,14 +155,7 @@ export default function Add({
)} )}
</div> </div>
<Output <Output newPoolTokens={newPoolTokens} newPoolShare={newPoolShare} />
newPoolTokens={newPoolTokens}
newPoolShare={newPoolShare}
swapFee={swapFee}
datatokenSymbol={datatokenSymbol}
totalPoolTokens={totalPoolTokens}
totalBalance={totalBalance}
/>
<Actions <Actions
isDisabled={ isDisabled={
@ -189,8 +171,8 @@ export default function Add({
actionName={content.pool.add.action} actionName={content.pool.add.action}
action={submitForm} action={submitForm}
amount={values.amount} amount={values.amount}
tokenAddress={tokenInAddress} tokenAddress={poolInfo?.baseTokenAddress}
tokenSymbol={tokenInSymbol} tokenSymbol={poolInfo?.baseTokenSymbol}
txId={txId} txId={txId}
setSubmitting={setSubmitting} setSubmitting={setSubmitting}
/> />

View File

@ -1,9 +1,11 @@
.type { .type {
position: absolute; position: relative;
bottom: -10px;
z-index: 1; z-index: 1;
width: 100%;
text-align: center; text-align: center;
padding: 5px var(--spacer); padding: 5px var(--spacer);
border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
} }
.button, .button,

View File

@ -1,19 +1,17 @@
.graphWrap { .graphWrap {
composes: container from '../Section/index.module.css';
padding: 0;
grid-column: full;
min-height: 120px; min-height: 120px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin: calc(var(--spacer) / 6) -1.35rem calc(var(--spacer) / 1.5) -1.35rem; flex-direction: column;
margin-bottom: calc(var(--spacer) / 2);
position: relative; position: relative;
} }
@media (min-width: 40rem) {
.graphWrap {
margin-left: -2rem;
margin-right: -2rem;
}
}
.graphWrap canvas { .graphWrap canvas {
position: relative; position: relative;
z-index: 0; z-index: 0;

View File

@ -11,17 +11,14 @@ import Decimal from 'decimal.js'
import { lineStyle, GraphType } from './_constants' import { lineStyle, GraphType } from './_constants'
import Nav from './Nav' import Nav from './Nav'
import { getOptions } from './_utils' import { getOptions } from './_utils'
import { PoolData_poolSnapshots as PoolDataPoolSnapshots } from 'src/@types/subgraph/PoolData'
import { usePrices } from '@context/Prices' import { usePrices } from '@context/Prices'
import { MAX_DECIMALS } from '@utils/constants' import { MAX_DECIMALS } from '@utils/constants'
import { usePool } from '@context/Pool'
export default function Graph({ export default function Graph(): ReactElement {
poolSnapshots
}: {
poolSnapshots: PoolDataPoolSnapshots[]
}): ReactElement {
const { locale, currency } = useUserPreferences() const { locale, currency } = useUserPreferences()
const { prices } = usePrices() const { prices } = usePrices()
const { poolSnapshots } = usePool()
const darkMode = useDarkMode(false, darkModeConfig) const darkMode = useDarkMode(false, darkModeConfig)
const [options, setOptions] = useState<ChartOptions<any>>() const [options, setOptions] = useState<ChartOptions<any>>()
@ -104,13 +101,13 @@ export default function Graph({
<Loader /> <Loader />
) : ( ) : (
<> <>
<Nav graphType={graphType} setGraphType={setGraphType} />
{graphType === 'volume' ? ( {graphType === 'volume' ? (
<Bar width={416} height={120} data={graphData} options={options} /> <Bar width={416} height={120} data={graphData} options={options} />
) : ( ) : (
<Line width={416} height={120} data={graphData} options={options} /> <Line width={416} height={120} data={graphData} options={options} />
)} )}
<Nav graphType={graphType} setGraphType={setGraphType} />
</> </>
)} )}
</div> </div>

View File

@ -0,0 +1,19 @@
import { calcMaxExactOut } from '@oceanprotocol/lib'
import Decimal from 'decimal.js'
export async function getMax(poolTokens: string, totalPoolTokens: string) {
const poolTokensAmount = !poolTokens || poolTokens === '0' ? '1' : poolTokens
const maxTokensToRemoveFromPool = calcMaxExactOut(totalPoolTokens)
const poolTokensDecimal = new Decimal(poolTokensAmount)
const maxTokensToRemoveForUser = maxTokensToRemoveFromPool.greaterThan(
poolTokensDecimal
)
? poolTokensDecimal
: maxTokensToRemoveFromPool
const maxPercent = new Decimal(100)
.mul(maxTokensToRemoveForUser)
.div(poolTokensDecimal)
return maxPercent.toDecimalPlaces(0, Decimal.ROUND_DOWN).toString()
}

View File

@ -6,11 +6,11 @@ import React, {
useRef useRef
} from 'react' } from 'react'
import styles from './index.module.css' import styles from './index.module.css'
import Header from '../Header' import Header from '../Actions/Header'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import Actions from '../Actions' import Actions from '../Actions'
import { LoggerInstance, Pool, calcMaxExactOut } from '@oceanprotocol/lib' import { LoggerInstance, Pool } from '@oceanprotocol/lib'
import Token from '../Token' import Token from '../../../../@shared/Token'
import FormHelp from '@shared/FormInput/Help' import FormHelp from '@shared/FormInput/Help'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
@ -21,27 +21,18 @@ import Decimal from 'decimal.js'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import content from '../../../../../../content/price.json' import content from '../../../../../../content/price.json'
import { usePool } from '@context/Pool' import { usePool } from '@context/Pool'
import { getMax } from './_utils'
const slippagePresets = ['5', '10', '15', '25', '50'] const slippagePresets = ['5', '10', '15', '25', '50']
export default function Remove({ export default function Remove({
setShowRemove, setShowRemove
poolAddress,
poolTokens,
totalPoolTokens,
tokenOutAddress,
tokenOutSymbol
}: { }: {
setShowRemove: (show: boolean) => void setShowRemove: (show: boolean) => void
poolAddress: string
poolTokens: string
totalPoolTokens: string
tokenOutAddress: string
tokenOutSymbol: string
}): ReactElement { }): ReactElement {
const { accountId, web3 } = useWeb3() const { accountId, web3 } = useWeb3()
const { isAssetNetwork } = useAsset() const { isAssetNetwork } = useAsset()
const { fetchAllData } = usePool() const { poolData, poolInfo, poolInfoUser, fetchAllData } = usePool()
const [amountPercent, setAmountPercent] = useState('0') const [amountPercent, setAmountPercent] = useState('0')
const [amountMaxPercent, setAmountMaxPercent] = useState('100') const [amountMaxPercent, setAmountMaxPercent] = useState('100')
@ -62,7 +53,7 @@ export default function Remove({
try { try {
const result = await poolInstance.exitswapPoolAmountIn( const result = await poolInstance.exitswapPoolAmountIn(
accountId, accountId,
poolAddress, poolData?.id,
amountPoolShares, amountPoolShares,
minOceanAmount minOceanAmount
) )
@ -81,35 +72,23 @@ export default function Remove({
} }
} }
//
// Calculate and set maximum shares user is able to remove
//
useEffect(() => { useEffect(() => {
if (!accountId || !poolTokens) return if (!accountId || !poolInfoUser?.poolShares || !poolInfo?.totalPoolTokens)
return
async function getMax() { getMax(poolInfoUser.poolShares, poolInfo.totalPoolTokens).then((max) =>
const poolTokensAmount = setAmountMaxPercent(max)
!poolTokens || poolTokens === '0' ? '1' : poolTokens )
const maxTokensToRemoveFromPool = calcMaxExactOut(totalPoolTokens) }, [accountId, poolInfoUser?.poolShares, poolInfo?.totalPoolTokens])
const poolTokensDecimal = new Decimal(poolTokensAmount)
const maxTokensToRemoveForUser = maxTokensToRemoveFromPool.greaterThan(
poolTokensDecimal
)
? poolTokensDecimal
: maxTokensToRemoveFromPool
const maxPercent = new Decimal(100)
.mul(maxTokensToRemoveForUser)
.div(poolTokensDecimal)
setAmountMaxPercent(
maxPercent.toDecimalPlaces(0, Decimal.ROUND_DOWN).toString()
)
}
getMax()
}, [accountId, poolAddress, poolTokens, totalPoolTokens])
const getValues = useRef( const getValues = useRef(
debounce(async (newAmountPoolShares) => { debounce(async (newAmountPoolShares) => {
const newAmountOcean = await poolInstance.calcSingleOutGivenPoolIn( const newAmountOcean = await poolInstance.calcSingleOutGivenPoolIn(
poolAddress, poolData?.id,
tokenOutAddress, poolInfo?.baseTokenAddress,
newAmountPoolShares newAmountPoolShares
) )
@ -119,9 +98,21 @@ export default function Remove({
// Check and set outputs when amountPoolShares changes // Check and set outputs when amountPoolShares changes
useEffect(() => { useEffect(() => {
if (!accountId || !poolTokens) return if (
!accountId ||
!poolInfoUser?.poolShares ||
!poolInfo?.totalPoolTokens ||
!poolData?.id
)
return
getValues.current(amountPoolShares) getValues.current(amountPoolShares)
}, [amountPoolShares, accountId, poolTokens, poolAddress, totalPoolTokens]) }, [
amountPoolShares,
accountId,
poolInfoUser?.poolShares,
poolData?.id,
poolInfo?.totalPoolTokens
])
useEffect(() => { useEffect(() => {
if (!amountOcean || amountPercent === '0') { if (!amountOcean || amountPercent === '0') {
@ -135,16 +126,16 @@ export default function Remove({
.toString() .toString()
setMinOceanAmount(minOceanAmount.slice(0, 18)) setMinOceanAmount(minOceanAmount.slice(0, 18))
}, [slippage, amountOcean]) }, [slippage, amountOcean, amountPercent])
// Set amountPoolShares based on set slider value // Set amountPoolShares based on set slider value
function handleAmountPercentChange(e: ChangeEvent<HTMLInputElement>) { function handleAmountPercentChange(e: ChangeEvent<HTMLInputElement>) {
setAmountPercent(e.target.value) setAmountPercent(e.target.value)
if (!poolTokens) return if (!poolInfoUser?.poolShares) return
const amountPoolShares = new Decimal(e.target.value) const amountPoolShares = new Decimal(e.target.value)
.dividedBy(100) .dividedBy(100)
.mul(new Decimal(poolTokens)) .mul(new Decimal(poolInfoUser.poolShares))
.toString() .toString()
setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`) setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`)
@ -156,7 +147,7 @@ export default function Remove({
const amountPoolShares = new Decimal(amountMaxPercent) const amountPoolShares = new Decimal(amountMaxPercent)
.dividedBy(100) .dividedBy(100)
.mul(new Decimal(poolTokens)) .mul(new Decimal(poolInfoUser?.poolShares))
.toString() .toString()
setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`) setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`)
@ -177,7 +168,7 @@ export default function Remove({
/> />
<form className={styles.removeInput}> <form className={styles.removeInput}>
<UserLiquidity amount={poolTokens} symbol="pool shares" /> <UserLiquidity amount={poolInfoUser?.poolShares} symbol="pool shares" />
<div className={styles.range}> <div className={styles.range}>
<h3>{amountPercent}%</h3> <h3>{amountPercent}%</h3>
<div className={styles.slider}> <div className={styles.slider}>
@ -206,7 +197,7 @@ export default function Remove({
<div className={styles.output}> <div className={styles.output}>
<div> <div>
<p>{content.pool.remove.output.titleOut} minimum</p> <p>{content.pool.remove.output.titleOut} minimum</p>
<Token symbol={tokenOutSymbol} balance={minOceanAmount} /> <Token symbol={poolInfo?.baseTokenSymbol} balance={minOceanAmount} />
</div> </div>
{/* <div> {/* <div>
<p>{content.pool.remove.output.titleIn}</p> <p>{content.pool.remove.output.titleIn}</p>
@ -237,11 +228,11 @@ export default function Remove({
!isAssetNetwork || !isAssetNetwork ||
amountPercent === '0' || amountPercent === '0' ||
amountOcean === '0' || amountOcean === '0' ||
poolTokens === '0' poolInfo?.totalPoolTokens === '0'
} }
txId={txId} txId={txId}
tokenAddress={tokenOutAddress} tokenAddress={poolInfo?.baseTokenAddress}
tokenSymbol={tokenOutSymbol} tokenSymbol={poolInfo?.baseTokenSymbol}
/> />
</div> </div>
) )

View File

@ -0,0 +1,13 @@
.title {
font-size: var(--font-size-base);
margin-bottom: calc(var(--spacer) / 3);
color: var(--color-secondary);
}
.titlePostfix {
font-size: var(--font-size-mini);
font-family: var(--font-family-base);
font-weight: var(--font-weight-base);
display: inline-block;
margin-left: 0.3rem;
}

View File

@ -0,0 +1,26 @@
import Tooltip from '@shared/atoms/Tooltip'
import React from 'react'
import styles from './Title.module.css'
export default function Title({
title,
tooltip,
titlePostfix,
titlePostfixTitle
}: {
title: string
tooltip?: string
titlePostfix?: string
titlePostfixTitle?: string
}) {
return (
<h3 className={styles.title}>
{title} {tooltip && <Tooltip content={tooltip} />}{' '}
{titlePostfix && (
<span className={styles.titlePostfix} title={titlePostfixTitle}>
{titlePostfix}
</span>
)}
</h3>
)
}

View File

@ -0,0 +1,39 @@
.container {
margin-left: calc(-1 * var(--spacer) / 1.5);
margin-right: calc(-1 * var(--spacer) / 1.5);
padding: calc(var(--spacer) / 1.5) calc(var(--spacer) / 1.5)
calc(var(--spacer) / 2) calc(var(--spacer) / 1.5);
}
@media (min-width: 40rem) {
.container {
padding-left: var(--spacer);
padding-right: var(--spacer);
margin-left: calc(-1 * var(--spacer));
margin-right: calc(-1 * var(--spacer));
}
}
.section {
composes: container;
border-top: 1px solid var(--border-color);
position: relative;
}
.section.highlight {
background: var(--background-highlight);
}
.section:first-child {
padding-top: 0;
border-top: 0;
}
.grid {
display: grid;
gap: calc(var(--spacer) / 2);
grid-template-columns:
[full-start] minmax(13rem, 1fr) [break] minmax(7rem, 1fr)
[full-end];
}

View File

@ -0,0 +1,39 @@
import React, { ReactElement, ReactNode } from 'react'
import styles from './index.module.css'
import Title from './Title'
export default function PoolSection({
title,
tooltip,
titlePostfix,
titlePostfixTitle,
children,
highlight,
className
}: {
title?: string
children: ReactNode
tooltip?: string
titlePostfix?: string
titlePostfixTitle?: string
highlight?: boolean
className?: string
}): ReactElement {
return (
<div
className={`${styles.section} ${highlight ? styles.highlight : ''} ${
className || ''
}`}
>
{title && (
<Title
title={title}
tooltip={tooltip}
titlePostfix={titlePostfix}
titlePostfixTitle={titlePostfixTitle}
/>
)}
<div className={styles.grid}>{children}</div>
</div>
)
}

View File

@ -0,0 +1,32 @@
.update {
font-size: var(--font-size-mini);
color: var(--color-secondary);
text-align: center;
padding-top: calc(var(--spacer) / 4);
padding-bottom: calc(var(--spacer) / 3);
}
/* .update:before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
display: inline-block;
border: 1px solid var(--brand-alert-green);
margin-right: 0.2rem;
margin-top: -0.1rem;
vertical-align: middle;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0% {
background: transparent;
}
50% {
background: var(--brand-alert-green);
}
100% {
background: transparent;
}
} */

View File

@ -0,0 +1,17 @@
import { usePool } from '@context/Pool'
import Button from '@shared/atoms/Button'
import React from 'react'
import styles from './Update.module.css'
export default function Update() {
const { fetchAllData } = usePool()
return (
<div className={styles.update}>
<Button style="text" size="small" onClick={() => fetchAllData()}>
Refresh Data
</Button>
{/* Fetching every {refreshInterval / 1000} sec. */}
</div>
)
}

View File

@ -0,0 +1,20 @@
.dataToken {
font-size: var(--font-size-large);
text-align: center;
}
.dataToken > div {
display: block;
}
.dataTokenLinks {
display: flex;
justify-content: center;
font-size: var(--font-size-small);
margin-top: calc(var(--spacer) / 4);
}
.dataTokenLinks a {
margin-left: calc(var(--spacer) / 3);
margin-right: calc(var(--spacer) / 3);
}

View File

@ -0,0 +1,130 @@
import { useAsset } from '@context/Asset'
import { usePool } from '@context/Pool'
import Tooltip from '@shared/atoms/Tooltip'
import ExplorerLink from '@shared/ExplorerLink'
import PriceUnit from '@shared/Price/PriceUnit'
import React from 'react'
import Graph from '../Graph'
import PoolSection from '../Section'
import Token from '../../../../@shared/Token'
import content from '../../../../../../content/price.json'
import styles from './index.module.css'
import Update from './Update'
export default function PoolSections() {
const { asset } = useAsset()
const { poolData, poolInfo, poolInfoUser, poolInfoOwner } = usePool()
return (
<>
<PoolSection className={styles.dataToken}>
<PriceUnit price="1" symbol={poolInfo?.datatokenSymbol} size="large" />{' '}
={' '}
<PriceUnit
price={`${poolData?.spotPrice}`}
symbol={poolInfo?.baseTokenSymbol}
size="large"
/>
<Tooltip content={content.pool.tooltips.price} />
<div className={styles.dataTokenLinks}>
<ExplorerLink
networkId={asset?.chainId}
path={`address/${asset?.accessDetails?.addressOrId}`}
>
Pool
</ExplorerLink>
<ExplorerLink
networkId={asset?.chainId}
path={
asset?.chainId === 2021000 || asset?.chainId === 1287
? `tokens/${asset?.services[0].datatokenAddress}`
: `token/${asset?.services[0].datatokenAddress}`
}
>
Datatoken
</ExplorerLink>
</div>
</PoolSection>
<PoolSection
title="Your Value Locked"
titlePostfix={
poolInfoUser?.poolShare && `${poolInfoUser?.poolShare}% of pool`
}
tooltip={content.pool.tooltips.liquidity.replace(
'SWAPFEE',
poolInfo?.liquidityProviderSwapFee
)}
highlight
>
<Token
symbol={poolInfo?.baseTokenSymbol}
balance={poolInfoUser?.liquidity.toString()}
conversion={poolInfoUser?.liquidity}
/>
</PoolSection>
<PoolSection
title="Owner Value Locked"
titlePostfix={`${poolInfoOwner?.poolShare}% of pool`}
>
<Token
symbol={poolInfo?.baseTokenSymbol}
balance={poolInfoOwner?.liquidity.toString()}
conversion={poolInfoOwner?.liquidity}
/>
</PoolSection>
<PoolSection title="Total Value Locked">
<Token
symbol={poolInfo?.baseTokenSymbol}
balance={poolInfo?.totalLiquidityInOcean.toString()}
conversion={poolInfo?.totalLiquidityInOcean}
/>
</PoolSection>
<PoolSection
title="Pool Statistics"
titlePostfix={
poolInfo?.weightDt &&
`${poolInfo?.weightBaseToken}/${poolInfo?.weightDt}`
}
titlePostfixTitle={`Weight of ${poolInfo?.weightBaseToken}% ${poolInfo?.baseTokenSymbol} & ${poolInfo?.weightDt}% ${poolInfo?.datatokenSymbol}`}
>
<Graph />
<Token
symbol={poolInfo?.baseTokenSymbol}
balance={`${poolData?.baseTokenLiquidity}`}
size="mini"
/>
<Token
symbol={poolInfo?.datatokenSymbol}
balance={`${poolData?.datatokenLiquidity}`}
size="mini"
/>
<Token
symbol="% pool fee"
balance={poolInfo?.liquidityProviderSwapFee}
noIcon
size="mini"
/>
<Token
symbol="% market fee"
balance={poolInfo?.publishMarketSwapFee}
noIcon
size="mini"
/>
<Token
symbol="% OPF fee"
balance={poolInfo?.opcFee}
noIcon
size="mini"
/>
</PoolSection>
<Update />
</>
)
}

View File

@ -1,27 +0,0 @@
import React, { ReactElement } from 'react'
import styles from './Token.module.css'
import PriceUnit from '@shared/Price/PriceUnit'
import Logo from '@shared/atoms/Logo'
export default function Token({
symbol,
balance,
noIcon,
size
}: {
symbol: string
balance: string
noIcon?: boolean
size?: 'small' | 'mini'
}): ReactElement {
return (
<div className={`${styles.token} ${size ? styles[size] : ''}`}>
<figure
className={`${styles.icon} ${symbol} ${noIcon ? styles.noIcon : ''}`}
>
<Logo noWordmark />
</figure>
<PriceUnit price={balance} symbol={symbol} size={size} />
</div>
)
}

View File

@ -1,43 +0,0 @@
.tokenlist {
margin-left: -2rem;
margin-right: -2rem;
padding: calc(var(--spacer) / 1.5) calc(var(--spacer) / 1.5)
calc(var(--spacer) / 2) calc(var(--spacer) / 1.5);
border-top: 1px solid var(--border-color);
position: relative;
}
@media (min-width: 40rem) {
.tokenlist {
padding-left: var(--spacer);
padding-right: var(--spacer);
}
}
.tokenlist.highlight {
background: var(--background-highlight);
}
.tokens {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
}
.title {
composes: title from './index.module.css';
}
.totalLiquidity {
composes: token from './Token.module.css';
margin-bottom: 0;
font-weight: var(--font-weight-base) !important;
font-size: var(--font-size-small);
padding-left: var(--font-size-base);
padding-top: calc(var(--spacer) / 10);
}
.totalLiquidity strong {
font-size: var(--font-size-base);
color: var(--font-color-heading);
line-height: 1;
}

View File

@ -1,51 +0,0 @@
import Conversion from '@shared/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,
children,
baseTokenValue,
baseTokenSymbol,
datatokenValue,
datatokenSymbol,
conversion,
highlight,
size = 'small'
}: {
title?: string | ReactNode
children?: ReactNode
baseTokenValue: string
baseTokenSymbol: string
datatokenValue?: string
datatokenSymbol?: string
conversion?: Decimal
highlight?: boolean
size?: 'small' | 'mini'
}): ReactElement {
return (
<div className={`${styles.tokenlist} ${highlight ? styles.highlight : ''}`}>
{title && <h3 className={styles.title}>{title}</h3>}
<div className={styles.tokens}>
<Token symbol={baseTokenSymbol} balance={baseTokenValue} size={size} />
{conversion?.greaterThan(0) && (
<Conversion
price={conversion.toString()}
className={styles.totalLiquidity}
/>
)}
{datatokenValue && (
<Token
symbol={datatokenSymbol}
balance={datatokenValue}
size={size}
/>
)}
{children}
</div>
</div>
)
}

View File

@ -1,75 +0,0 @@
.container {
margin-left: -2rem;
margin-right: -2rem;
padding-left: calc(var(--spacer) / 1.5);
padding-right: calc(var(--spacer) / 1.5);
}
.dataToken {
composes: container;
padding-bottom: calc(var(--spacer) / 1.5);
font-size: var(--font-size-large);
text-align: center;
position: relative;
}
.dataTokenLinks {
display: flex;
justify-content: center;
font-size: var(--font-size-small);
margin-top: calc(var(--spacer) / 4);
}
.dataTokenLinks a {
margin-left: calc(var(--spacer) / 3);
margin-right: calc(var(--spacer) / 3);
}
.title {
font-size: var(--font-size-base);
margin-bottom: calc(var(--spacer) / 3);
color: var(--color-secondary);
}
.titleInfo {
font-size: var(--font-size-mini);
font-family: var(--font-family-base);
font-weight: var(--font-weight-base);
display: inline-block;
margin-left: 0.3rem;
}
.update {
composes: container;
font-size: var(--font-size-mini);
color: var(--color-secondary);
text-align: center;
border-top: 1px solid var(--border-color);
padding-top: calc(var(--spacer) / 4);
padding-bottom: calc(var(--spacer) / 4);
}
/* .update:before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
display: inline-block;
border: 1px solid var(--brand-alert-green);
margin-right: 0.2rem;
margin-top: -0.1rem;
vertical-align: middle;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0% {
background: transparent;
}
50% {
background: var(--brand-alert-green);
}
100% {
background: transparent;
}
} */

View File

@ -1,37 +1,20 @@
import React, { ReactElement, useState } from 'react' import React, { ReactElement, useState } from 'react'
import styles from './index.module.css' import stylesActions from './Actions/index.module.css'
import stylesActions from './Actions.module.css'
import PriceUnit from '@shared/Price/PriceUnit'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import Add from './Add' import Add from './Add'
import Remove from './Remove' import Remove from './Remove'
import Tooltip from '@shared/atoms/Tooltip'
import ExplorerLink from '@shared/ExplorerLink'
import TokenList from './TokenList'
import AssetActionHistoryTable from '../AssetActionHistoryTable' import AssetActionHistoryTable from '../AssetActionHistoryTable'
import Graph from './Graph'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
import PoolTransactions from '@shared/PoolTransactions' import PoolTransactions from '@shared/PoolTransactions'
import Decimal from 'decimal.js'
import content from '../../../../../content/price.json'
import { usePool } from '@context/Pool' import { usePool } from '@context/Pool'
import Token from './Token' import PoolSections from './Sections'
export default function Pool(): ReactElement { export default function Pool(): ReactElement {
const { accountId } = useWeb3()
const { isInPurgatory, asset, isAssetNetwork } = useAsset() const { isInPurgatory, asset, isAssetNetwork } = useAsset()
const { const { hasUserAddedLiquidity, isRemoveDisabled } = usePool()
poolData, const { accountId } = useWeb3()
poolInfo,
poolInfoUser,
poolInfoOwner,
poolSnapshots,
hasUserAddedLiquidity,
isRemoveDisabled,
fetchAllData
// refreshInterval
} = usePool()
const [showAdd, setShowAdd] = useState(false) const [showAdd, setShowAdd] = useState(false)
const [showRemove, setShowRemove] = useState(false) const [showRemove, setShowRemove] = useState(false)
@ -39,150 +22,13 @@ export default function Pool(): ReactElement {
return ( return (
<> <>
{showAdd ? ( {showAdd ? (
<Add <Add setShowAdd={setShowAdd} />
setShowAdd={setShowAdd}
poolAddress={asset?.accessDetails?.addressOrId}
totalPoolTokens={poolInfo?.totalPoolTokens}
totalBalance={{
baseToken: new Decimal(poolData?.baseTokenLiquidity).toString(),
datatoken: new Decimal(poolData?.datatokenLiquidity).toString()
}}
swapFee={poolInfo?.liquidityProviderSwapFee}
datatokenSymbol={poolInfo?.datatokenSymbol}
tokenInAddress={poolInfo?.baseTokenAddress}
tokenInSymbol={poolInfo?.baseTokenSymbol}
/>
) : showRemove ? ( ) : showRemove ? (
<Remove <Remove setShowRemove={setShowRemove} />
setShowRemove={setShowRemove}
poolAddress={asset?.accessDetails?.addressOrId}
poolTokens={poolInfoUser?.poolShares}
totalPoolTokens={poolInfo?.totalPoolTokens}
tokenOutAddress={poolInfo?.baseTokenAddress}
tokenOutSymbol={poolInfo?.baseTokenSymbol}
/>
) : ( ) : (
<> <>
<div className={styles.dataToken}> <PoolSections />
<PriceUnit
price="1"
symbol={poolInfo?.datatokenSymbol}
size="large"
/>{' '}
={' '}
<PriceUnit
price={`${poolData?.spotPrice}`}
symbol={poolInfo?.baseTokenSymbol}
size="large"
/>
<Tooltip content={content.pool.tooltips.price} />
<div className={styles.dataTokenLinks}>
<ExplorerLink
networkId={asset?.chainId}
path={`address/${asset?.accessDetails?.addressOrId}`}
>
Pool
</ExplorerLink>
<ExplorerLink
networkId={asset?.chainId}
path={
asset?.chainId === 2021000 || asset?.chainId === 1287
? `tokens/${asset?.services[0].datatokenAddress}`
: `token/${asset?.services[0].datatokenAddress}`
}
>
Datatoken
</ExplorerLink>
</div>
</div>
<TokenList
title={
<>
Your Value Locked
<Tooltip
content={content.pool.tooltips.liquidity.replace(
'SWAPFEE',
poolInfo?.liquidityProviderSwapFee
)}
/>
{poolInfoUser?.poolShare && (
<span className={styles.titleInfo}>
{poolInfoUser?.poolShare}% of pool
</span>
)}
</>
}
baseTokenValue={poolInfoUser?.liquidity.toString()}
baseTokenSymbol={poolInfo?.baseTokenSymbol}
conversion={poolInfoUser?.liquidity}
highlight
/>
<TokenList
title={
<>
Owner Value Locked
<span className={styles.titleInfo}>
{poolInfoOwner?.poolShare}% of pool
</span>
</>
}
baseTokenValue={poolInfoOwner?.liquidity.toString()}
baseTokenSymbol={poolInfo?.baseTokenSymbol}
conversion={poolInfoOwner?.liquidity}
/>
<TokenList
title={
<>
Pool Statistics
{poolInfo?.weightDt && (
<span
className={styles.titleInfo}
title={`Weight of ${poolInfo?.weightBaseToken}% ${poolInfo?.baseTokenSymbol} & ${poolInfo?.weightDt}% ${poolInfo?.datatokenSymbol}`}
>
{poolInfo?.weightBaseToken}/{poolInfo?.weightDt}
</span>
)}
<Graph poolSnapshots={poolSnapshots} />
</>
}
baseTokenValue={`${poolInfo?.totalLiquidityInOcean}`}
baseTokenSymbol={poolInfo?.baseTokenSymbol}
conversion={poolInfo?.totalLiquidityInOcean}
/>
<TokenList
size="mini"
baseTokenValue={`${poolData?.baseTokenLiquidity}`}
baseTokenSymbol={poolInfo?.baseTokenSymbol}
datatokenValue={`${poolData?.datatokenLiquidity}`}
datatokenSymbol={poolInfo?.datatokenSymbol}
>
<Token
symbol="% pool fee"
balance={poolInfo?.liquidityProviderSwapFee}
noIcon
size="mini"
/>
<Token
symbol="% market fee"
balance={poolInfo?.publishMarketSwapFee}
noIcon
size="mini"
/>
<Token
symbol="% OPF fee"
balance={poolInfo?.opcFee}
noIcon
size="mini"
/>
</TokenList>
<div className={styles.update}>
<Button style="text" size="small" onClick={() => fetchAllData()}>
Refresh Data
</Button>
{/* Fetching every {refreshInterval / 1000} sec. */}
</div>
<div className={stylesActions.actions}> <div className={stylesActions.actions}>
<Button <Button
style="primary" style="primary"

View File

@ -1,7 +1,7 @@
import { FormikContextType, useFormikContext } from 'formik' import { FormikContextType, useFormikContext } from 'formik'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import Token from '../Pool/Token' import Token from '../../../@shared/Token'
import styles from './Output.module.css' import styles from './Output.module.css'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'

View File

@ -61,15 +61,16 @@ export default function TradeInput({
placeholder="0" placeholder="0"
field={field} field={field}
form={form} form={form}
disabled={!accountId || disabled}
additionalComponent={<Error meta={meta} />}
value={`${field.value}`} value={`${field.value}`}
{...field}
onChange={(e: ChangeEvent<HTMLInputElement>) => { onChange={(e: ChangeEvent<HTMLInputElement>) => {
handleChange(e) handleChange(e)
handleValueChange(name, Number(e.target.value)) handleValueChange(name, Number(e.target.value))
// debounce needed to avoid validating the wrong (pass) value // debounce needed to avoid validating the wrong (pass) value
debounce(() => validateForm(), 100) debounce(() => validateForm(), 100)
}} }}
disabled={!accountId || disabled}
additionalComponent={<Error meta={meta} />}
/> />
)} )}
</Field> </Field>

View File

@ -1,6 +1,7 @@
.buildId { .buildId {
display: inline-block; display: block;
font-size: var(--font-size-mini); font-size: var(--font-size-mini);
margin-bottom: var(--spacer); margin-bottom: var(--spacer);
font-family: var(--font-family-monospace); font-family: var(--font-family-monospace);
text-align: center;
} }

View File

@ -1,38 +1,69 @@
.content { .footer {
padding: var(--spacer) calc(var(--spacer) / 2); padding: var(--spacer) calc(var(--spacer) / 2);
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-width: var(--layout-max-width); max-width: var(--layout-max-width);
color: var(--brand-grey-light);
font-size: var(--font-size-small);
} }
.content a, .footer a,
.content button { .footer button {
color: inherit; color: inherit;
} }
.content button { .footer button {
text-transform: none; text-transform: none;
transform: none !important; transform: none !important;
font-weight: var(--font-weight-normal); font-weight: var(--font-weight-normal);
} }
.content a:hover, .footer a:hover,
.content a:focus, .footer a:focus,
.content button:hover, .footer button:hover,
.content button:focus { .footer button:focus {
color: var(--color-primary); color: var(--color-primary);
} }
.content p { .copyright > div,
.copyright > div > p {
display: inline; display: inline;
} }
.copyright div { .copyright,
display: inline-block; .grid > div:first-child {
}
.footer {
color: var(--brand-grey-light);
font-size: var(--font-size-small);
text-align: center; text-align: center;
} }
.grid {
display: grid;
gap: var(--spacer);
}
@media (min-width: 40rem) {
.grid {
display: grid;
gap: var(--spacer);
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
}
.grid > div:first-child {
text-align: left;
}
.copyright {
text-align: right;
}
}
.grid a {
text-transform: none;
font-family: var(--font-family-base);
font-weight: var(--font-weight-base);
}
.svg {
display: inline;
fill: currentColor;
width: 0.6em;
height: 0.6em;
}

View File

@ -2,56 +2,33 @@ import React, { ReactElement } from 'react'
import styles from './Footer.module.css' import styles from './Footer.module.css'
import Markdown from '@shared/Markdown' import Markdown from '@shared/Markdown'
import { useSiteMetadata } from '@hooks/useSiteMetadata' import { useSiteMetadata } from '@hooks/useSiteMetadata'
import Link from 'next/link'
import MarketStats from './MarketStats' import MarketStats from './MarketStats'
import BuildId from './BuildId' import BuildId from './BuildId'
import { useUserPreferences } from '@context/UserPreferences' import Links from './Links'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import { useGdprMetadata } from '@hooks/useGdprMetadata' import External from '@images/external.svg'
export default function Footer(): ReactElement { export default function Footer(): ReactElement {
const { copyright, appConfig } = useSiteMetadata() const { copyright } = useSiteMetadata()
const { setShowPPC } = useUserPreferences()
const { privacyPolicySlug } = useUserPreferences()
const cookies = useGdprMetadata()
const year = new Date().getFullYear() const year = new Date().getFullYear()
return ( return (
<footer className={styles.footer}> <footer className={styles.footer}>
<div className={styles.content}> <BuildId />
<BuildId /> <MarketStats />
<MarketStats />
<div className={styles.grid}>
<Links />
<div className={styles.copyright}> <div className={styles.copyright}>
© {year} <Markdown text={copyright} /> © {year} <Markdown text={copyright} />
<br /> <Button
<Link href="/imprint"> style="text"
<a>Imprint</a> size="small"
</Link> href="https://oceanprotocol.com"
{' — '} target="_blank"
<Link href="/terms"> >
<a>Terms</a> Ocean Protocol <External className={styles.svg} />
</Link> </Button>
{' — '}
<Link href={privacyPolicySlug}>
<a>Privacy</a>
</Link>
{appConfig.privacyPreferenceCenter === 'true' && (
<>
{' — '}
<Button
style="text"
size="small"
className="link"
onClick={() => {
setShowPPC(true)
}}
>
{cookies.optionalCookies ? 'Cookie Settings' : 'Cookies'}
</Button>
</>
)}
</div> </div>
</div> </div>
</footer> </footer>

View File

@ -0,0 +1,12 @@
.links a {
text-transform: none;
font-family: var(--font-family-base);
font-weight: var(--font-weight-base);
}
.links svg {
display: inline;
fill: currentColor;
width: 0.6em;
height: 0.6em;
}

View File

@ -0,0 +1,55 @@
import { useUserPreferences } from '@context/UserPreferences'
import { useGdprMetadata } from '@hooks/useGdprMetadata'
import { useSiteMetadata } from '@hooks/useSiteMetadata'
import Button from '@shared/atoms/Button'
import Link from 'next/link'
import React, { Fragment } from 'react'
import content from '../../../content/footer.json'
import External from '@images/external.svg'
import styles from './Links.module.css'
export default function Links() {
const { appConfig } = useSiteMetadata()
const { setShowPPC, privacyPolicySlug } = useUserPreferences()
const cookies = useGdprMetadata()
return (
<div className={styles.links}>
{content.links.map(({ name, url }) => (
<Fragment key={name}>
<Button style="text" size="small" href={url} target="_blank">
{name} <External />
</Button>
{' — '}
</Fragment>
))}
<Link href="/imprint">
<a>Imprint</a>
</Link>
{' — '}
<Link href="/terms">
<a>Terms</a>
</Link>
{' — '}
<Link href={privacyPolicySlug}>
<a>Privacy</a>
</Link>
{appConfig.privacyPreferenceCenter === 'true' && (
<>
{' — '}
<Button
style="text"
size="small"
className="link"
onClick={() => {
setShowPPC(true)
}}
>
{cookies.optionalCookies ? 'Cookie Settings' : 'Cookies'}
</Button>
</>
)}
</div>
)
}

View File

@ -1,5 +1,6 @@
.stats { .stats {
margin-bottom: calc(var(--spacer) * 2); margin-bottom: calc(var(--spacer) * 2);
text-align: center;
} }
/* specificity sledgehammer override without !important */ /* specificity sledgehammer override without !important */

View File

@ -44,7 +44,7 @@ const Account = React.forwardRef((props, ref: any) => {
// the Tippy to show in this state. // the Tippy to show in this state.
ref={ref} ref={ref}
> >
Connect <span>Wallet</span> Connect&nbsp;<span>Wallet</span>
</button> </button>
) )
}) })

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import Conversion from '@shared/Price/Conversion' import Conversion from '@shared/Price/Conversion'
import styles from './Liquidity.module.css' import styles from './Liquidity.module.css'
import Token from '../../../Asset/AssetActions/Pool/Token' import Token from '../../../@shared/Token'
import { isValidNumber } from '@utils/numbers' import { isValidNumber } from '@utils/numbers'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import { AssetPoolShare } from './index' import { AssetPoolShare } from './index'

View File

@ -5,6 +5,7 @@ import { FormikContextType, useFormikContext } from 'formik'
import { FormPublishData } from '../_types' import { FormPublishData } from '../_types'
import { wizardSteps } from '../_constants' import { wizardSteps } from '../_constants'
import SuccessConfetti from '@shared/SuccessConfetti' import SuccessConfetti from '@shared/SuccessConfetti'
import { useWeb3 } from '../../../@context/Web3'
export default function Actions({ export default function Actions({
scrollToRef, scrollToRef,
@ -20,6 +21,14 @@ export default function Actions({
isSubmitting, isSubmitting,
setFieldValue setFieldValue
}: FormikContextType<FormPublishData> = useFormikContext() }: FormikContextType<FormPublishData> = useFormikContext()
const { connect, accountId } = useWeb3()
async function handleActivation(e: FormEvent<HTMLButtonElement>) {
// prevent accidentially submitting a form the button might be in
e.preventDefault()
await connect()
}
function handleNext(e: FormEvent) { function handleNext(e: FormEvent) {
e.preventDefault() e.preventDefault()
@ -65,13 +74,15 @@ export default function Actions({
> >
Continue Continue
</Button> </Button>
) : !accountId ? (
<Button type="submit" style="primary" onClick={handleActivation}>
Connect Wallet
</Button>
) : ( ) : (
<Button <Button
type="submit" type="submit"
style="primary" style="primary"
disabled={ disabled={isSubmitting || !isValid}
values.user.accountId === '' || !isValid || isSubmitting
}
> >
Submit Submit
</Button> </Button>

View File

@ -20,7 +20,6 @@ export default function Dynamic({ content }: { content: any }): ReactElement {
const { dataTokenOptions } = values.services[0] const { dataTokenOptions } = values.services[0]
const { const {
price,
weightOnDataToken, weightOnDataToken,
weightOnOcean, weightOnOcean,
swapFee, swapFee,

View File

@ -5,6 +5,7 @@ import Tabs from '@shared/atoms/Tabs'
import { isValidNumber } from '@utils/numbers' import { isValidNumber } from '@utils/numbers'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import { FormPublishData } from '../_types' import { FormPublishData } from '../_types'
import { initialValues } from '../_constants'
import Dynamic from './Dynamic' import Dynamic from './Dynamic'
import Fixed from './Fixed' import Fixed from './Fixed'
import Free from './Free' import Free from './Free'
@ -28,7 +29,19 @@ export default function PricingFields(): ReactElement {
type !== 'free' && setFieldValue('pricing.amountDataToken', 1000) type !== 'free' && setFieldValue('pricing.amountDataToken', 1000)
} }
// Always update everything when price value changes // Update ocean amount when price is changed
useEffect(() => {
if (type === 'fixed' || type === 'free') return
const amountOcean =
isValidNumber(weightOnOcean) && isValidNumber(price) && price > 0
? new Decimal(price).mul(new Decimal(weightOnOcean).mul(10)).mul(2)
: new Decimal(initialValues.pricing.amountOcean)
setFieldValue('pricing.amountOcean', amountOcean)
}, [price, weightOnOcean, type, setFieldValue])
// Update dataToken value when ocean amount is changed
useEffect(() => { useEffect(() => {
if (type === 'fixed' || type === 'free') return if (type === 'fixed' || type === 'free') return
@ -36,22 +49,16 @@ export default function PricingFields(): ReactElement {
isValidNumber(amountOcean) && isValidNumber(amountOcean) &&
isValidNumber(weightOnOcean) && isValidNumber(weightOnOcean) &&
isValidNumber(price) && isValidNumber(price) &&
isValidNumber(weightOnDataToken) isValidNumber(weightOnDataToken) &&
price > 0
? new Decimal(amountOcean) ? new Decimal(amountOcean)
.dividedBy(new Decimal(weightOnOcean)) .dividedBy(new Decimal(weightOnOcean))
.dividedBy(new Decimal(price)) .dividedBy(new Decimal(price))
.mul(new Decimal(weightOnDataToken)) .mul(new Decimal(weightOnDataToken))
: 0 : new Decimal(initialValues.pricing.amountDataToken)
setFieldValue('pricing.amountDataToken', amountDataToken) setFieldValue('pricing.amountDataToken', amountDataToken)
}, [ }, [amountOcean, weightOnOcean, weightOnDataToken, type, setFieldValue])
price,
amountOcean,
weightOnOcean,
weightOnDataToken,
type,
setFieldValue
])
const tabs = [ const tabs = [
appConfig.allowFixedPricing === 'true' appConfig.allowFixedPricing === 'true'

View File

@ -79,6 +79,10 @@
margin-left: var(--spacer); margin-left: var(--spacer);
font-size: var(--font-size-small); font-size: var(--font-size-small);
color: var(--brand-alert-red); color: var(--brand-alert-red);
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
@keyframes loader { @keyframes loader {

View File

@ -87,8 +87,8 @@ export const initialValues: FormPublishData = {
: allowFixedPricing === 'true' : allowFixedPricing === 'true'
? 'fixed' ? 'fixed'
: 'free', : 'free',
amountDataToken: allowDynamicPricing === 'true' ? 50 : 1000, amountDataToken: allowDynamicPricing === 'true' ? 100 : 1000,
amountOcean: 50, amountOcean: 100,
weightOnOcean: '5', // 50% on OCEAN weightOnOcean: '5', // 50% on OCEAN
weightOnDataToken: '5', // 50% on datatoken weightOnDataToken: '5', // 50% on datatoken
swapFee: 0.1, // in % swapFee: 0.1, // in %

View File

@ -1,5 +1,7 @@
import { MAX_DECIMALS } from '@utils/constants' import { MAX_DECIMALS } from '@utils/constants'
import { initialValues } from './_constants'
import * as Yup from 'yup' import * as Yup from 'yup'
import Decimal from 'decimal.js'
// TODO: conditional validation // TODO: conditional validation
// e.g. when algo is selected, Docker image is required // e.g. when algo is selected, Docker image is required
@ -78,11 +80,24 @@ const validationPricing = {
(param) => maxDecimalsValidation.test(param?.toString()) (param) => maxDecimalsValidation.test(param?.toString())
) )
.required('Required'), .required('Required'),
amountDataToken: Yup.number() amountDataToken: Yup.number().required('Required'),
.min(50, (param) => `Must be more or equal to ${param.min}`)
.required('Required'),
amountOcean: Yup.number() amountOcean: Yup.number()
.min(50, (param) => `Must be more or equal to ${param.min}`) .test('validator-min-amountOcean', '', function (value) {
const minValue =
this.parent.price > 0
? new Decimal(this.parent.price)
.mul(this.parent.weightOnOcean)
.mul(10)
.mul(2)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
: initialValues.pricing.amountOcean.toString()
return value < parseInt(minValue)
? this.createError({
message: `Must be more or equal to ${minValue}, as at least ${initialValues.pricing.amountDataToken} datatokens are required for this pool to work properly`
})
: true
})
.max( .max(
1000000, 1000000,
(param: { max: number }) => `Must be less than or equal to ${param.max}` (param: { max: number }) => `Must be less than or equal to ${param.max}`

View File

@ -105,6 +105,10 @@ export default function PublishPage({
})) }))
} catch (error) { } catch (error) {
LoggerInstance.error('[publish] error', error.message) LoggerInstance.error('[publish] error', error.message)
if (error.message.length > 65) {
error.message = 'No Token created.'
}
setFeedback((prevState) => ({ setFeedback((prevState) => ({
...prevState, ...prevState,
'1': { '1': {