Merge branch 'v4' of github.com:oceanprotocol/market into v4

This commit is contained in:
Jamie Hewitt 2022-04-06 15:53:26 +03:00
commit d61f9c7940
40 changed files with 634 additions and 606 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

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

View File

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

View File

@ -1,6 +1,5 @@
.token {
font-weight: var(--font-weight-bold);
margin-bottom: calc(var(--spacer) / 4);
white-space: nowrap;
}
@ -26,6 +25,21 @@
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 */
.icon:not([class*='OCEAN']) path {
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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +1,17 @@
.graphWrap {
composes: container from '../Section/index.module.css';
padding: 0;
grid-column: full;
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;
flex-direction: column;
margin-bottom: calc(var(--spacer) / 2);
position: relative;
}
@media (min-width: 40rem) {
.graphWrap {
margin-left: -2rem;
margin-right: -2rem;
}
}
.graphWrap canvas {
position: relative;
z-index: 0;

View File

@ -11,17 +11,14 @@ 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'
import { usePrices } from '@context/Prices'
import { MAX_DECIMALS } from '@utils/constants'
import { usePool } from '@context/Pool'
export default function Graph({
poolSnapshots
}: {
poolSnapshots: PoolDataPoolSnapshots[]
}): ReactElement {
export default function Graph(): ReactElement {
const { locale, currency } = useUserPreferences()
const { prices } = usePrices()
const { poolSnapshots } = usePool()
const darkMode = useDarkMode(false, darkModeConfig)
const [options, setOptions] = useState<ChartOptions<any>>()
@ -104,13 +101,13 @@ export default function Graph({
<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} />
)}
<Nav graphType={graphType} setGraphType={setGraphType} />
</>
)}
</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
} from 'react'
import styles from './index.module.css'
import Header from '../Header'
import Header from '../Actions/Header'
import { toast } from 'react-toastify'
import Actions from '../Actions'
import { LoggerInstance, Pool, calcMaxExactOut } from '@oceanprotocol/lib'
import Token from '../Token'
import { LoggerInstance, Pool } from '@oceanprotocol/lib'
import Token from '../../../../@shared/Token'
import FormHelp from '@shared/FormInput/Help'
import Button from '@shared/atoms/Button'
import debounce from 'lodash.debounce'
@ -21,27 +21,18 @@ import Decimal from 'decimal.js'
import { useAsset } from '@context/Asset'
import content from '../../../../../../content/price.json'
import { usePool } from '@context/Pool'
import { getMax } from './_utils'
const slippagePresets = ['5', '10', '15', '25', '50']
export default function Remove({
setShowRemove,
poolAddress,
poolTokens,
totalPoolTokens,
tokenOutAddress,
tokenOutSymbol
setShowRemove
}: {
setShowRemove: (show: boolean) => void
poolAddress: string
poolTokens: string
totalPoolTokens: string
tokenOutAddress: string
tokenOutSymbol: string
}): ReactElement {
const { accountId, web3 } = useWeb3()
const { isAssetNetwork } = useAsset()
const { fetchAllData } = usePool()
const { poolData, poolInfo, poolInfoUser, fetchAllData } = usePool()
const [amountPercent, setAmountPercent] = useState('0')
const [amountMaxPercent, setAmountMaxPercent] = useState('100')
@ -62,7 +53,7 @@ export default function Remove({
try {
const result = await poolInstance.exitswapPoolAmountIn(
accountId,
poolAddress,
poolData?.id,
amountPoolShares,
minOceanAmount
)
@ -81,35 +72,23 @@ export default function Remove({
}
}
//
// Calculate and set maximum shares user is able to remove
//
useEffect(() => {
if (!accountId || !poolTokens) return
if (!accountId || !poolInfoUser?.poolShares || !poolInfo?.totalPoolTokens)
return
async function getMax() {
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)
setAmountMaxPercent(
maxPercent.toDecimalPlaces(0, Decimal.ROUND_DOWN).toString()
)
}
getMax()
}, [accountId, poolAddress, poolTokens, totalPoolTokens])
getMax(poolInfoUser.poolShares, poolInfo.totalPoolTokens).then((max) =>
setAmountMaxPercent(max)
)
}, [accountId, poolInfoUser?.poolShares, poolInfo?.totalPoolTokens])
const getValues = useRef(
debounce(async (newAmountPoolShares) => {
const newAmountOcean = await poolInstance.calcSingleOutGivenPoolIn(
poolAddress,
tokenOutAddress,
poolData?.id,
poolInfo?.baseTokenAddress,
newAmountPoolShares
)
@ -119,9 +98,21 @@ export default function Remove({
// Check and set outputs when amountPoolShares changes
useEffect(() => {
if (!accountId || !poolTokens) return
if (
!accountId ||
!poolInfoUser?.poolShares ||
!poolInfo?.totalPoolTokens ||
!poolData?.id
)
return
getValues.current(amountPoolShares)
}, [amountPoolShares, accountId, poolTokens, poolAddress, totalPoolTokens])
}, [
amountPoolShares,
accountId,
poolInfoUser?.poolShares,
poolData?.id,
poolInfo?.totalPoolTokens
])
useEffect(() => {
if (!amountOcean || amountPercent === '0') {
@ -135,16 +126,16 @@ export default function Remove({
.toString()
setMinOceanAmount(minOceanAmount.slice(0, 18))
}, [slippage, amountOcean])
}, [slippage, amountOcean, amountPercent])
// Set amountPoolShares based on set slider value
function handleAmountPercentChange(e: ChangeEvent<HTMLInputElement>) {
setAmountPercent(e.target.value)
if (!poolTokens) return
if (!poolInfoUser?.poolShares) return
const amountPoolShares = new Decimal(e.target.value)
.dividedBy(100)
.mul(new Decimal(poolTokens))
.mul(new Decimal(poolInfoUser.poolShares))
.toString()
setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`)
@ -156,7 +147,7 @@ export default function Remove({
const amountPoolShares = new Decimal(amountMaxPercent)
.dividedBy(100)
.mul(new Decimal(poolTokens))
.mul(new Decimal(poolInfoUser?.poolShares))
.toString()
setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`)
@ -177,7 +168,7 @@ export default function Remove({
/>
<form className={styles.removeInput}>
<UserLiquidity amount={poolTokens} symbol="pool shares" />
<UserLiquidity amount={poolInfoUser?.poolShares} symbol="pool shares" />
<div className={styles.range}>
<h3>{amountPercent}%</h3>
<div className={styles.slider}>
@ -206,7 +197,7 @@ export default function Remove({
<div className={styles.output}>
<div>
<p>{content.pool.remove.output.titleOut} minimum</p>
<Token symbol={tokenOutSymbol} balance={minOceanAmount} />
<Token symbol={poolInfo?.baseTokenSymbol} balance={minOceanAmount} />
</div>
{/* <div>
<p>{content.pool.remove.output.titleIn}</p>
@ -237,11 +228,11 @@ export default function Remove({
!isAssetNetwork ||
amountPercent === '0' ||
amountOcean === '0' ||
poolTokens === '0'
poolInfo?.totalPoolTokens === '0'
}
txId={txId}
tokenAddress={tokenOutAddress}
tokenSymbol={tokenOutSymbol}
tokenAddress={poolInfo?.baseTokenAddress}
tokenSymbol={poolInfo?.baseTokenSymbol}
/>
</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 styles from './index.module.css'
import stylesActions from './Actions.module.css'
import PriceUnit from '@shared/Price/PriceUnit'
import stylesActions from './Actions/index.module.css'
import Button from '@shared/atoms/Button'
import Add from './Add'
import Remove from './Remove'
import Tooltip from '@shared/atoms/Tooltip'
import ExplorerLink from '@shared/ExplorerLink'
import TokenList from './TokenList'
import AssetActionHistoryTable from '../AssetActionHistoryTable'
import Graph from './Graph'
import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3'
import PoolTransactions from '@shared/PoolTransactions'
import Decimal from 'decimal.js'
import content from '../../../../../content/price.json'
import { usePool } from '@context/Pool'
import Token from './Token'
import PoolSections from './Sections'
export default function Pool(): ReactElement {
const { accountId } = useWeb3()
const { isInPurgatory, asset, isAssetNetwork } = useAsset()
const {
poolData,
poolInfo,
poolInfoUser,
poolInfoOwner,
poolSnapshots,
hasUserAddedLiquidity,
isRemoveDisabled,
fetchAllData
// refreshInterval
} = usePool()
const { hasUserAddedLiquidity, isRemoveDisabled } = usePool()
const { accountId } = useWeb3()
const [showAdd, setShowAdd] = useState(false)
const [showRemove, setShowRemove] = useState(false)
@ -39,150 +22,13 @@ export default function Pool(): ReactElement {
return (
<>
{showAdd ? (
<Add
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}
/>
<Add setShowAdd={setShowAdd} />
) : showRemove ? (
<Remove
setShowRemove={setShowRemove}
poolAddress={asset?.accessDetails?.addressOrId}
poolTokens={poolInfoUser?.poolShares}
totalPoolTokens={poolInfo?.totalPoolTokens}
tokenOutAddress={poolInfo?.baseTokenAddress}
tokenOutSymbol={poolInfo?.baseTokenSymbol}
/>
<Remove setShowRemove={setShowRemove} />
) : (
<>
<div 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>
</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}
/>
<PoolSections />
<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}>
<Button
style="primary"

View File

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

View File

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

View File

@ -1,38 +1,57 @@
.content {
.footer {
padding: var(--spacer) calc(var(--spacer) / 2);
margin-left: auto;
margin-right: auto;
max-width: var(--layout-max-width);
color: var(--brand-grey-light);
font-size: var(--font-size-small);
}
.content a,
.content button {
.footer a,
.footer button {
color: inherit;
}
.content button {
.footer button {
text-transform: none;
transform: none !important;
font-weight: var(--font-weight-normal);
}
.content a:hover,
.content a:focus,
.content button:hover,
.content button:focus {
.footer a:hover,
.footer a:focus,
.footer button:hover,
.footer button:focus {
color: var(--color-primary);
}
.content p {
.copyright > div,
.copyright > div > p {
display: inline;
}
.copyright div {
display: inline-block;
}
.footer {
color: var(--brand-grey-light);
font-size: var(--font-size-small);
.copyright,
.grid > div:first-child {
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;
}
}

View File

@ -2,56 +2,23 @@ import React, { ReactElement } from 'react'
import styles from './Footer.module.css'
import Markdown from '@shared/Markdown'
import { useSiteMetadata } from '@hooks/useSiteMetadata'
import Link from 'next/link'
import MarketStats from './MarketStats'
import BuildId from './BuildId'
import { useUserPreferences } from '@context/UserPreferences'
import Button from '@shared/atoms/Button'
import { useGdprMetadata } from '@hooks/useGdprMetadata'
import Links from './Links'
export default function Footer(): ReactElement {
const { copyright, appConfig } = useSiteMetadata()
const { setShowPPC } = useUserPreferences()
const { privacyPolicySlug } = useUserPreferences()
const cookies = useGdprMetadata()
const { copyright } = useSiteMetadata()
const year = new Date().getFullYear()
return (
<footer className={styles.footer}>
<div className={styles.content}>
<BuildId />
<MarketStats />
<BuildId />
<MarketStats />
<div className={styles.grid}>
<Links />
<div className={styles.copyright}>
© {year} <Markdown text={copyright} />
<br />
<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>
</div>
</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 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 }) => (
<>
<Button style="text" size="small" href={url}>
{name} <External />
</Button>
{' — '}
</>
))}
<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 {
margin-bottom: calc(var(--spacer) * 2);
text-align: center;
}
/* 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.
ref={ref}
>
Connect <span>Wallet</span>
Connect&nbsp;<span>Wallet</span>
</button>
)
})

View File

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