From 77ccfba1eb98621470b3e304ae1d4ddef7ceaf76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren?= Date: Thu, 23 Nov 2023 16:45:29 -0600 Subject: [PATCH] Add account-selection UI to ChooseAddress request It's user-friendlier for onboarding in the CPL dashboard, to select their account, and not their address. Also makes sense for other uses. It's opt-in with the `ui: 2` request option. --- client/PublicRequestTypes.ts | 1 + demos/Demo.ts | 4 +- src/components/icons/LedgerIcon.vue | 3 + src/components/icons/LoginFileIcon.vue | 24 +++ src/i18n/en.po | 19 +- src/lib/RequestParser.ts | 1 + src/lib/RequestTypes.ts | 1 + src/lib/WalletInfoCollector.ts | 12 +- src/views/ChooseAddress.vue | 240 +++++++++++++++++++++---- 9 files changed, 265 insertions(+), 40 deletions(-) create mode 100644 src/components/icons/LedgerIcon.vue create mode 100644 src/components/icons/LoginFileIcon.vue diff --git a/client/PublicRequestTypes.ts b/client/PublicRequestTypes.ts index 0d3e8094..f6dedc11 100644 --- a/client/PublicRequestTypes.ts +++ b/client/PublicRequestTypes.ts @@ -74,6 +74,7 @@ export interface ChooseAddressRequest extends BasicRequest { disableLegacyAccounts?: boolean; disableBip39Accounts?: boolean; disableLedgerAccounts?: boolean; + ui?: number; } export interface ChooseAddressResult extends Address { diff --git a/demos/Demo.ts b/demos/Demo.ts index 8c52c353..85a0dd42 100644 --- a/demos/Demo.ts +++ b/demos/Demo.ts @@ -162,7 +162,7 @@ class Demo { document.querySelector('button#choose-address').addEventListener('click', async () => { try { - const result = await demo.client.chooseAddress({ appName: 'Hub Demos' }, demo._defaultBehavior); + const result = await demo.client.chooseAddress({ appName: 'Hub Demos', ui: 2 }, demo._defaultBehavior); console.log('Result', result); document.querySelector('#result').textContent = `Address was chosen: ${result ? result.address : '-'}`; } catch (e) { @@ -173,7 +173,7 @@ class Demo { document.querySelector('button#choose-address-and-btc').addEventListener('click', async () => { try { - const result = await demo.client.chooseAddress({ appName: 'Hub Demos', returnBtcAddress: true }, demo._defaultBehavior); + const result = await demo.client.chooseAddress({ appName: 'Hub Demos', returnBtcAddress: true, ui: 2 }, demo._defaultBehavior); console.log('Result', result); document.querySelector('#result').textContent = `Address was chosen: ${result ? result.address : '-'}`; } catch (e) { diff --git a/src/components/icons/LedgerIcon.vue b/src/components/icons/LedgerIcon.vue new file mode 100644 index 00000000..5b0d042b --- /dev/null +++ b/src/components/icons/LedgerIcon.vue @@ -0,0 +1,3 @@ + diff --git a/src/components/icons/LoginFileIcon.vue b/src/components/icons/LoginFileIcon.vue new file mode 100644 index 00000000..b099e239 --- /dev/null +++ b/src/components/icons/LoginFileIcon.vue @@ -0,0 +1,24 @@ + diff --git a/src/i18n/en.po b/src/i18n/en.po index ae02a3d5..85eebb60 100644 --- a/src/i18n/en.po +++ b/src/i18n/en.po @@ -2,6 +2,10 @@ msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" +#: src/views/ChooseAddress.vue:14 +msgid "{appName} is asking for an account to use." +msgstr "" + #: src/views/ChooseAddress.vue:7 msgid "{appName} is asking for an address to use." msgstr "" @@ -275,8 +279,13 @@ msgstr "" msgid "Choose a new Address" msgstr "" +#: src/views/ChooseAddress.vue:12 +msgid "Choose an Account" +msgstr "" + #: src/views/CashlinkReceive.vue:64 -#: src/views/ChooseAddress.vue:4 +#: src/views/ChooseAddress.vue:18 +#: src/views/ChooseAddress.vue:5 msgid "Choose an Address" msgstr "" @@ -302,6 +311,10 @@ msgstr "" msgid "Choose Sender" msgstr "" +#: src/views/ChooseAddress.vue:20 +msgid "Choose which Nimiq address to use." +msgstr "" + #: src/views/CashlinkReceive.vue:297 msgid "Claim Cashlink" msgstr "" @@ -673,6 +686,10 @@ msgstr "" msgid "Login File saved!" msgstr "" +#: src/views/ChooseAddress.vue:67 +msgid "Login to another account" +msgstr "" + #: src/views/CashlinkReceive.vue:91 msgid "Login to existing account" msgstr "" diff --git a/src/lib/RequestParser.ts b/src/lib/RequestParser.ts index b7c4ab0f..509cee2f 100644 --- a/src/lib/RequestParser.ts +++ b/src/lib/RequestParser.ts @@ -284,6 +284,7 @@ export class RequestParser { disableLegacyAccounts: !!chooseAddressRequest.disableLegacyAccounts, disableBip39Accounts: !!chooseAddressRequest.disableBip39Accounts, disableLedgerAccounts: !!chooseAddressRequest.disableLedgerAccounts, + ui: chooseAddressRequest.ui, } as ParsedChooseAddressRequest; case RequestType.SIGNUP: case RequestType.LOGIN: diff --git a/src/lib/RequestTypes.ts b/src/lib/RequestTypes.ts index 3415dfd3..74c1610f 100644 --- a/src/lib/RequestTypes.ts +++ b/src/lib/RequestTypes.ts @@ -28,6 +28,7 @@ export interface ParsedChooseAddressRequest extends ParsedBasicRequest { disableLegacyAccounts: boolean; disableBip39Accounts: boolean; disableLedgerAccounts: boolean; + ui?: number; } export interface ParsedSignTransactionRequest extends ParsedBasicRequest { diff --git a/src/lib/WalletInfoCollector.ts b/src/lib/WalletInfoCollector.ts index 249b78e3..9245bfcc 100644 --- a/src/lib/WalletInfoCollector.ts +++ b/src/lib/WalletInfoCollector.ts @@ -119,7 +119,7 @@ export default class WalletInfoCollector { }; } - public static async detectBitcoinAddresses(xpub: string, startIndex = 0): Promise<{ + public static async detectBitcoinAddresses(xpub: string, startIndex = 0, onlyUnusedExternal = Infinity): Promise<{ internal: BtcAddressInfo[], external: BtcAddressInfo[], }> { @@ -147,7 +147,7 @@ export default class WalletInfoCollector { const addresses: [BtcAddressInfo[], BtcAddressInfo[]] = [[], []]; - for (const INDEX of [EXTERNAL_INDEX, INTERNAL_INDEX]) { + addressTypeLoop: for (const INDEX of [EXTERNAL_INDEX, INTERNAL_INDEX]) { const baseKey = extendedKey.derive(INDEX); const basePath = `${BTC_ACCOUNT_KEY_PATH[xPubType][Config.bitcoinNetwork]}/${INDEX}`; @@ -178,6 +178,14 @@ export default class WalletInfoCollector { balance, )); + if (INDEX === EXTERNAL_INDEX && !used) { + // Found an unused external address, reducing remaining counter + onlyUnusedExternal -= 1; + + // When all found, break outer loop and return + if (onlyUnusedExternal <= 0) break addressTypeLoop; + } + if (used) { gap = 0; } else { diff --git a/src/views/ChooseAddress.vue b/src/views/ChooseAddress.vue index c6c2a7ab..d5a21357 100644 --- a/src/views/ChooseAddress.vue +++ b/src/views/ChooseAddress.vue @@ -1,24 +1,74 @@