|
|
|
@@ -1,6 +1,5 @@
|
|
|
|
|
import { BeeModes } from '@ethersphere/bee-js'
|
|
|
|
|
import { Box, Typography } from '@material-ui/core'
|
|
|
|
|
import BigNumber from 'bignumber.js'
|
|
|
|
|
import { useSnackbar } from 'notistack'
|
|
|
|
|
import { ReactElement, useContext, useEffect, useState } from 'react'
|
|
|
|
|
import { useNavigate } from 'react-router'
|
|
|
|
@@ -14,11 +13,11 @@ import { Loading } from '../../components/Loading'
|
|
|
|
|
import { SwarmButton } from '../../components/SwarmButton'
|
|
|
|
|
import { SwarmDivider } from '../../components/SwarmDivider'
|
|
|
|
|
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
|
|
|
|
import { BzzToken } from '../../models/BzzToken'
|
|
|
|
|
import { BzzToken, BZZ_DECIMAL_PLACES } from '../../models/BzzToken'
|
|
|
|
|
import { DaiToken } from '../../models/DaiToken'
|
|
|
|
|
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
|
|
|
|
import { Context as BeeContext } from '../../providers/Bee'
|
|
|
|
|
import { Context as SettingsContext } from '../../providers/Settings'
|
|
|
|
|
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
|
|
|
|
import { ROUTES } from '../../routes'
|
|
|
|
|
import { sleepMs } from '../../utils'
|
|
|
|
|
import { getBzzPriceAsDai, performSwap, restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
|
|
|
|
@@ -31,19 +30,15 @@ interface Props {
|
|
|
|
|
header: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isPositiveDecimal(value: string): boolean {
|
|
|
|
|
try {
|
|
|
|
|
return new BigNumber(value).isPositive()
|
|
|
|
|
} catch {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function Swap({ header }: Props): ReactElement {
|
|
|
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
|
const [hasSwapped, setSwapped] = useState(false)
|
|
|
|
|
const [userInputSwap, setUserInputSwap] = useState<string | null>(null)
|
|
|
|
|
const [price, setPrice] = useState(DaiToken.fromDecimal('0.6', 18))
|
|
|
|
|
const [price, setPrice] = useState(DaiToken.fromDecimal('0.6'))
|
|
|
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
|
const [daiToSwap, setDaiToSwap] = useState<DaiToken | null>(null)
|
|
|
|
|
const [bzzAfterSwap, setBzzAfterSwap] = useState<BzzToken | null>(null)
|
|
|
|
|
const [daiAfterSwap, setDaiAfterSwap] = useState<DaiToken | null>(null)
|
|
|
|
|
|
|
|
|
|
const { rpcProviderUrl, isDesktop, desktopUrl } = useContext(SettingsContext)
|
|
|
|
|
const { nodeAddresses, nodeInfo } = useContext(BeeContext)
|
|
|
|
@@ -52,29 +47,70 @@ export function Swap({ header }: Props): ReactElement {
|
|
|
|
|
const navigate = useNavigate()
|
|
|
|
|
const { enqueueSnackbar } = useSnackbar()
|
|
|
|
|
|
|
|
|
|
// Fetch current price of BZZ
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
getBzzPriceAsDai(desktopUrl).then(setPrice).catch(console.error)
|
|
|
|
|
}, [desktopUrl])
|
|
|
|
|
|
|
|
|
|
if (!balance || !nodeAddresses) {
|
|
|
|
|
// Set the initial xDAI to swap
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!balance) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const minimumOptimalValue = DaiToken.fromDecimal('1').plusBaseUnits(MINIMUM_XDAI).toDecimal
|
|
|
|
|
|
|
|
|
|
if (balance.dai.toDecimal.isGreaterThanOrEqualTo(minimumOptimalValue)) {
|
|
|
|
|
// Balance has at least 1 + MINIMUM_XDAI xDai
|
|
|
|
|
setDaiToSwap(balance.dai.minusBaseUnits('1'))
|
|
|
|
|
} else {
|
|
|
|
|
// Balance is low, halve the amount
|
|
|
|
|
setDaiToSwap(new DaiToken(balance.dai.toBigNumber.dividedToIntegerBy(2)))
|
|
|
|
|
}
|
|
|
|
|
}, [balance])
|
|
|
|
|
|
|
|
|
|
// Set the xDAI to swap based on user input
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setError(null)
|
|
|
|
|
try {
|
|
|
|
|
if (userInputSwap) {
|
|
|
|
|
const dai = DaiToken.fromDecimal(userInputSwap)
|
|
|
|
|
setDaiToSwap(dai)
|
|
|
|
|
|
|
|
|
|
if (dai.toDecimal.lte(0)) {
|
|
|
|
|
setError('xDAI to swap must be a positive number')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
setError('Cannot parse xDAI amount')
|
|
|
|
|
}
|
|
|
|
|
}, [userInputSwap])
|
|
|
|
|
|
|
|
|
|
// Calculate the amount of tokens after swap
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!balance || !daiToSwap || error) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const daiAfterSwap = new DaiToken(balance.dai.toBigNumber.minus(daiToSwap.toBigNumber))
|
|
|
|
|
setDaiAfterSwap(daiAfterSwap)
|
|
|
|
|
const tokensConverted = BzzToken.fromDecimal(
|
|
|
|
|
daiToSwap.toBigNumber.dividedBy(price.toBigNumber).decimalPlaces(BZZ_DECIMAL_PLACES),
|
|
|
|
|
)
|
|
|
|
|
const bzzAfterSwap = new BzzToken(tokensConverted.toBigNumber.plus(balance.bzz.toBigNumber))
|
|
|
|
|
setBzzAfterSwap(bzzAfterSwap)
|
|
|
|
|
|
|
|
|
|
if (daiAfterSwap.toDecimal.lt(MINIMUM_XDAI)) {
|
|
|
|
|
setError(`Must keep at least ${MINIMUM_XDAI} xDAI after swap!`)
|
|
|
|
|
} else if (bzzAfterSwap.toDecimal.lt(MINIMUM_XBZZ)) {
|
|
|
|
|
setError(`Must have at least ${MINIMUM_XBZZ} xBZZ after swap!`)
|
|
|
|
|
}
|
|
|
|
|
}, [error, balance, daiToSwap, price])
|
|
|
|
|
|
|
|
|
|
if (!balance || !nodeAddresses || !daiToSwap || !bzzAfterSwap || !daiAfterSwap) {
|
|
|
|
|
return <Loading />
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const optimalSwap = balance.dai.minusBaseUnits('1')
|
|
|
|
|
const lowAmountSwap = new DaiToken(balance.dai.toBigNumber.dividedToIntegerBy(2))
|
|
|
|
|
|
|
|
|
|
let daiToSwap: DaiToken
|
|
|
|
|
|
|
|
|
|
if (userInputSwap && isPositiveDecimal(userInputSwap)) {
|
|
|
|
|
daiToSwap = DaiToken.fromDecimal(userInputSwap, 18)
|
|
|
|
|
} else {
|
|
|
|
|
daiToSwap = lowAmountSwap.toBigNumber.gt(optimalSwap.toBigNumber) ? lowAmountSwap : optimalSwap
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const daiAfterSwap = new DaiToken(balance.dai.toBigNumber.minus(daiToSwap.toBigNumber))
|
|
|
|
|
const bzzAfterSwap = new BzzToken(daiToSwap.toBigNumber.dividedBy(100).dividedToIntegerBy(price.toDecimal))
|
|
|
|
|
|
|
|
|
|
const canUpgradeToLightNode = isDesktop && nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT
|
|
|
|
|
|
|
|
|
|
async function restart() {
|
|
|
|
@@ -92,7 +128,7 @@ export function Swap({ header }: Props): ReactElement {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onSwap() {
|
|
|
|
|
if (hasSwapped) {
|
|
|
|
|
if (hasSwapped || !daiToSwap) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
setLoading(true)
|
|
|
|
@@ -100,7 +136,10 @@ export function Swap({ header }: Props): ReactElement {
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await performSwap(desktopUrl, daiToSwap.toString)
|
|
|
|
|
enqueueSnackbar('Successfully swapped', { variant: 'success' })
|
|
|
|
|
const message = canUpgradeToLightNode
|
|
|
|
|
? 'Successfully swapped. Beginning light node upgrade...'
|
|
|
|
|
: 'Successfully swapped. Balances will refresh soon. You may now leave the page.'
|
|
|
|
|
enqueueSnackbar(message, { variant: 'success' })
|
|
|
|
|
|
|
|
|
|
if (canUpgradeToLightNode) await restart()
|
|
|
|
|
} catch (error) {
|
|
|
|
@@ -136,18 +175,13 @@ export function Swap({ header }: Props): ReactElement {
|
|
|
|
|
</Box>
|
|
|
|
|
<Box mb={4}>
|
|
|
|
|
<SwarmTextInput
|
|
|
|
|
label="Amount to swap"
|
|
|
|
|
defaultValue={`${daiToSwap.toSignificantDigits(4)} xDAI`}
|
|
|
|
|
placeholder={`${daiToSwap.toSignificantDigits(4)} xDAI`}
|
|
|
|
|
label="xDAI to swap"
|
|
|
|
|
defaultValue={daiToSwap.toSignificantDigits(4)}
|
|
|
|
|
placeholder={daiToSwap.toSignificantDigits(4)}
|
|
|
|
|
name="x"
|
|
|
|
|
onChange={event => setUserInputSwap(event.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
{daiAfterSwap.toDecimal.lt(MINIMUM_XDAI) ? (
|
|
|
|
|
<Typography>Must keep at least {MINIMUM_XDAI} xDAI after swap!</Typography>
|
|
|
|
|
) : null}
|
|
|
|
|
{bzzAfterSwap.toDecimal.lt(MINIMUM_XBZZ) ? (
|
|
|
|
|
<Typography>Must have at least {MINIMUM_XBZZ} xBZZ after swap!</Typography>
|
|
|
|
|
) : null}
|
|
|
|
|
{error && <Typography>{error}</Typography>}
|
|
|
|
|
</Box>
|
|
|
|
|
<Box mb={4}>
|
|
|
|
|
<ArrowDown size={24} color="#aaaaaa" />
|
|
|
|
@@ -171,9 +205,7 @@ export function Swap({ header }: Props): ReactElement {
|
|
|
|
|
<SwarmButton
|
|
|
|
|
iconType={Check}
|
|
|
|
|
onClick={onSwap}
|
|
|
|
|
disabled={
|
|
|
|
|
hasSwapped || loading || daiAfterSwap.toDecimal.lt(MINIMUM_XDAI) || bzzAfterSwap.toDecimal.lt(MINIMUM_XBZZ)
|
|
|
|
|
}
|
|
|
|
|
disabled={hasSwapped || loading || error !== null}
|
|
|
|
|
loading={loading}
|
|
|
|
|
>
|
|
|
|
|
{canUpgradeToLightNode ? 'Swap Now and Upgrade' : 'Swap Now'}
|
|
|
|
|