Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(swap): Move calculations to swap package #2697

Merged
merged 21 commits into from
Sep 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {formatTokenWithText} from '../../../../legacy/format'
import {COLORS} from '../../../../theme'
import {YoroiWallet} from '../../../../yoroi-wallets/cardano/types'
import {useTokenInfo} from '../../../../yoroi-wallets/hooks'
import {Quantities} from '../../../../yoroi-wallets/utils'

type Props = {
label?: string
Expand Down Expand Up @@ -48,7 +49,7 @@ export const AmountCard = ({
const noTokenSelected = !touched

const name = tokenInfo.ticker ?? tokenInfo.name
const formattedAmount = noTokenSelected ? '0' : formatTokenWithText(quantity, tokenInfo, 18)
const formattedAmount = noTokenSelected ? Quantities.zero : formatTokenWithText(quantity, tokenInfo, 18)
const fallback = React.useCallback(() => <TokenIconPlaceholder />, [])

const focusInput = () => {
Expand All @@ -75,6 +76,7 @@ export const AmountCard = ({
underlineColorAndroid="transparent"
ref={amountInputRef}
editable={inputEditable}
selectTextOnFocus
/>
</Pressable>

Expand Down
29 changes: 0 additions & 29 deletions apps/wallet-mobile/src/features/Swap/common/helpers.test.ts

This file was deleted.

32 changes: 0 additions & 32 deletions apps/wallet-mobile/src/features/Swap/common/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,9 @@
import {Swap} from '@yoroi/types'
import {BalanceQuantity} from '@yoroi/types/lib/balance/token'
import BigNumber from 'bignumber.js'

import {YoroiWallet} from '../../../yoroi-wallets/cardano/types'
import {YoroiEntry} from '../../../yoroi-wallets/types'
import {Quantities} from '../../../yoroi-wallets/utils'

export const getBuyQuantityForLimitOrder = (
sellQuantityDenominated: BalanceQuantity,
limitPrice: BalanceQuantity,
buyTokenDecimals: number,
): BalanceQuantity => {
if (Quantities.isZero(limitPrice)) {
return Quantities.zero
}

return Quantities.integer(
Quantities.quotient(sellQuantityDenominated, limitPrice),
buyTokenDecimals,
).toString() as BalanceQuantity
}

export const getSellQuantityForLimitOrder = (
buyQuantityDenominated: BalanceQuantity,
limitPrice: BalanceQuantity,
sellTokenDecimals: number,
): BalanceQuantity => {
if (Quantities.isZero(limitPrice)) {
return Quantities.zero
}

return Quantities.integer(
BigNumber(buyQuantityDenominated).times(BigNumber(limitPrice)).toString() as BalanceQuantity,
sellTokenDecimals,
).toString() as BalanceQuantity
}

export const createYoroiEntry = (
createOrder: Swap.CreateOrderData,
address: string,
Expand Down
3 changes: 2 additions & 1 deletion apps/wallet-mobile/src/features/Swap/common/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export const mocks = {
},
datum: '',
datumHash: '',
limitPrice: undefined,
limitPrice: '0.089' as `${number}`,
marketPrice: '0.089' as `${number}`,
selectedPool: {
batcherFee: {quantity: asQuantity(2500000), tokenId: ''},
deposit: {quantity: asQuantity(2000000), tokenId: ''},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {makeLimitOrder, makePossibleMarketOrder, useSwap, useSwapCreateOrder, useSwapPoolsByPair} from '@yoroi/swap'
import {Swap} from '@yoroi/types'
import BigNumber from 'bignumber.js'
import React, {useEffect, useState} from 'react'
import React, {useEffect, useMemo, useState} from 'react'
import {KeyboardAvoidingView, Platform, StyleSheet, View, ViewProps} from 'react-native'
import {ScrollView} from 'react-native-gesture-handler'

Expand Down Expand Up @@ -46,13 +46,20 @@ export const CreateOrder = () => {
tokenB: createOrder.amounts.buy.tokenId ?? '',
})

const bestPool = useMemo(() => {
if (poolList !== undefined && poolList.length > 0) {
return poolList.sort((a, b) => a.price - b.price).find(() => true)
}
return undefined
}, [poolList])

useEffect(() => {
if (poolList !== undefined) {
const bestPool = poolList.map((a) => a).sort((a, b) => a.price - b.price)[0]
if (bestPool?.poolId !== undefined) {
selectedPoolChanged(bestPool)
poolDefaulted()
}
}, [poolDefaulted, selectedPoolChanged, poolList])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [poolDefaulted, selectedPoolChanged, bestPool?.poolId])

const {createUnsignedTx, isLoading} = useSwapTx({
onSuccess: (yoroiUnsignedTx) => {
Expand Down Expand Up @@ -87,8 +94,10 @@ export const CreateOrder = () => {
(createOrder.type === 'limit' && createOrder.limitPrice !== undefined && Quantities.isZero(createOrder.limitPrice))

const swap = () => {
const sellTokenInfo = tokenInfos.filter((tokenInfo) => tokenInfo.id === createOrder.amounts.sell.tokenId)[0]
const buyTokenInfo = tokenInfos.filter((tokenInfo) => tokenInfo.id === createOrder.amounts.buy.tokenId)[0]
const sellTokenInfo = tokenInfos.find((tokenInfo) => tokenInfo.id === createOrder.amounts.sell.tokenId)
const buyTokenInfo = tokenInfos.find((tokenInfo) => tokenInfo.id === createOrder.amounts.buy.tokenId)

if (!sellTokenInfo || !buyTokenInfo) return

track.swapOrderSelected({
from_asset: [
Expand Down Expand Up @@ -129,6 +138,7 @@ export const CreateOrder = () => {
)
if (orderResult) createSwapOrder(orderResult)
}

if (createOrder.type === 'limit') {
const orderResult = makeLimitOrder(
orderDetails.sell,
Expand All @@ -155,14 +165,7 @@ export const CreateOrder = () => {

const handleOnSwap = () => {
if (createOrder.type === 'limit' && createOrder.limitPrice !== undefined) {
const marketPrice = BigNumber(
isBuyTouched &&
isSellTouched &&
createOrder.selectedPool?.price !== undefined &&
!Number.isNaN(createOrder.selectedPool.price)
? createOrder.selectedPool.price
: 0,
)
const marketPrice = BigNumber(createOrder.marketPrice)
const limitPrice = BigNumber(createOrder.limitPrice)

if (limitPrice.isGreaterThan(marketPrice.times(1 + LIMIT_PRICE_WARNING_THRESHOLD))) {
Expand All @@ -181,9 +184,7 @@ export const CreateOrder = () => {
<LimitPriceWarning
open={showLimitPriceWarning}
onClose={() => setShowLimitPriceWarning(false)}
onSubmit={() => {
handleOnSwap()
}}
onSubmit={handleOnSwap}
/>

<KeyboardAvoidingView
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {getSellAmountByChangingBuy, useSwap} from '@yoroi/swap'
import {BalanceQuantity} from '@yoroi/types/lib/balance/token'
import {useSwap} from '@yoroi/swap'
import * as React from 'react'
import {TextInput} from 'react-native'

Expand All @@ -9,7 +8,6 @@ import {useBalance, useTokenInfo} from '../../../../../../yoroi-wallets/hooks'
import {Logger} from '../../../../../../yoroi-wallets/logging'
import {Quantities} from '../../../../../../yoroi-wallets/utils'
import {AmountCard} from '../../../../common/AmountCard/AmountCard'
import {getSellQuantityForLimitOrder} from '../../../../common/helpers'
import {useNavigateTo} from '../../../../common/navigation'
import {useStrings} from '../../../../common/strings'
import {useSwapTouched} from '../../../../common/SwapFormProvider'
Expand All @@ -21,11 +19,10 @@ export const EditBuyAmount = () => {
const {numberLocale} = useLanguage()
const inputRef = React.useRef<TextInput>(null)

const {createOrder, buyAmountChanged, sellAmountChanged} = useSwap()
const {isBuyTouched} = useSwapTouched()
const {createOrder, buyAmountChanged} = useSwap()
const {isBuyTouched, isSellTouched} = useSwapTouched()
const {tokenId, quantity} = createOrder.amounts.buy
const tokenInfo = useTokenInfo({wallet, tokenId})
const sellTokenInfo = useTokenInfo({wallet, tokenId: createOrder.amounts.sell.tokenId})
const {decimals} = tokenInfo
const balance = useBalance({wallet, tokenId})

Expand All @@ -37,44 +34,14 @@ export const EditBuyAmount = () => {
}
}, [isBuyTouched, quantity, tokenInfo.decimals])

const hasSupply = !Quantities.isGreaterThan(quantity, createOrder?.selectedPool?.tokenB?.quantity ?? `0`)
const hasSupply = !Quantities.isGreaterThan(quantity, createOrder?.selectedPool?.tokenB?.quantity ?? Quantities.zero)
const showError = !Quantities.isZero(quantity) && !hasSupply

const recalculateSellValue = (buyQuantity: BalanceQuantity) => {
if (createOrder.type === 'limit' && createOrder.limitPrice !== undefined) {
const buyQuantityDenominated = Quantities.denominated(buyQuantity, tokenInfo.decimals ?? 0)
sellAmountChanged({
quantity: getSellQuantityForLimitOrder(
buyQuantityDenominated,
createOrder.limitPrice,
sellTokenInfo.decimals ?? 0,
),
tokenId: createOrder.amounts.sell.tokenId,
})
return
}

const {sell} = getSellAmountByChangingBuy(createOrder?.selectedPool, {
quantity: buyQuantity,
tokenId: tokenId,
})
sellAmountChanged({
quantity: sell?.quantity,
tokenId: createOrder.amounts.sell.tokenId,
})
}

React.useEffect(() => {
recalculateSellValue(quantity)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sellTokenInfo?.id])

const onChangeQuantity = (text: string) => {
try {
const [input, quantity] = Quantities.parseFromText(text, decimals ?? 0, numberLocale)
setInputValue(input)
setInputValue(text === '' ? text : input)
buyAmountChanged({tokenId, quantity})
recalculateSellValue(quantity)
} catch (error) {
Logger.error('SwapAmountScreen::onChangeQuantity', error)
}
Expand All @@ -91,7 +58,7 @@ export const EditBuyAmount = () => {
navigateTo={navigate.selectBuyToken}
touched={isBuyTouched}
inputRef={inputRef}
inputEditable={isBuyTouched}
inputEditable={isBuyTouched && isSellTouched}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ const SelectableToken = ({tokenForList, wallet}: SelectableTokenProps) => {
to_asset: [{asset_name: tokenForList.name, asset_ticker: tokenForList.ticker, policy_id: tokenForList.group}],
})
buyTouched()
buyAmountChanged({tokenId: tokenForList.id, quantity: balanceAvailable})
buyAmountChanged({tokenId: tokenForList.id, quantity: Quantities.zero})
navigateTo.startSwap()
closeSearch()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/* eslint-disable react/jsx-newline */
import {useSwap} from '@yoroi/swap'
import BigNumber from 'bignumber.js'
import * as React from 'react'
import {StyleSheet, Text, TextInput, View} from 'react-native'

import {useLanguage} from '../../../../../i18n'
import {useSelectedWallet} from '../../../../../SelectedWallet'
import {COLORS} from '../../../../../theme'
import {useTokenInfo} from '../../../../../yoroi-wallets/hooks'
import {asQuantity, Quantities} from '../../../../../yoroi-wallets/utils'
import {getBuyQuantityForLimitOrder} from '../../../common/helpers'
import {Quantities} from '../../../../../yoroi-wallets/utils'
import {useStrings} from '../../../common/strings'
import {useSwapTouched} from '../../../common/SwapFormProvider'

Expand All @@ -18,9 +18,10 @@ const PRECISION = 10
export const EditLimitPrice = () => {
const strings = useStrings()
const {numberLocale} = useLanguage()
const [text, setText] = React.useState('')
const wallet = useSelectedWallet()

const {createOrder, limitPriceChanged, buyAmountChanged} = useSwap()
const {createOrder, limitPriceChanged} = useSwap()
const sellTokenInfo = useTokenInfo({wallet, tokenId: createOrder.amounts.sell.tokenId})
const buyTokenInfo = useTokenInfo({wallet, tokenId: createOrder.amounts.buy.tokenId})
const disabled = createOrder.type === 'market'
Expand All @@ -31,51 +32,37 @@ export const EditLimitPrice = () => {
const tokenToBuyName = isBuyTouched ? buyTokenInfo.ticker ?? buyTokenInfo.name : '-'

React.useEffect(() => {
const defaultPrice =
isBuyTouched &&
isSellTouched &&
createOrder?.selectedPool?.price !== undefined &&
!Number.isNaN(createOrder.selectedPool.price)
? createOrder.selectedPool.price
: 0

limitPriceChanged(`${defaultPrice}`)

const sellQuantityDenominated = Quantities.denominated(
createOrder.amounts.sell.quantity,
sellTokenInfo.decimals ?? 0,
)
buyAmountChanged({
quantity: getBuyQuantityForLimitOrder(
sellQuantityDenominated,
asQuantity(defaultPrice ?? 0),
buyTokenInfo.decimals ?? 0,
),
tokenId: createOrder.amounts.buy.tokenId,
})
const defaultPrice = createOrder.marketPrice

const formattedValue = BigNumber(defaultPrice).toFormat(numberLocale)
setText(formattedValue)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isBuyTouched, isSellTouched, sellTokenInfo.id, buyTokenInfo.id, createOrder?.selectedPool?.price, numberLocale])
}, [createOrder.marketPrice])

React.useEffect(() => {
if (createOrder.type === 'limit') return

const defaultPrice = createOrder.marketPrice

const formattedValue = BigNumber(defaultPrice).toFormat(numberLocale)
setText(formattedValue)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [createOrder.type])

const onChange = (text: string) => {
const [_, quantity] = Quantities.parseFromText(text, PRECISION, numberLocale)
const value = Quantities.denominated(quantity, PRECISION)
const sellQuantityDenominated = Quantities.denominated(
createOrder.amounts.sell.quantity,
sellTokenInfo.decimals ?? 0,
)
const [formattedPrice, price] = Quantities.parseFromText(text, PRECISION, numberLocale)
const value = Quantities.denominated(price, PRECISION)

setText(formattedPrice)
limitPriceChanged(value)
buyAmountChanged({
quantity: getBuyQuantityForLimitOrder(sellQuantityDenominated, value, buyTokenInfo.decimals ?? 0),
tokenId: createOrder.amounts.buy.tokenId,
})
}

return (
<View style={[styles.container, disabled && styles.disabled]}>
<Text style={styles.label}>{disabled ? strings.marketPrice : strings.limitPrice}</Text>

<View style={styles.content}>
<AmountInput onChange={onChange} value={createOrder.limitPrice} editable={!disabled} />
<AmountInput onChange={onChange} value={text} editable={!disabled} />

<View style={[styles.textWrapper, disabled && styles.disabled]}>
<Text style={styles.text}>
Expand Down
Loading