@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
package/lib/helpers.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
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.toAccountUnit = exports.toBaseUnit = exports.toUnprefixedCashAddress = exports.setNetwork = exports.retry = exports.getNetworkLabel = exports.getJSON = exports.init = void 0;
|
|
39
|
+
const axios_1 = __importDefault(require("axios"));
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const bchaddrjs_1 = __importDefault(require("bchaddrjs"));
|
|
42
|
+
const settings_1 = require("./configuration/settings");
|
|
43
|
+
const currencies_1 = require("./configuration/currencies");
|
|
44
|
+
const bip32_1 = __importDefault(require("bip32"));
|
|
45
|
+
const ecc = __importStar(require("tiny-secp256k1"));
|
|
46
|
+
const bip32 = (0, bip32_1.default)(ecc);
|
|
47
|
+
function getJSON(url, APIKey, { retries, retryDelayMS } = {}) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
const job = () => __awaiter(this, void 0, void 0, function* () {
|
|
50
|
+
const headers = Object.assign({}, (APIKey ? { "X-API-Key": APIKey } : {}));
|
|
51
|
+
const res = yield axios_1.default.get(url, { headers });
|
|
52
|
+
if (res.status !== 200) {
|
|
53
|
+
console.log(chalk_1.default.red("GET request error"));
|
|
54
|
+
throw new Error("GET REQUEST ERROR: "
|
|
55
|
+
.concat(url)
|
|
56
|
+
.concat(", Status Code: ")
|
|
57
|
+
.concat(String(res.status)));
|
|
58
|
+
}
|
|
59
|
+
return res.data;
|
|
60
|
+
});
|
|
61
|
+
return retry(job, { retries, retryDelayMS });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
exports.getJSON = getJSON;
|
|
65
|
+
function retry(job, { retries = 5, retryDelayMS = 0 } = {}) {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
let err = null;
|
|
68
|
+
for (let i = 0; i < retries; i++) {
|
|
69
|
+
try {
|
|
70
|
+
return yield job();
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
err = e;
|
|
74
|
+
// wait before retrying if it's not the last try
|
|
75
|
+
if (retryDelayMS && i < retries - 1) {
|
|
76
|
+
yield new Promise((r) => setTimeout(r, retryDelayMS));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (err)
|
|
81
|
+
throw err;
|
|
82
|
+
throw new Error(`No result after ${retries} retries`);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
exports.retry = retry;
|
|
86
|
+
function setNetwork(xpub, currency, testnet) {
|
|
87
|
+
settings_1.configuration.testnet = testnet || false;
|
|
88
|
+
if (typeof currency === "undefined" ||
|
|
89
|
+
currency === "BTC" ||
|
|
90
|
+
currency === "LTC" ||
|
|
91
|
+
currency === "DOGE") {
|
|
92
|
+
const prefix = xpub.substring(0, 4).toLocaleLowerCase();
|
|
93
|
+
if (prefix === "xpub") {
|
|
94
|
+
// Bitcoin mainnet
|
|
95
|
+
settings_1.configuration.currency = currencies_1.currencies.btc;
|
|
96
|
+
settings_1.configuration.currency.network = currencies_1.currencies.btc.network_mainnet;
|
|
97
|
+
}
|
|
98
|
+
else if (prefix === "tpub") {
|
|
99
|
+
// Bitcoin testnet
|
|
100
|
+
settings_1.configuration.currency = currencies_1.currencies.btc;
|
|
101
|
+
settings_1.configuration.currency.network = currencies_1.currencies.btc.network_testnet;
|
|
102
|
+
settings_1.configuration.testnet = true;
|
|
103
|
+
}
|
|
104
|
+
else if (prefix === "ltub") {
|
|
105
|
+
// Litecoin
|
|
106
|
+
settings_1.configuration.currency = currencies_1.currencies.ltc;
|
|
107
|
+
// TODO: LTC testnet
|
|
108
|
+
settings_1.configuration.currency.network = currencies_1.currencies.ltc.network_mainnet;
|
|
109
|
+
}
|
|
110
|
+
else if (prefix === "dgub") {
|
|
111
|
+
// Dogecoin
|
|
112
|
+
settings_1.configuration.currency = currencies_1.currencies.doge;
|
|
113
|
+
settings_1.configuration.currency.network = currencies_1.currencies.doge.network_mainnet;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
throw new Error("INVALID XPUB: " + xpub + " has not a valid prefix");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Bitcoin Cash
|
|
121
|
+
if (currency === "BCH") {
|
|
122
|
+
settings_1.configuration.currency = currencies_1.currencies.bch;
|
|
123
|
+
// TODO: BCH testnet
|
|
124
|
+
settings_1.configuration.currency.network = currencies_1.currencies.bch.network_mainnet;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Ethereum
|
|
128
|
+
else if (currency === "ETH") {
|
|
129
|
+
settings_1.configuration.currency = currencies_1.currencies.eth;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
throw new Error("INVALID CURRENCY: '" + currency + "' is not supported");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.setNetwork = setNetwork;
|
|
136
|
+
/**
|
|
137
|
+
* Configure the external provider URL (i.e., default v. Crypto APIs provider)
|
|
138
|
+
* @param {string} currency?
|
|
139
|
+
* Symbol of the currency (e.g. 'BCH')
|
|
140
|
+
* @returns void
|
|
141
|
+
*/
|
|
142
|
+
const setExternalProviderURL = () => {
|
|
143
|
+
// custom provider (i.e., API key is set)
|
|
144
|
+
if (process.env.XPUB_SCAN_CUSTOM_API_KEY_V2) {
|
|
145
|
+
settings_1.configuration.externalProviderURL = settings_1.CRYPTOAPIS_URL.replace("{network}", getNetworkLabel());
|
|
146
|
+
settings_1.configuration.providerType = "Crypto APIs";
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// default provider
|
|
150
|
+
const currency = settings_1.configuration.currency;
|
|
151
|
+
if (currency.symbol === currencies_1.currencies.btc.symbol ||
|
|
152
|
+
currency.symbol === currencies_1.currencies.ltc.symbol ||
|
|
153
|
+
currency.symbol === currencies_1.currencies.doge.symbol) {
|
|
154
|
+
settings_1.configuration.externalProviderURL = settings_1.DEFAULT_API_URLS.general;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (currency.symbol === currencies_1.currencies.bch.symbol) {
|
|
158
|
+
settings_1.configuration.externalProviderURL = settings_1.DEFAULT_API_URLS.bch;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (currency.symbol === currencies_1.currencies.eth.symbol) {
|
|
162
|
+
settings_1.configuration.externalProviderURL = settings_1.DEFAULT_API_URLS.eth;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
function checkXpub(xpub) {
|
|
166
|
+
try {
|
|
167
|
+
bip32.fromBase58(xpub, settings_1.configuration.currency.network);
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
throw new Error("INVALID XPUB: " + xpub + " is not a valid xpub -- " + e);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function init(xpub, silent, quiet, currency, testnet, derivationMode) {
|
|
174
|
+
if (typeof silent !== "undefined") {
|
|
175
|
+
settings_1.configuration.silent = silent;
|
|
176
|
+
}
|
|
177
|
+
if (typeof quiet !== "undefined") {
|
|
178
|
+
settings_1.configuration.quiet = quiet;
|
|
179
|
+
}
|
|
180
|
+
setNetwork(xpub, currency, testnet);
|
|
181
|
+
setExternalProviderURL();
|
|
182
|
+
if (settings_1.configuration.currency.utxo_based) {
|
|
183
|
+
checkXpub(xpub);
|
|
184
|
+
}
|
|
185
|
+
settings_1.configuration.specificDerivationMode = derivationMode;
|
|
186
|
+
if (settings_1.configuration.silent) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
console.log(chalk_1.default.grey("(Data fetched from the "
|
|
190
|
+
.concat(chalk_1.default.bold(settings_1.configuration.providerType))
|
|
191
|
+
.concat(" provider)")));
|
|
192
|
+
}
|
|
193
|
+
exports.init = init;
|
|
194
|
+
// remove prefixes (`bitcoincash:`) from Bitcoin Cash addresses
|
|
195
|
+
function toUnprefixedCashAddress(address) {
|
|
196
|
+
if (settings_1.configuration.currency.symbol !== currencies_1.currencies.bch.symbol) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
if (!bchaddrjs_1.default.isCashAddress(address)) {
|
|
200
|
+
address = bchaddrjs_1.default.toCashAddress(address);
|
|
201
|
+
}
|
|
202
|
+
return address.replace("bitcoincash:", "");
|
|
203
|
+
}
|
|
204
|
+
exports.toUnprefixedCashAddress = toUnprefixedCashAddress;
|
|
205
|
+
/**
|
|
206
|
+
* Convert from unit of account to base unit (e.g. bitcoins to satoshis)
|
|
207
|
+
* @param amount the amount (in unit of account) to convert
|
|
208
|
+
* @returns the converted amount, in base unit
|
|
209
|
+
*/
|
|
210
|
+
function toBaseUnit(amount) {
|
|
211
|
+
if (amount.isZero()) {
|
|
212
|
+
return amount.toFixed(0);
|
|
213
|
+
}
|
|
214
|
+
const convertedAmount = amount.times(settings_1.configuration.currency.precision);
|
|
215
|
+
return convertedAmount.toFixed(0);
|
|
216
|
+
}
|
|
217
|
+
exports.toBaseUnit = toBaseUnit;
|
|
218
|
+
/**
|
|
219
|
+
* Convert from base unit to unit of account (e.g. satoshis to bitcoins)
|
|
220
|
+
* @param amount the amount (in base unit) to convert
|
|
221
|
+
* @param decimalPlaces (optional) decimal precision
|
|
222
|
+
* @returns the converted amount, in unit of account
|
|
223
|
+
*/
|
|
224
|
+
function toAccountUnit(amount, decimalPlaces) {
|
|
225
|
+
if (amount.isZero()) {
|
|
226
|
+
return amount.toFixed();
|
|
227
|
+
}
|
|
228
|
+
let convertedValue;
|
|
229
|
+
if (settings_1.configuration.currency.symbol === currencies_1.currencies.eth.symbol) {
|
|
230
|
+
return (amount.toNumber() / settings_1.configuration.currency.precision).toFixed(settings_1.ETH_FIXED_PRECISION);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
convertedValue = amount.dividedBy(settings_1.configuration.currency.precision);
|
|
234
|
+
if (typeof decimalPlaces !== "undefined" && decimalPlaces) {
|
|
235
|
+
return convertedValue.toFixed(decimalPlaces);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return convertedValue.toFixed();
|
|
239
|
+
}
|
|
240
|
+
exports.toAccountUnit = toAccountUnit;
|
|
241
|
+
function getNetworkLabel() {
|
|
242
|
+
if (settings_1.configuration.testnet) {
|
|
243
|
+
if (settings_1.configuration.currency.symbol === currencies_1.currencies.eth.symbol &&
|
|
244
|
+
typeof settings_1.configuration.APIKey !== "undefined") {
|
|
245
|
+
return "ropsten";
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
return "testnet";
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
return "mainnet";
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
exports.getNetworkLabel = getNetworkLabel;
|
|
@@ -0,0 +1,129 @@
|
|
|
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.getArgs = void 0;
|
|
7
|
+
const yargs_1 = __importDefault(require("yargs"));
|
|
8
|
+
const check_1 = require("./check");
|
|
9
|
+
/**
|
|
10
|
+
* Returns the valid args entered by the user
|
|
11
|
+
* @returns any
|
|
12
|
+
* The validated args
|
|
13
|
+
*/
|
|
14
|
+
const getArgs = () => {
|
|
15
|
+
const args = yargs_1.default
|
|
16
|
+
// primary options
|
|
17
|
+
.option("currency", {
|
|
18
|
+
description: "currency",
|
|
19
|
+
demand: false,
|
|
20
|
+
type: "string",
|
|
21
|
+
})
|
|
22
|
+
.option("testnet", {
|
|
23
|
+
description: "testnet",
|
|
24
|
+
demand: false,
|
|
25
|
+
type: "boolean",
|
|
26
|
+
default: false,
|
|
27
|
+
})
|
|
28
|
+
.option("account", {
|
|
29
|
+
alias: "a",
|
|
30
|
+
description: "Account number",
|
|
31
|
+
demand: false,
|
|
32
|
+
type: "number",
|
|
33
|
+
})
|
|
34
|
+
.option("index", {
|
|
35
|
+
alias: "i",
|
|
36
|
+
description: "Index number",
|
|
37
|
+
demand: false,
|
|
38
|
+
type: "number",
|
|
39
|
+
})
|
|
40
|
+
.option("from-index", {
|
|
41
|
+
description: "ScanLimits: FROM index X",
|
|
42
|
+
demand: false,
|
|
43
|
+
type: "number",
|
|
44
|
+
})
|
|
45
|
+
.option("to-index", {
|
|
46
|
+
description: "ScanLimits: TO index Y",
|
|
47
|
+
demand: false,
|
|
48
|
+
type: "number",
|
|
49
|
+
})
|
|
50
|
+
.option("pre-derivation-size", {
|
|
51
|
+
description: "ScanLimits: number of pre-derived addresses per account",
|
|
52
|
+
demand: false,
|
|
53
|
+
type: "number",
|
|
54
|
+
})
|
|
55
|
+
.option("address", {
|
|
56
|
+
description: "Address",
|
|
57
|
+
demand: false,
|
|
58
|
+
type: "string",
|
|
59
|
+
})
|
|
60
|
+
.option("derivation-mode", {
|
|
61
|
+
description: "Select specific derivation mode (legacy, SegWit, etc.)",
|
|
62
|
+
demand: false,
|
|
63
|
+
type: "string",
|
|
64
|
+
})
|
|
65
|
+
// stdout options
|
|
66
|
+
.option("silent", {
|
|
67
|
+
description: "Do not display anything (except for the filepath of the saved reports)",
|
|
68
|
+
demand: false,
|
|
69
|
+
type: "boolean",
|
|
70
|
+
default: false,
|
|
71
|
+
})
|
|
72
|
+
.option("quiet", {
|
|
73
|
+
description: "Do not display analysis progress",
|
|
74
|
+
demand: false,
|
|
75
|
+
type: "boolean",
|
|
76
|
+
default: false,
|
|
77
|
+
})
|
|
78
|
+
.option("diff", {
|
|
79
|
+
description: "Show diffs",
|
|
80
|
+
demand: false,
|
|
81
|
+
type: "boolean",
|
|
82
|
+
})
|
|
83
|
+
.option("addresses", {
|
|
84
|
+
description: "Import addresses (file) for comparison",
|
|
85
|
+
demand: false,
|
|
86
|
+
type: "string",
|
|
87
|
+
})
|
|
88
|
+
.option("balance", {
|
|
89
|
+
description: "Import balance for comparison (has to be in base unit) for comparison",
|
|
90
|
+
demand: false,
|
|
91
|
+
type: "string",
|
|
92
|
+
})
|
|
93
|
+
.option("balance-only", {
|
|
94
|
+
description: "Do not fetch operations, only balance of the account (utxo-list for btc)",
|
|
95
|
+
demand: false,
|
|
96
|
+
type: "boolean",
|
|
97
|
+
default: false,
|
|
98
|
+
})
|
|
99
|
+
.option("utxos", {
|
|
100
|
+
description: "Import UTXOs (file) for comparison",
|
|
101
|
+
demand: false,
|
|
102
|
+
type: "string",
|
|
103
|
+
})
|
|
104
|
+
.option("operations", {
|
|
105
|
+
description: "Import operations history (file) for comparison",
|
|
106
|
+
demand: false,
|
|
107
|
+
type: "string",
|
|
108
|
+
})
|
|
109
|
+
// save
|
|
110
|
+
.option("save", {
|
|
111
|
+
description: "Save analysis",
|
|
112
|
+
demand: false,
|
|
113
|
+
type: "string",
|
|
114
|
+
})
|
|
115
|
+
.option("custom-provider", {
|
|
116
|
+
description: "Require the use of the custom provider",
|
|
117
|
+
demand: false,
|
|
118
|
+
type: "boolean",
|
|
119
|
+
default: false,
|
|
120
|
+
})
|
|
121
|
+
.option("block-height-limit", {
|
|
122
|
+
description: "Block height limit",
|
|
123
|
+
demand: false,
|
|
124
|
+
type: "number",
|
|
125
|
+
}).argv;
|
|
126
|
+
(0, check_1.checkArgs)(args, process.argv);
|
|
127
|
+
return args;
|
|
128
|
+
};
|
|
129
|
+
exports.getArgs = getArgs;
|
|
@@ -0,0 +1,217 @@
|
|
|
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.checkArgs = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const currencies_1 = require("../configuration/currencies");
|
|
10
|
+
const settings_1 = require("../configuration/settings");
|
|
11
|
+
/**
|
|
12
|
+
* Ensure that args are valid
|
|
13
|
+
* @param {any} args
|
|
14
|
+
* @returns void
|
|
15
|
+
*/
|
|
16
|
+
const checkArgs = (args, argv) => {
|
|
17
|
+
// (important) Command line mode: enable output
|
|
18
|
+
settings_1.configuration.commandLineMode = true;
|
|
19
|
+
args.itemToScan = args._[0];
|
|
20
|
+
if (Number(args.itemToScan)) {
|
|
21
|
+
args.itemToScan = argv[2];
|
|
22
|
+
}
|
|
23
|
+
const itemToScan = args.itemToScan;
|
|
24
|
+
const testnet = args.testnet;
|
|
25
|
+
const address = args.address;
|
|
26
|
+
const balance = args.balance;
|
|
27
|
+
const save = args.save;
|
|
28
|
+
const currency = args.currency;
|
|
29
|
+
const derivationMode = args.derivationMode;
|
|
30
|
+
const account = args.account;
|
|
31
|
+
const index = args.index;
|
|
32
|
+
const fromIndex = args.fromIndex;
|
|
33
|
+
const toIndex = args.toIndex;
|
|
34
|
+
const preDerivationSize = args.preDerivationSize;
|
|
35
|
+
const blockHeightLimit = args.blockHeightLimit;
|
|
36
|
+
// xpub: set, non-empty
|
|
37
|
+
if (typeof itemToScan === "undefined" || itemToScan === "") {
|
|
38
|
+
throw new Error("Xpub or address is required");
|
|
39
|
+
}
|
|
40
|
+
// address: non-empty
|
|
41
|
+
if (typeof address !== "undefined" && address === "") {
|
|
42
|
+
throw new Error("Address should not be empty");
|
|
43
|
+
}
|
|
44
|
+
// imported balance: integer (i.e., base unit)
|
|
45
|
+
if (typeof balance !== "undefined") {
|
|
46
|
+
if (balance % 1 !== 0) {
|
|
47
|
+
throw new Error("Balance is not an integer: " + balance);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (args.balanceOnly && args.operations) {
|
|
51
|
+
throw new Error("You cannot pass an operation file in --balance-only mode");
|
|
52
|
+
}
|
|
53
|
+
// currency: exists
|
|
54
|
+
if (typeof currency !== "undefined") {
|
|
55
|
+
args.currency = args.currency.toUpperCase();
|
|
56
|
+
const currencyProperties = Object.entries(currencies_1.currencies).filter((c) => c[1].symbol.toUpperCase() === args.currency.toUpperCase());
|
|
57
|
+
if (currencyProperties.length === 0) {
|
|
58
|
+
throw new Error("Currency '" + currency + "' has not been implemented yet");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// derivation mode: compatible with (implicitly) selected currency
|
|
62
|
+
if (typeof derivationMode !== "undefined") {
|
|
63
|
+
let availableDerivationModes = [];
|
|
64
|
+
if (typeof currency !== "undefined") {
|
|
65
|
+
// if currency is defined, explicitly use its derivation modes
|
|
66
|
+
const configuredCurrency = Object.entries(currencies_1.currencies)
|
|
67
|
+
.filter((c) => c[1].symbol.toUpperCase() === args.currency.toUpperCase())
|
|
68
|
+
.map((c) => {
|
|
69
|
+
return c[1];
|
|
70
|
+
})[0];
|
|
71
|
+
// implementation note: this complex way of performing this verification
|
|
72
|
+
// is due to the fact that derivationModes is optional...
|
|
73
|
+
for (const c of Object.entries(configuredCurrency)) {
|
|
74
|
+
if (c[0] === "derivationModes") {
|
|
75
|
+
availableDerivationModes = c[1];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// if currency is not defined, implicitly use BTC's derivation modes
|
|
81
|
+
availableDerivationModes = currencies_1.currencies.btc.derivationModes;
|
|
82
|
+
}
|
|
83
|
+
if (availableDerivationModes.filter((d) => d.toLocaleLowerCase().startsWith(derivationMode.toLocaleLowerCase())).length === 0) {
|
|
84
|
+
throw new Error("Selected derivation mode " +
|
|
85
|
+
derivationMode +
|
|
86
|
+
" is not compatible with selected currency");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// warnings: not implemented yet
|
|
90
|
+
if (typeof args.addresses !== "undefined") {
|
|
91
|
+
console.log(chalk_1.default.bgYellowBright.black(" Warning: `--addresses` option has not been implemented yet. Skipped. "));
|
|
92
|
+
}
|
|
93
|
+
if (typeof args.utxos !== "undefined") {
|
|
94
|
+
console.log(chalk_1.default.bgYellowBright.black(" Warning: `--utxos` option has not been implemented yet. Skipped. "));
|
|
95
|
+
}
|
|
96
|
+
// imported files: non-empty, exist
|
|
97
|
+
const importedFiles = [args.addresses, args.utxos, args.operations];
|
|
98
|
+
for (const importedFile of importedFiles) {
|
|
99
|
+
if (typeof importedFile !== "undefined") {
|
|
100
|
+
if (importedFile === "" || !fs_1.default.existsSync(importedFile)) {
|
|
101
|
+
throw new Error("Imported file " + importedFile + " does not exist");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (blockHeightLimit) {
|
|
106
|
+
if (!args.operations) {
|
|
107
|
+
throw new Error("The block height limit can only be used in comparison mode");
|
|
108
|
+
}
|
|
109
|
+
if (blockHeightLimit < 0) {
|
|
110
|
+
throw new Error("The block height limit cannot be negative");
|
|
111
|
+
}
|
|
112
|
+
settings_1.configuration.blockHeightUpperLimit = blockHeightLimit;
|
|
113
|
+
}
|
|
114
|
+
// save dirpath: exists, is a directory, writable
|
|
115
|
+
if (typeof save !== "undefined" && save.toLocaleLowerCase() !== "stdout") {
|
|
116
|
+
try {
|
|
117
|
+
if (!fs_1.default.statSync(save).isDirectory()) {
|
|
118
|
+
throw new Error("Save path " + save + " is not a directory");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (_a) {
|
|
122
|
+
throw new Error("Save path " + save + " does not exist");
|
|
123
|
+
}
|
|
124
|
+
fs_1.default.access(save, fs_1.default.constants.W_OK, function (err) {
|
|
125
|
+
if (err) {
|
|
126
|
+
throw new Error("Save directory " + save + " is not writable");
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// account/index/scanLimits options
|
|
131
|
+
if (typeof account !== "undefined") {
|
|
132
|
+
// -a {positive number}
|
|
133
|
+
if (account < 0) {
|
|
134
|
+
throw new Error("Account number is required to be positive (including zero)");
|
|
135
|
+
}
|
|
136
|
+
// -a X -i Y or -a X --from-index Y [--to-index Z]
|
|
137
|
+
if (typeof index === "undefined" && typeof fromIndex === "undefined") {
|
|
138
|
+
throw new Error("Index or scanLimits is required when account number option (`-a`) is enabled");
|
|
139
|
+
}
|
|
140
|
+
// -a X -i {positive number}
|
|
141
|
+
if (typeof index !== "undefined" && index < 0) {
|
|
142
|
+
throw new Error("Index number is required to be positive (including zero)");
|
|
143
|
+
}
|
|
144
|
+
if (typeof fromIndex !== "undefined") {
|
|
145
|
+
// -a X --from-index {postive number} [--to-index {postive number}]
|
|
146
|
+
if (fromIndex < 0) {
|
|
147
|
+
throw new Error("`--from-index` option is required to be positive (including zero)");
|
|
148
|
+
}
|
|
149
|
+
if (typeof toIndex !== "undefined") {
|
|
150
|
+
if (toIndex < 0) {
|
|
151
|
+
throw new Error("`--to-index` option is required to be positive");
|
|
152
|
+
}
|
|
153
|
+
// -a X --from-index Y --to-index Z | Y <= Z
|
|
154
|
+
if (fromIndex > toIndex) {
|
|
155
|
+
throw new Error("`--from-index` has to be less or equal to `--to-index`");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// -a X -i Y
|
|
162
|
+
if (typeof index !== "undefined") {
|
|
163
|
+
throw new Error("Account number is required when index number option (`-i`) is enabled");
|
|
164
|
+
}
|
|
165
|
+
// -a X --from-index Y --to-index Z
|
|
166
|
+
if (typeof fromIndex !== "undefined") {
|
|
167
|
+
throw new Error("Account number is required when scanLimits index option (`--from-index`) is enabled");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (typeof preDerivationSize !== "undefined") {
|
|
171
|
+
if (preDerivationSize < 0) {
|
|
172
|
+
throw new Error("`--pre-derivation-size` option is required to be positive");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (typeof account !== "undefined") {
|
|
176
|
+
args.preDerivationSize = 2000; // magic number
|
|
177
|
+
}
|
|
178
|
+
// if needed, create scanLimits
|
|
179
|
+
if (typeof account !== "undefined") {
|
|
180
|
+
if (typeof index !== "undefined") {
|
|
181
|
+
args.scanLimits = {
|
|
182
|
+
account,
|
|
183
|
+
indexFrom: index,
|
|
184
|
+
indexTo: index,
|
|
185
|
+
preDerivationSize: args.preDerivationSize,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
else if (typeof fromIndex !== "undefined") {
|
|
189
|
+
args.scanLimits = {
|
|
190
|
+
account,
|
|
191
|
+
indexFrom: fromIndex,
|
|
192
|
+
indexTo: toIndex,
|
|
193
|
+
preDerivationSize: args.preDerivationSize,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// testnet
|
|
198
|
+
if (typeof testnet !== "undefined" && testnet) {
|
|
199
|
+
// temporary guard clause:
|
|
200
|
+
// only Bitcoin and Ethereum testnet are supported at the moment
|
|
201
|
+
if (
|
|
202
|
+
// case 1. non-tpub
|
|
203
|
+
(typeof args.xpub !== "undefined" &&
|
|
204
|
+
args.xpub.substring(0, 4).toLocaleLowerCase() !== "tpub") ||
|
|
205
|
+
// case 2. non-ETH
|
|
206
|
+
(typeof currency !== "undefined" && currency.toUpperCase() !== "ETH") ||
|
|
207
|
+
// case 3. ETH via default provider
|
|
208
|
+
(currency.toUpperCase() === "ETH" &&
|
|
209
|
+
typeof process.env.XPUB_SCAN_CUSTOM_API_KEY_V2 === "undefined")) {
|
|
210
|
+
throw new Error("The analysis of this currency cannot be performed on testnet");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (args.customProvider && !process.env.XPUB_SCAN_CUSTOM_API_KEY_V2) {
|
|
214
|
+
throw new Error("Custom provider v2 API key (XPUB_SCAN_CUSTOM_API_KEY_V2) is missing");
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
exports.checkArgs = checkArgs;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Operation } from "../models/operation";
|
|
2
|
+
/**
|
|
3
|
+
* Dispatcher: detect the type of the imported file
|
|
4
|
+
* based on its contents
|
|
5
|
+
* @param {string} path
|
|
6
|
+
* Path of file to import
|
|
7
|
+
* @returns Operation
|
|
8
|
+
* Imported transactions
|
|
9
|
+
*/
|
|
10
|
+
declare const importOperations: (path: string) => Array<Operation>;
|
|
11
|
+
export { importOperations };
|