@hackerhouse/xpub-scan 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +8 -0
- package/CONTRIBUTING.md +130 -0
- package/LICENSE +23 -0
- package/README.md +215 -0
- package/__tests__/checkAddresses/checkBitcoinAddressesNegative.test.ts +75 -0
- package/__tests__/checkAddresses/checkBitcoinAddressesPositive.test.ts +87 -0
- package/__tests__/checkAddresses/checkDogeAddressesPositive.test.ts +43 -0
- package/__tests__/checkAddresses/checkLitecoinAddressesNegative.test.ts +75 -0
- package/__tests__/checkAddresses/checkLitecoinAddressesPositive.test.ts +87 -0
- package/__tests__/checkModels/address.test.ts +183 -0
- package/__tests__/checkModels/fakeRawTransactions.json +182 -0
- package/__tests__/deriveAddresses/deriveBitcoinAddresses.test.ts +207 -0
- package/__tests__/deriveAddresses/deriveBitcoinCashAddresses.test copy.ts +79 -0
- package/__tests__/deriveAddresses/deriveDogecoinAddresses.test.ts +43 -0
- package/__tests__/deriveAddresses/deriveEthereumAddresses.test.ts +26 -0
- package/__tests__/deriveAddresses/deriveLitecoinAddresses.test.ts +110 -0
- package/__tests__/helpers.test.ts +274 -0
- package/__tests__/test-utils.ts +3 -0
- package/babel.config.js +6 -0
- package/jest.config.ts +5 -0
- package/ledgerhq-xpub-scan-1.0.4.tgz +0 -0
- package/lib/actions/checkAddress.d.ts +29 -0
- package/lib/actions/checkAddress.js +122 -0
- package/lib/actions/checkBalance.d.ts +20 -0
- package/lib/actions/checkBalance.js +300 -0
- package/lib/actions/deriveAddresses.d.ts +17 -0
- package/lib/actions/deriveAddresses.js +239 -0
- package/lib/actions/processTransactions.d.ts +29 -0
- package/lib/actions/processTransactions.js +289 -0
- package/lib/actions/saveAnalysis.d.ts +2 -0
- package/lib/actions/saveAnalysis.js +800 -0
- package/lib/actions/scanner.d.ts +15 -0
- package/lib/actions/scanner.js +152 -0
- package/lib/api/customProvider.d.ts +19 -0
- package/lib/api/customProvider.js +434 -0
- package/lib/api/defaultProvider.d.ts +23 -0
- package/lib/api/defaultProvider.js +275 -0
- package/lib/comparison/compareOperations.d.ts +13 -0
- package/lib/comparison/compareOperations.js +500 -0
- package/lib/comparison/diffs.d.ts +18 -0
- package/lib/comparison/diffs.js +70 -0
- package/lib/configuration/currencies.d.ts +55 -0
- package/lib/configuration/currencies.js +72 -0
- package/lib/configuration/settings.d.ts +51 -0
- package/lib/configuration/settings.js +113 -0
- package/lib/display.d.ts +12 -0
- package/lib/display.js +251 -0
- package/lib/helpers.d.ts +27 -0
- package/lib/helpers.js +255 -0
- package/lib/input/args.d.ts +6 -0
- package/lib/input/args.js +129 -0
- package/lib/input/check.d.ts +6 -0
- package/lib/input/check.js +217 -0
- package/lib/input/importOperations.d.ts +11 -0
- package/lib/input/importOperations.js +406 -0
- package/lib/models/address.d.ts +40 -0
- package/lib/models/address.js +101 -0
- package/lib/models/comparison.d.ts +8 -0
- package/lib/models/comparison.js +6 -0
- package/lib/models/currency.d.ts +11 -0
- package/lib/models/currency.js +6 -0
- package/lib/models/operation.d.ts +33 -0
- package/lib/models/operation.js +80 -0
- package/lib/models/ownAddresses.d.ts +11 -0
- package/lib/models/ownAddresses.js +31 -0
- package/lib/models/scanLimits.d.ts +7 -0
- package/lib/models/scanLimits.js +6 -0
- package/lib/models/stats.d.ts +7 -0
- package/lib/models/stats.js +6 -0
- package/lib/models/transaction.d.ts +10 -0
- package/lib/models/transaction.js +13 -0
- package/lib/scan.d.ts +2 -0
- package/lib/scan.js +31 -0
- package/lib/templates/logos.base64.d.ts +2 -0
- package/lib/templates/logos.base64.js +9 -0
- package/lib/templates/report.html.d.ts +1 -0
- package/lib/templates/report.html.js +393 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/lib/types.d.ts +55 -0
- package/lib/types.js +2 -0
- package/npm-shrinkwrap.json +12323 -0
- package/package.json +81 -0
- package/sonar-project.properties +15 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.save = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const html_minifier_1 = __importDefault(require("html-minifier"));
|
|
9
|
+
const settings_1 = require("../configuration/settings");
|
|
10
|
+
const report_html_1 = require("../templates/report.html");
|
|
11
|
+
const logos_base64_1 = require("../templates/logos.base64");
|
|
12
|
+
const helpers_1 = require("../helpers");
|
|
13
|
+
const currencies_1 = require("../configuration/currencies");
|
|
14
|
+
const bignumber_js_1 = __importDefault(require("bignumber.js"));
|
|
15
|
+
function renderToken(token, status) {
|
|
16
|
+
const renderedAmount = new bignumber_js_1.default(token.amount).toFormat(6);
|
|
17
|
+
let renderedToken = `<br><span class="token_details` +
|
|
18
|
+
(typeof status == "undefined" || status.includes("Match")
|
|
19
|
+
? ``
|
|
20
|
+
: ` token_mismatch`) +
|
|
21
|
+
`">`;
|
|
22
|
+
renderedToken += `${parseFloat(renderedAmount)} ${token.symbol}<br>${token.name}</span>`;
|
|
23
|
+
return renderedToken;
|
|
24
|
+
}
|
|
25
|
+
// align float numbers or zeros
|
|
26
|
+
function renderAmount(amount) {
|
|
27
|
+
const decimalPrecision = 8;
|
|
28
|
+
const filler = "¤"; // (any non-numeric non-dot char)
|
|
29
|
+
let renderedAmount;
|
|
30
|
+
const n = new bignumber_js_1.default(amount);
|
|
31
|
+
if (n.isZero()) {
|
|
32
|
+
// align '0': insert filler on the right
|
|
33
|
+
renderedAmount = "0".padEnd(decimalPrecision, filler);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
renderedAmount = (0, helpers_1.toAccountUnit)(n, 8);
|
|
37
|
+
}
|
|
38
|
+
// align any number: insert filler on the left
|
|
39
|
+
renderedAmount = renderedAmount.padStart(decimalPrecision, filler);
|
|
40
|
+
renderedAmount = renderedAmount.replace(/^0+(\d)|(\d)0+$/gm, "$1$2"); // remove trailing zeros
|
|
41
|
+
// insert non-breaking spaces by replacing the filler with ` `
|
|
42
|
+
return ('<span class="monospaced">' +
|
|
43
|
+
renderedAmount.split(filler).join(" ") +
|
|
44
|
+
"</span>");
|
|
45
|
+
}
|
|
46
|
+
// generate the url to an external explorer allowing to get more info
|
|
47
|
+
// regarding an item (address or transaction)
|
|
48
|
+
function getUrl(itemType, item) {
|
|
49
|
+
// general case (Bitcoin, Litecoin)
|
|
50
|
+
// --------------------------------
|
|
51
|
+
let url = settings_1.EXTERNAL_EXPLORERS_URLS.general;
|
|
52
|
+
const itemTypes = {
|
|
53
|
+
address: "address",
|
|
54
|
+
transaction: "tx",
|
|
55
|
+
};
|
|
56
|
+
// exception(s)
|
|
57
|
+
// ------------
|
|
58
|
+
// Testnet
|
|
59
|
+
if (settings_1.configuration.testnet) {
|
|
60
|
+
url = url.replace("{currency}", "{currency}-testnet");
|
|
61
|
+
}
|
|
62
|
+
// Bitcoin Cash
|
|
63
|
+
//
|
|
64
|
+
// coin: "bitcoin-cash"
|
|
65
|
+
// item types: "address" | "transaction"
|
|
66
|
+
if (settings_1.configuration.currency.symbol === currencies_1.currencies.bch.symbol) {
|
|
67
|
+
url = settings_1.EXTERNAL_EXPLORERS_URLS.bch;
|
|
68
|
+
url = url.replace("{currency}", "bitcoin-cash");
|
|
69
|
+
itemTypes.address = "address";
|
|
70
|
+
itemTypes.transaction = "transaction";
|
|
71
|
+
}
|
|
72
|
+
// Ethereum
|
|
73
|
+
//
|
|
74
|
+
// item types: "address" | "tx"
|
|
75
|
+
if (settings_1.configuration.currency.symbol === currencies_1.currencies.eth.symbol) {
|
|
76
|
+
url = settings_1.EXTERNAL_EXPLORERS_URLS.eth;
|
|
77
|
+
itemTypes.address = "address";
|
|
78
|
+
itemTypes.transaction = "tx";
|
|
79
|
+
if (settings_1.configuration.testnet) {
|
|
80
|
+
// https://etherscan.io -> https://ropsten.etherscan.io
|
|
81
|
+
url = url.replace("https://", "https://ropsten.");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// specify item type
|
|
85
|
+
switch (itemType) {
|
|
86
|
+
case "address":
|
|
87
|
+
url = url.replace("{type}", itemTypes.address);
|
|
88
|
+
break;
|
|
89
|
+
case "transaction":
|
|
90
|
+
url = url.replace("{type}", itemTypes.transaction);
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
throw new Error('Unrecognized item type "' +
|
|
94
|
+
itemType +
|
|
95
|
+
"\" (expected: 'address' or 'transaction')");
|
|
96
|
+
}
|
|
97
|
+
return url
|
|
98
|
+
.replace("{currency}", settings_1.configuration.currency.symbol.toLowerCase())
|
|
99
|
+
.replace("{item}", item);
|
|
100
|
+
}
|
|
101
|
+
// make address clickable
|
|
102
|
+
function addressAsLink(address) {
|
|
103
|
+
// if no address, return empty string
|
|
104
|
+
// (used for CSV files that do not contain any address)
|
|
105
|
+
if (typeof address === "undefined") {
|
|
106
|
+
return "";
|
|
107
|
+
}
|
|
108
|
+
const url = getUrl("address", address);
|
|
109
|
+
address = address.length < 45 ? address : address.substring(0, 45) + "...";
|
|
110
|
+
return ('<a class="monospaced" href="' + url + '" target=_blank>' + address + "</a>");
|
|
111
|
+
}
|
|
112
|
+
function renderAddress(address, cashAddress) {
|
|
113
|
+
const renderedAddress = addressAsLink(address);
|
|
114
|
+
if (settings_1.configuration.currency.symbol !== currencies_1.currencies.bch.symbol || !cashAddress) {
|
|
115
|
+
return renderedAddress;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Bitcoin Cash: handle Legacy/Cash address duality:
|
|
119
|
+
// {legacy}
|
|
120
|
+
// {Cash address}
|
|
121
|
+
return renderedAddress.concat("</br>").concat(addressAsLink(cashAddress));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// make TXID clickable
|
|
125
|
+
function renderTxid(txid) {
|
|
126
|
+
if (!txid) {
|
|
127
|
+
return "(no txid)";
|
|
128
|
+
}
|
|
129
|
+
const url = getUrl("transaction", txid);
|
|
130
|
+
txid = txid.substring(0, 10) + "...";
|
|
131
|
+
return ('<a class="monospaced" href="' + url + '" target=_blank>' + txid + "</a>");
|
|
132
|
+
}
|
|
133
|
+
// explain some operation types
|
|
134
|
+
function createTooltip(opType) {
|
|
135
|
+
if (opType === "Sent" || opType === "Received") {
|
|
136
|
+
return opType;
|
|
137
|
+
}
|
|
138
|
+
let tooltip = "";
|
|
139
|
+
if (opType === "Received (non-sibling to change)") {
|
|
140
|
+
tooltip = `
|
|
141
|
+
<span class="tooltiptext">
|
|
142
|
+
Change address that received funds from an address NOT belonging to the same xpub
|
|
143
|
+
</span>
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
else if (opType === "Sent to self") {
|
|
147
|
+
tooltip = `
|
|
148
|
+
<span class="tooltiptext">
|
|
149
|
+
Sent to itself (same address)
|
|
150
|
+
</span>
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
else if (opType === "Sent to sibling") {
|
|
154
|
+
tooltip = `
|
|
155
|
+
<span class="tooltiptext">
|
|
156
|
+
Sent to another address, belonging to the same xpub (sibling)
|
|
157
|
+
</span>
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
else if (opType === "Failed to send") {
|
|
161
|
+
tooltip = `
|
|
162
|
+
<span class="tooltiptext">
|
|
163
|
+
Send operation failed (it can impact the balance)
|
|
164
|
+
</span>
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
else if (opType.includes("token")) {
|
|
168
|
+
tooltip = `
|
|
169
|
+
<span class="tooltiptext">
|
|
170
|
+
Ethereum token (e.g. ERC20) related operation
|
|
171
|
+
</span>
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
else if (opType.includes("dapp")) {
|
|
175
|
+
tooltip = `
|
|
176
|
+
<span class="tooltiptext">
|
|
177
|
+
Ethereum Dapp related operation
|
|
178
|
+
</span>
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
else if (opType.includes("SCI")) {
|
|
182
|
+
tooltip = `
|
|
183
|
+
<span class="tooltiptext">
|
|
184
|
+
Ethereum smart contract interaction (not a token transfer)
|
|
185
|
+
</span>
|
|
186
|
+
`;
|
|
187
|
+
}
|
|
188
|
+
else if (opType === "Swapped") {
|
|
189
|
+
tooltip = `
|
|
190
|
+
<span class="tooltiptext">
|
|
191
|
+
Swapped Ethers for tokens (note that it is expected for the imported ETH amount to be positive and for the actual one to be negative)
|
|
192
|
+
</span>
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
return opType;
|
|
197
|
+
}
|
|
198
|
+
return '<div class="tooltip">' + opType + tooltip + "</div>";
|
|
199
|
+
}
|
|
200
|
+
function makeTransactionsTable(outputData) {
|
|
201
|
+
// balance only mode: do not display the transaction table
|
|
202
|
+
if (outputData.meta.balanceOnly) {
|
|
203
|
+
return "";
|
|
204
|
+
}
|
|
205
|
+
const transactionsTableHead = `
|
|
206
|
+
<thead>
|
|
207
|
+
<tr>
|
|
208
|
+
<th>Date</th>
|
|
209
|
+
<th>Block</th>
|
|
210
|
+
<th>Tx id</th>
|
|
211
|
+
<th>Address</th>
|
|
212
|
+
<th>Amount</th>
|
|
213
|
+
<th>Type</th>
|
|
214
|
+
</tr>
|
|
215
|
+
</thead>
|
|
216
|
+
`;
|
|
217
|
+
let transactionsTemplate = `
|
|
218
|
+
{paginationStyle}
|
|
219
|
+
<li class="tab">
|
|
220
|
+
<input type="radio" name="tabs" id="tab4" />
|
|
221
|
+
<label for="tab4">${outputData.transactions.length} Transactions</label>
|
|
222
|
+
<div id="tab-content4" class="content">
|
|
223
|
+
<div class="warning">{warning}</div>
|
|
224
|
+
{paginationRadios}
|
|
225
|
+
{transactions}
|
|
226
|
+
{paginationSlider}
|
|
227
|
+
</div>
|
|
228
|
+
</li>
|
|
229
|
+
`;
|
|
230
|
+
// display warning if default provider is being used
|
|
231
|
+
if (outputData.meta.provider === "default") {
|
|
232
|
+
transactionsTemplate = transactionsTemplate.replace("{warning}", "Default provider used: only the last ~50 operations by address are displayed");
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
transactionsTemplate = transactionsTemplate.replace("{warning}", "");
|
|
236
|
+
}
|
|
237
|
+
const transactions = [];
|
|
238
|
+
for (const e of outputData.transactions) {
|
|
239
|
+
let rowStyle = "<tr>";
|
|
240
|
+
const transactionRow = [];
|
|
241
|
+
if (e.operationType === "Failed to send") {
|
|
242
|
+
rowStyle = '<tr class="failed_operation">';
|
|
243
|
+
}
|
|
244
|
+
else if (e.operationType.includes("token") ||
|
|
245
|
+
e.operationType === "Swapped") {
|
|
246
|
+
rowStyle = '<tr class="token_operation">';
|
|
247
|
+
}
|
|
248
|
+
else if (e.operationType.includes("SCI")) {
|
|
249
|
+
rowStyle = '<tr class="sci_operation">';
|
|
250
|
+
}
|
|
251
|
+
transactionRow.push(rowStyle);
|
|
252
|
+
transactionRow.push("<td>" + e.date + "</td>");
|
|
253
|
+
transactionRow.push("<td>" + e.block + "</td>");
|
|
254
|
+
transactionRow.push("<td>" + renderTxid(e.txid) + "</td>");
|
|
255
|
+
transactionRow.push("<td>" + renderAddress(e.address, e.cashAddress) + "</td>");
|
|
256
|
+
let amount = renderAmount(e.amount);
|
|
257
|
+
if (typeof e.token !== "undefined") {
|
|
258
|
+
amount += renderToken(e.token);
|
|
259
|
+
}
|
|
260
|
+
if (typeof e.dapp !== "undefined") {
|
|
261
|
+
amount += `<br><span class="dapp_details">${e.dapp.contract_name}</span>`;
|
|
262
|
+
}
|
|
263
|
+
transactionRow.push("<td>" + amount + "</td>");
|
|
264
|
+
transactionRow.push("<td>" + createTooltip(e.operationType) + "</td></tr>");
|
|
265
|
+
transactions.push(transactionRow);
|
|
266
|
+
}
|
|
267
|
+
return makePaginatedTable(transactionsTableHead, transactionsTemplate, transactions, 100, "transactions");
|
|
268
|
+
}
|
|
269
|
+
function makeUTXOSTable(outputData) {
|
|
270
|
+
if (typeof outputData.utxos === "undefined" ||
|
|
271
|
+
outputData.utxos.length === 0) {
|
|
272
|
+
return "";
|
|
273
|
+
}
|
|
274
|
+
const UTXOSTableHead = `
|
|
275
|
+
<thead>
|
|
276
|
+
<tr>
|
|
277
|
+
<th>Type</th>
|
|
278
|
+
<th>Derivation</th>
|
|
279
|
+
<th>Address</th>
|
|
280
|
+
<th>Balance</th>
|
|
281
|
+
<th>Funded</th>
|
|
282
|
+
<th>Spent</th>
|
|
283
|
+
</tr>
|
|
284
|
+
</thead>`;
|
|
285
|
+
const UTXOSTemplate = `
|
|
286
|
+
{paginationStyle}
|
|
287
|
+
<li class="tab">
|
|
288
|
+
<input type="radio" name="tabs" id="tab3" />
|
|
289
|
+
<label for="tab3">${outputData.utxos.length} UTXO${outputData.utxos.length > 1 ? "S" : ""}</label>
|
|
290
|
+
<div id="tab-content3" class="content">
|
|
291
|
+
{paginationRadios}
|
|
292
|
+
{utxos}
|
|
293
|
+
{paginationSlider}
|
|
294
|
+
</div>
|
|
295
|
+
</li>
|
|
296
|
+
`;
|
|
297
|
+
const utxos = [];
|
|
298
|
+
for (const e of outputData.utxos) {
|
|
299
|
+
const utxoRow = [];
|
|
300
|
+
utxoRow.push("<tr><td>" + e.derivationMode + "</td>");
|
|
301
|
+
const derivationPath = "m/" + e.derivation.account + "/" + e.derivation.index;
|
|
302
|
+
utxoRow.push("<td>" + derivationPath + "</td>");
|
|
303
|
+
utxoRow.push("<td>" + renderAddress(e.address, e.cashAddress) + "</td>");
|
|
304
|
+
const balance = renderAmount(e.balance);
|
|
305
|
+
const funded = renderAmount(e.funded);
|
|
306
|
+
const spent = renderAmount(e.spent);
|
|
307
|
+
utxoRow.push("<td>" + balance + "</td>");
|
|
308
|
+
utxoRow.push("<td>" + funded + "</td>");
|
|
309
|
+
utxoRow.push("<td>" + spent + "</td></tr>");
|
|
310
|
+
utxos.push(utxoRow);
|
|
311
|
+
}
|
|
312
|
+
return makePaginatedTable(UTXOSTableHead, UTXOSTemplate, utxos, 100, "utxos");
|
|
313
|
+
}
|
|
314
|
+
function makeComparisonsTable(outputData, onlyDiff) {
|
|
315
|
+
const getLabel = (status) => {
|
|
316
|
+
if (status.includes("Match")) {
|
|
317
|
+
return "match_label";
|
|
318
|
+
}
|
|
319
|
+
else if (status === "Skipped") {
|
|
320
|
+
return "skipped_label";
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
return "mismatch_label";
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const comparisonsTableHead = `
|
|
327
|
+
<thead>
|
|
328
|
+
<tr style="text-align: center">
|
|
329
|
+
<th rowspan="1" colspan="3" class="right_sep">IMPORTED OPERATION, FROM PRODUCT</th>
|
|
330
|
+
<th rowspan="1" colspan="3" class="right_sep">ACTUAL OPERATION, FROM EXTERNAL PROVIDER</th>
|
|
331
|
+
<th rowspan="2" colspan="1">TXID</th>
|
|
332
|
+
<th rowspan="2" colspan="1">TYPE</th>
|
|
333
|
+
<th rowspan="2" colspan="1">STATUS</th>
|
|
334
|
+
</tr>
|
|
335
|
+
<tr>
|
|
336
|
+
<th>Date</th>
|
|
337
|
+
<th>Address</th>
|
|
338
|
+
<th class="right_sep">Amount</th>
|
|
339
|
+
<th>Date</th>
|
|
340
|
+
<th>Address</th>
|
|
341
|
+
<th class="right_sep">Amount</th>
|
|
342
|
+
</tr>
|
|
343
|
+
</thead>
|
|
344
|
+
`;
|
|
345
|
+
let comparisonsTemplate = `
|
|
346
|
+
{paginationStyle}
|
|
347
|
+
<li class="tab">
|
|
348
|
+
<input type="radio" name="tabs" id="tab{id}" />
|
|
349
|
+
<label for="tab{id}">${onlyDiff ? outputData.diffs.length : outputData.comparisons.length} {label}</label>
|
|
350
|
+
<div id="tab-content{id}" class="content">
|
|
351
|
+
{paginationRadios}
|
|
352
|
+
${onlyDiff ? "{diffs}" : "{comparisons}"}
|
|
353
|
+
{paginationSlider}
|
|
354
|
+
</div>
|
|
355
|
+
</li>
|
|
356
|
+
`;
|
|
357
|
+
let comp;
|
|
358
|
+
if (!onlyDiff) {
|
|
359
|
+
comp = outputData.comparisons;
|
|
360
|
+
comparisonsTemplate = comparisonsTemplate.replace("{label}", `Comparison${outputData.comparisons.length > 1 ? "s" : ""}`);
|
|
361
|
+
comparisonsTemplate = comparisonsTemplate.split("{id}").join("5"); // comparisons have id 5
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
comp = outputData.diffs;
|
|
365
|
+
comparisonsTemplate = comparisonsTemplate.replace("{label}", `Difference${outputData.diffs.length > 1 ? "s" : ""}`);
|
|
366
|
+
comparisonsTemplate = comparisonsTemplate.split("{id}").join("6"); // differences have id 6
|
|
367
|
+
}
|
|
368
|
+
const comparisons = [];
|
|
369
|
+
if (typeof comp !== "undefined") {
|
|
370
|
+
for (const e of comp) {
|
|
371
|
+
const comparisonRow = [];
|
|
372
|
+
let txid = "";
|
|
373
|
+
let opType = "";
|
|
374
|
+
// by default: no imported operation
|
|
375
|
+
const imported = {
|
|
376
|
+
date: "",
|
|
377
|
+
address: "(no operation)",
|
|
378
|
+
amount: "",
|
|
379
|
+
token: undefined,
|
|
380
|
+
dapp: undefined,
|
|
381
|
+
};
|
|
382
|
+
if (typeof e.imported !== "undefined") {
|
|
383
|
+
imported.date = e.imported.date;
|
|
384
|
+
imported.address = renderAddress(e.imported.address);
|
|
385
|
+
imported.amount = renderAmount(e.imported.amount);
|
|
386
|
+
imported.token = e.imported.token;
|
|
387
|
+
imported.dapp = e.imported.dapp;
|
|
388
|
+
txid = e.imported.txid;
|
|
389
|
+
opType = e.imported.operationType;
|
|
390
|
+
}
|
|
391
|
+
// by default: no actual operation
|
|
392
|
+
const actual = {
|
|
393
|
+
date: "",
|
|
394
|
+
address: "(no operation)",
|
|
395
|
+
amount: "",
|
|
396
|
+
token: undefined,
|
|
397
|
+
dapp: undefined,
|
|
398
|
+
};
|
|
399
|
+
if (typeof e.actual !== "undefined") {
|
|
400
|
+
actual.date = e.actual.date;
|
|
401
|
+
actual.address = renderAddress(e.actual.address, e.actual.cashAddress);
|
|
402
|
+
actual.amount = renderAmount(e.actual.amount);
|
|
403
|
+
actual.token = e.actual.token;
|
|
404
|
+
actual.dapp = e.actual.dapp;
|
|
405
|
+
txid = e.actual.txid;
|
|
406
|
+
opType = e.actual.operationType;
|
|
407
|
+
}
|
|
408
|
+
if (e.status === "Match") {
|
|
409
|
+
if (onlyDiff) {
|
|
410
|
+
continue; // if diff: ignore matches
|
|
411
|
+
}
|
|
412
|
+
if (opType === "Failed to send") {
|
|
413
|
+
comparisonRow.push('<tr class="failed_operation">');
|
|
414
|
+
}
|
|
415
|
+
else if (opType.includes("token") || opType === "Swapped") {
|
|
416
|
+
comparisonRow.push('<tr class="token_operation">');
|
|
417
|
+
}
|
|
418
|
+
else if (opType.includes("SCI")) {
|
|
419
|
+
comparisonRow.push('<tr class="sci_operation">');
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
comparisonRow.push('<tr class="comparison_match">');
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else if (e.status.includes("aggregated")) {
|
|
426
|
+
if (onlyDiff) {
|
|
427
|
+
continue; // if diff: ignore aggregated operations
|
|
428
|
+
}
|
|
429
|
+
comparisonRow.push('<tr class="comparison_aggregated">');
|
|
430
|
+
}
|
|
431
|
+
else if (e.status === "Skipped") {
|
|
432
|
+
comparisonRow.push('<tr class="skipped_comparison">');
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
comparisonRow.push('<tr class="comparison_mismatch">');
|
|
436
|
+
}
|
|
437
|
+
comparisonRow.push("<td>" + imported.date + "</td>");
|
|
438
|
+
comparisonRow.push("<td>" + imported.address + "</td>");
|
|
439
|
+
let importedAmount = imported.amount;
|
|
440
|
+
if (typeof imported.token !== "undefined") {
|
|
441
|
+
importedAmount += renderToken(e.imported.token, e.status);
|
|
442
|
+
}
|
|
443
|
+
if (typeof imported.dapp !== "undefined") {
|
|
444
|
+
importedAmount += `<br><span class="dapp_details">${e.imported.dapp.contract_name}</span>`;
|
|
445
|
+
}
|
|
446
|
+
comparisonRow.push('<td class="right_sep">' + importedAmount + "</td>");
|
|
447
|
+
comparisonRow.push("<td>" + actual.date + "</td>");
|
|
448
|
+
comparisonRow.push("<td>" + actual.address + "</td>");
|
|
449
|
+
let actualAmount = actual.amount;
|
|
450
|
+
if (typeof actual.token !== "undefined") {
|
|
451
|
+
actualAmount += renderToken(actual.token, e.status);
|
|
452
|
+
}
|
|
453
|
+
comparisonRow.push('<td class="right_sep">' + actualAmount + "</td>");
|
|
454
|
+
comparisonRow.push("<td>" + renderTxid(txid) + "</td>");
|
|
455
|
+
comparisonRow.push("<td>" + createTooltip(opType) + "</td>");
|
|
456
|
+
comparisonRow.push('<td><span class="label ' + getLabel(e.status) + '">');
|
|
457
|
+
comparisonRow.push(e.status +
|
|
458
|
+
(e.status === "Skipped"
|
|
459
|
+
? ` (> block #${settings_1.configuration.blockHeightUpperLimit})`
|
|
460
|
+
: "") +
|
|
461
|
+
"</span></td></tr>");
|
|
462
|
+
comparisons.push(comparisonRow);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (comparisons.length > 0) {
|
|
466
|
+
return makePaginatedTable(comparisonsTableHead, comparisonsTemplate, comparisons, 100, onlyDiff ? "diffs" : "comparisons");
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
return "";
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function makePaginatedTable(tableHead, template, rowsData, pageSize, key) {
|
|
473
|
+
if (rowsData.length > pageSize) {
|
|
474
|
+
const pageCount = Math.ceil(rowsData.length / 100);
|
|
475
|
+
const pageArray = [...Array(pageCount).keys()].map((i) => i + 1);
|
|
476
|
+
template = template
|
|
477
|
+
.replace("{paginationStyle}", `<style type="text/css">
|
|
478
|
+
${pageArray.map((i) => `#${key}-radio${i}`).join(", ")} {
|
|
479
|
+
display: none;
|
|
480
|
+
}
|
|
481
|
+
.page-slider {
|
|
482
|
+
display: flex;
|
|
483
|
+
flex-wrap: wrap;
|
|
484
|
+
justify-content: center;
|
|
485
|
+
max-width: 1000px;
|
|
486
|
+
margin: auto;
|
|
487
|
+
text-align: center;
|
|
488
|
+
}
|
|
489
|
+
.${key}-page-label {
|
|
490
|
+
cursor: pointer;
|
|
491
|
+
color: white;
|
|
492
|
+
background-color: #303030;
|
|
493
|
+
padding: 6px;
|
|
494
|
+
width: 50px;
|
|
495
|
+
margin: 0px;
|
|
496
|
+
}
|
|
497
|
+
${pageArray.map((i) => `#${key}-page${i}`).join(", ")} {
|
|
498
|
+
display: none;
|
|
499
|
+
}
|
|
500
|
+
${pageArray
|
|
501
|
+
.map((i) => `#${key}-radio${i}:checked ~ .page-slider #${key}-label${i}`)
|
|
502
|
+
.join(", ")} {
|
|
503
|
+
background-color: #4a83fd;
|
|
504
|
+
}
|
|
505
|
+
${pageArray
|
|
506
|
+
.map((i) => `#${key}-radio${i}:checked ~ #${key}-page${i}`)
|
|
507
|
+
.join(", ")} {
|
|
508
|
+
display: table;
|
|
509
|
+
}
|
|
510
|
+
</style>`)
|
|
511
|
+
.replace("{paginationRadios}", pageArray
|
|
512
|
+
.map((i) => `<input type="radio" name="${key}-page-radio" id="${key}-radio${i}" ${i === 1 ? "checked" : ""} />`)
|
|
513
|
+
.join(""))
|
|
514
|
+
.replace("{paginationSlider}", `<div class="page-slider">
|
|
515
|
+
${pageArray
|
|
516
|
+
.map((i) => `<label for="${key}-radio${i}" id="${key}-label${i}" class="${key}-page-label">
|
|
517
|
+
${i}
|
|
518
|
+
</label>`)
|
|
519
|
+
.join("")}
|
|
520
|
+
</div>`)
|
|
521
|
+
.replace(`{${key}}`, pageArray
|
|
522
|
+
.map((i) => `<table id="${key}-page${i}">
|
|
523
|
+
${tableHead}
|
|
524
|
+
<tbody>
|
|
525
|
+
${rowsData
|
|
526
|
+
.slice(i * 100 - 100, i * 100)
|
|
527
|
+
.map((rowData) => rowData.join(""))
|
|
528
|
+
.join("")}
|
|
529
|
+
</tbody>
|
|
530
|
+
</table>`)
|
|
531
|
+
.join(""));
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
template = template
|
|
535
|
+
.replace("{paginationStyle}", "")
|
|
536
|
+
.replace("{paginationRadios}", "")
|
|
537
|
+
.replace("{paginationSlider}", "")
|
|
538
|
+
.replace(`{${key}}`, `<table>
|
|
539
|
+
${tableHead}
|
|
540
|
+
<tbody>
|
|
541
|
+
${rowsData.map((rowData) => rowData.join("")).join("")}
|
|
542
|
+
</tbody>
|
|
543
|
+
</table>`);
|
|
544
|
+
}
|
|
545
|
+
return template;
|
|
546
|
+
}
|
|
547
|
+
function makeAddressesTable(outputData) {
|
|
548
|
+
const addressesTableHead = `
|
|
549
|
+
<thead>
|
|
550
|
+
<tr>
|
|
551
|
+
<th>Type</th>
|
|
552
|
+
<th>Derivation</th>
|
|
553
|
+
<th>Address</th>
|
|
554
|
+
<th>Balance</th>
|
|
555
|
+
<th>Funded</th>
|
|
556
|
+
<th>Spent</th>
|
|
557
|
+
</tr>
|
|
558
|
+
</thead>
|
|
559
|
+
`;
|
|
560
|
+
let addressesTemplate = `
|
|
561
|
+
{paginationStyle}
|
|
562
|
+
<li class="tab">
|
|
563
|
+
<input type="radio" name="tabs" id="tab2" />
|
|
564
|
+
<label for="tab2">{addresses_count} Address{addresses_plural}</label>
|
|
565
|
+
<div id="tab-content2" class="content">
|
|
566
|
+
{paginationRadios}
|
|
567
|
+
{addresses}
|
|
568
|
+
{paginationSlider}
|
|
569
|
+
</div>
|
|
570
|
+
</li>`;
|
|
571
|
+
const addresses = [];
|
|
572
|
+
for (const e of outputData.addresses) {
|
|
573
|
+
const addressRow = [];
|
|
574
|
+
if (typeof e.derivation.account !== "undefined") {
|
|
575
|
+
addressRow.push("<tr><td>" + e.derivationMode + "</td>");
|
|
576
|
+
const derivationPath = "m/" + e.derivation.account + "/" + e.derivation.index;
|
|
577
|
+
addressRow.push("<td>" + derivationPath + "</td>");
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
addressRow.push("<tr><td>" + settings_1.configuration.currency.name + "</td><td>-</td>");
|
|
581
|
+
}
|
|
582
|
+
addressRow.push("<td>" + renderAddress(e.address, e.cashAddress) + "</td>");
|
|
583
|
+
const balance = renderAmount(e.balance);
|
|
584
|
+
const funded = renderAmount(e.funded);
|
|
585
|
+
const spent = renderAmount(e.spent);
|
|
586
|
+
addressRow.push("<td>" + balance + "</td>");
|
|
587
|
+
addressRow.push("<td>" + funded + "</td>");
|
|
588
|
+
addressRow.push("<td>" + spent + "</td></tr>");
|
|
589
|
+
addresses.push(addressRow);
|
|
590
|
+
}
|
|
591
|
+
addressesTemplate = addressesTemplate.replace("{addresses_count}", outputData.addresses.length.toFixed());
|
|
592
|
+
addressesTemplate = addressesTemplate.replace("{addresses_plural}", outputData.addresses.length > 1 ? "es" : "");
|
|
593
|
+
return makePaginatedTable(addressesTableHead, addressesTemplate, addresses, 100, "addresses");
|
|
594
|
+
}
|
|
595
|
+
function saveHTML(outputData, filepath) {
|
|
596
|
+
let report = report_html_1.reportTemplate;
|
|
597
|
+
// background color and logo
|
|
598
|
+
if (settings_1.configuration.testnet) {
|
|
599
|
+
// yellow if testnet
|
|
600
|
+
report = report.replace("{body_background_color}", "#f7f48a");
|
|
601
|
+
report = report.replace("{logo_base_64}", logos_base64_1.base64YellowLogo.substring(0, 28685));
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
// white otherwise
|
|
605
|
+
report = report.replace("{body_background_color}", "#ffffff");
|
|
606
|
+
report = report.replace("{logo_base_64}", !filepath.includes(`/${"e".repeat(2)}${"g".repeat(2)}/`)
|
|
607
|
+
? logos_base64_1.base64WhiteLogo.substring(0, 24557)
|
|
608
|
+
: logos_base64_1.base64WhiteLogo.substring(24557, logos_base64_1.base64WhiteLogo.length - 1));
|
|
609
|
+
}
|
|
610
|
+
// meta
|
|
611
|
+
if (typeof outputData.meta.preDerivationSize === "undefined") {
|
|
612
|
+
report = report.replace("{pre_derivation_size}", "");
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
report = report.replace("{pre_derivation_size}", `| pre-derivation size: ${outputData.meta.preDerivationSize}`);
|
|
616
|
+
}
|
|
617
|
+
if (typeof outputData.meta.derivationMode === "undefined") {
|
|
618
|
+
report = report.replace("{derivation_mode}", "");
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
report = report.replace("{derivation_mode}", `| specific derivation mode: ${outputData.meta.derivationMode}`);
|
|
622
|
+
}
|
|
623
|
+
for (const key of Object.keys(outputData.meta)) {
|
|
624
|
+
report = report.split("{" + key + "}").join(outputData.meta[key]);
|
|
625
|
+
}
|
|
626
|
+
// warning range
|
|
627
|
+
if (!outputData.meta.mode.startsWith("Full")) {
|
|
628
|
+
report = report.replace("{warning_range}", `<div id='warning_range'>The data is based on a partial scan:<br/> ${outputData.meta.mode}</div>`);
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
report = report.replace("{warning_range}", "");
|
|
632
|
+
}
|
|
633
|
+
// summary
|
|
634
|
+
const summary = [];
|
|
635
|
+
for (const e of outputData.summary) {
|
|
636
|
+
if (typeof e.derivationMode !== "undefined") {
|
|
637
|
+
summary.push("<tr><td>" + e.derivationMode + "</td>");
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
summary.push("<tr><td>" + settings_1.configuration.currency.name + "</td>");
|
|
641
|
+
}
|
|
642
|
+
const balance = (0, helpers_1.toAccountUnit)(new bignumber_js_1.default(e.balance));
|
|
643
|
+
if (balance === "0") {
|
|
644
|
+
summary.push('<td class="summary_empty">');
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
summary.push('<td class="summary_non_empty">');
|
|
648
|
+
}
|
|
649
|
+
summary.push(balance + "</td></tr>");
|
|
650
|
+
}
|
|
651
|
+
report = report.replace("{summary}", summary.join(""));
|
|
652
|
+
// addresses
|
|
653
|
+
report = report.replace("{addresses_table}", makeAddressesTable(outputData));
|
|
654
|
+
// UTXOs
|
|
655
|
+
report = report.replace("{utxos_table}", makeUTXOSTable(outputData));
|
|
656
|
+
// transactions
|
|
657
|
+
report = report.replace("{transactions_table}", makeTransactionsTable(outputData));
|
|
658
|
+
// comparisons and diff
|
|
659
|
+
if (typeof outputData.comparisons === "undefined" ||
|
|
660
|
+
outputData.comparisons.length === 0) {
|
|
661
|
+
report = report.replace("{comparisons_table}", "");
|
|
662
|
+
report = report.replace("{diff_table}", "");
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
report = report.replace("{comparisons_table}", makeComparisonsTable(outputData));
|
|
666
|
+
report = report.replace("{diff_table}", makeComparisonsTable(outputData, true));
|
|
667
|
+
}
|
|
668
|
+
filepath += ".html";
|
|
669
|
+
const minifiedReport = html_minifier_1.default.minify(report, {
|
|
670
|
+
removeAttributeQuotes: true,
|
|
671
|
+
minifyCSS: true,
|
|
672
|
+
removeComments: true,
|
|
673
|
+
useShortDoctype: true,
|
|
674
|
+
removeRedundantAttributes: true,
|
|
675
|
+
removeOptionalTags: true,
|
|
676
|
+
removeEmptyAttributes: true,
|
|
677
|
+
removeEmptyElements: false, // do NOT remove empty elements (e.g., empty addresses)
|
|
678
|
+
});
|
|
679
|
+
fs_1.default.writeFileSync(filepath, minifiedReport);
|
|
680
|
+
console.log("\nHTML report saved: ".concat(filepath));
|
|
681
|
+
}
|
|
682
|
+
function saveJSON(outputData, filepath) {
|
|
683
|
+
// stringify -> parse -> stringify to remove `undefined` in final JSON
|
|
684
|
+
const JSONobject = JSON.stringify(JSON.parse(JSON.stringify(outputData)), null, 2);
|
|
685
|
+
if (filepath.toLocaleLowerCase() === "stdout") {
|
|
686
|
+
// display
|
|
687
|
+
console.log(JSONobject);
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
// save file
|
|
691
|
+
filepath += ".json";
|
|
692
|
+
fs_1.default.writeFileSync(filepath, JSONobject);
|
|
693
|
+
console.log("\nJSON export saved: ".concat(filepath));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function save(meta, data, directory) {
|
|
697
|
+
const balanceOnly = meta.balanceOnly;
|
|
698
|
+
// convert amounts into base unit
|
|
699
|
+
const addresses = data.addresses.map((e) => {
|
|
700
|
+
return {
|
|
701
|
+
derivationMode: e.derivationMode,
|
|
702
|
+
derivation: e.getDerivation(),
|
|
703
|
+
address: e.toString(),
|
|
704
|
+
cashAddress: e.asCashAddress(),
|
|
705
|
+
balance: (0, helpers_1.toBaseUnit)(e.balance),
|
|
706
|
+
funded: (0, helpers_1.toBaseUnit)(e.stats.funded),
|
|
707
|
+
spent: (0, helpers_1.toBaseUnit)(e.stats.spent),
|
|
708
|
+
};
|
|
709
|
+
});
|
|
710
|
+
let utxos = [];
|
|
711
|
+
if (settings_1.configuration.currency.utxo_based) {
|
|
712
|
+
utxos = data.addresses
|
|
713
|
+
.filter((a) => a.isUTXO())
|
|
714
|
+
.map((e) => {
|
|
715
|
+
return {
|
|
716
|
+
derivationMode: e.derivationMode,
|
|
717
|
+
derivation: e.getDerivation(),
|
|
718
|
+
address: e.toString(),
|
|
719
|
+
cashAddress: e.asCashAddress(),
|
|
720
|
+
balance: (0, helpers_1.toBaseUnit)(e.balance),
|
|
721
|
+
funded: (0, helpers_1.toBaseUnit)(e.stats.funded),
|
|
722
|
+
spent: (0, helpers_1.toBaseUnit)(e.stats.spent),
|
|
723
|
+
// balance only mode: ignore the following fields
|
|
724
|
+
txid: balanceOnly || typeof e.transactions[0] === "undefined"
|
|
725
|
+
? undefined
|
|
726
|
+
: e.transactions[0].txid,
|
|
727
|
+
height: balanceOnly || typeof e.transactions[0] === "undefined"
|
|
728
|
+
? undefined
|
|
729
|
+
: e.transactions[0].blockHeight,
|
|
730
|
+
time: balanceOnly || typeof e.transactions[0] === "undefined"
|
|
731
|
+
? undefined
|
|
732
|
+
: e.transactions[0].date,
|
|
733
|
+
};
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
const summary = data.summary.map((e) => {
|
|
737
|
+
return Object.assign(Object.assign({}, e), { balance: (0, helpers_1.toBaseUnit)(new bignumber_js_1.default(e.balance)) });
|
|
738
|
+
});
|
|
739
|
+
const transactions = !balanceOnly
|
|
740
|
+
? data.transactions.map((e) => {
|
|
741
|
+
return Object.assign(Object.assign({}, e), { cashAddress: (0, helpers_1.toUnprefixedCashAddress)(e.address), amount: (0, helpers_1.toBaseUnit)(e.amount) });
|
|
742
|
+
})
|
|
743
|
+
: [];
|
|
744
|
+
const comparisons = typeof data.comparisons !== "undefined"
|
|
745
|
+
? data.comparisons.map((e) => {
|
|
746
|
+
return Object.assign(Object.assign({}, e), { imported: typeof e.imported !== "undefined"
|
|
747
|
+
? Object.assign(Object.assign({}, e.imported), { amount: (0, helpers_1.toBaseUnit)(e.imported.amount) }) : undefined, actual: typeof e.actual !== "undefined"
|
|
748
|
+
? Object.assign(Object.assign({}, e.actual), { cashAddress: (0, helpers_1.toUnprefixedCashAddress)(e.actual.address), amount: (0, helpers_1.toBaseUnit)(e.actual.amount) }) : undefined });
|
|
749
|
+
})
|
|
750
|
+
: undefined;
|
|
751
|
+
let diffs = [];
|
|
752
|
+
if (typeof comparisons !== "undefined") {
|
|
753
|
+
diffs =
|
|
754
|
+
comparisons.filter((comparison) => !comparison.status.startsWith("Match") &&
|
|
755
|
+
comparison.status !== "Skipped") || [];
|
|
756
|
+
}
|
|
757
|
+
let warningRange;
|
|
758
|
+
if (!meta.mode.startsWith("Full")) {
|
|
759
|
+
warningRange = `! The data is based on a partial scan: ${meta.mode} !`;
|
|
760
|
+
}
|
|
761
|
+
const providerUrl = settings_1.configuration.externalProviderURL.split("/");
|
|
762
|
+
const providerBaseUrl = providerUrl[0] + "//" + providerUrl[2];
|
|
763
|
+
const outputData = {
|
|
764
|
+
meta: {
|
|
765
|
+
by: "xpub scan <https://github.com/LedgerHQ/xpub-scan>",
|
|
766
|
+
version: meta.version,
|
|
767
|
+
xpub: meta.xpub,
|
|
768
|
+
analysis_date: meta.date,
|
|
769
|
+
currency: `${settings_1.configuration.currency.name} (${(0, helpers_1.getNetworkLabel)()})`,
|
|
770
|
+
provider: settings_1.configuration.providerType,
|
|
771
|
+
provider_url: providerBaseUrl,
|
|
772
|
+
gap_limit: settings_1.configuration.gap_limit,
|
|
773
|
+
unit: "Base unit (i.e., satoshis or equivalent unit)",
|
|
774
|
+
mode: meta.mode,
|
|
775
|
+
preDerivationSize: meta.preDerivationSize,
|
|
776
|
+
derivationMode: meta.derivationMode,
|
|
777
|
+
warningRange,
|
|
778
|
+
balanceOnly,
|
|
779
|
+
},
|
|
780
|
+
addresses,
|
|
781
|
+
utxos,
|
|
782
|
+
summary,
|
|
783
|
+
transactions: balanceOnly ? undefined : transactions,
|
|
784
|
+
comparisons,
|
|
785
|
+
diffs: balanceOnly ? undefined : diffs, // ignore in balance only mode
|
|
786
|
+
};
|
|
787
|
+
// if no filepath/filename specify -> set to current directory
|
|
788
|
+
if (directory === "") {
|
|
789
|
+
directory = __dirname;
|
|
790
|
+
}
|
|
791
|
+
let filepath = directory;
|
|
792
|
+
if (filepath.toLocaleLowerCase() !== "stdout") {
|
|
793
|
+
filepath += `/${meta.xpub}`;
|
|
794
|
+
saveHTML(outputData, filepath); // do not save HTML if stdout
|
|
795
|
+
}
|
|
796
|
+
saveJSON(outputData, filepath);
|
|
797
|
+
// add empty line to separate this text block from potential check results
|
|
798
|
+
console.log();
|
|
799
|
+
}
|
|
800
|
+
exports.save = save;
|