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

put back Remove Liquidity components (#1551)

* put back Remove Liquidity components

* remove Pool Share Lock action
This commit is contained in:
Matthias Kretschmann 2022-06-28 14:44:32 +01:00 committed by GitHub
parent e885a5c921
commit 65045ba7b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 641 additions and 18 deletions

View File

@ -0,0 +1,25 @@
.actions {
margin-left: -2rem;
margin-right: -2rem;
padding-left: var(--spacer);
padding-right: var(--spacer);
padding-top: calc(var(--spacer) / 1.5);
border-top: 1px solid var(--border-color);
text-align: center;
display: flex;
justify-content: center;
}
.success {
margin-top: calc(var(--spacer) / 2);
margin-bottom: calc(var(--spacer) / 2);
}
.actions button {
margin-left: calc(var(--spacer) / 4);
margin-right: calc(var(--spacer) / 4);
}
.actions button svg {
fill: currentColor;
}

View File

@ -0,0 +1,75 @@
import React, { ReactElement } from 'react'
import Loader from '../../../atoms/Loader'
import Button from '../../../atoms/Button'
import styles from './Actions.module.css'
import ExplorerLink from '../../../atoms/ExplorerLink'
import SuccessConfetti from '../../../atoms/SuccessConfetti'
import { useOcean } from '../../../../providers/Ocean'
import { useWeb3 } from '../../../../providers/Web3'
import TokenApproval from '../../../molecules/TokenApproval'
export default function Actions({
isLoading,
loaderMessage,
successMessage,
txId,
actionName,
amount,
coin,
action,
isDisabled
}: {
isLoading: boolean
loaderMessage: string
successMessage: string
txId: string
actionName: string
amount?: string
coin?: string
action: () => void
isDisabled?: boolean
}): ReactElement {
const { networkId } = useWeb3()
const { ocean } = useOcean()
const actionButton = (
<Button
style="primary"
size="small"
onClick={() => action()}
disabled={!ocean || isDisabled}
>
{actionName}
</Button>
)
return (
<>
<div className={styles.actions}>
{isLoading ? (
<Loader message={loaderMessage} />
) : actionName === 'Supply' || actionName === 'Swap' ? (
<TokenApproval
actionButton={actionButton}
amount={amount}
coin={coin}
disabled={!ocean || isDisabled}
/>
) : (
actionButton
)}
</div>
{txId && (
<SuccessConfetti
className={styles.success}
success={successMessage}
action={
<ExplorerLink networkId={networkId} path={`/tx/${txId}`}>
View transaction
</ExplorerLink>
}
/>
)}
</>
)
}

View File

@ -0,0 +1,24 @@
.output {
display: grid;
gap: var(--spacer);
grid-template-columns: 1fr 1fr;
padding-bottom: calc(var(--spacer) / 2);
}
.output p {
font-weight: var(--font-weight-bold);
margin-bottom: calc(var(--spacer) / 8);
font-size: var(--font-size-small);
}
.output div:first-child [class*='token'] {
white-space: normal;
}
.output div:first-child [class*='token'] > figure {
display: none;
}
.help {
text-align: center;
}

View File

@ -0,0 +1,24 @@
.addInput {
margin: 0 auto calc(var(--spacer) / 1.5) auto;
background: var(--background-highlight);
padding: var(--spacer) calc(var(--spacer) * 2.5) calc(var(--spacer) * 1.2)
calc(var(--spacer) * 2.5);
border-bottom: 1px solid var(--border-color);
margin-top: -2rem;
margin-left: -2rem;
margin-right: -2rem;
position: relative;
}
.addInput input {
text-align: center;
}
.addInput div[class*='field'] {
margin-bottom: 0;
}
.warning {
margin-left: -3rem;
margin-right: -3rem;
}

View File

@ -0,0 +1,70 @@
.removeInput {
composes: addInput from './Add/index.module.css';
padding-left: calc(var(--spacer) * 2);
padding-right: calc(var(--spacer) * 2);
padding-bottom: calc(var(--spacer) / 2);
}
.range {
margin-top: calc(var(--spacer) / 2);
text-align: center;
}
.range h3 {
margin-bottom: calc(var(--spacer) / 4);
}
.range input {
width: 100%;
}
.range p {
margin-bottom: 0;
margin-left: -2rem;
margin-right: -2rem;
}
.slider {
position: relative;
z-index: 1;
}
.maximum {
position: absolute;
right: -2rem;
bottom: 2rem;
font-size: var(--font-size-mini);
min-width: 5rem;
text-align: center;
}
.toggle {
margin-top: calc(var(--spacer) / 2);
margin-bottom: 0;
font-size: var(--font-size-mini);
margin-bottom: -2rem;
}
.output {
composes: output from './Add/Output.module.css';
}
.output [class*='token'] {
white-space: nowrap;
}
.output [class*='token'] > figure {
display: inline-block;
}
.output figure[class*='pool shares'] {
display: none;
}
.output p {
font-size: var(--font-size-small);
}
.slippage {
composes: slippage from '../Trade/Slippage.module.css';
}

View File

@ -0,0 +1,313 @@
import React, {
ReactElement,
useState,
ChangeEvent,
useEffect,
FormEvent,
useRef
} from 'react'
import styles from './Remove.module.css'
import Header from './Header'
import { toast } from 'react-toastify'
import Actions from './Actions'
import { Logger } from '@oceanprotocol/lib'
import Token from './Token'
import FormHelp from '../../../atoms/Input/Help'
import Button from '../../../atoms/Button'
import { getMaxPercentRemove } from './utils'
import { graphql, useStaticQuery } from 'gatsby'
import debounce from 'lodash.debounce'
import UserLiquidity from '../../../atoms/UserLiquidity'
import InputElement from '../../../atoms/Input/InputElement'
import { useOcean } from '../../../../providers/Ocean'
import { useWeb3 } from '../../../../providers/Web3'
import Decimal from 'decimal.js'
import { useAsset } from '../../../../providers/Asset'
const contentQuery = graphql`
query PoolRemoveQuery {
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
edges {
node {
childContentJson {
pool {
remove {
title
simple
advanced
output {
titleIn
titleOut
}
action
}
}
}
}
}
}
}
`
export default function Remove({
setShowRemove,
refreshInfo,
poolAddress,
poolTokens,
totalPoolTokens,
dtSymbol
}: {
setShowRemove: (show: boolean) => void
refreshInfo: () => void
poolAddress: string
poolTokens: string
totalPoolTokens: string
dtSymbol: string
}): ReactElement {
const data = useStaticQuery(contentQuery)
const content = data.content.edges[0].node.childContentJson.pool.remove
const slippagePresets = ['5', '10', '15', '25', '50']
const { accountId } = useWeb3()
const { ocean } = useOcean()
const { isAssetNetwork } = useAsset()
const [amountPercent, setAmountPercent] = useState('0')
const [amountMaxPercent, setAmountMaxPercent] = useState('100')
const [amountPoolShares, setAmountPoolShares] = useState('0')
const [amountOcean, setAmountOcean] = useState('0')
const [amountDatatoken, setAmountDatatoken] = useState('0')
const [isAdvanced, setIsAdvanced] = useState(false)
const [isLoading, setIsLoading] = useState<boolean>()
const [txId, setTxId] = useState<string>()
const [slippage, setSlippage] = useState<string>('5')
const [minOceanAmount, setMinOceanAmount] = useState<string>('0')
const [minDatatokenAmount, setMinDatatokenAmount] = useState<string>('0')
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
async function handleRemoveLiquidity() {
setIsLoading(true)
try {
const result =
isAdvanced === true
? await ocean.pool.removePoolLiquidity(
accountId,
poolAddress,
amountPoolShares,
minDatatokenAmount,
minOceanAmount
)
: await ocean.pool.removeOceanLiquidityWithMinimum(
accountId,
poolAddress,
amountPoolShares,
minOceanAmount
)
setTxId(result?.transactionHash)
refreshInfo()
} catch (error) {
Logger.error(error.message)
toast.error(error.message)
} finally {
setIsLoading(false)
}
}
// Get and set max percentage
useEffect(() => {
if (!ocean || !poolTokens) return
async function getMax() {
const amountMaxPercent =
isAdvanced === true
? '100'
: await getMaxPercentRemove(ocean, poolAddress, poolTokens)
setAmountMaxPercent(amountMaxPercent)
}
getMax()
}, [ocean, isAdvanced, poolAddress, poolTokens])
const getValues = useRef(
debounce(async (newAmountPoolShares, isAdvanced) => {
if (isAdvanced === true) {
const tokens = await ocean.pool.getTokensRemovedforPoolShares(
poolAddress,
`${newAmountPoolShares}`
)
setAmountOcean(tokens?.oceanAmount)
setAmountDatatoken(tokens?.dtAmount)
return
}
const amountOcean = await ocean.pool.getOceanRemovedforPoolShares(
poolAddress,
newAmountPoolShares
)
setAmountOcean(amountOcean)
}, 150)
)
// Check and set outputs when amountPoolShares changes
useEffect(() => {
if (!ocean || !poolTokens) return
getValues.current(amountPoolShares, isAdvanced)
}, [
amountPoolShares,
isAdvanced,
ocean,
poolTokens,
poolAddress,
totalPoolTokens
])
useEffect(() => {
const minOceanAmount = new Decimal(amountOcean)
.mul(new Decimal(100).minus(new Decimal(slippage)))
.dividedBy(100)
.toString()
const minDatatokenAmount = new Decimal(amountDatatoken)
.mul(new Decimal(100).minus(new Decimal(slippage)))
.dividedBy(100)
.toString()
setMinOceanAmount(minOceanAmount.slice(0, 18))
setMinDatatokenAmount(minDatatokenAmount.slice(0, 18))
}, [slippage, amountOcean, amountDatatoken, isAdvanced])
// Set amountPoolShares based on set slider value
function handleAmountPercentChange(e: ChangeEvent<HTMLInputElement>) {
setAmountPercent(e.target.value)
if (!poolTokens) return
const amountPoolShares = new Decimal(e.target.value)
.dividedBy(100)
.mul(new Decimal(poolTokens))
.toString()
setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`)
}
function handleMaxButton(e: ChangeEvent<HTMLInputElement>) {
e.preventDefault()
setAmountPercent(amountMaxPercent)
const amountPoolShares = new Decimal(amountMaxPercent)
.dividedBy(100)
.mul(new Decimal(poolTokens))
.toString()
setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`)
}
function handleAdvancedButton(e: FormEvent<HTMLButtonElement>) {
e.preventDefault()
setIsAdvanced(!isAdvanced)
setAmountPoolShares('0')
setAmountPercent('0')
setAmountOcean('0')
setSlippage('5')
setMinOceanAmount('0')
setMinDatatokenAmount('0')
if (isAdvanced === true) {
setAmountDatatoken('0')
}
}
function handleSlippageChange(e: ChangeEvent<HTMLSelectElement>) {
setSlippage(e.target.value)
}
return (
<div className={styles.remove}>
<Header title={content.title} backAction={() => setShowRemove(false)} />
<form className={styles.removeInput}>
<UserLiquidity amount={poolTokens} symbol="pool shares" />
<div className={styles.range}>
<h3>{amountPercent}%</h3>
<div className={styles.slider}>
<input
type="range"
min="0"
max={amountMaxPercent}
disabled={!isAssetNetwork}
value={amountPercent}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleAmountPercentChange(e)
}
/>
<Button
style="text"
size="small"
className={styles.maximum}
disabled={!isAssetNetwork}
onClick={(e: ChangeEvent<HTMLInputElement>) => handleMaxButton(e)}
>
{`${amountMaxPercent}% max`}
</Button>
</div>
<FormHelp>
{isAdvanced === true ? content.advanced : content.simple}
</FormHelp>
<Button
style="text"
size="small"
onClick={(e: FormEvent<HTMLButtonElement>) =>
handleAdvancedButton(e)
}
disabled={!isAssetNetwork}
className={styles.toggle}
>
{isAdvanced === true ? 'Simple' : 'Advanced'}
</Button>
</div>
</form>
<div className={styles.output}>
<div>
<p>{content.output.titleIn}</p>
<Token symbol="pool shares" balance={amountPoolShares} noIcon />
</div>
<div>
<p>{content.output.titleOut} minimum</p>
{isAdvanced === true ? (
<>
<Token symbol="OCEAN" balance={minOceanAmount} />
<Token symbol={dtSymbol} balance={minDatatokenAmount} />
</>
) : (
<Token symbol="OCEAN" balance={minOceanAmount} />
)}
</div>
</div>
<div className={styles.slippage}>
<strong>Expected price impact</strong>
<InputElement
name="slippage"
type="select"
size="mini"
postfix="%"
sortOptions={false}
options={slippagePresets}
disabled={!isAssetNetwork}
value={slippage}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
handleSlippageChange(e)
}
/>
</div>
<Actions
isLoading={isLoading}
loaderMessage="Removing Liquidity..."
actionName={content.action}
action={() => handleRemoveLiquidity()}
successMessage="Successfully removed liquidity."
isDisabled={!isAssetNetwork}
txId={txId}
/>
</div>
)
}

View File

@ -1,8 +1,8 @@
import React, { ReactElement, useEffect, useState } from 'react'
import { Logger } from '@oceanprotocol/lib'
import styles from './index.module.css'
import stylesActions from './Actions.module.css'
import PriceUnit from '../../../atoms/Price/PriceUnit'
import Alert from '../../../atoms/Alert'
import Tooltip from '../../../atoms/Tooltip'
import ExplorerLink from '../../../atoms/ExplorerLink'
import Token from './Token'
@ -19,10 +19,8 @@ import PoolTransactions from '../../../molecules/PoolTransactions'
import { fetchData, getQueryContext } from '../../../../utils/subgraph'
import { isValidNumber } from './../../../../utils/numberValidations'
import Decimal from 'decimal.js'
import {
MigrationStatus,
useMigrationStatus
} from '../../../../providers/Migration'
import Remove from './Remove'
import Button from '../../../atoms/Button'
const REFETCH_INTERVAL = 5000
@ -86,11 +84,10 @@ export default function Pool(): ReactElement {
const content = data.content.edges[0].node.childContentJson.pool
const { accountId } = useWeb3()
const { status } = useMigrationStatus()
const [dtSymbol, setDtSymbol] = useState<string>()
const [oceanSymbol, setOceanSymbol] = useState<string>()
const { ddo, owner, price } = useAsset()
const { ddo, owner, price, isInPurgatory, isAssetNetwork } = useAsset()
const [poolTokens, setPoolTokens] = useState<string>()
const [totalPoolTokens, setTotalPoolTokens] = useState<string>()
@ -116,6 +113,13 @@ export default function Pool(): ReactElement {
const [liquidityFetchInterval, setLiquidityFetchInterval] =
useState<NodeJS.Timeout>()
const [showRemove, setShowRemove] = useState(false)
const [isRemoveDisabled, setIsRemoveDisabled] = useState(false)
const [hasAddedLiquidity, setHasAddedLiquidity] = useState(false)
// the purpose of the value is just to trigger the effect
const [refreshPool, setRefreshPool] = useState(false)
async function getPoolLiquidity() {
const queryContext = getQueryContext(ddo.chainId)
const queryVariables = {
@ -267,6 +271,10 @@ export default function Pool(): ReactElement {
init()
}, [dataLiquidity, ddo.dataToken, price.datatoken, price.ocean, price?.value])
useEffect(() => {
setIsRemoveDisabled(isInPurgatory && owner === accountId)
}, [isInPurgatory, owner, accountId])
useEffect(() => {
if (!dataLiquidity) return
const poolShare =
@ -280,6 +288,7 @@ export default function Pool(): ReactElement {
.toFixed(5)
setPoolShare(poolShare)
setHasAddedLiquidity(Number(poolShare) > 0)
const totalUserLiquidityInOcean =
isValidNumber(userLiquidity?.ocean) &&
@ -350,7 +359,23 @@ export default function Pool(): ReactElement {
init()
}, [accountId, price, ddo, owner, totalPoolTokens])
return (
const refreshInfo = async () => {
setRefreshPool(!refreshPool)
// need some form of replacement or something.
// await refreshPrice()
}
return showRemove ? (
<Remove
setShowRemove={setShowRemove}
refreshInfo={refreshInfo}
poolAddress={price.address}
poolTokens={poolTokens}
totalPoolTokens={totalPoolTokens}
dtSymbol={dtSymbol}
/>
) : (
<>
<div className={styles.dataToken}>
<PriceUnit price="1" symbol={dtSymbol} /> ={' '}
@ -433,15 +458,16 @@ export default function Pool(): ReactElement {
>
<Token symbol="% swap fee" balance={swapFee} noIcon />
</TokenList>
<Alert
title="Adding and removing liquidity is disabled"
text={
status === MigrationStatus.ALLOWED
? 'Pool Shares are currently being locked. Adding and removing liquidity is disabled while the pool shares are being locked.'
: 'Adding and removing liquidity is currently disabled for all pools.'
}
state="info"
/>
<div className={stylesActions.actions}>
<Button
size="small"
onClick={() => setShowRemove(true)}
disabled={!isAssetNetwork || !hasAddedLiquidity || isRemoveDisabled}
>
Remove Liquidity
</Button>
</div>
{accountId && (
<AssetActionHistoryTable title="Your Pool Transactions">

View File

@ -0,0 +1,37 @@
import { Ocean } from '@oceanprotocol/lib'
import { isValidNumber } from './../../../../utils/numberValidations'
import Decimal from 'decimal.js'
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
export async function getMaxPercentRemove(
ocean: Ocean,
poolAddress: string,
poolTokens: string
): Promise<string> {
const amountMaxOcean = await ocean.pool.getOceanMaxRemoveLiquidity(
poolAddress
)
const amountMaxPoolShares =
await ocean.pool.getPoolSharesRequiredToRemoveOcean(
poolAddress,
amountMaxOcean
)
let amountMaxPercent =
isValidNumber(amountMaxPoolShares) && isValidNumber(poolTokens)
? new Decimal(amountMaxPoolShares)
.dividedBy(new Decimal(poolTokens))
.mul(100)
.floor()
.toString()
: '0'
if (Number(amountMaxPercent) > 100) {
amountMaxPercent = '100'
}
return amountMaxPercent
}

View File

@ -0,0 +1,30 @@
.slippage {
font-size: var(--font-size-small);
border-top: 1px solid var(--border-color);
margin-left: -2rem;
margin-right: -2rem;
padding: calc(var(--spacer) / 4) var(--spacer);
color: var(--color-secondary);
display: grid;
grid-template-columns: 1fr 1fr;
gap: calc(var(--spacer) / 3);
}
.slippage strong {
font-weight: var(--font-weight-base);
text-align: right;
}
.title {
font-family: var(--font-family-base);
font-weight: var(--font-weight-base);
font-size: var(--font-size-mini);
text-align: center;
margin-bottom: calc(var(--spacer) / 4);
color: var(--color-secondary);
}
.slippage select {
width: fit-content;
display: inline-block;
}

View File

@ -135,7 +135,6 @@ export default function AssetActions(): ReactElement {
return (
<>
{price?.type === 'pool' && <Migration />}
<Permission eventType="consume">
<Tabs items={tabs} className={styles.actions} />
</Permission>