Changeset View
Changeset View
Standalone View
Standalone View
web/cashtab/src/hooks/useWallet.js
/* eslint-disable react-hooks/exhaustive-deps */ | /* eslint-disable react-hooks/exhaustive-deps */ | ||||
import { useState, useEffect } from 'react'; | import { useState, useEffect } from 'react'; | ||||
import useAsyncTimeout from '@hooks/useAsyncTimeout'; | import useAsyncTimeout from '@hooks/useAsyncTimeout'; | ||||
import usePrevious from '@hooks/usePrevious'; | import usePrevious from '@hooks/usePrevious'; | ||||
import useBCH from '@hooks/useBCH'; | import useBCH from '@hooks/useBCH'; | ||||
import BigNumber from 'bignumber.js'; | import BigNumber from 'bignumber.js'; | ||||
import { | import { | ||||
fromSmallestDenomination, | fromSmallestDenomination, | ||||
loadStoredWallet, | loadStoredWallet, | ||||
isValidStoredWallet, | isValidStoredWallet, | ||||
isLegacyMigrationRequired, | isLegacyMigrationRequired, | ||||
whichUtxosWereAdded, | |||||
whichUtxosWereConsumed, | |||||
addNewHydratedUtxos, | |||||
removeConsumedUtxos, | |||||
areAllUtxosIncludedInIncrementallyHydratedUtxos, | |||||
} from '@utils/cashMethods'; | } from '@utils/cashMethods'; | ||||
import { isValidCashtabSettings } from '@utils/validation'; | import { isValidCashtabSettings } from '@utils/validation'; | ||||
import localforage from 'localforage'; | import localforage from 'localforage'; | ||||
import { currency } from '@components/Common/Ticker'; | import { currency } from '@components/Common/Ticker'; | ||||
import isEmpty from 'lodash.isempty'; | import isEmpty from 'lodash.isempty'; | ||||
import isEqual from 'lodash.isequal'; | import isEqual from 'lodash.isequal'; | ||||
import { | import { | ||||
xecReceivedNotification, | xecReceivedNotification, | ||||
▲ Show 20 Lines • Show All 184 Lines • ▼ Show 20 Lines | const update = async ({ wallet }) => { | ||||
const publicKeys = [ | const publicKeys = [ | ||||
wallet.Path145.publicKey, | wallet.Path145.publicKey, | ||||
wallet.Path245.publicKey, | wallet.Path245.publicKey, | ||||
wallet.Path1899.publicKey, | wallet.Path1899.publicKey, | ||||
]; | ]; | ||||
const utxos = await getUtxos(BCH, cashAddresses); | const utxos = await getUtxos(BCH, cashAddresses); | ||||
console.log(`utxos`, utxos); | |||||
// If an error is returned or utxos from only 1 address are returned | // If an error is returned or utxos from only 1 address are returned | ||||
if (!utxos || isEmpty(utxos) || utxos.error || utxos.length < 2) { | if (!utxos || isEmpty(utxos) || utxos.error || utxos.length < 2) { | ||||
// Throw error here to prevent more attempted api calls | // Throw error here to prevent more attempted api calls | ||||
// as you are likely already at rate limits | // as you are likely already at rate limits | ||||
throw new Error('Error fetching utxos'); | throw new Error('Error fetching utxos'); | ||||
} | } | ||||
// Need to call with wallet as a parameter rather than trusting it is in state, otherwise can sometimes get wallet=false from haveUtxosChanged | // Need to call with wallet as a parameter rather than trusting it is in state, otherwise can sometimes get wallet=false from haveUtxosChanged | ||||
const utxosHaveChanged = haveUtxosChanged( | const utxosHaveChanged = haveUtxosChanged( | ||||
wallet, | wallet, | ||||
utxos, | utxos, | ||||
previousUtxos, | previousUtxos, | ||||
); | ); | ||||
console.log(`utxosHaveChanged`, utxosHaveChanged); | |||||
// If the utxo set has not changed, | // If the utxo set has not changed, | ||||
if (!utxosHaveChanged) { | if (!utxosHaveChanged) { | ||||
// remove api error here; otherwise it will remain if recovering from a rate | // remove api error here; otherwise it will remain if recovering from a rate | ||||
// limit error with an unchanged utxo set | // limit error with an unchanged utxo set | ||||
setApiError(false); | setApiError(false); | ||||
// then wallet.state has not changed and does not need to be updated | // then wallet.state has not changed and does not need to be updated | ||||
//console.timeEnd("update"); | //console.timeEnd("update"); | ||||
return; | return; | ||||
} | } | ||||
const hydratedUtxoDetails = await getHydratedUtxoDetails( | // Hm you can only do this if utxos and wallet.state.utxos are not undefined | ||||
// they may be undefined if you boot up a new wallet | |||||
// Nest a try...catch; if you have an error in incremental utxo approach, fall back to hydrating them all | |||||
let incrementalHydratedUtxosValid; | |||||
let incrementallyAdjustedHydratedUtxoDetails; | |||||
try { | |||||
const utxosAdded = whichUtxosWereAdded( | |||||
wallet.state.utxos, | |||||
utxos, | |||||
); | |||||
console.log(`utxosAdded`, utxosAdded); | |||||
const utxosConsumed = whichUtxosWereConsumed( | |||||
wallet.state.utxos, | |||||
utxos, | |||||
); | |||||
console.log(`utxosConsumed`, utxosConsumed); | |||||
console.log( | |||||
`wallet.state.hydratedUtxoDetails`, | |||||
wallet.state.hydratedUtxoDetails, | |||||
); | |||||
let incrementallyAdjustedHydratedUtxoDetails = | |||||
wallet.state.hydratedUtxoDetails; | |||||
// Hydrate only added utxos | |||||
// Note this needs to be in the right format! | |||||
if (utxosConsumed) { | |||||
incrementallyAdjustedHydratedUtxoDetails = | |||||
removeConsumedUtxos( | |||||
utxosConsumed, | |||||
incrementallyAdjustedHydratedUtxoDetails, | |||||
); | |||||
const afterRemoveConsumedUtxos = | |||||
incrementallyAdjustedHydratedUtxoDetails; | |||||
console.log( | |||||
`incrementallyAdjustedHydratedUtxoDetails afterRemoveConsumedUtxos`, | |||||
afterRemoveConsumedUtxos, | |||||
); | |||||
} | |||||
if (utxosAdded) { | |||||
const addedHydratedUtxos = await getHydratedUtxoDetails( | |||||
BCH, | BCH, | ||||
utxosAdded, | |||||
); | |||||
console.log(`addedHydratedUtxos`, addedHydratedUtxos); | |||||
incrementallyAdjustedHydratedUtxoDetails = | |||||
addNewHydratedUtxos( | |||||
addedHydratedUtxos, | |||||
incrementallyAdjustedHydratedUtxoDetails, | |||||
); | |||||
const afterAddHydratedUtxoDetails = | |||||
incrementallyAdjustedHydratedUtxoDetails; | |||||
console.log( | |||||
`incrementallyAdjustedHydratedUtxoDetails afterAddHydratedUtxoDetails`, | |||||
afterAddHydratedUtxoDetails, | |||||
); | |||||
} | |||||
// If all the above is good, find some checks, then set wallet state with that | |||||
// Else use the old way | |||||
// If (incrementallyAdjustedHydratedUtxoDetails contains all utxos as utxos) then set wallet state | |||||
// else hydrate all of'em | |||||
const incrementalHydratedUtxosValid = | |||||
areAllUtxosIncludedInIncrementallyHydratedUtxos( | |||||
utxos, | utxos, | ||||
incrementallyAdjustedHydratedUtxoDetails, | |||||
); | |||||
console.log( | |||||
`areAllUtxosIncludedInIncrementallyHydratedUtxos`, | |||||
incrementalHydratedUtxosValid, | |||||
); | |||||
} catch (err) { | |||||
console.log( | |||||
`Error in incremental determination of hydratedUtxoDetails`, | |||||
); | ); | ||||
console.log(err); | |||||
incrementalHydratedUtxosValid = false; | |||||
} | |||||
if (!incrementalHydratedUtxosValid) { | |||||
console.log( | |||||
`Incremental approach invalid, hydrating all utxos`, | |||||
); | |||||
incrementallyAdjustedHydratedUtxoDetails = | |||||
await getHydratedUtxoDetails(BCH, utxos); | |||||
} | |||||
const slpBalancesAndUtxos = await getSlpBalancesAndUtxos( | const slpBalancesAndUtxos = await getSlpBalancesAndUtxos( | ||||
BCH, | BCH, | ||||
hydratedUtxoDetails, | incrementallyAdjustedHydratedUtxoDetails, | ||||
); | ); | ||||
const txHistory = await getTxHistory(BCH, cashAddresses); | const txHistory = await getTxHistory(BCH, cashAddresses); | ||||
// public keys are used to determined if a tx is incoming outgoing | // public keys are used to determined if a tx is incoming outgoing | ||||
const parsedTxHistory = await getTxData( | const parsedTxHistory = await getTxData( | ||||
BCH, | BCH, | ||||
txHistory, | txHistory, | ||||
publicKeys, | publicKeys, | ||||
Show All 17 Lines | const update = async ({ wallet }) => { | ||||
newState.slpBalancesAndUtxos = normalizeSlpBalancesAndUtxos( | newState.slpBalancesAndUtxos = normalizeSlpBalancesAndUtxos( | ||||
slpBalancesAndUtxos, | slpBalancesAndUtxos, | ||||
wallet, | wallet, | ||||
); | ); | ||||
newState.balances = normalizeBalance(slpBalancesAndUtxos); | newState.balances = normalizeBalance(slpBalancesAndUtxos); | ||||
// TODO -- sometimes seeing a discrepancy here | |||||
// Looks like occasionally a change utxo is seen as added, but the sent utxo is not seen as consumed | |||||
// e.g. this tx got notification for +287 https://explorer.be.cash/tx/670bd1f85413d5a093c321f32494a7df560844e04d15a10c484fef45f0527627 | |||||
console.log(`balances`, newState.balances); | |||||
newState.tokens = tokens; | newState.tokens = tokens; | ||||
newState.parsedTxHistory = parsedWithTokens; | newState.parsedTxHistory = parsedWithTokens; | ||||
newState.utxos = utxos; | newState.utxos = utxos; | ||||
newState.hydratedUtxoDetails = hydratedUtxoDetails; | newState.hydratedUtxoDetails = | ||||
incrementallyAdjustedHydratedUtxoDetails; | |||||
// Set wallet with new state field | // Set wallet with new state field | ||||
wallet.state = newState; | wallet.state = newState; | ||||
setWallet(wallet); | setWallet(wallet); | ||||
// Write this state to indexedDb using localForage | // Write this state to indexedDb using localForage | ||||
writeWalletState(wallet, newState); | writeWalletState(wallet, newState); | ||||
// If everything executed correctly, remove apiError | // If everything executed correctly, remove apiError | ||||
▲ Show 20 Lines • Show All 754 Lines • ▼ Show 20 Lines | |||||
// Update wallet every 10s | // Update wallet every 10s | ||||
useAsyncTimeout(async () => { | useAsyncTimeout(async () => { | ||||
const wallet = await getWallet(); | const wallet = await getWallet(); | ||||
update({ | update({ | ||||
wallet, | wallet, | ||||
}).finally(() => { | }).finally(() => { | ||||
setLoading(false); | setLoading(false); | ||||
}); | }); | ||||
}, 10000); | }, 3000); | ||||
const fetchBchPrice = async ( | const fetchBchPrice = async ( | ||||
fiatCode = cashtabSettings ? cashtabSettings.fiatCurrency : 'usd', | fiatCode = cashtabSettings ? cashtabSettings.fiatCurrency : 'usd', | ||||
) => { | ) => { | ||||
// Split this variable out in case coingecko changes | // Split this variable out in case coingecko changes | ||||
const cryptoId = currency.coingeckoId; | const cryptoId = currency.coingeckoId; | ||||
// Keep this in the code, because different URLs will have different outputs require different parsing | // Keep this in the code, because different URLs will have different outputs require different parsing | ||||
const priceApiUrl = `https://api.coingecko.com/api/v3/simple/price?ids=${cryptoId}&vs_currencies=${fiatCode}&include_last_updated_at=true`; | const priceApiUrl = `https://api.coingecko.com/api/v3/simple/price?ids=${cryptoId}&vs_currencies=${fiatCode}&include_last_updated_at=true`; | ||||
▲ Show 20 Lines • Show All 78 Lines • Show Last 20 Lines |