diff --git a/web/cashtab/src/components/Wallet/Wallet.js b/web/cashtab/src/components/Wallet/Wallet.js --- a/web/cashtab/src/components/Wallet/Wallet.js +++ b/web/cashtab/src/components/Wallet/Wallet.js @@ -203,14 +203,33 @@ const WalletInfo = () => { const ContextValue = React.useContext(WalletContext); - const { - wallet, - fiatPrice, - balances, - tokens, - parsedTxHistory, - apiError, - } = ContextValue; + const { wallet, fiatPrice, apiError } = ContextValue; + let balances; + let parsedTxHistory; + let tokens; + // use parameters from wallet.state object and not legacy separate parameters, if they are in state + // handle edge case of user with old wallet who has not opened latest Cashtab version yet + + // If the wallet object from ContextValue has a `state key`, then check which keys are in the wallet object + // Else set it as blank + const paramsInWalletState = wallet.state ? Object.keys(wallet.state) : []; + // If wallet.state includes balances and parsedTxHistory params, use these + // These are saved in indexedDb in the latest version of the app, hence accessible more quickly + if ( + paramsInWalletState.includes('balances') && + paramsInWalletState.includes('parsedTxHistory') && + paramsInWalletState.includes('tokens') + ) { + balances = wallet.state.balances; + parsedTxHistory = wallet.state.parsedTxHistory; + tokens = wallet.state.tokens; + } else { + // If balances and parsedTxHistory are not in the wallet.state object, load them from Context + // This is how the app used to work + balances = ContextValue.balances; + parsedTxHistory = ContextValue.parsedTxHistory; + tokens = ContextValue.tokens; + } const [address, setAddress] = React.useState('cashAddress'); const [activeTab, setActiveTab] = React.useState('txHistory'); diff --git a/web/cashtab/src/components/Wallet/__mocks__/walletAndBalancesMock.js b/web/cashtab/src/components/Wallet/__mocks__/walletAndBalancesMock.js --- a/web/cashtab/src/components/Wallet/__mocks__/walletAndBalancesMock.js +++ b/web/cashtab/src/components/Wallet/__mocks__/walletAndBalancesMock.js @@ -152,3 +152,190 @@ }, ], }; + +export const walletWithBalancesAndTokensWithCorrectState = { + wallet: { + name: 'MigrationTestAlpha', + Path245: { + cashAddress: + 'bitcoincash:qztqe8k4v8ckn8cvfxt5659nhd7dcyvxy54hkry298', + slpAddress: + 'simpleledger:qztqe8k4v8ckn8cvfxt5659nhd7dcyvxy5evac32me', + fundingWif: 'KwgNkyijAaxFr5XQdnaYyNMXVSZobgHzSoKKfWiC3Q7Xr4n7iYMG', + fundingAddress: + 'simpleledger:qztqe8k4v8ckn8cvfxt5659nhd7dcyvxy5evac32me', + legacyAddress: '1EgPUfBgU7ekho3EjtGze87dRADnUE8ojP', + }, + Path145: { + cashAddress: + 'bitcoincash:qq47pcxfn8n7w7jy86njd7pvgsv39l9f9v0lgx569z', + slpAddress: + 'simpleledger:qq47pcxfn8n7w7jy86njd7pvgsv39l9f9vryrap6mu', + fundingWif: 'L2xvTe6CdNxroR6pbdpGWNjAa55AZX5Wm59W5TXMuH31ihNJdDjt', + fundingAddress: + 'simpleledger:qq47pcxfn8n7w7jy86njd7pvgsv39l9f9vryrap6mu', + legacyAddress: '1511T3ynXKgCwXhFijCUWKuTfqbPxFV1AF', + }, + Path1899: { + cashAddress: + 'bitcoincash:qzagy47mvh6qxkvcn3acjnz73rkhkc6y7cptzgcqy6', + slpAddress: + 'simpleledger:qzagy47mvh6qxkvcn3acjnz73rkhkc6y7cdsfndq6y', + fundingWif: 'Kx4FiBMvKK1iXjFk5QTaAK6E4mDGPjmwDZ2HDKGUZpE4gCXMaPe9', + fundingAddress: + 'simpleledger:qzagy47mvh6qxkvcn3acjnz73rkhkc6y7cdsfndq6y', + legacyAddress: '1J1Aq5tAAYxZgSDRo8soKM2Rb41z3xrYpm', + }, + state: { + balances: { + totalBalanceInSatoshis: 6047469, + totalBalance: 0.06047469, + }, + tokens: [ + { + info: { + height: 666987, + tx_hash: + 'e7d554c317db71fd5b50fcf0b2cb4cbdce54a09f1732cfaade0820659318e30a', + tx_pos: 2, + value: 546, + satoshis: 546, + txid: + 'e7d554c317db71fd5b50fcf0b2cb4cbdce54a09f1732cfaade0820659318e30a', + vout: 2, + utxoType: 'token', + transactionType: 'send', + tokenId: + 'bd1acc4c986de57af8d6d2a64aecad8c30ee80f37ae9d066d758923732ddc9ba', + tokenTicker: 'TBS', + tokenName: 'TestBits', + tokenDocumentUrl: 'https://thecryptoguy.com/', + tokenDocumentHash: '', + decimals: 9, + tokenType: 1, + tokenQty: '6.001', + isValid: true, + address: + 'bitcoincash:qztqe8k4v8ckn8cvfxt5659nhd7dcyvxy54hkry298', + }, + tokenId: + 'bd1acc4c986de57af8d6d2a64aecad8c30ee80f37ae9d066d758923732ddc9ba', + balance: '6.001', + hasBaton: false, + }, + ], + parsedTxHistory: [], + }, + }, + balances: { + totalBalanceInSatoshis: 6047469, + totalBalance: 0.06047469, + }, + tokens: [ + { + info: { + height: 666987, + tx_hash: + 'e7d554c317db71fd5b50fcf0b2cb4cbdce54a09f1732cfaade0820659318e30a', + tx_pos: 2, + value: 546, + satoshis: 546, + txid: + 'e7d554c317db71fd5b50fcf0b2cb4cbdce54a09f1732cfaade0820659318e30a', + vout: 2, + utxoType: 'token', + transactionType: 'send', + tokenId: + 'bd1acc4c986de57af8d6d2a64aecad8c30ee80f37ae9d066d758923732ddc9ba', + tokenTicker: 'TBS', + tokenName: 'TestBits', + tokenDocumentUrl: 'https://thecryptoguy.com/', + tokenDocumentHash: '', + decimals: 9, + tokenType: 1, + tokenQty: '6.001', + isValid: true, + address: + 'bitcoincash:qztqe8k4v8ckn8cvfxt5659nhd7dcyvxy54hkry298', + }, + tokenId: + 'bd1acc4c986de57af8d6d2a64aecad8c30ee80f37ae9d066d758923732ddc9ba', + balance: '6.001', + hasBaton: false, + }, + ], +}; + +export const walletWithBalancesAndTokensWithEmptyState = { + wallet: { + name: 'MigrationTestAlpha', + Path245: { + cashAddress: + 'bitcoincash:qztqe8k4v8ckn8cvfxt5659nhd7dcyvxy54hkry298', + slpAddress: + 'simpleledger:qztqe8k4v8ckn8cvfxt5659nhd7dcyvxy5evac32me', + fundingWif: 'KwgNkyijAaxFr5XQdnaYyNMXVSZobgHzSoKKfWiC3Q7Xr4n7iYMG', + fundingAddress: + 'simpleledger:qztqe8k4v8ckn8cvfxt5659nhd7dcyvxy5evac32me', + legacyAddress: '1EgPUfBgU7ekho3EjtGze87dRADnUE8ojP', + }, + Path145: { + cashAddress: + 'bitcoincash:qq47pcxfn8n7w7jy86njd7pvgsv39l9f9v0lgx569z', + slpAddress: + 'simpleledger:qq47pcxfn8n7w7jy86njd7pvgsv39l9f9vryrap6mu', + fundingWif: 'L2xvTe6CdNxroR6pbdpGWNjAa55AZX5Wm59W5TXMuH31ihNJdDjt', + fundingAddress: + 'simpleledger:qq47pcxfn8n7w7jy86njd7pvgsv39l9f9vryrap6mu', + legacyAddress: '1511T3ynXKgCwXhFijCUWKuTfqbPxFV1AF', + }, + Path1899: { + cashAddress: + 'bitcoincash:qzagy47mvh6qxkvcn3acjnz73rkhkc6y7cptzgcqy6', + slpAddress: + 'simpleledger:qzagy47mvh6qxkvcn3acjnz73rkhkc6y7cdsfndq6y', + fundingWif: 'Kx4FiBMvKK1iXjFk5QTaAK6E4mDGPjmwDZ2HDKGUZpE4gCXMaPe9', + fundingAddress: + 'simpleledger:qzagy47mvh6qxkvcn3acjnz73rkhkc6y7cdsfndq6y', + legacyAddress: '1J1Aq5tAAYxZgSDRo8soKM2Rb41z3xrYpm', + }, + state: {}, + }, + balances: { + totalBalanceInSatoshis: 6047469, + totalBalance: 0.06047469, + }, + tokens: [ + { + info: { + height: 666987, + tx_hash: + 'e7d554c317db71fd5b50fcf0b2cb4cbdce54a09f1732cfaade0820659318e30a', + tx_pos: 2, + value: 546, + satoshis: 546, + txid: + 'e7d554c317db71fd5b50fcf0b2cb4cbdce54a09f1732cfaade0820659318e30a', + vout: 2, + utxoType: 'token', + transactionType: 'send', + tokenId: + 'bd1acc4c986de57af8d6d2a64aecad8c30ee80f37ae9d066d758923732ddc9ba', + tokenTicker: 'TBS', + tokenName: 'TestBits', + tokenDocumentUrl: 'https://thecryptoguy.com/', + tokenDocumentHash: '', + decimals: 9, + tokenType: 1, + tokenQty: '6.001', + isValid: true, + address: + 'bitcoincash:qztqe8k4v8ckn8cvfxt5659nhd7dcyvxy54hkry298', + }, + tokenId: + 'bd1acc4c986de57af8d6d2a64aecad8c30ee80f37ae9d066d758923732ddc9ba', + balance: '6.001', + hasBaton: false, + }, + ], +}; diff --git a/web/cashtab/src/components/Wallet/__tests__/Wallet.test.js b/web/cashtab/src/components/Wallet/__tests__/Wallet.test.js --- a/web/cashtab/src/components/Wallet/__tests__/Wallet.test.js +++ b/web/cashtab/src/components/Wallet/__tests__/Wallet.test.js @@ -7,6 +7,8 @@ walletWithBalancesAndTokens, walletWithBalancesMock, walletWithoutBalancesMock, + walletWithBalancesAndTokensWithCorrectState, + walletWithBalancesAndTokensWithEmptyState, } from '../__mocks__/walletAndBalancesMock'; import { BrowserRouter as Router } from 'react-router-dom'; @@ -61,6 +63,32 @@ expect(tree).toMatchSnapshot(); }); +test('Wallet with BCH balances and tokens and state field', () => { + useContextMock.mockReturnValue(walletWithBalancesAndTokensWithCorrectState); + const component = renderer.create( + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); + +test('Wallet with BCH balances and tokens and state field, but no params in state', () => { + useContextMock.mockReturnValue(walletWithBalancesAndTokensWithEmptyState); + const component = renderer.create( + + + + + , + ); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); +}); + test('Without wallet defined', () => { useContextMock.mockReturnValue({ wallet: {}, diff --git a/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap b/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap --- a/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap +++ b/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://goo.gl/fbAQLP @generated exports[`Wallet with BCH balances 1`] = ` Array [ @@ -200,6 +200,206 @@ ] `; +exports[`Wallet with BCH balances and tokens and state field 1`] = ` +Array [ +
+ 0.06047469 + + BCHA +
, +
+ $ + NaN + + USD +
, +
+
+ Copied +
+ + + + + +
+ + + qzagy4 + + 7mvh6qxkvcn3acjnz73rkhkc6y7cpt + + zgcqy6 + +
+
, +
+
+ BCHA +
+
+ SLPA +
+
, +] +`; + +exports[`Wallet with BCH balances and tokens and state field, but no params in state 1`] = ` +Array [ +
+ 0.06047469 + + BCHA +
, +
+ $ + NaN + + USD +
, +
+
+ Copied +
+ + + + + +
+ + + qzagy4 + + 7mvh6qxkvcn3acjnz73rkhkc6y7cpt + + zgcqy6 + +
+
, +
+
+ BCHA +
+
+ SLPA +
+
, +] +`; + exports[`Wallet without BCH balance 1`] = ` Array [
{ + // get wallet object from localforage + const wallet = await getWallet(); + // If wallet object in storage is valid, use it to set state on startup + if (isValidStoredWallet(wallet)) { + // Convert all the token balance figures to big numbers + const liveWalletState = loadStoredWallet(wallet.state); + wallet.state = liveWalletState; + + return wallet; + } + // If no wallet in local storage or no valid wallet in local storage, return blank wallet object + // This is what app used for initial wallet state before all items were stored in localforage + return { + balances: {}, + hydratedUtxoDetails: {}, + tokens: [], + slpBalancesAndUtxos: {}, + parsedTxHistory: [], + utxos: [], + }; + // if valid set the state + }; + const haveUtxosChanged = (wallet, utxos, previousUtxos) => { // Relevant points for this array comparing exercise // https://stackoverflow.com/questions/13757109/triple-equal-signs-return-false-for-arrays-in-javascript-why @@ -179,6 +204,7 @@ if (!wallet) { return; } + // Here is where you want to load the wallet immediately before calling anything, if it's the first function call const cashAddresses = [ wallet.Path245.cashAddress, wallet.Path145.cashAddress, @@ -297,6 +323,7 @@ */ const getWallet = async () => { + //console.log(`getWallet`); let wallet; let existingWallet; try { @@ -391,10 +418,14 @@ }; const getWalletDetails = async wallet => { + console.log(`getWalletDetails()`); if (!wallet) { return false; } // Since this info is in localforage now, only get the var + + // TODO how much of this info is in localforage? do we really need this async function? + const NETWORK = process.env.REACT_APP_NETWORK; const mnemonic = wallet.mnemonic; const rootSeedBuffer = await BCH.Mnemonic.toSeed(mnemonic); @@ -569,6 +600,12 @@ return false; } } + // Make sure stored wallet is in correct format to be used as live wallet + if (isValidStoredWallet(walletToActivate)) { + // Convert all the token balance figures to big numbers + const liveWalletState = loadStoredWallet(walletToActivate.state); + walletToActivate.state = liveWalletState; + } return walletToActivate; }; @@ -764,8 +801,11 @@ }; const handleUpdateWallet = async setWallet => { - const wallet = await getWallet(); + //console.log(`handleUpdateWallet()`); + //const wallet = await getWallet(); + const wallet = await loadWalletFromStorageOnStartup(); setWallet(wallet); + setLoading(false); }; // Parse for incoming BCH transactions @@ -1277,10 +1317,7 @@ setLoading(true); const newWallet = await activateWallet(walletToActivate); setWallet(newWallet); - update({ - wallet: newWallet, - setWalletState, - }).finally(() => setLoading(false)); + setLoading(false); }, addNewSavedWallet, renameWallet,