From b0c31f79633d332cfcca6ac96ffb73dcc4d5dca5 Mon Sep 17 00:00:00 2001 From: Francis Rodriguez <39339295+Freshenext@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:21:07 -0300 Subject: [PATCH] =?UTF-8?q?US-1856=20Added=20security=20measure=20for=20bi?= =?UTF-8?q?tcoin=20where=20return=20remainder=20bal=E2=80=A6=20(#712)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * US-1856 Added security measure for bitcoin where return remainder balance will go to a new address of the user. Increased bitcoin decimals to 6 * Modified fetchAddressToReturnFundsTo * Increased package.json version * Fixed back button for bitcoin --- package.json | 4 ++-- src/redux/rootReducer.ts | 2 +- src/redux/slices/settingsSlice/index.ts | 8 +++++++ src/redux/slices/settingsSlice/types.ts | 1 + src/screens/home/index.tsx | 2 -- src/screens/send/bitcoinUtils.ts | 21 ++++++++++++++++ src/screens/send/transferBitcoin.ts | 10 +++++++- src/screens/send/usePaymentExecutor.ts | 32 ++++++++++++++++++++++--- yarn.lock | 16 ++++++------- 9 files changed, 79 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 4935303b3..3365ca3b6 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,10 @@ "@rsksmart/rif-relay-light-sdk": "^1.0.3", "@rsksmart/rif-wallet-abi-enhancer": "^1.0.4", "@rsksmart/rif-wallet-adapters": "^1.0.0", - "@rsksmart/rif-wallet-bitcoin": "^1.1.0", + "@rsksmart/rif-wallet-bitcoin": "^1.2.0", "@rsksmart/rif-wallet-core": "^1.0.2", "@rsksmart/rif-wallet-eip681": "1.0.1", - "@rsksmart/rif-wallet-services": "^1.0.4", + "@rsksmart/rif-wallet-services": "^1.0.5", "@rsksmart/rif-wallet-token": "^1.0.1", "@rsksmart/rlogin-dpath": "^1.0.1", "@rsksmart/rns-resolver.js": "^1.0.1", diff --git a/src/redux/rootReducer.ts b/src/redux/rootReducer.ts index 395c88483..fe7adb0a7 100644 --- a/src/redux/rootReducer.ts +++ b/src/redux/rootReducer.ts @@ -30,7 +30,7 @@ const migrations = { const settingsPersistConfig: PersistConfig = { key: 'settings', - whitelist: ['pin', 'chainId', 'isFirstLaunch'], + whitelist: ['pin', 'chainId', 'isFirstLaunch', 'usedBitcoinAddresses'], storage: reduxStorage, } diff --git a/src/redux/slices/settingsSlice/index.ts b/src/redux/slices/settingsSlice/index.ts index 3c3297018..477e75154 100644 --- a/src/redux/slices/settingsSlice/index.ts +++ b/src/redux/slices/settingsSlice/index.ts @@ -317,6 +317,7 @@ const initialState: SettingsSlice = { pin: null, bitcoin: null, chainId: 31, + usedBitcoinAddresses: {}, } const settingsSlice = createSlice({ @@ -428,6 +429,12 @@ const settingsSlice = createSlice({ setBitcoinState: (state, { payload }: PayloadAction) => { state.bitcoin = payload }, + addAddressToUsedBitcoinAddresses: ( + state, + { payload }: PayloadAction, + ) => { + state.usedBitcoinAddresses[payload] = payload + }, }, extraReducers(builder) { builder.addCase(createWallet.pending, state => { @@ -480,6 +487,7 @@ export const { setFullscreen, setHideBalance, setBitcoinState, + addAddressToUsedBitcoinAddresses, } = settingsSlice.actions export const settingsSliceReducer = settingsSlice.reducer diff --git a/src/redux/slices/settingsSlice/types.ts b/src/redux/slices/settingsSlice/types.ts index ce26411c2..7ef9bf5ee 100644 --- a/src/redux/slices/settingsSlice/types.ts +++ b/src/redux/slices/settingsSlice/types.ts @@ -85,4 +85,5 @@ export interface SettingsSlice { hideBalance: boolean pin: string | null bitcoin: Bitcoin | null + usedBitcoinAddresses: { [key: string]: string } } diff --git a/src/screens/home/index.tsx b/src/screens/home/index.tsx index eb9024653..0dbef27a7 100644 --- a/src/screens/home/index.tsx +++ b/src/screens/home/index.tsx @@ -38,7 +38,6 @@ import { selectTransactions } from 'store/slices/transactionsSlice' import { sharedColors } from 'shared/constants' import { castStyle } from 'shared/utils' import { ActivityBasicRow } from 'screens/activity/ActivityRow' -import { rootTabsRouteNames } from 'navigation/rootNavigator' import { HomeInformationBar } from './HomeInformationBar' import { getTokenColor } from './tokenColor' @@ -124,7 +123,6 @@ export const HomeScreen = ({ }) case 'SEND': return navigation.navigate(homeStackRouteNames.Send, { - backScreen: rootTabsRouteNames.Home, token: _selected?.symbol, contractAddress: _selected?.contractAddress, }) diff --git a/src/screens/send/bitcoinUtils.ts b/src/screens/send/bitcoinUtils.ts index a92f60a44..a4583cd41 100644 --- a/src/screens/send/bitcoinUtils.ts +++ b/src/screens/send/bitcoinUtils.ts @@ -9,6 +9,12 @@ interface FetchUtxoFunction { onSetBalance?: (balance: BigNumber) => void } +interface FetchAddressToReturnFundsToFunction { + token: BitcoinTokenBalanceObject + usedBitcoinAddresses: { [key: string]: string } + onSetAddress?: (address: string) => void +} + export const calculateBalanceFromUtxos = ( utxos: UnspentTransactionType[], ): BigNumber => @@ -35,3 +41,18 @@ export const fetchUtxo = ({ }) } } + +export const fetchAddressToReturnFundsTo = ({ + token, + onSetAddress, + usedBitcoinAddresses, +}: FetchAddressToReturnFundsToFunction) => { + token.bips[0].fetchExternalAvailableAddresses({}).then(addresses => { + for (const address of addresses) { + if (!usedBitcoinAddresses[address]) { + onSetAddress?.(address) + break + } + } + }) +} diff --git a/src/screens/send/transferBitcoin.ts b/src/screens/send/transferBitcoin.ts index 431c1b1ae..b15bfb8f9 100644 --- a/src/screens/send/transferBitcoin.ts +++ b/src/screens/send/transferBitcoin.ts @@ -13,9 +13,11 @@ interface ITransferBitcoin { btcToPay: number to: string utxos: Array + balance: number + addressToReturnRemainingAmount: string + onBitcoinTransactionSuccess?: (options: { addressUsed: string }) => void onSetError?: OnSetErrorFunction onSetCurrentTransaction?: OnSetCurrentTransactionFunction - balance: number } const MINIMUM_FEE = 141 // should be removed when estimate fee is up... @@ -28,6 +30,8 @@ export const transferBitcoin = ({ to, utxos, balance, + addressToReturnRemainingAmount, + onBitcoinTransactionSuccess, }: ITransferBitcoin) => { if (onSetError) { onSetError(null) @@ -44,11 +48,15 @@ export const transferBitcoin = ({ unspentTransactions: utxos, miningFee: Number(MINIMUM_FEE), balance, + addressToReturnRemainingAmount, }) .then(async txIdJson => { if (txIdJson.result) { // success if (onSetCurrentTransaction) { + onBitcoinTransactionSuccess?.({ + addressUsed: addressToReturnRemainingAmount, + }) //@TODO: make the status a constant value onSetCurrentTransaction({ status: 'PENDING', diff --git a/src/screens/send/usePaymentExecutor.ts b/src/screens/send/usePaymentExecutor.ts index 8f9ffe2c9..d2d8bacb7 100644 --- a/src/screens/send/usePaymentExecutor.ts +++ b/src/screens/send/usePaymentExecutor.ts @@ -7,16 +7,23 @@ import { RIFWallet } from '@rsksmart/rif-wallet-core' import { ITokenWithBalance } from '@rsksmart/rif-wallet-services' import { useTranslation } from 'react-i18next' -import { useAppDispatch } from 'store/storeUtils' +import { useAppDispatch, useAppSelector } from 'store/storeUtils' import { addPendingTransaction, modifyTransaction, ApiTransactionWithExtras, ModifyTransaction, } from 'store/slices/transactionsSlice' -import { fetchUtxo } from 'screens/send/bitcoinUtils' +import { + fetchAddressToReturnFundsTo, + fetchUtxo, +} from 'screens/send/bitcoinUtils' import { AppDispatch } from 'store/index' import { TokenBalanceObject } from 'store/slices/balancesSlice/types' +import { + addAddressToUsedBitcoinAddresses, + selectWholeSettingsState, +} from 'store/slices/settingsSlice' import { transferBitcoin } from './transferBitcoin' import { transfer } from './transferTokens' @@ -119,9 +126,20 @@ export const usePaymentExecutor = ( useState(null) const [error, setError] = useState() const [utxos, setUtxos] = useState([]) + const [addressToReturnRemainingAmount, setAddressToReturnRemainingAmount] = + useState('') const [bitcoinBalance, setBalanceAvailable] = useState(0) const { t } = useTranslation() const dispatch = useAppDispatch() + const { usedBitcoinAddresses } = useAppSelector(selectWholeSettingsState) + + const onBitcoinTransactionSuccess = ({ + addressUsed, + }: { + addressUsed: string + }) => { + dispatch(addAddressToUsedBitcoinAddresses(addressUsed)) + } const executePayment = ({ token, @@ -150,6 +168,8 @@ export const usePaymentExecutor = ( to, utxos, balance: bitcoinBalance, + addressToReturnRemainingAmount, + onBitcoinTransactionSuccess, }) } else { transfer({ @@ -166,6 +186,7 @@ export const usePaymentExecutor = ( } } // When bitcoin network changes - fetch utxos + // and also set the return address useEffect(() => { if (bitcoinNetwork && 'satoshis' in bitcoinNetwork) { fetchUtxo({ @@ -173,8 +194,13 @@ export const usePaymentExecutor = ( onSetUtxos: setUtxos, onSetBalance: balance => setBalanceAvailable(balance.toNumber()), }) + fetchAddressToReturnFundsTo({ + token: bitcoinNetwork, + onSetAddress: setAddressToReturnRemainingAmount, + usedBitcoinAddresses, + }) } - }, [bitcoinNetwork]) + }, [bitcoinNetwork, usedBitcoinAddresses]) return { currentTransaction, diff --git a/yarn.lock b/yarn.lock index a15028df8..b689a5a86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2085,10 +2085,10 @@ bitcoin-address-validation "^2.2.1" bitcoinjs-lib "^6.0.2" -"@rsksmart/rif-wallet-bitcoin@^1.1.0": - version "1.1.0" - resolved "https://npm.pkg.github.com/download/@rsksmart/rif-wallet-bitcoin/1.1.0/42a8730086f472152406d32ff9dd5518857448ca#42a8730086f472152406d32ff9dd5518857448ca" - integrity sha512-mGtZNHin6SY5T5PQrXao/2Vu3qb5uugU6tk2+zTOvdnX9D/cPuj95nBIBw+utxqzYoRqIv2/hBVf2QfjMtRkZg== +"@rsksmart/rif-wallet-bitcoin@^1.2.0": + version "1.2.0" + resolved "https://npm.pkg.github.com/download/@rsksmart/rif-wallet-bitcoin/1.2.0/b40502c4511daf596e6b98add0152602257a4cfc#b40502c4511daf596e6b98add0152602257a4cfc" + integrity sha512-x9NgO+CCIx6ZPVByH7bFsSKWahGjzEhJyr49fSi9qU+d0jvvqfd2JpNygAKV6mP1VHAQfLvN4FgBacwp/w9HEw== dependencies: "@ethersproject/bignumber" "^5.7.0" "@ethersproject/units" "^5.7.0" @@ -2115,10 +2115,10 @@ dependencies: "@rsksmart/rsk-utils" "^1.1.0" -"@rsksmart/rif-wallet-services@^1.0.4": - version "1.0.4" - resolved "https://npm.pkg.github.com/download/@rsksmart/rif-wallet-services/1.0.4/511c4cd4a3751e494cb4f3a6395cbc5b3c03cd56#511c4cd4a3751e494cb4f3a6395cbc5b3c03cd56" - integrity sha512-i5DR1wXd3YDIWG2BkvAxkVolKdyB0HoIyW4/G2R61TNVBOs1Sq5J7tyY9LV7qD3yehcu0anym25UvS9TaP6uHg== +"@rsksmart/rif-wallet-services@^1.0.5": + version "1.0.5" + resolved "https://npm.pkg.github.com/download/@rsksmart/rif-wallet-services/1.0.5/dd97e829db5229df0b7c632e9b9e37c4c0118d96#dd97e829db5229df0b7c632e9b9e37c4c0118d96" + integrity sha512-RjCbqU4aj3Q2Hn5XhhxBqcQnKNgJY/dgGLton4d8k5B5xPDqCxOCvH6W3zyGRQXhqQz+Ax2pPl16L8GqsZwjUA== dependencies: "@ethersproject/contracts" "^5.7.0" "@rsksmart/rif-wallet-abi-enhancer" "*"