UNPKG

@trezor/connect

Version:

High-level javascript interface for Trezor hardware wallet.

188 lines 9.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("@trezor/utils"); const BlockchainLink_1 = require("../backend/BlockchainLink"); const constants_1 = require("../constants"); const AbstractMethod_1 = require("../core/AbstractMethod"); const coinInfo_1 = require("../data/coinInfo"); const events_1 = require("../events"); const paramsValidator_1 = require("./common/paramsValidator"); const discoverAccounts_1 = require("../types/api/discoverAccounts"); const accountUtils_1 = require("../utils/accountUtils"); const pathUtils_1 = require("../utils/pathUtils"); const ACCOUNT_LIMIT = 10; const TXS_PER_PAGE = 25; const DETAILS = 'txs'; const isCardano = (account) => account.symbol === 'ada' || account.symbol === 'tada'; const isCardanoRequest = (request) => isCardano(request.account); const isEvmLedger = (account, coinInfo) => coinInfo.type === 'ethereum' && account.type === 'ledger'; const getAccountTypeKey = ({ symbol, type }) => `${symbol}-${type}`; class DiscoverAccounts extends AbstractMethod_1.AbstractMethod { disposed = false; init() { this.requiredPermissions = ['read']; this.useDevice = true; this.useDeviceState = true; this.useUi = false; const { payload } = this; (0, paramsValidator_1.validateParams)(payload, [{ name: 'accounts', type: 'array' }]); this.params = payload.accounts.flatMap(item => { (0, paramsValidator_1.validateParams)(item, [ { name: 'symbol', type: 'string', required: true }, { name: 'type', type: 'string' }, { name: 'identity', type: 'string' }, { name: 'details', type: 'string' }, { name: 'pageSize', type: 'number' }, { name: 'skip', type: 'number' }, ]); const { symbol, type, ...rest } = item; const coinInfo = (0, coinInfo_1.getCoinInfo)(item.symbol); if (!coinInfo) { throw constants_1.ERRORS.TypedError('Method_UnknownCoin'); } (0, BlockchainLink_1.isBackendSupported)(coinInfo); const firmwareRange = (0, paramsValidator_1.getFirmwareRange)(this.name, coinInfo, AbstractMethod_1.DEFAULT_FIRMWARE_RANGE); return discoverAccounts_1.ACCOUNT_TYPES.filter(a => a.symbol === symbol && (!type || a.type === type)).map(account => ({ pageSize: TXS_PER_PAGE, details: DETAILS, coinInfo, firmwareRange, skip: 0, account, ...rest, offset: isEvmLedger(account, coinInfo) ? 1 : 0, derivation: isCardano(account) ? discoverAccounts_1.CARDANO_DERIVATIONS[account.type] : undefined, })); }); } progress = {}; updateProgress(account, done, last = false) { const progress = last ? 1 : done / Math.max(ACCOUNT_LIMIT, done + 1); const key = getAccountTypeKey(account); this.progress[key] = progress; } sendProgress(account) { const progress = Object.values(this.progress).reduce((sum, typeProgress) => sum + typeProgress, 0) / (Object.keys(this.progress).length || 1); this.postMessage((0, events_1.createUiMessage)(events_1.UI.BUNDLE_PROGRESS, { total: 100, progress: 100 * progress, response: account, })); } async run() { const [unsupported, supported] = this.filterUnsupportedAccounts(this.params); unsupported.forEach(({ account: { path, ...rest }, error }) => this.sendProgress({ ...rest, index: 0, error })); const [cardanoAccounts, otherAccounts] = (0, utils_1.arrayPartition)(supported, isCardanoRequest); const [_, filteredCardanoAccounts] = await this.filterCardanoDerivations(cardanoAccounts); const accounts = [...otherAccounts, ...filteredCardanoAccounts]; accounts.forEach(({ account, skip }) => this.updateProgress(account, skip)); const counts = await Promise.all(accounts.map(account => this.discoverAccount(account))); const nonempty = counts.reduce((sum, acc) => sum + acc.nonempty, 0); const failed = counts.filter(acc => acc.error).length; const empty = counts.length - failed; return { empty, nonempty, failed }; } filterUnsupportedAccounts(accounts) { const version = this.device.getVersion(); const model = this.device.features?.internal_model; if (!version || !model) return [[], accounts]; return (0, utils_1.arrayPartition)(accounts.map(item => { const { min, max } = item.firmwareRange[model]; let error; if (min === '0') { error = events_1.UI.FIRMWARE_NOT_SUPPORTED; } else if (!utils_1.versionUtils.isNewerOrEqual(version, min)) { error = events_1.UI.FIRMWARE_OLD; } else if (max !== '0' && utils_1.versionUtils.isNewer(version, max)) { error = events_1.UI.FIRMWARE_NOT_COMPATIBLE; } return error ? { ...item, error } : item; }), item => 'error' in item); } async filterCardanoDerivations(accounts) { const tryGetDescriptor = (coin) => coin && this.getDescriptor(coin.coinInfo, coin.account.path, coin.derivation, 0); const normal = await tryGetDescriptor(accounts.find(a => a.account.type === 'normal')); const ledger = await tryGetDescriptor(accounts.find(a => a.account.type === 'ledger')); const legacy = await tryGetDescriptor(accounts.find(a => a.account.type === 'legacy')); const omitLegacy = legacy && legacy.descriptor === (normal ?? ledger)?.descriptor; const omitLedger = ledger && ledger.descriptor === normal?.descriptor; return (0, utils_1.arrayPartition)(accounts.map(item => (item.account.type === 'legacy' && omitLegacy) || (item.account.type === 'ledger' && omitLedger) ? { ...item, error: 'ignored cardano derivation' } : item), item => 'error' in item); } descriptorLock = (0, utils_1.getSynchronize)(); descriptorCache = {}; async getDescriptor(coinInfo, bip43PathTemplate, derivationType, index) { const path = bip43PathTemplate.replace('i', String(index)); const { address_n: _, ...descriptorRest } = await this.descriptorLock(async () => { const key = `${path}-${derivationType}`; if (!this.descriptorCache[key]) { const address_n = (0, pathUtils_1.validatePath)(path, 3); this.descriptorCache[key] = await this.device .getCommands() .getAccountDescriptor(coinInfo, address_n, derivationType); } return this.descriptorCache[key]; }); return { path, ...descriptorRest }; } async discoverAccount(request) { const { details, identity, pageSize, coinInfo, derivation, offset, skip } = request; const { path, ...accountKey } = request.account; const utxoRequired = (0, accountUtils_1.isUtxoBased)(coinInfo) && details && details !== 'basic'; let index = skip; let blockchain; try { blockchain = await (0, BlockchainLink_1.initBlockchain)(coinInfo, this.postMessage, identity); } catch (error) { this.updateProgress(accountKey, index + 1, true); this.sendProgress({ ...accountKey, index, error: error.message }); return { nonempty: 0, error: error.message }; } let descPromise = this.getDescriptor(coinInfo, path, derivation, offset + index); while (true) { try { const { descriptor, ...descRest } = await descPromise; descPromise = this.getDescriptor(coinInfo, path, derivation, offset + index + 1); const info = await blockchain.getAccountInfo({ descriptor, details, pageSize }); const utxo = !utxoRequired ? undefined : info.empty ? [] : await blockchain.getAccountUtxo(descriptor); this.updateProgress(accountKey, index + 1, info.empty); this.sendProgress({ ...info, descriptor, ...descRest, utxo, ...accountKey, index, backendType: request.coinInfo.blockchainLink?.type, }); if (info.empty) { await descPromise.catch(() => { }); return { nonempty: index - skip }; } } catch (error) { descPromise.catch(() => { }); this.updateProgress(accountKey, index + 1, true); this.sendProgress({ ...accountKey, index, error: error.message, code: error.code }); return { nonempty: index - skip, error }; } index++; } } dispose() { this.disposed = true; } } exports.default = DiscoverAccounts; //# sourceMappingURL=discoverAccounts.js.map