@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
144 lines (124 loc) • 4.14 kB
text/typescript
;
import "../../__tests__/test-helpers/environment";
// import path from "path";
// import fs from "fs/promises";
import allSpecs from "../../generated/specs";
import type { AppSpec } from "../types";
import { Account } from "@ledgerhq/types-live";
import { AppCandidate } from "@ledgerhq/ledger-wallet-framework/bot/types";
import {
createSpeculosDevice,
findAppCandidate,
listAppCandidates,
releaseSpeculosDevice,
} from "../../load/speculos";
import { makeBridgeCacheSystem } from "../../bridge/cache";
import { getCurrencyBridge } from "../../bridge";
import { filter, map, reduce, timeout } from "rxjs/operators";
import { getEnv } from "@ledgerhq/live-env";
import { firstValueFrom, throwError } from "rxjs";
import { Report } from "./types";
import { toAccountRaw } from "../../account";
import { Audit } from "./audits";
main().then(
r => {
// eslint-disable-next-line no-console
console.log(JSON.stringify(r));
process.exit(0);
},
error => {
// eslint-disable-next-line no-console
console.log(JSON.stringify({ error: String(error) }));
process.exit(0);
},
);
async function main(): Promise<Report> {
const report: Report = {};
const [family, key] = process.argv.slice(2);
const spec: AppSpec<any> = allSpecs[family][key];
const { COINAPPS, SEED } = process.env;
if (!COINAPPS) {
throw new Error("COINAPPS env variable is required");
}
if (!SEED) {
throw new Error("SEED env variable is required");
}
// Prepare speculos device simulator
const appCandidates = await listAppCandidates(COINAPPS);
const { appQuery, currency, dependency, onSpeculosDeviceCreated } = spec;
const appCandidate = findAppCandidate(appCandidates, appQuery);
if (!appCandidate) {
console.warn("no app found for " + spec.name);
console.warn(appQuery);
console.warn(JSON.stringify(appCandidates, undefined, 2));
}
if (!appCandidate) {
throw new Error(`no app found for ${spec.name}. Are you sure your COINAPPS is up to date?`);
}
const deviceParams = {
...(appCandidate as AppCandidate),
appName: spec.currency.managerAppName,
seed: SEED,
dependency,
coinapps: COINAPPS,
onSpeculosDeviceCreated,
};
const device = await createSpeculosDevice(deviceParams);
try {
const audit = new Audit();
// We scan and synchronize the accounts
const localCache = {};
const cache = makeBridgeCacheSystem({
saveData(c, d) {
localCache[c.id] = d;
return Promise.resolve();
},
getData(c) {
return Promise.resolve(localCache[c.id]);
},
});
const bridge = getCurrencyBridge(currency);
const syncConfig = {
paginationConfig: {},
};
await cache.prepareCurrency(currency);
const accounts = await firstValueFrom(
bridge
.scanAccounts({
currency,
deviceId: device.id,
syncConfig,
})
.pipe(
filter(e => e.type === "discovered"),
map(e => e.account),
reduce<Account, Account[]>((all, a) => all.concat(a), []),
timeout({
each: getEnv("BOT_TIMEOUT_SCAN_ACCOUNTS"),
with: () =>
throwError(() => new Error("scan accounts timeout for currency " + currency.name)),
}),
),
);
audit.end();
const accountsRaw = JSON.stringify(accounts.map(a => toAccountRaw(a)));
const preloadJSON = JSON.stringify(localCache);
audit.setAccountsJSONSize(accountsRaw.length);
audit.setPreloadJSONSize(preloadJSON.length);
/*
// TODO big data
if (REPORT_FOLDER) {
const accountsFile = path.join(REPORT_FOLDER, "accounts.json");
await fs.writeFile(accountsFile, accountsRaw);
}
*/
report.refillAddress = accounts[0]?.freshAddress;
report.accountBalances = accounts.map(a => a.balance.toString());
report.accountIds = accounts.map(a => a.id);
report.accountOperationsLength = accounts.map(a => a.operations.length);
report.auditResult = audit.result();
} finally {
await releaseSpeculosDevice(device.id);
}
return report;
}