Skip to content

Commit

Permalink
Merge branch 'develop' into feat_swapper_rates_quotes_split_2
Browse files Browse the repository at this point in the history
  • Loading branch information
0xApotheosis authored Oct 31, 2024
2 parents 787c525 + fb20a69 commit 425f1aa
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 102 deletions.
8 changes: 4 additions & 4 deletions packages/caip/src/adapters/banxa/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import entries from 'lodash/entries'
import toLower from 'lodash/toLower'

import type { AssetId } from '../../assetId/assetId'
import type { ChainId } from '../../chainId/chainId'
Expand All @@ -24,6 +23,7 @@ import {
optimismChainId,
polygonAssetId,
polygonChainId,
solanaChainId,
solAssetId,
thorchainAssetId,
thorchainChainId,
Expand Down Expand Up @@ -106,8 +106,8 @@ export const getSupportedBanxaAssets = () =>
ticker,
}))

export const assetIdToBanxaTicker = (assetId: string): string | undefined =>
AssetIdToBanxaTickerMap[toLower(assetId)]
export const assetIdToBanxaTicker = (assetId: AssetId): string | undefined =>
AssetIdToBanxaTickerMap[assetId]

/**
* map ChainIds to Banxa blockchain codes (ETH, BTC, COSMOS),
Expand All @@ -129,7 +129,7 @@ const chainIdToBanxaBlockchainCodeMap: Record<ChainId, string> = {
[arbitrumChainId]: 'ARB',
[baseChainId]: 'BASE',
[thorchainChainId]: 'THORCHAIN',
[solAssetId]: 'SOL',
[solanaChainId]: 'SOL',
} as const

/**
Expand Down
10 changes: 9 additions & 1 deletion src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,15 @@
"previewOrder": "Preview Order",
"youGet": "You Get",
"whenPriceReaches": "When price reaches",
"expiry": "Expiry"
"expiry": "Expiry",
"expiryOption": {
"oneHour": "1 hour",
"oneDay": "1 day",
"threeDays": "3 days",
"sevenDays": "7 days",
"twentyEightDays": "28 days",
"custom": "Custom"
}
},
"modals": {
"assetSearch": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const TradeAssetSearchModalBase: FC<AssetSearchModalBaseProps> = ({
<TradeAssetSearch
onAssetClick={handleAssetClick}
allowWalletUnsupportedAssets={allowWalletUnsupportedAssets}
isSwapper={true}
/>
</Dialog>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,71 @@ import {
} from '@chakra-ui/react'
import type { Asset } from '@shapeshiftoss/types'
import { bn, bnOrZero } from '@shapeshiftoss/utils'
import { noop } from 'lodash'
import { useCallback, useMemo, useState } from 'react'
import { Amount } from 'components/Amount/Amount'
import { useCallback, useMemo, useRef, useState } from 'react'
import type { NumberFormatValues } from 'react-number-format'
import NumberFormat from 'react-number-format'
import { StyledAssetMenuButton } from 'components/AssetSelection/components/AssetMenuButton'
import { SwapIcon } from 'components/Icons/SwapIcon'
import { RawText, Text } from 'components/Text'
import { Text } from 'components/Text'
import { useLocaleFormatter } from 'hooks/useLocaleFormatter/useLocaleFormatter'
import { assertUnreachable } from 'lib/utils'
import { allowedDecimalSeparators } from 'state/slices/preferencesSlice/preferencesSlice'

const EXPIRY_TIME_PERIODS = ['1 hour', '1 day', '3 days', '7 days', '28 days'] as const
import { AmountInput } from '../../TradeAmountInput'

enum PriceDirection {
Default = 'default',
Reversed = 'reversed',
}

enum PresetLimit {
Market = 'market',
OnePercent = 'onePercent',
TwoPercent = 'twoPercent',
FivePercent = 'fivePercent',
TenPercent = 'tenPercent',
}

enum ExpiryOption {
OneHour = 'oneHour',
OneDay = 'oneDay',
ThreeDays = 'threeDays',
SevenDays = 'sevenDays',
TwentyEightDays = 'twentyEightDays',
// TODO: implement custom expiry
// Custom = 'custom',
}

const EXPIRY_OPTIONS = [
ExpiryOption.OneHour,
ExpiryOption.OneDay,
ExpiryOption.ThreeDays,
ExpiryOption.SevenDays,
ExpiryOption.TwentyEightDays,
] as const

const getExpiryOptionTranslation = (expiryOption: ExpiryOption) => {
switch (expiryOption) {
case ExpiryOption.OneHour:
return `limitOrder.expiryOption.${expiryOption}`
case ExpiryOption.OneDay:
return `limitOrder.expiryOption.${expiryOption}`
case ExpiryOption.ThreeDays:
return `limitOrder.expiryOption.${expiryOption}`
case ExpiryOption.SevenDays:
return `limitOrder.expiryOption.${expiryOption}`
case ExpiryOption.TwentyEightDays:
return `limitOrder.expiryOption.${expiryOption}`
// TODO: implement custom expiry
// case ExpiryOption.Custom:
// return `limitOrder.expiryOption.${expiryOption}`
default:
assertUnreachable(expiryOption)
}
}

const timePeriodRightIcon = <ChevronDownIcon />
const swapIcon = <SwapIcon />

const swapPriceButtonProps = { pr: 4 }

type LimitOrderConfigProps = {
Expand All @@ -34,91 +87,150 @@ type LimitOrderConfigProps = {
setLimitPriceBuyAssetCryptoPrecision: (priceBuyAssetCryptoPrecision: string) => void
}

enum PriceDirection {
Sell = 'sell',
Buy = 'buy',
}

enum PresetLimit {
Market = 'market',
OnePercent = 'onePercent',
TwoPercent = 'twoPercent',
FivePercent = 'fivePercent',
TenPercent = 'tenPercent',
}

export const LimitOrderConfig = ({
sellAsset,
buyAsset,
marketPriceBuyAssetCryptoPrecision,
limitPriceBuyAssetCryptoPrecision,
setLimitPriceBuyAssetCryptoPrecision,
}: LimitOrderConfigProps) => {
const [priceDirection, setPriceDirection] = useState(PriceDirection.Sell)
const [presetLimit, setPresetLimit] = useState(PresetLimit.Market)
const priceAmountRef = useRef<string | null>(null)

const renderedChains = useMemo(() => {
return EXPIRY_TIME_PERIODS.map(timePeriod => {
const [priceDirection, setPriceDirection] = useState(PriceDirection.Default)
const [presetLimit, setPresetLimit] = useState<PresetLimit | undefined>(PresetLimit.Market)
const [expiryOption, setExpiryOption] = useState(ExpiryOption.SevenDays)

const {
number: { localeParts },
} = useLocaleFormatter()

const expiryOptions = useMemo(() => {
return EXPIRY_OPTIONS.map(expiryOption => {
return (
<MenuItemOption value={timePeriod} key={timePeriod}>
<RawText>{timePeriod}</RawText>
<MenuItemOption value={expiryOption} key={expiryOption}>
<Text translation={getExpiryOptionTranslation(expiryOption)} />
</MenuItemOption>
)
})
}, [])

const priceAsset = useMemo(() => {
return priceDirection === PriceDirection.Sell ? sellAsset : buyAsset
return priceDirection === PriceDirection.Default ? buyAsset : sellAsset
}, [buyAsset, priceDirection, sellAsset])

const priceCryptoPrecision = useMemo(() => {
if (bnOrZero(limitPriceBuyAssetCryptoPrecision).isZero()) {
return '0'
}

return priceDirection === PriceDirection.Sell
? bn(1).div(limitPriceBuyAssetCryptoPrecision).toFixed()
: limitPriceBuyAssetCryptoPrecision
}, [limitPriceBuyAssetCryptoPrecision, priceDirection])
// Lower the decimal places when the integer is greater than 8 significant digits for better UI
const priceCryptoFormatted = useMemo(() => {
const cryptoAmountIntegerCount = bnOrZero(
bnOrZero(limitPriceBuyAssetCryptoPrecision).toFixed(0),
).precision(true)

const handleTogglePriceDirection = useCallback(() => {
setPriceDirection(
priceDirection === PriceDirection.Sell ? PriceDirection.Buy : PriceDirection.Sell,
)
}, [priceDirection])
return cryptoAmountIntegerCount <= 8
? limitPriceBuyAssetCryptoPrecision
: bnOrZero(limitPriceBuyAssetCryptoPrecision).toFixed(3)
}, [limitPriceBuyAssetCryptoPrecision])

const arrow = useMemo(() => {
return priceDirection === PriceDirection.Sell ? '↑' : '↓'
return priceDirection === PriceDirection.Default ? '↑' : '↓'
}, [priceDirection])

const handleSetPresetLimit = useCallback(
(presetLimit: PresetLimit, priceDirection: PriceDirection) => {
setPresetLimit(presetLimit)
const multiplier = (() => {
switch (presetLimit) {
case PresetLimit.Market:
return '1.00'
case PresetLimit.OnePercent:
return '1.01'
case PresetLimit.TwoPercent:
return '1.02'
case PresetLimit.FivePercent:
return '1.05'
case PresetLimit.TenPercent:
return '1.10'
default:
assertUnreachable(presetLimit)
}
})()
const adjustedLimitPrice = bn(marketPriceBuyAssetCryptoPrecision).times(multiplier).toFixed()
const maybeReversedPrice =
priceDirection === PriceDirection.Reversed
? bn(1).div(adjustedLimitPrice).toFixed()
: adjustedLimitPrice
setLimitPriceBuyAssetCryptoPrecision(maybeReversedPrice)
},
[marketPriceBuyAssetCryptoPrecision, setLimitPriceBuyAssetCryptoPrecision],
)

const handleSetMarketLimit = useCallback(() => {
setPresetLimit(PresetLimit.Market)
setLimitPriceBuyAssetCryptoPrecision(marketPriceBuyAssetCryptoPrecision)
}, [marketPriceBuyAssetCryptoPrecision, setLimitPriceBuyAssetCryptoPrecision])
handleSetPresetLimit(PresetLimit.Market, priceDirection)
}, [handleSetPresetLimit, priceDirection])

const handleSetOnePercentLimit = useCallback(() => {
setPresetLimit(PresetLimit.OnePercent)
const price = bn(marketPriceBuyAssetCryptoPrecision).div('1.01').toFixed()
setLimitPriceBuyAssetCryptoPrecision(price)
}, [marketPriceBuyAssetCryptoPrecision, setLimitPriceBuyAssetCryptoPrecision])
handleSetPresetLimit(PresetLimit.OnePercent, priceDirection)
}, [handleSetPresetLimit, priceDirection])

const handleSetTwoPercentLimit = useCallback(() => {
setPresetLimit(PresetLimit.TwoPercent)
const price = bn(marketPriceBuyAssetCryptoPrecision).div('1.02').toFixed()
setLimitPriceBuyAssetCryptoPrecision(price)
}, [marketPriceBuyAssetCryptoPrecision, setLimitPriceBuyAssetCryptoPrecision])
handleSetPresetLimit(PresetLimit.TwoPercent, priceDirection)
}, [handleSetPresetLimit, priceDirection])

const handleSetFivePercentLimit = useCallback(() => {
setPresetLimit(PresetLimit.FivePercent)
const price = bn(marketPriceBuyAssetCryptoPrecision).div('1.05').toFixed()
setLimitPriceBuyAssetCryptoPrecision(price)
}, [marketPriceBuyAssetCryptoPrecision, setLimitPriceBuyAssetCryptoPrecision])
handleSetPresetLimit(PresetLimit.FivePercent, priceDirection)
}, [handleSetPresetLimit, priceDirection])

const handleSetTenPercentLimit = useCallback(() => {
setPresetLimit(PresetLimit.TenPercent)
const price = bn(marketPriceBuyAssetCryptoPrecision).div('1.10').toFixed()
setLimitPriceBuyAssetCryptoPrecision(price)
}, [marketPriceBuyAssetCryptoPrecision, setLimitPriceBuyAssetCryptoPrecision])
handleSetPresetLimit(PresetLimit.TenPercent, priceDirection)
}, [handleSetPresetLimit, priceDirection])

const handleTogglePriceDirection = useCallback(() => {
const newPriceDirection =
priceDirection === PriceDirection.Default ? PriceDirection.Reversed : PriceDirection.Default
setPriceDirection(newPriceDirection)

const isCustomLimit = presetLimit === undefined

if (isCustomLimit) {
// For custom limit, just take the reciprocal as we don't know what the original input value was
setLimitPriceBuyAssetCryptoPrecision(bn(1).div(limitPriceBuyAssetCryptoPrecision).toFixed())
} else {
// Otherwise set it to the precise value based on the original market price
handleSetPresetLimit(presetLimit, newPriceDirection)
}
}, [
handleSetPresetLimit,
limitPriceBuyAssetCryptoPrecision,
presetLimit,
priceDirection,
setLimitPriceBuyAssetCryptoPrecision,
])

const handlePriceChange = useCallback(() => {
// onChange will send us the formatted value
// To get around this we need to get the value from the onChange using a ref
// Now when the max buttons are clicked the onChange will not fire
setLimitPriceBuyAssetCryptoPrecision(priceAmountRef.current ?? '0')

// Unset the preset limit, as this is a custom value
setPresetLimit(undefined)
}, [setLimitPriceBuyAssetCryptoPrecision])

const handleValueChange = useCallback(
(values: NumberFormatValues) => {
// This fires anytime value changes including setting it on max click
// Store the value in a ref to send when we actually want the onChange to fire
priceAmountRef.current = values.value
setLimitPriceBuyAssetCryptoPrecision(values.value)
},
[setLimitPriceBuyAssetCryptoPrecision],
)

const expiryOptionTranslation = useMemo(() => {
return getExpiryOptionTranslation(expiryOption)
}, [expiryOption])

const handleChangeExpiryOption = useCallback((newExpiry: string | string[]) => {
setExpiryOption(newExpiry as ExpiryOption)
}, [])

return (
<Stack spacing={4} px={6} py={4}>
Expand All @@ -128,18 +240,33 @@ export const LimitOrderConfig = ({
<Text translation='limitOrder.expiry' mr={4} />
<Menu isLazy>
<MenuButton as={Button} rightIcon={timePeriodRightIcon}>
<RawText>1 hour</RawText>
<Text translation={expiryOptionTranslation} />
</MenuButton>
<MenuList zIndex='modal'>
<MenuOptionGroup type='radio' value={'1 hour'} onChange={noop}>
{renderedChains}
<MenuOptionGroup
type='radio'
value={expiryOption}
onChange={handleChangeExpiryOption}
>
{expiryOptions}
</MenuOptionGroup>
</MenuList>
</Menu>
</Flex>
</Flex>
<HStack width='full' justify='space-between'>
<Amount.Crypto value={priceCryptoPrecision} symbol='' size='lg' fontSize='xl' />
<NumberFormat
customInput={AmountInput}
decimalScale={priceAsset.precision}
isNumericString={true}
decimalSeparator={localeParts.decimal}
inputMode='decimal'
allowedDecimalSeparators={allowedDecimalSeparators}
thousandSeparator={localeParts.group}
value={priceCryptoFormatted}
onValueChange={handleValueChange}
onChange={handlePriceChange}
/>
<StyledAssetMenuButton
rightIcon={swapIcon}
assetId={priceAsset.assetId}
Expand Down
Loading

0 comments on commit 425f1aa

Please sign in to comment.