@pioneer-platform/pioneer-history 0.2.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @pioneer-platform/pioneer-history
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - chore: feat(pioneer-sdk): Add ERC-20/BEP-20 token table to portfolio dashboard
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @pioneer-platform/cosmos-network@8.27.0
13
+ - @pioneer-platform/eth-network@8.31.0
14
+ - @pioneer-platform/utxo-network@8.28.0
15
+
16
+ ## 0.3.0
17
+
18
+ ### Minor Changes
19
+
20
+ - feat(pioneer-sdk): Add ERC-20/BEP-20 token table to portfolio dashboard
21
+
3
22
  ## 0.2.0
4
23
 
5
24
  ### Minor Changes
package/lib/index.d.ts CHANGED
@@ -42,6 +42,8 @@ export declare class History {
42
42
  private blockchains;
43
43
  private nodes;
44
44
  private networks;
45
+ private swapDetector;
46
+ private pendingSwapsService;
45
47
  constructor(config: any);
46
48
  init(): Promise<boolean>;
47
49
  classifyCaip(caip: string): Promise<string>;
package/lib/index.js CHANGED
@@ -71,6 +71,8 @@ exports.History = exports.SUPPORTED_CAIPS = exports.OTHER_SUPPORT = exports.TEND
71
71
  var TAG = " | pioneer-history | ";
72
72
  var log = require('@pioneer-platform/loggerdog')();
73
73
  var caipToNetworkId = require('@pioneer-platform/pioneer-caip').caipToNetworkId;
74
+ // Swap detection
75
+ var swap_detector_1 = require("./services/swap-detector");
74
76
  // CAIP support constants (reuse from pioneer-balance)
75
77
  exports.UTXO_SUPPORT = [
76
78
  'bip122:000000000019d6689c085ae165831e93/slip44:0', // BTC
@@ -99,9 +101,19 @@ exports.SUPPORTED_CAIPS = {
99
101
  __exportStar(require("./schemas/history-response"), exports);
100
102
  var History = /** @class */ (function () {
101
103
  function History(config) {
104
+ this.swapDetector = null;
102
105
  log.debug(TAG, "🏗️ History constructor called");
103
106
  this.blockchains = config.wss || [];
104
107
  this.nodes = config.nodes || [];
108
+ this.pendingSwapsService = config.pendingSwapsService || null;
109
+ // Initialize swap detector with pendingSwapsService (enables Layer 1 DB lookup)
110
+ this.swapDetector = new swap_detector_1.SwapDetector(this.pendingSwapsService, log);
111
+ if (this.pendingSwapsService) {
112
+ log.info(TAG, "✅ SwapDetector initialized with DB service (Layer 1 enabled)");
113
+ }
114
+ else {
115
+ log.warn(TAG, "⚠️ SwapDetector initialized without DB service (Layer 1 disabled - only memo/contract detection active)");
116
+ }
105
117
  // CRITICAL: Use globally initialized networks if available (server environment)
106
118
  var globalNetworks = global.pioneerNetworks;
107
119
  if (globalNetworks) {
@@ -373,9 +385,9 @@ var History = /** @class */ (function () {
373
385
  };
374
386
  History.prototype.getHistory = function (query, options) {
375
387
  return __awaiter(this, void 0, void 0, function () {
376
- var tag, queries, results, _i, queries_1, q, networkId, type, asset, pubkey, _a, networkIdToSymbol, coin, rawTxs, rawTxs, rawTxs, rawTxs, error_1;
377
- return __generator(this, function (_b) {
378
- switch (_b.label) {
388
+ var tag, queries, results, _i, queries_1, q, networkId, type, asset, pubkey, _a, networkIdToSymbol, coin, rawTxs, rawTxs, rawTxs, rawTxs, error_1, allTransactions, _b, results_1, result, swapMetadata, _c, results_2, result, _d, _e, tx, metadata, error_2;
389
+ return __generator(this, function (_f) {
390
+ switch (_f.label) {
379
391
  case 0:
380
392
  tag = TAG + " | getHistory | ";
381
393
  log.debug(tag, "🔍 HISTORY QUERY START");
@@ -383,7 +395,7 @@ var History = /** @class */ (function () {
383
395
  log.debug(tag, "Processing ".concat(queries.length, " ").concat(queries.length === 1 ? 'query' : 'queries'));
384
396
  results = [];
385
397
  _i = 0, queries_1 = queries;
386
- _b.label = 1;
398
+ _f.label = 1;
387
399
  case 1:
388
400
  if (!(_i < queries_1.length)) return [3 /*break*/, 30];
389
401
  q = queries_1[_i];
@@ -396,13 +408,13 @@ var History = /** @class */ (function () {
396
408
  networkId = caipToNetworkId(q.caip);
397
409
  return [4 /*yield*/, this.classifyCaip(q.caip)];
398
410
  case 2:
399
- type = _b.sent();
411
+ type = _f.sent();
400
412
  log.debug(tag, "🎯 Classified CAIP type:", type);
401
413
  asset = { caip: q.caip };
402
414
  pubkey = { pubkey: q.pubkey };
403
- _b.label = 3;
415
+ _f.label = 3;
404
416
  case 3:
405
- _b.trys.push([3, 28, , 29]);
417
+ _f.trys.push([3, 28, , 29]);
406
418
  _a = type;
407
419
  switch (_a) {
408
420
  case 'UTXO': return [3 /*break*/, 4];
@@ -428,7 +440,7 @@ var History = /** @class */ (function () {
428
440
  log.debug(tag, 'Fetching UTXO transactions for', coin);
429
441
  return [4 /*yield*/, this.networks.networks.utxo.txsByXpub(coin, pubkey.pubkey)];
430
442
  case 5:
431
- rawTxs = _b.sent();
443
+ rawTxs = _f.sent();
432
444
  log.debug(tag, "Received ".concat((rawTxs === null || rawTxs === void 0 ? void 0 : rawTxs.length) || 0, " UTXO transactions"));
433
445
  asset.transactions = this.normalizeUtxoTransactions(rawTxs || [], pubkey.pubkey, q.caip);
434
446
  results.push(asset);
@@ -438,14 +450,14 @@ var History = /** @class */ (function () {
438
450
  if (!this.networks.networks.ethereum.getTransactionsByNetwork) return [3 /*break*/, 8];
439
451
  return [4 /*yield*/, this.networks.networks.ethereum.getTransactionsByNetwork(networkId, pubkey.pubkey, options)];
440
452
  case 7:
441
- rawTxs = _b.sent();
453
+ rawTxs = _f.sent();
442
454
  log.debug(tag, "Received ".concat((rawTxs === null || rawTxs === void 0 ? void 0 : rawTxs.length) || 0, " EVM transactions"));
443
455
  asset.transactions = this.normalizeEvmTransactions(rawTxs || [], pubkey.pubkey, q.caip);
444
456
  return [3 /*break*/, 9];
445
457
  case 8:
446
458
  log.warn(tag, 'EVM transaction history not available - method not found');
447
459
  asset.transactions = [];
448
- _b.label = 9;
460
+ _f.label = 9;
449
461
  case 9:
450
462
  results.push(asset);
451
463
  return [3 /*break*/, 27];
@@ -456,32 +468,32 @@ var History = /** @class */ (function () {
456
468
  if (!this.networks.networks.mayachain.getTransactions) return [3 /*break*/, 12];
457
469
  return [4 /*yield*/, this.networks.networks.mayachain.getTransactions(pubkey.pubkey)];
458
470
  case 11:
459
- rawTxs = _b.sent();
460
- _b.label = 12;
471
+ rawTxs = _f.sent();
472
+ _f.label = 12;
461
473
  case 12: return [3 /*break*/, 21];
462
474
  case 13:
463
475
  if (!q.caip.startsWith('cosmos:osmosis-1')) return [3 /*break*/, 16];
464
476
  if (!this.networks.networks.osmosis.getTransactions) return [3 /*break*/, 15];
465
477
  return [4 /*yield*/, this.networks.networks.osmosis.getTransactions(pubkey.pubkey)];
466
478
  case 14:
467
- rawTxs = _b.sent();
468
- _b.label = 15;
479
+ rawTxs = _f.sent();
480
+ _f.label = 15;
469
481
  case 15: return [3 /*break*/, 21];
470
482
  case 16:
471
483
  if (!q.caip.startsWith('cosmos:thorchain-mainnet-v1')) return [3 /*break*/, 19];
472
484
  if (!this.networks.networks.thorchain.getTransactions) return [3 /*break*/, 18];
473
485
  return [4 /*yield*/, this.networks.networks.thorchain.getTransactions(pubkey.pubkey)];
474
486
  case 17:
475
- rawTxs = _b.sent();
476
- _b.label = 18;
487
+ rawTxs = _f.sent();
488
+ _f.label = 18;
477
489
  case 18: return [3 /*break*/, 21];
478
490
  case 19:
479
491
  if (!q.caip.startsWith('cosmos:cosmoshub-4')) return [3 /*break*/, 21];
480
492
  if (!this.networks.networks.cosmos.getTransactions) return [3 /*break*/, 21];
481
493
  return [4 /*yield*/, this.networks.networks.cosmos.getTransactions(pubkey.pubkey)];
482
494
  case 20:
483
- rawTxs = _b.sent();
484
- _b.label = 21;
495
+ rawTxs = _f.sent();
496
+ _f.label = 21;
485
497
  case 21:
486
498
  log.debug(tag, "Received ".concat((rawTxs === null || rawTxs === void 0 ? void 0 : rawTxs.length) || 0, " Tendermint transactions"));
487
499
  asset.transactions = this.normalizeTendermintTransactions(rawTxs || [], pubkey.pubkey, q.caip);
@@ -493,21 +505,21 @@ var History = /** @class */ (function () {
493
505
  if (!this.networks.networks.ripple.getTransactions) return [3 /*break*/, 24];
494
506
  return [4 /*yield*/, this.networks.networks.ripple.getTransactions(pubkey.pubkey)];
495
507
  case 23:
496
- rawTxs = _b.sent();
508
+ rawTxs = _f.sent();
497
509
  log.debug(tag, "Received ".concat((rawTxs === null || rawTxs === void 0 ? void 0 : rawTxs.length) || 0, " Ripple transactions"));
498
510
  asset.transactions = this.normalizeRippleTransactions(rawTxs || [], pubkey.pubkey, q.caip);
499
511
  return [3 /*break*/, 25];
500
512
  case 24:
501
513
  log.warn(tag, 'Ripple transaction history not available');
502
514
  asset.transactions = [];
503
- _b.label = 25;
515
+ _f.label = 25;
504
516
  case 25:
505
517
  results.push(asset);
506
518
  return [3 /*break*/, 27];
507
519
  case 26: throw new Error("FAIL FAST: Unsupported chain type: ".concat(type));
508
520
  case 27: return [3 /*break*/, 29];
509
521
  case 28:
510
- error_1 = _b.sent();
522
+ error_1 = _f.sent();
511
523
  log.error(tag, "Error fetching history for ".concat(q.caip, ":"), error_1);
512
524
  // Add empty result so batch processing continues
513
525
  asset.transactions = [];
@@ -518,6 +530,43 @@ var History = /** @class */ (function () {
518
530
  _i++;
519
531
  return [3 /*break*/, 1];
520
532
  case 30:
533
+ if (!(this.swapDetector && results.length > 0)) return [3 /*break*/, 35];
534
+ _f.label = 31;
535
+ case 31:
536
+ _f.trys.push([31, 34, , 35]);
537
+ allTransactions = [];
538
+ for (_b = 0, results_1 = results; _b < results_1.length; _b++) {
539
+ result = results_1[_b];
540
+ if (result.transactions && Array.isArray(result.transactions)) {
541
+ allTransactions.push.apply(allTransactions, result.transactions);
542
+ }
543
+ }
544
+ if (!(allTransactions.length > 0)) return [3 /*break*/, 33];
545
+ log.debug(tag, "Running swap detection on ".concat(allTransactions.length, " transactions"));
546
+ return [4 /*yield*/, this.swapDetector.detectSwaps(allTransactions)];
547
+ case 32:
548
+ swapMetadata = _f.sent();
549
+ // Enrich each transaction with swap metadata
550
+ for (_c = 0, results_2 = results; _c < results_2.length; _c++) {
551
+ result = results_2[_c];
552
+ if (result.transactions && Array.isArray(result.transactions)) {
553
+ for (_d = 0, _e = result.transactions; _d < _e.length; _d++) {
554
+ tx = _e[_d];
555
+ metadata = swapMetadata.get(tx.txid);
556
+ if (metadata && metadata.isSwap) {
557
+ tx.swapMetadata = metadata;
558
+ }
559
+ }
560
+ }
561
+ }
562
+ log.debug(tag, "Enriched ".concat(swapMetadata.size, " swap transactions"));
563
+ _f.label = 33;
564
+ case 33: return [3 /*break*/, 35];
565
+ case 34:
566
+ error_2 = _f.sent();
567
+ log.error(tag, 'Swap detection failed (non-critical):', error_2.message);
568
+ return [3 /*break*/, 35];
569
+ case 35:
521
570
  log.debug(tag, "✅ HISTORY QUERY COMPLETE");
522
571
  // Return single result if single query, array if batch
523
572
  return [2 /*return*/, Array.isArray(query) ? results : results[0]];
@@ -1,4 +1,29 @@
1
1
  import { z } from 'zod';
2
+ /**
3
+ * Swap metadata schema
4
+ */
5
+ export declare const SwapMetadataSchema: z.ZodObject<{
6
+ isSwap: z.ZodBoolean;
7
+ swapId: z.ZodOptional<z.ZodString>;
8
+ protocol: z.ZodOptional<z.ZodString>;
9
+ status: z.ZodOptional<z.ZodEnum<{
10
+ PENDING: "PENDING";
11
+ CONFIRMING: "CONFIRMING";
12
+ COMPLETED: "COMPLETED";
13
+ FAILED: "FAILED";
14
+ REFUNDED: "REFUNDED";
15
+ }>>;
16
+ fromAsset: z.ZodOptional<z.ZodString>;
17
+ toAsset: z.ZodOptional<z.ZodString>;
18
+ fromAmount: z.ZodOptional<z.ZodString>;
19
+ toAmount: z.ZodOptional<z.ZodString>;
20
+ destinationAddress: z.ZodOptional<z.ZodString>;
21
+ minimumReceived: z.ZodOptional<z.ZodString>;
22
+ userMessage: z.ZodOptional<z.ZodString>;
23
+ estimatedCompletionTime: z.ZodOptional<z.ZodNumber>;
24
+ inboundTxHash: z.ZodOptional<z.ZodString>;
25
+ outboundTxHash: z.ZodOptional<z.ZodString>;
26
+ }, z.core.$strip>;
2
27
  /**
3
28
  * Transaction schema for API responses
4
29
  */
@@ -34,6 +59,28 @@ export declare const TransactionSchema: z.ZodObject<{
34
59
  fee: z.ZodOptional<z.ZodString>;
35
60
  memo: z.ZodOptional<z.ZodString>;
36
61
  raw: z.ZodOptional<z.ZodAny>;
62
+ swapMetadata: z.ZodOptional<z.ZodObject<{
63
+ isSwap: z.ZodBoolean;
64
+ swapId: z.ZodOptional<z.ZodString>;
65
+ protocol: z.ZodOptional<z.ZodString>;
66
+ status: z.ZodOptional<z.ZodEnum<{
67
+ PENDING: "PENDING";
68
+ CONFIRMING: "CONFIRMING";
69
+ COMPLETED: "COMPLETED";
70
+ FAILED: "FAILED";
71
+ REFUNDED: "REFUNDED";
72
+ }>>;
73
+ fromAsset: z.ZodOptional<z.ZodString>;
74
+ toAsset: z.ZodOptional<z.ZodString>;
75
+ fromAmount: z.ZodOptional<z.ZodString>;
76
+ toAmount: z.ZodOptional<z.ZodString>;
77
+ destinationAddress: z.ZodOptional<z.ZodString>;
78
+ minimumReceived: z.ZodOptional<z.ZodString>;
79
+ userMessage: z.ZodOptional<z.ZodString>;
80
+ estimatedCompletionTime: z.ZodOptional<z.ZodNumber>;
81
+ inboundTxHash: z.ZodOptional<z.ZodString>;
82
+ outboundTxHash: z.ZodOptional<z.ZodString>;
83
+ }, z.core.$strip>>;
37
84
  }, z.core.$strip>;
38
85
  /**
39
86
  * Individual history result schema
@@ -73,6 +120,28 @@ export declare const HistoryResultSchema: z.ZodObject<{
73
120
  fee: z.ZodOptional<z.ZodString>;
74
121
  memo: z.ZodOptional<z.ZodString>;
75
122
  raw: z.ZodOptional<z.ZodAny>;
123
+ swapMetadata: z.ZodOptional<z.ZodObject<{
124
+ isSwap: z.ZodBoolean;
125
+ swapId: z.ZodOptional<z.ZodString>;
126
+ protocol: z.ZodOptional<z.ZodString>;
127
+ status: z.ZodOptional<z.ZodEnum<{
128
+ PENDING: "PENDING";
129
+ CONFIRMING: "CONFIRMING";
130
+ COMPLETED: "COMPLETED";
131
+ FAILED: "FAILED";
132
+ REFUNDED: "REFUNDED";
133
+ }>>;
134
+ fromAsset: z.ZodOptional<z.ZodString>;
135
+ toAsset: z.ZodOptional<z.ZodString>;
136
+ fromAmount: z.ZodOptional<z.ZodString>;
137
+ toAmount: z.ZodOptional<z.ZodString>;
138
+ destinationAddress: z.ZodOptional<z.ZodString>;
139
+ minimumReceived: z.ZodOptional<z.ZodString>;
140
+ userMessage: z.ZodOptional<z.ZodString>;
141
+ estimatedCompletionTime: z.ZodOptional<z.ZodNumber>;
142
+ inboundTxHash: z.ZodOptional<z.ZodString>;
143
+ outboundTxHash: z.ZodOptional<z.ZodString>;
144
+ }, z.core.$strip>>;
76
145
  }, z.core.$strip>>;
77
146
  fetchedAt: z.ZodNumber;
78
147
  fetchedAtISO: z.ZodString;
@@ -120,6 +189,28 @@ export declare const TransactionHistoryResponseSchema: z.ZodObject<{
120
189
  fee: z.ZodOptional<z.ZodString>;
121
190
  memo: z.ZodOptional<z.ZodString>;
122
191
  raw: z.ZodOptional<z.ZodAny>;
192
+ swapMetadata: z.ZodOptional<z.ZodObject<{
193
+ isSwap: z.ZodBoolean;
194
+ swapId: z.ZodOptional<z.ZodString>;
195
+ protocol: z.ZodOptional<z.ZodString>;
196
+ status: z.ZodOptional<z.ZodEnum<{
197
+ PENDING: "PENDING";
198
+ CONFIRMING: "CONFIRMING";
199
+ COMPLETED: "COMPLETED";
200
+ FAILED: "FAILED";
201
+ REFUNDED: "REFUNDED";
202
+ }>>;
203
+ fromAsset: z.ZodOptional<z.ZodString>;
204
+ toAsset: z.ZodOptional<z.ZodString>;
205
+ fromAmount: z.ZodOptional<z.ZodString>;
206
+ toAmount: z.ZodOptional<z.ZodString>;
207
+ destinationAddress: z.ZodOptional<z.ZodString>;
208
+ minimumReceived: z.ZodOptional<z.ZodString>;
209
+ userMessage: z.ZodOptional<z.ZodString>;
210
+ estimatedCompletionTime: z.ZodOptional<z.ZodNumber>;
211
+ inboundTxHash: z.ZodOptional<z.ZodString>;
212
+ outboundTxHash: z.ZodOptional<z.ZodString>;
213
+ }, z.core.$strip>>;
123
214
  }, z.core.$strip>>;
124
215
  fetchedAt: z.ZodNumber;
125
216
  fetchedAtISO: z.ZodString;
@@ -1,7 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TransactionHistoryResponseSchema = exports.HistoryResultSchema = exports.TransactionSchema = void 0;
3
+ exports.TransactionHistoryResponseSchema = exports.HistoryResultSchema = exports.TransactionSchema = exports.SwapMetadataSchema = void 0;
4
4
  var zod_1 = require("zod");
5
+ /**
6
+ * Swap metadata schema
7
+ */
8
+ exports.SwapMetadataSchema = zod_1.z.object({
9
+ isSwap: zod_1.z.boolean(),
10
+ swapId: zod_1.z.string().optional(),
11
+ protocol: zod_1.z.string().optional(),
12
+ status: zod_1.z.enum(['PENDING', 'CONFIRMING', 'COMPLETED', 'FAILED', 'REFUNDED']).optional(),
13
+ fromAsset: zod_1.z.string().optional(),
14
+ toAsset: zod_1.z.string().optional(),
15
+ fromAmount: zod_1.z.string().optional(),
16
+ toAmount: zod_1.z.string().optional(),
17
+ destinationAddress: zod_1.z.string().optional(),
18
+ minimumReceived: zod_1.z.string().optional(),
19
+ userMessage: zod_1.z.string().optional(),
20
+ estimatedCompletionTime: zod_1.z.number().optional(),
21
+ inboundTxHash: zod_1.z.string().optional(),
22
+ outboundTxHash: zod_1.z.string().optional(),
23
+ });
5
24
  /**
6
25
  * Transaction schema for API responses
7
26
  */
@@ -21,6 +40,7 @@ exports.TransactionSchema = zod_1.z.object({
21
40
  fee: zod_1.z.string().optional(),
22
41
  memo: zod_1.z.string().optional(),
23
42
  raw: zod_1.z.any().optional(),
43
+ swapMetadata: exports.SwapMetadataSchema.optional(),
24
44
  });
25
45
  /**
26
46
  * Individual history result schema
@@ -0,0 +1,54 @@
1
+ import type { Transaction } from '../index';
2
+ /**
3
+ * Swap metadata attached to transactions
4
+ */
5
+ export interface SwapMetadata {
6
+ isSwap: boolean;
7
+ swapId?: string;
8
+ protocol?: string;
9
+ status?: 'PENDING' | 'CONFIRMING' | 'COMPLETED' | 'FAILED' | 'REFUNDED';
10
+ fromAsset?: string;
11
+ toAsset?: string;
12
+ fromAmount?: string;
13
+ toAmount?: string;
14
+ destinationAddress?: string;
15
+ minimumReceived?: string;
16
+ inboundTxHash?: string;
17
+ outboundTxHash?: string;
18
+ userMessage?: string;
19
+ estimatedCompletionTime?: number;
20
+ }
21
+ /**
22
+ * Detect if transaction is a swap and enrich with metadata
23
+ */
24
+ export declare class SwapDetector {
25
+ private pendingSwapsService;
26
+ private log;
27
+ constructor(pendingSwapsService: any, log: any);
28
+ /**
29
+ * Detect swaps in batch of transactions
30
+ */
31
+ detectSwaps(transactions: Transaction[]): Promise<Map<string, SwapMetadata>>;
32
+ /**
33
+ * Parse Thorchain/Maya memo format using pioneer-coins parseMemo
34
+ * Handles both verbose (SWAP:asset:address) and shorthand (=:asset:address) formats
35
+ */
36
+ private parseMemo;
37
+ /**
38
+ * Detect DEX contract addresses
39
+ */
40
+ private detectDexContract;
41
+ /**
42
+ * Generate user-friendly status message
43
+ */
44
+ private generateUserMessage;
45
+ /**
46
+ * Estimate completion time based on swap status
47
+ */
48
+ private estimateCompletionTime;
49
+ /**
50
+ * Query Midgard API for outbound transaction hash
51
+ * Queries both THORChain and Maya Midgard endpoints
52
+ */
53
+ private queryMidgardForOutbound;
54
+ }
@@ -0,0 +1,353 @@
1
+ "use strict";
2
+ /*
3
+ Swap Detection Service
4
+
5
+ Multi-layered detection strategy for identifying swap transactions:
6
+ 1. Pending Swaps DB Lookup (PRIMARY - highest accuracy)
7
+ 2. Memo Parsing (FALLBACK - Thorchain/Maya format detection)
8
+ 3. Contract Address Detection (EVM ONLY - DEX router matching)
9
+ */
10
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
11
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
12
+ return new (P || (P = Promise))(function (resolve, reject) {
13
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
14
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
15
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
16
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
17
+ });
18
+ };
19
+ var __generator = (this && this.__generator) || function (thisArg, body) {
20
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
21
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
22
+ function verb(n) { return function (v) { return step([n, v]); }; }
23
+ function step(op) {
24
+ if (f) throw new TypeError("Generator is already executing.");
25
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
26
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
27
+ if (y = 0, t) op = [op[0] & 2, t.value];
28
+ switch (op[0]) {
29
+ case 0: case 1: t = op; break;
30
+ case 4: _.label++; return { value: op[1], done: false };
31
+ case 5: _.label++; y = op[1]; op = [0]; continue;
32
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
33
+ default:
34
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
35
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
36
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
37
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
38
+ if (t[2]) _.ops.pop();
39
+ _.trys.pop(); continue;
40
+ }
41
+ op = body.call(thisArg, _);
42
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
43
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
44
+ }
45
+ };
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.SwapDetector = void 0;
48
+ var TAG = ' | swap-detector | ';
49
+ /**
50
+ * Detect if transaction is a swap and enrich with metadata
51
+ */
52
+ var SwapDetector = /** @class */ (function () {
53
+ function SwapDetector(pendingSwapsService, log) {
54
+ this.pendingSwapsService = pendingSwapsService;
55
+ this.log = log;
56
+ }
57
+ /**
58
+ * Detect swaps in batch of transactions
59
+ */
60
+ SwapDetector.prototype.detectSwaps = function (transactions) {
61
+ return __awaiter(this, void 0, void 0, function () {
62
+ var tag, swapMetadata, txids, pendingSwaps, _i, pendingSwaps_1, swap, metadata, error_1, _a, transactions_1, tx, memoMetadata, swapsNeedingOutbound, outboundPromises, _b, transactions_2, tx, contractMetadata;
63
+ var _this = this;
64
+ var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
65
+ return __generator(this, function (_s) {
66
+ switch (_s.label) {
67
+ case 0:
68
+ tag = TAG + ' | detectSwaps | ';
69
+ swapMetadata = new Map();
70
+ txids = transactions.map(function (tx) { return tx.txid; });
71
+ if (!this.pendingSwapsService) return [3 /*break*/, 4];
72
+ _s.label = 1;
73
+ case 1:
74
+ _s.trys.push([1, 3, , 4]);
75
+ console.log(tag, "\uD83D\uDD0D Running swap detection on ".concat(txids.length, " transactions"));
76
+ return [4 /*yield*/, this.pendingSwapsService.findByTxids(txids)];
77
+ case 2:
78
+ pendingSwaps = _s.sent();
79
+ console.log(tag, "\u2705 DB lookup found ".concat(pendingSwaps.length, " swaps"));
80
+ for (_i = 0, pendingSwaps_1 = pendingSwaps; _i < pendingSwaps_1.length; _i++) {
81
+ swap = pendingSwaps_1[_i];
82
+ metadata = {
83
+ isSwap: true,
84
+ swapId: ((_c = swap._id) === null || _c === void 0 ? void 0 : _c.toString()) || swap._id,
85
+ protocol: swap.integration,
86
+ status: swap.status.toUpperCase(),
87
+ // Use CAIPs directly - more exact than symbols
88
+ fromAsset: ((_d = swap.sellAsset) === null || _d === void 0 ? void 0 : _d.caip) || ((_e = swap.sellAsset) === null || _e === void 0 ? void 0 : _e.symbol),
89
+ toAsset: ((_f = swap.buyAsset) === null || _f === void 0 ? void 0 : _f.caip) || ((_g = swap.buyAsset) === null || _g === void 0 ? void 0 : _g.symbol),
90
+ fromAmount: (_h = swap.sellAsset) === null || _h === void 0 ? void 0 : _h.amount,
91
+ toAmount: ((_j = swap.buyAsset) === null || _j === void 0 ? void 0 : _j.amount) || ((_k = swap.quote) === null || _k === void 0 ? void 0 : _k.expectedAmountOut),
92
+ destinationAddress: (_l = swap.buyAsset) === null || _l === void 0 ? void 0 : _l.address,
93
+ minimumReceived: (_m = swap.quote) === null || _m === void 0 ? void 0 : _m.minimumAmountOut,
94
+ userMessage: this.generateUserMessage(swap),
95
+ estimatedCompletionTime: this.estimateCompletionTime(swap),
96
+ inboundTxHash: ((_o = swap.thorchainData) === null || _o === void 0 ? void 0 : _o.inboundTxHash) || swap.txHash,
97
+ outboundTxHash: (_p = swap.thorchainData) === null || _p === void 0 ? void 0 : _p.outboundTxHash
98
+ };
99
+ // Map swap to all related transaction hashes
100
+ // Inbound transaction
101
+ if (swap.txHash) {
102
+ swapMetadata.set(swap.txHash, metadata);
103
+ }
104
+ // Thorchain inbound (if different)
105
+ if (((_q = swap.thorchainData) === null || _q === void 0 ? void 0 : _q.inboundTxHash) && swap.thorchainData.inboundTxHash !== swap.txHash) {
106
+ swapMetadata.set(swap.thorchainData.inboundTxHash, metadata);
107
+ }
108
+ // Thorchain outbound
109
+ if ((_r = swap.thorchainData) === null || _r === void 0 ? void 0 : _r.outboundTxHash) {
110
+ swapMetadata.set(swap.thorchainData.outboundTxHash, metadata);
111
+ }
112
+ }
113
+ this.log.info(tag, "DB lookup found ".concat(pendingSwaps.length, " swaps"));
114
+ return [3 /*break*/, 4];
115
+ case 3:
116
+ error_1 = _s.sent();
117
+ this.log.error(tag, 'DB lookup failed (non-critical):', error_1.message);
118
+ return [3 /*break*/, 4];
119
+ case 4:
120
+ // Layer 2: Memo Parsing (FALLBACK for Thorchain/Maya)
121
+ for (_a = 0, transactions_1 = transactions; _a < transactions_1.length; _a++) {
122
+ tx = transactions_1[_a];
123
+ // Skip if already detected from DB
124
+ if (swapMetadata.has(tx.txid))
125
+ continue;
126
+ if (tx.memo) {
127
+ memoMetadata = this.parseMemo(tx.memo);
128
+ if (memoMetadata.isSwap) {
129
+ // Set inbound txHash
130
+ memoMetadata.inboundTxHash = tx.txid;
131
+ swapMetadata.set(tx.txid, memoMetadata);
132
+ }
133
+ }
134
+ }
135
+ // Layer 2.5: Recursive Outbound Lookup for Memo-Detected Swaps
136
+ // Query Midgard for outbound transactions for all memo-detected swaps
137
+ console.log(tag, "\uD83D\uDD0D Running recursive outbound lookup for ".concat(swapMetadata.size, " detected swaps"));
138
+ swapsNeedingOutbound = Array.from(swapMetadata.entries()).filter(function (_a) {
139
+ var txid = _a[0], metadata = _a[1];
140
+ return metadata.isSwap && !metadata.outboundTxHash;
141
+ });
142
+ if (!(swapsNeedingOutbound.length > 0)) return [3 /*break*/, 6];
143
+ console.log(tag, "\uD83D\uDCE1 Querying Midgard for ".concat(swapsNeedingOutbound.length, " swaps"));
144
+ outboundPromises = swapsNeedingOutbound.map(function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
145
+ var outboundTxHash, error_2;
146
+ var txid = _b[0], metadata = _b[1];
147
+ return __generator(this, function (_c) {
148
+ switch (_c.label) {
149
+ case 0:
150
+ _c.trys.push([0, 2, , 3]);
151
+ return [4 /*yield*/, this.queryMidgardForOutbound(txid)];
152
+ case 1:
153
+ outboundTxHash = _c.sent();
154
+ if (outboundTxHash) {
155
+ console.log(tag, "\u2705 Found outbound tx for ".concat(txid.substring(0, 10), "...: ").concat(outboundTxHash.substring(0, 10), "..."));
156
+ // Update metadata with outbound txHash
157
+ metadata.outboundTxHash = outboundTxHash;
158
+ metadata.status = 'COMPLETED';
159
+ metadata.userMessage = 'Swap completed successfully';
160
+ }
161
+ return [3 /*break*/, 3];
162
+ case 2:
163
+ error_2 = _c.sent();
164
+ console.log(tag, "\u26A0\uFE0F Midgard lookup failed for ".concat(txid.substring(0, 10), "...: ").concat(error_2.message));
165
+ return [3 /*break*/, 3];
166
+ case 3: return [2 /*return*/];
167
+ }
168
+ });
169
+ }); });
170
+ return [4 /*yield*/, Promise.all(outboundPromises)];
171
+ case 5:
172
+ _s.sent();
173
+ console.log(tag, "\u2705 Recursive outbound lookup complete");
174
+ _s.label = 6;
175
+ case 6:
176
+ // Layer 3: Contract Address Detection (EVM ONLY)
177
+ for (_b = 0, transactions_2 = transactions; _b < transactions_2.length; _b++) {
178
+ tx = transactions_2[_b];
179
+ // Skip if already detected
180
+ if (swapMetadata.has(tx.txid))
181
+ continue;
182
+ // Only for EVM chains
183
+ if (tx.networkId && tx.networkId.startsWith('eip155:')) {
184
+ contractMetadata = this.detectDexContract(tx);
185
+ if (contractMetadata.isSwap) {
186
+ swapMetadata.set(tx.txid, contractMetadata);
187
+ }
188
+ }
189
+ }
190
+ console.log(tag, "\u2705 Total swaps detected: ".concat(swapMetadata.size));
191
+ this.log.info(tag, "Total swaps detected: ".concat(swapMetadata.size));
192
+ return [2 /*return*/, swapMetadata];
193
+ }
194
+ });
195
+ });
196
+ };
197
+ /**
198
+ * Parse Thorchain/Maya memo format using pioneer-coins parseMemo
199
+ * Handles both verbose (SWAP:asset:address) and shorthand (=:asset:address) formats
200
+ */
201
+ SwapDetector.prototype.parseMemo = function (memo) {
202
+ // TODO: Use parseMemo from @pioneer-platform/pioneer-coins instead of custom implementation
203
+ // For now, keep basic pattern matching until we can import it properly
204
+ // Try verbose format: SWAP:asset:address
205
+ var verbosePattern = /^SWAP:([^:]+):([^:]+)(?::(\d+))?/i;
206
+ var match = memo.match(verbosePattern);
207
+ // Try shorthand format: =:asset:address
208
+ if (!match) {
209
+ var shorthandPattern = /^=:([^:]+):([^:]+)(?::(\d+))?/;
210
+ match = memo.match(shorthandPattern);
211
+ }
212
+ if (match) {
213
+ // Return raw asset code/CAIP - let display layer handle conversion
214
+ var toAsset = match[1]; // Could be 'e', 'ETH', or full CAIP
215
+ return {
216
+ isSwap: true,
217
+ protocol: 'thorchain',
218
+ toAsset: toAsset, // Return raw value
219
+ destinationAddress: match[2],
220
+ minimumReceived: match[3],
221
+ userMessage: 'Swap detected from transaction memo'
222
+ };
223
+ }
224
+ return { isSwap: false };
225
+ };
226
+ /**
227
+ * Detect DEX contract addresses
228
+ */
229
+ SwapDetector.prototype.detectDexContract = function (tx) {
230
+ var _a;
231
+ var knownDexContracts = [
232
+ '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'.toLowerCase(), // Uniswap V2 Router
233
+ '0xE592427A0AEce92De3Edee1F18E0157C05861564'.toLowerCase(), // Uniswap V3 Router
234
+ '0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146'.toLowerCase(), // Thorchain Router
235
+ ];
236
+ var toAddress = (_a = tx.to[0]) === null || _a === void 0 ? void 0 : _a.toLowerCase();
237
+ if (toAddress && knownDexContracts.includes(toAddress)) {
238
+ return {
239
+ isSwap: true,
240
+ protocol: 'uniswap', // Simplified - would need more logic
241
+ userMessage: 'DEX swap detected'
242
+ };
243
+ }
244
+ return { isSwap: false };
245
+ };
246
+ /**
247
+ * Generate user-friendly status message
248
+ */
249
+ SwapDetector.prototype.generateUserMessage = function (swap) {
250
+ switch (swap.status) {
251
+ case 'PENDING':
252
+ return "Swap pending - waiting for network confirmations";
253
+ case 'CONFIRMING':
254
+ var confirmations = swap.confirmations || 0;
255
+ var required = swap.requiredConfirmations || 6;
256
+ return "Confirming swap (".concat(confirmations, "/").concat(required, " confirmations)");
257
+ case 'COMPLETED':
258
+ return "Swap completed successfully";
259
+ case 'FAILED':
260
+ return "Swap failed - funds may be refunded";
261
+ case 'REFUNDED':
262
+ return "Swap refunded to your wallet";
263
+ default:
264
+ return 'Swap status unknown';
265
+ }
266
+ };
267
+ /**
268
+ * Estimate completion time based on swap status
269
+ */
270
+ SwapDetector.prototype.estimateCompletionTime = function (swap) {
271
+ if (swap.status === 'COMPLETED' || swap.status === 'FAILED') {
272
+ return undefined; // Already finished
273
+ }
274
+ // Rough estimates (would be more sophisticated in production)
275
+ var now = Date.now();
276
+ var avgBlockTime = 12000; // 12 seconds for Ethereum
277
+ var remainingConfirmations = (swap.requiredConfirmations || 6) - (swap.confirmations || 0);
278
+ return now + (remainingConfirmations * avgBlockTime);
279
+ };
280
+ /**
281
+ * Query Midgard API for outbound transaction hash
282
+ * Queries both THORChain and Maya Midgard endpoints
283
+ */
284
+ SwapDetector.prototype.queryMidgardForOutbound = function (inboundTxHash) {
285
+ return __awaiter(this, void 0, void 0, function () {
286
+ var tag, thorUrl, response, data, action, outboundTxid, thorError_1, mayaUrl, response, data, action, outboundTxid, mayaError_1;
287
+ return __generator(this, function (_a) {
288
+ switch (_a.label) {
289
+ case 0:
290
+ tag = TAG + ' | queryMidgardForOutbound | ';
291
+ _a.label = 1;
292
+ case 1:
293
+ _a.trys.push([1, 5, , 6]);
294
+ thorUrl = "https://midgard.ninerealms.com/v2/actions?txid=".concat(inboundTxHash);
295
+ console.log(tag, "Querying THORChain Midgard: ".concat(thorUrl));
296
+ return [4 /*yield*/, fetch(thorUrl)];
297
+ case 2:
298
+ response = _a.sent();
299
+ if (!response.ok) return [3 /*break*/, 4];
300
+ return [4 /*yield*/, response.json()];
301
+ case 3:
302
+ data = _a.sent();
303
+ if (data.actions && data.actions.length > 0) {
304
+ action = data.actions[0];
305
+ // Check for outbound transaction
306
+ if (action.out && action.out.length > 0 && action.out[0].txID) {
307
+ outboundTxid = action.out[0].txID;
308
+ console.log(tag, "\u2705 Found outbound from THORChain: ".concat(outboundTxid));
309
+ return [2 /*return*/, outboundTxid];
310
+ }
311
+ }
312
+ _a.label = 4;
313
+ case 4: return [3 /*break*/, 6];
314
+ case 5:
315
+ thorError_1 = _a.sent();
316
+ console.log(tag, "THORChain Midgard failed: ".concat(thorError_1.message));
317
+ return [3 /*break*/, 6];
318
+ case 6:
319
+ _a.trys.push([6, 10, , 11]);
320
+ mayaUrl = "https://midgard.mayachain.info/v2/actions?txid=".concat(inboundTxHash);
321
+ console.log(tag, "Querying Maya Midgard: ".concat(mayaUrl));
322
+ return [4 /*yield*/, fetch(mayaUrl)];
323
+ case 7:
324
+ response = _a.sent();
325
+ if (!response.ok) return [3 /*break*/, 9];
326
+ return [4 /*yield*/, response.json()];
327
+ case 8:
328
+ data = _a.sent();
329
+ if (data.actions && data.actions.length > 0) {
330
+ action = data.actions[0];
331
+ // Check for outbound transaction
332
+ if (action.out && action.out.length > 0 && action.out[0].txID) {
333
+ outboundTxid = action.out[0].txID;
334
+ console.log(tag, "\u2705 Found outbound from Maya: ".concat(outboundTxid));
335
+ return [2 /*return*/, outboundTxid];
336
+ }
337
+ }
338
+ _a.label = 9;
339
+ case 9: return [3 /*break*/, 11];
340
+ case 10:
341
+ mayaError_1 = _a.sent();
342
+ console.log(tag, "Maya Midgard failed: ".concat(mayaError_1.message));
343
+ return [3 /*break*/, 11];
344
+ case 11:
345
+ console.log(tag, "No outbound tx found for ".concat(inboundTxHash));
346
+ return [2 /*return*/, null];
347
+ }
348
+ });
349
+ });
350
+ };
351
+ return SwapDetector;
352
+ }());
353
+ exports.SwapDetector = SwapDetector;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pioneer-platform/pioneer-history",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Transaction history aggregation across all blockchain networks",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",