From b89a1b2877082fc84d327727f635003dee535ff4 Mon Sep 17 00:00:00 2001 From: Alexander <69318224+owlsua@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:32:37 +0200 Subject: [PATCH 1/6] fix: unused address (#179) --- .../containers/Wallet/ShowAddress.js | 24 ++++++++++++------- src/pages/Wallet/Wallet.js | 1 + 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/components/containers/Wallet/ShowAddress.js b/src/components/containers/Wallet/ShowAddress.js index ac6179f4..55fe2442 100644 --- a/src/components/containers/Wallet/ShowAddress.js +++ b/src/components/containers/Wallet/ShowAddress.js @@ -6,7 +6,12 @@ import { CenteredLayout, VerticalGroup } from '@LayoutComponents' import './ShowAddress.css' -const ShowAddress = ({ address: defaultAddress, onCopy, unusedAddress }) => { +const ShowAddress = ({ + address: defaultAddress, + onCopy, + unusedAddress, + transactions, +}) => { const [toCopyLabel, afterCopyLabel] = ['Copy Address', 'Copied!'] const copiedTimeoutInMs = 2 * 1_000 const [label, setLabel] = useState(toCopyLabel) @@ -48,13 +53,16 @@ const ShowAddress = ({ address: defaultAddress, onCopy, unusedAddress }) => {

Address:

{address} - - {unusedAddress && - (showUnused ? ( - (new) - ) : ( - (used) - ))} + {unusedAddress && ( + <> + {(showUnused || transactions.length === 0) && ( + (new) + )} + {!showUnused && transactions.length > 0 && ( + (used) + )} + + )}

{unusedAddress ? ( diff --git a/src/pages/Wallet/Wallet.js b/src/pages/Wallet/Wallet.js index 3ab1c4f0..f26beba8 100644 --- a/src/pages/Wallet/Wallet.js +++ b/src/pages/Wallet/Wallet.js @@ -103,6 +103,7 @@ const WalletPage = () => { )} From 92a189faaf544d957b990746b30ccf82b564ed25 Mon Sep 17 00:00:00 2001 From: Alexander <69318224+owlsua@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:03:40 +0200 Subject: [PATCH 2/6] A-1203628035052742 (#178) * feat: update Network provifer with pool info * feat: update AppInfo with new data * feat: add new warning message if pool reward is low or 0 * fix: margin ration value --- .../containers/SendTransaction/SendTransaction.js | 12 ++++++++++++ .../SendTransaction/SendTransactionConfirmation.css | 12 +++++++++--- .../SendTransaction/SendTransactionConfirmation.js | 8 ++++++++ src/contexts/NetworkProvider/NetworkProvider.js | 6 ++++++ src/utils/Constants/AppInfo/AppInfo.js | 4 ++++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/components/containers/SendTransaction/SendTransaction.js b/src/components/containers/SendTransaction/SendTransaction.js index c98329c5..308dc381 100644 --- a/src/components/containers/SendTransaction/SendTransaction.js +++ b/src/components/containers/SendTransaction/SendTransaction.js @@ -55,9 +55,20 @@ const SendTransaction = ({ const [allowClosing, setAllowClosing] = useState(true) const [askPassword, setAskPassword] = useState(false) const [pass, setPass] = useState(null) + const [poolData, setPoolData] = useState(null) const isBitcoinWallet = walletType.name === 'Bitcoin' const NC = useContext(NetworkContext) + useEffect(() => { + if (transactionMode === AppInfo.ML_TRANSACTION_MODES.DELEGATION && NC && addressTo) { + const fetchPoolData = async () => { + const poolData = await NC.getPoolsData([addressTo]) + setPoolData(poolData) + } + fetchPoolData() + } + }, [addressTo, transactionMode, NC]) + const [openSendFundConfirmation, setOpenSendFundConfirmation] = useState(false) @@ -398,6 +409,7 @@ const SendTransaction = ({ onConfirm={handleConfirm} onCancel={handleCancel} walletType={walletType} + poolData={poolData} > ) : feeLoading ? (
diff --git a/src/components/containers/SendTransaction/SendTransactionConfirmation.css b/src/components/containers/SendTransaction/SendTransactionConfirmation.css index 8995aad0..09e9f877 100644 --- a/src/components/containers/SendTransaction/SendTransactionConfirmation.css +++ b/src/components/containers/SendTransaction/SendTransactionConfirmation.css @@ -5,13 +5,11 @@ dt { font-size: 1.5rem; font-weight: 300; - line-height: 2.25rem; } dd { font-weight: 300; - line-height: 1.81rem; - margin-bottom: 1.5rem; + margin-bottom: 1.1rem; line-break: anywhere; } @@ -43,3 +41,11 @@ dd span, dd span * { font-size: 1.12rem; } + +.pool-note-message { + font-size: 1.2rem; + font-weight: 600; + color: rgb(var(--color-red)); + word-wrap: break-word; + line-break: normal; +} diff --git a/src/components/containers/SendTransaction/SendTransactionConfirmation.js b/src/components/containers/SendTransaction/SendTransactionConfirmation.js index 63317020..0b1d7c15 100644 --- a/src/components/containers/SendTransaction/SendTransactionConfirmation.js +++ b/src/components/containers/SendTransaction/SendTransactionConfirmation.js @@ -19,11 +19,16 @@ const SendFundConfirmation = ({ onConfirm, onCancel, walletType, + poolData }) => { const { networkType } = useContext(SettingsContext) const isTestnet = networkType === AppInfo.NETWORK_TYPES.TESTNET const amountFiat = isTestnet ? '0,00' : amountInFiat const feeFiat = isTestnet ? '0,00' : totalFeeFiat + + const isLowReward = (poolData && poolData[0].cost_per_block.decimal > AppInfo.APPROPRIATE_COST_PER_BLOCK) || (poolData && parseFloat(poolData[0].margin_ratio_per_thousand) > AppInfo.APPROPRIATE_MARGIN_RATIO_PER_THOUSAND) + const rewardMessage = 'The pool you are using has a high cost per block and/or margin ratio. This may result in lower rewards.' + return (
@@ -58,6 +63,9 @@ const SendFundConfirmation = ({ )} + {poolData && isLowReward && ( +
Please note: {rewardMessage}
+ )}
diff --git a/src/contexts/NetworkProvider/NetworkProvider.js b/src/contexts/NetworkProvider/NetworkProvider.js index 8a441cf9..22e0497f 100644 --- a/src/contexts/NetworkProvider/NetworkProvider.js +++ b/src/contexts/NetworkProvider/NetworkProvider.js @@ -327,6 +327,11 @@ const NetworkProvider = ({ value: propValue, children }) => { return () => clearInterval(data) }, []) + const getPoolsData = async (poolIds) => { + const pools_data = await Mintlayer.getPoolsData(poolIds) + return pools_data + } + const value = { balance, lockedBalance, @@ -342,6 +347,7 @@ const NetworkProvider = ({ value: propValue, children }) => { fetchAllData, fetchDelegations, + getPoolsData, currentAccountId, unusedAddresses, diff --git a/src/utils/Constants/AppInfo/AppInfo.js b/src/utils/Constants/AppInfo/AppInfo.js index 1601cdef..8a18822f 100644 --- a/src/utils/Constants/AppInfo/AppInfo.js +++ b/src/utils/Constants/AppInfo/AppInfo.js @@ -13,6 +13,8 @@ const minEntropyLength = 192 const DEFAULT_WALLETS_TO_CREATE = ['btc'] const ML_ATOMS_PER_COIN = 100000000000 const DEFAULT_ML_WALLET_OFFSET = 21 +const APPROPRIATE_COST_PER_BLOCK = 190 +const APPROPRIATE_MARGIN_RATIO_PER_THOUSAND = 80 const UNCONFIRMED_TRANSACTION_NAME = 'ml_unconfirmed_transaction' const NETWORK_TYPES = { MAINNET: 'mainnet', @@ -59,4 +61,6 @@ export { ML_TRANSACTION_TYPES, ML_TRANSACTION_MODES, MAX_ML_FEE, + APPROPRIATE_COST_PER_BLOCK, + APPROPRIATE_MARGIN_RATIO_PER_THOUSAND, } From a9c5e293e096a3e56fd3347d35cb820fc365cf5a Mon Sep 17 00:00:00 2001 From: Alexander <69318224+owlsua@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:27:12 +0200 Subject: [PATCH 3/6] A-1206276599219845 (#176) * feat: add string helpers * merge dev * feat: update Mintlayer and Bitcoin API services to support custom API * feat: TextField improvements * feat: add SettingsAPI item * feat: add SettingsAPI component * fix: add condition if custom serves is empty --- .../composed/TextField/TextField.css | 1 - .../composed/TextField/TextField.js | 7 +- .../SendTransaction/SendTransaction.js | 1 + .../Settings/SettingsAPI/SettingsAPI.css | 8 + .../Settings/SettingsAPI/SettingsAPI.js | 154 ++++++++++++++++++ .../Settings/SettingsAPI/SettingsAPIItem.css | 36 ++++ .../Settings/SettingsAPI/SettingsAPIItem.js | 94 +++++++++++ src/components/containers/index.js | 2 + src/pages/Settings/Settings.css | 2 + src/pages/Settings/Settings.js | 4 + src/services/API/Electrum/Electrum.js | 31 +++- src/services/API/Mintlayer/Mintlayer.js | 23 ++- .../Storage/LocalStorage/LocalStorage.js | 10 -- src/utils/Constants/AppInfo/AppInfo.js | 3 + src/utils/Helpers/String/String.js | 5 + src/utils/Helpers/index.js | 2 + 16 files changed, 359 insertions(+), 24 deletions(-) create mode 100644 src/components/containers/Settings/SettingsAPI/SettingsAPI.css create mode 100644 src/components/containers/Settings/SettingsAPI/SettingsAPI.js create mode 100644 src/components/containers/Settings/SettingsAPI/SettingsAPIItem.css create mode 100644 src/components/containers/Settings/SettingsAPI/SettingsAPIItem.js create mode 100644 src/utils/Helpers/String/String.js diff --git a/src/components/composed/TextField/TextField.css b/src/components/composed/TextField/TextField.css index 66f4f83f..c192f2e4 100644 --- a/src/components/composed/TextField/TextField.css +++ b/src/components/composed/TextField/TextField.css @@ -1,7 +1,6 @@ .inputLabel { color: rgb(var(--color-dark-gray)); display: block; - text-align: center; } .inputLabel.alternate { diff --git a/src/components/composed/TextField/TextField.js b/src/components/composed/TextField/TextField.js index 94856c92..8260e302 100644 --- a/src/components/composed/TextField/TextField.js +++ b/src/components/composed/TextField/TextField.js @@ -19,7 +19,8 @@ const TextField = ({ errorMessages, pristinity = true, reference, - focus = false, + focus = true, + bigGap = true, }) => { const inputId = useId() @@ -44,7 +45,7 @@ const TextField = ({ const setPristineState = (e) => setIsPristine(false) return ( - + {label && ( diff --git a/src/components/containers/SendTransaction/SendTransaction.js b/src/components/containers/SendTransaction/SendTransaction.js index 308dc381..68f0f8e0 100644 --- a/src/components/containers/SendTransaction/SendTransaction.js +++ b/src/components/containers/SendTransaction/SendTransaction.js @@ -426,6 +426,7 @@ const SendTransaction = ({ pristinity={passPristinity} errorMessages={passErrorMessage} onChangeHandle={changePassHandle} + alternate /> diff --git a/src/components/containers/Settings/SettingsAPI/SettingsAPI.css b/src/components/containers/Settings/SettingsAPI/SettingsAPI.css new file mode 100644 index 00000000..d19818be --- /dev/null +++ b/src/components/containers/Settings/SettingsAPI/SettingsAPI.css @@ -0,0 +1,8 @@ +.settings-api { + display: flex; + flex-direction: column; +} + +.api-description { + margin-bottom: 10px; +} \ No newline at end of file diff --git a/src/components/containers/Settings/SettingsAPI/SettingsAPI.js b/src/components/containers/Settings/SettingsAPI/SettingsAPI.js new file mode 100644 index 00000000..eaf441df --- /dev/null +++ b/src/components/containers/Settings/SettingsAPI/SettingsAPI.js @@ -0,0 +1,154 @@ +import { useState, useEffect } from 'react' +import { VerticalGroup } from '@LayoutComponents' + +import { EnvVars, AppInfo } from '@Constants' +import { LocalStorageService } from '@Storage' +import SettingsApiItem from './SettingsAPIItem.js' + +import './SettingsAPI.css' + +const SettingsAPI = () => { + const [mintlayerTestnetFieldValue, setMintlayerTestnetFieldValue] = useState('') + const [mintlayerMainnetFieldValue, setMintlayerMainnetFieldValue] = useState('') + const [bitconinTestnetFieldValue, setBitconinTestnetFieldValue] = useState('') + const [bitconinMainnetFieldValue, setBitconinMainnetFieldValue] = useState('') + const [currentServers, setCurrentServers] = useState({}) + + const mintlayerDefauldTestnetServer = EnvVars.TESTNET_MINTLAYER_SERVERS + const mintlayerDefauldMainnetServer = EnvVars.MAINNET_MINTLAYER_SERVERS + const bitconinDefauldTestnetServer = EnvVars.TESTNET_ELECTRUM_SERVERS + const bitconinDefauldMainnetServer = EnvVars.MAINNET_ELECTRUM_SERVERS + + useEffect(() => { + const getCurrentServer = () => { + const customServersFromStore = + LocalStorageService.getItem(AppInfo.APP_LOCAL_STORAGE_CUSTOM_SERVERS) || + {} // Ensure it's an object even if null/undefined is returned + + const currentServers = { + mintlayer_testnet: + customServersFromStore.mintlayer_testnet || + mintlayerDefauldTestnetServer, + mintlayer_mainnet: + customServersFromStore.mintlayer_mainnet || + mintlayerDefauldMainnetServer, + bitcoin_testnet: + customServersFromStore.bitcoin_testnet || + bitconinDefauldTestnetServer, + bitcoin_mainnet: + customServersFromStore.bitcoin_mainnet || + bitconinDefauldMainnetServer, + } + + setCurrentServers(currentServers) + } + getCurrentServer() + }, [bitconinDefauldMainnetServer, bitconinDefauldTestnetServer, mintlayerDefauldMainnetServer, mintlayerDefauldTestnetServer]) + + + const submitHandler = (data) => { + const customServersFromStore = LocalStorageService.getItem( + AppInfo.APP_LOCAL_STORAGE_CUSTOM_SERVERS, + ) + const customServers = customServersFromStore ? customServersFromStore : {} + const key = `${data.wallet}_${data.networkType}` + customServers[key] = data.data + + LocalStorageService.setItem( + AppInfo.APP_LOCAL_STORAGE_CUSTOM_SERVERS, + customServers, + ) + } + + const resetHandler = (data) => { + const customServersFromStore = LocalStorageService.getItem( + AppInfo.APP_LOCAL_STORAGE_CUSTOM_SERVERS, + ) + const key = `${data.wallet}_${data.networkType}` + const storageKey = customServersFromStore[key] + + if (storageKey) { + delete customServersFromStore[key] + LocalStorageService.setItem( + AppInfo.APP_LOCAL_STORAGE_CUSTOM_SERVERS, + customServersFromStore, + ) + setMintlayerTestnetFieldValue('') + setMintlayerMainnetFieldValue('') + setBitconinTestnetFieldValue('') + setBitconinMainnetFieldValue('') + } else { + console.log('Invalid wallet or network type.') + } + } + + const inputsList = [ + { + wallet: 'mintlayer', + networkType: 'testnet', + cuurrentServer: currentServers.mintlayer_testnet, + inputValue: mintlayerTestnetFieldValue, + setInputValue: setMintlayerTestnetFieldValue, + }, + { + wallet: 'mintlayer', + networkType: 'mainnet', + cuurrentServer: currentServers.mintlayer_mainnet, + inputValue: mintlayerMainnetFieldValue, + setInputValue: setMintlayerMainnetFieldValue, + }, + { + wallet: 'bitcoin', + networkType: 'testnet', + cuurrentServer: currentServers.bitcoin_testnet, + inputValue: bitconinTestnetFieldValue, + setInputValue: setBitconinTestnetFieldValue, + }, + { + wallet: 'bitcoin', + networkType: 'mainnet', + cuurrentServer: currentServers.bitcoin_mainnet, + inputValue: bitconinMainnetFieldValue, + setInputValue: setBitconinMainnetFieldValue, + }, + ] + + return ( +
+
+ +

API SERVERS

+

+ Here you can define the API server for each wallet and network type. + If you leave the field empty, the default server will be used. To + reset the API server to default, click the reset button. +

+

+ Plese note that the API server is used for the transaction and if + you are using a custom server, make sure it is a safe and reliable + server +

+
+
+ {inputsList.map((item, index) => ( + + ))} +
+ ) +} + +export default SettingsAPI diff --git a/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.css b/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.css new file mode 100644 index 00000000..df698f32 --- /dev/null +++ b/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.css @@ -0,0 +1,36 @@ +.api-field-wrapper { + display: flex; + margin-bottom: 5px; +} + +.api-input-wrapper { + width: 75%; + margin-right: 5px; +} + +.api-button-wrapper { + display: flex; + justify-content: space-between; + align-items: flex-end; + width: 25%; +} + +.submit-api-button, +.reset-api-button { + display: flex; + justify-content: center; + align-items: center; + width: 85px; + padding: 10px 0; + height: 63px; + font-size: 1.2rem; +} + +.reset-api-button { + background-color: rgb(var(--color-red)); + color: white; +} + +.api-input { + padding: 1rem 1rem; +} \ No newline at end of file diff --git a/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.js b/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.js new file mode 100644 index 00000000..dab4f69d --- /dev/null +++ b/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.js @@ -0,0 +1,94 @@ +import { useState } from 'react' + +import { Button } from '@BasicComponents' +import { TextField } from '@ComposedComponents' + +import { StringHelpers } from '@Helpers' + +import './SettingsAPIItem.css' + +const SettingsApiItem = ({ + inputValue, + setInputValue, + walletData, + onSubmitClick, + onResetClick, +}) => { + const submitButtonExtraClasses = ['submit-api-button'] + const resetButtonExtraClasses = ['reset-api-button'] + const inputExtraclasses = ['api-input'] + + const [fieldValidity, setFieldValidity] = useState(false) + const [fieldPristinity, setFieldPristinity] = useState(true) + + const checkFieldValidity = (fieldValidity) => { + const regex = /^https?:\/\/.{3,}\..+$/m + setFieldValidity(regex.test(fieldValidity)) + return regex.test(fieldValidity) + } + + const fieldChangeHandler = (value) => { + checkFieldValidity(value) + setInputValue(value) + } + + const onSubmit = (data) => { + setFieldPristinity(false) + onSubmitClick(data) + } + + const onReset = (data) => { + setFieldPristinity(true) + onResetClick(data) + } + + const label = `${StringHelpers.capitalizeFirstLetter(walletData.wallet)} ${walletData.networkType} server` + + return ( +
+
+ +
+
+ + +
+
+ ) +} + + +export default SettingsApiItem diff --git a/src/components/containers/index.js b/src/components/containers/index.js index 8e45ea4f..8828aa51 100644 --- a/src/components/containers/index.js +++ b/src/components/containers/index.js @@ -22,6 +22,7 @@ import CryptoList from './Dashboard/CryptoList' import DeleteAccount from './DeleteAccount/DeleteAccount' import SettingsDelete from './Settings/SettingsDelete/SettingsDelete' import SettingsTestnet from './Settings/SettingsTestnet/SettingsTestnet' +import SettingsAPI from './Settings/SettingsAPI/SettingsAPI' /* istanbul ignore next */ const Wallet = { @@ -50,6 +51,7 @@ const Dashboard = { const Settings = { SettingsTestnet, SettingsDelete, + SettingsAPI, } export { diff --git a/src/pages/Settings/Settings.css b/src/pages/Settings/Settings.css index 888c9eac..36b0948c 100644 --- a/src/pages/Settings/Settings.css +++ b/src/pages/Settings/Settings.css @@ -1,6 +1,8 @@ .settingsWrapper { display: grid; margin-top: 50px; + max-height: 435px; + overflow: auto; } .settingsItem { diff --git a/src/pages/Settings/Settings.js b/src/pages/Settings/Settings.js index 06c8b202..62fef754 100644 --- a/src/pages/Settings/Settings.js +++ b/src/pages/Settings/Settings.js @@ -11,6 +11,10 @@ const SettingsList = [ component: , value: 'testnet', }, + { + component: , + value: 'api', + }, // Keep the delete wallet option at the bottom { component: , diff --git a/src/services/API/Electrum/Electrum.js b/src/services/API/Electrum/Electrum.js index c10e535b..b72892e0 100644 --- a/src/services/API/Electrum/Electrum.js +++ b/src/services/API/Electrum/Electrum.js @@ -31,17 +31,38 @@ const requestElectrum = async (url, body = null, request = fetch) => { const tryServers = async (endpoint, body = null) => { const networkType = LocalStorageService.getItem('networkType') - const electrumServes = + const customElectrumServerList = LocalStorageService.getItem( + AppInfo.APP_LOCAL_STORAGE_CUSTOM_SERVERS, + ) + + const customServer = customElectrumServerList + ? networkType === AppInfo.NETWORK_TYPES.TESTNET + ? customElectrumServerList.bitcoin_testnet + : customElectrumServerList.bitcoin_mainnet + : null + + const defaultElectrumServes = networkType === AppInfo.NETWORK_TYPES.TESTNET ? EnvVars.TESTNET_ELECTRUM_SERVERS : EnvVars.MAINNET_ELECTRUM_SERVERS - for (let i = 0; i < electrumServes.length; i++) { + + const combinedElectrumServers = customServer + ? [customServer, ...defaultElectrumServes] + : [...defaultElectrumServes] + + for (let i = 0; i < combinedElectrumServers.length; i++) { try { - const response = await requestElectrum(electrumServes[i] + endpoint, body) + const response = await requestElectrum( + combinedElectrumServers[i] + endpoint, + body, + ) return response } catch (error) { - console.warn(`${electrumServes[i] + endpoint} request failed: `, error) - if (i === electrumServes.length - 1) { + console.warn( + `${combinedElectrumServers[i] + endpoint} request failed: `, + error, + ) + if (i === combinedElectrumServers.length - 1) { throw error } } diff --git a/src/services/API/Mintlayer/Mintlayer.js b/src/services/API/Mintlayer/Mintlayer.js index f69b44c9..1349d7fa 100644 --- a/src/services/API/Mintlayer/Mintlayer.js +++ b/src/services/API/Mintlayer/Mintlayer.js @@ -70,23 +70,36 @@ const requestMintlayer = async (url, body = null, request = fetch) => { const tryServers = async (endpoint, body = null, forceNetwork) => { const networkType = forceNetwork || LocalStorageService.getItem('networkType') - const mintlayerServers = + const customMintlayerServerList = LocalStorageService.getItem( + AppInfo.APP_LOCAL_STORAGE_CUSTOM_SERVERS, + ) + const customMintlayerServer = customMintlayerServerList + ? networkType === AppInfo.NETWORK_TYPES.TESTNET + ? customMintlayerServerList.mintlayer_testnet + : customMintlayerServerList.mintlayer_mainnet + : null + + const defaultMintlayerServers = networkType === AppInfo.NETWORK_TYPES.TESTNET ? EnvVars.TESTNET_MINTLAYER_SERVERS : EnvVars.MAINNET_MINTLAYER_SERVERS - for (let i = 0; i < mintlayerServers.length; i++) { + + const combinedMintlayerServers = customMintlayerServer + ? [customMintlayerServer, ...defaultMintlayerServers] + : [...defaultMintlayerServers] + for (let i = 0; i < combinedMintlayerServers.length; i++) { try { const response = await requestMintlayer( - mintlayerServers[i] + prefix + endpoint, + combinedMintlayerServers[i] + prefix + endpoint, body, ) return response } catch (error) { console.warn( - `${mintlayerServers[i] + prefix + endpoint} request failed: `, + `${combinedMintlayerServers[i] + prefix + endpoint} request failed: `, error, ) - if (i === mintlayerServers.length - 1) { + if (i === combinedMintlayerServers.length - 1) { throw error } } diff --git a/src/services/Storage/LocalStorage/LocalStorage.js b/src/services/Storage/LocalStorage/LocalStorage.js index 8dcedc61..87416b5b 100644 --- a/src/services/Storage/LocalStorage/LocalStorage.js +++ b/src/services/Storage/LocalStorage/LocalStorage.js @@ -1,5 +1,4 @@ // global localStorage -import { AppInfo } from '@Constants' const setItem = (key, value) => { localStorage.setItem(key, JSON.stringify(value)) @@ -14,15 +13,6 @@ const getItem = (key) => { const item = localStorage.getItem(key) const result = item ? JSON.parse(item) : null - // fallback for unconfirmed transactions that are not stored in an array - if ( - result !== null && - key.startsWith(AppInfo.UNCONFIRMED_TRANSACTION_NAME) && - !Array.isArray(result) - ) { - return [result] - } - return result } else { console.log('localStorage is not available') diff --git a/src/utils/Constants/AppInfo/AppInfo.js b/src/utils/Constants/AppInfo/AppInfo.js index 8a18822f..50d7bccb 100644 --- a/src/utils/Constants/AppInfo/AppInfo.js +++ b/src/utils/Constants/AppInfo/AppInfo.js @@ -16,6 +16,8 @@ const DEFAULT_ML_WALLET_OFFSET = 21 const APPROPRIATE_COST_PER_BLOCK = 190 const APPROPRIATE_MARGIN_RATIO_PER_THOUSAND = 80 const UNCONFIRMED_TRANSACTION_NAME = 'ml_unconfirmed_transaction' +const APP_LOCAL_STORAGE_CUSTOM_SERVERS = 'customAPIServers' + const NETWORK_TYPES = { MAINNET: 'mainnet', TESTNET: 'testnet', @@ -63,4 +65,5 @@ export { MAX_ML_FEE, APPROPRIATE_COST_PER_BLOCK, APPROPRIATE_MARGIN_RATIO_PER_THOUSAND, + APP_LOCAL_STORAGE_CUSTOM_SERVERS, } diff --git a/src/utils/Helpers/String/String.js b/src/utils/Helpers/String/String.js new file mode 100644 index 00000000..71374579 --- /dev/null +++ b/src/utils/Helpers/String/String.js @@ -0,0 +1,5 @@ +const capitalizeFirstLetter = (string) => { + return string.charAt(0).toUpperCase() + string.slice(1) +} + +export { capitalizeFirstLetter } diff --git a/src/utils/Helpers/index.js b/src/utils/Helpers/index.js index ba4af35d..46d90c76 100644 --- a/src/utils/Helpers/index.js +++ b/src/utils/Helpers/index.js @@ -7,6 +7,7 @@ import * as NumbersHelper from './Number/Number' import * as Format from './Number/Format' import * as ArrayHelper from './Array/Array' import * as MLTransaction from './ML/MLTransaction' +import * as StringHelpers from './String/String' export { BTC, @@ -18,4 +19,5 @@ export { NumbersHelper, Format, ArrayHelper, + StringHelpers, } From 6c12299dc4f191163dfbde899fc28abf49789a9c Mon Sep 17 00:00:00 2001 From: Alexander <69318224+owlsua@users.noreply.github.com> Date: Sat, 27 Jul 2024 22:53:31 +0200 Subject: [PATCH 4/6] A-1207658279353173 (#175) * merge dev * test: update unit tests * merge dev * feat: update useBtcWalletInfo hook * feat: add Bitcoin data fentching to the force update * feat: add feedback to settings api button --- src/components/composed/Balance/Balance.js | 11 +- .../composed/Balance/Balance.test.js | 68 +++++---- .../composed/FeeField/FeeFieldML.js | 19 ++- src/components/composed/Header/Header.test.js | 32 ++--- .../LockedBalanceList/LockedBalanceList.js | 4 +- .../composed/UpdateButton/UpdateButton.js | 26 ++-- .../UpdateButton/UpdateButton.test.js | 38 +++-- .../containers/Dashboard/CryptoList.js | 4 +- .../containers/Dashboard/CryptoList.test.js | 124 ++++++++-------- .../containers/Dashboard/CryptoSharesChart.js | 7 +- .../SendTransaction/SendTransaction.js | 10 +- .../SendTransaction/SendTransaction.test.js | 7 +- .../Settings/SettingsAPI/SettingsAPIItem.css | 4 + .../Settings/SettingsAPI/SettingsAPIItem.js | 33 ++++- .../containers/Wallet/TransactionsList.js | 4 +- .../Wallet/TransactionsList.test.js | 14 +- .../BitcoinProvider/BitcoinProvider.js | 133 ++++++++++++++++++ .../MintlayerProvider.js} | 16 +-- src/contexts/index.js | 18 ++- src/hooks/UseWalletInfo/useBtcWalletInfo.js | 72 +++------- src/hooks/UseWalletInfo/useMlWalletInfo.js | 4 +- src/index.js | 28 ++-- src/pages/Dashboard/Dashboard.js | 6 +- src/utils/Constants/AppInfo/AppInfo.js | 2 + 24 files changed, 436 insertions(+), 248 deletions(-) create mode 100644 src/contexts/BitcoinProvider/BitcoinProvider.js rename src/contexts/{NetworkProvider/NetworkProvider.js => MintlayerProvider/MintlayerProvider.js} (96%) diff --git a/src/components/composed/Balance/Balance.js b/src/components/composed/Balance/Balance.js index e4a39cd2..a646b8cf 100644 --- a/src/components/composed/Balance/Balance.js +++ b/src/components/composed/Balance/Balance.js @@ -4,7 +4,7 @@ import { useNavigate, useParams } from 'react-router-dom' import { ReactComponent as BtcLogo } from '@Assets/images/btc-logo.svg' import { LogoRound } from '@BasicComponents' import { Format, NumbersHelper } from '@Helpers' -import { NetworkContext, SettingsContext } from '@Contexts' +import { MintlayerContext, SettingsContext } from '@Contexts' import { AppInfo } from '@Constants' import './Balance.css' @@ -12,9 +12,9 @@ import TokenLogoRound from '../../basic/TokenLogoRound/TokenLogoRound' const Balance = ({ balance, balanceLocked, exchangeRate, walletType }) => { const { networkType } = useContext(SettingsContext) - const { tokenBalances } = useContext(NetworkContext) const { coinType } = useParams() const navigate = useNavigate() + const { tokenBalances } = useContext(MintlayerContext) // TODO Consider the correct format for 0,00 that might also be 0.00 const balanceInUSD = networkType === AppInfo.NETWORK_TYPES.TESTNET @@ -48,7 +48,7 @@ const Balance = ({ balance, balanceLocked, exchangeRate, walletType }) => { } const onLockedClick = () => { - navigate('/wallet/' + coinType + '/locked-balance') + navigate('/wallet/' + coinType + '/locked-balance') } return ( @@ -71,7 +71,10 @@ const Balance = ({ balance, balanceLocked, exchangeRate, walletType }) => { {Format.fiatValue(balanceInUSD)} USD

{parseFloat(balanceLocked) > 0 ? ( -
+
Locked: {balanceLocked} {symbol()}
) : ( diff --git a/src/components/composed/Balance/Balance.test.js b/src/components/composed/Balance/Balance.test.js index 7acfca62..e57d970a 100644 --- a/src/components/composed/Balance/Balance.test.js +++ b/src/components/composed/Balance/Balance.test.js @@ -1,22 +1,24 @@ import { render, screen } from '@testing-library/react' import Balance from './Balance' -import { SettingsProvider, NetworkContext } from '@Contexts' +import { SettingsProvider, MintlayerContext } from '@Contexts' const BALANCE_SAMPLE = 1 const EXCHANGE_RATE_SAMPLE = 25000 test('Render account balance with ML', () => { render( - - - - - , - + + + + + , + , ) const currantBalanceComponent = screen.getByTestId('current-balance') const balanceParagraphs = screen.getAllByTestId('balance-paragraph') @@ -32,16 +34,18 @@ test('Render account balance with ML', () => { test('Render account balance with BTC', () => { render( - - - - - , - + + + + + , + , ) const currantBalanceComponent = screen.getByTestId('current-balance') const balanceParagraphs = screen.getAllByTestId('balance-paragraph') @@ -57,16 +61,18 @@ test('Render account balance with BTC', () => { test('renders balance with zero value when networkType is testnet', () => { render( - - - - - , - + + + + + , + , ) const currantBalanceComponent = screen.getByTestId('current-balance') diff --git a/src/components/composed/FeeField/FeeFieldML.js b/src/components/composed/FeeField/FeeFieldML.js index 00579a4c..ff912cf6 100644 --- a/src/components/composed/FeeField/FeeFieldML.js +++ b/src/components/composed/FeeField/FeeFieldML.js @@ -1,19 +1,14 @@ -import React, { - useContext, -} from 'react' +import React, { useContext } from 'react' -import { NetworkContext } from '@Contexts' +import { MintlayerContext } from '@Contexts' import { Input } from '@BasicComponents' import { OptionButtons } from '@ComposedComponents' import './FeeField.css' import { ML as MLHelpers } from '@Helpers' -const FeeFieldML = ({ - value: parentValue, - id, -}) => { - const { feerate } = useContext(NetworkContext) +const FeeFieldML = ({ value: parentValue, id }) => { + const { feerate } = useContext(MintlayerContext) const timeToFirstConfirmations = '~2 minutes' const options = [{ name: 'norm', value: feerate }] @@ -24,7 +19,11 @@ const FeeFieldML = ({
ML diff --git a/src/components/composed/Header/Header.test.js b/src/components/composed/Header/Header.test.js index 11fe8c7d..6ea93e0b 100644 --- a/src/components/composed/Header/Header.test.js +++ b/src/components/composed/Header/Header.test.js @@ -8,7 +8,7 @@ import { useLocation, } from 'react-router-dom' -import { AccountProvider, SettingsProvider, NetworkProvider } from '@Contexts' +import { AccountProvider, SettingsProvider, MintlayerProvider } from '@Contexts' import Header from './Header' const toggleNetworkType = jest.fn() @@ -41,21 +41,21 @@ const setup = async (location) => { await render( - - - - } - /> - } - /> - - - + + + + } + /> + } + /> + + + , ) diff --git a/src/components/composed/LockedBalanceList/LockedBalanceList.js b/src/components/composed/LockedBalanceList/LockedBalanceList.js index c00b11ec..6f0c4234 100644 --- a/src/components/composed/LockedBalanceList/LockedBalanceList.js +++ b/src/components/composed/LockedBalanceList/LockedBalanceList.js @@ -1,5 +1,5 @@ import React, { useContext, useState, useEffect, useCallback } from 'react' -import { NetworkContext } from '@Contexts' +import { MintlayerContext } from '@Contexts' import { Loading } from '@ComposedComponents' import { Mintlayer } from '@APIs' @@ -9,7 +9,7 @@ import './LockedBalanceList.css' const LockedBalanceList = () => { const { lockedUtxos, transactions, fetchingUtxos } = - useContext(NetworkContext) + useContext(MintlayerContext) const [loading, setLoading] = useState(false) const [updatedUtxosList, setUpdatedUtxosList] = useState([]) diff --git a/src/components/composed/UpdateButton/UpdateButton.js b/src/components/composed/UpdateButton/UpdateButton.js index 2582f536..fd8bfc94 100644 --- a/src/components/composed/UpdateButton/UpdateButton.js +++ b/src/components/composed/UpdateButton/UpdateButton.js @@ -4,26 +4,25 @@ import { Button } from '@BasicComponents' import { ReactComponent as SuccessImg } from '@Assets/images/icon-success.svg' import { ReactComponent as LoadingImg } from '@Assets/images/icon-loading.svg' -import { NetworkContext } from '@Contexts' +import { MintlayerContext, BitcoinContext } from '@Contexts' import './UpdateButton.css' - const UpdateButton = () => { - const { fetchAllData, fetchDelegations } = - useContext(NetworkContext) + const { fetchAllData: fetchAllDataMintlayer, fetchDelegations } = useContext(MintlayerContext) + const { fetchAllData: fetchAllDataBitcoin } = useContext(BitcoinContext) const [loading, setLoading] = useState(false) const [showSuccess, setShowSuccess] = useState(false) const handleClick = async () => { try { setLoading(true) - await fetchAllData() + await fetchAllDataBitcoin() + await fetchAllDataMintlayer() await fetchDelegations() setLoading(false) setShowSuccess(true) - } - catch (error) { + } catch (error) { console.error(error) setLoading(false) } @@ -43,9 +42,16 @@ const UpdateButton = () => { alternate extraStyleClasses={['update-button']} > - {loading && } - {!loading && showSuccess && } - {!loading && !showSuccess && } + {loading && ( + + )} + {!loading && showSuccess && } + {!loading && !showSuccess && ( + + )} ) } diff --git a/src/components/composed/UpdateButton/UpdateButton.test.js b/src/components/composed/UpdateButton/UpdateButton.test.js index 33e0c1ee..14e6874f 100644 --- a/src/components/composed/UpdateButton/UpdateButton.test.js +++ b/src/components/composed/UpdateButton/UpdateButton.test.js @@ -1,7 +1,7 @@ import React from 'react' import { render, fireEvent, waitFor, screen } from '@testing-library/react' import UpdateButton from './UpdateButton' -import { NetworkContext } from '@Contexts' +import { MintlayerContext } from '@Contexts' describe('UpdateButton', () => { const mockFetchAllData = jest.fn() @@ -14,9 +14,14 @@ describe('UpdateButton', () => { it('should display the default loading icon initially', () => { render( - + - + , ) expect(screen.getByTestId('icon-loading-default')).toBeInTheDocument() @@ -27,9 +32,14 @@ describe('UpdateButton', () => { mockFetchDelegations.mockResolvedValueOnce() render( - + - + , ) fireEvent.click(screen.getByTestId('icon-loading-default')) @@ -42,9 +52,14 @@ describe('UpdateButton', () => { mockFetchDelegations.mockResolvedValueOnce() render( - + - + , ) fireEvent.click(screen.getByTestId('icon-loading-default')) @@ -58,9 +73,14 @@ describe('UpdateButton', () => { mockFetchDelegations.mockResolvedValueOnce() render( - + - + , ) fireEvent.click(screen.getByTestId('icon-loading-default')) diff --git a/src/components/containers/Dashboard/CryptoList.js b/src/components/containers/Dashboard/CryptoList.js index 21c171cb..34ea3ec0 100644 --- a/src/components/containers/Dashboard/CryptoList.js +++ b/src/components/containers/Dashboard/CryptoList.js @@ -3,7 +3,7 @@ import { ReactComponent as BtcLogo } from '@Assets/images/btc-logo.svg' import { LogoRound, SkeletonLoader } from '@BasicComponents' import { LineChart } from '@ComposedComponents' import { AppInfo } from '@Constants' -import { SettingsContext, NetworkContext } from '@Contexts' +import { SettingsContext, MintlayerContext } from '@Contexts' import './CryptoList.css' import TokenLogoRound from '../../basic/TokenLogoRound/TokenLogoRound' @@ -11,7 +11,7 @@ import TokenLogoRound from '../../basic/TokenLogoRound/TokenLogoRound' export const CryptoItem = ({ colorList, onClickItem, item }) => { const { networkType } = useContext(SettingsContext) const fetchingBalances = item.fetchingBalances - const { tokenBalances } = useContext(NetworkContext) + const { tokenBalances } = useContext(MintlayerContext) const isTestnet = networkType === AppInfo.NETWORK_TYPES.TESTNET const color = colorList[item.symbol.toLowerCase()] const balance = item.balance diff --git a/src/components/containers/Dashboard/CryptoList.test.js b/src/components/containers/Dashboard/CryptoList.test.js index 9cf7a023..c079ae1a 100644 --- a/src/components/containers/Dashboard/CryptoList.test.js +++ b/src/components/containers/Dashboard/CryptoList.test.js @@ -1,6 +1,6 @@ import React from 'react' import { render, fireEvent, screen } from '@testing-library/react' -import { SettingsContext, NetworkContext } from '@Contexts' +import { SettingsContext, MintlayerContext } from '@Contexts' import { CryptoItem, ConnectItem } from './CryptoList' import CryptoList from './CryptoList' @@ -27,16 +27,17 @@ describe('CryptoItem', () => { const renderComponent = (networkType, balanceLoading = false) => render( - - - - - - , + + + + + , ) it('renders the crypto item correctly', () => { @@ -64,16 +65,17 @@ describe('CryptoItem', () => { } render( - - - - - - , + + + + + , ) expect(screen.getByTestId('logo-round')).toBeInTheDocument() @@ -113,15 +115,16 @@ describe('ConnectItem', () => { const renderComponent = (networkType) => render( - - - - - - , + + + + + , ) it('renders the connect item correctly', () => { @@ -139,15 +142,16 @@ describe('ConnectItem', () => { } render( - - - - - - , + + + + + , ) expect(screen.getByTestId('logo-round')).toBeInTheDocument() @@ -170,15 +174,16 @@ describe('ConnectItem', () => { } render( - - - - - - , + + + + + , ) expect(screen.getByText('Add wallet')).toBeInTheDocument() @@ -242,17 +247,18 @@ describe('CryptoList', () => { const renderEmptyComponent = (networkType) => render( - - - - - - , + + + + + , ) //TDOO: enable this test when mainnet is ready diff --git a/src/components/containers/Dashboard/CryptoSharesChart.js b/src/components/containers/Dashboard/CryptoSharesChart.js index 74a6fcc8..ff81313d 100644 --- a/src/components/containers/Dashboard/CryptoSharesChart.js +++ b/src/components/containers/Dashboard/CryptoSharesChart.js @@ -1,7 +1,7 @@ import { useContext } from 'react' import { ArcChart } from '@ComposedComponents' import { Format } from '@Helpers' -import { NetworkContext, SettingsContext } from '@Contexts' +import { MintlayerContext, SettingsContext } from '@Contexts' import { AppInfo } from '@Constants' import './CryptoSharesChart.css' @@ -14,7 +14,7 @@ const CryptoSharesChart = ({ colorList, }) => { const { networkType } = useContext(SettingsContext) - const { balanceLoading } = useContext(NetworkContext) + const { balanceLoading } = useContext(MintlayerContext) const totalBalanceInFiat = networkType === AppInfo.NETWORK_TYPES.TESTNET ? '0' @@ -41,7 +41,8 @@ const CryptoSharesChart = ({ '' ) : ( <> - {totalBalanceInFiat} {fiatSymbol} + {totalBalanceInFiat} + {fiatSymbol} )} {accountName} diff --git a/src/components/containers/SendTransaction/SendTransaction.js b/src/components/containers/SendTransaction/SendTransaction.js index 68f0f8e0..8202e07b 100644 --- a/src/components/containers/SendTransaction/SendTransaction.js +++ b/src/components/containers/SendTransaction/SendTransaction.js @@ -4,7 +4,7 @@ import { Button } from '@BasicComponents' import { Loading, PopUp, TextField } from '@ComposedComponents' import { CenteredLayout, VerticalGroup } from '@LayoutComponents' import { BTC, Format, NumbersHelper } from '@Helpers' -import { AccountContext, NetworkContext, TransactionContext } from '@Contexts' +import { AccountContext, MintlayerContext, TransactionContext } from '@Contexts' import { AppInfo } from '@Constants' import SendTransactionConfirmation from './SendTransactionConfirmation' @@ -57,10 +57,14 @@ const SendTransaction = ({ const [pass, setPass] = useState(null) const [poolData, setPoolData] = useState(null) const isBitcoinWallet = walletType.name === 'Bitcoin' - const NC = useContext(NetworkContext) + const NC = useContext(MintlayerContext) useEffect(() => { - if (transactionMode === AppInfo.ML_TRANSACTION_MODES.DELEGATION && NC && addressTo) { + if ( + transactionMode === AppInfo.ML_TRANSACTION_MODES.DELEGATION && + NC && + addressTo + ) { const fetchPoolData = async () => { const poolData = await NC.getPoolsData([addressTo]) setPoolData(poolData) diff --git a/src/components/containers/SendTransaction/SendTransaction.test.js b/src/components/containers/SendTransaction/SendTransaction.test.js index 9839e72b..4da672cf 100644 --- a/src/components/containers/SendTransaction/SendTransaction.test.js +++ b/src/components/containers/SendTransaction/SendTransaction.test.js @@ -3,7 +3,8 @@ import { render, screen, act, fireEvent } from '@testing-library/react' import SendTransaction from './SendTransaction' import { - AccountProvider, NetworkContext, + AccountProvider, + MintlayerContext, SettingsProvider, TransactionProvider, } from '@Contexts' @@ -21,14 +22,14 @@ test('Send Transaction', async () => { - + {}} calculateTotalFee={() => {}} walletType={{ name: 'Mintlayer' }} /> - + , diff --git a/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.css b/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.css index df698f32..2acafb19 100644 --- a/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.css +++ b/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.css @@ -33,4 +33,8 @@ .api-input { padding: 1rem 1rem; +} + +.success-api-icon path { + fill: #fff; } \ No newline at end of file diff --git a/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.js b/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.js index dab4f69d..333bed4f 100644 --- a/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.js +++ b/src/components/containers/Settings/SettingsAPI/SettingsAPIItem.js @@ -1,7 +1,8 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { Button } from '@BasicComponents' import { TextField } from '@ComposedComponents' +import { ReactComponent as SuccessImg } from '@Assets/images/icon-success.svg' import { StringHelpers } from '@Helpers' @@ -20,6 +21,8 @@ const SettingsApiItem = ({ const [fieldValidity, setFieldValidity] = useState(false) const [fieldPristinity, setFieldPristinity] = useState(true) + const [showSubmitFeedback, setShowSubmitFeedback] = useState(false) + const [showResetFeedback, setShowResetFeedback] = useState(false) const checkFieldValidity = (fieldValidity) => { const regex = /^https?:\/\/.{3,}\..+$/m @@ -35,13 +38,29 @@ const SettingsApiItem = ({ const onSubmit = (data) => { setFieldPristinity(false) onSubmitClick(data) + setShowSubmitFeedback(true) } const onReset = (data) => { setFieldPristinity(true) onResetClick(data) + setShowResetFeedback(true) } + useEffect(() => { + if (showSubmitFeedback) { + setTimeout(() => { + setShowSubmitFeedback(false) + }, 2000) + } + if (showResetFeedback) { + setTimeout(() => { + setShowResetFeedback(false) + }, 2000) + } + } + , [showSubmitFeedback, showResetFeedback]) + const label = `${StringHelpers.capitalizeFirstLetter(walletData.wallet)} ${walletData.networkType} server` return ( @@ -71,7 +90,11 @@ const SettingsApiItem = ({ extraStyleClasses={submitButtonExtraClasses} disabled={!fieldValidity} > - Submit + {showSubmitFeedback ? ( + + ) : ( + 'Submit' + )}
diff --git a/src/components/containers/Wallet/TransactionsList.js b/src/components/containers/Wallet/TransactionsList.js index 75071282..1ced83d6 100644 --- a/src/components/containers/Wallet/TransactionsList.js +++ b/src/components/containers/Wallet/TransactionsList.js @@ -1,11 +1,11 @@ import { useContext } from 'react' import Transaction from './Transaction' -import { NetworkContext } from '@Contexts' +import { MintlayerContext } from '@Contexts' import { SkeletonLoader } from '@BasicComponents' import './TransactionsList.css' const TransactionsList = ({ transactionsList, getConfirmations }) => { - const { fetchingTransactions } = useContext(NetworkContext) + const { fetchingTransactions } = useContext(MintlayerContext) const transactionsLoading = fetchingTransactions && transactionsList.length === 0 diff --git a/src/components/containers/Wallet/TransactionsList.test.js b/src/components/containers/Wallet/TransactionsList.test.js index 9dcb0f05..073eb685 100644 --- a/src/components/containers/Wallet/TransactionsList.test.js +++ b/src/components/containers/Wallet/TransactionsList.test.js @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react' import TransactionsList from './TransactionsList' -import { NetworkContext } from '@Contexts' +import { MintlayerContext } from '@Contexts' const TRANSACTIONSSAMPLE = [ { @@ -35,9 +35,9 @@ const TRANSACTIONSSAMPLE = [ test('Render transactions list component', () => { render( - + - , + , ) const transactionsList = screen.getByTestId('transactions-list') const transactions = screen.getAllByTestId('transaction') @@ -48,9 +48,9 @@ test('Render transactions list component', () => { test('Render transactions list component - empty', () => { render( - + - , + , ) const transactionsList = screen.getByTestId('transactions-list') const transactions = screen.getAllByTestId('transaction') @@ -61,9 +61,9 @@ test('Render transactions list component - empty', () => { test('Render transactions list component - loading', () => { render( - + - , + , ) const transactionsList = screen.getByTestId('transactions-list') const skeletonLoading = screen.getAllByTestId('card') diff --git a/src/contexts/BitcoinProvider/BitcoinProvider.js b/src/contexts/BitcoinProvider/BitcoinProvider.js new file mode 100644 index 00000000..240bc9c6 --- /dev/null +++ b/src/contexts/BitcoinProvider/BitcoinProvider.js @@ -0,0 +1,133 @@ +import { useEffect, useState, createContext, useContext } from 'react' +import { AccountContext, SettingsContext } from '@Contexts' +import { LocalStorageService } from '@Storage' +import { AppInfo } from '@Constants' + +import { Electrum } from '@APIs' +import { BTC, Format } from '@Helpers' + +const BitcoinContext = createContext() + +const BitcoinProvider = ({ value: propValue, children }) => { + const { addresses, accountID } = useContext(AccountContext) + const { networkType } = useContext(SettingsContext) + + const currentBtcAddress = + networkType === AppInfo.NETWORK_TYPES.MAINNET + ? addresses.btcMainnetAddress + : addresses.btcTestnetAddress + + const [currentBlockHeight, setCurrentBlockHeight] = useState(0) + const [onlineHeight, setOnlineHeight] = useState(0) + + + const [btcTransactions, setBtcTransactions] = useState([]) + const [btcBalance, setBtcBalance] = useState(0) + const [btcUtxos, setBtcUtxos] = useState([]) + + const [fetchingBalances, setFetchingBalances] = useState(false) + const [fetchingTransactions, setFetchingTransactions] = useState(false) + const [fetchingUtxos, setFetchingUtxos] = useState(false) + + const fetchAllData = async () => { + const account = LocalStorageService.getItem('unlockedAccount') + + if (!account) return + + setFetchingBalances(true) + setFetchingTransactions(true) + setFetchingUtxos(true) + + const getTransactions = async () => { + try { + const response = await Electrum.getAddressTransactions(currentBtcAddress) + const transactions = JSON.parse(response) + const parsedTransactions = BTC.getParsedTransactions( + transactions, + currentBtcAddress, + ) + setBtcTransactions(parsedTransactions) + setFetchingTransactions(false) + } catch (error) { + console.error(error) + setFetchingTransactions(false) + } + } + + const getWalletUtxos = async () => { + try { + setFetchingUtxos(true) + const utxos = await Electrum.getAddressUtxo(currentBtcAddress) + setBtcUtxos(JSON.parse(utxos)) + setFetchingUtxos(false) + } catch (error) { + console.error(error) + setFetchingUtxos(false) + } + } + + const getBalance = async () => { + try { + const utxos = await Electrum.getAddressUtxo(currentBtcAddress) + const satoshiBalance = BTC.calculateBalanceFromUtxoList(JSON.parse(utxos)) + const balanceConvertedToBTC = BTC.convertSatoshiToBtc(satoshiBalance) + const formattedBalance = Format.BTCValue(balanceConvertedToBTC) + setBtcBalance(formattedBalance) + setFetchingBalances(false) + } catch (error) { + console.error(error) + setFetchingBalances(false) + } + } + + await getTransactions() + await getWalletUtxos() + await getBalance() + } + + useEffect(() => { + setCurrentBlockHeight(onlineHeight) + // fetch addresses, utxos, transactions + // fetch data one by one + const getData = async () => { + await fetchAllData() + } + getData() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [onlineHeight, accountID, networkType]) + + useEffect(() => { + const getData = async () => { + const result = await Electrum.getLastBlockHeight() + setOnlineHeight(result) + } + getData() + const data = setInterval(getData, AppInfo.REFRESH_INTERVAL) + + return () => clearInterval(data) + }, []) + + const value = { + btcBalance, + btcTransactions, + btcUtxos, + currentBlockHeight, + + fetchingBalances, + fetchingTransactions, + fetchingUtxos, + setFetchingBalances, + setFetchingTransactions, + setFetchingUtxos, + + fetchAllData, + } + + return ( + + {children} + + ) +} + +export { BitcoinContext, BitcoinProvider } diff --git a/src/contexts/NetworkProvider/NetworkProvider.js b/src/contexts/MintlayerProvider/MintlayerProvider.js similarity index 96% rename from src/contexts/NetworkProvider/NetworkProvider.js rename to src/contexts/MintlayerProvider/MintlayerProvider.js index 22e0497f..b2be9aa0 100644 --- a/src/contexts/NetworkProvider/NetworkProvider.js +++ b/src/contexts/MintlayerProvider/MintlayerProvider.js @@ -7,11 +7,9 @@ import { ML } from '@Helpers' import { Mintlayer } from '@APIs' import { LocalStorageService } from '@Storage' -const NetworkContext = createContext() +const MintlayerContext = createContext() -const REFRESH_INTERVAL = 1000 * 60 // one per minute - -const NetworkProvider = ({ value: propValue, children }) => { +const MintlayerProvider = ({ value: propValue, children }) => { const { addresses, accountID, accountName } = useContext(AccountContext) const { networkType } = useContext(SettingsContext) @@ -154,7 +152,7 @@ const NetworkProvider = ({ value: propValue, children }) => { setFetchingTransactions(false) // fetch utxos - const accountName = account.name + const accountName = account && account.name const unconfirmedTransactionString = `${AppInfo.UNCONFIRMED_TRANSACTION_NAME}_${accountName}_${networkType}` const unconfirmedTransactions = LocalStorageService.getItem(unconfirmedTransactionString) || [] @@ -322,7 +320,7 @@ const NetworkProvider = ({ value: propValue, children }) => { setOnlineHeight(block_height) } getData() - const data = setInterval(getData, REFRESH_INTERVAL) + const data = setInterval(getData, AppInfo.REFRESH_INTERVAL) return () => clearInterval(data) }, []) @@ -362,10 +360,10 @@ const NetworkProvider = ({ value: propValue, children }) => { } return ( - + {children} - + ) } -export { NetworkContext, NetworkProvider } +export { MintlayerContext, MintlayerProvider } diff --git a/src/contexts/index.js b/src/contexts/index.js index af1dd7ab..02fc271b 100644 --- a/src/contexts/index.js +++ b/src/contexts/index.js @@ -14,16 +14,20 @@ import { } from './TransactionProvider/TransactionProvider' import { - NetworkContext, - NetworkProvider, -} from './NetworkProvider/NetworkProvider' + MintlayerContext, + MintlayerProvider, +} from './MintlayerProvider/MintlayerProvider' + +import { + BitcoinContext, + BitcoinProvider, +} from './BitcoinProvider/BitcoinProvider' import { ExchangeRatesContext, ExchangeRatesProvider, } from './ExchangeRatesProvider/ExchangeRatesProvider' - export { AccountContext, AccountProvider, @@ -31,8 +35,10 @@ export { SettingsProvider, TransactionContext, TransactionProvider, - NetworkContext, - NetworkProvider, + MintlayerContext, + MintlayerProvider, + BitcoinContext, + BitcoinProvider, ExchangeRatesContext, ExchangeRatesProvider, } diff --git a/src/hooks/UseWalletInfo/useBtcWalletInfo.js b/src/hooks/UseWalletInfo/useBtcWalletInfo.js index 01edcda9..b2838ed2 100644 --- a/src/hooks/UseWalletInfo/useBtcWalletInfo.js +++ b/src/hooks/UseWalletInfo/useBtcWalletInfo.js @@ -1,59 +1,31 @@ -import { useEffect, useState, useRef, useCallback } from 'react' - -import { Electrum } from '@APIs' -import { BTC, Format } from '@Helpers' +import { useContext } from 'react' +import { BitcoinContext } from '@Contexts' const useBtcWalletInfo = (address) => { - const effectCalled = useRef(false) - const [btcTransactionsList, setBtcTransactionsList] = useState([]) - const [btcBalance, setBtcBalance] = useState(0) - const [fetchingBalances, setFetchingBalances] = useState(false) - const isBitcoin = true - - const getTransactions = useCallback(async () => { - try { - if (!address || !isBitcoin) return - const response = await Electrum.getAddressTransactions(address) - const transactions = JSON.parse(response) - const parsedTransactions = BTC.getParsedTransactions( - transactions, - address, - ) - setBtcTransactionsList(parsedTransactions) - } catch (error) { - console.error(error) - } - }, [address, isBitcoin]) - - const getBalance = useCallback(async () => { - try { - setFetchingBalances(true) - if (!address) return '' - const utxos = await Electrum.getAddressUtxo(address) - const satoshiBalance = BTC.calculateBalanceFromUtxoList(JSON.parse(utxos)) - const balanceConvertedToBTC = BTC.convertSatoshiToBtc(satoshiBalance) - const formattedBalance = Format.BTCValue(balanceConvertedToBTC) - setBtcBalance(formattedBalance) - setFetchingBalances(false) - } catch (error) { - console.error(error) - setFetchingBalances(false) - } - }, [address]) - - useEffect(() => { - /* istanbul ignore next */ - if (effectCalled.current) return - effectCalled.current = true - - getTransactions() - getBalance() - }, [getBalance, getTransactions]) + const { + btcBalance, + btcTransactions, + btcUtxos, + currentBlockHeight, + fetchingBalances, + fetchingTransactions, + fetchingUtxos, + setFetchingBalances, + setFetchingTransactions, + setFetchingUtxos, + } = useContext(BitcoinContext) return { - transactions: btcTransactionsList, + transactions: btcTransactions, balance: btcBalance, + utxos: btcUtxos, + currentBlockHeight, fetchingBalances, + fetchingTransactions, + fetchingUtxos, + setFetchingBalances, + setFetchingTransactions, + setFetchingUtxos, } } diff --git a/src/hooks/UseWalletInfo/useMlWalletInfo.js b/src/hooks/UseWalletInfo/useMlWalletInfo.js index d16a6e74..79787734 100644 --- a/src/hooks/UseWalletInfo/useMlWalletInfo.js +++ b/src/hooks/UseWalletInfo/useMlWalletInfo.js @@ -1,5 +1,5 @@ import { useContext } from 'react' -import { NetworkContext } from '@Contexts' +import { MintlayerContext } from '@Contexts' const useMlWalletInfo = (addresses, token) => { const { @@ -20,7 +20,7 @@ const useMlWalletInfo = (addresses, token) => { fetchingUtxos, fetchingTransactions, fetchingDelegations, - } = useContext(NetworkContext) + } = useContext(MintlayerContext) // const nativecoins const nativecoins = ['Mintlayer', 'Bitcoin'] diff --git a/src/index.js b/src/index.js index f2d0ab40..7ec092eb 100644 --- a/src/index.js +++ b/src/index.js @@ -34,7 +34,8 @@ import { AccountProvider, SettingsProvider, TransactionProvider, - NetworkProvider, + MintlayerProvider, + BitcoinProvider, ExchangeRatesProvider, } from '@Contexts' import { ML } from '@Cryptos' @@ -138,7 +139,8 @@ const App = () => { }) } } - browser.runtime && browser.runtime.onMessage.addListener(onMessageListener) + browser.runtime && + browser.runtime.onMessage.addListener(onMessageListener) return () => { browser.runtime && browser.runtime.onMessage.removeListener(onMessageListener) @@ -177,7 +179,7 @@ const App = () => { )} {removeAccountPopupOpen && ( - + )} @@ -251,15 +253,17 @@ root.render( - - - - - - - - - + + + + + + + + + + + , diff --git a/src/pages/Dashboard/Dashboard.js b/src/pages/Dashboard/Dashboard.js index f9f30554..e317dabc 100644 --- a/src/pages/Dashboard/Dashboard.js +++ b/src/pages/Dashboard/Dashboard.js @@ -22,10 +22,6 @@ import { AppInfo } from '@Constants' const DashboardPage = () => { const { addresses, accountName, accountID } = useContext(AccountContext) const { networkType } = useContext(SettingsContext) - const currentBtcAddress = - networkType === AppInfo.NETWORK_TYPES.MAINNET - ? addresses.btcMainnetAddress - : addresses.btcTestnetAddress const [openConnectConfirmation, setOpenConnectConfirmation] = useState(false) const [allowClosing, setAllowClosing] = useState(true) @@ -33,7 +29,7 @@ const DashboardPage = () => { const [connectedWalletType, setConnectedWalletType] = useState('') const { balance: btcBalance, fetchingBalances: btcFetchingBalances } = - useBtcWalletInfo(currentBtcAddress) + useBtcWalletInfo() const { balance: mlBalance, tokenBalances, diff --git a/src/utils/Constants/AppInfo/AppInfo.js b/src/utils/Constants/AppInfo/AppInfo.js index 50d7bccb..78a31c04 100644 --- a/src/utils/Constants/AppInfo/AppInfo.js +++ b/src/utils/Constants/AppInfo/AppInfo.js @@ -47,6 +47,7 @@ const walletTypes = [ ] const MAX_ML_FEE = 500000000000 +const REFRESH_INTERVAL = 1000 * 60 // one per minute export { appAccounts, @@ -66,4 +67,5 @@ export { APPROPRIATE_COST_PER_BLOCK, APPROPRIATE_MARGIN_RATIO_PER_THOUSAND, APP_LOCAL_STORAGE_CUSTOM_SERVERS, + REFRESH_INTERVAL, } From e9fbd8a9ab04ae132a7afe6ab13fd0044d8c6360 Mon Sep 17 00:00:00 2001 From: Alexander <69318224+owlsua@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:26:55 +0200 Subject: [PATCH 5/6] feat: add AbortSignal for data fetching (#181) --- src/services/API/Mintlayer/Mintlayer.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/services/API/Mintlayer/Mintlayer.js b/src/services/API/Mintlayer/Mintlayer.js index 1349d7fa..7883be30 100644 --- a/src/services/API/Mintlayer/Mintlayer.js +++ b/src/services/API/Mintlayer/Mintlayer.js @@ -23,7 +23,11 @@ const requestMintlayer = async (url, body = null, request = fetch) => { const method = body ? 'POST' : 'GET' try { - const result = await request(url, { method, body }) + const result = await request(url, { + method, + body, + signal: AbortSignal.timeout(20000), + }) if (!result.ok) { const error = await result.json() if (error.error === 'Address not found') { @@ -87,6 +91,7 @@ const tryServers = async (endpoint, body = null, forceNetwork) => { const combinedMintlayerServers = customMintlayerServer ? [customMintlayerServer, ...defaultMintlayerServers] : [...defaultMintlayerServers] + for (let i = 0; i < combinedMintlayerServers.length; i++) { try { const response = await requestMintlayer( From fb2d877f489bc444eded30a994194b154ebc220d Mon Sep 17 00:00:00 2001 From: owlsua Date: Thu, 1 Aug 2024 10:31:52 +0200 Subject: [PATCH 6/6] bump version to 1.3.0 --- package-lock.json | 4 ++-- package.json | 2 +- public/manifestDefault.json | 2 +- public/manifestFirefox.json | 2 +- src/pages/CreateRestore/CreateRestore.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 689b6a62..f2b028a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "browser-extension", - "version": "1.2.9", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "browser-extension", - "version": "1.2.8", + "version": "1.3.0", "dependencies": { "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", "@mintlayer/entropy-generator": "^1.0.4", diff --git a/package.json b/package.json index 17f2f270..31614d53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "browser-extension", - "version": "1.2.9", + "version": "1.3.0", "private": true, "dependencies": { "@mintlayer/entropy-generator": "^1.0.4", diff --git a/public/manifestDefault.json b/public/manifestDefault.json index 3f2df829..160c282d 100644 --- a/public/manifestDefault.json +++ b/public/manifestDefault.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Mojito - A Mintlayer Wallet", - "version": "1.2.9", + "version": "1.3.0", "short_name": "Mojito", "description": "Mojito is a non-custodial decentralized crypto wallet that lets you send and receive BTC and ML from any other address.", "homepage_url": "https://www.mintlayer.org/", diff --git a/public/manifestFirefox.json b/public/manifestFirefox.json index 78996593..7bc11ca5 100644 --- a/public/manifestFirefox.json +++ b/public/manifestFirefox.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Mojito - A Mintlayer Wallet", - "version": "1.2.9", + "version": "1.3.0", "description": "Mojito is a non-custodial decentralized crypto wallet that lets you send and receive BTC and ML from any other address.", "homepage_url": "https://www.mintlayer.org/", "icons": { diff --git a/src/pages/CreateRestore/CreateRestore.js b/src/pages/CreateRestore/CreateRestore.js index f5e76dfa..ae710817 100644 --- a/src/pages/CreateRestore/CreateRestore.js +++ b/src/pages/CreateRestore/CreateRestore.js @@ -76,7 +76,7 @@ const CreateRestorePage = () => { className="footnote-version" data-testid="footnote-name" > - v1.2.9 + v1.3.0