@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,239 @@
|
|
|
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 __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.deriveAddress = exports.getDerivationMode = void 0;
|
|
30
|
+
const bjs = __importStar(require("bitcoinjs-lib"));
|
|
31
|
+
const bchaddrjs_1 = __importDefault(require("bchaddrjs"));
|
|
32
|
+
const bitcore_lib_cash_1 = __importDefault(require("bitcore-lib-cash"));
|
|
33
|
+
const ethereumjs_wallet_1 = __importDefault(require("ethereumjs-wallet"));
|
|
34
|
+
const currencies_1 = require("../configuration/currencies");
|
|
35
|
+
const settings_1 = require("../configuration/settings");
|
|
36
|
+
const create_hmac_1 = __importDefault(require("create-hmac"));
|
|
37
|
+
const bip32_1 = __importDefault(require("bip32"));
|
|
38
|
+
const ecc = __importStar(require("tiny-secp256k1"));
|
|
39
|
+
const bs58check_1 = __importDefault(require("bs58check"));
|
|
40
|
+
const secp256k1_1 = require("secp256k1");
|
|
41
|
+
const bip32 = (0, bip32_1.default)(ecc);
|
|
42
|
+
// Initialize ECC library for Taproot (p2tr) support
|
|
43
|
+
bjs.initEccLib(ecc);
|
|
44
|
+
/**
|
|
45
|
+
* Convert a public key to x-only format (32 bytes) for Taproot
|
|
46
|
+
* Compressed public keys are 33 bytes (02/03 prefix + 32 bytes x-coordinate)
|
|
47
|
+
* X-only public keys are 32 bytes (just the x-coordinate)
|
|
48
|
+
*/
|
|
49
|
+
function toXOnly(pubKey) {
|
|
50
|
+
return pubKey.length === 32 ? pubKey : pubKey.subarray(1, 33);
|
|
51
|
+
}
|
|
52
|
+
class BIP32 {
|
|
53
|
+
constructor(publicKey, chainCode, network, depth = 0, index = 0) {
|
|
54
|
+
this.publicKey = publicKey;
|
|
55
|
+
this.chainCode = chainCode;
|
|
56
|
+
this.network = network;
|
|
57
|
+
this.depth = depth;
|
|
58
|
+
this.index = index;
|
|
59
|
+
}
|
|
60
|
+
derive(index) {
|
|
61
|
+
const data = Buffer.allocUnsafe(37);
|
|
62
|
+
this.publicKey.copy(data, 0);
|
|
63
|
+
data.writeUInt32BE(index, 33);
|
|
64
|
+
const I = (0, create_hmac_1.default)("sha512", this.chainCode).update(data).digest();
|
|
65
|
+
const IL = I.slice(0, 32);
|
|
66
|
+
const IR = I.slice(32);
|
|
67
|
+
const Ki = Buffer.from((0, secp256k1_1.publicKeyTweakAdd)(this.publicKey, IL));
|
|
68
|
+
return new BIP32(Ki, IR, this.network, this.depth + 1, index);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const getPubkeyAt = (xpub, account, index) => {
|
|
72
|
+
const buffer = Buffer.from(bs58check_1.default.decode(xpub));
|
|
73
|
+
const depth = buffer[4];
|
|
74
|
+
const i = buffer.readUInt32BE(9);
|
|
75
|
+
const chainCode = buffer.slice(13, 45);
|
|
76
|
+
const publicKey = buffer.slice(45, 78);
|
|
77
|
+
return new BIP32(publicKey, chainCode, settings_1.configuration.currency.network, depth, i)
|
|
78
|
+
.derive(account)
|
|
79
|
+
.derive(index).publicKey;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* derive a legacy address at a given account and index positions
|
|
83
|
+
* @param xpub the xpub from which to derive the legacy address
|
|
84
|
+
* @param account account number from which to derive the legacy address
|
|
85
|
+
* @param index index number from which to derive the legacy address
|
|
86
|
+
* @returns the derived legacy address
|
|
87
|
+
*/
|
|
88
|
+
function getLegacyAddress(xpub, account, index) {
|
|
89
|
+
const publicKeyBuffer = getPubkeyAt(xpub, account, index);
|
|
90
|
+
const publicKeyHash160 = bjs.crypto.hash160(publicKeyBuffer);
|
|
91
|
+
return bjs.address.toBase58Check(publicKeyHash160, settings_1.configuration.currency.network.pubKeyHash);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* derive a SegWit address at a given account and index positions
|
|
95
|
+
* @param xpub the xpub from which to derive the SegWit address
|
|
96
|
+
* @param account account number from which to derive the SegWit address
|
|
97
|
+
* @param index index number from which to derive the SegWit address
|
|
98
|
+
* @returns the derived SegWit address
|
|
99
|
+
*/
|
|
100
|
+
function getSegWitAddress(xpub, account, index) {
|
|
101
|
+
const { address } = bjs.payments.p2sh({
|
|
102
|
+
redeem: bjs.payments.p2wpkh({
|
|
103
|
+
pubkey: bip32
|
|
104
|
+
.fromBase58(xpub, settings_1.configuration.currency.network)
|
|
105
|
+
.derive(account)
|
|
106
|
+
.derive(index).publicKey,
|
|
107
|
+
network: settings_1.configuration.currency.network,
|
|
108
|
+
}),
|
|
109
|
+
});
|
|
110
|
+
return String(address);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* derive a native SegWit address at a given account and index positions
|
|
114
|
+
* @param xpub the xpub from which to derive the native SegWit address
|
|
115
|
+
* @param account account number from which to derive the native SegWit address
|
|
116
|
+
* @param index index number from which to derive the native SegWit address
|
|
117
|
+
* @returns the derived native SegWit address
|
|
118
|
+
*/
|
|
119
|
+
function getNativeSegWitAddress(xpub, account, index) {
|
|
120
|
+
const { address } = bjs.payments.p2wpkh({
|
|
121
|
+
pubkey: bip32
|
|
122
|
+
.fromBase58(xpub, settings_1.configuration.currency.network)
|
|
123
|
+
.derive(account)
|
|
124
|
+
.derive(index).publicKey,
|
|
125
|
+
network: settings_1.configuration.currency.network,
|
|
126
|
+
});
|
|
127
|
+
return String(address);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* derive a Taproot address at a given account and index positions
|
|
131
|
+
* @param xpub the xpub from which to derive the Taproot address
|
|
132
|
+
* @param account account number (0 = external/receiving, 1 = internal/change)
|
|
133
|
+
* @param index index number from which to derive the Taproot address
|
|
134
|
+
* @returns the derived Taproot address (bc1p... for mainnet, tb1p... for testnet)
|
|
135
|
+
*/
|
|
136
|
+
function getTaprootAddress(xpub, account, index) {
|
|
137
|
+
const childPubKey = bip32
|
|
138
|
+
.fromBase58(xpub, settings_1.configuration.currency.network)
|
|
139
|
+
.derive(account)
|
|
140
|
+
.derive(index).publicKey;
|
|
141
|
+
const { address } = bjs.payments.p2tr({
|
|
142
|
+
internalPubkey: toXOnly(childPubKey),
|
|
143
|
+
network: settings_1.configuration.currency.network,
|
|
144
|
+
});
|
|
145
|
+
return String(address);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* derive a Bitcoin Cash address at a given account and index positions
|
|
149
|
+
* @param xpub the xpub from which to derive the Bitcoin Cash address
|
|
150
|
+
* @param account account number from which to derive the Bitcoin Cash address
|
|
151
|
+
* @param index index number from which to derive the Bitcoin Cash address
|
|
152
|
+
* @returns the derived Bitcoin Cash address
|
|
153
|
+
* note: based on https://github.com/go-faast/bitcoin-cash-payments/blob/54397eb97c7a9bf08b32e10bef23d5f27aa5ab01/index.js#L63-L73
|
|
154
|
+
*/
|
|
155
|
+
function getLegacyBitcoinCashAddress(xpub, account, index) {
|
|
156
|
+
const node = new bitcore_lib_cash_1.default.HDPublicKey(xpub);
|
|
157
|
+
const child = node.derive(account).derive(index);
|
|
158
|
+
const address = new bitcore_lib_cash_1.default.Address(child.publicKey, bitcore_lib_cash_1.default.Networks.livenet);
|
|
159
|
+
const addrstr = address.toString().split(":");
|
|
160
|
+
if (addrstr.length === 2) {
|
|
161
|
+
return bchaddrjs_1.default.toLegacyAddress(addrstr[1]);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
throw new Error("Unable to derive cash address for " + address);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* derive a unique Ethereum address (the first one)
|
|
169
|
+
* @param xpub the xpub from which to derive the Ethereum address
|
|
170
|
+
* @returns the first derived Ethereum address
|
|
171
|
+
*/
|
|
172
|
+
function getEthereumAddress(xpub) {
|
|
173
|
+
return ethereumjs_wallet_1.default.fromExtendedPublicKey(xpub).getAddressString();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* derive an address at a given account and index positions
|
|
177
|
+
* @param derivationMode the derivation mode used to derive the address
|
|
178
|
+
* @param xpub the xpub from which to derive the address
|
|
179
|
+
* @param account account number from which to derive the address
|
|
180
|
+
* @param index index number from which to derive the address
|
|
181
|
+
* @returns the derived address
|
|
182
|
+
*/
|
|
183
|
+
function deriveAddress(derivationMode, xpub, account, index) {
|
|
184
|
+
if (typeof account === "undefined") {
|
|
185
|
+
account = 0;
|
|
186
|
+
}
|
|
187
|
+
if (typeof index === "undefined") {
|
|
188
|
+
index = 0;
|
|
189
|
+
}
|
|
190
|
+
switch (derivationMode) {
|
|
191
|
+
case currencies_1.DerivationMode.LEGACY:
|
|
192
|
+
return getLegacyAddress(xpub, account, index);
|
|
193
|
+
case currencies_1.DerivationMode.SEGWIT:
|
|
194
|
+
return getSegWitAddress(xpub, account, index);
|
|
195
|
+
case currencies_1.DerivationMode.NATIVE:
|
|
196
|
+
return getNativeSegWitAddress(xpub, account, index);
|
|
197
|
+
case currencies_1.DerivationMode.TAPROOT:
|
|
198
|
+
return getTaprootAddress(xpub, account, index);
|
|
199
|
+
case currencies_1.DerivationMode.BCH:
|
|
200
|
+
return getLegacyBitcoinCashAddress(xpub, account, index);
|
|
201
|
+
case currencies_1.DerivationMode.DOGECOIN:
|
|
202
|
+
return getLegacyAddress(xpub, account, index);
|
|
203
|
+
case currencies_1.DerivationMode.ETHEREUM:
|
|
204
|
+
return getEthereumAddress(xpub);
|
|
205
|
+
case currencies_1.DerivationMode.UNKNOWN:
|
|
206
|
+
/* fallthrough */
|
|
207
|
+
default:
|
|
208
|
+
throw new Error("Unknown derivation mode");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
exports.deriveAddress = deriveAddress;
|
|
212
|
+
/**
|
|
213
|
+
* infer the derivation mode from the address syntax
|
|
214
|
+
* @param address any address (Bitcoin, Ethereum, etc.)
|
|
215
|
+
* @returns the derivation mode associated with the address
|
|
216
|
+
*/
|
|
217
|
+
function getDerivationMode(address) {
|
|
218
|
+
if (address.match("^(bc1q|tb1|ltc1).*")) {
|
|
219
|
+
return currencies_1.DerivationMode.NATIVE;
|
|
220
|
+
}
|
|
221
|
+
else if (address.match("^(bc1p|tb1p).*")) {
|
|
222
|
+
return currencies_1.DerivationMode.TAPROOT;
|
|
223
|
+
}
|
|
224
|
+
else if (address.match("^[32M].*")) {
|
|
225
|
+
return currencies_1.DerivationMode.SEGWIT;
|
|
226
|
+
}
|
|
227
|
+
else if (address.match("^[1nmL].*")) {
|
|
228
|
+
return currencies_1.DerivationMode.LEGACY;
|
|
229
|
+
}
|
|
230
|
+
else if (address.match("^(D).*")) {
|
|
231
|
+
return currencies_1.DerivationMode.DOGECOIN;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
throw new Error("INVALID ADDRESS: "
|
|
235
|
+
.concat(address)
|
|
236
|
+
.concat(" is not a valid or a supported address"));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
exports.getDerivationMode = getDerivationMode;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Address } from "../models/address";
|
|
2
|
+
import { OwnAddresses } from "../models/ownAddresses";
|
|
3
|
+
import { Operation } from "../models/operation";
|
|
4
|
+
/**
|
|
5
|
+
* fetch the processed basic stats related to an address
|
|
6
|
+
* its balance, funded and spend sums and counts
|
|
7
|
+
* @param address the address being analyzed
|
|
8
|
+
* @param balanceOnly an option to return only the balance (only for Crypto APIs)
|
|
9
|
+
*/
|
|
10
|
+
declare function getStats(address: Address, balanceOnly: boolean): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* get all processed transactions related to an address
|
|
13
|
+
* @param address the address being analyzed
|
|
14
|
+
* @param ownAddresses (optional) list of addresses derived from the same xpub as `address`
|
|
15
|
+
*/
|
|
16
|
+
declare function getTransactions(address: Address, ownAddresses?: OwnAddresses): void;
|
|
17
|
+
/**
|
|
18
|
+
* Returns an array of ordered operations
|
|
19
|
+
* @param {Array<Address>} addresses - all active addresses belonging to the xpub
|
|
20
|
+
* @returns {Array<Address>} Array of operations in reverse chronological order
|
|
21
|
+
*/
|
|
22
|
+
declare function getSortedOperations(addresses: Array<Address>): Array<Operation>;
|
|
23
|
+
/**
|
|
24
|
+
* Returns an array of ordered UTXOs
|
|
25
|
+
* @param {Array<Address>} addresses - all active addresses belonging to the xpub
|
|
26
|
+
* @returns {Array<Address>} Array of UTXOs in reverse chronological order
|
|
27
|
+
*/
|
|
28
|
+
declare function getSortedUTXOS(addresses: Array<Address>): Array<Address>;
|
|
29
|
+
export { getStats, getTransactions, getSortedOperations, getSortedUTXOS };
|
|
@@ -0,0 +1,289 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.getSortedUTXOS = exports.getSortedOperations = exports.getTransactions = exports.getStats = void 0;
|
|
36
|
+
const settings_1 = require("../configuration/settings");
|
|
37
|
+
const operation_1 = require("../models/operation");
|
|
38
|
+
const defaultProvider = __importStar(require("../api/defaultProvider"));
|
|
39
|
+
const customProvider = __importStar(require("../api/customProvider"));
|
|
40
|
+
const currencies_1 = require("../configuration/currencies");
|
|
41
|
+
/**
|
|
42
|
+
* fetch the processed basic stats related to an address
|
|
43
|
+
* its balance, funded and spend sums and counts
|
|
44
|
+
* @param address the address being analyzed
|
|
45
|
+
* @param balanceOnly an option to return only the balance (only for Crypto APIs)
|
|
46
|
+
*/
|
|
47
|
+
function getStats(address, balanceOnly) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
switch (settings_1.configuration.providerType) {
|
|
50
|
+
case "default":
|
|
51
|
+
yield defaultProvider.getStats(address);
|
|
52
|
+
break;
|
|
53
|
+
case "Crypto APIs":
|
|
54
|
+
yield customProvider.getStats(address, balanceOnly);
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
throw new Error("Should not be reachable: providerType should be 'default' or 'Crypto APIs'");
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
exports.getStats = getStats;
|
|
62
|
+
/**
|
|
63
|
+
* get all transactions associated with an UTXO-based address
|
|
64
|
+
* @param address the UTXO-based address being analyzed
|
|
65
|
+
*/
|
|
66
|
+
function getUTXOBasedTransactions(address) {
|
|
67
|
+
switch (settings_1.configuration.providerType) {
|
|
68
|
+
case "default":
|
|
69
|
+
defaultProvider.getTransactions(address);
|
|
70
|
+
break;
|
|
71
|
+
case "Crypto APIs":
|
|
72
|
+
customProvider.getTransactions(address);
|
|
73
|
+
break;
|
|
74
|
+
default:
|
|
75
|
+
throw new Error("Should not be reachable: providerType should be 'default' or 'Crypto APIs'");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* get all transactions associated with an account-based address (typically Ethereum)
|
|
80
|
+
* @param address the account-based address being analyzed
|
|
81
|
+
*/
|
|
82
|
+
function getAccountBasedTransactions(address) {
|
|
83
|
+
switch (settings_1.configuration.providerType) {
|
|
84
|
+
case "default":
|
|
85
|
+
defaultProvider.getAccountBasedTransactions(address);
|
|
86
|
+
break;
|
|
87
|
+
case "Crypto APIs":
|
|
88
|
+
customProvider.getAccountBasedTransactions(address);
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
throw new Error("Should not be reachable: providerType should be 'default' or 'Crypto APIs'");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* get all processed transactions related to an address
|
|
96
|
+
* @param address the address being analyzed
|
|
97
|
+
* @param ownAddresses (optional) list of addresses derived from the same xpub as `address`
|
|
98
|
+
*/
|
|
99
|
+
function getTransactions(address, ownAddresses) {
|
|
100
|
+
if (settings_1.configuration.currency.utxo_based) {
|
|
101
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━┓
|
|
102
|
+
// ┃ UTXO-BASED CURRENCY ┃
|
|
103
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━┛
|
|
104
|
+
getUTXOBasedTransactions(address);
|
|
105
|
+
// important step: distinguish funded from sent transactions
|
|
106
|
+
processFundedTransactions(address, ownAddresses);
|
|
107
|
+
processSentTransactions(address, ownAddresses);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
111
|
+
// ┃ ACCOUNT-BASED CURRENCY ┃
|
|
112
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
113
|
+
getAccountBasedTransactions(address);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.getTransactions = getTransactions;
|
|
117
|
+
/**
|
|
118
|
+
* identify _funded_ transactions among the address' transactions
|
|
119
|
+
* @param address the address being analyzed
|
|
120
|
+
* @param ownAddresses list of addresses derived from the same xpub as `address`
|
|
121
|
+
*/
|
|
122
|
+
function processFundedTransactions(address, ownAddresses) {
|
|
123
|
+
// all transactions associated with the address, without distinction
|
|
124
|
+
const transactions = address.getTransactions();
|
|
125
|
+
// all addresses derived from the same xpub as `address`
|
|
126
|
+
const allOwnAddresses = ownAddresses.getAllAddresses();
|
|
127
|
+
const accountNumber = address.getDerivation().account;
|
|
128
|
+
let isFunded;
|
|
129
|
+
for (const tx of transactions) {
|
|
130
|
+
isFunded = true;
|
|
131
|
+
if (typeof tx.ins !== "undefined" && tx.ins.length > 0) {
|
|
132
|
+
if (accountNumber === 1) {
|
|
133
|
+
// when account is internal (i.e., 1), and
|
|
134
|
+
// - has a sibling as sender: not externally funded (expected behavior: sent to change)
|
|
135
|
+
// - has no sibling as sender: process the operation (edge case: non-sibling to change)
|
|
136
|
+
for (const txin of tx.ins) {
|
|
137
|
+
if (allOwnAddresses.includes(txin.address)) {
|
|
138
|
+
// has a sibling as sender: not funded
|
|
139
|
+
isFunded = false;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (isFunded) {
|
|
145
|
+
// this is a _funded_ operation...
|
|
146
|
+
const op = new operation_1.Operation(tx.date, tx.ins[0].amount);
|
|
147
|
+
op.setTxid(tx.txid);
|
|
148
|
+
op.setBlockNumber(tx.blockHeight);
|
|
149
|
+
// ... that has to be categorized:
|
|
150
|
+
// there are 2 types of received transaction:
|
|
151
|
+
// case 1 — received to an address that is NOT a change address: default received
|
|
152
|
+
// case 2 — received to a change address (account #1): received from non-sibling to change
|
|
153
|
+
op.setOperationType(accountNumber !== 1 ? "Received" : "Received (non-sibling to change)");
|
|
154
|
+
// associate this funded operation with the address
|
|
155
|
+
address.addFundedOperation(op);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (settings_1.VERBOSE) {
|
|
160
|
+
console.log("FUNDED\t", address.getFundedOperations());
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* identify _sent_ transactions among the address' transactions
|
|
165
|
+
* @param address the address being analyzed
|
|
166
|
+
* @param ownAddresses list of addresses derived from the same xpub as `address`
|
|
167
|
+
*/
|
|
168
|
+
function processSentTransactions(address, ownAddresses) {
|
|
169
|
+
// all transactions associated with the address, without distinction
|
|
170
|
+
const transactions = address.getTransactions();
|
|
171
|
+
// addresses derived from the same xpub as `address`,
|
|
172
|
+
// either external or internal (i.e., change)
|
|
173
|
+
const externalAddresses = ownAddresses.getExternalAddresses();
|
|
174
|
+
const internalAddresses = ownAddresses.getInternalAddresses();
|
|
175
|
+
for (const tx of transactions) {
|
|
176
|
+
const outs = tx.outs;
|
|
177
|
+
outs.forEach((out) => {
|
|
178
|
+
// analyse all recipient addresses and identify whether they
|
|
179
|
+
// are external/internal/third-party addresses
|
|
180
|
+
const isInternalAddress = internalAddresses.includes(out.address);
|
|
181
|
+
const isExternalAddress = externalAddresses.includes(out.address);
|
|
182
|
+
// exclude internal addresses—by definition (i.e., change addresses)
|
|
183
|
+
if (!isInternalAddress) {
|
|
184
|
+
// at this stage, the recipient is either an external address or a
|
|
185
|
+
// third-party address (i.e., not belonging to the same xpub)...
|
|
186
|
+
const op = new operation_1.Operation(tx.date, out.amount);
|
|
187
|
+
op.setTxid(tx.txid);
|
|
188
|
+
op.setBlockNumber(tx.blockHeight);
|
|
189
|
+
// ... and has to be categorized:
|
|
190
|
+
// there are 3 types of sent transaction:
|
|
191
|
+
if (out.address === address.toString()) {
|
|
192
|
+
// case 1 — sent to self: sent to same address
|
|
193
|
+
op.setOperationType("Sent to self");
|
|
194
|
+
}
|
|
195
|
+
else if (isExternalAddress) {
|
|
196
|
+
// case 2 — sent to a sibling: sent to an address belonging to the same xpub
|
|
197
|
+
// _while not being a change address_
|
|
198
|
+
op.setOperationType("Sent to sibling");
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// case 3 — sent to an address not belonging to the xpub
|
|
202
|
+
op.setOperationType("Sent");
|
|
203
|
+
}
|
|
204
|
+
// associate this sent operation with the address
|
|
205
|
+
address.addSentOperation(op);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (settings_1.VERBOSE) {
|
|
210
|
+
console.log("SENT\t", address.getSentOperations());
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* sort by block number and, _then, if needed_, by date
|
|
215
|
+
* @param A first operation
|
|
216
|
+
* @param B second operation
|
|
217
|
+
* @returns -1|0|1, depending on the ordering
|
|
218
|
+
*/
|
|
219
|
+
function compareOpsByBlockThenDate(A, B) {
|
|
220
|
+
// block number
|
|
221
|
+
if (A.block > B.block) {
|
|
222
|
+
return -1;
|
|
223
|
+
}
|
|
224
|
+
if (A.block < B.block) {
|
|
225
|
+
return 1;
|
|
226
|
+
}
|
|
227
|
+
// date
|
|
228
|
+
if (A.date > B.date) {
|
|
229
|
+
return -1;
|
|
230
|
+
}
|
|
231
|
+
if (A.date < B.date) {
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Returns an array of ordered operations
|
|
238
|
+
* @param {Array<Address>} addresses - all active addresses belonging to the xpub
|
|
239
|
+
* @returns {Array<Address>} Array of operations in reverse chronological order
|
|
240
|
+
*/
|
|
241
|
+
function getSortedOperations(addresses) {
|
|
242
|
+
const operations = [];
|
|
243
|
+
const processedTxids = [];
|
|
244
|
+
// flatten the array of arrays in one dimension, and loop over
|
|
245
|
+
[].concat(addresses).forEach((address) => {
|
|
246
|
+
address.getFundedOperations().forEach((op) => {
|
|
247
|
+
op.setAddress(address.toString());
|
|
248
|
+
if (settings_1.configuration.currency.symbol === currencies_1.currencies.bch.symbol) {
|
|
249
|
+
op.setCashAddress(address.asCashAddress());
|
|
250
|
+
}
|
|
251
|
+
operations.push(op);
|
|
252
|
+
});
|
|
253
|
+
address.getSentOperations().forEach((op) => {
|
|
254
|
+
// only process a given txid once
|
|
255
|
+
if (!processedTxids.includes(op.txid)) {
|
|
256
|
+
op.setAddress(address.toString());
|
|
257
|
+
if (settings_1.configuration.currency.symbol === currencies_1.currencies.bch.symbol) {
|
|
258
|
+
op.setCashAddress(address.asCashAddress());
|
|
259
|
+
}
|
|
260
|
+
operations.push(op);
|
|
261
|
+
processedTxids.push(op.txid);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
// reverse chronological order
|
|
266
|
+
operations.sort(compareOpsByBlockThenDate);
|
|
267
|
+
return operations;
|
|
268
|
+
}
|
|
269
|
+
exports.getSortedOperations = getSortedOperations;
|
|
270
|
+
/**
|
|
271
|
+
* Returns an array of ordered UTXOs
|
|
272
|
+
* @param {Array<Address>} addresses - all active addresses belonging to the xpub
|
|
273
|
+
* @returns {Array<Address>} Array of UTXOs in reverse chronological order
|
|
274
|
+
*/
|
|
275
|
+
// (reverse chronological order)
|
|
276
|
+
function getSortedUTXOS(addresses) {
|
|
277
|
+
// note: no need to explicitely sort the UTXOs as they inherit
|
|
278
|
+
// the order from the addresses themselves
|
|
279
|
+
const utxos = [];
|
|
280
|
+
// flatten the array of arrays in one dimension, and loop over
|
|
281
|
+
[].concat(addresses).forEach((address) => {
|
|
282
|
+
if (address.isUTXO()) {
|
|
283
|
+
// if the address is an UTXO, just add it to the list of UTXOs
|
|
284
|
+
utxos.push(address);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
return utxos;
|
|
288
|
+
}
|
|
289
|
+
exports.getSortedUTXOS = getSortedUTXOS;
|