diff --git a/index.template.html b/index.template.html index bb9f6d053..cc85a1dd2 100644 --- a/index.template.html +++ b/index.template.html @@ -475,7 +475,7 @@

Export - + Refresh address diff --git a/scripts/contacts-book.js b/scripts/contacts-book.js index 47aec4edf..8a6bc51d1 100644 --- a/scripts/contacts-book.js +++ b/scripts/contacts-book.js @@ -343,17 +343,7 @@ export async function guiRenderReceiveModal( let strPubkey = ''; // If HD: use xpub, otherwise we'll fallback to our single address - if (wallet.isHD()) { - // Get our current wallet XPub - const derivationPath = wallet - .getDerivationPath() - .split('/') - .slice(0, 4) - .join('/'); - strPubkey = await wallet.getMasterKey().getxpub(derivationPath); - } else { - strPubkey = await wallet.getMasterKey().getCurrentAddress(); - } + strPubkey = await wallet.getKeyToExport(); // Construct the Contact Share URI const strContactURI = await localContactToURI(cAccount, strPubkey); @@ -374,7 +364,7 @@ export async function guiRenderReceiveModal( document.getElementById('clipboard').value = strPubkey; } else { // Get our current wallet address - const strAddress = await wallet.getMasterKey().getCurrentAddress(); + const strAddress = await wallet.getCurrentAddress(); // Update the QR Label (we'll show the address here for now, user can set Contact "Name" optionally later) doms.domModalQrLabel.innerHTML = @@ -404,7 +394,7 @@ export async function guiRenderReceiveModal( } } else if (cReceiveType === RECEIVE_TYPES.ADDRESS) { // Get our current wallet address - const strAddress = await wallet.getMasterKey().getCurrentAddress(); + const strAddress = await wallet.getCurrentAddress(); createQR('pivx:' + strAddress, doms.domModalQR); doms.domModalQrLabel.innerHTML = strAddress + @@ -415,12 +405,7 @@ export async function guiRenderReceiveModal( document.getElementById('clipboard').value = strAddress; } else { // Get our current wallet XPub - const derivationPath = wallet - .getDerivationPath() - .split('/') - .slice(0, 4) - .join('/'); - const strXPub = await wallet.getMasterKey().getxpub(derivationPath); + const strXPub = await wallet.getXPub(); // Update the QR Label (we'll show the address here for now, user can set Contact "Name" optionally later) doms.domModalQrLabel.innerHTML = @@ -522,15 +507,8 @@ export async function guiAddContact() { // Ensure we're not adding our own XPub if (isXPub(strAddr)) { if (wallet.isHD()) { - const derivationPath = wallet - .getDerivationPath() - .split('/') - .slice(0, 4) - .join('/'); // Compare the XPub against our own - const fOurs = - strAddr === - (await wallet.getMasterKey().getxpub(derivationPath)); + const fOurs = strAddr === (await wallet.getXPub()); if (fOurs) { createAlert( 'warning', @@ -621,15 +599,8 @@ export async function guiAddContactPrompt( // Ensure we're not adding our own XPub if (isXPub(strPubkey)) { if (wallet.isHD()) { - const derivationPath = wallet - .getDerivationPath() - .split('/') - .slice(0, 4) - .join('/'); // Compare the XPub against our own - const fOurs = - strPubkey === - (await wallet.getMasterKey().getxpub(derivationPath)); + const fOurs = strPubkey === (await wallet.getXPub()); if (fOurs) { createAlert( 'warning', @@ -982,19 +953,7 @@ export async function localContactToURI(account, pubkey) { let strPubkey = pubkey || ''; // If HD: use xpub, otherwise we'll fallback to our single address - if (!strPubkey) { - if (wallet.isHD()) { - // Get our current wallet XPub - const derivationPath = wallet - .getDerivationPath() - .split('/') - .slice(0, 4) - .join('/'); - strPubkey = await wallet.getMasterKey().getxpub(derivationPath); - } else { - strPubkey = await wallet.getMasterKey().getCurrentAddress(); - } - } + if (!strPubkey) strPubkey = await wallet.getKeyToExport(); // Construct the Contact URI Root const strURL = window.location.origin + window.location.pathname; diff --git a/scripts/global.js b/scripts/global.js index 881d4a9b2..0098f5c00 100644 --- a/scripts/global.js +++ b/scripts/global.js @@ -7,6 +7,7 @@ import { hasEncryptedWallet, importWallet, decryptWallet, + getNewAddress, } from './wallet.js'; import { LegacyMasterKey } from './masterkey.js'; import { getNetwork, HistoricalTxType } from './network.js'; @@ -800,16 +801,10 @@ export async function openSendQRScanner() { */ export async function openExplorer(strAddress = '') { if (wallet.isLoaded() && wallet.isHD() && !strAddress) { - const derivationPath = wallet - .getDerivationPath() - .split('/') - .slice(0, 4) - .join('/'); - const xpub = await wallet.getMasterKey().getxpub(derivationPath); + const xpub = await wallet.getxpub(); window.open(cExplorer.url + '/xpub/' + xpub, '_blank'); } else { - const address = - strAddress || (await wallet.getMasterKey().getAddress()); + const address = strAddress || (await wallet.getAddress()); window.open(cExplorer.url + '/address/' + address, '_blank'); } } @@ -1270,7 +1265,7 @@ export async function guiImportWallet() { if (wallet.isLoaded()) { // Prepare a new Account to add const cAccount = new Account({ - publicKey: await wallet.getMasterKey().keyToExport, + publicKey: await wallet.getKeyToExport(), encWif: strPrivKey, }); @@ -1521,7 +1516,7 @@ export async function sweepAddress(arrUTXOs, sweepingMasterKey, nFixedFee = 0) { const nFee = nFixedFee || getNetwork().getFee(cTx.serialize().length); // Use a new address from our wallet to sweep the UTXOs in to - const strAddress = (await wallet.getNewAddress(true, false))[0]; + const strAddress = (await getNewAddress(true, false))[0]; // Sweep the full funds amount, minus the fee, leaving no change from any sweeped UTXOs cTx.addoutput(strAddress, (nTotal - nFee) / COIN); @@ -1601,7 +1596,7 @@ export async function wipePrivateData() { html, }) ) { - wallet.getMasterKey().wipePrivateData(); + wallet.wipePrivateData(); doms.domWipeWallet.hidden = true; if (isEncrypted) { doms.domRestoreWallet.hidden = false; @@ -2356,7 +2351,7 @@ export async function updateMasternodeTab() { for (const [key] of mapCollateralAddresses) { const option = document.createElement('option'); option.value = key; - option.innerText = await wallet.getMasterKey().getAddress(key); + option.innerText = await wallet.getAddress(key); doms.domMnTxId.appendChild(option); } } diff --git a/scripts/index.js b/scripts/index.js index 1ac79b206..89d890056 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -44,7 +44,12 @@ export { switchSettings, govVote, } from './global.js'; -export { wallet, generateWallet, importWallet } from './wallet.js'; +export { + wallet, + getNewAddress, + generateWallet, + importWallet, +} from './wallet.js'; export { toggleTestnet, toggleDebug, diff --git a/scripts/masterkey.js b/scripts/masterkey.js index c6bda516a..b87b2a89a 100644 --- a/scripts/masterkey.js +++ b/scripts/masterkey.js @@ -1,23 +1,18 @@ import HDKey from 'hdkey'; -import { getNetwork } from './network.js'; import { bytesToHex } from './utils.js'; import { getHardwareWalletKeys } from './ledger.js'; -import { cChainParams, MAX_ACCOUNT_GAP } from './chain_params.js'; +import { cChainParams } from './chain_params.js'; import { deriveAddress, generateOrEncodePrivkey } from './encoding.js'; /** - * Abstract class masterkey + * Abstract class masterkey, it handles address generation + * this class must not know anything about the wallet it self + * so for example don't take for granted nAccount when generating. + * Ideally the only class having access to those functions is the wallet itself. * @abstract */ export class MasterKey { - #addressIndex = 0; - /** - * Map our own address -> Path - * @type {Map} - */ - #ownAddresses = new Map(); - constructor() { if (this.constructor === MasterKey) { throw new Error('initializing virtual class'); @@ -66,7 +61,7 @@ export class MasterKey { * @return {void} * @abstract */ - wipePrivateData() { + wipePrivateData(_nAccount) { throw new Error('Not implemented'); } @@ -82,7 +77,7 @@ export class MasterKey { * @return {Promise} public key to export. Only suitable for monitoring balance. * @abstract */ - get keyToExport() { + getKeyToExport(_nAccount) { throw new Error('Not implemented'); } @@ -108,7 +103,7 @@ export class MasterKey { } // Construct a full BIP44 pubkey derivation path from it's parts - getDerivationPath(nAccount = 0, nReceiving = 0, nIndex = 0) { + getDerivationPath(nAccount, nReceiving, nIndex) { // Coin-Type is different on Ledger, as such, for local wallets; we modify it if we're using a Ledger to derive a key const strCoinType = this.isHardwareWallet ? cChainParams.current.BIP44_TYPE_LEDGER @@ -118,63 +113,6 @@ export class MasterKey { } return `m/44'/${strCoinType}'/${nAccount}'/${nReceiving}/${nIndex}`; } - - /** - * @param {string} address - address to check - * @return {Promise} BIP32 path or null if it's not your address - */ - async isOwnAddress(address) { - if (this.#ownAddresses.has(address)) { - return this.#ownAddresses.get(address); - } - const last = getNetwork().lastWallet; - this.#addressIndex = - this.#addressIndex > last ? this.#addressIndex : last; - if (this.isHD) { - for (let i = 0; i < this.#addressIndex; i++) { - const path = this.getDerivationPath(0, 0, i); - const testAddress = await this.getAddress(path); - if (address === testAddress) { - this.#ownAddresses.set(address, path); - return path; - } - } - } else { - const value = address === (await this.keyToExport) ? ':)' : null; - this.#ownAddresses.set(address, value); - return value; - } - - this.#ownAddresses.set(address, null); - return null; - } - - /** - * @return Promise<[string, string]> Address and its BIP32 derivation path - */ - async getNewAddress() { - const last = getNetwork().lastWallet; - this.#addressIndex = - (this.#addressIndex > last ? this.#addressIndex : last) + 1; - if (this.#addressIndex - last > MAX_ACCOUNT_GAP) { - // If the user creates more than ${MAX_ACCOUNT_GAP} empty wallets we will not be able to sync them! - this.#addressIndex = last; - } - const path = this.getDerivationPath(0, 0, this.#addressIndex); - const address = await this.getAddress(path); - return [address, path]; - } - - /** - * Derive the current address (by internal index) - * @return {Promise} Address - * @abstract - */ - async getCurrentAddress() { - return await this.getAddress( - this.getDerivationPath(0, 0, this.#addressIndex) - ); - } } export class HdMasterKey extends MasterKey { @@ -229,18 +167,20 @@ export class HdMasterKey extends MasterKey { return deriveAddress({ publicKey: bytesToHex(child.publicKey) }); } - wipePrivateData() { + wipePrivateData(nAccount) { if (this._isViewOnly) return; - this._hdKey = HDKey.fromExtendedKey(this.keyToExport); + this._hdKey = HDKey.fromExtendedKey(this.getKeyToExport(nAccount)); this._isViewOnly = true; } - - get keyToExport() { + getKeyToExport(nAccount) { if (this._isViewOnly) return this._hdKey.publicExtendedKey; // We need the xpub to point at the account level return this._hdKey.derive( - this.getDerivationPath(0, 0, 0).split('/').slice(0, 4).join('/') + this.getDerivationPath(nAccount, 0, 0) + .split('/') + .slice(0, 4) + .join('/') ).publicExtendedKey; } } @@ -280,13 +220,13 @@ export class HardwareWalletMasterKey extends MasterKey { } // Hardware Wallets don't have exposed private data - wipePrivateData() {} + wipePrivateData(_nAccount) {} get isViewOnly() { return false; } - get keyToExport() { - const derivationPath = this.getDerivationPath() + getKeyToExport(nAccount) { + const derivationPath = this.getDerivationPath(nAccount, 0, 0) .split('/') .slice(0, 4) .join('/'); @@ -308,7 +248,7 @@ export class LegacyMasterKey extends MasterKey { return this._address; } - get keyToExport() { + getKeyToExport(_nAccount) { return this._address; } @@ -331,7 +271,7 @@ export class LegacyMasterKey extends MasterKey { ); } - wipePrivateData() { + wipePrivateData(_nAccount) { this._pkBytes = null; this._isViewOnly = true; } diff --git a/scripts/network.js b/scripts/network.js index 59e28553f..80df65fe9 100644 --- a/scripts/network.js +++ b/scripts/network.js @@ -90,15 +90,19 @@ export class HistoricalTx { /** * Virtual class rapresenting any network backend + * */ export class Network { - constructor(masterKey) { + wallet; + /** + * @param {import('./wallet.js').Wallet} wallet + */ + constructor(wallet) { if (this.constructor === Network) { throw new Error('Initializing virtual class'); } this._enabled = true; - - this.masterKey = masterKey; + this.wallet = wallet; this.lastWallet = 0; this.isHistorySynced = false; @@ -155,8 +159,8 @@ export class Network { throw new Error('submitAnalytics must be implemented'); } - setMasterKey(masterKey) { - this.masterKey = masterKey; + setWallet(wallet) { + this.wallet = wallet; } async getTxInfo(_txHash) { @@ -171,8 +175,8 @@ export class ExplorerNetwork extends Network { /** * @param {string} strUrl - Url pointing to the blockbook explorer */ - constructor(strUrl, masterKey) { - super(masterKey); + constructor(strUrl, wallet) { + super(wallet); /** * @type{string} * @public @@ -246,25 +250,12 @@ export class ExplorerNetwork extends Network { async getUTXOs(strAddress = '') { // Don't fetch UTXOs if we're already scanning for them! if (!strAddress) { - if (!this.masterKey) return; + if (!this.wallet || !this.wallet.isLoaded()) return; if (this.isSyncing) return; this.isSyncing = true; } try { - let publicKey; - // Derive our XPub, or fetch a single pubkey - if (this.masterKey.isHD && !strAddress) { - const derivationPath = this.masterKey - .getDerivationPath() - .split('/') - .slice(0, 4) - .join('/'); - publicKey = await this.masterKey.getxpub(derivationPath); - } else { - // Use the param address if specified, or the Master Key by default - publicKey = strAddress || (await this.masterKey.getAddress()); - } - + let publicKey = strAddress || (await this.wallet.getKeyToExport()); // Fetch UTXOs for the key const arrUTXOs = await ( await retryWrapper(fetchBlockbook, `/api/v2/utxo/${publicKey}`) @@ -300,7 +291,7 @@ export class ExplorerNetwork extends Network { if (cUTXO.path) { path = cUTXO.path.split('/'); path[2] = - (this.masterKey.isHardwareWallet + (this.wallet.isHardwareWallet() ? cChainParams.current.BIP44_TYPE_LEDGER : cChainParams.current.BIP44_TYPE) + "'"; this.lastWallet = Math.max(parseInt(path[5]), this.lastWallet); @@ -372,7 +363,8 @@ export class ExplorerNetwork extends Network { } try { - if (!this.enabled || !this.masterKey) return this.arrTxHistory; + if (!this.enabled || !this.wallet || !this.wallet.isLoaded()) + return this.arrTxHistory; this.historySyncing = true; const nHeight = this.arrTxHistory.length ? this.arrTxHistory[this.arrTxHistory.length - 1].blockHeight @@ -380,16 +372,10 @@ export class ExplorerNetwork extends Network { const mapPaths = new Map(); // Form the API call using our wallet information - const fHD = this.masterKey.isHD; - const strDerivPath = this.masterKey - .getDerivationPath() - .split('/') - .slice(0, 4) - .join('/'); - const strKey = fHD - ? await this.masterKey.getxpub(strDerivPath) - : await this.masterKey.getAddress(); - const strRoot = `/api/v2/${fHD ? 'xpub/' : 'address/'}${strKey}`; + const strKey = await this.wallet.getKeyToExport(); + const strRoot = `/api/v2/${ + this.wallet.isHD() ? 'xpub/' : 'address/' + }${strKey}`; const strCoreParams = `?details=txs&tokens=derived&pageSize=200`; const strAPI = strRoot + strCoreParams; @@ -414,7 +400,7 @@ export class ExplorerNetwork extends Network { ) ).json() : {}; - if (fHD && (cData.tokens || cRecentTXs.tokens)) { + if (this.wallet.isHD() && (cData.tokens || cRecentTXs.tokens)) { // Map all address <--> derivation paths // - From historical transactions if (cData.tokens) { @@ -591,17 +577,17 @@ export class ExplorerNetwork extends Network { .filter((tx) => tx.amount != 0); } - async setMasterKey(masterKey) { + async setWallet(wallet) { // If the public Master Key (xpub, address...) is different, then wipe TX history if ( - (await this.masterKey?.keyToExport) !== - (await masterKey?.keyToExport) + (await this.wallet?.getKeyToExport()) !== + (await wallet?.getKeyToExport()) ) { this.arrTxHistory = []; } // Set the key - this.masterKey = masterKey; + this.wallet = wallet; } async getTxInfo(txHash) { diff --git a/scripts/settings.js b/scripts/settings.js index 04a39c5be..0f4016280 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -273,7 +273,7 @@ export async function setExplorer(explorer, fSilent = false) { cExplorer = explorer; // Enable networking + notify if allowed - const network = new ExplorerNetwork(cExplorer.url, wallet.getMasterKey()); + const network = new ExplorerNetwork(cExplorer.url, wallet); setNetwork(network); activityDashboard.reset(); @@ -546,7 +546,7 @@ export async function toggleTestnet() { mempool.UTXOs = []; getBalance(true); getStakingBalance(true); - await updateEncryptionGUI(!!wallet.getMasterKey()); + await updateEncryptionGUI(wallet.isLoaded()); await fillExplorerSelect(); await fillNodeSelect(); await updateGovernanceTab(); diff --git a/scripts/wallet.js b/scripts/wallet.js index 5163fa4c0..c13ac4a9c 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -2,8 +2,8 @@ import { parseWIF } from './encoding.js'; import { generateMnemonic, mnemonicToSeed, validateMnemonic } from 'bip39'; import { doms, beforeUnloadListener } from './global.js'; import { getNetwork } from './network.js'; +import { MAX_ACCOUNT_GAP } from './chain_params.js'; import { - MasterKey, LegacyMasterKey, HdMasterKey, HardwareWalletMasterKey, @@ -31,21 +31,50 @@ import { debug, fAdvancedMode } from './settings.js'; import { strHardwareName, getHardwareWalletKeys } from './ledger.js'; export let fWalletLoaded = false; -class Wallet { +/** + * Class Wallet, at the moment it is just a "realization" of Masterkey with a given nAccount + * it also remembers which addresses we generated. + * in future PRs this class will manage balance, UTXOs, masternode etc... + */ +export class Wallet { /** - * @type {MasterKey} + * @type {import('./masterkey.js').MasterKey} */ #masterKey; - constructor() {} + /** + * @type {number} + */ + #nAccount; + /** + * @type {number} + */ + #addressIndex = 0; + /** + * Map our own address -> Path + * @type {Map} + */ + #ownAddresses = new Map(); + constructor(nAccount) { + this.#nAccount = nAccount; + } getMasterKey() { return this.#masterKey; } + get nAccount() { + return this.#nAccount; + } + + wipePrivateData() { + this.#masterKey.wipePrivateData(this.#nAccount); + } + isViewOnly() { if (!this.#masterKey) return false; return this.#masterKey.isViewOnly; } + isHD() { if (!this.#masterKey) return false; return this.#masterKey.isHD; @@ -82,13 +111,47 @@ class Wallet { async setMasterKey(mk) { this.#masterKey = mk; // Update the network master key - await getNetwork().setMasterKey(this.#masterKey); + await getNetwork().setWallet(this); } - getDefaultAddress() { - return this.#masterKey.getAddress(this.#masterKey.getDerivationPath()); + /** + * Derive the current address (by internal index) + * @return {Promise} Address + * + */ + async getCurrentAddress() { + return await this.getAddress(0, this.#addressIndex); + } + + /** + * Derive a generic address (given nReceiving and nIndex) + * @return {Promise} Address + */ + async getAddress(nReceiving = 0, nIndex = 0) { + const path = this.getDerivationPath(nReceiving, nIndex); + return await this.#masterKey.getAddress(path); + } + + /** + * Derive xpub (given nReceiving and nIndex) + * @return {Promise} Address + */ + async getXPub(nReceiving = 0, nIndex = 0) { + if (this.isHD()) { + // Get our current wallet XPub + const derivationPath = this.getDerivationPath(nReceiving, nIndex) + .split('/') + .slice(0, 4) + .join('/'); + return await this.#masterKey.getxpub(derivationPath); + } + throw new Error('Legacy wallet does not have a xpub'); } + /** + * Derive xpub (given nReceiving and nIndex) + * @return {bool} Return true if a masterKey has been loaded in the wallet + */ isLoaded() { return !!this.#masterKey; } @@ -103,7 +166,7 @@ class Wallet { // Prepare to Add/Update an account in the DB const cAccount = new Account({ - publicKey: await this.#masterKey.keyToExport, + publicKey: await this.getKeyToExport(), encWif: strEncWIF, }); @@ -122,25 +185,20 @@ class Wallet { capture: true, }); } - async getNewAddress({ updateGUI = false, verify = false } = {}) { - const [address, path] = await this.#masterKey.getNewAddress(); - if (verify && this.#masterKey.isHardwareWallet) { - // Generate address to present to the user without asking to verify - const confAddress = await confirmPopup({ - title: ALERTS.CONFIRM_POPUP_VERIFY_ADDR, - html: createAddressConfirmation(address), - resolvePromise: this.#masterKey.getAddress(path, { verify }), - }); - if (address !== confAddress) { - throw new Error('User did not verify address'); - } - } - // If we're generating a new address manually, then render the new address in our Receive Modal - if (updateGUI) { - guiRenderCurrentReceiveModal(); + /** + * @return Promise<[string, string]> Address and its BIP32 derivation path + */ + async getNewAddress() { + const last = getNetwork().lastWallet; + this.#addressIndex = + (this.#addressIndex > last ? this.#addressIndex : last) + 1; + if (this.#addressIndex - last > MAX_ACCOUNT_GAP) { + // If the user creates more than ${MAX_ACCOUNT_GAP} empty wallets we will not be able to sync them! + this.#addressIndex = last; } - + const path = this.getDerivationPath(0, this.#addressIndex); + const address = await this.getAddress(0, this.#addressIndex); return [address, path]; } // If the privateKey is null then the user connected a hardware wallet @@ -148,18 +206,58 @@ class Wallet { if (!this.#masterKey) return false; return this.#masterKey.isHardwareWallet == true; } + + /** + * @param {string} address - address to check + * @return {Promise} BIP32 path or null if it's not your address + */ async isOwnAddress(address) { - return await this.#masterKey.isOwnAddress(address); + if (this.#ownAddresses.has(address)) { + return this.#ownAddresses.get(address); + } + const last = getNetwork().lastWallet; + this.#addressIndex = + this.#addressIndex > last ? this.#addressIndex : last; + if (this.isHD()) { + for (let i = 0; i < this.#addressIndex; i++) { + const path = this.getDerivationPath(0, i); + const testAddress = await this.#masterKey.getAddress(path); + if (address === testAddress) { + this.#ownAddresses.set(address, path); + return path; + } + } + } else { + const value = + address === (await this.getKeyToExport()) ? ':)' : null; + this.#ownAddresses.set(address, value); + return value; + } + + this.#ownAddresses.set(address, null); + return null; + } + + /** + * @return {String} BIP32 path or null if it's not your address + */ + getDerivationPath(nReceiving = 0, nIndex = 0) { + return this.#masterKey.getDerivationPath( + this.#nAccount, + nReceiving, + nIndex + ); } - getDerivationPath(nAccount = 0, nReceiving = 0, nIndex = 0) { - return this.#masterKey.getDerivationPath(nAccount, nReceiving, nIndex); + + async getKeyToExport() { + return await this.#masterKey?.getKeyToExport(this.#nAccount); } } /** * @type{Wallet} */ -export const wallet = new Wallet(); +export const wallet = new Wallet(0); // For now we are using only the 0-th account, (TODO: update once account system is done) /** * Import a wallet (with it's private, public or encrypted data) @@ -316,7 +414,7 @@ export async function importWallet({ doms.domDashboard.click(); // Update identicon - doms.domIdenticon.dataset.jdenticonValue = wallet.getDefaultAddress(); + doms.domIdenticon.dataset.jdenticonValue = await wallet.getAddress(); jdenticon.update('#identicon'); // Hide the encryption prompt if the user is using @@ -378,10 +476,10 @@ export async function generateWallet(noUI = false) { setDisplayForAllWalletOptions('none'); // Update identicon - doms.domIdenticon.dataset.jdenticonValue = wallet.getDefaultAddress(); + doms.domIdenticon.dataset.jdenticonValue = await wallet.getAddress(); jdenticon.update('#identicon'); - wallet.getNewAddress({ updateGUI: true }); + await getNewAddress({ updateGUI: true }); // Refresh the balance UI (why? because it'll also display any 'get some funds!' alerts) getBalance(true); @@ -516,6 +614,31 @@ export async function hasEncryptedWallet() { return !!account?.encWif; } +export async function getNewAddress({ + updateGUI = false, + verify = false, +} = {}) { + const [address, path] = await wallet.getNewAddress(); + if (verify && wallet.isHardwareWallet()) { + // Generate address to present to the user without asking to verify + const confAddress = await confirmPopup({ + title: ALERTS.CONFIRM_POPUP_VERIFY_ADDR, + html: createAddressConfirmation(address), + resolvePromise: wallet.getMasterKey().getAddress(path, { verify }), + }); + if (address !== confAddress) { + throw new Error('User did not verify address'); + } + } + + // If we're generating a new address manually, then render the new address in our Receive Modal + if (updateGUI) { + guiRenderCurrentReceiveModal(); + } + + return [address, path]; +} + function createAddressConfirmation(address) { return `${translation.popupHardwareAddrCheck} ${strHardwareName}.
${address}
`;