Skip to content

Commit

Permalink
feat: implement manual receive address to limit orders (#8065)
Browse files Browse the repository at this point in the history
* feat: the Great Receive Address Refactor

* chore: cleanup

* fix: recommit skill issue git rugs

* chore: cleanup fetchUnchainedAddress flag

* chore: cleanup todos

* chore: move limit order recipient address things into useLimitOrderRecipientAddress

* feat: wire up recipientAddress to limit orders input

* feat: move walletReceiveAddress to react-query

* chore: unify naming of sellAccountId within reason

* chore: unify naming of manual receive address flags and setters

* fix: use correct check for metamask

* fix: use runtime support check when deciding whether to show manual address entry

* chore: better naming

* chore: use hook for shouldForceDisplayManualAddressEntry

* fix: invalidate receive address cache when sellAccountId changes

* fix: hide receive address ui when wallet receive address is loading

* fix: corrupted accountId in redux causing receive address dramas

* fix: dont require account number when getting a quote
  • Loading branch information
woodenfurniture authored Nov 8, 2024
1 parent c5b58f9 commit ccdcd4f
Show file tree
Hide file tree
Showing 18 changed files with 875 additions and 625 deletions.
3 changes: 0 additions & 3 deletions packages/swapper/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,13 @@ type CommonTradeInputBase = {

export type CommonTradeQuoteInput = CommonTradeInputBase & {
sendAddress?: string
receiveAccountNumber?: number
receiveAddress: string
accountNumber: number
quoteOrRate: 'quote'
}

type CommonTradeRateInput = CommonTradeInputBase & {
sendAddress?: undefined
receiveAccountNumber?: undefined
receiveAddress: undefined
accountNumber: undefined
quoteOrRate: 'rate'
Expand Down Expand Up @@ -253,7 +251,6 @@ type TradeQuoteBase = {
id: string
rate: string // top-level rate for all steps (i.e. output amount / input amount)
receiveAddress: string | undefined // if receiveAddress is undefined, this is not a trade quote but a trade rate
receiveAccountNumber?: number
potentialAffiliateBps: string // even if the swapper does not support affiliateBps, we need to zero-them out or view-layer will be borked
affiliateBps: string // even if the swapper does not support affiliateBps, we need to zero-them out or view-layer will be borked
isStreaming?: boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Divider, Stack, useMediaQuery } from '@chakra-ui/react'
import { skipToken } from '@reduxjs/toolkit/query'
import { foxAssetId, fromAccountId, usdcAssetId } from '@shapeshiftoss/caip'
import { isLedger } from '@shapeshiftoss/hdwallet-ledger'
import { SwapperName } from '@shapeshiftoss/swapper'
import type { Asset } from '@shapeshiftoss/types'
import { bnOrZero, toBaseUnit } from '@shapeshiftoss/utils'
Expand All @@ -11,7 +10,6 @@ import { useFormContext } from 'react-hook-form'
import { useHistory } from 'react-router'
import type { Address } from 'viem'
import { WarningAcknowledgement } from 'components/Acknowledgement/Acknowledgement'
import { useReceiveAddress } from 'components/MultiHopTrade/hooks/useReceiveAddress'
import { TradeInputTab } from 'components/MultiHopTrade/types'
import { WalletActions } from 'context/WalletProvider/actions'
import { useErrorHandler } from 'hooks/useErrorToast/useErrorToast'
Expand All @@ -38,6 +36,7 @@ import { breakpoints } from 'theme/theme'
import { SharedTradeInput } from '../../SharedTradeInput/SharedTradeInput'
import { SharedTradeInputBody } from '../../SharedTradeInput/SharedTradeInputBody'
import { SharedTradeInputFooter } from '../../SharedTradeInput/SharedTradeInputFooter/SharedTradeInputFooter'
import { useLimitOrderRecipientAddress } from '../hooks/useLimitOrderRecipientAddress'
import { LimitOrderRoutePaths } from '../types'
import { CollapsibleLimitOrderList } from './CollapsibleLimitOrderList'
import { LimitOrderBuyAsset } from './LimitOrderBuyAsset'
Expand All @@ -58,15 +57,12 @@ export const LimitOrderInput = ({
}: LimitOrderInputProps) => {
const {
dispatch: walletDispatch,
state: { isConnected, isDemoWallet, wallet },
state: { isConnected, isDemoWallet },
} = useWallet()

const history = useHistory()
const { handleSubmit } = useFormContext()
const { showErrorToast } = useErrorHandler()
const { manualReceiveAddress, walletReceiveAddress } = useReceiveAddress({
fetchUnchainedAddress: Boolean(wallet && isLedger(wallet)),
})
const [isSmallerThanXl] = useMediaQuery(`(max-width: ${breakpoints.xl})`, { ssr: false })

const [sellAsset, setSellAsset] = useState(localAssetData[usdcAssetId] ?? defaultAsset)
Expand All @@ -76,8 +72,15 @@ export const LimitOrderInput = ({
selectFirstAccountIdByChainId(state, sellAsset.chainId),
)

const [buyAssetAccountId, setBuyAssetAccountId] = useState(defaultAccountId)
const [sellAssetAccountId, setSellAssetAccountId] = useState(defaultAccountId)
const [buyAccountId, setBuyAccountId] = useState(defaultAccountId)
const [sellAccountId, setSellAccountId] = useState(defaultAccountId)

const { isRecipientAddressEntryActive, renderedRecipientAddress, recipientAddress } =
useLimitOrderRecipientAddress({
buyAsset,
buyAccountId,
sellAccountId,
})

const [isInputtingFiatSellAmount, setIsInputtingFiatSellAmount] = useState(false)
const [isConfirmationLoading, setIsConfirmationLoading] = useState(false)
Expand Down Expand Up @@ -213,10 +216,10 @@ export const LimitOrderInput = ({
}, [sellAmountCryptoPrecision, sellAsset.precision])

const sellAccountAddress = useMemo(() => {
if (!sellAssetAccountId) return
if (!sellAccountId) return

return fromAccountId(sellAssetAccountId).account as Address
}, [sellAssetAccountId])
return fromAccountId(sellAccountId).account as Address
}, [sellAccountId])

const limitOrderQuoteParams = useMemo(() => {
// Return skipToken if any required params are missing
Expand All @@ -232,13 +235,15 @@ export const LimitOrderInput = ({
affiliateBps: '0', // TODO: wire this up!
sellAccountAddress,
sellAmountCryptoBaseUnit,
recipientAddress,
}
}, [
buyAsset.assetId,
sellAccountAddress,
sellAmountCryptoBaseUnit,
sellAsset.assetId,
sellAsset.chainId,
recipientAddress,
])

const { data, error } = useQuoteLimitOrderQuery(limitOrderQuoteParams)
Expand Down Expand Up @@ -270,19 +275,19 @@ export const LimitOrderInput = ({
sellAmountCryptoPrecision={sellAmountCryptoPrecision}
sellAmountUserCurrency={sellAmountUserCurrency}
sellAsset={sellAsset}
sellAssetAccountId={sellAssetAccountId}
sellAccountId={sellAccountId}
handleSwitchAssets={handleSwitchAssets}
onChangeIsInputtingFiatSellAmount={setIsInputtingFiatSellAmount}
onChangeSellAmountCryptoPrecision={setSellAmountCryptoPrecision}
setSellAsset={handleSetSellAsset}
setSellAssetAccountId={setSellAssetAccountId}
setSellAccountId={setSellAccountId}
>
<Stack>
<LimitOrderBuyAsset
asset={buyAsset}
accountId={buyAssetAccountId}
accountId={buyAccountId}
isInputtingFiatSellAmount={isInputtingFiatSellAmount}
onAccountIdChange={setBuyAssetAccountId}
onAccountIdChange={setBuyAccountId}
onSetBuyAsset={handleSetBuyAsset}
/>
<Divider />
Expand All @@ -303,12 +308,12 @@ export const LimitOrderInput = ({
sellAmountCryptoPrecision,
sellAmountUserCurrency,
sellAsset,
sellAssetAccountId,
sellAccountId,
handleSwitchAssets,
handleSetSellAsset,
setSellAssetAccountId,
buyAssetAccountId,
setBuyAssetAccountId,
setSellAccountId,
buyAccountId,
setBuyAccountId,
handleSetBuyAsset,
limitPriceBuyAssetCryptoPrecision,
])
Expand All @@ -324,32 +329,30 @@ export const LimitOrderInput = ({
isCompact={isCompact}
isError={false}
isLoading={isLoading}
manualAddressEntryDescription={undefined}
onRateClick={handleOpenCompactQuoteList}
quoteStatusTranslation={'trade.previewTrade'}
rate={activeQuote?.rate}
receiveAddress={manualReceiveAddress ?? walletReceiveAddress}
recipientAddressDescription={undefined}
sellAsset={sellAsset}
sellAssetAccountId={sellAssetAccountId}
shouldDisablePreviewButton={false}
shouldForceManualAddressEntry={false}
sellAccountId={sellAccountId}
shouldDisablePreviewButton={isRecipientAddressEntryActive}
swapperName={SwapperName.CowSwap}
swapSource={SwapperName.CowSwap}
totalNetworkFeeFiatPrecision={'1.1234'}
/>
>
{renderedRecipientAddress}
</SharedTradeInputFooter>
)
}, [
activeQuote?.rate,
buyAsset,
handleOpenCompactQuoteList,
hasUserEnteredAmount,
isCompact,
isLoading,
manualReceiveAddress,
activeQuote?.rate,
sellAsset,
sellAssetAccountId,
walletReceiveAddress,
sellAccountId,
isRecipientAddressEntryActive,
renderedRecipientAddress,
])

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import type { Asset } from '@shapeshiftoss/types'
import { useCallback, useMemo, useState } from 'react'
import type { Address } from 'viem'
import { useIsManualReceiveAddressRequired } from 'components/MultiHopTrade/hooks/useIsManualReceiveAddressRequired'
import { useReceiveAddress } from 'components/MultiHopTrade/hooks/useReceiveAddress'

import { SharedRecipientAddress } from '../../SharedTradeInput/SharedRecipientAddress'

type UseLimitOrderRecipientAddressProps = {
buyAsset: Asset
buyAccountId: string | undefined
sellAccountId: string | undefined
}

export const useLimitOrderRecipientAddress = ({
buyAsset,
buyAccountId,
sellAccountId,
}: UseLimitOrderRecipientAddressProps) => {
const [manualReceiveAddress, setManualReceiveAddress] = useState<string | undefined>(undefined)
const [isManualReceiveAddressValid, setIsManualReceiveAddressValid] = useState<
boolean | undefined
>(undefined)
const [isManualReceiveAddressEditing, setIsManualReceiveAddressEditing] = useState(false)
const [isManualReceiveAddressValidating, setIsManualReceiveAddressValidating] = useState(false)
const { walletReceiveAddress, isLoading: isWalletReceiveAddressLoading } = useReceiveAddress({
sellAccountId,
buyAccountId,
buyAsset,
})

const handleManualReceiveAddressError = useCallback(() => {
setManualReceiveAddress(undefined)
}, [])

const handleEditManualReceiveAddress = useCallback(() => {
setIsManualReceiveAddressEditing(true)
}, [])

const handleCancelManualReceiveAddress = useCallback(() => {
setIsManualReceiveAddressEditing(false)
// Reset form value and valid state on cancel so the valid check doesn't wrongly evaluate to false after bailing out of editing an invalid address
setIsManualReceiveAddressValid(undefined)
}, [])

const handleResetManualReceiveAddress = useCallback(() => {
// Reset the manual receive address in store
setManualReceiveAddress(undefined)
// Reset the valid state in store
setIsManualReceiveAddressValid(undefined)
}, [])

const handleSubmitManualReceiveAddress = useCallback((address: string) => {
setManualReceiveAddress(address)
setIsManualReceiveAddressEditing(false)
}, [])

const isManualReceiveAddressRequired = useIsManualReceiveAddressRequired({
shouldForceManualAddressEntry: false,
sellAccountId,
buyAsset,
manualReceiveAddress,
walletReceiveAddress,
isWalletReceiveAddressLoading,
})

const isRecipientAddressEntryActive = useMemo(() => {
return (
isManualReceiveAddressRequired ||
isManualReceiveAddressValidating ||
isManualReceiveAddressEditing ||
isManualReceiveAddressValid === false
)
}, [
isManualReceiveAddressEditing,
isManualReceiveAddressRequired,
isManualReceiveAddressValid,
isManualReceiveAddressValidating,
])

const renderedRecipientAddress = useMemo(() => {
return (
<SharedRecipientAddress
buyAsset={buyAsset}
isWalletReceiveAddressLoading={isWalletReceiveAddressLoading}
manualReceiveAddress={manualReceiveAddress}
walletReceiveAddress={walletReceiveAddress}
onCancel={handleCancelManualReceiveAddress}
onEdit={handleEditManualReceiveAddress}
onError={handleManualReceiveAddressError}
onIsValidatingChange={setIsManualReceiveAddressValidating}
onIsValidChange={setIsManualReceiveAddressValid}
onReset={handleResetManualReceiveAddress}
onSubmit={handleSubmitManualReceiveAddress}
/>
)
}, [
buyAsset,
manualReceiveAddress,
handleCancelManualReceiveAddress,
handleEditManualReceiveAddress,
handleManualReceiveAddressError,
handleResetManualReceiveAddress,
handleSubmitManualReceiveAddress,
walletReceiveAddress,
isWalletReceiveAddressLoading,
])

return {
isRecipientAddressEntryActive,
renderedRecipientAddress,
recipientAddress: (manualReceiveAddress ?? walletReceiveAddress) as Address | undefined,
}
}
Loading

0 comments on commit ccdcd4f

Please sign in to comment.