From 51c669dbe42e03249b95b92fd48671897b728319 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 7 Mar 2024 09:38:50 -0600 Subject: [PATCH 01/20] feat: add borrowAndUnwrap --- apps/evm/src/clients/api/__mocks__/index.ts | 4 ++ .../mutations/borrowAndUnwrap/index.spec.ts | 31 ++++++++++ .../api/mutations/borrowAndUnwrap/index.ts | 18 ++++++ .../borrowAndUnwrap/useBorrowAndUnwrap.ts | 60 +++++++++++++++++++ apps/evm/src/constants/functionKey.ts | 1 + 5 files changed, 114 insertions(+) create mode 100644 apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.spec.ts create mode 100644 apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.ts create mode 100644 apps/evm/src/clients/api/mutations/borrowAndUnwrap/useBorrowAndUnwrap.ts diff --git a/apps/evm/src/clients/api/__mocks__/index.ts b/apps/evm/src/clients/api/__mocks__/index.ts index 5a614e1102..026eb65821 100644 --- a/apps/evm/src/clients/api/__mocks__/index.ts +++ b/apps/evm/src/clients/api/__mocks__/index.ts @@ -489,3 +489,7 @@ export const useWrapTokensAndRepay = (_variables: never, options?: MutationObser export const updatePoolDelegateStatus = vi.fn(); export const useUpdatePoolDelegateStatus = (_variables: never, options?: MutationObserverOptions) => useMutation(FunctionKey.UPDATE_POOL_DELEGATE_STATUS, updatePoolDelegateStatus, options); + +export const borrowAndUnwrap = vi.fn(); +export const useBorrowAndUnwrap = (_variables: never, options?: MutationObserverOptions) => + useMutation(FunctionKey.BORROW_AND_UNWRAP, borrowAndUnwrap, options); diff --git a/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.spec.ts b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.spec.ts new file mode 100644 index 0000000000..a1edd0a96d --- /dev/null +++ b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.spec.ts @@ -0,0 +1,31 @@ +import BigNumber from 'bignumber.js'; + +import fakeContractTransaction from '__mocks__/models/contractTransaction'; + +import { NativeTokenGateway } from 'libs/contracts'; + +import borrowAndUnwrap from '.'; + +const fakeAmountMantissa = new BigNumber('10000000000000000'); + +vi.mock('libs/contracts'); + +describe('borrowAndUnwrap', () => { + it('returns transaction when request succeeds', async () => { + const borrowAndUnwrapMock = vi.fn(() => fakeContractTransaction); + + const fakeNativeTokenGatewayContract = { + borrowAndUnwrap: borrowAndUnwrapMock, + } as unknown as NativeTokenGateway; + + const response = await borrowAndUnwrap({ + nativeTokenGatewayContract: fakeNativeTokenGatewayContract, + amountMantissa: fakeAmountMantissa, + }); + + expect(response).toBe(fakeContractTransaction); + + expect(borrowAndUnwrapMock).toHaveBeenCalledTimes(1); + expect(borrowAndUnwrapMock).toHaveBeenCalledWith(fakeAmountMantissa.toFixed()); + }); +}); diff --git a/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.ts b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.ts new file mode 100644 index 0000000000..1c7595c00f --- /dev/null +++ b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.ts @@ -0,0 +1,18 @@ +import BigNumber from 'bignumber.js'; +import { ContractTransaction } from 'ethers'; + +import { NativeTokenGateway } from 'libs/contracts'; + +export interface BorrowAndUnwrapInput { + nativeTokenGatewayContract: NativeTokenGateway; + amountMantissa: BigNumber; +} + +export type BorrowAndUnwrapOutput = ContractTransaction; + +const borrowAndUnwrap = async ({ + nativeTokenGatewayContract, + amountMantissa, +}: BorrowAndUnwrapInput) => nativeTokenGatewayContract.borrowAndUnwrap(amountMantissa.toFixed()); + +export default borrowAndUnwrap; diff --git a/apps/evm/src/clients/api/mutations/borrowAndUnwrap/useBorrowAndUnwrap.ts b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/useBorrowAndUnwrap.ts new file mode 100644 index 0000000000..203e8c3bd0 --- /dev/null +++ b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/useBorrowAndUnwrap.ts @@ -0,0 +1,60 @@ +import borrowAndUnwrap, { BorrowAndUnwrapInput } from 'clients/api/mutations/borrowAndUnwrap'; +import queryClient from 'clients/api/queryClient'; +import FunctionKey from 'constants/functionKey'; +import { UseSendTransactionOptions, useSendTransaction } from 'hooks/useSendTransaction'; +import { useGetNativeTokenGatewayContract } from 'libs/contracts'; +import { useChainId } from 'libs/wallet'; +import { VToken } from 'types'; +import { callOrThrow } from 'utilities'; + +type TrimmedBorrowAndUnwrapInput = Omit; +type Options = UseSendTransactionOptions; + +const useBorrowAndUnwrap = ( + { vToken, poolComptrollerAddress }: { vToken: VToken; poolComptrollerAddress: string }, + options?: Options, +) => { + const { chainId } = useChainId(); + const nativeToken = vToken.underlyingToken.tokenWrapped; + const nativeTokenGatewayContract = useGetNativeTokenGatewayContract({ + passSigner: true, + comptrollerContractAddress: poolComptrollerAddress, + }); + + return useSendTransaction({ + fnKey: FunctionKey.BORROW_AND_UNWRAP, + fn: (input: TrimmedBorrowAndUnwrapInput) => + callOrThrow({ nativeTokenGatewayContract }, params => + borrowAndUnwrap({ + ...input, + ...params, + }), + ), + onConfirmed: async () => { + const accountAddress = await nativeTokenGatewayContract?.signer.getAddress(); + + queryClient.invalidateQueries(FunctionKey.GET_V_TOKEN_BALANCES_ALL); + queryClient.invalidateQueries([ + FunctionKey.GET_BALANCE_OF, + { + chainId, + accountAddress, + vTokenAddress: vToken.address, + }, + ]); + queryClient.invalidateQueries([ + FunctionKey.GET_BALANCE_OF, + { + chainId, + accountAddress, + tokenAddress: nativeToken?.address, + }, + ]); + queryClient.invalidateQueries(FunctionKey.GET_MAIN_MARKETS); + queryClient.invalidateQueries(FunctionKey.GET_ISOLATED_POOLS); + }, + options, + }); +}; + +export default useBorrowAndUnwrap; diff --git a/apps/evm/src/constants/functionKey.ts b/apps/evm/src/constants/functionKey.ts index bfbba2ef08..b72aa5cc8e 100644 --- a/apps/evm/src/constants/functionKey.ts +++ b/apps/evm/src/constants/functionKey.ts @@ -102,6 +102,7 @@ enum FunctionKey { WRAP_TOKENS_AND_SUPPLY = 'WRAP_TOKENS_AND_SUPPLY', WRAP_TOKENS_AND_REPAY = 'WRAP_TOKENS_AND_REPAY', UPDATE_POOL_DELEGATE_STATUS = 'UPDATE_POOL_DELEGATE_STATUS', + BORROW_AND_UNWRAP = 'BORROW_AND_UNWRAP', } export default FunctionKey; From ef51bc3b1be4baa7087db349ee9edc4a27852b28 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 7 Mar 2024 09:51:06 -0600 Subject: [PATCH 02/20] feat: integrate borrow form with borrowAndUnwrap --- apps/evm/src/clients/api/index.ts | 4 ++++ .../useOperationModal/Modal/BorrowForm/index.tsx | 13 +++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/evm/src/clients/api/index.ts b/apps/evm/src/clients/api/index.ts index efc9289829..95eda64f8b 100644 --- a/apps/evm/src/clients/api/index.ts +++ b/apps/evm/src/clients/api/index.ts @@ -136,6 +136,10 @@ export { default as updatePoolDelegateStatus } from './mutations/updatePoolDeleg export * from './mutations/updatePoolDelegateStatus'; export { default as useUpdatePoolDelegateStatus } from './mutations/updatePoolDelegateStatus/useUpdatePoolDelegateStatus'; +export { default as borrowAndUnwrap } from './mutations/borrowAndUnwrap'; +export * from './mutations/borrowAndUnwrap'; +export { default as useBorrowAndUnwrap } from './mutations/borrowAndUnwrap/useBorrowAndUnwrap'; + // Queries export { default as getVaiCalculateRepayAmount } from './queries/getVaiCalculateRepayAmount'; export * from './queries/getVaiCalculateRepayAmount'; diff --git a/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx index 545da3a47c..566acd80c9 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js'; import { useCallback, useMemo, useState } from 'react'; -import { useBorrow } from 'clients/api'; +import { useBorrow, useBorrowAndUnwrap } from 'clients/api'; import { AssetWarning, Delimiter, LabeledInlineContent, Toggle, TokenTextField } from 'components'; import { SAFE_BORROW_LIMIT_PERCENTAGE } from 'constants/safeBorrowLimitPercentage'; import { AccountData } from 'containers/AccountData'; @@ -249,6 +249,11 @@ const BorrowForm: React.FC = ({ asset, pool, onCloseModal }) => vToken: asset.vToken, }); + const { mutateAsync: borrowAndUnwrap, isLoading: isBorrowAndUnwrapLoading } = useBorrowAndUnwrap({ + poolComptrollerAddress: pool.comptrollerAddress, + vToken: asset.vToken, + }); + const nativeTokenGatewayContractAddress = useGetNativeTokenGatewayContractAddress({ comptrollerContractAddress: pool.comptrollerAddress, }); @@ -264,7 +269,7 @@ const BorrowForm: React.FC = ({ asset, pool, onCloseModal }) => enabled: formValues.receiveNativeToken, }); - const isSubmitting = isBorrowLoading; + const isSubmitting = isBorrowLoading || isBorrowAndUnwrapLoading; const onSubmit: BorrowFormUiProps['onSubmit'] = async ({ fromToken, fromTokenAmountTokens }) => { const amountMantissa = convertTokensToMantissa({ @@ -272,6 +277,10 @@ const BorrowForm: React.FC = ({ asset, pool, onCloseModal }) => token: fromToken, }); + if (formValues.receiveNativeToken) { + return borrowAndUnwrap({ amountMantissa }); + } + return borrow({ amountMantissa }); }; From c163b83daaed514f3aa814d536f1ea48c25702c8 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 7 Mar 2024 10:14:45 -0600 Subject: [PATCH 03/20] feat: add redeemAndUnwrap --- apps/evm/src/clients/api/index.ts | 4 ++ .../mutations/redeemAndUnwrap/index.spec.ts | 31 ++++++++++ .../api/mutations/redeemAndUnwrap/index.ts | 19 ++++++ .../redeemAndUnwrap/useRedeemAndUnwrap.ts | 60 +++++++++++++++++++ apps/evm/src/constants/functionKey.ts | 1 + 5 files changed, 115 insertions(+) create mode 100644 apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.spec.ts create mode 100644 apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.ts create mode 100644 apps/evm/src/clients/api/mutations/redeemAndUnwrap/useRedeemAndUnwrap.ts diff --git a/apps/evm/src/clients/api/index.ts b/apps/evm/src/clients/api/index.ts index 95eda64f8b..2e813a0883 100644 --- a/apps/evm/src/clients/api/index.ts +++ b/apps/evm/src/clients/api/index.ts @@ -140,6 +140,10 @@ export { default as borrowAndUnwrap } from './mutations/borrowAndUnwrap'; export * from './mutations/borrowAndUnwrap'; export { default as useBorrowAndUnwrap } from './mutations/borrowAndUnwrap/useBorrowAndUnwrap'; +export { default as redeemAndUnwrap } from './mutations/redeemAndUnwrap'; +export * from './mutations/redeemAndUnwrap'; +export { default as useRedeemAndUnwrap } from './mutations/redeemAndUnwrap/useRedeemAndUnwrap'; + // Queries export { default as getVaiCalculateRepayAmount } from './queries/getVaiCalculateRepayAmount'; export * from './queries/getVaiCalculateRepayAmount'; diff --git a/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.spec.ts b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.spec.ts new file mode 100644 index 0000000000..4b61cd7adc --- /dev/null +++ b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.spec.ts @@ -0,0 +1,31 @@ +import BigNumber from 'bignumber.js'; + +import fakeContractTransaction from '__mocks__/models/contractTransaction'; + +import { NativeTokenGateway } from 'libs/contracts'; + +import redeemAndUnwrap from '.'; + +const fakeAmountMantissa = new BigNumber('10000000000000000'); + +vi.mock('libs/contracts'); + +describe('redeemAndUnwrap', () => { + it('returns transaction when request succeeds', async () => { + const redeemUnderlyingAndUnwrapMock = vi.fn(() => fakeContractTransaction); + + const fakeNativeTokenGatewayContract = { + redeemUnderlyingAndUnwrap: redeemUnderlyingAndUnwrapMock, + } as unknown as NativeTokenGateway; + + const response = await redeemAndUnwrap({ + nativeTokenGatewayContract: fakeNativeTokenGatewayContract, + amountMantissa: fakeAmountMantissa, + }); + + expect(response).toBe(fakeContractTransaction); + + expect(redeemUnderlyingAndUnwrapMock).toHaveBeenCalledTimes(1); + expect(redeemUnderlyingAndUnwrapMock).toHaveBeenCalledWith(fakeAmountMantissa.toFixed()); + }); +}); diff --git a/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.ts b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.ts new file mode 100644 index 0000000000..db58e3866f --- /dev/null +++ b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.ts @@ -0,0 +1,19 @@ +import BigNumber from 'bignumber.js'; +import { ContractTransaction } from 'ethers'; + +import { NativeTokenGateway } from 'libs/contracts'; + +export interface RedeemAndUnwrapInput { + nativeTokenGatewayContract: NativeTokenGateway; + amountMantissa: BigNumber; +} + +export type RedeemAndUnwrapOutput = ContractTransaction; + +const redeemAndUnwrap = async ({ + nativeTokenGatewayContract, + amountMantissa, +}: RedeemAndUnwrapInput) => + nativeTokenGatewayContract.redeemUnderlyingAndUnwrap(amountMantissa.toFixed()); + +export default redeemAndUnwrap; diff --git a/apps/evm/src/clients/api/mutations/redeemAndUnwrap/useRedeemAndUnwrap.ts b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/useRedeemAndUnwrap.ts new file mode 100644 index 0000000000..18aac4667a --- /dev/null +++ b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/useRedeemAndUnwrap.ts @@ -0,0 +1,60 @@ +import redeemAndUnwrap, { RedeemAndUnwrapInput } from 'clients/api/mutations/redeemAndUnwrap'; +import queryClient from 'clients/api/queryClient'; +import FunctionKey from 'constants/functionKey'; +import { UseSendTransactionOptions, useSendTransaction } from 'hooks/useSendTransaction'; +import { useGetNativeTokenGatewayContract } from 'libs/contracts'; +import { useChainId } from 'libs/wallet'; +import { VToken } from 'types'; +import { callOrThrow } from 'utilities'; + +type TrimmedRedeemAndUnwrapInput = Omit; +type Options = UseSendTransactionOptions; + +const useRedeemAndUnwrap = ( + { vToken, poolComptrollerAddress }: { vToken: VToken; poolComptrollerAddress: string }, + options?: Options, +) => { + const { chainId } = useChainId(); + const nativeToken = vToken.underlyingToken.tokenWrapped; + const nativeTokenGatewayContract = useGetNativeTokenGatewayContract({ + passSigner: true, + comptrollerContractAddress: poolComptrollerAddress, + }); + + return useSendTransaction({ + fnKey: FunctionKey.REDEEM_AND_UNWRAP, + fn: (input: TrimmedRedeemAndUnwrapInput) => + callOrThrow({ nativeTokenGatewayContract }, params => + redeemAndUnwrap({ + ...input, + ...params, + }), + ), + onConfirmed: async () => { + const accountAddress = await nativeTokenGatewayContract?.signer.getAddress(); + + queryClient.invalidateQueries(FunctionKey.GET_V_TOKEN_BALANCES_ALL); + queryClient.invalidateQueries([ + FunctionKey.GET_BALANCE_OF, + { + chainId, + accountAddress, + vTokenAddress: vToken.address, + }, + ]); + queryClient.invalidateQueries([ + FunctionKey.GET_BALANCE_OF, + { + chainId, + accountAddress, + tokenAddress: nativeToken?.address, + }, + ]); + queryClient.invalidateQueries(FunctionKey.GET_MAIN_MARKETS); + queryClient.invalidateQueries(FunctionKey.GET_ISOLATED_POOLS); + }, + options, + }); +}; + +export default useRedeemAndUnwrap; diff --git a/apps/evm/src/constants/functionKey.ts b/apps/evm/src/constants/functionKey.ts index b72aa5cc8e..15708d1dec 100644 --- a/apps/evm/src/constants/functionKey.ts +++ b/apps/evm/src/constants/functionKey.ts @@ -103,6 +103,7 @@ enum FunctionKey { WRAP_TOKENS_AND_REPAY = 'WRAP_TOKENS_AND_REPAY', UPDATE_POOL_DELEGATE_STATUS = 'UPDATE_POOL_DELEGATE_STATUS', BORROW_AND_UNWRAP = 'BORROW_AND_UNWRAP', + REDEEM_AND_UNWRAP = 'REDEEM_AND_UNWRAP', } export default FunctionKey; From 6a4f38fe63160fcf9f774feb589e648321e46ef2 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 7 Mar 2024 10:17:51 -0600 Subject: [PATCH 04/20] feat: integrate withdraw form with redeemAndUnwrap --- apps/evm/src/clients/api/__mocks__/index.ts | 4 +++ .../Modal/WithdrawForm/index.tsx | 26 +++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/evm/src/clients/api/__mocks__/index.ts b/apps/evm/src/clients/api/__mocks__/index.ts index 026eb65821..6f4b28877c 100644 --- a/apps/evm/src/clients/api/__mocks__/index.ts +++ b/apps/evm/src/clients/api/__mocks__/index.ts @@ -493,3 +493,7 @@ export const useUpdatePoolDelegateStatus = (_variables: never, options?: Mutatio export const borrowAndUnwrap = vi.fn(); export const useBorrowAndUnwrap = (_variables: never, options?: MutationObserverOptions) => useMutation(FunctionKey.BORROW_AND_UNWRAP, borrowAndUnwrap, options); + +export const redeemAndUnwrap = vi.fn(); +export const useRedeemAndUnwrap = (_variables: never, options?: MutationObserverOptions) => + useMutation(FunctionKey.REDEEM_AND_UNWRAP, redeemAndUnwrap, options); diff --git a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx index c90b014c87..7458ae9d9a 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx @@ -2,7 +2,12 @@ import BigNumber from 'bignumber.js'; import { useCallback, useMemo, useState } from 'react'; -import { useGetVTokenBalanceOf, useRedeem, useRedeemUnderlying } from 'clients/api'; +import { + useGetVTokenBalanceOf, + useRedeem, + useRedeemAndUnwrap, + useRedeemUnderlying, +} from 'clients/api'; import { Delimiter, LabeledInlineContent, Toggle, TokenTextField } from 'components'; import { AccountData } from 'containers/AccountData'; import useDelegateApproval from 'hooks/useDelegateApproval'; @@ -251,6 +256,11 @@ const WithdrawForm: React.FC = ({ asset, pool, onCloseModal } vToken: asset.vToken, }); + const { mutateAsync: redeemAndUnwrap, isLoading: isRedeemAndUnwrapLoading } = useRedeemAndUnwrap({ + poolComptrollerAddress: pool.comptrollerAddress, + vToken: asset.vToken, + }); + const nativeTokenGatewayContractAddress = useGetNativeTokenGatewayContractAddress({ comptrollerContractAddress: pool.comptrollerAddress, }); @@ -272,7 +282,8 @@ const WithdrawForm: React.FC = ({ asset, pool, onCloseModal } vToken: asset.vToken, }); - const isWithdrawLoading = isRedeemLoading || isRedeemUnderlyingLoading; + const isWithdrawLoading = + isRedeemLoading || isRedeemUnderlyingLoading || isRedeemAndUnwrapLoading; const onSubmit: UseFormInput['onSubmit'] = async ({ fromToken, fromTokenAmountTokens }) => { // This cose should never be reached, but just in case we throw a generic @@ -294,6 +305,12 @@ const WithdrawForm: React.FC = ({ asset, pool, onCloseModal } token: fromToken, }); + if (formValues.receiveNativeToken) { + return redeemAndUnwrap({ + amountMantissa: withdrawAmountMantissa, + }); + } + return redeemUnderlying({ amountMantissa: withdrawAmountMantissa, }); @@ -301,6 +318,11 @@ const WithdrawForm: React.FC = ({ asset, pool, onCloseModal } // Withdraw entire supply if (vTokenBalanceMantissa) { + if (formValues.receiveNativeToken) { + return redeemAndUnwrap({ + amountMantissa: vTokenBalanceMantissa, + }); + } return redeem({ amountMantissa: vTokenBalanceMantissa }); } From e362302bbe52bde0de0870ee991de27067da3868 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 7 Mar 2024 10:55:12 -0600 Subject: [PATCH 05/20] feat: add NativeTokenGateway addresses for opBNB and BSC mainnet --- apps/evm/src/libs/contracts/config/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/evm/src/libs/contracts/config/index.ts b/apps/evm/src/libs/contracts/config/index.ts index 424784bb00..5eaf5a9919 100644 --- a/apps/evm/src/libs/contracts/config/index.ts +++ b/apps/evm/src/libs/contracts/config/index.ts @@ -396,8 +396,14 @@ export const contracts: ContractConfig[] = [ [isolatedPoolsBscTestnetDeployments.addresses.Comptroller_LiquidStakedBNB.toLowerCase()]: '0xae5A30d694DFF2268C864834DEDa745B784c48bD', }, - [ChainId.BSC_MAINNET]: {}, - [ChainId.OPBNB_MAINNET]: {}, + [ChainId.BSC_MAINNET]: { + [isolatedPoolsBscMainnetDeployments.addresses.Comptroller_LiquidStakedBNB.toLowerCase()]: + '0xa8433F284795aE7f8652127af47482578b58673d', + }, + [ChainId.OPBNB_MAINNET]: { + [isolatedPoolsOpBnbMainnetDeployments.addresses.Comptroller_Core.toLowerCase()]: + '0x996597fc8726eC0f62BCA0aF4f2Af67D2f7563Ee', + }, [ChainId.OPBNB_TESTNET]: { [isolatedPoolsOpBnbTestnetDeployments.addresses.Comptroller_Core.toLowerCase()]: '0xbA12d0BFC59fd29C44795FfFa8A3Ccc877A41325', From 0d94af3e241000db7919ca927a7747992a1caf33 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 7 Mar 2024 10:56:36 -0600 Subject: [PATCH 06/20] feat: update WBNB to wrap BNB on BSC mainnet --- .../tokens/infos/commonTokens/bscMainnet.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/evm/src/libs/tokens/infos/commonTokens/bscMainnet.ts b/apps/evm/src/libs/tokens/infos/commonTokens/bscMainnet.ts index b04aeea100..acf4fbcbe9 100644 --- a/apps/evm/src/libs/tokens/infos/commonTokens/bscMainnet.ts +++ b/apps/evm/src/libs/tokens/infos/commonTokens/bscMainnet.ts @@ -52,6 +52,14 @@ import xrpLogo from 'libs/tokens/img/xrp.svg'; import xvsLogo from 'libs/tokens/img/xvs.svg'; import type { Token } from 'types'; +const bnbToken: Token = { + address: '0x0000000000000000000000000000000000000000', + decimals: 18, + symbol: 'BNB', + asset: bnbLogo, + isNative: true, +}; + export const tokens: Token[] = [ { address: '0xcF6BB5389c92Bdda8a3747Ddb454cB7a64626C63', @@ -71,13 +79,7 @@ export const tokens: Token[] = [ symbol: 'VRT', asset: vrtLogo, }, - { - address: '0x0000000000000000000000000000000000000000', - decimals: 18, - symbol: 'BNB', - asset: bnbLogo, - isNative: true, - }, + bnbToken, { address: '0x47BEAd2563dCBf3bF2c9407fEa4dC236fAbA485A', decimals: 18, @@ -324,6 +326,7 @@ export const tokens: Token[] = [ decimals: 18, symbol: 'WBNB', asset: wbnbLogo, + tokenWrapped: bnbToken, }, { address: '0x3BC5AC0dFdC871B365d159f728dd1B9A0B5481E8', From ea315f85e920430e1d039e6cbdb7485a56f4a378 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 7 Mar 2024 10:56:50 -0600 Subject: [PATCH 07/20] feat: update WBNB to wrap BNB on opBNB mainnet --- .../tokens/infos/commonTokens/opBnbMainnet.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/evm/src/libs/tokens/infos/commonTokens/opBnbMainnet.ts b/apps/evm/src/libs/tokens/infos/commonTokens/opBnbMainnet.ts index 0d7cc5060b..3d0c9258e7 100644 --- a/apps/evm/src/libs/tokens/infos/commonTokens/opBnbMainnet.ts +++ b/apps/evm/src/libs/tokens/infos/commonTokens/opBnbMainnet.ts @@ -7,14 +7,16 @@ import wbnbLogo from 'libs/tokens/img/wbnb.svg'; import xvsLogo from 'libs/tokens/img/xvs.svg'; import type { Token } from 'types'; +const bnbToken: Token = { + address: '0x0000000000000000000000000000000000000000', + decimals: 18, + symbol: 'BNB', + asset: bnbLogo, + isNative: true, +}; + export const tokens: Token[] = [ - { - address: '0x0000000000000000000000000000000000000000', - decimals: 18, - symbol: 'BNB', - asset: bnbLogo, - isNative: true, - }, + bnbToken, { address: '0x3E2e61F1c075881F3fB8dd568043d8c221fd5c61', decimals: 18, @@ -50,5 +52,6 @@ export const tokens: Token[] = [ decimals: 18, symbol: 'WBNB', asset: wbnbLogo, + tokenWrapped: bnbToken, }, ]; From 6cceaf1a2f42d7ab6ff06b759b6c85c95b5a2129 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Tue, 12 Mar 2024 18:06:54 -0600 Subject: [PATCH 08/20] feat: separate redeemAndUnwrap and redeemUnderlyingAndUnwrap --- apps/evm/src/clients/api/__mocks__/index.ts | 6 ++ apps/evm/src/clients/api/index.ts | 4 ++ .../mutations/borrowAndUnwrap/index.spec.ts | 2 +- .../api/mutations/borrowAndUnwrap/index.ts | 6 +- .../borrowAndUnwrap/useBorrowAndUnwrap.ts | 6 +- .../mutations/redeemAndUnwrap/index.spec.ts | 10 +-- .../api/mutations/redeemAndUnwrap/index.ts | 9 ++- .../redeemAndUnwrap/useRedeemAndUnwrap.ts | 6 +- .../redeemUnderlyingAndUnwrap/index.spec.ts | 31 +++++++++ .../redeemUnderlyingAndUnwrap/index.ts | 19 ++++++ .../useRedeemUnderlyingAndUnwrap.ts | 65 +++++++++++++++++++ apps/evm/src/constants/functionKey.ts | 1 + .../externalAbis/NativeTokenGateway.json | 13 ++++ 13 files changed, 158 insertions(+), 20 deletions(-) create mode 100644 apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/index.spec.ts create mode 100644 apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/index.ts create mode 100644 apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/useRedeemUnderlyingAndUnwrap.ts diff --git a/apps/evm/src/clients/api/__mocks__/index.ts b/apps/evm/src/clients/api/__mocks__/index.ts index 6f4b28877c..013cb9b943 100644 --- a/apps/evm/src/clients/api/__mocks__/index.ts +++ b/apps/evm/src/clients/api/__mocks__/index.ts @@ -497,3 +497,9 @@ export const useBorrowAndUnwrap = (_variables: never, options?: MutationObserver export const redeemAndUnwrap = vi.fn(); export const useRedeemAndUnwrap = (_variables: never, options?: MutationObserverOptions) => useMutation(FunctionKey.REDEEM_AND_UNWRAP, redeemAndUnwrap, options); + +export const redeemUnderlyingAndUnwrap = vi.fn(); +export const useRedeemUnderlyingAndUnwrap = ( + _variables: never, + options?: MutationObserverOptions, +) => useMutation(FunctionKey.REDEEM_UNDERLYING_AND_UNWRAP, redeemUnderlyingAndUnwrap, options); diff --git a/apps/evm/src/clients/api/index.ts b/apps/evm/src/clients/api/index.ts index 2e813a0883..894e5333fc 100644 --- a/apps/evm/src/clients/api/index.ts +++ b/apps/evm/src/clients/api/index.ts @@ -144,6 +144,10 @@ export { default as redeemAndUnwrap } from './mutations/redeemAndUnwrap'; export * from './mutations/redeemAndUnwrap'; export { default as useRedeemAndUnwrap } from './mutations/redeemAndUnwrap/useRedeemAndUnwrap'; +export { default as redeemUnderlyingAndUnwrap } from './mutations/redeemUnderlyingAndUnwrap'; +export * from './mutations/redeemUnderlyingAndUnwrap'; +export { default as useRedeemUnderlyingAndUnwrap } from './mutations/redeemUnderlyingAndUnwrap/useRedeemUnderlyingAndUnwrap'; + // Queries export { default as getVaiCalculateRepayAmount } from './queries/getVaiCalculateRepayAmount'; export * from './queries/getVaiCalculateRepayAmount'; diff --git a/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.spec.ts b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.spec.ts index a1edd0a96d..db07dcf243 100644 --- a/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.spec.ts +++ b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.spec.ts @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js'; import fakeContractTransaction from '__mocks__/models/contractTransaction'; -import { NativeTokenGateway } from 'libs/contracts'; +import type { NativeTokenGateway } from 'libs/contracts'; import borrowAndUnwrap from '.'; diff --git a/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.ts b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.ts index 1c7595c00f..e3902591f8 100644 --- a/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.ts +++ b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/index.ts @@ -1,7 +1,7 @@ -import BigNumber from 'bignumber.js'; -import { ContractTransaction } from 'ethers'; +import type BigNumber from 'bignumber.js'; +import type { ContractTransaction } from 'ethers'; -import { NativeTokenGateway } from 'libs/contracts'; +import type { NativeTokenGateway } from 'libs/contracts'; export interface BorrowAndUnwrapInput { nativeTokenGatewayContract: NativeTokenGateway; diff --git a/apps/evm/src/clients/api/mutations/borrowAndUnwrap/useBorrowAndUnwrap.ts b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/useBorrowAndUnwrap.ts index 203e8c3bd0..22b85c1288 100644 --- a/apps/evm/src/clients/api/mutations/borrowAndUnwrap/useBorrowAndUnwrap.ts +++ b/apps/evm/src/clients/api/mutations/borrowAndUnwrap/useBorrowAndUnwrap.ts @@ -1,10 +1,10 @@ -import borrowAndUnwrap, { BorrowAndUnwrapInput } from 'clients/api/mutations/borrowAndUnwrap'; +import borrowAndUnwrap, { type BorrowAndUnwrapInput } from 'clients/api/mutations/borrowAndUnwrap'; import queryClient from 'clients/api/queryClient'; import FunctionKey from 'constants/functionKey'; -import { UseSendTransactionOptions, useSendTransaction } from 'hooks/useSendTransaction'; +import { type UseSendTransactionOptions, useSendTransaction } from 'hooks/useSendTransaction'; import { useGetNativeTokenGatewayContract } from 'libs/contracts'; import { useChainId } from 'libs/wallet'; -import { VToken } from 'types'; +import type { VToken } from 'types'; import { callOrThrow } from 'utilities'; type TrimmedBorrowAndUnwrapInput = Omit; diff --git a/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.spec.ts b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.spec.ts index 4b61cd7adc..7b93570629 100644 --- a/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.spec.ts +++ b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.spec.ts @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js'; import fakeContractTransaction from '__mocks__/models/contractTransaction'; -import { NativeTokenGateway } from 'libs/contracts'; +import type { NativeTokenGateway } from 'libs/contracts'; import redeemAndUnwrap from '.'; @@ -12,10 +12,10 @@ vi.mock('libs/contracts'); describe('redeemAndUnwrap', () => { it('returns transaction when request succeeds', async () => { - const redeemUnderlyingAndUnwrapMock = vi.fn(() => fakeContractTransaction); + const redeemAndUnwrapMock = vi.fn(() => fakeContractTransaction); const fakeNativeTokenGatewayContract = { - redeemUnderlyingAndUnwrap: redeemUnderlyingAndUnwrapMock, + redeemAndUnwrap: redeemAndUnwrapMock, } as unknown as NativeTokenGateway; const response = await redeemAndUnwrap({ @@ -25,7 +25,7 @@ describe('redeemAndUnwrap', () => { expect(response).toBe(fakeContractTransaction); - expect(redeemUnderlyingAndUnwrapMock).toHaveBeenCalledTimes(1); - expect(redeemUnderlyingAndUnwrapMock).toHaveBeenCalledWith(fakeAmountMantissa.toFixed()); + expect(redeemAndUnwrapMock).toHaveBeenCalledTimes(1); + expect(redeemAndUnwrapMock).toHaveBeenCalledWith(fakeAmountMantissa.toFixed()); }); }); diff --git a/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.ts b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.ts index db58e3866f..abc9df088d 100644 --- a/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.ts +++ b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/index.ts @@ -1,7 +1,7 @@ -import BigNumber from 'bignumber.js'; -import { ContractTransaction } from 'ethers'; +import type BigNumber from 'bignumber.js'; +import type { ContractTransaction } from 'ethers'; -import { NativeTokenGateway } from 'libs/contracts'; +import type { NativeTokenGateway } from 'libs/contracts'; export interface RedeemAndUnwrapInput { nativeTokenGatewayContract: NativeTokenGateway; @@ -13,7 +13,6 @@ export type RedeemAndUnwrapOutput = ContractTransaction; const redeemAndUnwrap = async ({ nativeTokenGatewayContract, amountMantissa, -}: RedeemAndUnwrapInput) => - nativeTokenGatewayContract.redeemUnderlyingAndUnwrap(amountMantissa.toFixed()); +}: RedeemAndUnwrapInput) => nativeTokenGatewayContract.redeemAndUnwrap(amountMantissa.toFixed()); export default redeemAndUnwrap; diff --git a/apps/evm/src/clients/api/mutations/redeemAndUnwrap/useRedeemAndUnwrap.ts b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/useRedeemAndUnwrap.ts index 18aac4667a..454de87a47 100644 --- a/apps/evm/src/clients/api/mutations/redeemAndUnwrap/useRedeemAndUnwrap.ts +++ b/apps/evm/src/clients/api/mutations/redeemAndUnwrap/useRedeemAndUnwrap.ts @@ -1,10 +1,10 @@ -import redeemAndUnwrap, { RedeemAndUnwrapInput } from 'clients/api/mutations/redeemAndUnwrap'; +import redeemAndUnwrap, { type RedeemAndUnwrapInput } from 'clients/api/mutations/redeemAndUnwrap'; import queryClient from 'clients/api/queryClient'; import FunctionKey from 'constants/functionKey'; -import { UseSendTransactionOptions, useSendTransaction } from 'hooks/useSendTransaction'; +import { type UseSendTransactionOptions, useSendTransaction } from 'hooks/useSendTransaction'; import { useGetNativeTokenGatewayContract } from 'libs/contracts'; import { useChainId } from 'libs/wallet'; -import { VToken } from 'types'; +import type { VToken } from 'types'; import { callOrThrow } from 'utilities'; type TrimmedRedeemAndUnwrapInput = Omit; diff --git a/apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/index.spec.ts b/apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/index.spec.ts new file mode 100644 index 0000000000..78d28e51da --- /dev/null +++ b/apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/index.spec.ts @@ -0,0 +1,31 @@ +import BigNumber from 'bignumber.js'; + +import fakeContractTransaction from '__mocks__/models/contractTransaction'; + +import type { NativeTokenGateway } from 'libs/contracts'; + +import redeemAndUnwrap from '.'; + +const fakeAmountMantissa = new BigNumber('10000000000000000'); + +vi.mock('libs/contracts'); + +describe('redeemUnderlyingAndUnwrap', () => { + it('returns transaction when request succeeds', async () => { + const redeemUnderlyingAndUnwrapMock = vi.fn(() => fakeContractTransaction); + + const fakeNativeTokenGatewayContract = { + redeemUnderlyingAndUnwrap: redeemUnderlyingAndUnwrapMock, + } as unknown as NativeTokenGateway; + + const response = await redeemAndUnwrap({ + nativeTokenGatewayContract: fakeNativeTokenGatewayContract, + amountMantissa: fakeAmountMantissa, + }); + + expect(response).toBe(fakeContractTransaction); + + expect(redeemUnderlyingAndUnwrapMock).toHaveBeenCalledTimes(1); + expect(redeemUnderlyingAndUnwrapMock).toHaveBeenCalledWith(fakeAmountMantissa.toFixed()); + }); +}); diff --git a/apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/index.ts b/apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/index.ts new file mode 100644 index 0000000000..570e0d304d --- /dev/null +++ b/apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/index.ts @@ -0,0 +1,19 @@ +import type BigNumber from 'bignumber.js'; +import type { ContractTransaction } from 'ethers'; + +import type { NativeTokenGateway } from 'libs/contracts'; + +export interface RedeemUnderlyingAndUnwrapInput { + nativeTokenGatewayContract: NativeTokenGateway; + amountMantissa: BigNumber; +} + +export type RedeemUnderlyingAndUnwrapOutput = ContractTransaction; + +const redeemUnderlyingAndUnwrap = async ({ + nativeTokenGatewayContract, + amountMantissa, +}: RedeemUnderlyingAndUnwrapInput) => + nativeTokenGatewayContract.redeemUnderlyingAndUnwrap(amountMantissa.toFixed()); + +export default redeemUnderlyingAndUnwrap; diff --git a/apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/useRedeemUnderlyingAndUnwrap.ts b/apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/useRedeemUnderlyingAndUnwrap.ts new file mode 100644 index 0000000000..c13fdb5697 --- /dev/null +++ b/apps/evm/src/clients/api/mutations/redeemUnderlyingAndUnwrap/useRedeemUnderlyingAndUnwrap.ts @@ -0,0 +1,65 @@ +import redeemUnderlyingAndUnwrap, { + type RedeemUnderlyingAndUnwrapInput, +} from 'clients/api/mutations/redeemUnderlyingAndUnwrap'; +import queryClient from 'clients/api/queryClient'; +import FunctionKey from 'constants/functionKey'; +import { type UseSendTransactionOptions, useSendTransaction } from 'hooks/useSendTransaction'; +import { useGetNativeTokenGatewayContract } from 'libs/contracts'; +import { useChainId } from 'libs/wallet'; +import type { VToken } from 'types'; +import { callOrThrow } from 'utilities'; + +type TrimmedRedeemUnderlyingAndUnwrapInput = Omit< + RedeemUnderlyingAndUnwrapInput, + 'nativeTokenGatewayContract' +>; +type Options = UseSendTransactionOptions; + +const useRedeemUnderlyingAndUnwrap = ( + { vToken, poolComptrollerAddress }: { vToken: VToken; poolComptrollerAddress: string }, + options?: Options, +) => { + const { chainId } = useChainId(); + const nativeToken = vToken.underlyingToken.tokenWrapped; + const nativeTokenGatewayContract = useGetNativeTokenGatewayContract({ + passSigner: true, + comptrollerContractAddress: poolComptrollerAddress, + }); + + return useSendTransaction({ + fnKey: FunctionKey.REDEEM_AND_UNWRAP, + fn: (input: TrimmedRedeemUnderlyingAndUnwrapInput) => + callOrThrow({ nativeTokenGatewayContract }, params => + redeemUnderlyingAndUnwrap({ + ...input, + ...params, + }), + ), + onConfirmed: async () => { + const accountAddress = await nativeTokenGatewayContract?.signer.getAddress(); + + queryClient.invalidateQueries(FunctionKey.GET_V_TOKEN_BALANCES_ALL); + queryClient.invalidateQueries([ + FunctionKey.GET_BALANCE_OF, + { + chainId, + accountAddress, + vTokenAddress: vToken.address, + }, + ]); + queryClient.invalidateQueries([ + FunctionKey.GET_BALANCE_OF, + { + chainId, + accountAddress, + tokenAddress: nativeToken?.address, + }, + ]); + queryClient.invalidateQueries(FunctionKey.GET_MAIN_MARKETS); + queryClient.invalidateQueries(FunctionKey.GET_ISOLATED_POOLS); + }, + options, + }); +}; + +export default useRedeemUnderlyingAndUnwrap; diff --git a/apps/evm/src/constants/functionKey.ts b/apps/evm/src/constants/functionKey.ts index 15708d1dec..ab3469ecb2 100644 --- a/apps/evm/src/constants/functionKey.ts +++ b/apps/evm/src/constants/functionKey.ts @@ -104,6 +104,7 @@ enum FunctionKey { UPDATE_POOL_DELEGATE_STATUS = 'UPDATE_POOL_DELEGATE_STATUS', BORROW_AND_UNWRAP = 'BORROW_AND_UNWRAP', REDEEM_AND_UNWRAP = 'REDEEM_AND_UNWRAP', + REDEEM_UNDERLYING_AND_UNWRAP = 'REDEEM_UNDERLYING_AND_UNWRAP', } export default FunctionKey; diff --git a/apps/evm/src/libs/contracts/config/externalAbis/NativeTokenGateway.json b/apps/evm/src/libs/contracts/config/externalAbis/NativeTokenGateway.json index 2bf9dd15ba..7d11f47d3e 100644 --- a/apps/evm/src/libs/contracts/config/externalAbis/NativeTokenGateway.json +++ b/apps/evm/src/libs/contracts/config/externalAbis/NativeTokenGateway.json @@ -257,6 +257,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "redeemTokens", + "type": "uint256" + } + ], + "name": "redeemAndUnwrap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { From a22a3c3f88d633abb666039a4216fb9cc7e789c5 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Tue, 12 Mar 2024 18:10:00 -0600 Subject: [PATCH 09/20] feat: add getUniqueTokenBalances --- .../__tests__/index.spec.ts | 35 +++++++++++++++++++ .../utilities/getUniqueTokenBalances/index.ts | 19 ++++++++++ apps/evm/src/utilities/index.ts | 1 + 3 files changed, 55 insertions(+) create mode 100644 apps/evm/src/utilities/getUniqueTokenBalances/__tests__/index.spec.ts create mode 100644 apps/evm/src/utilities/getUniqueTokenBalances/index.ts diff --git a/apps/evm/src/utilities/getUniqueTokenBalances/__tests__/index.spec.ts b/apps/evm/src/utilities/getUniqueTokenBalances/__tests__/index.spec.ts new file mode 100644 index 0000000000..d69c71d27c --- /dev/null +++ b/apps/evm/src/utilities/getUniqueTokenBalances/__tests__/index.spec.ts @@ -0,0 +1,35 @@ +import BigNumber from 'bignumber.js'; + +import { bnb, eth, usdt } from '__mocks__/models/tokens'; +import type { TokenBalance } from 'types'; +import getUniqueTokenBalances from '..'; + +const fakeTokenBalanceBnb: TokenBalance = { + token: bnb, + balanceMantissa: new BigNumber('100'), +}; + +const fakeTokenBalanceUsdt: TokenBalance = { + token: usdt, + balanceMantissa: new BigNumber('100'), +}; + +const fakeTokenBalanceEth: TokenBalance = { + token: eth, + balanceMantissa: new BigNumber('100'), +}; + +describe('utilities/getUniqueTokenBalances', () => { + it('should return token balances with no duplicates', () => { + const tokenBalances = [ + fakeTokenBalanceEth, + fakeTokenBalanceBnb, + fakeTokenBalanceEth, + fakeTokenBalanceEth, + fakeTokenBalanceBnb, + fakeTokenBalanceUsdt, + ]; + const result = getUniqueTokenBalances(...tokenBalances); + expect(result).toStrictEqual([fakeTokenBalanceEth, fakeTokenBalanceBnb, fakeTokenBalanceUsdt]); + }); +}); diff --git a/apps/evm/src/utilities/getUniqueTokenBalances/index.ts b/apps/evm/src/utilities/getUniqueTokenBalances/index.ts new file mode 100644 index 0000000000..4ad91a9501 --- /dev/null +++ b/apps/evm/src/utilities/getUniqueTokenBalances/index.ts @@ -0,0 +1,19 @@ +import type { TokenBalance } from 'types'; + +const getUniqueTokenBalances = (...tokenBalances: TokenBalance[]) => { + const uniqueBalancesObj = tokenBalances.reduce>( + (acc, tokenBalance) => { + if (tokenBalance.token.address in acc) { + return acc; + } + return { + ...acc, + [tokenBalance.token.address]: tokenBalance, + }; + }, + {}, + ); + return Object.values(uniqueBalancesObj); +}; + +export default getUniqueTokenBalances; diff --git a/apps/evm/src/utilities/index.ts b/apps/evm/src/utilities/index.ts index 38f1044abc..03d4053a22 100755 --- a/apps/evm/src/utilities/index.ts +++ b/apps/evm/src/utilities/index.ts @@ -46,3 +46,4 @@ export * from './createStoreSelectors'; export * from './convertAprToApy'; export { default as extractSettledPromiseValue } from './extractSettledPromiseValue'; export { appendPrimeSimulationDistributions } from './appendPrimeSimulationDistributions'; +export { default as getUniqueTokenBalances } from './getUniqueTokenBalances'; From e254f5173a5f9ae3e980ccc49d894726e73780d1 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Tue, 12 Mar 2024 18:11:42 -0600 Subject: [PATCH 10/20] feat: update NativeTokenGateway addresses --- apps/evm/src/libs/contracts/config/index.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/apps/evm/src/libs/contracts/config/index.ts b/apps/evm/src/libs/contracts/config/index.ts index 5eaf5a9919..fc4d5ea870 100644 --- a/apps/evm/src/libs/contracts/config/index.ts +++ b/apps/evm/src/libs/contracts/config/index.ts @@ -394,26 +394,20 @@ export const contracts: ContractConfig[] = [ address: { [ChainId.BSC_TESTNET]: { [isolatedPoolsBscTestnetDeployments.addresses.Comptroller_LiquidStakedBNB.toLowerCase()]: - '0xae5A30d694DFF2268C864834DEDa745B784c48bD', - }, - [ChainId.BSC_MAINNET]: { - [isolatedPoolsBscMainnetDeployments.addresses.Comptroller_LiquidStakedBNB.toLowerCase()]: - '0xa8433F284795aE7f8652127af47482578b58673d', - }, - [ChainId.OPBNB_MAINNET]: { - [isolatedPoolsOpBnbMainnetDeployments.addresses.Comptroller_Core.toLowerCase()]: - '0x996597fc8726eC0f62BCA0aF4f2Af67D2f7563Ee', + '0xCf4C75398DaD73f16c762026144a1496f6869CD1', }, + [ChainId.BSC_MAINNET]: {}, + [ChainId.OPBNB_MAINNET]: {}, [ChainId.OPBNB_TESTNET]: { [isolatedPoolsOpBnbTestnetDeployments.addresses.Comptroller_Core.toLowerCase()]: - '0xbA12d0BFC59fd29C44795FfFa8A3Ccc877A41325', + '0x78FB73687209019CC1799B99Af30b6FB0A5b8e14', }, [ChainId.ETHEREUM]: {}, [ChainId.SEPOLIA]: { [isolatedPoolsSepoliaDeployments.addresses.Comptroller_Core.toLowerCase()]: - '0x02fC3253e6839e001Ac959b9834f6BdDAC7bE705', + '0xb8fD67f215117FADeF06447Af31590309750529D', [isolatedPoolsSepoliaDeployments.addresses['Comptroller_Liquid Staked ETH'].toLowerCase()]: - '0xe6FF9010852a14fA58CdBe3F2e91d6FbCB3567f9', + '0x1FD30e761C3296fE36D9067b1e398FD97B4C0407', }, }, }, From 2ce30982fa55c1ef5c1ef09c0e57faa9679eb7a4 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Tue, 12 Mar 2024 19:03:41 -0600 Subject: [PATCH 11/20] feat: add useRedeemUnderlyingAndUnwrap to WithdrawForm --- .../Modal/WithdrawForm/index.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx index 7458ae9d9a..f5d9490704 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx @@ -7,6 +7,7 @@ import { useRedeem, useRedeemAndUnwrap, useRedeemUnderlying, + useRedeemUnderlyingAndUnwrap, } from 'clients/api'; import { Delimiter, LabeledInlineContent, Toggle, TokenTextField } from 'components'; import { AccountData } from 'containers/AccountData'; @@ -261,6 +262,12 @@ const WithdrawForm: React.FC = ({ asset, pool, onCloseModal } vToken: asset.vToken, }); + const { mutateAsync: redeemUnderlyingAndUnwrap, isLoading: isRedeemUnderlyingAndUnwrapLoading } = + useRedeemUnderlyingAndUnwrap({ + poolComptrollerAddress: pool.comptrollerAddress, + vToken: asset.vToken, + }); + const nativeTokenGatewayContractAddress = useGetNativeTokenGatewayContractAddress({ comptrollerContractAddress: pool.comptrollerAddress, }); @@ -283,10 +290,13 @@ const WithdrawForm: React.FC = ({ asset, pool, onCloseModal } }); const isWithdrawLoading = - isRedeemLoading || isRedeemUnderlyingLoading || isRedeemAndUnwrapLoading; + isRedeemLoading || + isRedeemUnderlyingLoading || + isRedeemAndUnwrapLoading || + isRedeemUnderlyingAndUnwrapLoading; const onSubmit: UseFormInput['onSubmit'] = async ({ fromToken, fromTokenAmountTokens }) => { - // This cose should never be reached, but just in case we throw a generic + // This case should never be reached, but just in case we throw a generic // internal error if (!asset) { throw new VError({ @@ -306,7 +316,7 @@ const WithdrawForm: React.FC = ({ asset, pool, onCloseModal } }); if (formValues.receiveNativeToken) { - return redeemAndUnwrap({ + return redeemUnderlyingAndUnwrap({ amountMantissa: withdrawAmountMantissa, }); } @@ -326,7 +336,7 @@ const WithdrawForm: React.FC = ({ asset, pool, onCloseModal } return redeem({ amountMantissa: vTokenBalanceMantissa }); } - // This cose should never be reached, but just in case we throw a generic + // This case should never be reached, but just in case we throw a generic // internal error throw new VError({ type: 'unexpected', From faa394ec9bf3e84e0d2c98dc491f71fd1d9a8a26 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Wed, 13 Mar 2024 10:51:44 -0600 Subject: [PATCH 12/20] feat: hook up RepayForm with getUniqueTokenBalances --- .../Modal/RepayForm/index.tsx | 52 ++++++++++++------- .../Modal/RepayForm/useForm/index.tsx | 2 +- .../RepayForm/useForm/useFormValidation.ts | 13 ++--- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx index a3df03daef..147a7e46f8 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx @@ -36,6 +36,7 @@ import { convertMantissaToTokens, convertTokensToMantissa, formatPercentageToReadableValue, + getUniqueTokenBalances, } from 'utilities'; import { useStyles as useSharedStyles } from '../styles'; @@ -108,7 +109,7 @@ export const RepayFormUi: React.FC = ({ const styles = useStyles(); const tokenBalances = useMemo( - () => [...integratedSwapTokenBalances, ...nativeWrappedTokenBalances], + () => getUniqueTokenBalances(...integratedSwapTokenBalances, ...nativeWrappedTokenBalances), [integratedSwapTokenBalances, nativeWrappedTokenBalances], ); @@ -372,11 +373,15 @@ const RepayForm: React.FC = ({ asset, pool, onCloseModal }) => { const isIntegratedSwapFeatureEnabled = useIsFeatureEnabled({ name: 'integratedSwap' }); const { accountAddress } = useAccountAddress(); - const [formValues, setFormValues] = useState({ - amountTokens: '', - fromToken: asset.vToken.underlyingToken, - fixedRepayPercentage: undefined, - }); + const { data: nativeTokenBalanceData } = useGetBalanceOf( + { + accountAddress: accountAddress || '', + token: asset.vToken.underlyingToken.tokenWrapped, + }, + { + enabled: isWrapUnwrapNativeTokenEnabled && !!asset.vToken.underlyingToken.tokenWrapped, + }, + ); const nativeTokenGatewayContractAddress = useGetNativeTokenGatewayContractAddress({ comptrollerContractAddress: pool.comptrollerAddress, @@ -387,6 +392,27 @@ const RepayForm: React.FC = ({ asset, pool, onCloseModal }) => { [isWrapUnwrapNativeTokenEnabled, asset.vToken.underlyingToken.tokenWrapped], ); + const nativeTokenBalanceTokens = useMemo(() => { + return nativeTokenBalanceData + ? convertMantissaToTokens({ + token: asset.vToken.underlyingToken.tokenWrapped, + value: nativeTokenBalanceData?.balanceMantissa, + }) + : undefined; + }, [asset.vToken.underlyingToken.tokenWrapped, nativeTokenBalanceData]); + + const shouldSelectNativeToken = + canWrapNativeToken && nativeTokenBalanceTokens?.gte(asset.userWalletBalanceTokens); + + const [formValues, setFormValues] = useState({ + amountTokens: '', + fromToken: + shouldSelectNativeToken && asset.vToken.underlyingToken.tokenWrapped + ? asset.vToken.underlyingToken.tokenWrapped + : asset.vToken.underlyingToken, + fixedRepayPercentage: undefined, + }); + const swapRouterContractAddress = useGetSwapRouterContractAddress({ comptrollerContractAddress: pool.comptrollerAddress, }); @@ -464,23 +490,13 @@ const RepayForm: React.FC = ({ asset, pool, onCloseModal }) => { const isSubmitting = isRepayLoading || isSwapAndRepayLoading || isWrapAndRepayLoading; - const { data: nativeTokenBalanceData } = useGetBalanceOf( - { - accountAddress: accountAddress || '', - token: asset.vToken.underlyingToken.tokenWrapped, - }, - { - enabled: isWrapUnwrapNativeTokenEnabled && !!asset.vToken.underlyingToken.tokenWrapped, - }, - ); - const nativeWrappedTokenBalances: TokenBalance[] = useMemo(() => { if (asset.vToken.underlyingToken.tokenWrapped && nativeTokenBalanceData) { const marketTokenBalance: TokenBalance = { token: asset.vToken.underlyingToken, balanceMantissa: convertTokensToMantissa({ token: asset.vToken.underlyingToken, - value: asset.userSupplyBalanceTokens, + value: asset.userWalletBalanceTokens, }), }; const nativeTokenBalance: TokenBalance = { @@ -490,7 +506,7 @@ const RepayForm: React.FC = ({ asset, pool, onCloseModal }) => { return [marketTokenBalance, nativeTokenBalance]; } return []; - }, [asset.userSupplyBalanceTokens, asset.vToken.underlyingToken, nativeTokenBalanceData]); + }, [asset.userWalletBalanceTokens, asset.vToken.underlyingToken, nativeTokenBalanceData]); const { data: integratedSwapTokenBalancesData } = useGetSwapTokenUserBalances({ accountAddress, diff --git a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/useForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/useForm/index.tsx index d1af18a81d..fd506f05f7 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/useForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/useForm/index.tsx @@ -56,11 +56,11 @@ const useForm = ({ const isMounted = useIsMounted(); const { isFormValid, formError } = useFormValidation({ - toToken: toVToken.underlyingToken, formValues, swap, swapError, isFromTokenApproved, + isUsingSwap, fromTokenUserWalletBalanceTokens, fromTokenUserBorrowBalanceTokens, fromTokenWalletSpendingLimitTokens, diff --git a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/useForm/useFormValidation.ts b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/useForm/useFormValidation.ts index d5ca48d93b..e13be05438 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/useForm/useFormValidation.ts +++ b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/useForm/useFormValidation.ts @@ -2,19 +2,18 @@ import BigNumber from 'bignumber.js'; import { useMemo } from 'react'; import { MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE } from 'constants/swap'; -import type { Swap, SwapError, Token } from 'types'; -import { areTokensEqual } from 'utilities'; +import type { Swap, SwapError } from 'types'; import { getSwapToTokenAmountReceivedTokens } from '../../getSwapToTokenAmountReceived'; import type { FormError, FormValues } from './types'; interface UseFormValidationInput { formValues: FormValues; - toToken: Token; fromTokenUserWalletBalanceTokens?: BigNumber; fromTokenUserBorrowBalanceTokens?: BigNumber; fromTokenWalletSpendingLimitTokens?: BigNumber; isFromTokenApproved?: boolean; + isUsingSwap: boolean; swap?: Swap; swapError?: SwapError; } @@ -27,9 +26,9 @@ interface UseFormValidationOutput { const useFormValidation = ({ swap, swapError, - toToken, formValues, isFromTokenApproved, + isUsingSwap, fromTokenUserWalletBalanceTokens, fromTokenUserBorrowBalanceTokens, fromTokenWalletSpendingLimitTokens, @@ -43,7 +42,7 @@ const useFormValidation = ({ UNWRAPPING_UNSUPPORTED: 'SWAP_UNWRAPPING_UNSUPPORTED', }; - if (swapError && swapError in swapErrorMapping) { + if (isUsingSwap && swapError && swapError in swapErrorMapping) { return swapErrorMapping[swapError]; } @@ -62,7 +61,6 @@ const useFormValidation = ({ return 'HIGHER_THAN_WALLET_BALANCE'; } - const isUsingSwap = !areTokensEqual(formValues.fromToken, toToken); const toTokensAmountRepaidTokens = isUsingSwap ? getSwapToTokenAmountReceivedTokens(swap).swapToTokenAmountReceivedTokens : fromTokenAmountTokens; @@ -94,11 +92,10 @@ const useFormValidation = ({ fromTokenUserWalletBalanceTokens, fromTokenWalletSpendingLimitTokens, isFromTokenApproved, + isUsingSwap, formValues.amountTokens, - formValues.fromToken, swap, swapError, - toToken, ]); return { From dc28849a68f865b431e9f66a9a8447f2b318ec9d Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Wed, 13 Mar 2024 10:52:12 -0600 Subject: [PATCH 13/20] feat: hook up SupplyForm with getUniqueTokenBalances --- .../Modal/SupplyForm/index.tsx | 61 +++++++++++++------ .../Modal/SupplyForm/useForm/index.tsx | 3 + .../SupplyForm/useForm/useFormValidation.ts | 9 ++- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx index 1c3de78c85..2722fc8c6b 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx @@ -36,7 +36,12 @@ import { isTokenActionEnabled } from 'libs/tokens'; import { useTranslation } from 'libs/translations'; import { useAccountAddress, useChainId } from 'libs/wallet'; import type { Asset, Pool, Swap, SwapError, TokenBalance } from 'types'; -import { areTokensEqual, convertMantissaToTokens, convertTokensToMantissa } from 'utilities'; +import { + areTokensEqual, + convertMantissaToTokens, + convertTokensToMantissa, + getUniqueTokenBalances, +} from 'utilities'; import { useStyles as useSharedStyles } from '../styles'; import Notice from './Notice'; @@ -110,7 +115,7 @@ export const SupplyFormUi: React.FC = ({ const { CollateralModal, toggleCollateral } = useCollateral(); const tokenBalances = useMemo( - () => [...integratedSwapTokenBalances, ...nativeWrappedTokenBalances], + () => getUniqueTokenBalances(...integratedSwapTokenBalances, ...nativeWrappedTokenBalances), [integratedSwapTokenBalances, nativeWrappedTokenBalances], ); @@ -166,6 +171,7 @@ export const SupplyFormUi: React.FC = ({ fromTokenUserWalletBalanceTokens, fromTokenWalletSpendingLimitTokens, isFromTokenApproved, + isUsingSwap, swap, swapError, onCloseModal, @@ -402,10 +408,15 @@ const SupplyForm: React.FC = ({ asset, pool, onCloseModal }) => const { accountAddress } = useAccountAddress(); const { chainId } = useChainId(); - const [formValues, setFormValues] = useState({ - amountTokens: '', - fromToken: asset.vToken.underlyingToken, - }); + const { data: nativeTokenBalanceData } = useGetBalanceOf( + { + accountAddress: accountAddress || '', + token: asset.vToken.underlyingToken.tokenWrapped, + }, + { + enabled: isWrapUnwrapNativeTokenEnabled && !!asset.vToken.underlyingToken.tokenWrapped, + }, + ); const nativeTokenGatewayContractAddress = useGetNativeTokenGatewayContractAddress({ comptrollerContractAddress: pool.comptrollerAddress, @@ -434,6 +445,26 @@ const SupplyForm: React.FC = ({ asset, pool, onCloseModal }) => [isWrapUnwrapNativeTokenEnabled, asset.vToken.underlyingToken.tokenWrapped], ); + const nativeTokenBalanceTokens = useMemo(() => { + return nativeTokenBalanceData + ? convertMantissaToTokens({ + token: asset.vToken.underlyingToken.tokenWrapped, + value: nativeTokenBalanceData?.balanceMantissa, + }) + : undefined; + }, [asset.vToken.underlyingToken.tokenWrapped, nativeTokenBalanceData]); + + const shouldSelectNativeToken = + canWrapNativeToken && nativeTokenBalanceTokens?.gte(asset.userWalletBalanceTokens); + + const [formValues, setFormValues] = useState({ + amountTokens: '', + fromToken: + shouldSelectNativeToken && asset.vToken.underlyingToken.tokenWrapped + ? asset.vToken.underlyingToken.tokenWrapped + : asset.vToken.underlyingToken, + }); + // a user is trying to wrap the chain's native token if // 1) the wrap/unwrap feature is enabled // 2) the selected form token is the native token @@ -446,8 +477,14 @@ const SupplyForm: React.FC = ({ asset, pool, onCloseModal }) => const isUsingSwap = useMemo( () => isIntegratedSwapFeatureEnabled && + !isWrappingNativeToken && !areTokensEqual(asset.vToken.underlyingToken, formValues.fromToken), - [isIntegratedSwapFeatureEnabled, formValues.fromToken, asset.vToken.underlyingToken], + [ + isIntegratedSwapFeatureEnabled, + formValues.fromToken, + asset.vToken.underlyingToken, + isWrappingNativeToken, + ], ); const swapRouterContractAddress = useGetSwapRouterContractAddress({ @@ -485,16 +522,6 @@ const SupplyForm: React.FC = ({ asset, pool, onCloseModal }) => accountAddress, }); - const { data: nativeTokenBalanceData } = useGetBalanceOf( - { - accountAddress: accountAddress || '', - token: asset.vToken.underlyingToken.tokenWrapped, - }, - { - enabled: isWrapUnwrapNativeTokenEnabled && !!asset.vToken.underlyingToken.tokenWrapped, - }, - ); - const nativeWrappedTokenBalances: TokenBalance[] = useMemo(() => { if (asset.vToken.underlyingToken.tokenWrapped && nativeTokenBalanceData) { const marketTokenBalance: TokenBalance = { diff --git a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/useForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/useForm/index.tsx index 85569ec351..77c1007942 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/useForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/useForm/index.tsx @@ -19,6 +19,7 @@ export interface UseFormInput { formValues: FormValues; setFormValues: (setter: (currentFormValues: FormValues) => FormValues | FormValues) => void; isFromTokenApproved?: boolean; + isUsingSwap: boolean; fromTokenWalletSpendingLimitTokens?: BigNumber; fromTokenUserWalletBalanceTokens?: BigNumber; swap?: Swap; @@ -36,6 +37,7 @@ const useForm = ({ fromTokenUserWalletBalanceTokens = new BigNumber(0), fromTokenWalletSpendingLimitTokens, isFromTokenApproved, + isUsingSwap, onCloseModal, swap, swapError, @@ -49,6 +51,7 @@ const useForm = ({ swap, swapError, isFromTokenApproved, + isUsingSwap, fromTokenWalletSpendingLimitTokens, fromTokenUserWalletBalanceTokens, }); diff --git a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/useForm/useFormValidation.ts b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/useForm/useFormValidation.ts index 3997d05adb..9171faf8d8 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/useForm/useFormValidation.ts +++ b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/useForm/useFormValidation.ts @@ -3,7 +3,6 @@ import { useMemo } from 'react'; import { MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE } from 'constants/swap'; import type { Asset, Swap, SwapError } from 'types'; -import { areTokensEqual } from 'utilities'; import { getSwapToTokenAmountReceivedTokens } from '../../getSwapToTokenAmountReceived'; import type { FormError, FormValues } from './types'; @@ -14,6 +13,7 @@ interface UseFormValidationInput { fromTokenUserWalletBalanceTokens?: BigNumber; fromTokenWalletSpendingLimitTokens?: BigNumber; isFromTokenApproved?: boolean; + isUsingSwap: boolean; swap?: Swap; swapError?: SwapError; } @@ -29,6 +29,7 @@ const useFormValidation = ({ swapError, formValues, isFromTokenApproved, + isUsingSwap, fromTokenUserWalletBalanceTokens, fromTokenWalletSpendingLimitTokens, }: UseFormValidationInput): UseFormValidationOutput => { @@ -41,7 +42,7 @@ const useFormValidation = ({ UNWRAPPING_UNSUPPORTED: 'SWAP_UNWRAPPING_UNSUPPORTED', }; - if (swapError && swapError in swapErrorMapping) { + if (isUsingSwap && swapError && swapError in swapErrorMapping) { return swapErrorMapping[swapError]; } @@ -67,7 +68,6 @@ const useFormValidation = ({ return 'HIGHER_THAN_WALLET_BALANCE'; } - const isUsingSwap = !areTokensEqual(formValues.fromToken, asset.vToken.underlyingToken); const toTokensAmountSuppliedTokens = isUsingSwap ? getSwapToTokenAmountReceivedTokens(swap).swapToTokenAmountReceivedTokens : fromTokenAmountTokens; @@ -97,14 +97,13 @@ const useFormValidation = ({ return 'PRICE_IMPACT_TOO_HIGH'; } }, [ - asset.vToken.underlyingToken, asset.supplyCapTokens, asset.supplyBalanceTokens, fromTokenUserWalletBalanceTokens, fromTokenWalletSpendingLimitTokens, isFromTokenApproved, + isUsingSwap, formValues.amountTokens, - formValues.fromToken, swap, swapError, ]); From b25390f4845ac4e7a4584eae23d34323eed9f4d8 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Wed, 13 Mar 2024 14:00:58 -0600 Subject: [PATCH 14/20] test: update integrated swap tests to select a different token --- .../__tests__/indexIntegratedSwap.spec.tsx | 8 +++++++- .../__tests__/indexIntegratedSwap.spec.tsx | 15 +++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/__tests__/indexIntegratedSwap.spec.tsx b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/__tests__/indexIntegratedSwap.spec.tsx index 4c93d25e8e..0fb71b02b9 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/__tests__/indexIntegratedSwap.spec.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/__tests__/indexIntegratedSwap.spec.tsx @@ -214,13 +214,19 @@ describe('RepayForm - Feature flag enabled: integratedSwap', () => { isLoading: false, })); - const { getByTestId, getByText } = renderComponent( + const { getByTestId, getByText, container } = renderComponent( , { accountAddress: fakeAccountAddress, }, ); + selectToken({ + container, + selectTokenTextFieldTestId: TEST_IDS.selectTokenTextField, + token: busd, + }); + const selectTokenTextField = getByTestId( getTokenTextFieldTestId({ parentTestId: TEST_IDS.selectTokenTextField, diff --git a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx index 6315d8f159..1d34d70956 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx @@ -60,9 +60,10 @@ vi.mock('hooks/useGetSwapRouterContractAddress'); describe('hooks/useSupplyWithdrawModal/Supply - Feature flag enabled: integratedSwap', () => { beforeEach(() => { - (useIsFeatureEnabled as Vi.Mock).mockImplementation( - ({ name }: UseIsFeatureEnabled) => name === 'integratedSwap', - ); + (useIsFeatureEnabled as Vi.Mock).mockImplementation(({ name }: UseIsFeatureEnabled) => { + console.log('powerslave222', name); + return name === 'integratedSwap'; + }); (useGetSwapInfo as Vi.Mock).mockImplementation(() => ({ swap: undefined, @@ -192,13 +193,19 @@ describe('hooks/useSupplyWithdrawModal/Supply - Feature flag enabled: integrated isLoading: false, })); - const { getByTestId, getByText } = renderComponent( + const { getByTestId, getByText, container } = renderComponent( , { accountAddress: fakeAccountAddress, }, ); + selectToken({ + container, + selectTokenTextFieldTestId: TEST_IDS.selectTokenTextField, + token: busd, + }); + const selectTokenTextField = getByTestId( getTokenTextFieldTestId({ parentTestId: TEST_IDS.selectTokenTextField, From eb990dab80379eefee432fd798d6d4c67f083cc8 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Wed, 13 Mar 2024 18:07:57 -0600 Subject: [PATCH 15/20] chore: add changeset --- .changeset/swift-suns-unite.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/swift-suns-unite.md diff --git a/.changeset/swift-suns-unite.md b/.changeset/swift-suns-unite.md new file mode 100644 index 0000000000..b33aeeaa2f --- /dev/null +++ b/.changeset/swift-suns-unite.md @@ -0,0 +1,5 @@ +--- +"@venusprotocol/evm": minor +--- + +unwrap native token in borrow/withdraw operations From 938cad6afd2827aa4f76e8b1acdb754af76a588b Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 14 Mar 2024 09:16:02 -0600 Subject: [PATCH 16/20] test: add tests for borrow/withdraw to native tokens --- apps/evm/src/clients/api/__mocks__/index.ts | 5 +- .../__tests__/indexWrapUnwrapNative.spec.tsx | 45 ++++++++++- .../Modal/BorrowForm/index.tsx | 2 +- .../__tests__/indexWrapUnwrapNative.spec.tsx | 81 +++++++++++++++++++ .../Modal/WithdrawForm/index.tsx | 2 +- 5 files changed, 128 insertions(+), 7 deletions(-) diff --git a/apps/evm/src/clients/api/__mocks__/index.ts b/apps/evm/src/clients/api/__mocks__/index.ts index 013cb9b943..7d2153ac03 100644 --- a/apps/evm/src/clients/api/__mocks__/index.ts +++ b/apps/evm/src/clients/api/__mocks__/index.ts @@ -60,8 +60,9 @@ export const useGetPendingRewards = () => useQuery(FunctionKey.GET_PENDING_REWARDS, getPendingRewards); export const getVTokenBalanceOf = vi.fn(); -export const useGetVTokenBalanceOf = () => - useQuery(FunctionKey.GET_V_TOKEN_BALANCE, getVTokenBalanceOf); +export const useGetVTokenBalanceOf = vi.fn(() => + useQuery(FunctionKey.GET_V_TOKEN_BALANCE, getVTokenBalanceOf), +); export const getAllowance = vi.fn(); export const useGetAllowance = () => useQuery(FunctionKey.GET_TOKEN_ALLOWANCE, getAllowance); diff --git a/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx b/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx index 0c159eaa84..54e83cbb71 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx @@ -1,12 +1,15 @@ -import noop from 'noop-ts'; -import type Vi from 'vitest'; - +import { fireEvent, waitFor } from '@testing-library/react'; import fakeAccountAddress from '__mocks__/models/address'; +import noop from 'noop-ts'; import { renderComponent } from 'testUtils/render'; +import type Vi from 'vitest'; import { type UseIsFeatureEnabled, useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; import { ChainId } from 'types'; +import BigNumber from 'bignumber.js'; +import { borrowAndUnwrap } from 'clients/api'; +import { en } from 'libs/translations'; import Borrow from '..'; import { fakeAsset, fakePool, fakeWethAsset } from '../__testUtils__/fakeData'; import TEST_IDS from '../testIds'; @@ -50,4 +53,40 @@ describe('BorrowForm - Feature flag enabled: wrapUnwrapNativeToken', () => { expect(queryByTestId(TEST_IDS.receiveNativeToken)).toBeVisible(); }); + + it('lets the user borrow native tokens by unwrapping', async () => { + const onCloseMock = vi.fn(); + const { getByText, getByTestId, getByRole } = renderComponent( + , + { + chainId: ChainId.SEPOLIA, + accountAddress: fakeAccountAddress, + }, + ); + + // click on receive native token + const receiveNativeTokenSwitch = getByRole('checkbox'); + fireEvent.click(receiveNativeTokenSwitch); + + // Enter amount in input + const correctAmountTokens = 1; + fireEvent.change(getByTestId(TEST_IDS.tokenTextField), { + target: { value: correctAmountTokens }, + }); + + // Click on submit button + await waitFor(() => getByText(en.operationModal.borrow.submitButtonLabel.borrow)); + fireEvent.click(getByText(en.operationModal.borrow.submitButtonLabel.borrow)); + + const expectedAmountMantissa = new BigNumber(correctAmountTokens).multipliedBy( + new BigNumber(10).pow(fakeAsset.vToken.underlyingToken.decimals), + ); + + await waitFor(() => expect(borrowAndUnwrap).toHaveBeenCalledTimes(1)); + expect(borrowAndUnwrap).toHaveBeenCalledWith({ + amountMantissa: expectedAmountMantissa, + }); + + expect(onCloseMock).toHaveBeenCalledTimes(1); + }); }); diff --git a/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx index 566acd80c9..ad0e42d9ac 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx @@ -58,7 +58,7 @@ export const BorrowFormUi: React.FC = ({ [isWrapUnwrapNativeTokenEnabled, asset.vToken.underlyingToken.tokenWrapped], ); - const handleToggleReceiveNativeToken = async () => { + const handleToggleReceiveNativeToken = () => { setFormValues(currentFormValues => ({ ...currentFormValues, receiveNativeToken: !currentFormValues.receiveNativeToken, diff --git a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx index cfd793e013..792010ffc2 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx @@ -7,6 +7,10 @@ import { renderComponent } from 'testUtils/render'; import { type UseIsFeatureEnabled, useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; import { ChainId } from 'types'; +import { fireEvent, waitFor } from '@testing-library/react'; +import BigNumber from 'bignumber.js'; +import { redeemAndUnwrap, redeemUnderlyingAndUnwrap, useGetVTokenBalanceOf } from 'clients/api'; +import { en } from 'libs/translations'; import Withdraw from '..'; import { fakeAsset, fakePool, fakeWethAsset } from '../__testUtils__/fakeData'; import TEST_IDS from '../testIds'; @@ -50,4 +54,81 @@ describe('WithdrawForm - Feature flag enabled: wrapUnwrapNativeToken', () => { expect(queryByTestId(TEST_IDS.receiveNativeToken)).toBeVisible(); }); + + it('lets the user partially withdraw directly to native tokens by unwrapping', async () => { + const onCloseMock = vi.fn(); + const { getByText, getByTestId, getByRole } = renderComponent( + , + { + chainId: ChainId.SEPOLIA, + accountAddress: fakeAccountAddress, + }, + ); + + // click on receive native token + const receiveNativeTokenSwitch = getByRole('checkbox'); + fireEvent.click(receiveNativeTokenSwitch); + + // Enter amount in input + const correctAmountTokens = 1; + fireEvent.change(getByTestId(TEST_IDS.valueInput), { + target: { value: correctAmountTokens }, + }); + + // Click on submit button + await waitFor(() => getByText(en.operationModal.withdraw.submitButtonLabel.withdraw)); + fireEvent.click(getByText(en.operationModal.withdraw.submitButtonLabel.withdraw)); + + const expectedAmountMantissa = new BigNumber(correctAmountTokens).multipliedBy( + new BigNumber(10).pow(fakeAsset.vToken.underlyingToken.decimals), + ); + + await waitFor(() => expect(redeemUnderlyingAndUnwrap).toHaveBeenCalledTimes(1)); + expect(redeemUnderlyingAndUnwrap).toHaveBeenCalledWith({ + amountMantissa: expectedAmountMantissa, + }); + + expect(onCloseMock).toHaveBeenCalledTimes(1); + }); + + it('lets the user withdraw all their tokens directly to native tokens by unwrapping', async () => { + // simulate a pool where the user can withdraw all their supplied tokens + const fakePoolWithLiquidity = { + ...fakePool, + userBorrowBalanceCents: new BigNumber(0), + }; + // simulate the total amount of VTokens the user has and will be redeemed + const fakeVTokenBalanceMantissa = new BigNumber(1234); + (useGetVTokenBalanceOf as Vi.Mock).mockImplementation(() => ({ + data: { + balanceMantissa: fakeVTokenBalanceMantissa, + }, + })); + const onCloseMock = vi.fn(); + const { getByText, getByRole } = renderComponent( + , + { + chainId: ChainId.SEPOLIA, + accountAddress: fakeAccountAddress, + }, + ); + + // click on receive native token + const receiveNativeTokenSwitch = getByRole('checkbox'); + fireEvent.click(receiveNativeTokenSwitch); + + // click on MAX button + fireEvent.click(getByText(en.operationModal.withdraw.rightMaxButtonLabel)); + + // Click on submit button + await waitFor(() => getByText(en.operationModal.withdraw.submitButtonLabel.withdraw)); + fireEvent.click(getByText(en.operationModal.withdraw.submitButtonLabel.withdraw)); + + await waitFor(() => expect(redeemAndUnwrap).toHaveBeenCalledTimes(1)); + expect(redeemAndUnwrap).toHaveBeenCalledWith({ + amountMantissa: fakeVTokenBalanceMantissa, + }); + + expect(onCloseMock).toHaveBeenCalledTimes(1); + }); }); diff --git a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx index f5d9490704..af7ed14f84 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx @@ -65,7 +65,7 @@ export const WithdrawFormUi: React.FC = ({ [isWrapUnwrapNativeTokenEnabled, asset.vToken.underlyingToken.tokenWrapped], ); - const handleToggleReceiveNativeToken = async () => { + const handleToggleReceiveNativeToken = () => { setFormValues(currentFormValues => ({ ...currentFormValues, receiveNativeToken: !currentFormValues.receiveNativeToken, From 7313e8191604a0907b8c564cced0d2bbe6cd1a66 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Thu, 14 Mar 2024 10:00:48 -0600 Subject: [PATCH 17/20] fix: isDelegateApproved is undefined when query is disabled --- apps/evm/src/hooks/useDelegateApproval/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/evm/src/hooks/useDelegateApproval/index.ts b/apps/evm/src/hooks/useDelegateApproval/index.ts index baf04221b3..339f554fe4 100644 --- a/apps/evm/src/hooks/useDelegateApproval/index.ts +++ b/apps/evm/src/hooks/useDelegateApproval/index.ts @@ -44,9 +44,7 @@ const useDelegateApproval = ({ }, ); - const isDelegateApproved = isDelegateApprovedData - ? isDelegateApprovedData.isDelegateeApproved - : undefined; + const isDelegateApproved = enabled ? isDelegateApprovedData?.isDelegateeApproved : undefined; return { updatePoolDelegateStatus, From 55e40e09979875aae79864f94b0bf279c1b07a16 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Fri, 15 Mar 2024 08:13:33 -0600 Subject: [PATCH 18/20] refactor: prefix variables with userWallet --- .../Modal/RepayForm/index.tsx | 22 +++++++++++-------- .../Modal/SupplyForm/index.tsx | 22 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx index 147a7e46f8..de7d039848 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx @@ -373,7 +373,7 @@ const RepayForm: React.FC = ({ asset, pool, onCloseModal }) => { const isIntegratedSwapFeatureEnabled = useIsFeatureEnabled({ name: 'integratedSwap' }); const { accountAddress } = useAccountAddress(); - const { data: nativeTokenBalanceData } = useGetBalanceOf( + const { data: userWalletNativeTokenBalanceData } = useGetBalanceOf( { accountAddress: accountAddress || '', token: asset.vToken.underlyingToken.tokenWrapped, @@ -392,17 +392,17 @@ const RepayForm: React.FC = ({ asset, pool, onCloseModal }) => { [isWrapUnwrapNativeTokenEnabled, asset.vToken.underlyingToken.tokenWrapped], ); - const nativeTokenBalanceTokens = useMemo(() => { - return nativeTokenBalanceData + const userWalletNativeTokenBalanceTokens = useMemo(() => { + return userWalletNativeTokenBalanceData ? convertMantissaToTokens({ token: asset.vToken.underlyingToken.tokenWrapped, - value: nativeTokenBalanceData?.balanceMantissa, + value: userWalletNativeTokenBalanceData?.balanceMantissa, }) : undefined; - }, [asset.vToken.underlyingToken.tokenWrapped, nativeTokenBalanceData]); + }, [asset.vToken.underlyingToken.tokenWrapped, userWalletNativeTokenBalanceData]); const shouldSelectNativeToken = - canWrapNativeToken && nativeTokenBalanceTokens?.gte(asset.userWalletBalanceTokens); + canWrapNativeToken && userWalletNativeTokenBalanceTokens?.gte(asset.userWalletBalanceTokens); const [formValues, setFormValues] = useState({ amountTokens: '', @@ -491,7 +491,7 @@ const RepayForm: React.FC = ({ asset, pool, onCloseModal }) => { const isSubmitting = isRepayLoading || isSwapAndRepayLoading || isWrapAndRepayLoading; const nativeWrappedTokenBalances: TokenBalance[] = useMemo(() => { - if (asset.vToken.underlyingToken.tokenWrapped && nativeTokenBalanceData) { + if (asset.vToken.underlyingToken.tokenWrapped && userWalletNativeTokenBalanceData) { const marketTokenBalance: TokenBalance = { token: asset.vToken.underlyingToken, balanceMantissa: convertTokensToMantissa({ @@ -501,12 +501,16 @@ const RepayForm: React.FC = ({ asset, pool, onCloseModal }) => { }; const nativeTokenBalance: TokenBalance = { token: asset.vToken.underlyingToken.tokenWrapped, - balanceMantissa: nativeTokenBalanceData.balanceMantissa, + balanceMantissa: userWalletNativeTokenBalanceData.balanceMantissa, }; return [marketTokenBalance, nativeTokenBalance]; } return []; - }, [asset.userWalletBalanceTokens, asset.vToken.underlyingToken, nativeTokenBalanceData]); + }, [ + asset.userWalletBalanceTokens, + asset.vToken.underlyingToken, + userWalletNativeTokenBalanceData, + ]); const { data: integratedSwapTokenBalancesData } = useGetSwapTokenUserBalances({ accountAddress, diff --git a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx index 2722fc8c6b..ec6a8c42ba 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx @@ -408,7 +408,7 @@ const SupplyForm: React.FC = ({ asset, pool, onCloseModal }) => const { accountAddress } = useAccountAddress(); const { chainId } = useChainId(); - const { data: nativeTokenBalanceData } = useGetBalanceOf( + const { data: userWalletNativeTokenBalanceData } = useGetBalanceOf( { accountAddress: accountAddress || '', token: asset.vToken.underlyingToken.tokenWrapped, @@ -445,17 +445,17 @@ const SupplyForm: React.FC = ({ asset, pool, onCloseModal }) => [isWrapUnwrapNativeTokenEnabled, asset.vToken.underlyingToken.tokenWrapped], ); - const nativeTokenBalanceTokens = useMemo(() => { - return nativeTokenBalanceData + const userWalletNativeTokenBalanceTokens = useMemo(() => { + return userWalletNativeTokenBalanceData ? convertMantissaToTokens({ token: asset.vToken.underlyingToken.tokenWrapped, - value: nativeTokenBalanceData?.balanceMantissa, + value: userWalletNativeTokenBalanceData?.balanceMantissa, }) : undefined; - }, [asset.vToken.underlyingToken.tokenWrapped, nativeTokenBalanceData]); + }, [asset.vToken.underlyingToken.tokenWrapped, userWalletNativeTokenBalanceData]); const shouldSelectNativeToken = - canWrapNativeToken && nativeTokenBalanceTokens?.gte(asset.userWalletBalanceTokens); + canWrapNativeToken && userWalletNativeTokenBalanceTokens?.gte(asset.userWalletBalanceTokens); const [formValues, setFormValues] = useState({ amountTokens: '', @@ -523,7 +523,7 @@ const SupplyForm: React.FC = ({ asset, pool, onCloseModal }) => }); const nativeWrappedTokenBalances: TokenBalance[] = useMemo(() => { - if (asset.vToken.underlyingToken.tokenWrapped && nativeTokenBalanceData) { + if (asset.vToken.underlyingToken.tokenWrapped && userWalletNativeTokenBalanceData) { const marketTokenBalance: TokenBalance = { token: asset.vToken.underlyingToken, balanceMantissa: convertTokensToMantissa({ @@ -533,12 +533,16 @@ const SupplyForm: React.FC = ({ asset, pool, onCloseModal }) => }; const nativeTokenBalance: TokenBalance = { token: asset.vToken.underlyingToken.tokenWrapped, - balanceMantissa: nativeTokenBalanceData.balanceMantissa, + balanceMantissa: userWalletNativeTokenBalanceData.balanceMantissa, }; return [marketTokenBalance, nativeTokenBalance]; } return []; - }, [asset.vToken.underlyingToken, asset.userWalletBalanceTokens, nativeTokenBalanceData]); + }, [ + asset.vToken.underlyingToken, + asset.userWalletBalanceTokens, + userWalletNativeTokenBalanceData, + ]); const { data: integratedSwapTokenBalancesData } = useGetSwapTokenUserBalances({ accountAddress, From 461642720753a1c4372c6cae366d4e4bfee99b06 Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Fri, 15 Mar 2024 08:18:49 -0600 Subject: [PATCH 19/20] feat: receiveNativeToken on by default in wrapped native token markets --- .../__tests__/indexWrapUnwrapNative.spec.tsx | 6 ++---- .../useOperationModal/Modal/BorrowForm/index.tsx | 2 +- .../__tests__/indexWrapUnwrapNative.spec.tsx | 12 ++++-------- .../useOperationModal/Modal/WithdrawForm/index.tsx | 2 +- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx b/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx index 54e83cbb71..f8b6347625 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/__tests__/indexWrapUnwrapNative.spec.tsx @@ -56,7 +56,7 @@ describe('BorrowForm - Feature flag enabled: wrapUnwrapNativeToken', () => { it('lets the user borrow native tokens by unwrapping', async () => { const onCloseMock = vi.fn(); - const { getByText, getByTestId, getByRole } = renderComponent( + const { getByText, getByTestId } = renderComponent( , { chainId: ChainId.SEPOLIA, @@ -64,9 +64,7 @@ describe('BorrowForm - Feature flag enabled: wrapUnwrapNativeToken', () => { }, ); - // click on receive native token - const receiveNativeTokenSwitch = getByRole('checkbox'); - fireEvent.click(receiveNativeTokenSwitch); + // receive native token is active by default, so no need to click on it // Enter amount in input const correctAmountTokens = 1; diff --git a/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx index ad0e42d9ac..2bcaf63737 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/BorrowForm/index.tsx @@ -241,7 +241,7 @@ const BorrowForm: React.FC = ({ asset, pool, onCloseModal }) => const [formValues, setFormValues] = useState({ amountTokens: '', fromToken: asset.vToken.underlyingToken, - receiveNativeToken: false, + receiveNativeToken: !!asset.vToken.underlyingToken.tokenWrapped, }); const { mutateAsync: borrow, isLoading: isBorrowLoading } = useBorrow({ diff --git a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx index 792010ffc2..e032551ead 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/__tests__/indexWrapUnwrapNative.spec.tsx @@ -57,7 +57,7 @@ describe('WithdrawForm - Feature flag enabled: wrapUnwrapNativeToken', () => { it('lets the user partially withdraw directly to native tokens by unwrapping', async () => { const onCloseMock = vi.fn(); - const { getByText, getByTestId, getByRole } = renderComponent( + const { getByText, getByTestId } = renderComponent( , { chainId: ChainId.SEPOLIA, @@ -65,9 +65,7 @@ describe('WithdrawForm - Feature flag enabled: wrapUnwrapNativeToken', () => { }, ); - // click on receive native token - const receiveNativeTokenSwitch = getByRole('checkbox'); - fireEvent.click(receiveNativeTokenSwitch); + // receive native token is active by default, so no need to click on it // Enter amount in input const correctAmountTokens = 1; @@ -105,7 +103,7 @@ describe('WithdrawForm - Feature flag enabled: wrapUnwrapNativeToken', () => { }, })); const onCloseMock = vi.fn(); - const { getByText, getByRole } = renderComponent( + const { getByText } = renderComponent( , { chainId: ChainId.SEPOLIA, @@ -113,9 +111,7 @@ describe('WithdrawForm - Feature flag enabled: wrapUnwrapNativeToken', () => { }, ); - // click on receive native token - const receiveNativeTokenSwitch = getByRole('checkbox'); - fireEvent.click(receiveNativeTokenSwitch); + // receive native token is active by default, so no need to click on it // click on MAX button fireEvent.click(getByText(en.operationModal.withdraw.rightMaxButtonLabel)); diff --git a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx index af7ed14f84..79fe8078cc 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/WithdrawForm/index.tsx @@ -238,7 +238,7 @@ const WithdrawForm: React.FC = ({ asset, pool, onCloseModal } const [formValues, setFormValues] = useState({ amountTokens: '', fromToken: asset.vToken.underlyingToken, - receiveNativeToken: false, + receiveNativeToken: !!asset.vToken.underlyingToken.tokenWrapped, }); const { data: getVTokenBalanceData } = useGetVTokenBalanceOf( From 9e5727e7116f189798ec7b3b8178f9a15373795f Mon Sep 17 00:00:00 2001 From: Gleiser Oliveira Date: Fri, 15 Mar 2024 09:47:47 -0600 Subject: [PATCH 20/20] feat: use gt instead of gte --- apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx | 2 +- apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx index de7d039848..d984e5d89b 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/RepayForm/index.tsx @@ -402,7 +402,7 @@ const RepayForm: React.FC = ({ asset, pool, onCloseModal }) => { }, [asset.vToken.underlyingToken.tokenWrapped, userWalletNativeTokenBalanceData]); const shouldSelectNativeToken = - canWrapNativeToken && userWalletNativeTokenBalanceTokens?.gte(asset.userWalletBalanceTokens); + canWrapNativeToken && userWalletNativeTokenBalanceTokens?.gt(asset.userWalletBalanceTokens); const [formValues, setFormValues] = useState({ amountTokens: '', diff --git a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx index ec6a8c42ba..84098ecf10 100644 --- a/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx +++ b/apps/evm/src/hooks/useOperationModal/Modal/SupplyForm/index.tsx @@ -455,7 +455,7 @@ const SupplyForm: React.FC = ({ asset, pool, onCloseModal }) => }, [asset.vToken.underlyingToken.tokenWrapped, userWalletNativeTokenBalanceData]); const shouldSelectNativeToken = - canWrapNativeToken && userWalletNativeTokenBalanceTokens?.gte(asset.userWalletBalanceTokens); + canWrapNativeToken && userWalletNativeTokenBalanceTokens?.gt(asset.userWalletBalanceTokens); const [formValues, setFormValues] = useState({ amountTokens: '',