@trezor/connect
Version:
High-level javascript interface for Trezor hardware wallet.
188 lines • 9.32 kB
JavaScript
;
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