@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,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 };
|