From e298a37e04243370235a47e3102f2a40890b5ef1 Mon Sep 17 00:00:00 2001 From: Apotheosis <0xapotheosis@gmail.com> Date: Tue, 3 Sep 2024 19:45:25 +1000 Subject: [PATCH 1/9] feat: portal slippage optimization approval swaps (#7672) --- .../getPortalsTradeQuote.ts | 24 +++++++-- .../utils/fetchPortalsTradeOrder.ts | 53 +++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeQuote/getPortalsTradeQuote.ts b/packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeQuote/getPortalsTradeQuote.ts index 240b14fd6fb..1fb4efd77cf 100644 --- a/packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeQuote/getPortalsTradeQuote.ts +++ b/packages/swapper/src/swappers/PortalsSwapper/getPortalsTradeQuote/getPortalsTradeQuote.ts @@ -21,7 +21,7 @@ import { import { getRate, makeSwapErrorRight } from '../../../utils' import { getTreasuryAddressFromChainId, isNativeEvmAsset } from '../../utils/helpers/helpers' import { chainIdToPortalsNetwork } from '../constants' -import { fetchPortalsTradeOrder } from '../utils/fetchPortalsTradeOrder' +import { fetchPortalsTradeEstimate, fetchPortalsTradeOrder } from '../utils/fetchPortalsTradeOrder' import { getDummyQuoteParams, isSupportedChainId } from '../utils/helpers' export async function getPortalsTradeQuote( @@ -120,10 +120,24 @@ export async function getPortalsTradeQuote( validate: true, swapperConfig, }).catch(async e => { - // If validation fails, fire two more quotes: - // 1. a quote with validation enabled, but using a well-funded address to get a rough gasLimit estimate - // 2. another quote with validation disabled, to get an actual quote + // If validation fails, fire 3 more quotes: + // 1. a quote estimate (does not require approval) to get the optimal slippage tolerance + // 2. a quote with validation enabled, but using a well-funded address to get a rough gasLimit estimate + // 3. another quote with validation disabled, to get an actual quote (using the user slippage, or the optimal from the estimate) console.info('failed to get Portals quote with validation enabled', e) + + // Use the quote estimate endpoint to get the optimal slippage tolerance + const quoteEstimateResponse = await fetchPortalsTradeEstimate({ + sender: sendAddress, + inputToken, + outputToken, + inputAmount: sellAmountIncludingProtocolFeesCryptoBaseUnit, + swapperConfig, + }).catch(e => { + console.info('failed to get Portals quote estimate', e) + return undefined + }) + const dummyQuoteParams = getDummyQuoteParams(sellAsset.chainId) const dummySellAssetAddress = fromAssetId(dummyQuoteParams.sellAssetId).assetReference @@ -132,6 +146,7 @@ export async function getPortalsTradeQuote( const dummyInputToken = `${portalsNetwork}:${dummySellAssetAddress}` const dummyOutputToken = `${portalsNetwork}:${dummyBuyAssetAddress}` + // Use a dummy request to the portal endpoint to get a rough gasLimit estimate const dummyOrderResponse = await fetchPortalsTradeOrder({ sender: dummyQuoteParams.accountAddress, inputToken: dummyInputToken, @@ -158,6 +173,7 @@ export async function getPortalsTradeQuote( inputAmount: sellAmountIncludingProtocolFeesCryptoBaseUnit, slippageTolerancePercentage: userSlippageTolerancePercentageDecimalOrDefault ?? + quoteEstimateResponse?.context.slippageTolerancePercentage ?? bnOrZero(getDefaultSlippageDecimalPercentageForSwapper(SwapperName.Portals)) .times(100) .toNumber(), diff --git a/packages/swapper/src/swappers/PortalsSwapper/utils/fetchPortalsTradeOrder.ts b/packages/swapper/src/swappers/PortalsSwapper/utils/fetchPortalsTradeOrder.ts index e879193e0b7..63430aef232 100644 --- a/packages/swapper/src/swappers/PortalsSwapper/utils/fetchPortalsTradeOrder.ts +++ b/packages/swapper/src/swappers/PortalsSwapper/utils/fetchPortalsTradeOrder.ts @@ -17,6 +17,8 @@ type PortalsTradeOrderParams = { swapperConfig: SwapperConfig } +type PortalsTradeOrderEstimateParams = Omit + type PortalsTradeOrderResponse = { context: { orderId: string @@ -50,6 +52,25 @@ type PortalsTradeOrderResponse = { gasLimit: string } } + +type PortalsTradeOrderEstimateResponse = { + outputAmount: string + minOutputAmount: string + outputToken: string + outputTokenDecimals: number + context: { + slippageTolerancePercentage: number + inputAmount: string + inputAmountUsd: number + inputToken: string + outputToken: string + outputAmount: string + outputAmountUsd: number + minOutputAmountUsd: number + sender?: string + } +} + export const fetchPortalsTradeOrder = async ({ sender, inputToken, @@ -90,3 +111,35 @@ export const fetchPortalsTradeOrder = async ({ throw error } } + +export const fetchPortalsTradeEstimate = async ({ + sender, + inputToken, + inputAmount, + outputToken, + slippageTolerancePercentage, + swapperConfig, +}: PortalsTradeOrderEstimateParams): Promise => { + const url = `${swapperConfig.REACT_APP_PORTALS_BASE_URL}/v2/portal/estimate` + + const params = new URLSearchParams({ + sender, + inputToken, + inputAmount, + outputToken, + }) + + if (slippageTolerancePercentage !== undefined) { + params.append('slippageTolerancePercentage', slippageTolerancePercentage.toFixed(2)) // Portals API expects a string with at most 2 decimal places + } + + try { + const response = await axios.get(url, { params }) + return response.data + } catch (error) { + if (axios.isAxiosError(error)) { + throw new Error(`Failed to fetch Portals trade estimate: ${error.message}`) + } + throw error + } +} From 7354e35ea7556812371645b3734ca1bcdd68a006 Mon Sep 17 00:00:00 2001 From: 0xean <0xean.eth@gmail.com> Date: Tue, 3 Sep 2024 14:55:53 -0400 Subject: [PATCH 2/9] chore: update hash for epoch 1 pending (#7680) --- src/pages/RFOX/constants.ts | 2 +- src/pages/RFOX/hooks/useLifetimeRewardDistributionsQuery.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/RFOX/constants.ts b/src/pages/RFOX/constants.ts index 72e35ffcfaf..33cafac3333 100644 --- a/src/pages/RFOX/constants.ts +++ b/src/pages/RFOX/constants.ts @@ -11,7 +11,7 @@ export const withdrawEvent = getAbiItem({ abi: foxStakingV1Abi, name: 'Withdraw' export const IPFS_GATEWAY = 'https://gateway.shapeshift.com/ipfs' -export const CURRENT_EPOCH_IPFS_HASH = 'QmTr3pFd14d5RaYao7LrtFkrR5ABoa4KCQqffx9G73SA1T' +export const CURRENT_EPOCH_IPFS_HASH = 'QmPYuHffJfCQuexWJYv4CpukhRAyw3YM8MJbCZ34ZGda3n' const client = viemClientByNetworkId[arbitrum.id] diff --git a/src/pages/RFOX/hooks/useLifetimeRewardDistributionsQuery.ts b/src/pages/RFOX/hooks/useLifetimeRewardDistributionsQuery.ts index 7bc1fe52b93..085150f8b8e 100644 --- a/src/pages/RFOX/hooks/useLifetimeRewardDistributionsQuery.ts +++ b/src/pages/RFOX/hooks/useLifetimeRewardDistributionsQuery.ts @@ -28,6 +28,7 @@ export const useLifetimeRewardDistributionsQuery = ({ if (!stakingAssetAccountAddresses) return [] return data .filter(epoch => epoch.number >= 0) + .sort((a, b) => b.number - a.number) .flatMap(epoch => stakingAssetAccountAddresses.map(stakingAssetAccountAddress => { const stakingAddress = getAddress(stakingAssetAccountAddress) From a98e6a91e82442c2212562f884ee738fd817b47d Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Tue, 3 Sep 2024 21:29:26 +0200 Subject: [PATCH 3/9] fix: make most of the addresses to be kopiable (#7663) --- .../AccountDropdown/AccountSegement.tsx | 1 + src/components/InlineCopyButton.tsx | 12 ++++-- .../Modals/Nfts/components/NftOverview.tsx | 43 ++++++++++--------- src/components/Modals/Send/views/Confirm.tsx | 28 ++++++++---- .../components/StepperStep.tsx | 6 ++- .../TransactionDetails/Address.tsx | 41 ++++++++++-------- .../Claim/components/Confirm.tsx | 13 ++++-- .../CosmosManager/Claim/components/Status.tsx | 13 ++++-- .../Withdraw/components/Status.tsx | 5 ++- .../FoxFarmingManager/Claim/ClaimConfirm.tsx | 17 +++++--- .../FoxFarmingManager/Claim/ClaimStatus.tsx | 17 +++++--- .../Overview/Claim/ClaimConfirm.tsx | 17 +++++--- .../Overview/Claim/ClaimStatus.tsx | 17 +++++--- .../components/Account.tsx | 17 +++++--- .../components/header/AddressLinks.tsx | 9 ++-- 15 files changed, 162 insertions(+), 94 deletions(-) diff --git a/src/components/AccountDropdown/AccountSegement.tsx b/src/components/AccountDropdown/AccountSegement.tsx index d65deb35d36..1298840cbb3 100644 --- a/src/components/AccountDropdown/AccountSegement.tsx +++ b/src/components/AccountDropdown/AccountSegement.tsx @@ -14,6 +14,7 @@ export const AccountSegment: FC = ({ title, subtitle }) => ( py={2} color='text.subtle' fontSize='sm' + alignItems='center' justifyContent='space-between' > {title} diff --git a/src/components/InlineCopyButton.tsx b/src/components/InlineCopyButton.tsx index d3938e1cafc..784eb5d1ce8 100644 --- a/src/components/InlineCopyButton.tsx +++ b/src/components/InlineCopyButton.tsx @@ -1,6 +1,6 @@ import { CheckIcon, CopyIcon } from '@chakra-ui/icons' import { Flex, IconButton } from '@chakra-ui/react' -import type { PropsWithChildren } from 'react' +import type { MouseEvent, PropsWithChildren } from 'react' import React, { useCallback } from 'react' import { useTranslate } from 'react-polyglot' import { useCopyToClipboard } from 'hooks/useCopyToClipboard' @@ -23,9 +23,13 @@ export const InlineCopyButton: React.FC = ({ const translate = useTranslate() const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }) - const handleCopyClick = useCallback(() => { - copyToClipboard(value) - }, [copyToClipboard, value]) + const handleCopyClick = useCallback( + (e: MouseEvent) => { + e.stopPropagation() + copyToClipboard(value) + }, + [copyToClipboard, value], + ) // Hide the copy button if it is disabled if (isDisabled) return <>{children} diff --git a/src/components/Modals/Nfts/components/NftOverview.tsx b/src/components/Modals/Nfts/components/NftOverview.tsx index 7a246fb561b..d18f7c96834 100644 --- a/src/components/Modals/Nfts/components/NftOverview.tsx +++ b/src/components/Modals/Nfts/components/NftOverview.tsx @@ -5,6 +5,7 @@ import { CopyButton } from 'plugins/walletConnectToDapps/components/modals/CopyB import { useCallback } from 'react' import { useTranslate } from 'react-polyglot' import { AssetIcon } from 'components/AssetIcon' +import { InlineCopyButton } from 'components/InlineCopyButton' import { MiddleEllipsis } from 'components/MiddleEllipsis/MiddleEllipsis' import { Row } from 'components/Row/Row' import { SanitizedHtml } from 'components/SanitizedHtml/SanitizedHtml' @@ -66,26 +67,28 @@ export const NftOverview: React.FC = ({ nftItem }) => { {translate('nft.address')} - + + + )} diff --git a/src/components/Modals/Send/views/Confirm.tsx b/src/components/Modals/Send/views/Confirm.tsx index c35a525636f..f149c044b47 100644 --- a/src/components/Modals/Send/views/Confirm.tsx +++ b/src/components/Modals/Send/views/Confirm.tsx @@ -8,6 +8,7 @@ import { Stack, useColorModeValue, } from '@chakra-ui/react' +import { fromAccountId } from '@shapeshiftoss/caip' import { fromAssetId } from '@shapeshiftoss/caip/dist/assetId/assetId' import { CHAIN_NAMESPACE } from '@shapeshiftoss/caip/dist/constants' import type { FeeDataKey } from '@shapeshiftoss/chain-adapters' @@ -19,6 +20,7 @@ import { useTranslate } from 'react-polyglot' import { useHistory } from 'react-router-dom' import { AccountDropdown } from 'components/AccountDropdown/AccountDropdown' import { Amount } from 'components/Amount/Amount' +import { InlineCopyButton } from 'components/InlineCopyButton' import { MiddleEllipsis } from 'components/MiddleEllipsis/MiddleEllipsis' import { DialogBackButton } from 'components/Modal/components/DialogBackButton' import { DialogBody } from 'components/Modal/components/DialogBody' @@ -30,6 +32,7 @@ import { SlideTransition } from 'components/SlideTransition' import { RawText, Text } from 'components/Text' import type { TextPropTypes } from 'components/Text/Text' import { bnOrZero } from 'lib/bignumber/bignumber' +import { isUtxoAccountId } from 'lib/utils/utxo' import { selectAssetById, selectFeeAssetById } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -142,20 +145,29 @@ export const Confirm = () => { - + + + - {vanityAddress ? vanityAddress : } + + + {vanityAddress ? vanityAddress : } + + {allowCustomSendNonce && ( diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/StepperStep.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/StepperStep.tsx index 59b5c723705..c1569cb9146 100644 --- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/StepperStep.tsx +++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/components/StepperStep.tsx @@ -12,6 +12,7 @@ import { Tag, useStyleConfig, } from '@chakra-ui/react' +import { InlineCopyButton } from 'components/InlineCopyButton' import { MiddleEllipsis } from 'components/MiddleEllipsis/MiddleEllipsis' import { selectActiveQuote } from 'state/slices/tradeQuoteSlice/selectors' import { useAppSelector } from 'state/store' @@ -39,10 +40,13 @@ const LastStepTag = () => { return ( - + + + ) } + export const StepperStep = ({ title, stepIndicator, diff --git a/src/components/TransactionHistoryRows/TransactionDetails/Address.tsx b/src/components/TransactionHistoryRows/TransactionDetails/Address.tsx index 75990c91e12..76501fc247b 100644 --- a/src/components/TransactionHistoryRows/TransactionDetails/Address.tsx +++ b/src/components/TransactionHistoryRows/TransactionDetails/Address.tsx @@ -1,5 +1,6 @@ import { Button, Link } from '@chakra-ui/react' import { useCallback } from 'react' +import { InlineCopyButton } from 'components/InlineCopyButton' import { MiddleEllipsis } from 'components/MiddleEllipsis/MiddleEllipsis' const buttonHover = { bg: 'transparent' } @@ -18,24 +19,28 @@ export const Address = ({ [], ) return explorerAddressLink ? ( - + + + ) : ( - + + + ) } diff --git a/src/features/defi/providers/cosmos/components/CosmosManager/Claim/components/Confirm.tsx b/src/features/defi/providers/cosmos/components/CosmosManager/Claim/components/Confirm.tsx index 662c17d0653..9c71a472b13 100644 --- a/src/features/defi/providers/cosmos/components/CosmosManager/Claim/components/Confirm.tsx +++ b/src/features/defi/providers/cosmos/components/CosmosManager/Claim/components/Confirm.tsx @@ -14,6 +14,7 @@ import { useTranslate } from 'react-polyglot' import { Amount } from 'components/Amount/Amount' import { AssetIcon } from 'components/AssetIcon' import type { StepComponentProps } from 'components/DeFi/components/Steps' +import { InlineCopyButton } from 'components/InlineCopyButton' import { MiddleEllipsis } from 'components/MiddleEllipsis/MiddleEllipsis' import { Row } from 'components/Row/Row' import { Text } from 'components/Text' @@ -193,9 +194,15 @@ export const Confirm: React.FC = ({ accountId, onNext }) => { - - {userAddress && } - + + + {userAddress && } + + diff --git a/src/features/defi/providers/cosmos/components/CosmosManager/Claim/components/Status.tsx b/src/features/defi/providers/cosmos/components/CosmosManager/Claim/components/Status.tsx index a33c0c42b87..1905192a0ab 100644 --- a/src/features/defi/providers/cosmos/components/CosmosManager/Claim/components/Status.tsx +++ b/src/features/defi/providers/cosmos/components/CosmosManager/Claim/components/Status.tsx @@ -12,6 +12,7 @@ import { Amount } from 'components/Amount/Amount' import { AssetIcon } from 'components/AssetIcon' import { CircularProgress } from 'components/CircularProgress/CircularProgress' import { IconCircle } from 'components/IconCircle' +import { InlineCopyButton } from 'components/InlineCopyButton' import { MiddleEllipsis } from 'components/MiddleEllipsis/MiddleEllipsis' import { Row } from 'components/Row/Row' import { RawText } from 'components/Text' @@ -151,9 +152,15 @@ export const Status: React.FC = ({ accountId }) => { {translate('defi.modals.claim.claimToAddress')} - - {userAddress && } - + + + {userAddress && } + + ) } + return ( - ) }, [ - buyVerified, - handleBuyVerify, + buyAddressVerificationStatus, + sellAddressVerificationStatus, handleContinue, - handleSellVerify, + handleBuyVerify, isBuyVerifying, - isSellVerifying, - sellVerified, - shouldVerifyBuyAddress, translate, verifyBuyAssetTranslation, + handleSellVerify, + isSellVerifying, verifySellAssetTranslation, ]) @@ -316,7 +385,7 @@ export const VerifyAddresses = () => { return ( - + @@ -341,7 +410,12 @@ export const VerifyAddresses = () => { {isBuyVerifying && } - {buyVerified && } + {buyAddressVerificationStatus === AddressVerificationStatus.Verified && ( + + )} + {buyAddressVerificationStatus === AddressVerificationStatus.Error && ( + + )} @@ -361,7 +435,12 @@ export const VerifyAddresses = () => { {isSellVerifying && } - {sellVerified && } + {sellAddressVerificationStatus === 'verified' && ( + + )} + {sellAddressVerificationStatus === 'error' && ( + + )} @@ -371,7 +450,15 @@ export const VerifyAddresses = () => { - + {Boolean( + buyAddressVerificationErrorMessage || sellAddressVerificationErrorMessage, + ) ? ( + + {buyAddressVerificationErrorMessage ?? sellAddressVerificationErrorMessage} + + ) : ( + + )} {renderButton} From bc1ed24a8e2aed080e08506283c2d6a5d0d555cc Mon Sep 17 00:00:00 2001 From: NeOMakinG <14963751+NeOMakinG@users.noreply.github.com> Date: Wed, 4 Sep 2024 17:43:04 +0200 Subject: [PATCH 7/9] fix: add copy button to some dropdown accounts (#7689) --- src/components/Modals/Send/views/Details.tsx | 27 ++++++--- .../defi/components/Overview/Overview.tsx | 7 ++- .../CosmosManager/Overview/CosmosOverview.tsx | 3 +- .../Overview/FoxFarmingOverview.tsx | 3 +- .../FoxyManager/Overview/FoxyOverview.tsx | 1 + .../Overview/ThorchainSaversOverview.tsx | 59 ++++++++++--------- .../UniV2Manager/Overview/UniV2Overview.tsx | 3 +- .../RFOX/components/AddressSelection.tsx | 31 +++++++--- .../ChangeAddress/ChangeAddressInput.tsx | 1 + src/pages/RFOX/components/RFOXHeader.tsx | 23 +++++--- .../RFOX/components/Stake/StakeInput.tsx | 5 +- 11 files changed, 106 insertions(+), 57 deletions(-) diff --git a/src/components/Modals/Send/views/Details.tsx b/src/components/Modals/Send/views/Details.tsx index 52710f9f51c..0fa92581ae4 100644 --- a/src/components/Modals/Send/views/Details.tsx +++ b/src/components/Modals/Send/views/Details.tsx @@ -11,7 +11,7 @@ import { usePrevious, } from '@chakra-ui/react' import type { AccountId } from '@shapeshiftoss/caip' -import { fromAssetId } from '@shapeshiftoss/caip' +import { fromAccountId, fromAssetId } from '@shapeshiftoss/caip' import { CHAIN_NAMESPACE } from '@shapeshiftoss/caip/dist/constants' import isNil from 'lodash/isNil' import React, { useCallback, useEffect, useMemo } from 'react' @@ -23,6 +23,7 @@ import { useHistory } from 'react-router-dom' import { AccountCard } from 'components/AccountCard' import { AccountDropdown } from 'components/AccountDropdown/AccountDropdown' import { Amount } from 'components/Amount/Amount' +import { InlineCopyButton } from 'components/InlineCopyButton' import { DialogBackButton } from 'components/Modal/components/DialogBackButton' import { DialogBody } from 'components/Modal/components/DialogBody' import { DialogFooter } from 'components/Modal/components/DialogFooter' @@ -35,6 +36,7 @@ import { TokenRow } from 'components/TokenRow/TokenRow' import { useModal } from 'hooks/useModal/useModal' import { useWallet } from 'hooks/useWallet/useWallet' import { bnOrZero } from 'lib/bignumber/bignumber' +import { isUtxoAccountId } from 'lib/utils/utxo' import { selectAssetById } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -54,7 +56,8 @@ const MAX_COSMOS_SDK_MEMO_LENGTH = 256 const controllerRules = { required: true, } -const accountDropdownButtonProps = { width: 'full', mb: 2, variant: 'solid' } +const accountDropdownBoxProps = { width: '100%', my: 0, pl: 0 } +const accountDropdownButtonProps = { width: 'full', variant: 'solid' } const formHelperTextHoverStyle = { color: 'gray.400', transition: '.2s color ease' } export const Details = () => { @@ -220,12 +223,20 @@ export const Details = () => { - + + + + + void + positionAddress?: string | undefined // The LP asset this opportunity represents lpAsset?: AssetWithBalance // The assets underlying the LP one @@ -69,6 +71,7 @@ const accountDropdownBoxProps = { px: 0, my: 0, width: 'full' } export const Overview: React.FC = ({ accountId, onAccountIdChange, + positionAddress, lpAsset, underlyingAssetsCryptoPrecision, rewardAssetsCryptoPrecision, @@ -109,7 +112,7 @@ export const Overview: React.FC = ({ {onAccountIdChange && ( <> - + = ({ buttonProps={accountDropdownButtonProps} boxProps={accountDropdownBoxProps} /> - + )} diff --git a/src/features/defi/providers/cosmos/components/CosmosManager/Overview/CosmosOverview.tsx b/src/features/defi/providers/cosmos/components/CosmosManager/Overview/CosmosOverview.tsx index f3e63164ba1..ed2598cff56 100644 --- a/src/features/defi/providers/cosmos/components/CosmosManager/Overview/CosmosOverview.tsx +++ b/src/features/defi/providers/cosmos/components/CosmosManager/Overview/CosmosOverview.tsx @@ -1,7 +1,7 @@ import { ArrowDownIcon, ArrowUpIcon } from '@chakra-ui/icons' import { Center } from '@chakra-ui/react' import type { AccountId } from '@shapeshiftoss/caip' -import { toAssetId } from '@shapeshiftoss/caip' +import { fromAccountId, toAssetId } from '@shapeshiftoss/caip' import { DefiModalContent } from 'features/defi/components/DefiModal/DefiModalContent' import { Overview } from 'features/defi/components/Overview/Overview' import type { @@ -239,6 +239,7 @@ export const CosmosOverview: React.FC = ({ = ({ = ({ = ({ const { chainId, assetReference, assetNamespace } = query const alertBg = useColorModeValue('gray.200', 'gray.900') + const { + state: { wallet }, + } = useWallet() + const assetId = toAssetId({ chainId, assetNamespace, @@ -161,24 +171,6 @@ export const ThorchainSaversOverview: React.FC = ({ : undefined, ) - const hasStakedBalance = useMemo(() => { - return bnOrZero(earnOpportunityData?.stakedAmountCryptoBaseUnit).gt(0) - }, [earnOpportunityData?.stakedAmountCryptoBaseUnit]) - - const highestAssetBalanceFilter = useMemo( - () => ({ - assetId, - }), - [assetId], - ) - const highestAssetBalanceAccountId = useAppSelector(state => - selectHighestUserCurrencyBalanceAccountByAssetId(state, highestAssetBalanceFilter), - ) - - const highestStakedOrAssetBalanceAccountId = hasStakedBalance - ? maybeAccountId - : highestAssetBalanceAccountId - const opportunityMetadataFilter = useMemo(() => ({ stakingId: assetId as StakingId }), [assetId]) const opportunityMetadata = useAppSelector(state => selectStakingOpportunityByFilter(state, opportunityMetadataFilter), @@ -263,6 +255,23 @@ export const ThorchainSaversOverview: React.FC = ({ return Math.ceil(diffTime / 1000) }, [earnOpportunityData]) + const accountFilter = useMemo(() => ({ accountId: maybeAccountId }), [maybeAccountId]) + + const accountMetadata = useAppSelector(state => + selectPortfolioAccountMetadataByAccountId(state, accountFilter), + ) + + const { data: fromAddress } = useQuery({ + ...reactQueries.common.thorchainFromAddress({ + accountId: accountId!, + assetId, + wallet: wallet!, + accountMetadata: accountMetadata!, + getPosition: getThorchainSaversPosition, + }), + enabled: Boolean(accountId && wallet && accountMetadata), + }) + const makeDefaultMenu = useCallback( ({ isFull, @@ -436,11 +445,7 @@ export const ThorchainSaversOverview: React.FC = ({ const handleThorchainSaversEmptyClick = useCallback(() => setHideEmptyState(true), []) - if ( - (!earnOpportunityData?.isLoaded && highestStakedOrAssetBalanceAccountId) || - isTradingActiveLoading || - isMockDepositQuoteLoading - ) { + if (!earnOpportunityData?.isLoaded || isTradingActiveLoading || isMockDepositQuoteLoading) { return (
@@ -452,15 +457,15 @@ export const ThorchainSaversOverview: React.FC = ({ return } - if (!(highestStakedOrAssetBalanceAccountId && opportunityDataFilter)) return null if (!asset) return null if (!underlyingAssetsWithBalancesAndIcons) return null if (!earnOpportunityData) return null return ( = ({ void + selectedAddress: string | undefined } & ( | { isNewAddress: boolean @@ -54,6 +56,7 @@ const buttonProps = { export const AddressSelection: FC = ({ onRuneAddressChange: handleRuneAddressChange, isNewAddress, + selectedAddress, validateIsNewAddress, }) => { const translate = useTranslate() @@ -180,19 +183,31 @@ export const AddressSelection: FC = ({ return undefined }, [currentRuneAddress, maybeMatchingRuneAccountId]) + const maybeSelectedRuneAddress = useMemo(() => { + if (selectedAddress) return selectedAddress + if (maybeDefaultRuneAccountId) return fromAccountId(maybeDefaultRuneAccountId).account + + return undefined + }, [maybeDefaultRuneAccountId, selectedAddress]) + const accountSelection = useMemo(() => { if (isManualAddress) return null return ( - + + + ) - }, [handleAccountIdChange, isManualAddress, maybeDefaultRuneAccountId]) + }, [handleAccountIdChange, isManualAddress, maybeDefaultRuneAccountId, maybeSelectedRuneAddress]) const addressSelectionLabel = useMemo( () => diff --git a/src/pages/RFOX/components/ChangeAddress/ChangeAddressInput.tsx b/src/pages/RFOX/components/ChangeAddress/ChangeAddressInput.tsx index 301f88b20e5..b26bb49d9e3 100644 --- a/src/pages/RFOX/components/ChangeAddress/ChangeAddressInput.tsx +++ b/src/pages/RFOX/components/ChangeAddress/ChangeAddressInput.tsx @@ -267,6 +267,7 @@ export const ChangeAddressInput: FC diff --git a/src/pages/RFOX/components/RFOXHeader.tsx b/src/pages/RFOX/components/RFOXHeader.tsx index 840da57ed6f..d3a43a0dd09 100644 --- a/src/pages/RFOX/components/RFOXHeader.tsx +++ b/src/pages/RFOX/components/RFOXHeader.tsx @@ -1,9 +1,11 @@ import { Flex, Heading, Stack } from '@chakra-ui/react' +import { fromAccountId } from '@shapeshiftoss/caip' import { useCallback, useEffect, useMemo } from 'react' import { useTranslate } from 'react-polyglot' import { useHistory } from 'react-router' import { AccountDropdown } from 'components/AccountDropdown/AccountDropdown' import { Display } from 'components/Display' +import { InlineCopyButton } from 'components/InlineCopyButton' import { PageBackButton, PageHeader } from 'components/Layout/Header/PageHeader' import { SEO } from 'components/Layout/Seo' import { Text } from 'components/Text' @@ -39,14 +41,19 @@ export const RFOXHeader = () => { - + + + ) }, [accountIds.length, setStakingAssetAccountId, stakingAssetAccountId, stakingAssetId]) diff --git a/src/pages/RFOX/components/Stake/StakeInput.tsx b/src/pages/RFOX/components/Stake/StakeInput.tsx index 12951573ab1..0beb3999cba 100644 --- a/src/pages/RFOX/components/Stake/StakeInput.tsx +++ b/src/pages/RFOX/components/Stake/StakeInput.tsx @@ -485,7 +485,10 @@ export const StakeInput: React.FC = ({ fiatAmount={amountUserCurrency} /> - + {stakingAssetAccountId && ( Date: Wed, 4 Sep 2024 17:58:56 +0200 Subject: [PATCH 8/9] fix: disable fiat ramp address copy for utxos (#7688) --- .../Modals/FiatRamps/views/Overview.tsx | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src/components/Modals/FiatRamps/views/Overview.tsx b/src/components/Modals/FiatRamps/views/Overview.tsx index b9fd5f71a80..ee7249b1da0 100644 --- a/src/components/Modals/FiatRamps/views/Overview.tsx +++ b/src/components/Modals/FiatRamps/views/Overview.tsx @@ -35,6 +35,7 @@ import { getMaybeCompositeAssetSymbol } from 'lib/mixpanel/helpers' import { getMixPanel } from 'lib/mixpanel/mixPanelSingleton' import { MixPanelEvent } from 'lib/mixpanel/types' import { isKeepKeyHDWallet } from 'lib/utils' +import { isUtxoAccountId } from 'lib/utils/utxo' import { useGetFiatRampsQuery } from 'state/apis/fiatRamps/fiatRamps' import { isAssetSupportedByWallet } from 'state/slices/portfolioSlice/utils' import { @@ -340,46 +341,48 @@ export const Overview: React.FC = ({ buttonProps={accountDropdownButtonProps} boxProps={accountDropdownBoxProps} /> - - - {!address && } - {address && ( - - - {supportsAddressVerification && address && ( + {accountId && !isUtxoAccountId(accountId) ? ( + + + {!address && } + {address && ( + : } - onClick={handleVerify} - aria-label={translate('common.verify')} + icon={copyIcon} + aria-label={translate('common.copy')} size='sm' - color={ - shownOnDisplay - ? 'green.500' - : shownOnDisplay === false - ? 'red.500' - : 'text.subtle' - } isRound variant='ghost' + onClick={handleCopyClick} /> - )} - - )} - + {supportsAddressVerification && address && ( + : } + onClick={handleVerify} + aria-label={translate('common.verify')} + size='sm' + color={ + shownOnDisplay + ? 'green.500' + : shownOnDisplay === false + ? 'red.500' + : 'text.subtle' + } + isRound + variant='ghost' + /> + )} + + )} + + ) : null} )} From 1df5578ffb8de1b9cd2145099fdf7a4a3556f669 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Wed, 4 Sep 2024 18:57:53 +0200 Subject: [PATCH 9/9] feat: thorchain savers deposit usdt allowance reset step (#7670) --- packages/caip/src/constants.ts | 2 + src/assets/translations/en/main.json | 2 +- src/components/Modals/FiatRamps/config.ts | 3 +- .../defi/components/Approve/Approve.tsx | 33 +++++++--- .../DefiManagerProvider/DefiCommon.ts | 1 + .../Deposit/components/Approve.tsx | 2 - .../Deposit/components/Approve.tsx | 2 - .../Withdraw/components/Approve.tsx | 2 - .../Deposit/ThorchainSaversDeposit.tsx | 15 ++++- .../Deposit/components/Approve.tsx | 22 ++++--- .../Deposit/components/Deposit.tsx | 65 +++++++------------ .../Deposit/components/Approve.tsx | 5 -- .../Withdraw/components/Approve.tsx | 2 - src/hooks/queries/useIsApprovalRequired.ts | 4 +- 14 files changed, 77 insertions(+), 83 deletions(-) diff --git a/packages/caip/src/constants.ts b/packages/caip/src/constants.ts index 4883de8e430..86cf23df9fe 100644 --- a/packages/caip/src/constants.ts +++ b/packages/caip/src/constants.ts @@ -25,6 +25,8 @@ export const foxatarAssetId: AssetId = 'eip155:137/erc721:0x2e727c425a11ce6b8819b3004db332c12d2af2a2' export const foxyAssetId: AssetId = 'eip155:1/erc20:0xdc49108ce5c57bc3408c3a5e95f3d864ec386ed3' +export const usdtAssetId: AssetId = 'eip155:1/erc20:0xdac17f958d2ee523a2206206994597c13d831ec7' + export const cosmosAssetId: AssetId = 'cosmos:cosmoshub-4/slip44:118' export const thorchainAssetId: AssetId = 'cosmos:thorchain-mainnet-v1/slip44:931' export const binanceAssetId: AssetId = 'cosmos:binance-chain-tigris/slip44:714' diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json index 23bb44010ce..1a90f16c11e 100644 --- a/src/assets/translations/en/main.json +++ b/src/assets/translations/en/main.json @@ -840,7 +840,6 @@ "pending": "Trade Pending", "complete": "Trade Complete", "approveAsset": "Approve interacting with %{symbol}", - "needPermission": "needs your permission to use your %{symbol}", "whyNeedThis": "Why do I need to do this?", "estimatedGasFee": "Estimated Gas Fee", "receiveInclFee": "Receive (incl. fees)", @@ -1227,6 +1226,7 @@ }, "approve": { "header": "Allow %{spenderName} to use your %{asset}", + "resetHeader": "Reset %{spenderName} allowance for %{asset}", "shapeshiftRouterName": "ShapeShift DAO Router", "body": "needs your permission to use your %{asset}", "learnMore": "Why do I need to do this?", diff --git a/src/components/Modals/FiatRamps/config.ts b/src/components/Modals/FiatRamps/config.ts index 0aa36dfff3f..838c1887d7d 100644 --- a/src/components/Modals/FiatRamps/config.ts +++ b/src/components/Modals/FiatRamps/config.ts @@ -1,5 +1,5 @@ import type { AssetId } from '@shapeshiftoss/caip' -import { adapters, btcAssetId, fromAssetId, gnosisChainId } from '@shapeshiftoss/caip' +import { adapters, btcAssetId, fromAssetId, gnosisChainId, usdtAssetId } from '@shapeshiftoss/caip' import banxaLogo from 'assets/banxa.png' import CoinbaseLogo from 'assets/coinbase-logo.svg' import MtPelerinLogo from 'assets/mtpelerin.png' @@ -26,7 +26,6 @@ import { import type { CreateUrlProps } from './types' export const usdcAssetId: AssetId = 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' -export const usdtAssetId: AssetId = 'eip155:1/erc20:0xdac17f958d2ee523a2206206994597c13d831ec7' export type FiatCurrencyItem = { symbol: string diff --git a/src/features/defi/components/Approve/Approve.tsx b/src/features/defi/components/Approve/Approve.tsx index 6e366f0e6ad..de352ad7deb 100644 --- a/src/features/defi/components/Approve/Approve.tsx +++ b/src/features/defi/components/Approve/Approve.tsx @@ -36,9 +36,8 @@ type ApproveProps = { fiatEstimatedGasFee: string isExactAllowance?: boolean isApproved?: boolean - learnMoreLink?: string loading: boolean - loadingText?: string + isReset?: boolean spenderContractAddress: string preFooter?: React.ReactNode onToggle?(): void @@ -59,9 +58,8 @@ export const Approve = ({ icons, isApproved, isExactAllowance, - learnMoreLink, loading, - loadingText, + isReset, onCancel: handleCancel, onConfirm: handleConfirm, onToggle: handleToggle, @@ -84,8 +82,25 @@ export const Approve = ({ ) const approveHeaderTranslation: TextPropTypes['translation'] = useMemo( - () => ['modals.approve.header', { asset: asset.name, spenderName }], - [asset.name, spenderName], + () => [ + isReset ? 'modals.approve.resetHeader' : 'modals.approve.header', + { asset: asset.name, spenderName }, + ], + [asset.name, isReset, spenderName], + ) + + const approveCopy = useMemo(() => { + if (isReset) return 'common.reset' + return !isApproved ? 'common.approve' : 'modals.approve.approved' + }, [isApproved, isReset]) + + const learnMoreLink = useMemo( + () => + isReset + ? // See https://github.com/tethercoin/USDT/blob/c3e3caa95c30e74a0f4f0d616e13ff97daa02191/TetherToken.sol#L204 + 'https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/' + : 'https://shapeshift.zendesk.com/hc/en-us/articles/360018501700', + [isReset], ) const handleApproveClick = useCallback( @@ -137,7 +152,7 @@ export const Approve = ({ {/* Because isExactAllowance is not used everywhere yet, we need to make it optional and check if it is defined because it's a boolean */} - {!isUndefined(isExactAllowance) && ( + {!isUndefined(isExactAllowance) && !isReset && ( @@ -171,9 +186,9 @@ export const Approve = ({ width='full' data-test='defi-modal-approve-button' isLoading={loading} - loadingText={loadingText} + loadingText={translate(isReset ? 'common.reset' : 'common.approve')} > - {translate(!isApproved ? 'common.approve' : 'modals.approve.approved')} + {translate(approveCopy)}