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

Token approval split-up (#640)

* Token approval component

* check if datatoken approved

* display token amount on button, add approve function

* approve token based on token type

* approve token for trade, remove for pool lequidity remove action

* verify approval on amount change

* show action button only if amount is approved

* catch approve error and stop loadin

* display token amount with 2 decimals on trade token approval

* infinite approval and UI fixes

* fixed alert warning not showing, account id for approve

* wip

* fixed displayed token amount to approve for swap

* token amount text fix

* lint error fix

* package version update

* version fix

* downgrade version

* fixed error for no wallet connected

* update package-lock

* display token name, and changed amount precision

* removed empty file, fixed token switch error

* refactor for better user experience

* move content

* ExplorerLink console error fixes

* UI tweaks

* slightly changed button logic

* fix Trade form approvals

* cleanup

* don't block add liquidity button

* merge fixes

* hook dependency cleanup

* dtItem fix, error fixes based on asset network match

* disable action button if field is not valid, undefined trade tokens

* fix infiniteApproval user preference saving

* remove unneccessary string conversion

* used Decimal for dtAmount and oceanAmount

* changed token spender address

* bump ocean.js to vo.17.5

* fix lint

* replace Number with Decimal

* fix getting to add liquidity screen without wallet connected

* fix crash when switching coins after value input

Co-authored-by: Norbi <katunanorbert@gmai.com>
Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
Co-authored-by: mihaisc <mihai@oceanprotocol.com>
This commit is contained in:
Norbi 2021-09-30 13:54:44 +03:00 committed by GitHub
parent 9b3cb3963e
commit 4b34e2f347
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 348 additions and 89 deletions

View File

@ -34,7 +34,9 @@
"pool": { "pool": {
"tooltips": { "tooltips": {
"price": "The price is determined by an automated market maker, which is a type of decentralized exchange protocol that relies on a mathematical formula. It is an alternative to a traditional order book.", "price": "The price is determined by an automated market maker, which is a type of decentralized exchange protocol that relies on a mathematical formula. It is an alternative to a traditional order book.",
"liquidity": "Providing liquidity will earn you SWAPFEE% on every transaction in this pool, proportionally to your share of the pool." "liquidity": "Providing liquidity will earn you SWAPFEE% on every transaction in this pool, proportionally to your share of the pool.",
"approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences.",
"approveInfinite": "Give the smart contract permission to spend infinte amounts of your COIN so you have to do this only once. You can disable allowing infinite amounts in your user preferences."
}, },
"add": { "add": {
"title": "Add Liquidity", "title": "Add Liquidity",
@ -43,7 +45,7 @@
"titleIn": "You will receive", "titleIn": "You will receive",
"titleOut": "Pool conversion" "titleOut": "Pool conversion"
}, },
"action": "Approve & Supply", "action": "Supply",
"warning": "Use at your own risk. Please familiarize yourself [with the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13) and the [Terms of Use](/terms)." "warning": "Use at your own risk. Please familiarize yourself [with the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13) and the [Terms of Use](/terms)."
}, },
"remove": { "remove": {
@ -54,11 +56,11 @@
"titleIn": "You will spend", "titleIn": "You will spend",
"titleOut": "You will receive" "titleOut": "You will receive"
}, },
"action": "Approve & Remove" "action": "Remove"
} }
}, },
"trade": { "trade": {
"action": "Approve & Swap", "action": "Swap",
"warning": "Use at your own risk. Please familiarize yourself [with the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13) and the [Terms of Use](/terms)." "warning": "Use at your own risk. Please familiarize yourself [with the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13) and the [Terms of Use](/terms)."
} }
} }

4
package-lock.json generated
View File

@ -5681,7 +5681,7 @@
"save-file": "^2.3.1", "save-file": "^2.3.1",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"web3": "^1.5.2", "web3": "^1.5.2",
"web3-core": "^1.5.3", "web3-core": "^1.5.2",
"web3-eth-contract": "^1.5.2" "web3-eth-contract": "^1.5.2"
}, },
"peerDependencies": { "peerDependencies": {
@ -63142,7 +63142,7 @@
"save-file": "^2.3.1", "save-file": "^2.3.1",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"web3": "^1.5.2", "web3": "^1.5.2",
"web3-core": "^1.5.3", "web3-core": "^1.5.2",
"web3-eth-contract": "^1.5.2" "web3-eth-contract": "^1.5.2"
}, },
"dependencies": { "dependencies": {

View File

@ -8,7 +8,7 @@ export default function DebugOutput({
output: any output: any
}): ReactElement { }): ReactElement {
return ( return (
<div> <div style={{ marginTop: 'var(--spacer)' }}>
<h5>{title}</h5> <h5>{title}</h5>
<pre> <pre>
<code>{JSON.stringify(output, null, 2)}</code> <code>{JSON.stringify(output, null, 2)}</code>

View File

@ -28,6 +28,8 @@ export default function ExplorerLink({
}) })
useEffect(() => { useEffect(() => {
if (!networkId) return
async function initOcean() { async function initOcean() {
const oceanInitialConfig = getOceanConfig(networkId) const oceanInitialConfig = getOceanConfig(networkId)
setOceanConfig(oceanInitialConfig) setOceanConfig(oceanInitialConfig)
@ -36,7 +38,7 @@ export default function ExplorerLink({
if (oceanConfig === undefined) { if (oceanConfig === undefined) {
initOcean() initOcean()
} }
}, [config, networkId, ocean]) }, [config, oceanConfig, networkId, ocean])
return ( return (
<a <a

View File

@ -0,0 +1,144 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
import Button from '../atoms/Button'
import { useOcean } from '../../providers/Ocean'
import { useAsset } from '../../providers/Asset'
import Loader from '../atoms/Loader'
import { useWeb3 } from '../../providers/Web3'
import { useUserPreferences } from '../../providers/UserPreferences'
import Tooltip from '../atoms/Tooltip'
import { graphql, useStaticQuery } from 'gatsby'
import Decimal from 'decimal.js'
import { getOceanConfig } from '../../utils/ocean'
const query = graphql`
query {
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
edges {
node {
childContentJson {
pool {
tooltips {
approveSpecific
approveInfinite
}
}
}
}
}
}
}
`
function ButtonApprove({
amount,
coin,
approveTokens,
isLoading
}: {
amount: string
coin: string
approveTokens: (amount: string) => void
isLoading: boolean
}) {
// Get content
const data = useStaticQuery(query)
const content = data.content.edges[0].node.childContentJson.pool.tooltips
const { infiniteApproval } = useUserPreferences()
return isLoading ? (
<Loader message={`Approving ${coin}...`} />
) : infiniteApproval ? (
<Button
style="primary"
size="small"
disabled={parseInt(amount) < 1}
onClick={() => approveTokens(`${2 ** 53 - 1}`)}
>
Approve {coin}{' '}
<Tooltip content={content.approveInfinite.replace('COIN', coin)} />
</Button>
) : (
<Button style="primary" size="small" onClick={() => approveTokens(amount)}>
Approve {amount} {coin}
<Tooltip content={content.approveSpecific.replace('COIN', coin)} />
</Button>
)
}
export default function TokenApproval({
actionButton,
disabled,
amount,
coin
}: {
actionButton: JSX.Element
disabled: boolean
amount: string
coin: string
}): ReactElement {
const { ddo, price } = useAsset()
const [tokenApproved, setTokenApproved] = useState(false)
const [loading, setLoading] = useState(false)
const { ocean } = useOcean()
const { accountId } = useWeb3()
const config = getOceanConfig(ddo.chainId)
const tokenAddress =
coin === 'OCEAN' ? config.oceanTokenAddress : ddo.dataTokenInfo.address
const spender = price.address
const checkTokenApproval = useCallback(async () => {
if (!ocean || !tokenAddress || !spender) return
const allowance = await ocean.datatokens.allowance(
tokenAddress,
accountId,
spender
)
amount &&
new Decimal(amount).greaterThan(new Decimal('0')) &&
setTokenApproved(
new Decimal(allowance).greaterThanOrEqualTo(new Decimal(amount))
)
}, [ocean, tokenAddress, spender, accountId, amount])
useEffect(() => {
checkTokenApproval()
}, [checkTokenApproval])
async function approveTokens(amount: string) {
setLoading(true)
try {
await ocean.datatokens.approve(tokenAddress, spender, amount, accountId)
} catch (error) {
setLoading(false)
}
await checkTokenApproval()
setLoading(false)
}
return (
<>
{tokenApproved ||
disabled ||
amount === '0' ||
amount === '' ||
!amount ||
typeof amount === 'undefined' ? (
actionButton
) : (
<ButtonApprove
amount={amount}
coin={coin}
approveTokens={approveTokens}
isLoading={loading}
/>
)}
</>
)
}

View File

@ -0,0 +1,21 @@
import React, { ReactElement } from 'react'
import { useUserPreferences } from '../../../providers/UserPreferences'
import Input from '../../atoms/Input'
export default function TokenApproval(): ReactElement {
const { infiniteApproval, setInfiniteApproval } = useUserPreferences()
return (
<li>
<Input
label="Token Approvals"
help="Use infinite amount when approving tokens in _Use_, _Pool_, or _Trade_."
name="infiniteApproval"
type="checkbox"
options={['Allow infinite amount']}
defaultChecked={infiniteApproval === true}
onChange={() => setInfiniteApproval(!infiniteApproval)}
/>
</li>
)
}

View File

@ -8,6 +8,7 @@ import { ReactComponent as Caret } from '../../../images/caret.svg'
import useDarkMode from 'use-dark-mode' import useDarkMode from 'use-dark-mode'
import Appearance from './Appearance' import Appearance from './Appearance'
import { darkModeConfig } from '../../../../app.config' import { darkModeConfig } from '../../../../app.config'
import TokenApproval from './TokenApproval'
export default function UserPreferences(): ReactElement { export default function UserPreferences(): ReactElement {
// Calling this here because <Style /> is not mounted on first load // Calling this here because <Style /> is not mounted on first load
@ -18,6 +19,7 @@ export default function UserPreferences(): ReactElement {
content={ content={
<ul className={styles.preferencesDetails}> <ul className={styles.preferencesDetails}>
<Currency /> <Currency />
<TokenApproval />
<Appearance darkMode={darkMode} /> <Appearance darkMode={darkMode} />
<Debug /> <Debug />
</ul> </ul>

View File

@ -79,7 +79,13 @@ export default function Consume({
}, [ddo, accountId, hasPreviousOrder, isMounted]) }, [ddo, accountId, hasPreviousOrder, isMounted])
useEffect(() => { useEffect(() => {
if (!data || !assetTimeout || data.tokenOrders.length === 0 || !accountId) if (
!data ||
!assetTimeout ||
data.tokenOrders.length === 0 ||
!accountId ||
!isAssetNetwork
)
return return
const lastOrder = data.tokenOrders[0] const lastOrder = data.tokenOrders[0]
@ -96,11 +102,11 @@ export default function Consume({
setHasPreviousOrder(false) setHasPreviousOrder(false)
} }
} }
}, [data, assetTimeout, accountId]) }, [data, assetTimeout, accountId, isAssetNetwork])
useEffect(() => { useEffect(() => {
const { timeout } = ddo.findServiceByType('access').attributes.main const { timeout } = ddo.findServiceByType('access').attributes.main
setAssetTimeout(timeout.toString()) setAssetTimeout(`${timeout}`)
}, [ddo]) }, [ddo])
useEffect(() => { useEffect(() => {
@ -137,8 +143,8 @@ export default function Consume({
pricingIsLoading, pricingIsLoading,
isConsumablePrice, isConsumablePrice,
hasDatatoken, hasDatatoken,
accountId, isConsumable,
isConsumable accountId
]) ])
async function handleConsume() { async function handleConsume() {

View File

@ -19,3 +19,7 @@
margin-left: calc(var(--spacer) / 4); margin-left: calc(var(--spacer) / 4);
margin-right: calc(var(--spacer) / 4); margin-right: calc(var(--spacer) / 4);
} }
.actions button svg {
fill: currentColor;
}

View File

@ -6,6 +6,7 @@ import ExplorerLink from '../../../atoms/ExplorerLink'
import SuccessConfetti from '../../../atoms/SuccessConfetti' import SuccessConfetti from '../../../atoms/SuccessConfetti'
import { useOcean } from '../../../../providers/Ocean' import { useOcean } from '../../../../providers/Ocean'
import { useWeb3 } from '../../../../providers/Web3' import { useWeb3 } from '../../../../providers/Web3'
import TokenApproval from '../../../molecules/TokenApproval'
export default function Actions({ export default function Actions({
isLoading, isLoading,
@ -13,6 +14,8 @@ export default function Actions({
successMessage, successMessage,
txId, txId,
actionName, actionName,
amount,
coin,
action, action,
isDisabled isDisabled
}: { }: {
@ -21,26 +24,39 @@ export default function Actions({
successMessage: string successMessage: string
txId: string txId: string
actionName: string actionName: string
amount?: string
coin?: string
action: () => void action: () => void
isDisabled?: boolean isDisabled?: boolean
}): ReactElement { }): ReactElement {
const { networkId } = useWeb3() const { networkId } = useWeb3()
const { ocean } = useOcean() const { ocean } = useOcean()
const actionButton = (
<Button
style="primary"
size="small"
onClick={() => action()}
disabled={!ocean || isDisabled}
>
{actionName}
</Button>
)
return ( return (
<> <>
<div className={styles.actions}> <div className={styles.actions}>
{isLoading ? ( {isLoading ? (
<Loader message={loaderMessage} /> <Loader message={loaderMessage} />
) : ( ) : actionName === 'Supply' || actionName === 'Swap' ? (
<Button <TokenApproval
style="primary" actionButton={actionButton}
size="small" amount={amount}
onClick={() => action()} coin={coin}
disabled={!ocean || isDisabled} disabled={!ocean || isDisabled}
> />
{actionName} ) : (
</Button> actionButton
)} )}
</div> </div>
{txId && ( {txId && (

View File

@ -24,6 +24,7 @@ export default function FormAdd({
dtSymbol, dtSymbol,
amountMax, amountMax,
setCoin, setCoin,
setAmount,
totalPoolTokens, totalPoolTokens,
totalBalance, totalBalance,
poolAddress, poolAddress,
@ -35,6 +36,7 @@ export default function FormAdd({
dtSymbol: string dtSymbol: string
amountMax: string amountMax: string
setCoin: (value: string) => void setCoin: (value: string) => void
setAmount: (value: string) => void
totalPoolTokens: string totalPoolTokens: string
totalBalance: PoolBalance totalBalance: PoolBalance
poolAddress: string poolAddress: string
@ -56,6 +58,7 @@ export default function FormAdd({
function handleFieldChange(e: ChangeEvent<HTMLInputElement>) { function handleFieldChange(e: ChangeEvent<HTMLInputElement>) {
// Workaround so validation kicks in on first touch // Workaround so validation kicks in on first touch
!touched?.amount && setTouched({ amount: true }) !touched?.amount && setTouched({ amount: true })
setAmount(e.target.value)
// Manually handle change events instead of using `handleChange` from Formik. // Manually handle change events instead of using `handleChange` from Formik.
// Solves bug where 0.0 can't be typed. // Solves bug where 0.0 can't be typed.

View File

@ -58,7 +58,8 @@ export default function Output({
const [poolDatatoken, setPoolDatatoken] = useState('0') const [poolDatatoken, setPoolDatatoken] = useState('0')
useEffect(() => { useEffect(() => {
if (!values.amount || !totalBalance || !totalPoolTokens) return if (!values.amount || !totalBalance || !totalPoolTokens || !newPoolTokens)
return
const newPoolSupply = new Decimal(totalPoolTokens).plus(newPoolTokens) const newPoolSupply = new Decimal(totalPoolTokens).plus(newPoolTokens)
const ratio = new Decimal(newPoolTokens).div(newPoolSupply) const ratio = new Decimal(newPoolTokens).div(newPoolSupply)
const newOceanReserve = const newOceanReserve =
@ -73,7 +74,14 @@ export default function Output({
const poolDatatoken = newDtReserve.mul(ratio).toString() const poolDatatoken = newDtReserve.mul(ratio).toString()
setPoolOcean(poolOcean) setPoolOcean(poolOcean)
setPoolDatatoken(poolDatatoken) setPoolDatatoken(poolDatatoken)
}, [values.amount, coin, totalBalance, totalPoolTokens, newPoolShare]) }, [
values.amount,
coin,
totalBalance,
totalPoolTokens,
newPoolShare,
newPoolTokens
])
return ( return (
<> <>

View File

@ -72,6 +72,7 @@ export default function Add({
const [coin, setCoin] = useState('OCEAN') const [coin, setCoin] = useState('OCEAN')
const [dtBalance, setDtBalance] = useState<string>() const [dtBalance, setDtBalance] = useState<string>()
const [amountMax, setAmountMax] = useState<string>() const [amountMax, setAmountMax] = useState<string>()
const [amount, setAmount] = useState<string>('0')
const [newPoolTokens, setNewPoolTokens] = useState('0') const [newPoolTokens, setNewPoolTokens] = useState('0')
const [newPoolShare, setNewPoolShare] = useState('0') const [newPoolShare, setNewPoolShare] = useState('0')
const [isWarningAccepted, setIsWarningAccepted] = useState(false) const [isWarningAccepted, setIsWarningAccepted] = useState(false)
@ -146,7 +147,6 @@ export default function Add({
return ( return (
<> <>
<Header title={content.title} backAction={() => setShowAdd(false)} /> <Header title={content.title} backAction={() => setShowAdd(false)} />
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
validationSchema={validationSchema} validationSchema={validationSchema}
@ -155,7 +155,7 @@ export default function Add({
setSubmitting(false) setSubmitting(false)
}} }}
> >
{({ isSubmitting, submitForm, values }) => ( {({ isSubmitting, submitForm, values, isValid }) => (
<> <>
<div className={styles.addInput}> <div className={styles.addInput}>
{isWarningAccepted ? ( {isWarningAccepted ? (
@ -165,6 +165,7 @@ export default function Add({
dtSymbol={dtSymbol} dtSymbol={dtSymbol}
amountMax={amountMax} amountMax={amountMax}
setCoin={setCoin} setCoin={setCoin}
setAmount={setAmount}
totalPoolTokens={totalPoolTokens} totalPoolTokens={totalPoolTokens}
totalBalance={totalBalance} totalBalance={totalBalance}
poolAddress={poolAddress} poolAddress={poolAddress}
@ -172,16 +173,18 @@ export default function Add({
setNewPoolShare={setNewPoolShare} setNewPoolShare={setNewPoolShare}
/> />
) : ( ) : (
<Alert content.warning && (
className={styles.warning} <Alert
text={content.warning} className={styles.warning}
state="info" text={content.warning.toString()}
action={{ state="info"
name: 'I understand', action={{
style: 'text', name: 'I understand',
handleAction: () => setIsWarningAccepted(true) style: 'text',
}} handleAction: () => setIsWarningAccepted(true)
/> }}
/>
)
)} )}
</div> </div>
@ -196,12 +199,19 @@ export default function Add({
/> />
<Actions <Actions
isDisabled={!isWarningAccepted} isDisabled={
!isValid ||
!isWarningAccepted ||
amount === '' ||
amount === '0'
}
isLoading={isSubmitting} isLoading={isSubmitting}
loaderMessage="Adding Liquidity..." loaderMessage="Adding Liquidity..."
successMessage="Successfully added liquidity." successMessage="Successfully added liquidity."
actionName={content.action} actionName={content.action}
action={submitForm} action={submitForm}
amount={amount}
coin={coin}
txId={txId} txId={txId}
/> />
{debug && <DebugOutput title="Collected values" output={values} />} {debug && <DebugOutput title="Collected values" output={values} />}

View File

@ -487,16 +487,14 @@ export default function Pool(): ReactElement {
</div> </div>
<div className={stylesActions.actions}> <div className={stylesActions.actions}>
{!isInPurgatory && ( <Button
<Button style="primary"
style="primary" size="small"
size="small" onClick={() => setShowAdd(true)}
onClick={() => setShowAdd(true)} disabled={isInPurgatory}
disabled={isInPurgatory} >
> Add Liquidity
Add Liquidity </Button>
</Button>
)}
{hasAddedLiquidity && !isRemoveDisabled && ( {hasAddedLiquidity && !isRemoveDisabled && (
<Button <Button

View File

@ -54,6 +54,7 @@ export default function FormTrade({
const { isAssetNetwork } = useAsset() const { isAssetNetwork } = useAsset()
const { debug } = useUserPreferences() const { debug } = useUserPreferences()
const [txId, setTxId] = useState<string>() const [txId, setTxId] = useState<string>()
const [coinFrom, setCoinFrom] = useState<string>('OCEAN')
const [maximumOcean, setMaximumOcean] = useState(maxOcean) const [maximumOcean, setMaximumOcean] = useState(maxOcean)
const [maximumDt, setMaximumDt] = useState(maxDt) const [maximumDt, setMaximumDt] = useState(maxDt)
@ -114,6 +115,7 @@ export default function FormTrade({
toast.error(error.message) toast.error(error.message)
} }
} }
return ( return (
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
@ -124,7 +126,7 @@ export default function FormTrade({
setSubmitting(false) setSubmitting(false)
}} }}
> >
{({ isSubmitting, submitForm, values }) => ( {({ isSubmitting, submitForm, values, isValid }) => (
<> <>
{isWarningAccepted ? ( {isWarningAccepted ? (
<Swap <Swap
@ -133,6 +135,7 @@ export default function FormTrade({
maxDt={maxDt} maxDt={maxDt}
maxOcean={maxOcean} maxOcean={maxOcean}
price={price} price={price}
setCoin={setCoinFrom}
setMaximumOcean={setMaximumOcean} setMaximumOcean={setMaximumOcean}
setMaximumDt={setMaximumDt} setMaximumDt={setMaximumDt}
/> />
@ -150,12 +153,22 @@ export default function FormTrade({
</div> </div>
)} )}
<Actions <Actions
isDisabled={!isWarningAccepted || !isAssetNetwork} isDisabled={
!isValid ||
!isWarningAccepted ||
!isAssetNetwork ||
values.datatoken === undefined ||
values.ocean === undefined
}
isLoading={isSubmitting} isLoading={isSubmitting}
loaderMessage="Swapping tokens..." loaderMessage="Swapping tokens..."
successMessage="Successfully swapped tokens." successMessage="Successfully swapped tokens."
actionName={content.action} actionName={content.action}
amount={`${
values.type === 'sell' ? values.datatoken : values.ocean
}`}
action={submitForm} action={submitForm}
coin={coinFrom}
txId={txId} txId={txId}
/> />

View File

@ -1,6 +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 { FormTradeData } from '../../../../models/FormTrade' import { FormTradeData } from '../../../../models/FormTrade'
import { useAsset } from '../../../../providers/Asset'
import { useOcean } from '../../../../providers/Ocean' import { useOcean } from '../../../../providers/Ocean'
import Token from '../Pool/Token' import Token from '../Pool/Token'
import styles from './Output.module.css' import styles from './Output.module.css'
@ -19,6 +20,7 @@ export default function Output({
oceanSymbol: string oceanSymbol: string
poolAddress: string poolAddress: string
}): ReactElement { }): ReactElement {
const { isAssetNetwork } = useAsset()
const { ocean } = useOcean() const { ocean } = useOcean()
const [maxOutput, setMaxOutput] = useState<string>() const [maxOutput, setMaxOutput] = useState<string>()
const [swapFee, setSwapFee] = useState<string>() const [swapFee, setSwapFee] = useState<string>()
@ -28,7 +30,7 @@ export default function Output({
// Get swap fee // Get swap fee
useEffect(() => { useEffect(() => {
if (!ocean || !poolAddress) return if (!ocean || !poolAddress || !isAssetNetwork) return
async function getSwapFee() { async function getSwapFee() {
const swapFee = await ocean.pool.getSwapFee(poolAddress) const swapFee = await ocean.pool.getSwapFee(poolAddress)
@ -49,11 +51,11 @@ export default function Output({
setSwapFeeValue(value.toString()) setSwapFeeValue(value.toString())
} }
getSwapFee() getSwapFee()
}, [ocean, poolAddress, values]) }, [ocean, poolAddress, values, isAssetNetwork])
// Get output values // Get output values
useEffect(() => { useEffect(() => {
if (!ocean || !poolAddress) return if (!ocean || !poolAddress || !isAssetNetwork) return
async function getOutput() { async function getOutput() {
// Minimum received // Minimum received
@ -73,7 +75,7 @@ export default function Output({
setMaxOutput(maxPrice) setMaxOutput(maxPrice)
} }
getOutput() getOutput()
}, [ocean, poolAddress, values]) }, [ocean, poolAddress, values, isAssetNetwork])
return ( return (
<div className={styles.output}> <div className={styles.output}>

View File

@ -51,7 +51,7 @@ export default function PriceImpact({
return ( return (
<div className={styles.priceImpact}> <div className={styles.priceImpact}>
<strong>Price impact</strong> <strong>Price Impact</strong>
<div> <div>
<span <span
className={`${styles.number} ${ className={`${styles.number} ${

View File

@ -6,10 +6,10 @@ import Button from '../../../atoms/Button'
import { ReactComponent as Arrow } from '../../../../images/arrow.svg' import { ReactComponent as Arrow } from '../../../../images/arrow.svg'
import { FormikContextType, useFormikContext } from 'formik' import { FormikContextType, useFormikContext } from 'formik'
import { PoolBalance } from '../../../../@types/TokenBalance' import { PoolBalance } from '../../../../@types/TokenBalance'
import Output from './Output'
import Slippage from './Slippage'
import { FormTradeData, TradeItem } from '../../../../models/FormTrade' import { FormTradeData, TradeItem } from '../../../../models/FormTrade'
import { useOcean } from '../../../../providers/Ocean' import { useOcean } from '../../../../providers/Ocean'
import Output from './Output'
import Slippage from './Slippage'
import PriceImpact from './PriceImpact' import PriceImpact from './PriceImpact'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
@ -24,7 +24,8 @@ export default function Swap({
balance, balance,
price, price,
setMaximumDt, setMaximumDt,
setMaximumOcean setMaximumOcean,
setCoin
}: { }: {
ddo: DDO ddo: DDO
maxDt: string maxDt: string
@ -33,6 +34,7 @@ export default function Swap({
price: BestPrice price: BestPrice
setMaximumDt: (value: string) => void setMaximumDt: (value: string) => void
setMaximumOcean: (value: string) => void setMaximumOcean: (value: string) => void
setCoin: (value: string) => void
}): ReactElement { }): ReactElement {
const { ocean, config } = useOcean() const { ocean, config } = useOcean()
const [oceanItem, setOceanItem] = useState<TradeItem>({ const [oceanItem, setOceanItem] = useState<TradeItem>({
@ -53,63 +55,82 @@ export default function Swap({
validateForm validateForm
}: FormikContextType<FormTradeData> = useFormikContext() }: FormikContextType<FormTradeData> = useFormikContext()
/// Values used for calculation of price impact // Values used for calculation of price impact
const [spotPrice, setSpotPrice] = useState<string>() const [spotPrice, setSpotPrice] = useState<string>()
const [totalValue, setTotalValue] = useState<string>() const [totalValue, setTotalValue] = useState<string>()
const [tokenAmount, setTokenAmount] = useState<string>() const [tokenAmount, setTokenAmount] = useState<string>()
///
useEffect(() => { useEffect(() => {
if (!ddo || !balance || !values || !price) return if (!ddo || !balance || !values?.type || !price) return
async function calculateMaximum() { async function calculateMaximum() {
const dtAmount = values.type === 'buy' ? maxDt : balance.datatoken const dtAmount =
const oceanAmount = values.type === 'buy' ? balance.ocean : maxOcean values.type === 'buy'
? new Decimal(maxDt)
: new Decimal(balance.datatoken)
const oceanAmount =
values.type === 'buy'
? new Decimal(balance.ocean)
: new Decimal(maxOcean)
const maxBuyOcean = await ocean.pool.getOceanReceived( const maxBuyOcean = await ocean.pool.getOceanReceived(
price.address, price.address,
dtAmount.toString() `${dtAmount.toString()}`
) )
const maxBuyDt = await ocean.pool.getDTReceived( const maxBuyDt = await ocean.pool.getDTReceived(
price.address, price.address,
oceanAmount.toString() `${oceanAmount.toString()}`
) )
const maximumDt = const maximumDt =
values.type === 'buy' values.type === 'buy'
? Number(dtAmount) > Number(maxBuyDt) ? dtAmount.greaterThan(new Decimal(maxBuyDt))
? new Decimal(maxBuyDt) ? maxBuyDt
: new Decimal(dtAmount) : dtAmount
: Number(dtAmount) > Number(balance.datatoken) : dtAmount.greaterThan(new Decimal(balance.datatoken))
? new Decimal(balance.datatoken) ? balance.datatoken
: new Decimal(dtAmount) : dtAmount
const maximumOcean = const maximumOcean =
values.type === 'sell' values.type === 'sell'
? Number(oceanAmount) > Number(maxBuyOcean) ? oceanAmount.greaterThan(new Decimal(maxBuyOcean))
? new Decimal(maxBuyOcean) ? maxBuyOcean
: new Decimal(oceanAmount) : oceanAmount
: Number(oceanAmount) > Number(balance.ocean) : oceanAmount.greaterThan(new Decimal(balance.ocean))
? new Decimal(balance.ocean) ? balance.ocean
: new Decimal(oceanAmount) : oceanAmount
setMaximumDt(maximumDt.toString()) setMaximumDt(maximumDt.toString())
setMaximumOcean(maximumOcean.toString()) setMaximumOcean(maximumOcean.toString())
setOceanItem({
...oceanItem, setOceanItem((prevState) => ({
...prevState,
amount: oceanAmount.toString(), amount: oceanAmount.toString(),
maxAmount: maximumOcean.toString() maxAmount: maximumOcean.toString()
}) }))
setDtItem({
...dtItem, setDtItem((prevState) => ({
...prevState,
amount: dtAmount.toString(), amount: dtAmount.toString(),
maxAmount: maximumDt.toString() maxAmount: maximumDt.toString()
}) }))
} }
calculateMaximum() calculateMaximum()
}, [ddo, maxOcean, maxDt, balance, price?.value, values.type]) }, [
ddo,
maxOcean,
maxDt,
balance,
price,
values?.type,
ocean,
setMaximumDt,
setMaximumOcean
])
const switchTokens = () => { const switchTokens = () => {
setFieldValue('type', values.type === 'buy' ? 'sell' : 'buy') setFieldValue('type', values.type === 'buy' ? 'sell' : 'buy')
setCoin(values.type === 'sell' ? 'OCEAN' : ddo.dataTokenInfo.symbol)
// don't reset form because we don't want to reset type // don't reset form because we don't want to reset type
setFieldValue('datatoken', 0) setFieldValue('datatoken', 0)
setFieldValue('ocean', 0) setFieldValue('ocean', 0)

View File

@ -12,15 +12,17 @@ import { useSiteMetadata } from '../hooks/useSiteMetadata'
interface UserPreferencesValue { interface UserPreferencesValue {
debug: boolean debug: boolean
currency: string
locale: string
chainIds: number[]
bookmarks: string[]
setChainIds: (chainIds: number[]) => void
setDebug: (value: boolean) => void setDebug: (value: boolean) => void
currency: string
setCurrency: (value: string) => void setCurrency: (value: string) => void
chainIds: number[]
setChainIds: (chainIds: number[]) => void
bookmarks: string[]
addBookmark: (did: string) => void addBookmark: (did: string) => void
removeBookmark: (did: string) => void removeBookmark: (did: string) => void
infiniteApproval: boolean
setInfiniteApproval: (value: boolean) => void
locale: string
} }
const UserPreferencesContext = createContext(null) const UserPreferencesContext = createContext(null)
@ -58,11 +60,14 @@ function UserPreferencesProvider({
const [chainIds, setChainIds] = useState( const [chainIds, setChainIds] = useState(
localStorage?.chainIds || appConfig.chainIds localStorage?.chainIds || appConfig.chainIds
) )
const [infiniteApproval, setInfiniteApproval] = useState(
localStorage?.infiniteApproval || false
)
// Write values to localStorage on change // Write values to localStorage on change
useEffect(() => { useEffect(() => {
setLocalStorage({ chainIds, debug, currency, bookmarks }) setLocalStorage({ chainIds, debug, currency, bookmarks, infiniteApproval })
}, [chainIds, debug, currency, bookmarks]) }, [chainIds, debug, currency, bookmarks, infiniteApproval])
// Set ocean.js log levels, default: Error // Set ocean.js log levels, default: Error
useEffect(() => { useEffect(() => {
@ -108,6 +113,8 @@ function UserPreferencesProvider({
locale, locale,
chainIds, chainIds,
bookmarks, bookmarks,
infiniteApproval,
setInfiniteApproval,
setChainIds, setChainIds,
setDebug, setDebug,
setCurrency, setCurrency,