@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,122 @@
|
|
|
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.run = exports._private = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const deriveAddresses_1 = require("./deriveAddresses");
|
|
9
|
+
const settings_1 = require("../configuration/settings");
|
|
10
|
+
const helpers_1 = require("../helpers");
|
|
11
|
+
function showError(message, derived, provided) {
|
|
12
|
+
let errorMessage = chalk_1.default.red("[Comparison error] ".concat(message));
|
|
13
|
+
if (typeof derived !== "undefined") {
|
|
14
|
+
const comparison = "\nProvided address:\t"
|
|
15
|
+
.concat(String(provided))
|
|
16
|
+
.concat("\nFirst derived address: ")
|
|
17
|
+
.concat(String(derived));
|
|
18
|
+
errorMessage = errorMessage.concat(chalk_1.default.redBright(comparison));
|
|
19
|
+
}
|
|
20
|
+
console.log(errorMessage);
|
|
21
|
+
}
|
|
22
|
+
// TODO?: export in a dedicated module (display.ts)?
|
|
23
|
+
function showComparisonResult(xpub, address, result) {
|
|
24
|
+
console.log("\nXpub:", chalk_1.default.whiteBright(xpub));
|
|
25
|
+
console.log("Provided address:", chalk_1.default.whiteBright(address));
|
|
26
|
+
if (Object.keys(result).length === 0) {
|
|
27
|
+
// no match
|
|
28
|
+
console.log(chalk_1.default.redBright("The address does not seem to have been derived from this xpub!"));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const derivationPath = "m/"
|
|
32
|
+
.concat(String(result.account))
|
|
33
|
+
.concat("/")
|
|
34
|
+
.concat(String(result.index));
|
|
35
|
+
if (typeof result.partial === "undefined") {
|
|
36
|
+
// full match
|
|
37
|
+
console.log(chalk_1.default.greenBright("The address has been derived from this xpub using derivation path ".concat(chalk_1.default.bold(derivationPath))));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// partial match
|
|
41
|
+
console.log("Derived address: ", chalk_1.default.whiteBright(result.partial));
|
|
42
|
+
console.log(chalk_1.default.blueBright("There is a partial match between the provided address and the one derived using derivation path ".concat(chalk_1.default.bold(derivationPath))));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// partial match, using '?' as wildcards
|
|
47
|
+
function partialMatch(derived, provided) {
|
|
48
|
+
for (let i = 0; i < derived.length; ++i) {
|
|
49
|
+
if (provided[i] === "?") {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (provided[i] !== derived[i]) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* identify whether an address provided by the user belongs or not to the xpub
|
|
60
|
+
* @param xpub the xpub from which the address may have been derived
|
|
61
|
+
* @param providedAddress the address provided by the user
|
|
62
|
+
* @param range the range of the search (i.e., accounts and indices ranges)
|
|
63
|
+
* @param searchType indication of the type of search (quick/deep)
|
|
64
|
+
* @returns a match or an empty object (i.e., non-match)
|
|
65
|
+
*/
|
|
66
|
+
function search(xpub, providedAddress, range, searchType) {
|
|
67
|
+
const derivationMode = (0, deriveAddresses_1.getDerivationMode)(providedAddress);
|
|
68
|
+
const partialSearch = providedAddress.includes("?");
|
|
69
|
+
(0, helpers_1.setNetwork)(xpub);
|
|
70
|
+
for (let account = range.account.min; account < range.account.max; ++account) {
|
|
71
|
+
for (let index = range.index.min; index < range.index.max; ++index) {
|
|
72
|
+
const derivedAddress = (0, deriveAddresses_1.deriveAddress)(derivationMode, xpub, account, index);
|
|
73
|
+
// m/{account}/{index}
|
|
74
|
+
const derivationPath = "m/"
|
|
75
|
+
.concat(account.toFixed())
|
|
76
|
+
.concat("/")
|
|
77
|
+
.concat(index.toFixed());
|
|
78
|
+
// quick|deep search {derivation path} {derived address}
|
|
79
|
+
const status = searchType
|
|
80
|
+
.padEnd(18, " ")
|
|
81
|
+
.concat(derivationPath.padEnd(14, " "))
|
|
82
|
+
.concat(derivedAddress);
|
|
83
|
+
const derived = derivedAddress.toUpperCase();
|
|
84
|
+
const provided = providedAddress.toUpperCase();
|
|
85
|
+
// perfect match (case insensitive)
|
|
86
|
+
if (derived === provided) {
|
|
87
|
+
console.log(chalk_1.default.green(status));
|
|
88
|
+
return {
|
|
89
|
+
account,
|
|
90
|
+
index,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// partial match (if enabled)
|
|
94
|
+
if (partialSearch && partialMatch(derived, provided)) {
|
|
95
|
+
console.log(chalk_1.default.blueBright(status));
|
|
96
|
+
return {
|
|
97
|
+
partial: derivedAddress,
|
|
98
|
+
account,
|
|
99
|
+
index,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
console.log(status);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
function run(xpub, providedAddress) {
|
|
108
|
+
if (typeof settings_1.DERIVATION_SCOPE === "undefined") {
|
|
109
|
+
showError("DERIVATION_SCOPE setting is not defined");
|
|
110
|
+
}
|
|
111
|
+
const quickSearchRange = settings_1.DERIVATION_SCOPE.quick_search;
|
|
112
|
+
let result = search(xpub, providedAddress, quickSearchRange, "quick search");
|
|
113
|
+
if (Object.keys(result).length === 0) {
|
|
114
|
+
const deepSearchRange = settings_1.DERIVATION_SCOPE.deep_search;
|
|
115
|
+
result = search(xpub, providedAddress, deepSearchRange, "deep search");
|
|
116
|
+
}
|
|
117
|
+
showComparisonResult(xpub, providedAddress, result);
|
|
118
|
+
}
|
|
119
|
+
exports.run = run;
|
|
120
|
+
exports._private = {
|
|
121
|
+
search,
|
|
122
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Address } from "../models/address";
|
|
2
|
+
import { ScanLimits } from "../models/scanLimits";
|
|
3
|
+
import { DerivationMode } from "../configuration/currencies";
|
|
4
|
+
import BigNumber from "bignumber.js";
|
|
5
|
+
/**
|
|
6
|
+
* Run the analysis
|
|
7
|
+
* @param itemToScan ITEM TO SCAN can be an xpub or an address
|
|
8
|
+
* (that is why it is named `itemToScan` instead of xpub)
|
|
9
|
+
* @param balanceOnly option to fetch the balance only—not the transactions
|
|
10
|
+
* @param scanLimits option to limit the scan to a certain account and indices range
|
|
11
|
+
* @returns a list of active addresses and a summary (total balance per derivation mode)
|
|
12
|
+
*/
|
|
13
|
+
declare function run(itemToScan: string, balanceOnly: boolean, scanLimits?: ScanLimits): Promise<{
|
|
14
|
+
addresses: Address[];
|
|
15
|
+
summary: {
|
|
16
|
+
derivationMode: DerivationMode;
|
|
17
|
+
balance: BigNumber;
|
|
18
|
+
}[];
|
|
19
|
+
}>;
|
|
20
|
+
export { run };
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.run = void 0;
|
|
39
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
40
|
+
const display = __importStar(require("../display"));
|
|
41
|
+
const address_1 = require("../models/address");
|
|
42
|
+
const ownAddresses_1 = require("../models/ownAddresses");
|
|
43
|
+
const settings_1 = require("../configuration/settings");
|
|
44
|
+
const currencies_1 = require("../configuration/currencies");
|
|
45
|
+
const processTransactions_1 = require("./processTransactions");
|
|
46
|
+
const deriveAddresses_1 = require("../actions/deriveAddresses");
|
|
47
|
+
const bignumber_js_1 = __importDefault(require("bignumber.js"));
|
|
48
|
+
/**
|
|
49
|
+
* derive and scan all active addresses _for a given derivation mode_
|
|
50
|
+
* note: an ACTIVE ADDRESS is an address with > 0 transactions
|
|
51
|
+
* @param derivationMode a derivation mode (enum)
|
|
52
|
+
* @param xpub the xpub to scan
|
|
53
|
+
* @param balanceOnly option to fetch the balance only—not the transactions
|
|
54
|
+
* @param scanLimits option to limit the scan to a certain account and indices range
|
|
55
|
+
* @returns an object containing the total balance for the derivation mode as well as
|
|
56
|
+
* a list of active addresses associated with it
|
|
57
|
+
*/
|
|
58
|
+
function deriveAndScanAddressesByDerivationMode(derivationMode, xpub, balanceOnly, scanLimits) {
|
|
59
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
60
|
+
display.logStatus("Scanning ".concat(chalk_1.default.bold(derivationMode)).concat(" addresses..."));
|
|
61
|
+
const ownAddresses = new ownAddresses_1.OwnAddresses();
|
|
62
|
+
let totalBalance = new bignumber_js_1.default(0);
|
|
63
|
+
let txCounter = 0;
|
|
64
|
+
const addresses = [];
|
|
65
|
+
let accountSpan = undefined;
|
|
66
|
+
let indexFromSpan = undefined;
|
|
67
|
+
let indexToSpan = undefined;
|
|
68
|
+
let preDerivationSize = undefined;
|
|
69
|
+
if (scanLimits) {
|
|
70
|
+
accountSpan = scanLimits.account;
|
|
71
|
+
indexFromSpan = scanLimits.indexFrom;
|
|
72
|
+
indexToSpan = scanLimits.indexTo;
|
|
73
|
+
preDerivationSize = scanLimits.preDerivationSize;
|
|
74
|
+
// crucial step in limited scan mode: precompute the addresses belonging
|
|
75
|
+
// to the same xpub in order to perform transaction analysis further
|
|
76
|
+
// down the flow. `preDerivationSize` define the number of addresses to pre-derive.
|
|
77
|
+
for (let a = 0; a < 2; a++) {
|
|
78
|
+
for (let i = 0; i < preDerivationSize; i++) {
|
|
79
|
+
ownAddresses.addAddress(new address_1.Address(xpub, derivationMode, a, i));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// loop over derivation path accounts: `m/{account}/{index}`
|
|
84
|
+
// note: we limit ourselves to accounts 0 and 1
|
|
85
|
+
// but the scope could be extended further if needed
|
|
86
|
+
for (let account = 0; account < 2; ++account) {
|
|
87
|
+
// if limited scan mode is enabled and the current account is outside the scope, skip it
|
|
88
|
+
if (typeof accountSpan != "undefined" && account !== accountSpan) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
// account 0 == external addresses
|
|
92
|
+
// account 1 == internal (aka change) addresses
|
|
93
|
+
const typeAccount = account === 1 ? "internal" : "external";
|
|
94
|
+
display.logStatus("- scanning " + chalk_1.default.italic(typeAccount) + " addresses -");
|
|
95
|
+
txCounter = 0;
|
|
96
|
+
for (let index = 0 /* scan all active indices */;; ++index) {
|
|
97
|
+
// if limited scan mode is enabled and the current index is _below_ the range, skip it
|
|
98
|
+
// ______(current index)_______[ LIMITED SCAN RANGE ]____________________________
|
|
99
|
+
if (typeof indexFromSpan !== "undefined" && index < indexFromSpan) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// if limited scan mode is enabled and the current index is _beyond_ the range, stop the scan
|
|
103
|
+
// ____________________________[ LIMITED SCAN RANGE ]______(current index)_______
|
|
104
|
+
if (typeof indexToSpan !== "undefined" && index > indexToSpan) {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
// get address derived according to:
|
|
108
|
+
// - its xpub (by definition),
|
|
109
|
+
// - the current derivation mode (legacy, SegWit, etc.)
|
|
110
|
+
// - the derivation path characteristics: `m/{account:0|1}/{index:0|∞}`
|
|
111
|
+
const address = new address_1.Address(xpub, derivationMode, account, index);
|
|
112
|
+
display.updateAddressDetails(address);
|
|
113
|
+
const status = txCounter === 0 ? "analyzing" : "probing address gap";
|
|
114
|
+
if (!settings_1.configuration.silent && !settings_1.configuration.quiet) {
|
|
115
|
+
process.stdout.write(chalk_1.default.yellow(status + "..."));
|
|
116
|
+
}
|
|
117
|
+
// fetch (from external provider) the basic data regarding the address
|
|
118
|
+
// (balance, transactions count, etc.)
|
|
119
|
+
yield (0, processTransactions_1.getStats)(address, balanceOnly);
|
|
120
|
+
const addressStats = address.getStats();
|
|
121
|
+
// here, evaluate if the address needs further analysis
|
|
122
|
+
if (addressStats.txsCount.isZero()) {
|
|
123
|
+
// no transaction associated with the address:
|
|
124
|
+
// perform address gap probing
|
|
125
|
+
// GAP PROBE: check whether an address is active in a certain range
|
|
126
|
+
//
|
|
127
|
+
// for instance:
|
|
128
|
+
// ┌┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐
|
|
129
|
+
// ┊ m/0/0 — active (10 transactions) ┊
|
|
130
|
+
// ┊ m/0/1 — active (2 transactions) ┊
|
|
131
|
+
// ┊ ┐ ┊
|
|
132
|
+
// ┊ m/0/2 — inactive (0 transaction) │ ┊
|
|
133
|
+
// ┊ m/0/3 — inactive (0 transaction) │ GAP ┊
|
|
134
|
+
// ┊ m/0/4 — inactive (0 transaction) │ ┊
|
|
135
|
+
// ┊ ┘ ┊
|
|
136
|
+
// ┊ m/0/5 — active (4 transactions) ┊
|
|
137
|
+
// └┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘
|
|
138
|
+
//
|
|
139
|
+
// in this example, the gap probing allows to detect that
|
|
140
|
+
// `m/0/5` is an active address
|
|
141
|
+
//
|
|
142
|
+
// note: the scope of the gap probing is 20 addresses by
|
|
143
|
+
// default (check `DEFAULT_GAP_LIMIT`) but can be configured
|
|
144
|
+
// using the `GAP_LIMIT` environment variable
|
|
145
|
+
txCounter++;
|
|
146
|
+
display.transientLine( /* delete address as it is not an active one */);
|
|
147
|
+
if (account === 1 || txCounter >= Number(settings_1.configuration.gap_limit)) {
|
|
148
|
+
// all active addresses have been scanned and the gap limit reached:
|
|
149
|
+
// stop the scan for this specific derivation mode
|
|
150
|
+
display.transientLine( /* delete last probing info */);
|
|
151
|
+
display.logStatus("- " + chalk_1.default.italic(typeAccount) + " addresses scanned -");
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
txCounter = 0;
|
|
158
|
+
}
|
|
159
|
+
// convert address balance into satoshis (or equivalent unit)
|
|
160
|
+
// in order to avoid issue with floats addition
|
|
161
|
+
totalBalance = totalBalance.plus(address.getBalance());
|
|
162
|
+
display.updateAddressDetails(address);
|
|
163
|
+
// important step: add the active address to the
|
|
164
|
+
// list of own addresses in order to perform
|
|
165
|
+
// transaction analysis further down the flow
|
|
166
|
+
ownAddresses.addAddress(address);
|
|
167
|
+
addresses.push(address);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// process transactions
|
|
171
|
+
display.transientLine(chalk_1.default.yellowBright("Processing transactions..."));
|
|
172
|
+
if (!balanceOnly) {
|
|
173
|
+
addresses.forEach((address) => {
|
|
174
|
+
(0, processTransactions_1.getTransactions)(address, ownAddresses);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
display.transientLine( /* delete address */);
|
|
178
|
+
display.logStatus(derivationMode.concat(" addresses scanned\n"));
|
|
179
|
+
return {
|
|
180
|
+
balance: totalBalance,
|
|
181
|
+
addresses,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Scan an address (account-based mode)
|
|
187
|
+
* @param itemToScan xpub from which an address will be derived, or directly an address
|
|
188
|
+
* @param balanceOnly option to fetch the balance only—not the transactions
|
|
189
|
+
* @returns an array containing this address and a summary that includes the total balance
|
|
190
|
+
* (TODO: simplify in order to normalize with xpub analysis)
|
|
191
|
+
*/
|
|
192
|
+
function addressAnalysis(itemToScan, balanceOnly) {
|
|
193
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
194
|
+
if (!settings_1.configuration.silent) {
|
|
195
|
+
console.log(chalk_1.default.bold("\nScanned address\n"));
|
|
196
|
+
}
|
|
197
|
+
let address;
|
|
198
|
+
if (itemToScan.substring(0, 4).toLocaleLowerCase() === "xpub") {
|
|
199
|
+
// the item to scan appears to be an xpub: derive just the first address
|
|
200
|
+
const derivationMode = settings_1.configuration.currency.derivationModes[0] || currencies_1.DerivationMode.UNKNOWN;
|
|
201
|
+
address = new address_1.Address((0, deriveAddresses_1.deriveAddress)(derivationMode, itemToScan));
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// the item to scan is directly an address: construct it
|
|
205
|
+
address = new address_1.Address(itemToScan);
|
|
206
|
+
}
|
|
207
|
+
if (typeof address === "undefined") {
|
|
208
|
+
throw new Error(`Address cannot be instantiated from "${itemToScan}"`);
|
|
209
|
+
}
|
|
210
|
+
display.updateAddressDetails(address);
|
|
211
|
+
// fetch (from external provider) the basic data regarding the address
|
|
212
|
+
// (balance, transactions count, etc.)
|
|
213
|
+
yield (0, processTransactions_1.getStats)(address, balanceOnly);
|
|
214
|
+
if (!balanceOnly) {
|
|
215
|
+
// also (if applicable), fetch (from the external provider) the raw transactions
|
|
216
|
+
// associated with the address
|
|
217
|
+
(0, processTransactions_1.getTransactions)(address);
|
|
218
|
+
}
|
|
219
|
+
display.updateAddressDetails(address);
|
|
220
|
+
const summary = [
|
|
221
|
+
{
|
|
222
|
+
derivationMode: currencies_1.DerivationMode.ETHEREUM,
|
|
223
|
+
balance: new bignumber_js_1.default(address.getBalance()),
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
return {
|
|
227
|
+
addresses: [address],
|
|
228
|
+
summary,
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Run the analysis
|
|
234
|
+
* @param itemToScan ITEM TO SCAN can be an xpub or an address
|
|
235
|
+
* (that is why it is named `itemToScan` instead of xpub)
|
|
236
|
+
* @param balanceOnly option to fetch the balance only—not the transactions
|
|
237
|
+
* @param scanLimits option to limit the scan to a certain account and indices range
|
|
238
|
+
* @returns a list of active addresses and a summary (total balance per derivation mode)
|
|
239
|
+
*/
|
|
240
|
+
function run(itemToScan, balanceOnly, scanLimits) {
|
|
241
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
242
|
+
if (settings_1.configuration.currency.utxo_based) {
|
|
243
|
+
// if the currency is UTXOs based (e.g., Bitcoin):
|
|
244
|
+
// the item to scan is an xpub
|
|
245
|
+
return xpubAnalysis(itemToScan, balanceOnly, scanLimits);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// otherwise, the currency is account-based (e.g., Ethereum):
|
|
249
|
+
// the item to scan is an address
|
|
250
|
+
return addressAnalysis(itemToScan, balanceOnly);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
exports.run = run;
|
|
255
|
+
/**
|
|
256
|
+
* scan an xpub (UTXO-based mode)
|
|
257
|
+
* @param xpub the xpub to scan
|
|
258
|
+
* @param balanceOnly option to fetch the balance only—not the transactions
|
|
259
|
+
* @param scanLimits option to limit the scan to a certain account and indices range
|
|
260
|
+
* @returns a list of active addresses and a summary (total balance per derivation mode)
|
|
261
|
+
*/
|
|
262
|
+
function xpubAnalysis(xpub, balanceOnly, scanLimits) {
|
|
263
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
264
|
+
let activeAddresses = [];
|
|
265
|
+
const summary = [];
|
|
266
|
+
// get all derivation modes associated with the currency type
|
|
267
|
+
// (e.g., for Bitcoin: legacy, SegWit, native SegWit, and taproot)
|
|
268
|
+
let derivationModes = settings_1.configuration.currency.derivationModes;
|
|
269
|
+
if (settings_1.configuration.specificDerivationMode) {
|
|
270
|
+
// if a specific derivation mode is set, limit the scan to this mode
|
|
271
|
+
derivationModes = derivationModes.filter((derivation) => derivation
|
|
272
|
+
.toString()
|
|
273
|
+
.toLocaleLowerCase()
|
|
274
|
+
.startsWith(settings_1.configuration.specificDerivationMode.toLocaleLowerCase()));
|
|
275
|
+
}
|
|
276
|
+
if (!settings_1.configuration.silent) {
|
|
277
|
+
console.log(chalk_1.default.bold("\nActive addresses\n"));
|
|
278
|
+
}
|
|
279
|
+
for (const derivationMode of derivationModes) {
|
|
280
|
+
// loop over the derivation modes and `scan` the addresses belonging to
|
|
281
|
+
// the current derivation mode
|
|
282
|
+
// (that is: derive them and identify the active ones)
|
|
283
|
+
const results = yield deriveAndScanAddressesByDerivationMode(derivationMode, xpub, balanceOnly, scanLimits);
|
|
284
|
+
activeAddresses = activeAddresses.concat(results.addresses);
|
|
285
|
+
summary.push({
|
|
286
|
+
derivationMode,
|
|
287
|
+
balance: results.balance,
|
|
288
|
+
});
|
|
289
|
+
// Stop scanning other derivation modes if we found active addresses
|
|
290
|
+
// (an xpub will only have addresses in one derivation mode)
|
|
291
|
+
if (results.addresses.length > 0) {
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
addresses: activeAddresses,
|
|
297
|
+
summary,
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DerivationMode } from "../configuration/currencies";
|
|
2
|
+
/**
|
|
3
|
+
* derive an address at a given account and index positions
|
|
4
|
+
* @param derivationMode the derivation mode used to derive the address
|
|
5
|
+
* @param xpub the xpub from which to derive the address
|
|
6
|
+
* @param account account number from which to derive the address
|
|
7
|
+
* @param index index number from which to derive the address
|
|
8
|
+
* @returns the derived address
|
|
9
|
+
*/
|
|
10
|
+
declare function deriveAddress(derivationMode: DerivationMode, xpub: string, account?: number, index?: number): string;
|
|
11
|
+
/**
|
|
12
|
+
* infer the derivation mode from the address syntax
|
|
13
|
+
* @param address any address (Bitcoin, Ethereum, etc.)
|
|
14
|
+
* @returns the derivation mode associated with the address
|
|
15
|
+
*/
|
|
16
|
+
declare function getDerivationMode(address: string): DerivationMode.LEGACY | DerivationMode.NATIVE | DerivationMode.SEGWIT | DerivationMode.TAPROOT | DerivationMode.DOGECOIN;
|
|
17
|
+
export { getDerivationMode, deriveAddress };
|