@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.
Files changed (83) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/CONTRIBUTING.md +130 -0
  3. package/LICENSE +23 -0
  4. package/README.md +215 -0
  5. package/__tests__/checkAddresses/checkBitcoinAddressesNegative.test.ts +75 -0
  6. package/__tests__/checkAddresses/checkBitcoinAddressesPositive.test.ts +87 -0
  7. package/__tests__/checkAddresses/checkDogeAddressesPositive.test.ts +43 -0
  8. package/__tests__/checkAddresses/checkLitecoinAddressesNegative.test.ts +75 -0
  9. package/__tests__/checkAddresses/checkLitecoinAddressesPositive.test.ts +87 -0
  10. package/__tests__/checkModels/address.test.ts +183 -0
  11. package/__tests__/checkModels/fakeRawTransactions.json +182 -0
  12. package/__tests__/deriveAddresses/deriveBitcoinAddresses.test.ts +207 -0
  13. package/__tests__/deriveAddresses/deriveBitcoinCashAddresses.test copy.ts +79 -0
  14. package/__tests__/deriveAddresses/deriveDogecoinAddresses.test.ts +43 -0
  15. package/__tests__/deriveAddresses/deriveEthereumAddresses.test.ts +26 -0
  16. package/__tests__/deriveAddresses/deriveLitecoinAddresses.test.ts +110 -0
  17. package/__tests__/helpers.test.ts +274 -0
  18. package/__tests__/test-utils.ts +3 -0
  19. package/babel.config.js +6 -0
  20. package/jest.config.ts +5 -0
  21. package/ledgerhq-xpub-scan-1.0.4.tgz +0 -0
  22. package/lib/actions/checkAddress.d.ts +29 -0
  23. package/lib/actions/checkAddress.js +122 -0
  24. package/lib/actions/checkBalance.d.ts +20 -0
  25. package/lib/actions/checkBalance.js +300 -0
  26. package/lib/actions/deriveAddresses.d.ts +17 -0
  27. package/lib/actions/deriveAddresses.js +239 -0
  28. package/lib/actions/processTransactions.d.ts +29 -0
  29. package/lib/actions/processTransactions.js +289 -0
  30. package/lib/actions/saveAnalysis.d.ts +2 -0
  31. package/lib/actions/saveAnalysis.js +800 -0
  32. package/lib/actions/scanner.d.ts +15 -0
  33. package/lib/actions/scanner.js +152 -0
  34. package/lib/api/customProvider.d.ts +19 -0
  35. package/lib/api/customProvider.js +434 -0
  36. package/lib/api/defaultProvider.d.ts +23 -0
  37. package/lib/api/defaultProvider.js +275 -0
  38. package/lib/comparison/compareOperations.d.ts +13 -0
  39. package/lib/comparison/compareOperations.js +500 -0
  40. package/lib/comparison/diffs.d.ts +18 -0
  41. package/lib/comparison/diffs.js +70 -0
  42. package/lib/configuration/currencies.d.ts +55 -0
  43. package/lib/configuration/currencies.js +72 -0
  44. package/lib/configuration/settings.d.ts +51 -0
  45. package/lib/configuration/settings.js +113 -0
  46. package/lib/display.d.ts +12 -0
  47. package/lib/display.js +251 -0
  48. package/lib/helpers.d.ts +27 -0
  49. package/lib/helpers.js +255 -0
  50. package/lib/input/args.d.ts +6 -0
  51. package/lib/input/args.js +129 -0
  52. package/lib/input/check.d.ts +6 -0
  53. package/lib/input/check.js +217 -0
  54. package/lib/input/importOperations.d.ts +11 -0
  55. package/lib/input/importOperations.js +406 -0
  56. package/lib/models/address.d.ts +40 -0
  57. package/lib/models/address.js +101 -0
  58. package/lib/models/comparison.d.ts +8 -0
  59. package/lib/models/comparison.js +6 -0
  60. package/lib/models/currency.d.ts +11 -0
  61. package/lib/models/currency.js +6 -0
  62. package/lib/models/operation.d.ts +33 -0
  63. package/lib/models/operation.js +80 -0
  64. package/lib/models/ownAddresses.d.ts +11 -0
  65. package/lib/models/ownAddresses.js +31 -0
  66. package/lib/models/scanLimits.d.ts +7 -0
  67. package/lib/models/scanLimits.js +6 -0
  68. package/lib/models/stats.d.ts +7 -0
  69. package/lib/models/stats.js +6 -0
  70. package/lib/models/transaction.d.ts +10 -0
  71. package/lib/models/transaction.js +13 -0
  72. package/lib/scan.d.ts +2 -0
  73. package/lib/scan.js +31 -0
  74. package/lib/templates/logos.base64.d.ts +2 -0
  75. package/lib/templates/logos.base64.js +9 -0
  76. package/lib/templates/report.html.d.ts +1 -0
  77. package/lib/templates/report.html.js +393 -0
  78. package/lib/tsconfig.tsbuildinfo +1 -0
  79. package/lib/types.d.ts +55 -0
  80. package/lib/types.js +2 -0
  81. package/npm-shrinkwrap.json +12323 -0
  82. package/package.json +81 -0
  83. package/sonar-project.properties +15 -0
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ // Here, the raw data is fetched from default (i.e., free of charge) providers:
3
+ // - balance,
4
+ // - total spent and received, and
5
+ // - operations
6
+ // per address
7
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
8
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
9
+ return new (P || (P = Promise))(function (resolve, reject) {
10
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
11
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
12
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
13
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
14
+ });
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.getAccountBasedTransactions = exports.getBitcoinCashTransactions = exports.getTransactions = exports.getStats = void 0;
21
+ const helpers_1 = require("../helpers");
22
+ const settings_1 = require("../configuration/settings");
23
+ const transaction_1 = require("../models/transaction");
24
+ const operation_1 = require("../models/operation");
25
+ const currencies_1 = require("../configuration/currencies");
26
+ const bignumber_js_1 = __importDefault(require("bignumber.js"));
27
+ const date_fns_1 = require("date-fns");
28
+ /**
29
+ * fetch the structured basic stats related to an address
30
+ * its balance, funded and spend sums and counts
31
+ * @param address the address being analyzed
32
+ */
33
+ function getStats(address) {
34
+ return __awaiter(this, void 0, void 0, function* () {
35
+ // important: coin name is required to be upper case for default provider
36
+ let coin = settings_1.configuration.currency.symbol.toUpperCase();
37
+ if (coin === currencies_1.currencies.bch.symbol) {
38
+ return getBchStats(address);
39
+ }
40
+ if (coin === currencies_1.currencies.eth.symbol) {
41
+ return getAccountBasedStats(address);
42
+ }
43
+ let url = settings_1.configuration.externalProviderURL.replace("{address}", address.toString());
44
+ if (coin === currencies_1.currencies.btc.symbol.toUpperCase()) {
45
+ url = url.replace("{network}", settings_1.configuration.testnet ? "testnet" : "");
46
+ }
47
+ const res = yield (0, helpers_1.getJSON)(url);
48
+ // TODO: check potential errors here (API returning invalid data...)
49
+ const fundedSum = res.chain_stats.funded_txo_sum;
50
+ const spentSum = res.chain_stats.spent_txo_sum;
51
+ const balance = fundedSum - spentSum;
52
+ address.setStats(res.chain_stats.tx_count, (0, helpers_1.toAccountUnit)((0, bignumber_js_1.default)(fundedSum)), (0, helpers_1.toAccountUnit)((0, bignumber_js_1.default)(spentSum)));
53
+ address.setBalance((0, helpers_1.toAccountUnit)((0, bignumber_js_1.default)(balance)));
54
+ if (res.chain_stats.tx_count > 0) {
55
+ // get transactions per address
56
+ address.setRawTransactions(yield (0, helpers_1.getJSON)(url + "/txs"));
57
+ }
58
+ });
59
+ }
60
+ exports.getStats = getStats;
61
+ /**
62
+ * get all structured transactions related to an address
63
+ * @param address the address being analyzed
64
+ */
65
+ function getTransactions(address) {
66
+ // Because the general default API is not compatible with Bitcoin Cash,
67
+ // these transactions have to be specifically handled
68
+ if (settings_1.configuration.currency.symbol === currencies_1.currencies.bch.symbol) {
69
+ return getBitcoinCashTransactions(address);
70
+ }
71
+ if (settings_1.configuration.currency.symbol === currencies_1.currencies.eth.symbol) {
72
+ return getAccountBasedTransactions(address);
73
+ }
74
+ // 1. get raw transactions
75
+ const rawTransactions = address.getRawTransactions();
76
+ // 2. parse raw transactions
77
+ const transactions = [];
78
+ rawTransactions.forEach((tx) => {
79
+ const ins = [];
80
+ const outs = [];
81
+ tx.vout.forEach((txout) => {
82
+ // the address currently being analyzed is a — recipient —
83
+ if (txout.scriptpubkey_address.toLowerCase() ===
84
+ address.toString().toLowerCase()) {
85
+ tx.vin.forEach((txin) => {
86
+ const op = new operation_1.Operation((0, date_fns_1.format)(new Date(tx.status.block_time * 1000), "yyyy-MM-dd HH:mm:ss"), (0, helpers_1.toAccountUnit)((0, bignumber_js_1.default)(txout.value)));
87
+ op.setTxid(tx.txid);
88
+ op.setAddress(txin.prevout.scriptpubkey_address);
89
+ op.setOperationType("Received");
90
+ ins.push(op);
91
+ });
92
+ }
93
+ // the address currently being analyzed is a — sender —
94
+ // (it is part of at least one vin's prevout)
95
+ if (tx.vin.some((t) => t.prevout.scriptpubkey_address.toLowerCase() ===
96
+ address.toString().toLowerCase())) {
97
+ const op = new operation_1.Operation((0, date_fns_1.format)(new Date(tx.status.block_time * 1000), "yyyy-MM-dd HH:mm:ss"), (0, helpers_1.toAccountUnit)((0, bignumber_js_1.default)(txout.value)));
98
+ op.setTxid(tx.txid);
99
+ op.setAddress(txout.scriptpubkey_address);
100
+ op.setOperationType("Sent");
101
+ outs.push(op);
102
+ }
103
+ });
104
+ transactions.push(new transaction_1.Transaction(tx.status.block_height, (0, date_fns_1.format)(new Date(tx.status.block_time * 1000), "yyyy-MM-dd HH:mm:ss"), // unix time to readable format
105
+ tx.txid, ins, outs));
106
+ });
107
+ address.setTransactions(transactions);
108
+ }
109
+ exports.getTransactions = getTransactions;
110
+ /**
111
+ * fetch the structured basic stats related to a Bitcoin Cash address
112
+ * its balance, funded and spend sums and counts
113
+ * @param address the address being analyzed
114
+ */
115
+ function getBchStats(address) {
116
+ return __awaiter(this, void 0, void 0, function* () {
117
+ const urlStats = settings_1.configuration.externalProviderURL
118
+ .replace("{type}", "details")
119
+ .replace("{address}", address.asCashAddress());
120
+ const res = yield (0, helpers_1.getJSON)(urlStats);
121
+ // TODO: check potential errors here (API returning invalid data...)
122
+ const fundedSum = res.totalReceived;
123
+ const balance = res.balance;
124
+ const spentSum = res.totalSent;
125
+ address.setStats(res.txApperances, fundedSum, spentSum);
126
+ address.setBalance(balance);
127
+ const urlTxs = settings_1.configuration.externalProviderURL
128
+ .replace("{type}", "transactions")
129
+ .replace("{address}", address.asCashAddress());
130
+ const payloads = [];
131
+ let totalPages = 1;
132
+ for (let i = 0; i < totalPages; i++) {
133
+ const response = yield (0, helpers_1.getJSON)(urlTxs.concat("?page=").concat(i.toString()));
134
+ totalPages = response.pagesTotal;
135
+ payloads.push(response.txs);
136
+ }
137
+ // flatten the payloads
138
+ const rawTransactions = [].concat(...payloads);
139
+ address.setRawTransactions(rawTransactions);
140
+ });
141
+ }
142
+ /**
143
+ * get all structured transactions related to a Bitcoin Cash address
144
+ * @param address the address being analyzed
145
+ */
146
+ function getBitcoinCashTransactions(address) {
147
+ // 1. get raw transactions
148
+ const rawTransactions = address.getRawTransactions();
149
+ // 2. parse raw transactions
150
+ const transactions = [];
151
+ rawTransactions.forEach((tx) => {
152
+ const ins = [];
153
+ const outs = [];
154
+ let amount = new bignumber_js_1.default(0);
155
+ let processIn = false;
156
+ let processOut = false;
157
+ // 1. Detect operation type
158
+ for (const txin of tx.vin) {
159
+ if (txin.addr.includes(address.toString())) {
160
+ processOut = true;
161
+ break;
162
+ }
163
+ }
164
+ for (const txout of tx.vout) {
165
+ if (typeof txout.scriptPubKey.addresses === "undefined") {
166
+ continue;
167
+ }
168
+ for (const outAddress of txout.scriptPubKey.addresses) {
169
+ if (outAddress.includes(address.toString())) {
170
+ // when IN op, amount corresponds to txout
171
+ amount = new bignumber_js_1.default(txout.value);
172
+ processIn = true;
173
+ break;
174
+ }
175
+ }
176
+ }
177
+ if (processIn) {
178
+ tx.vin.forEach((txin) => {
179
+ const op = new operation_1.Operation(String(tx.time), amount);
180
+ op.setAddress(txin.addr);
181
+ op.setTxid(tx.txid);
182
+ op.setOperationType("Received");
183
+ ins.push(op);
184
+ });
185
+ }
186
+ if (processOut) {
187
+ tx.vout.forEach((txout) => {
188
+ if (parseFloat(txout.value) === 0) {
189
+ return;
190
+ }
191
+ const op = new operation_1.Operation(String(tx.time), txout.value);
192
+ op.setAddress(txout.scriptPubKey.addresses[0]);
193
+ op.setTxid(tx.txid);
194
+ op.setOperationType("Sent");
195
+ outs.push(op);
196
+ });
197
+ }
198
+ transactions.push(new transaction_1.Transaction(tx.blockheight, (0, date_fns_1.format)(new Date(tx.time * 1000), "yyyy-MM-dd HH:mm:ss"), // unix time to readable format
199
+ tx.txid, ins, outs));
200
+ });
201
+ address.setTransactions(transactions);
202
+ }
203
+ exports.getBitcoinCashTransactions = getBitcoinCashTransactions;
204
+ /**
205
+ * fetch the structured basic stats related to an Ethereum address
206
+ * its balance, funded and spend sums and counts
207
+ * @param address the address being analyzed
208
+ */
209
+ function getAccountBasedStats(address) {
210
+ return __awaiter(this, void 0, void 0, function* () {
211
+ const url = settings_1.configuration.externalProviderURL
212
+ .replace("{type}", "addrs")
213
+ .replace("{item}", address.toString());
214
+ const res = yield (0, helpers_1.getJSON)(url);
215
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
216
+ const removeScientificNotation = (value) => value.toLocaleString("fullwide", { useGrouping: false });
217
+ const fundedSum = (0, helpers_1.toAccountUnit)(new bignumber_js_1.default(removeScientificNotation(res.total_received)));
218
+ const balance = (0, helpers_1.toAccountUnit)(new bignumber_js_1.default(removeScientificNotation(res.balance)));
219
+ const spentSum = (0, helpers_1.toAccountUnit)(new bignumber_js_1.default(removeScientificNotation(res.total_sent)));
220
+ address.setStats(res.n_tx, fundedSum, spentSum);
221
+ address.setBalance(balance);
222
+ // additional request to get the fees
223
+ for (const txref of res.txrefs) {
224
+ // if not a Sent transaction, skip
225
+ if (txref.tx_output_n !== -1) {
226
+ continue;
227
+ }
228
+ const urlTxs = settings_1.configuration.externalProviderURL
229
+ .replace("{type}", "txs")
230
+ .replace("{item}", txref.tx_hash);
231
+ const resTxs = yield (0, helpers_1.getJSON)(urlTxs);
232
+ txref.total = resTxs.total;
233
+ }
234
+ address.setRawTransactions(res.txrefs);
235
+ });
236
+ }
237
+ /**
238
+ * get all structured transactions related to an account-based address (generally Ethereum)
239
+ * @param address the address being analyzed
240
+ */
241
+ function getAccountBasedTransactions(address) {
242
+ // 1. get raw transactions
243
+ const rawTransactions = address.getRawTransactions();
244
+ // 2. parse raw transactions
245
+ const transactions = [];
246
+ rawTransactions.forEach((tx) => {
247
+ const ins = [];
248
+ const outs = [];
249
+ const isRecipient = tx.tx_input_n === -1;
250
+ const isSender = tx.tx_output_n === -1;
251
+ let amount = tx.value;
252
+ if (isSender) {
253
+ amount = tx.total;
254
+ }
255
+ amount /= settings_1.configuration.currency.precision;
256
+ const op = new operation_1.Operation(String(tx.confirmed), amount.toFixed(settings_1.ETH_FIXED_PRECISION));
257
+ const txHash = "0x".concat(tx.tx_hash);
258
+ op.setAddress(address.toString());
259
+ op.setTxid(txHash);
260
+ op.setBlockNumber(tx.block_height);
261
+ if (isRecipient) {
262
+ op.setOperationType("Received");
263
+ address.addFundedOperation(op);
264
+ ins.push(op);
265
+ }
266
+ else if (isSender) {
267
+ op.setOperationType("Sent");
268
+ address.addSentOperation(op);
269
+ outs.push(op);
270
+ }
271
+ transactions.push(new transaction_1.Transaction(tx.block_height, (0, date_fns_1.format)(new Date(tx.confirmed), "yyyy-MM-dd'T'HH:mm:ss'Z'"), txHash, ins, outs));
272
+ });
273
+ address.setTransactions(transactions);
274
+ }
275
+ exports.getAccountBasedTransactions = getAccountBasedTransactions;
@@ -0,0 +1,13 @@
1
+ import { Address } from "../models/address";
2
+ import { Operation } from "../models/operation";
3
+ import { Comparison } from "../models/comparison";
4
+ /**
5
+ * Compare the imported operations with the actual ones
6
+ * @param importedOperations operations from the product
7
+ * @param actualOperations operations from the provider (source of truth)
8
+ * @param actualAddresses actual addresses
9
+ * @param partialComparison (optional) partial comparison
10
+ * @returns list of comparisons
11
+ */
12
+ declare const checkImportedOperations: (importedOperations: Array<Operation>, actualOperations: Array<Operation>, actualAddresses: Array<Address>, partialComparison?: boolean) => Array<Comparison>;
13
+ export { checkImportedOperations };