@hackerhouse/xpub-scan 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/CONTRIBUTING.md +130 -0
  3. package/LICENSE +23 -0
  4. package/README.md +215 -0
  5. package/__tests__/checkAddresses/checkBitcoinAddressesNegative.test.ts +75 -0
  6. package/__tests__/checkAddresses/checkBitcoinAddressesPositive.test.ts +87 -0
  7. package/__tests__/checkAddresses/checkDogeAddressesPositive.test.ts +43 -0
  8. package/__tests__/checkAddresses/checkLitecoinAddressesNegative.test.ts +75 -0
  9. package/__tests__/checkAddresses/checkLitecoinAddressesPositive.test.ts +87 -0
  10. package/__tests__/checkModels/address.test.ts +183 -0
  11. package/__tests__/checkModels/fakeRawTransactions.json +182 -0
  12. package/__tests__/deriveAddresses/deriveBitcoinAddresses.test.ts +207 -0
  13. package/__tests__/deriveAddresses/deriveBitcoinCashAddresses.test copy.ts +79 -0
  14. package/__tests__/deriveAddresses/deriveDogecoinAddresses.test.ts +43 -0
  15. package/__tests__/deriveAddresses/deriveEthereumAddresses.test.ts +26 -0
  16. package/__tests__/deriveAddresses/deriveLitecoinAddresses.test.ts +110 -0
  17. package/__tests__/helpers.test.ts +274 -0
  18. package/__tests__/test-utils.ts +3 -0
  19. package/babel.config.js +6 -0
  20. package/jest.config.ts +5 -0
  21. package/ledgerhq-xpub-scan-1.0.4.tgz +0 -0
  22. package/lib/actions/checkAddress.d.ts +29 -0
  23. package/lib/actions/checkAddress.js +122 -0
  24. package/lib/actions/checkBalance.d.ts +20 -0
  25. package/lib/actions/checkBalance.js +300 -0
  26. package/lib/actions/deriveAddresses.d.ts +17 -0
  27. package/lib/actions/deriveAddresses.js +239 -0
  28. package/lib/actions/processTransactions.d.ts +29 -0
  29. package/lib/actions/processTransactions.js +289 -0
  30. package/lib/actions/saveAnalysis.d.ts +2 -0
  31. package/lib/actions/saveAnalysis.js +800 -0
  32. package/lib/actions/scanner.d.ts +15 -0
  33. package/lib/actions/scanner.js +152 -0
  34. package/lib/api/customProvider.d.ts +19 -0
  35. package/lib/api/customProvider.js +434 -0
  36. package/lib/api/defaultProvider.d.ts +23 -0
  37. package/lib/api/defaultProvider.js +275 -0
  38. package/lib/comparison/compareOperations.d.ts +13 -0
  39. package/lib/comparison/compareOperations.js +500 -0
  40. package/lib/comparison/diffs.d.ts +18 -0
  41. package/lib/comparison/diffs.js +70 -0
  42. package/lib/configuration/currencies.d.ts +55 -0
  43. package/lib/configuration/currencies.js +72 -0
  44. package/lib/configuration/settings.d.ts +51 -0
  45. package/lib/configuration/settings.js +113 -0
  46. package/lib/display.d.ts +12 -0
  47. package/lib/display.js +251 -0
  48. package/lib/helpers.d.ts +27 -0
  49. package/lib/helpers.js +255 -0
  50. package/lib/input/args.d.ts +6 -0
  51. package/lib/input/args.js +129 -0
  52. package/lib/input/check.d.ts +6 -0
  53. package/lib/input/check.js +217 -0
  54. package/lib/input/importOperations.d.ts +11 -0
  55. package/lib/input/importOperations.js +406 -0
  56. package/lib/models/address.d.ts +40 -0
  57. package/lib/models/address.js +101 -0
  58. package/lib/models/comparison.d.ts +8 -0
  59. package/lib/models/comparison.js +6 -0
  60. package/lib/models/currency.d.ts +11 -0
  61. package/lib/models/currency.js +6 -0
  62. package/lib/models/operation.d.ts +33 -0
  63. package/lib/models/operation.js +80 -0
  64. package/lib/models/ownAddresses.d.ts +11 -0
  65. package/lib/models/ownAddresses.js +31 -0
  66. package/lib/models/scanLimits.d.ts +7 -0
  67. package/lib/models/scanLimits.js +6 -0
  68. package/lib/models/stats.d.ts +7 -0
  69. package/lib/models/stats.js +6 -0
  70. package/lib/models/transaction.d.ts +10 -0
  71. package/lib/models/transaction.js +13 -0
  72. package/lib/scan.d.ts +2 -0
  73. package/lib/scan.js +31 -0
  74. package/lib/templates/logos.base64.d.ts +2 -0
  75. package/lib/templates/logos.base64.js +9 -0
  76. package/lib/templates/report.html.d.ts +1 -0
  77. package/lib/templates/report.html.js +393 -0
  78. package/lib/tsconfig.tsbuildinfo +1 -0
  79. package/lib/types.d.ts +55 -0
  80. package/lib/types.js +2 -0
  81. package/npm-shrinkwrap.json +12323 -0
  82. package/package.json +81 -0
  83. package/sonar-project.properties +15 -0
@@ -0,0 +1,15 @@
1
+ import { ScannerArguments, ScanResult } from "../types";
2
+ export declare class Scanner {
3
+ args: ScannerArguments;
4
+ scanLimits: import("../models/scanLimits").ScanLimits | undefined;
5
+ address: string | undefined;
6
+ currency: string | undefined;
7
+ testnet: boolean | undefined;
8
+ derivationMode: string | undefined;
9
+ itemToScan: string;
10
+ balanceOnly: boolean;
11
+ now: Date;
12
+ exitCode: number;
13
+ constructor(args: ScannerArguments);
14
+ scan(): Promise<ScanResult>;
15
+ }
@@ -0,0 +1,152 @@
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.Scanner = void 0;
36
+ const checkBalances = __importStar(require("./checkBalance"));
37
+ const compare = __importStar(require("./checkAddress"));
38
+ const display = __importStar(require("../display"));
39
+ const processTransactions_1 = require("./processTransactions");
40
+ const diffs_1 = require("../comparison/diffs");
41
+ const compareOperations_1 = require("../comparison/compareOperations");
42
+ const importOperations_1 = require("../input/importOperations");
43
+ const saveAnalysis_1 = require("./saveAnalysis");
44
+ const settings_1 = require("../configuration/settings");
45
+ const helpers_1 = require("../helpers");
46
+ // eslint-disable-next-line
47
+ const { version } = require("../../package.json"); // do not modify: get the version of Xpub Scan from `package.json`
48
+ class Scanner {
49
+ constructor(args) {
50
+ this.now = new Date();
51
+ this.exitCode = 0;
52
+ this.args = args;
53
+ this.scanLimits = args.scanLimits;
54
+ this.address = args.address;
55
+ this.currency = args.currency;
56
+ this.testnet = args.testnet;
57
+ this.derivationMode = args.derivationMode;
58
+ this.itemToScan = args.itemToScan; // xpub or address
59
+ this.balanceOnly = args.balanceOnly;
60
+ (0, helpers_1.init)(this.itemToScan, args.silent, args.quiet, this.currency, this.testnet, this.derivationMode);
61
+ }
62
+ scan() {
63
+ return __awaiter(this, void 0, void 0, function* () {
64
+ // library mode: suppress all outputs
65
+ if (!settings_1.configuration.commandLineMode) {
66
+ /* eslint-disable */
67
+ console.log = function () { };
68
+ /* eslint-enable */
69
+ settings_1.configuration.silent = true;
70
+ }
71
+ if (this.address) {
72
+ // mode A: `--address {address}`:
73
+ // an address has been provided by the user: check whether its belongs or not to the xpub
74
+ compare.run(this.itemToScan, this.address);
75
+ return { exitCode: this.exitCode };
76
+ }
77
+ else {
78
+ // mode B: scan mode
79
+ let importedTransactions;
80
+ if (this.args.operations) {
81
+ // a file path has been provided: import its transactions
82
+ importedTransactions = (0, importOperations_1.importOperations)(this.args.operations);
83
+ }
84
+ const scanResult = yield checkBalances.run(this.itemToScan, this.balanceOnly, this.scanLimits);
85
+ const actualAddresses = scanResult.addresses; // active addresses belonging to the xpub
86
+ const actualUTXOs = (0, processTransactions_1.getSortedUTXOS)(actualAddresses); // UTXOs (if any) belonging to the xpub
87
+ const summary = scanResult.summary; // summary: balance per derivation path
88
+ const actualTransactions = (0, processTransactions_1.getSortedOperations)(actualAddresses); // transactions related to the xpub
89
+ display.showResults(actualUTXOs, actualTransactions, summary, this.balanceOnly);
90
+ const partialScan = typeof this.scanLimits !== "undefined";
91
+ let comparisonResults;
92
+ if (typeof importedTransactions !== "undefined") {
93
+ // transactions have been imported: comparison mode enabled
94
+ // — compare imported transactions with actual ones
95
+ comparisonResults = (0, compareOperations_1.checkImportedOperations)(importedTransactions, actualTransactions, actualAddresses, // scan limits
96
+ partialScan);
97
+ }
98
+ // full v. partial scan
99
+ let mode;
100
+ if (typeof this.args.account !== "undefined" &&
101
+ typeof this.args.index !== "undefined") {
102
+ mode = `Specific derivation path - m/${this.args.account}/${this.args.index}`;
103
+ }
104
+ else if (typeof this.scanLimits !== "undefined") {
105
+ let upperLimit = "∞";
106
+ if (typeof this.scanLimits.indexTo !== "undefined") {
107
+ upperLimit = this.scanLimits.indexTo;
108
+ }
109
+ mode = `Partial range — account ${this.scanLimits.account}, indices ${this.scanLimits.indexFrom}⟶${upperLimit}`;
110
+ }
111
+ else {
112
+ mode = "Full scan";
113
+ }
114
+ // (special mode for Custom W)
115
+ if (settings_1.configuration.augmentedImport) {
116
+ // Augmented import mode:
117
+ // Use of an augmented JSON to compare smart contract interactions
118
+ mode += " | Augmented Import";
119
+ }
120
+ // balance only mode
121
+ if (this.balanceOnly) {
122
+ // Balance only mode
123
+ mode += " | Balance Only";
124
+ }
125
+ const meta = {
126
+ xpub: this.itemToScan,
127
+ date: this.now,
128
+ version,
129
+ mode,
130
+ preDerivationSize: this.args.preDerivationSize,
131
+ derivationMode: settings_1.configuration.specificDerivationMode,
132
+ balanceOnly: this.balanceOnly,
133
+ };
134
+ const data = {
135
+ summary,
136
+ addresses: actualAddresses,
137
+ transactions: actualTransactions,
138
+ comparisons: comparisonResults,
139
+ };
140
+ if (this.args.save || this.args.save === "" /* allow empty arg */) {
141
+ (0, saveAnalysis_1.save)(meta, data, this.args.save);
142
+ }
143
+ if (this.args.diff || this.args.balance || this.args.balance === "0") {
144
+ const actualBalance = summary.reduce((accumulator, s) => accumulator + s.balance.toNumber(), 0);
145
+ this.exitCode = (0, diffs_1.showDiff)(actualBalance, this.args.balance, comparisonResults, this.args.diff);
146
+ }
147
+ return { meta, data, exitCode: this.exitCode };
148
+ }
149
+ });
150
+ }
151
+ }
152
+ exports.Scanner = Scanner;
@@ -0,0 +1,19 @@
1
+ import { Address } from "../models/address";
2
+ /**
3
+ * fetch the structured basic stats related to an address
4
+ * its balance, funded and spend sums and counts
5
+ * @param address the address being analyzed
6
+ * @param balanceOnly an option to return only the balance
7
+ */
8
+ declare function getStats(address: Address, balanceOnly: boolean): Promise<void>;
9
+ /**
10
+ * get all structured transactions related to an address
11
+ * @param address the address being analyzed
12
+ */
13
+ declare function getTransactions(address: Address): void;
14
+ /**
15
+ * get all normalized transactions related to an account-based address
16
+ * @param address an account-based address (typically Ethereum)
17
+ */
18
+ declare function getAccountBasedTransactions(address: Address): void;
19
+ export { getStats, getTransactions, getAccountBasedTransactions };
@@ -0,0 +1,434 @@
1
+ "use strict";
2
+ // Here, the raw data is fetched from Crypto APIs (i.e., Crypto APIs):
3
+ // - balance,
4
+ // - total spent and received, and
5
+ // - operations
6
+ // per address
7
+ //
8
+ // Crypto APIs 2.0 <https://cryptoapis.io/>
9
+ // https://developers.cryptoapis.io/technical-documentation/general-information/overview
10
+ //
11
+ // In order to enable Crypto APIs, an API key has to be provided (see: README.md)
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.getAccountBasedTransactions = exports.getTransactions = exports.getStats = void 0;
49
+ const helpers = __importStar(require("../helpers"));
50
+ const currencies_1 = require("../configuration/currencies");
51
+ const settings_1 = require("../configuration/settings");
52
+ const transaction_1 = require("../models/transaction");
53
+ const operation_1 = require("../models/operation");
54
+ const date_fns_1 = require("date-fns");
55
+ const bchaddrjs_1 = __importDefault(require("bchaddrjs"));
56
+ const bignumber_js_1 = __importDefault(require("bignumber.js"));
57
+ const object_hash_1 = __importDefault(require("object-hash"));
58
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
59
+ // ┃ FETCH RAW DATA FROM CRYPTO APIS ┃
60
+ // ┃ just fetch the JSON responses ┃
61
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
62
+ /**
63
+ * fetch the response associated with the request (basic data, transactions)
64
+ * @param currency the currency being analyzed
65
+ * @param address the address being analyzed
66
+ * @param endpoint the endpoint to call
67
+ * @returns an array of transactions (if any)
68
+ */
69
+ function fetchPayloads(currency, address, endpoint) {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ // limit the number of transactions per request
72
+ const maxItemsPerRequest = 50;
73
+ const payloads = [];
74
+ const getTxsURLTemplate = settings_1.configuration.externalProviderURL
75
+ .concat("/addresses/")
76
+ .concat(address.toString())
77
+ .replace("{currency}", currency)
78
+ .concat(endpoint
79
+ .concat("?limit=")
80
+ .concat(maxItemsPerRequest.toString())
81
+ .concat("&offset={offset}"));
82
+ // to handle large number of transactions by address, use the index+limit logic
83
+ // offered by Crypto APIs
84
+ let offset = 0;
85
+ let itemsRemainingToBeFetched = true;
86
+ while (itemsRemainingToBeFetched) {
87
+ const url = getTxsURLTemplate.replace("{offset}", String(offset));
88
+ const txs = yield helpers.getJSON(url, settings_1.configuration.APIKey);
89
+ const payload = txs.data.items;
90
+ // when the limit includes the total number of transactions,
91
+ // no need to go further
92
+ if (payload.length === 0) {
93
+ itemsRemainingToBeFetched = false;
94
+ break;
95
+ }
96
+ payloads.push(payload);
97
+ offset += maxItemsPerRequest;
98
+ }
99
+ return payloads;
100
+ });
101
+ }
102
+ /**
103
+ * fetch the transactions associated with a transaction hash
104
+ * @param currency the currency being analyzed
105
+ * @param transactionHash a transaction hash
106
+ * @returns the transactions associated with a transaction hash
107
+ */
108
+ function fetchTransactionPayload(currency, transactionHash) {
109
+ return __awaiter(this, void 0, void 0, function* () {
110
+ const url = settings_1.configuration.externalProviderURL
111
+ .replace("{currency}", currency)
112
+ .concat("/transactions/")
113
+ .concat(transactionHash);
114
+ // important: a valid API key has to be provided
115
+ const txs = yield helpers.getJSON(url, settings_1.configuration.APIKey);
116
+ return txs.data.item;
117
+ });
118
+ }
119
+ /**
120
+ * fetch the raw basic stats (balance, transactions count...) associated with an address
121
+ * @param currency the currency being analyzed
122
+ * @param address the address being analyzed
123
+ * @returns the basic data associated with an address
124
+ */
125
+ function fetchOperationsPayloads(currency, address) {
126
+ return __awaiter(this, void 0, void 0, function* () {
127
+ return fetchPayloads(currency, address.toString(), "/transactions");
128
+ });
129
+ }
130
+ /**
131
+ * fetch the raw token-related transactions related to an address
132
+ * @param currency the currency being analyzed (account-based; typically Ethereum)
133
+ * @param address the address being analyzed (account-based; typically Ethereum)
134
+ * @returns the token-related transactions related to an address
135
+ */
136
+ function fetchTokenPayloads(currency, address) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ const rawTokenOperations = yield fetchPayloads(currency, address.toString(), "/tokens-transfers");
139
+ const tokenOperations = [].concat(...rawTokenOperations);
140
+ // augment token operations with transaction data
141
+ for (const tokenOperation of tokenOperations) {
142
+ const transaction = yield fetchTransactionPayload(currency, tokenOperation.transactionHash);
143
+ // add data related to recipients and senders
144
+ tokenOperation.recipients = transaction.recipients;
145
+ tokenOperation.senders = transaction.senders;
146
+ tokenOperation.fee = transaction.fee;
147
+ }
148
+ return tokenOperations;
149
+ });
150
+ }
151
+ /**
152
+ * fetch the raw internal transactions related to an address
153
+ * @param currency the currency being analyzed (account-based; typically Ethereum)
154
+ * @param address the address being analyzed (account-based; typically Ethereum)
155
+ * @returns the internal transactions related to an address
156
+ */
157
+ function fetchInternalTransactionsPayloads(currency, address) {
158
+ return __awaiter(this, void 0, void 0, function* () {
159
+ return fetchPayloads(currency, address.toString(), "/internal");
160
+ });
161
+ }
162
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
163
+ // ┃ NORMALIZE TRANSACTIONS FROM CRYPTO APIS ┃
164
+ // ┃ transform JSONs into stats and Operations objects ┃
165
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
166
+ /**
167
+ * fetch the structured basic stats related to an address
168
+ * its balance, funded and spend sums and counts
169
+ * @param address the address being analyzed
170
+ * @param balanceOnly an option to return only the balance
171
+ */
172
+ function getStats(address, balanceOnly) {
173
+ return __awaiter(this, void 0, void 0, function* () {
174
+ // important: currency name is required to be lower case for Crypto APIs
175
+ const currency = settings_1.configuration.currency.name.toLowerCase().replace(" ", "-");
176
+ const url = settings_1.configuration.externalProviderURL
177
+ .concat("/addresses/")
178
+ .concat(address.toString())
179
+ .replace("{currency}", currency);
180
+ const res = yield helpers.getJSON(url, settings_1.configuration.APIKey);
181
+ const item = res.data.item;
182
+ const fundedSum = item.totalReceived.amount;
183
+ const spentSum = item.totalSpent.amount;
184
+ const balance = item.confirmedBalance.amount;
185
+ const txCount = item.transactionsCount;
186
+ address.setStats(txCount, fundedSum, spentSum);
187
+ address.setBalance(balance);
188
+ // get transactions (when applicable)
189
+ if (!balanceOnly) {
190
+ let payloads = yield fetchOperationsPayloads(currency, address);
191
+ // Ethereum: add token-related and internal transactions
192
+ if (settings_1.configuration.currency.symbol === currencies_1.currencies.eth.symbol) {
193
+ payloads = payloads.concat(yield fetchTokenPayloads(currency, address));
194
+ payloads = payloads.concat(payloads, yield fetchInternalTransactionsPayloads(currency, address));
195
+ }
196
+ // flatten the payloads
197
+ const rawTransactions = [].concat(...payloads);
198
+ // Remove duplicates
199
+ // (related to a bug from Crypto APIs)
200
+ const uniqueRawTransactions = [];
201
+ for (let i = rawTransactions.length - 1; i >= 0; i--) {
202
+ const transaction = rawTransactions[i];
203
+ const h = (0, object_hash_1.default)(transaction);
204
+ if (!uniqueRawTransactions.includes(h)) {
205
+ uniqueRawTransactions.push(h);
206
+ }
207
+ else {
208
+ // remove duplicate
209
+ rawTransactions.splice(i, 1);
210
+ }
211
+ }
212
+ address.setRawTransactions(rawTransactions);
213
+ }
214
+ });
215
+ }
216
+ exports.getStats = getStats;
217
+ /**
218
+ * get all structured transactions related to an address
219
+ * @param address the address being analyzed
220
+ */
221
+ function getTransactions(address) {
222
+ const rawTransactions = address.getRawTransactions();
223
+ const transactions = [];
224
+ // Bitcoin Cash addresses are expressed as cash addresses by Crypto APIs:
225
+ // they have to be converted into legacy ones (if needed)
226
+ const processAddress = (originalAddress) => {
227
+ if (settings_1.configuration.currency.symbol === currencies_1.currencies.bch.symbol) {
228
+ return bchaddrjs_1.default.toLegacyAddress(originalAddress);
229
+ }
230
+ else {
231
+ return originalAddress;
232
+ }
233
+ };
234
+ // transforms raw transactions associated with an address
235
+ // into an array of processed transactions:
236
+ // [ { blockHeight, txid, ins: [ { address, value }... ], outs: [ { address, value }...] } ]
237
+ rawTransactions.forEach((tx) => {
238
+ const ins = [];
239
+ const outs = [];
240
+ // identify whether the address belongs to the list of transactors or not
241
+ const addressBelongsToTransactors = (transactors) => {
242
+ return transactors.some((t) => processAddress(t.address).includes(address.toString()));
243
+ };
244
+ // the address currently being analyzed is a — recipient —
245
+ if (addressBelongsToTransactors(tx.recipients)) {
246
+ for (const recipient of tx.recipients) {
247
+ if (processAddress(recipient.address).includes(address.toString())) {
248
+ // add one operation per sender
249
+ for (const sender of tx.senders) {
250
+ const op = new operation_1.Operation(String(tx.timestamp), new bignumber_js_1.default(recipient.amount));
251
+ op.setAddress(processAddress(sender.address));
252
+ op.setTxid(tx.transactionId);
253
+ op.setOperationType("Received");
254
+ ins.push(op);
255
+ }
256
+ }
257
+ }
258
+ }
259
+ // the address currently being analyzed is a — sender —
260
+ if (addressBelongsToTransactors(tx.senders)) {
261
+ for (let i = 0; i < tx.recipients.length; i++) {
262
+ const recipient = tx.recipients[i];
263
+ // note: the amount sent is specified in blockchainSpecific.vout
264
+ // _at the same index as the recipient_
265
+ const op = new operation_1.Operation(String(tx.timestamp), new bignumber_js_1.default(tx.blockchainSpecific.vout[i].value));
266
+ op.setAddress(processAddress(recipient.address));
267
+ op.setTxid(tx.transactionId);
268
+ op.setOperationType("Sent");
269
+ outs.push(op);
270
+ }
271
+ }
272
+ transactions.push(new transaction_1.Transaction(tx.minedInBlockHeight, (0, date_fns_1.format)(new Date(tx.timestamp * 1000), "yyyy-MM-dd HH:mm:ss"), tx.transactionId, ins, outs));
273
+ });
274
+ address.setTransactions(transactions);
275
+ }
276
+ exports.getTransactions = getTransactions;
277
+ /**
278
+ * get all normalized transactions related to an account-based address
279
+ * @param address an account-based address (typically Ethereum)
280
+ */
281
+ function getAccountBasedTransactions(address) {
282
+ const rawTransactions = address.getRawTransactions();
283
+ rawTransactions.forEach((tx) => {
284
+ // skip non-basic operations
285
+ if (typeof tx.blockchainSpecific === "undefined") {
286
+ return;
287
+ }
288
+ const isSender = tx.senders.some((t) => t.address.toLowerCase() === address.toString().toLowerCase());
289
+ const isRecipient = tx.recipients.some((t) => t.address.toLowerCase() === address.toString().toLowerCase());
290
+ const isFailedOperation = tx.blockchainSpecific.transactionStatus !== "0x1";
291
+ // ignore failed *incoming* transactions
292
+ if (isFailedOperation && isRecipient) {
293
+ return;
294
+ }
295
+ const timestamp = (0, date_fns_1.format)(new Date(tx.timestamp * 1000), "yyyy-MM-dd HH:mm:ss");
296
+ // the address currently being analyzed is a — recipient —
297
+ if (isRecipient) {
298
+ const amount = tx.recipients.reduce((a, b) => +a + +b.amount, 0);
299
+ const fixedAmount = amount.toFixed(settings_1.ETH_FIXED_PRECISION);
300
+ const op = new operation_1.Operation(timestamp, new bignumber_js_1.default(fixedAmount)); // ETH: use fixed-point notation
301
+ op.setAddress(address.toString());
302
+ op.setTxid(tx.transactionId);
303
+ op.setOperationType("Received");
304
+ op.setBlockNumber(tx.minedInBlockHeight);
305
+ address.addFundedOperation(op);
306
+ }
307
+ // the address currently being analyzed is a — sender —
308
+ if (isSender) {
309
+ const amount = new bignumber_js_1.default(tx.recipients.reduce((a, b) => +a + +b.amount, 0));
310
+ const fixedAmount = amount.toFixed(settings_1.ETH_FIXED_PRECISION);
311
+ const op = new operation_1.Operation(timestamp, new bignumber_js_1.default(fixedAmount)); // ETH: use fixed-point notation
312
+ op.setAddress(address.toString());
313
+ op.setTxid(tx.transactionId);
314
+ if (!isFailedOperation) {
315
+ op.setOperationType(isRecipient ? "Sent to self" : "Sent");
316
+ }
317
+ else {
318
+ // failed outgoing operation
319
+ op.setOperationType("Failed to send");
320
+ }
321
+ op.setBlockNumber(tx.minedInBlockHeight);
322
+ address.addSentOperation(op);
323
+ }
324
+ });
325
+ getTokenTransactions(address);
326
+ getInternalTransactions(address);
327
+ }
328
+ exports.getAccountBasedTransactions = getAccountBasedTransactions;
329
+ /**
330
+ * get all normalized token-related transactions associated with an account-based address
331
+ * @param address an account-based address (typically Ethereum)
332
+ */
333
+ function getTokenTransactions(address) {
334
+ const rawTransactions = address.getRawTransactions();
335
+ rawTransactions.forEach((tx) => {
336
+ // skip non-token operations
337
+ if (typeof tx.senderAddress === "undefined") {
338
+ return;
339
+ }
340
+ const isSender = tx.senderAddress.toLocaleLowerCase() ===
341
+ address.toString().toLocaleLowerCase();
342
+ const isRecipient = tx.recipientAddress.toLocaleLowerCase() ===
343
+ address.toString().toLocaleLowerCase();
344
+ const tokenAmount = new bignumber_js_1.default(tx.tokensAmount);
345
+ const tokenName = tx.tokenName;
346
+ const tokenSymbol = tx.tokenSymbol;
347
+ const timestamp = (0, date_fns_1.format)(new Date(tx.transactionTimestamp * 1000), "yyyy-MM-dd HH:mm:ss");
348
+ // compute amount
349
+ // (note: the dualities isSender/isRecipient and has sent/has received do not necessarily
350
+ // overlap (e.g., a recipient can also have sent in the swapping context)
351
+ const fees = new bignumber_js_1.default(tx.fee.amount);
352
+ let amount = new bignumber_js_1.default(0);
353
+ let hasSent = false;
354
+ // the address currently being analyzed is a — recipient —
355
+ for (const recipient of tx.recipients) {
356
+ if (recipient.address.toLocaleLowerCase() ===
357
+ address.toString().toLocaleLowerCase()) {
358
+ amount = amount.plus(recipient.amount);
359
+ }
360
+ }
361
+ // the address currently being analyzed is a — sender —
362
+ for (const sender of tx.senders) {
363
+ if (sender.address.toLocaleLowerCase() ===
364
+ address.toString().toLocaleLowerCase()) {
365
+ amount = amount.minus(sender.amount);
366
+ hasSent = true;
367
+ }
368
+ }
369
+ if (hasSent) {
370
+ amount = amount.plus(fees); // if has sent, add fees
371
+ }
372
+ // the address currently being analyzed is a — recipient —
373
+ if (isRecipient) {
374
+ const fixedAmount = amount.toFixed(settings_1.ETH_FIXED_PRECISION);
375
+ const op = new operation_1.Operation(timestamp, new bignumber_js_1.default(fixedAmount)); // ETH: use fixed-point notation
376
+ op.setAddress(address.toString());
377
+ op.setTxid(tx.transactionHash);
378
+ // operation type: if is recipient but has sent: swap operation
379
+ op.setOperationType(hasSent ? "Swapped" : "Received (token)");
380
+ op.setBlockNumber(tx.minedInBlockHeight);
381
+ op.addToken(tokenSymbol, tokenName, tokenAmount);
382
+ address.addFundedOperation(op);
383
+ }
384
+ // the address currently being analyzed is a — sender —
385
+ if (isSender) {
386
+ const fixedAmount = amount.toFixed(settings_1.ETH_FIXED_PRECISION);
387
+ const op = new operation_1.Operation(timestamp, new bignumber_js_1.default(fixedAmount)); // ETH: use fixed-point notation
388
+ op.setAddress(address.toString());
389
+ op.setTxid(tx.transactionHash);
390
+ op.setOperationType("Sent (token)");
391
+ op.setBlockNumber(tx.minedInBlockHeight);
392
+ op.addToken(tokenSymbol, tokenName, tokenAmount);
393
+ address.addSentOperation(op);
394
+ }
395
+ });
396
+ }
397
+ /**
398
+ * get all normalized internal transactions associated with an account-based address
399
+ * @param address an account-based address (typically Ethereum)
400
+ */
401
+ function getInternalTransactions(address) {
402
+ const rawTransactions = address.getRawTransactions();
403
+ rawTransactions.forEach((tx) => {
404
+ // skip non-internal transactions
405
+ if (typeof tx.operationType === "undefined") {
406
+ return;
407
+ }
408
+ const isSender = tx.sender.toLocaleLowerCase() === address.toString().toLocaleLowerCase();
409
+ const isRecipient = tx.recipient.toLocaleLowerCase() ===
410
+ address.toString().toLocaleLowerCase();
411
+ const amount = new bignumber_js_1.default(0);
412
+ const timestamp = (0, date_fns_1.format)(new Date(tx.timestamp * 1000), "yyyy-MM-dd HH:mm:ss");
413
+ // the address currently being analyzed is a — recipient —
414
+ if (isRecipient) {
415
+ const fixedAmount = amount.toFixed(settings_1.ETH_FIXED_PRECISION);
416
+ const op = new operation_1.Operation(timestamp, new bignumber_js_1.default(fixedAmount)); // ETH: use fixed-point notation
417
+ op.setAddress(address.toString());
418
+ op.setTxid(tx.parentHash);
419
+ op.setOperationType("SCI (recipient)");
420
+ op.setBlockNumber(tx.minedInBlockHeight);
421
+ address.addFundedOperation(op);
422
+ }
423
+ // the address currently being analyzed is a — sender —
424
+ if (isSender) {
425
+ const fixedAmount = amount.toFixed(settings_1.ETH_FIXED_PRECISION);
426
+ const op = new operation_1.Operation(timestamp, new bignumber_js_1.default(fixedAmount)); // ETH: use fixed-point notation
427
+ op.setAddress(address.toString());
428
+ op.setTxid(tx.parentHash);
429
+ op.setOperationType("SCI (caller)");
430
+ op.setBlockNumber(tx.minedInBlockHeight);
431
+ address.addSentOperation(op);
432
+ }
433
+ });
434
+ }
@@ -0,0 +1,23 @@
1
+ import { Address } from "../models/address";
2
+ /**
3
+ * fetch the structured basic stats related to an address
4
+ * its balance, funded and spend sums and counts
5
+ * @param address the address being analyzed
6
+ */
7
+ declare function getStats(address: Address): Promise<void>;
8
+ /**
9
+ * get all structured transactions related to an address
10
+ * @param address the address being analyzed
11
+ */
12
+ declare function getTransactions(address: Address): void;
13
+ /**
14
+ * get all structured transactions related to a Bitcoin Cash address
15
+ * @param address the address being analyzed
16
+ */
17
+ declare function getBitcoinCashTransactions(address: Address): void;
18
+ /**
19
+ * get all structured transactions related to an account-based address (generally Ethereum)
20
+ * @param address the address being analyzed
21
+ */
22
+ declare function getAccountBasedTransactions(address: Address): void;
23
+ export { getStats, getTransactions, getBitcoinCashTransactions, getAccountBasedTransactions, };