Changeset View
Changeset View
Standalone View
Standalone View
web/cashtab/src/hooks/useWallet.js
Show First 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | const tryNextAPI = () => { | ||||
console.log(`Setting Api Index to ${currentApiIndex}`); | console.log(`Setting Api Index to ${currentApiIndex}`); | ||||
setApiIndex(currentApiIndex); | setApiIndex(currentApiIndex); | ||||
return setBCH(getBCH(currentApiIndex)); | return setBCH(getBCH(currentApiIndex)); | ||||
// If you have more than one, use the next one | // If you have more than one, use the next one | ||||
// If you are at the "end" of the array, use the first one | // If you are at the "end" of the array, use the first one | ||||
}; | }; | ||||
const normalizeSlpBalancesAndUtxos = (slpBalancesAndUtxos, wallet) => { | const normalizeSlpBalancesAndUtxos = (slpBalancesAndUtxos, wallet) => { | ||||
const Accounts = [wallet.Path245, wallet.Path145]; | const Accounts = [wallet.Path245, wallet.Path145, wallet.Path1899]; | ||||
slpBalancesAndUtxos.nonSlpUtxos.forEach(utxo => { | slpBalancesAndUtxos.nonSlpUtxos.forEach(utxo => { | ||||
const derivatedAccount = Accounts.find( | const derivatedAccount = Accounts.find( | ||||
account => account.cashAddress === utxo.address, | account => account.cashAddress === utxo.address, | ||||
); | ); | ||||
utxo.wif = derivatedAccount.fundingWif; | utxo.wif = derivatedAccount.fundingWif; | ||||
}); | }); | ||||
return slpBalancesAndUtxos; | return slpBalancesAndUtxos; | ||||
}; | }; | ||||
const normalizeBalance = slpBalancesAndUtxos => { | const normalizeBalance = slpBalancesAndUtxos => { | ||||
const totalBalanceInSatoshis = slpBalancesAndUtxos.nonSlpUtxos.reduce( | const totalBalanceInSatoshis = slpBalancesAndUtxos.nonSlpUtxos.reduce( | ||||
(previousBalance, utxo) => previousBalance + utxo.satoshis, | (previousBalance, utxo) => previousBalance + utxo.satoshis, | ||||
0, | 0, | ||||
); | ); | ||||
return { | return { | ||||
totalBalanceInSatoshis, | totalBalanceInSatoshis, | ||||
totalBalance: BCH.BitcoinCash.toBitcoinCash(totalBalanceInSatoshis), | totalBalance: BCH.BitcoinCash.toBitcoinCash(totalBalanceInSatoshis), | ||||
}; | }; | ||||
}; | }; | ||||
const deriveAccount = async ({ masterHDNode, path }) => { | const deriveAccount = async (BCH, { masterHDNode, path }) => { | ||||
const node = BCH.HDNode.derivePath(masterHDNode, path); | const node = BCH.HDNode.derivePath(masterHDNode, path); | ||||
const cashAddress = BCH.HDNode.toCashAddress(node); | const cashAddress = BCH.HDNode.toCashAddress(node); | ||||
const slpAddress = BCH.SLP.Address.toSLPAddress(cashAddress); | const slpAddress = BCH.SLP.Address.toSLPAddress(cashAddress); | ||||
return { | return { | ||||
cashAddress, | cashAddress, | ||||
slpAddress, | slpAddress, | ||||
fundingWif: BCH.HDNode.toWIF(node), | fundingWif: BCH.HDNode.toWIF(node), | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | const update = async ({ wallet, setWalletState }) => { | ||||
//console.time("update"); | //console.time("update"); | ||||
try { | try { | ||||
if (!wallet) { | if (!wallet) { | ||||
return; | return; | ||||
} | } | ||||
const cashAddresses = [ | const cashAddresses = [ | ||||
wallet.Path245.cashAddress, | wallet.Path245.cashAddress, | ||||
wallet.Path145.cashAddress, | wallet.Path145.cashAddress, | ||||
wallet.Path1899.cashAddress, | |||||
]; | ]; | ||||
const utxos = await getUtxos(BCH, cashAddresses); | const utxos = await getUtxos(BCH, cashAddresses); | ||||
//console.log(`utxos`, utxos); | //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 | ||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | const update = async ({ wallet, setWalletState }) => { | ||||
//console.timeEnd("update"); | //console.timeEnd("update"); | ||||
// Try another endpoint | // Try another endpoint | ||||
console.log(`Trying next API...`); | console.log(`Trying next API...`); | ||||
tryNextAPI(); | tryNextAPI(); | ||||
} | } | ||||
//console.timeEnd("update"); | //console.timeEnd("update"); | ||||
}; | }; | ||||
const getWallet = async () => { | const getActiveWalletFromLocalForage = async () => { | ||||
let wallet; | let wallet; | ||||
try { | try { | ||||
wallet = await localforage.getItem('wallet'); | |||||
} catch (err) { | |||||
console.log(`Error in getActiveWalletFromLocalForage`, err); | |||||
wallet = null; | |||||
} | |||||
return wallet; | |||||
}; | |||||
/* | |||||
const getSavedWalletsFromLocalForage = async () => { | |||||
let savedWallets; | |||||
try { | |||||
savedWallets = await localforage.getItem('savedWallets'); | |||||
} catch (err) { | |||||
console.log(`Error in getSavedWalletsFromLocalForage`, err); | |||||
savedWallets = null; | |||||
} | |||||
return savedWallets; | |||||
}; | |||||
*/ | |||||
const getWallet = async () => { | |||||
let wallet; | |||||
let existingWallet; | let existingWallet; | ||||
try { | try { | ||||
existingWallet = await localforage.getItem('wallet'); | existingWallet = await getActiveWalletFromLocalForage(); | ||||
// existing wallet will be | |||||
// 1 - the 'wallet' value from localForage, if it exists | |||||
// 2 - false if it does not exist in localForage | |||||
// 3 - null if error | |||||
// If the wallet does not have Path1899, add it | |||||
if (existingWallet && !existingWallet.Path1899) { | |||||
console.log(`Wallet does not have Path1899`); | |||||
existingWallet = await migrateLegacyWallet(BCH, existingWallet); | |||||
} | |||||
// If not in localforage then existingWallet = false, check localstorage | // If not in localforage then existingWallet = false, check localstorage | ||||
if (!existingWallet) { | if (!existingWallet) { | ||||
console.log(`no existing wallet, checking local storage`); | console.log(`no existing wallet, checking local storage`); | ||||
existingWallet = JSON.parse( | existingWallet = JSON.parse( | ||||
window.localStorage.getItem('wallet'), | window.localStorage.getItem('wallet'), | ||||
); | ); | ||||
console.log( | console.log(`existingWallet from localStorage`, existingWallet); | ||||
`existingWallet from localStorage`, | |||||
existingWallet, | |||||
); | |||||
// If you find it here, move it to indexedDb | // If you find it here, move it to indexedDb | ||||
if (existingWallet !== null) { | if (existingWallet !== null) { | ||||
wallet = await getWalletDetails(existingWallet); | wallet = await getWalletDetails(existingWallet); | ||||
await localforage.setItem('wallet', wallet); | await localforage.setItem('wallet', wallet); | ||||
return wallet; | return wallet; | ||||
} | } | ||||
} | } | ||||
} catch (e) { | } catch (err) { | ||||
console.log(e); | console.log(`Error in getWallet()`, err); | ||||
existingWallet = null; | /* | ||||
Error here implies problem interacting with localForage or localStorage API | |||||
Have not seen this error in testing | |||||
In this case, you still want to return 'wallet' using the logic below based on | |||||
the determination of 'existingWallet' from the logic above | |||||
*/ | |||||
} | } | ||||
// If no wallet in indexedDb or localforage or caught error above or the initial 'false' is in indexedDB | |||||
if (existingWallet === null || !existingWallet) { | if (existingWallet === null || !existingWallet) { | ||||
wallet = await getWalletDetails(existingWallet); | wallet = await getWalletDetails(existingWallet); | ||||
await localforage.setItem('wallet', wallet); | await localforage.setItem('wallet', wallet); | ||||
} else { | } else { | ||||
wallet = existingWallet; | wallet = existingWallet; | ||||
} | } | ||||
return wallet; | |||||
}; | |||||
// todo: only do this if you didn't get it out of storage | const migrateLegacyWallet = async (BCH, wallet) => { | ||||
//wallet = await getWalletDetails(existingWallet); | console.log(`migrateLegacyWallet`); | ||||
//await localforage.setItem("wallet", wallet); | console.log(`legacyWallet`, wallet); | ||||
} catch (error) { | const NETWORK = process.env.REACT_APP_NETWORK; | ||||
console.log(error); | const mnemonic = wallet.mnemonic; | ||||
const rootSeedBuffer = await BCH.Mnemonic.toSeed(mnemonic); | |||||
let masterHDNode; | |||||
if (NETWORK === `mainnet`) { | |||||
masterHDNode = BCH.HDNode.fromSeed(rootSeedBuffer); | |||||
} else { | |||||
masterHDNode = BCH.HDNode.fromSeed(rootSeedBuffer, 'testnet'); | |||||
} | } | ||||
const Path1899 = await deriveAccount(BCH, { | |||||
masterHDNode, | |||||
path: "m/44'/1899'/0'/0/0", | |||||
}); | |||||
wallet.Path1899 = Path1899; | |||||
try { | |||||
await localforage.setItem('wallet', wallet); | |||||
} catch (err) { | |||||
console.log( | |||||
`Error setting wallet to wallet indexedDb in migrateLegacyWallet()`, | |||||
); | |||||
console.log(err); | |||||
} | |||||
return wallet; | return wallet; | ||||
}; | }; | ||||
const getWalletDetails = async wallet => { | const getWalletDetails = async wallet => { | ||||
if (!wallet) { | if (!wallet) { | ||||
return false; | return false; | ||||
} | } | ||||
// Since this info is in localforage now, only get the var | // Since this info is in localforage now, only get the var | ||||
const NETWORK = process.env.REACT_APP_NETWORK; | const NETWORK = process.env.REACT_APP_NETWORK; | ||||
const mnemonic = wallet.mnemonic; | const mnemonic = wallet.mnemonic; | ||||
const rootSeedBuffer = await BCH.Mnemonic.toSeed(mnemonic); | const rootSeedBuffer = await BCH.Mnemonic.toSeed(mnemonic); | ||||
let masterHDNode; | let masterHDNode; | ||||
if (NETWORK === `mainnet`) | if (NETWORK === `mainnet`) { | ||||
masterHDNode = BCH.HDNode.fromSeed(rootSeedBuffer); | masterHDNode = BCH.HDNode.fromSeed(rootSeedBuffer); | ||||
else masterHDNode = BCH.HDNode.fromSeed(rootSeedBuffer, 'testnet'); | } else { | ||||
masterHDNode = BCH.HDNode.fromSeed(rootSeedBuffer, 'testnet'); | |||||
} | |||||
const Path245 = await deriveAccount({ | const Path245 = await deriveAccount(BCH, { | ||||
masterHDNode, | masterHDNode, | ||||
path: "m/44'/245'/0'/0/0", | path: "m/44'/245'/0'/0/0", | ||||
}); | }); | ||||
const Path145 = await deriveAccount({ | const Path145 = await deriveAccount(BCH, { | ||||
masterHDNode, | masterHDNode, | ||||
path: "m/44'/145'/0'/0/0", | path: "m/44'/145'/0'/0/0", | ||||
}); | }); | ||||
const Path1899 = await deriveAccount(BCH, { | |||||
masterHDNode, | |||||
path: "m/44'/1899'/0'/0/0", | |||||
}); | |||||
let name = Path145.cashAddress.slice(12, 17); | let name = Path145.cashAddress.slice(12, 17); | ||||
// Only set the name if it does not currently exist | // Only set the name if it does not currently exist | ||||
if (wallet && wallet.name) { | if (wallet && wallet.name) { | ||||
name = wallet.name; | name = wallet.name; | ||||
} | } | ||||
return { | return { | ||||
mnemonic: wallet.mnemonic, | mnemonic: wallet.mnemonic, | ||||
name, | name, | ||||
Path245, | Path245, | ||||
Path145, | Path145, | ||||
Path1899, | |||||
}; | }; | ||||
}; | }; | ||||
const getSavedWallets = async activeWallet => { | const getSavedWallets = async activeWallet => { | ||||
let savedWallets; | let savedWallets; | ||||
try { | try { | ||||
savedWallets = await localforage.getItem('savedWallets'); | savedWallets = await localforage.getItem('savedWallets'); | ||||
if (savedWallets === null) { | if (savedWallets === null) { | ||||
Show All 38 Lines | */ | ||||
try { | try { | ||||
savedWallets = await localforage.getItem('savedWallets'); | savedWallets = await localforage.getItem('savedWallets'); | ||||
} catch (err) { | } catch (err) { | ||||
console.log( | console.log( | ||||
`Error in localforage.getItem("savedWallets") in activateWallet()`, | `Error in localforage.getItem("savedWallets") in activateWallet()`, | ||||
); | ); | ||||
return false; | return false; | ||||
} | } | ||||
/* | |||||
When a legacy user runs cashtabapp.com/, their active wallet will be migrated to Path1899 by | |||||
the getWallet function | |||||
Wallets in savedWallets are migrated when they are activated, in this function | |||||
Two cases to handle | |||||
1 - currentlyActiveWallet has Path1899, but its stored keyvalue pair in savedWallets does not | |||||
> Update savedWallets so that Path1899 is added to currentlyActiveWallet | |||||
2 - walletToActivate does not have Path1899 | |||||
> Update walletToActivate with Path1899 before activation | |||||
*/ | |||||
// Check savedWallets for currentlyActiveWallet | // Check savedWallets for currentlyActiveWallet | ||||
let walletInSavedWallets = false; | let walletInSavedWallets = false; | ||||
let walletUnmigrated = false; | |||||
for (let i = 0; i < savedWallets.length; i += 1) { | for (let i = 0; i < savedWallets.length; i += 1) { | ||||
if (savedWallets[i].name === currentlyActiveWallet.name) { | if (savedWallets[i].name === currentlyActiveWallet.name) { | ||||
walletInSavedWallets = true; | walletInSavedWallets = true; | ||||
// Check savedWallets for unmigrated currentlyActiveWallet | |||||
if (!savedWallets[i].Path1899) { | |||||
// Case 1, described above | |||||
console.log( | |||||
`Case 1: Wallet migration in saved wallets still pending, adding Path1899`, | |||||
); | |||||
savedWallets[i].Path1899 = currentlyActiveWallet.Path1899; | |||||
walletUnmigrated = true; | |||||
} | |||||
} | |||||
} | |||||
// Case 1 | |||||
if (walletUnmigrated) { | |||||
// resave savedWallets | |||||
try { | |||||
// Set walletName as the active wallet | |||||
await localforage.setItem('savedWallets', savedWallets); | |||||
} catch (err) { | |||||
console.log( | |||||
`Error in localforage.setItem("savedWallets") in activateWallet() for unmigrated wallet`, | |||||
); | |||||
} | } | ||||
} | } | ||||
if (!walletInSavedWallets) { | if (!walletInSavedWallets) { | ||||
console.log(`Wallet is not in saved Wallets, adding`); | console.log(`Wallet is not in saved Wallets, adding`); | ||||
savedWallets.push(currentlyActiveWallet); | savedWallets.push(currentlyActiveWallet); | ||||
// resave savedWallets | // resave savedWallets | ||||
try { | try { | ||||
// Set walletName as the active wallet | // Set walletName as the active wallet | ||||
await localforage.setItem('savedWallets', savedWallets); | await localforage.setItem('savedWallets', savedWallets); | ||||
} catch (err) { | } catch (err) { | ||||
console.log( | console.log( | ||||
`Error in localforage.setItem("savedWallets") in activateWallet()`, | `Error in localforage.setItem("savedWallets") in activateWallet()`, | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
// If wallet does not have Path1899, add it | |||||
if (!walletToActivate.Path1899) { | |||||
// Case 2, described above | |||||
console.log(`Case 2: Wallet to activate does not have Path1899`); | |||||
console.log( | |||||
`Wallet to activate from SavedWallets does not have Path1899`, | |||||
); | |||||
console.log(`walletToActivate`, walletToActivate); | |||||
walletToActivate = await migrateLegacyWallet(BCH, walletToActivate); | |||||
} else { | |||||
// Otherwise activate it as normal | |||||
// Now that we have verified the last wallet was saved, we can activate the new wallet | // Now that we have verified the last wallet was saved, we can activate the new wallet | ||||
try { | try { | ||||
await localforage.setItem('wallet', walletToActivate); | await localforage.setItem('wallet', walletToActivate); | ||||
} catch (err) { | } catch (err) { | ||||
console.log( | console.log( | ||||
`Error in localforage.setItem("wallet", walletToActivate) in activateWallet()`, | `Error in localforage.setItem("wallet", walletToActivate) in activateWallet()`, | ||||
); | ); | ||||
return false; | return false; | ||||
} | } | ||||
} | |||||
return walletToActivate; | return walletToActivate; | ||||
}; | }; | ||||
const renameWallet = async (oldName, newName) => { | const renameWallet = async (oldName, newName) => { | ||||
// Load savedWallets | // Load savedWallets | ||||
let savedWallets; | let savedWallets; | ||||
try { | try { | ||||
savedWallets = await localforage.getItem('savedWallets'); | savedWallets = await localforage.getItem('savedWallets'); | ||||
▲ Show 20 Lines • Show All 664 Lines • ▼ Show 20 Lines | return { | ||||
wallet, | wallet, | ||||
fiatPrice, | fiatPrice, | ||||
slpBalancesAndUtxos, | slpBalancesAndUtxos, | ||||
balances, | balances, | ||||
tokens, | tokens, | ||||
txHistory, | txHistory, | ||||
loading, | loading, | ||||
apiError, | apiError, | ||||
getActiveWalletFromLocalForage, | |||||
getWallet, | getWallet, | ||||
validateMnemonic, | validateMnemonic, | ||||
getWalletDetails, | getWalletDetails, | ||||
getSavedWallets, | getSavedWallets, | ||||
migrateLegacyWallet, | |||||
update: async () => | update: async () => | ||||
update({ | update({ | ||||
wallet: await getWallet(), | wallet: await getWallet(), | ||||
setLoading, | setLoading, | ||||
setWalletState, | setWalletState, | ||||
}), | }), | ||||
createWallet: async importMnemonic => { | createWallet: async importMnemonic => { | ||||
setLoading(true); | setLoading(true); | ||||
Show All 23 Lines |