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:
parent
9b3cb3963e
commit
4b34e2f347
@ -34,7 +34,9 @@
|
||||
"pool": {
|
||||
"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.",
|
||||
"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": {
|
||||
"title": "Add Liquidity",
|
||||
@ -43,7 +45,7 @@
|
||||
"titleIn": "You will receive",
|
||||
"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)."
|
||||
},
|
||||
"remove": {
|
||||
@ -54,11 +56,11 @@
|
||||
"titleIn": "You will spend",
|
||||
"titleOut": "You will receive"
|
||||
},
|
||||
"action": "Approve & Remove"
|
||||
"action": "Remove"
|
||||
}
|
||||
},
|
||||
"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)."
|
||||
}
|
||||
}
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -5681,7 +5681,7 @@
|
||||
"save-file": "^2.3.1",
|
||||
"uuid": "^8.3.2",
|
||||
"web3": "^1.5.2",
|
||||
"web3-core": "^1.5.3",
|
||||
"web3-core": "^1.5.2",
|
||||
"web3-eth-contract": "^1.5.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@ -63142,7 +63142,7 @@
|
||||
"save-file": "^2.3.1",
|
||||
"uuid": "^8.3.2",
|
||||
"web3": "^1.5.2",
|
||||
"web3-core": "^1.5.3",
|
||||
"web3-core": "^1.5.2",
|
||||
"web3-eth-contract": "^1.5.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -8,7 +8,7 @@ export default function DebugOutput({
|
||||
output: any
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginTop: 'var(--spacer)' }}>
|
||||
<h5>{title}</h5>
|
||||
<pre>
|
||||
<code>{JSON.stringify(output, null, 2)}</code>
|
||||
|
@ -28,6 +28,8 @@ export default function ExplorerLink({
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!networkId) return
|
||||
|
||||
async function initOcean() {
|
||||
const oceanInitialConfig = getOceanConfig(networkId)
|
||||
setOceanConfig(oceanInitialConfig)
|
||||
@ -36,7 +38,7 @@ export default function ExplorerLink({
|
||||
if (oceanConfig === undefined) {
|
||||
initOcean()
|
||||
}
|
||||
}, [config, networkId, ocean])
|
||||
}, [config, oceanConfig, networkId, ocean])
|
||||
|
||||
return (
|
||||
<a
|
||||
|
144
src/components/molecules/TokenApproval.tsx
Normal file
144
src/components/molecules/TokenApproval.tsx
Normal 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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
21
src/components/molecules/UserPreferences/TokenApproval.tsx
Normal file
21
src/components/molecules/UserPreferences/TokenApproval.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -8,6 +8,7 @@ import { ReactComponent as Caret } from '../../../images/caret.svg'
|
||||
import useDarkMode from 'use-dark-mode'
|
||||
import Appearance from './Appearance'
|
||||
import { darkModeConfig } from '../../../../app.config'
|
||||
import TokenApproval from './TokenApproval'
|
||||
|
||||
export default function UserPreferences(): ReactElement {
|
||||
// Calling this here because <Style /> is not mounted on first load
|
||||
@ -18,6 +19,7 @@ export default function UserPreferences(): ReactElement {
|
||||
content={
|
||||
<ul className={styles.preferencesDetails}>
|
||||
<Currency />
|
||||
<TokenApproval />
|
||||
<Appearance darkMode={darkMode} />
|
||||
<Debug />
|
||||
</ul>
|
||||
|
@ -79,7 +79,13 @@ export default function Consume({
|
||||
}, [ddo, accountId, hasPreviousOrder, isMounted])
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || !assetTimeout || data.tokenOrders.length === 0 || !accountId)
|
||||
if (
|
||||
!data ||
|
||||
!assetTimeout ||
|
||||
data.tokenOrders.length === 0 ||
|
||||
!accountId ||
|
||||
!isAssetNetwork
|
||||
)
|
||||
return
|
||||
|
||||
const lastOrder = data.tokenOrders[0]
|
||||
@ -96,11 +102,11 @@ export default function Consume({
|
||||
setHasPreviousOrder(false)
|
||||
}
|
||||
}
|
||||
}, [data, assetTimeout, accountId])
|
||||
}, [data, assetTimeout, accountId, isAssetNetwork])
|
||||
|
||||
useEffect(() => {
|
||||
const { timeout } = ddo.findServiceByType('access').attributes.main
|
||||
setAssetTimeout(timeout.toString())
|
||||
setAssetTimeout(`${timeout}`)
|
||||
}, [ddo])
|
||||
|
||||
useEffect(() => {
|
||||
@ -137,8 +143,8 @@ export default function Consume({
|
||||
pricingIsLoading,
|
||||
isConsumablePrice,
|
||||
hasDatatoken,
|
||||
accountId,
|
||||
isConsumable
|
||||
isConsumable,
|
||||
accountId
|
||||
])
|
||||
|
||||
async function handleConsume() {
|
||||
|
@ -19,3 +19,7 @@
|
||||
margin-left: calc(var(--spacer) / 4);
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.actions button svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ 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,
|
||||
@ -13,6 +14,8 @@ export default function Actions({
|
||||
successMessage,
|
||||
txId,
|
||||
actionName,
|
||||
amount,
|
||||
coin,
|
||||
action,
|
||||
isDisabled
|
||||
}: {
|
||||
@ -21,26 +24,39 @@ export default function Actions({
|
||||
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} />
|
||||
) : (
|
||||
<Button
|
||||
style="primary"
|
||||
size="small"
|
||||
onClick={() => action()}
|
||||
) : actionName === 'Supply' || actionName === 'Swap' ? (
|
||||
<TokenApproval
|
||||
actionButton={actionButton}
|
||||
amount={amount}
|
||||
coin={coin}
|
||||
disabled={!ocean || isDisabled}
|
||||
>
|
||||
{actionName}
|
||||
</Button>
|
||||
/>
|
||||
) : (
|
||||
actionButton
|
||||
)}
|
||||
</div>
|
||||
{txId && (
|
||||
|
@ -24,6 +24,7 @@ export default function FormAdd({
|
||||
dtSymbol,
|
||||
amountMax,
|
||||
setCoin,
|
||||
setAmount,
|
||||
totalPoolTokens,
|
||||
totalBalance,
|
||||
poolAddress,
|
||||
@ -35,6 +36,7 @@ export default function FormAdd({
|
||||
dtSymbol: string
|
||||
amountMax: string
|
||||
setCoin: (value: string) => void
|
||||
setAmount: (value: string) => void
|
||||
totalPoolTokens: string
|
||||
totalBalance: PoolBalance
|
||||
poolAddress: string
|
||||
@ -56,6 +58,7 @@ export default function FormAdd({
|
||||
function handleFieldChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
// Workaround so validation kicks in on first touch
|
||||
!touched?.amount && setTouched({ amount: true })
|
||||
setAmount(e.target.value)
|
||||
|
||||
// Manually handle change events instead of using `handleChange` from Formik.
|
||||
// Solves bug where 0.0 can't be typed.
|
||||
|
@ -58,7 +58,8 @@ export default function Output({
|
||||
const [poolDatatoken, setPoolDatatoken] = useState('0')
|
||||
|
||||
useEffect(() => {
|
||||
if (!values.amount || !totalBalance || !totalPoolTokens) return
|
||||
if (!values.amount || !totalBalance || !totalPoolTokens || !newPoolTokens)
|
||||
return
|
||||
const newPoolSupply = new Decimal(totalPoolTokens).plus(newPoolTokens)
|
||||
const ratio = new Decimal(newPoolTokens).div(newPoolSupply)
|
||||
const newOceanReserve =
|
||||
@ -73,7 +74,14 @@ export default function Output({
|
||||
const poolDatatoken = newDtReserve.mul(ratio).toString()
|
||||
setPoolOcean(poolOcean)
|
||||
setPoolDatatoken(poolDatatoken)
|
||||
}, [values.amount, coin, totalBalance, totalPoolTokens, newPoolShare])
|
||||
}, [
|
||||
values.amount,
|
||||
coin,
|
||||
totalBalance,
|
||||
totalPoolTokens,
|
||||
newPoolShare,
|
||||
newPoolTokens
|
||||
])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -72,6 +72,7 @@ export default function Add({
|
||||
const [coin, setCoin] = useState('OCEAN')
|
||||
const [dtBalance, setDtBalance] = useState<string>()
|
||||
const [amountMax, setAmountMax] = useState<string>()
|
||||
const [amount, setAmount] = useState<string>('0')
|
||||
const [newPoolTokens, setNewPoolTokens] = useState('0')
|
||||
const [newPoolShare, setNewPoolShare] = useState('0')
|
||||
const [isWarningAccepted, setIsWarningAccepted] = useState(false)
|
||||
@ -146,7 +147,6 @@ export default function Add({
|
||||
return (
|
||||
<>
|
||||
<Header title={content.title} backAction={() => setShowAdd(false)} />
|
||||
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
@ -155,7 +155,7 @@ export default function Add({
|
||||
setSubmitting(false)
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, submitForm, values }) => (
|
||||
{({ isSubmitting, submitForm, values, isValid }) => (
|
||||
<>
|
||||
<div className={styles.addInput}>
|
||||
{isWarningAccepted ? (
|
||||
@ -165,6 +165,7 @@ export default function Add({
|
||||
dtSymbol={dtSymbol}
|
||||
amountMax={amountMax}
|
||||
setCoin={setCoin}
|
||||
setAmount={setAmount}
|
||||
totalPoolTokens={totalPoolTokens}
|
||||
totalBalance={totalBalance}
|
||||
poolAddress={poolAddress}
|
||||
@ -172,16 +173,18 @@ export default function Add({
|
||||
setNewPoolShare={setNewPoolShare}
|
||||
/>
|
||||
) : (
|
||||
<Alert
|
||||
className={styles.warning}
|
||||
text={content.warning}
|
||||
state="info"
|
||||
action={{
|
||||
name: 'I understand',
|
||||
style: 'text',
|
||||
handleAction: () => setIsWarningAccepted(true)
|
||||
}}
|
||||
/>
|
||||
content.warning && (
|
||||
<Alert
|
||||
className={styles.warning}
|
||||
text={content.warning.toString()}
|
||||
state="info"
|
||||
action={{
|
||||
name: 'I understand',
|
||||
style: 'text',
|
||||
handleAction: () => setIsWarningAccepted(true)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -196,12 +199,19 @@ export default function Add({
|
||||
/>
|
||||
|
||||
<Actions
|
||||
isDisabled={!isWarningAccepted}
|
||||
isDisabled={
|
||||
!isValid ||
|
||||
!isWarningAccepted ||
|
||||
amount === '' ||
|
||||
amount === '0'
|
||||
}
|
||||
isLoading={isSubmitting}
|
||||
loaderMessage="Adding Liquidity..."
|
||||
successMessage="Successfully added liquidity."
|
||||
actionName={content.action}
|
||||
action={submitForm}
|
||||
amount={amount}
|
||||
coin={coin}
|
||||
txId={txId}
|
||||
/>
|
||||
{debug && <DebugOutput title="Collected values" output={values} />}
|
||||
|
@ -487,16 +487,14 @@ export default function Pool(): ReactElement {
|
||||
</div>
|
||||
|
||||
<div className={stylesActions.actions}>
|
||||
{!isInPurgatory && (
|
||||
<Button
|
||||
style="primary"
|
||||
size="small"
|
||||
onClick={() => setShowAdd(true)}
|
||||
disabled={isInPurgatory}
|
||||
>
|
||||
Add Liquidity
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
style="primary"
|
||||
size="small"
|
||||
onClick={() => setShowAdd(true)}
|
||||
disabled={isInPurgatory}
|
||||
>
|
||||
Add Liquidity
|
||||
</Button>
|
||||
|
||||
{hasAddedLiquidity && !isRemoveDisabled && (
|
||||
<Button
|
||||
|
@ -54,6 +54,7 @@ export default function FormTrade({
|
||||
const { isAssetNetwork } = useAsset()
|
||||
const { debug } = useUserPreferences()
|
||||
const [txId, setTxId] = useState<string>()
|
||||
const [coinFrom, setCoinFrom] = useState<string>('OCEAN')
|
||||
|
||||
const [maximumOcean, setMaximumOcean] = useState(maxOcean)
|
||||
const [maximumDt, setMaximumDt] = useState(maxDt)
|
||||
@ -114,6 +115,7 @@ export default function FormTrade({
|
||||
toast.error(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
@ -124,7 +126,7 @@ export default function FormTrade({
|
||||
setSubmitting(false)
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, submitForm, values }) => (
|
||||
{({ isSubmitting, submitForm, values, isValid }) => (
|
||||
<>
|
||||
{isWarningAccepted ? (
|
||||
<Swap
|
||||
@ -133,6 +135,7 @@ export default function FormTrade({
|
||||
maxDt={maxDt}
|
||||
maxOcean={maxOcean}
|
||||
price={price}
|
||||
setCoin={setCoinFrom}
|
||||
setMaximumOcean={setMaximumOcean}
|
||||
setMaximumDt={setMaximumDt}
|
||||
/>
|
||||
@ -150,12 +153,22 @@ export default function FormTrade({
|
||||
</div>
|
||||
)}
|
||||
<Actions
|
||||
isDisabled={!isWarningAccepted || !isAssetNetwork}
|
||||
isDisabled={
|
||||
!isValid ||
|
||||
!isWarningAccepted ||
|
||||
!isAssetNetwork ||
|
||||
values.datatoken === undefined ||
|
||||
values.ocean === undefined
|
||||
}
|
||||
isLoading={isSubmitting}
|
||||
loaderMessage="Swapping tokens..."
|
||||
successMessage="Successfully swapped tokens."
|
||||
actionName={content.action}
|
||||
amount={`${
|
||||
values.type === 'sell' ? values.datatoken : values.ocean
|
||||
}`}
|
||||
action={submitForm}
|
||||
coin={coinFrom}
|
||||
txId={txId}
|
||||
/>
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { FormikContextType, useFormikContext } from 'formik'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { FormTradeData } from '../../../../models/FormTrade'
|
||||
import { useAsset } from '../../../../providers/Asset'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import Token from '../Pool/Token'
|
||||
import styles from './Output.module.css'
|
||||
@ -19,6 +20,7 @@ export default function Output({
|
||||
oceanSymbol: string
|
||||
poolAddress: string
|
||||
}): ReactElement {
|
||||
const { isAssetNetwork } = useAsset()
|
||||
const { ocean } = useOcean()
|
||||
const [maxOutput, setMaxOutput] = useState<string>()
|
||||
const [swapFee, setSwapFee] = useState<string>()
|
||||
@ -28,7 +30,7 @@ export default function Output({
|
||||
|
||||
// Get swap fee
|
||||
useEffect(() => {
|
||||
if (!ocean || !poolAddress) return
|
||||
if (!ocean || !poolAddress || !isAssetNetwork) return
|
||||
|
||||
async function getSwapFee() {
|
||||
const swapFee = await ocean.pool.getSwapFee(poolAddress)
|
||||
@ -49,11 +51,11 @@ export default function Output({
|
||||
setSwapFeeValue(value.toString())
|
||||
}
|
||||
getSwapFee()
|
||||
}, [ocean, poolAddress, values])
|
||||
}, [ocean, poolAddress, values, isAssetNetwork])
|
||||
|
||||
// Get output values
|
||||
useEffect(() => {
|
||||
if (!ocean || !poolAddress) return
|
||||
if (!ocean || !poolAddress || !isAssetNetwork) return
|
||||
|
||||
async function getOutput() {
|
||||
// Minimum received
|
||||
@ -73,7 +75,7 @@ export default function Output({
|
||||
setMaxOutput(maxPrice)
|
||||
}
|
||||
getOutput()
|
||||
}, [ocean, poolAddress, values])
|
||||
}, [ocean, poolAddress, values, isAssetNetwork])
|
||||
|
||||
return (
|
||||
<div className={styles.output}>
|
||||
|
@ -51,7 +51,7 @@ export default function PriceImpact({
|
||||
|
||||
return (
|
||||
<div className={styles.priceImpact}>
|
||||
<strong>Price impact</strong>
|
||||
<strong>Price Impact</strong>
|
||||
<div>
|
||||
<span
|
||||
className={`${styles.number} ${
|
||||
|
@ -6,10 +6,10 @@ import Button from '../../../atoms/Button'
|
||||
import { ReactComponent as Arrow } from '../../../../images/arrow.svg'
|
||||
import { FormikContextType, useFormikContext } from 'formik'
|
||||
import { PoolBalance } from '../../../../@types/TokenBalance'
|
||||
import Output from './Output'
|
||||
import Slippage from './Slippage'
|
||||
import { FormTradeData, TradeItem } from '../../../../models/FormTrade'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import Output from './Output'
|
||||
import Slippage from './Slippage'
|
||||
import PriceImpact from './PriceImpact'
|
||||
|
||||
import Decimal from 'decimal.js'
|
||||
@ -24,7 +24,8 @@ export default function Swap({
|
||||
balance,
|
||||
price,
|
||||
setMaximumDt,
|
||||
setMaximumOcean
|
||||
setMaximumOcean,
|
||||
setCoin
|
||||
}: {
|
||||
ddo: DDO
|
||||
maxDt: string
|
||||
@ -33,6 +34,7 @@ export default function Swap({
|
||||
price: BestPrice
|
||||
setMaximumDt: (value: string) => void
|
||||
setMaximumOcean: (value: string) => void
|
||||
setCoin: (value: string) => void
|
||||
}): ReactElement {
|
||||
const { ocean, config } = useOcean()
|
||||
const [oceanItem, setOceanItem] = useState<TradeItem>({
|
||||
@ -53,63 +55,82 @@ export default function Swap({
|
||||
validateForm
|
||||
}: FormikContextType<FormTradeData> = useFormikContext()
|
||||
|
||||
/// Values used for calculation of price impact
|
||||
// Values used for calculation of price impact
|
||||
const [spotPrice, setSpotPrice] = useState<string>()
|
||||
const [totalValue, setTotalValue] = useState<string>()
|
||||
const [tokenAmount, setTokenAmount] = useState<string>()
|
||||
///
|
||||
|
||||
useEffect(() => {
|
||||
if (!ddo || !balance || !values || !price) return
|
||||
if (!ddo || !balance || !values?.type || !price) return
|
||||
|
||||
async function calculateMaximum() {
|
||||
const dtAmount = values.type === 'buy' ? maxDt : balance.datatoken
|
||||
const oceanAmount = values.type === 'buy' ? balance.ocean : maxOcean
|
||||
const dtAmount =
|
||||
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(
|
||||
price.address,
|
||||
dtAmount.toString()
|
||||
`${dtAmount.toString()}`
|
||||
)
|
||||
const maxBuyDt = await ocean.pool.getDTReceived(
|
||||
price.address,
|
||||
oceanAmount.toString()
|
||||
`${oceanAmount.toString()}`
|
||||
)
|
||||
|
||||
const maximumDt =
|
||||
values.type === 'buy'
|
||||
? Number(dtAmount) > Number(maxBuyDt)
|
||||
? new Decimal(maxBuyDt)
|
||||
: new Decimal(dtAmount)
|
||||
: Number(dtAmount) > Number(balance.datatoken)
|
||||
? new Decimal(balance.datatoken)
|
||||
: new Decimal(dtAmount)
|
||||
? dtAmount.greaterThan(new Decimal(maxBuyDt))
|
||||
? maxBuyDt
|
||||
: dtAmount
|
||||
: dtAmount.greaterThan(new Decimal(balance.datatoken))
|
||||
? balance.datatoken
|
||||
: dtAmount
|
||||
|
||||
const maximumOcean =
|
||||
values.type === 'sell'
|
||||
? Number(oceanAmount) > Number(maxBuyOcean)
|
||||
? new Decimal(maxBuyOcean)
|
||||
: new Decimal(oceanAmount)
|
||||
: Number(oceanAmount) > Number(balance.ocean)
|
||||
? new Decimal(balance.ocean)
|
||||
: new Decimal(oceanAmount)
|
||||
? oceanAmount.greaterThan(new Decimal(maxBuyOcean))
|
||||
? maxBuyOcean
|
||||
: oceanAmount
|
||||
: oceanAmount.greaterThan(new Decimal(balance.ocean))
|
||||
? balance.ocean
|
||||
: oceanAmount
|
||||
|
||||
setMaximumDt(maximumDt.toString())
|
||||
setMaximumOcean(maximumOcean.toString())
|
||||
setOceanItem({
|
||||
...oceanItem,
|
||||
|
||||
setOceanItem((prevState) => ({
|
||||
...prevState,
|
||||
amount: oceanAmount.toString(),
|
||||
maxAmount: maximumOcean.toString()
|
||||
})
|
||||
setDtItem({
|
||||
...dtItem,
|
||||
}))
|
||||
|
||||
setDtItem((prevState) => ({
|
||||
...prevState,
|
||||
amount: dtAmount.toString(),
|
||||
maxAmount: maximumDt.toString()
|
||||
})
|
||||
}))
|
||||
}
|
||||
calculateMaximum()
|
||||
}, [ddo, maxOcean, maxDt, balance, price?.value, values.type])
|
||||
}, [
|
||||
ddo,
|
||||
maxOcean,
|
||||
maxDt,
|
||||
balance,
|
||||
price,
|
||||
values?.type,
|
||||
ocean,
|
||||
setMaximumDt,
|
||||
setMaximumOcean
|
||||
])
|
||||
|
||||
const switchTokens = () => {
|
||||
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
|
||||
setFieldValue('datatoken', 0)
|
||||
setFieldValue('ocean', 0)
|
||||
|
@ -12,15 +12,17 @@ import { useSiteMetadata } from '../hooks/useSiteMetadata'
|
||||
|
||||
interface UserPreferencesValue {
|
||||
debug: boolean
|
||||
currency: string
|
||||
locale: string
|
||||
chainIds: number[]
|
||||
bookmarks: string[]
|
||||
setChainIds: (chainIds: number[]) => void
|
||||
setDebug: (value: boolean) => void
|
||||
currency: string
|
||||
setCurrency: (value: string) => void
|
||||
chainIds: number[]
|
||||
setChainIds: (chainIds: number[]) => void
|
||||
bookmarks: string[]
|
||||
addBookmark: (did: string) => void
|
||||
removeBookmark: (did: string) => void
|
||||
infiniteApproval: boolean
|
||||
setInfiniteApproval: (value: boolean) => void
|
||||
locale: string
|
||||
}
|
||||
|
||||
const UserPreferencesContext = createContext(null)
|
||||
@ -58,11 +60,14 @@ function UserPreferencesProvider({
|
||||
const [chainIds, setChainIds] = useState(
|
||||
localStorage?.chainIds || appConfig.chainIds
|
||||
)
|
||||
const [infiniteApproval, setInfiniteApproval] = useState(
|
||||
localStorage?.infiniteApproval || false
|
||||
)
|
||||
|
||||
// Write values to localStorage on change
|
||||
useEffect(() => {
|
||||
setLocalStorage({ chainIds, debug, currency, bookmarks })
|
||||
}, [chainIds, debug, currency, bookmarks])
|
||||
setLocalStorage({ chainIds, debug, currency, bookmarks, infiniteApproval })
|
||||
}, [chainIds, debug, currency, bookmarks, infiniteApproval])
|
||||
|
||||
// Set ocean.js log levels, default: Error
|
||||
useEffect(() => {
|
||||
@ -108,6 +113,8 @@ function UserPreferencesProvider({
|
||||
locale,
|
||||
chainIds,
|
||||
bookmarks,
|
||||
infiniteApproval,
|
||||
setInfiniteApproval,
|
||||
setChainIds,
|
||||
setDebug,
|
||||
setCurrency,
|
||||
|
Loading…
Reference in New Issue
Block a user