From cf184cd26e662794a6f0bcc28cf3909e1cd49b0e Mon Sep 17 00:00:00 2001 From: meriadec Date: Wed, 4 Jul 2018 14:53:09 +0200 Subject: [PATCH] Libcore handling of splitted currencies --- .../AddAccounts/steps/03-step-import.js | 15 +++- src/config/constants.js | 2 +- src/helpers/accountName.js | 3 +- src/helpers/bip32.js | 17 ++++ src/helpers/libcore.js | 86 ++++++++++++++++--- static/i18n/en/app.yml | 2 + 6 files changed, 107 insertions(+), 18 deletions(-) diff --git a/src/components/modals/AddAccounts/steps/03-step-import.js b/src/components/modals/AddAccounts/steps/03-step-import.js index b74046fa..1142c7e4 100644 --- a/src/components/modals/AddAccounts/steps/03-step-import.js +++ b/src/components/modals/AddAccounts/steps/03-step-import.js @@ -70,10 +70,21 @@ class StepImport extends PureComponent { const { t } = this.props let { name } = account + const isLegacy = name.indexOf('legacy') !== -1 + const isUnsplit = name.indexOf('unsplit') !== -1 + if (name === 'New Account') { name = t('app:addAccounts.newAccount') - } else if (name.indexOf('legacy') !== -1) { - name = t('app:addAccounts.legacyAccount', { accountName: name.replace(' (legacy)', '') }) + } else if (isLegacy) { + if (isUnsplit) { + name = t('app:addAccounts.legacyUnsplitAccount', { + accountName: name.replace(' (legacy)', '').replace(' (unsplit)', ''), + }) + } else { + name = t('app:addAccounts.legacyAccount', { accountName: name.replace(' (legacy)', '') }) + } + } else if (isUnsplit) { + name = t('app:addAccounts.unsplitAccount', { accountName: name.replace(' (unsplit)', '') }) } return { diff --git a/src/config/constants.js b/src/config/constants.js index 7396c0f5..2d42e5a3 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -88,7 +88,7 @@ export const EXPERIMENTAL_MARKET_INDICATOR_SETTINGS = boolFromEnv( // Other constants -export const MAX_ACCOUNT_NAME_SIZE = 30 +export const MAX_ACCOUNT_NAME_SIZE = 50 export const MODAL_ADD_ACCOUNTS = 'MODAL_ADD_ACCOUNTS' export const MODAL_OPERATION_DETAILS = 'MODAL_OPERATION_DETAILS' diff --git a/src/helpers/accountName.js b/src/helpers/accountName.js index dd319a5e..ce9d2d36 100644 --- a/src/helpers/accountName.js +++ b/src/helpers/accountName.js @@ -6,7 +6,8 @@ export const getAccountPlaceholderName = ( c: CryptoCurrency, index: number, isLegacy: boolean = false, -) => `${c.name} ${index + 1}${isLegacy ? ' (legacy)' : ''}` + isUnsplit: boolean = false, +) => `${c.name} ${index + 1}${isLegacy ? ' (legacy)' : ''}${isUnsplit ? ' (unsplit)' : ''}` export const getNewAccountPlaceholderName = getAccountPlaceholderName // same naming // export const getNewAccountPlaceholderName = (_c: CryptoCurrency, _index: number) => `New Account` diff --git a/src/helpers/bip32.js b/src/helpers/bip32.js index 12438290..665f4422 100644 --- a/src/helpers/bip32.js +++ b/src/helpers/bip32.js @@ -1,7 +1,24 @@ // @flow + import type { Account, AccountRaw } from '@ledgerhq/live-common/lib/types' +type SplitConfig = { + coinType: number, +} + export const isSegwitPath = (path: string): boolean => path.startsWith("49'") export const isSegwitAccount = (account: Account | AccountRaw): boolean => isSegwitPath(account.freshAddressPath) + +export const isUnsplitPath = (path: string, splitConfig: SplitConfig) => { + try { + const coinType = parseInt(path.split('/')[1], 10) + return coinType === splitConfig.coinType + } catch (e) { + return false + } +} + +export const isUnsplitAccount = (account: Account | AccountRaw, splitConfig: ?SplitConfig) => + !!splitConfig && isUnsplitPath(account.freshAddressPath, splitConfig) diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index 7c5c1913..b4148c49 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -9,13 +9,23 @@ import { SHOW_LEGACY_NEW_ACCOUNT } from 'config/constants' import type { AccountRaw, OperationRaw, OperationType } from '@ledgerhq/live-common/lib/types' import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgercore_doc' -import { isSegwitAccount } from 'helpers/bip32' +import { isSegwitAccount, isUnsplitAccount } from 'helpers/bip32' import * as accountIdHelper from 'helpers/accountId' import { createCustomErrorClass, deserializeError } from './errors' import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName' const NoAddressesFound = createCustomErrorClass('NoAddressesFound') +// TODO: put that info inside currency itself +const SPLITTED_CURRENCIES = { + bitcoin_cash: { + coinType: 0, + }, + bitcoin_gold: { + coinType: 0, + }, +} + export function isValidAddress(core: *, currency: *, address: string): boolean { const addr = new core.NJSAddress(address, currency) return addr.isValid(address, currency) @@ -48,6 +58,7 @@ export function scanAccountsOnDevice(props: Props): Promise { ...commonParams, showNewAccount: !!SHOW_LEGACY_NEW_ACCOUNT || !currency.supportsSegwit, isSegwit: false, + isUnsplit: false, }) allAccounts = allAccounts.concat(nonSegwitAccounts) @@ -56,10 +67,32 @@ export function scanAccountsOnDevice(props: Props): Promise { ...commonParams, showNewAccount: true, isSegwit: true, + isUnsplit: false, }) allAccounts = allAccounts.concat(segwitAccounts) } + // TODO: put that info inside currency itself + if (currencyId in SPLITTED_CURRENCIES) { + const splittedAccounts = await scanAccountsOnDeviceBySegwit({ + ...commonParams, + isSegwit: false, + showNewAccount: false, + isUnsplit: true, + }) + allAccounts = allAccounts.concat(splittedAccounts) + + if (currency.supportsSegwit) { + const segwitAccounts = await scanAccountsOnDeviceBySegwit({ + ...commonParams, + showNewAccount: false, + isUnsplit: true, + isSegwit: true, + }) + allAccounts = allAccounts.concat(segwitAccounts) + } + } + return allAccounts }) } @@ -68,12 +101,15 @@ function encodeWalletName({ publicKey, currencyId, isSegwit, + isUnsplit, }: { publicKey: string, currencyId: string, isSegwit: boolean, + isUnsplit: boolean, }) { - return `${publicKey}__${currencyId}${isSegwit ? '_segwit' : ''}` + const splitConfig = isUnsplit ? SPLITTED_CURRENCIES[currencyId] || null : null + return `${publicKey}__${currencyId}${isSegwit ? '_segwit' : ''}${splitConfig ? '_unsplit' : ''}` } async function scanAccountsOnDeviceBySegwit({ @@ -82,6 +118,7 @@ async function scanAccountsOnDeviceBySegwit({ currencyId, onAccountScanned, isSegwit, + isUnsplit, showNewAccount, }: { core: *, @@ -90,17 +127,20 @@ async function scanAccountsOnDeviceBySegwit({ onAccountScanned: AccountRaw => void, isSegwit: boolean, // FIXME all segwit to change to 'purpose' showNewAccount: boolean, + isUnsplit: boolean, }): Promise { - const { coinType } = getCryptoCurrencyById(currencyId) - const { publicKey } = await hwApp.getWalletPublicKey( - `${isSegwit ? '49' : '44'}'/${coinType}'`, - false, - isSegwit, - ) - const walletName = encodeWalletName({ publicKey, currencyId, isSegwit }) + const customOpts = + isUnsplit && SPLITTED_CURRENCIES[currencyId] ? SPLITTED_CURRENCIES[currencyId] : null + const { coinType } = customOpts ? customOpts.coinType : getCryptoCurrencyById(currencyId) + + const path = `${isSegwit ? '49' : '44'}'/${coinType}'` + + const { publicKey } = await hwApp.getWalletPublicKey(path, false, isSegwit) + + const walletName = encodeWalletName({ publicKey, currencyId, isSegwit, isUnsplit }) // retrieve or create the wallet - const wallet = await getOrCreateWallet(core, walletName, currencyId, isSegwit) + const wallet = await getOrCreateWallet(core, walletName, currencyId, isSegwit, isUnsplit) const accountsCount = await wallet.getAccountCount() // recursively scan all accounts on device on the given app @@ -115,6 +155,7 @@ async function scanAccountsOnDeviceBySegwit({ accounts: [], onAccountScanned, isSegwit, + isUnsplit, showNewAccount, }) @@ -188,6 +229,7 @@ async function scanNextAccount(props: { accounts: AccountRaw[], onAccountScanned: AccountRaw => void, isSegwit: boolean, + isUnsplit: boolean, showNewAccount: boolean, }): Promise { const { @@ -200,6 +242,7 @@ async function scanNextAccount(props: { accounts, onAccountScanned, isSegwit, + isUnsplit, showNewAccount, } = props @@ -222,6 +265,7 @@ async function scanNextAccount(props: { const account = await buildAccountRaw({ njsAccount, isSegwit, + isUnsplit, accountIndex, wallet, currencyId, @@ -259,6 +303,7 @@ async function getOrCreateWallet( WALLET_IDENTIFIER: string, currencyId: string, isSegwit: boolean, + isUnsplit: boolean, ): NJSWallet { const pool = core.getPoolInstance() try { @@ -266,12 +311,18 @@ async function getOrCreateWallet( return wallet } catch (err) { const currency = await pool.getCurrency(currencyId) + const splitConfig = isUnsplit ? SPLITTED_CURRENCIES[currencyId] || null : null + const coinType = splitConfig ? splitConfig.coinType : '' const walletConfig = isSegwit ? { KEYCHAIN_ENGINE: 'BIP49_P2SH', - KEYCHAIN_DERIVATION_SCHEME: "49'/'/'//
", + KEYCHAIN_DERIVATION_SCHEME: `49'/${coinType}'/'//
`, } - : undefined + : splitConfig + ? { + KEYCHAIN_DERIVATION_SCHEME: `44'/${coinType}'/'//
`, + } + : undefined const njsWalletConfig = createWalletConfig(core, walletConfig) const wallet = await core .getPoolInstance() @@ -283,6 +334,7 @@ async function getOrCreateWallet( async function buildAccountRaw({ njsAccount, isSegwit, + isUnsplit, wallet, currencyId, core, @@ -291,6 +343,7 @@ async function buildAccountRaw({ }: { njsAccount: NJSAccount, isSegwit: boolean, + isUnsplit: boolean, wallet: NJSWallet, currencyId: string, accountIndex: number, @@ -336,6 +389,7 @@ async function buildAccountRaw({ currency, accountIndex, (currency.supportsSegwit && !isSegwit) || false, + isUnsplit, ) const rawAccount: AccountRaw = { @@ -411,6 +465,8 @@ function buildOperationRaw({ export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: AccountRaw }) { const decodedAccountId = accountIdHelper.decode(rawAccount.id) + const isSegwit = isSegwitAccount(rawAccount) + const isUnsplit = isUnsplitAccount(rawAccount, SPLITTED_CURRENCIES[rawAccount.currencyId]) let njsWallet try { njsWallet = await core.getPoolInstance().getWallet(decodedAccountId.walletName) @@ -420,7 +476,8 @@ export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: A core, decodedAccountId.walletName, rawAccount.currencyId, - isSegwitAccount(rawAccount), + isSegwit, + isUnsplit, ) } @@ -443,7 +500,8 @@ export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: A const syncedRawAccount = await buildAccountRaw({ njsAccount, - isSegwit: isSegwitAccount(rawAccount), + isSegwit, + isUnsplit, accountIndex: rawAccount.index, wallet: njsWallet, currencyId: rawAccount.currencyId, diff --git a/static/i18n/en/app.yml b/static/i18n/en/app.yml index 2edb6b3c..89e0c339 100644 --- a/static/i18n/en/app.yml +++ b/static/i18n/en/app.yml @@ -168,6 +168,8 @@ addAccounts: editName: Edit name newAccount: New account legacyAccount: '{{accountName}} (legacy)' + legacyUnsplitAccount: '{{accountName}} (legacy) (unsplit)' + unsplitAccount: '{{accountName}} (unsplit)' noAccountToImport: No existing {{currencyName}} accounts to add success: Account successfully added to your portfolio success_plural: Accounts successfully added to your portfolio