@waiaas/daemon 2.5.0 → 2.6.0-rc

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 (142) hide show
  1. package/dist/api/middleware/error-handler.d.ts +1 -1
  2. package/dist/api/middleware/error-handler.js +2 -2
  3. package/dist/api/middleware/error-handler.js.map +1 -1
  4. package/dist/api/routes/admin.d.ts.map +1 -1
  5. package/dist/api/routes/admin.js +6 -30
  6. package/dist/api/routes/admin.js.map +1 -1
  7. package/dist/api/routes/incoming.d.ts +40 -0
  8. package/dist/api/routes/incoming.d.ts.map +1 -0
  9. package/dist/api/routes/incoming.js +281 -0
  10. package/dist/api/routes/incoming.js.map +1 -0
  11. package/dist/api/routes/openapi-schemas.d.ts +243 -2
  12. package/dist/api/routes/openapi-schemas.d.ts.map +1 -1
  13. package/dist/api/routes/openapi-schemas.js +77 -0
  14. package/dist/api/routes/openapi-schemas.js.map +1 -1
  15. package/dist/api/routes/wallets.d.ts +4 -0
  16. package/dist/api/routes/wallets.d.ts.map +1 -1
  17. package/dist/api/routes/wallets.js +173 -1
  18. package/dist/api/routes/wallets.js.map +1 -1
  19. package/dist/api/routes/x402.js +1 -1
  20. package/dist/api/routes/x402.js.map +1 -1
  21. package/dist/api/server.d.ts +4 -0
  22. package/dist/api/server.d.ts.map +1 -1
  23. package/dist/api/server.js +12 -0
  24. package/dist/api/server.js.map +1 -1
  25. package/dist/infrastructure/config/loader.d.ts +43 -0
  26. package/dist/infrastructure/config/loader.d.ts.map +1 -1
  27. package/dist/infrastructure/config/loader.js +13 -1
  28. package/dist/infrastructure/config/loader.js.map +1 -1
  29. package/dist/infrastructure/database/index.d.ts +1 -1
  30. package/dist/infrastructure/database/index.d.ts.map +1 -1
  31. package/dist/infrastructure/database/index.js +1 -1
  32. package/dist/infrastructure/database/index.js.map +1 -1
  33. package/dist/infrastructure/database/migrate.d.ts +2 -2
  34. package/dist/infrastructure/database/migrate.d.ts.map +1 -1
  35. package/dist/infrastructure/database/migrate.js +83 -5
  36. package/dist/infrastructure/database/migrate.js.map +1 -1
  37. package/dist/infrastructure/database/schema.d.ts +381 -1
  38. package/dist/infrastructure/database/schema.d.ts.map +1 -1
  39. package/dist/infrastructure/database/schema.js +42 -2
  40. package/dist/infrastructure/database/schema.js.map +1 -1
  41. package/dist/infrastructure/settings/hot-reload.d.ts +9 -0
  42. package/dist/infrastructure/settings/hot-reload.d.ts.map +1 -1
  43. package/dist/infrastructure/settings/hot-reload.js +34 -5
  44. package/dist/infrastructure/settings/hot-reload.js.map +1 -1
  45. package/dist/infrastructure/settings/setting-keys.d.ts +2 -2
  46. package/dist/infrastructure/settings/setting-keys.d.ts.map +1 -1
  47. package/dist/infrastructure/settings/setting-keys.js +12 -3
  48. package/dist/infrastructure/settings/setting-keys.js.map +1 -1
  49. package/dist/lifecycle/daemon.d.ts +3 -1
  50. package/dist/lifecycle/daemon.d.ts.map +1 -1
  51. package/dist/lifecycle/daemon.js +84 -4
  52. package/dist/lifecycle/daemon.js.map +1 -1
  53. package/dist/notifications/channels/discord.d.ts.map +1 -1
  54. package/dist/notifications/channels/discord.js +17 -8
  55. package/dist/notifications/channels/discord.js.map +1 -1
  56. package/dist/notifications/channels/format-utils.d.ts +11 -0
  57. package/dist/notifications/channels/format-utils.d.ts.map +1 -0
  58. package/dist/notifications/channels/format-utils.js +19 -0
  59. package/dist/notifications/channels/format-utils.js.map +1 -0
  60. package/dist/notifications/channels/ntfy.d.ts.map +1 -1
  61. package/dist/notifications/channels/ntfy.js +15 -2
  62. package/dist/notifications/channels/ntfy.js.map +1 -1
  63. package/dist/notifications/channels/slack.d.ts.map +1 -1
  64. package/dist/notifications/channels/slack.js +16 -7
  65. package/dist/notifications/channels/slack.js.map +1 -1
  66. package/dist/notifications/channels/telegram.d.ts.map +1 -1
  67. package/dist/notifications/channels/telegram.js +17 -5
  68. package/dist/notifications/channels/telegram.js.map +1 -1
  69. package/dist/notifications/notification-service.d.ts +14 -0
  70. package/dist/notifications/notification-service.d.ts.map +1 -1
  71. package/dist/notifications/notification-service.js +83 -2
  72. package/dist/notifications/notification-service.js.map +1 -1
  73. package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.d.ts +11 -0
  74. package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.d.ts.map +1 -0
  75. package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.js +432 -0
  76. package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.js.map +1 -0
  77. package/dist/services/incoming/__tests__/incoming-tx-queue.test.d.ts +12 -0
  78. package/dist/services/incoming/__tests__/incoming-tx-queue.test.d.ts.map +1 -0
  79. package/dist/services/incoming/__tests__/incoming-tx-queue.test.js +419 -0
  80. package/dist/services/incoming/__tests__/incoming-tx-queue.test.js.map +1 -0
  81. package/dist/services/incoming/__tests__/incoming-tx-workers.test.d.ts +14 -0
  82. package/dist/services/incoming/__tests__/incoming-tx-workers.test.d.ts.map +1 -0
  83. package/dist/services/incoming/__tests__/incoming-tx-workers.test.js +452 -0
  84. package/dist/services/incoming/__tests__/incoming-tx-workers.test.js.map +1 -0
  85. package/dist/services/incoming/__tests__/integration-pitfall.test.d.ts +17 -0
  86. package/dist/services/incoming/__tests__/integration-pitfall.test.d.ts.map +1 -0
  87. package/dist/services/incoming/__tests__/integration-pitfall.test.js +653 -0
  88. package/dist/services/incoming/__tests__/integration-pitfall.test.js.map +1 -0
  89. package/dist/services/incoming/__tests__/integration-resilience.test.d.ts +14 -0
  90. package/dist/services/incoming/__tests__/integration-resilience.test.d.ts.map +1 -0
  91. package/dist/services/incoming/__tests__/integration-resilience.test.js +501 -0
  92. package/dist/services/incoming/__tests__/integration-resilience.test.js.map +1 -0
  93. package/dist/services/incoming/__tests__/integration-wiring.test.d.ts +15 -0
  94. package/dist/services/incoming/__tests__/integration-wiring.test.d.ts.map +1 -0
  95. package/dist/services/incoming/__tests__/integration-wiring.test.js +355 -0
  96. package/dist/services/incoming/__tests__/integration-wiring.test.js.map +1 -0
  97. package/dist/services/incoming/__tests__/safety-rules.test.d.ts +10 -0
  98. package/dist/services/incoming/__tests__/safety-rules.test.d.ts.map +1 -0
  99. package/dist/services/incoming/__tests__/safety-rules.test.js +165 -0
  100. package/dist/services/incoming/__tests__/safety-rules.test.js.map +1 -0
  101. package/dist/services/incoming/__tests__/subscription-multiplexer.test.d.ts +2 -0
  102. package/dist/services/incoming/__tests__/subscription-multiplexer.test.d.ts.map +1 -0
  103. package/dist/services/incoming/__tests__/subscription-multiplexer.test.js +267 -0
  104. package/dist/services/incoming/__tests__/subscription-multiplexer.test.js.map +1 -0
  105. package/dist/services/incoming/incoming-tx-monitor-service.d.ts +98 -0
  106. package/dist/services/incoming/incoming-tx-monitor-service.d.ts.map +1 -0
  107. package/dist/services/incoming/incoming-tx-monitor-service.js +336 -0
  108. package/dist/services/incoming/incoming-tx-monitor-service.js.map +1 -0
  109. package/dist/services/incoming/incoming-tx-queue.d.ts +52 -0
  110. package/dist/services/incoming/incoming-tx-queue.d.ts.map +1 -0
  111. package/dist/services/incoming/incoming-tx-queue.js +109 -0
  112. package/dist/services/incoming/incoming-tx-queue.js.map +1 -0
  113. package/dist/services/incoming/incoming-tx-workers.d.ts +89 -0
  114. package/dist/services/incoming/incoming-tx-workers.d.ts.map +1 -0
  115. package/dist/services/incoming/incoming-tx-workers.js +176 -0
  116. package/dist/services/incoming/incoming-tx-workers.js.map +1 -0
  117. package/dist/services/incoming/index.d.ts +14 -0
  118. package/dist/services/incoming/index.d.ts.map +1 -0
  119. package/dist/services/incoming/index.js +11 -0
  120. package/dist/services/incoming/index.js.map +1 -0
  121. package/dist/services/incoming/safety-rules.d.ts +70 -0
  122. package/dist/services/incoming/safety-rules.d.ts.map +1 -0
  123. package/dist/services/incoming/safety-rules.js +68 -0
  124. package/dist/services/incoming/safety-rules.js.map +1 -0
  125. package/dist/services/incoming/subscription-multiplexer.d.ts +87 -0
  126. package/dist/services/incoming/subscription-multiplexer.d.ts.map +1 -0
  127. package/dist/services/incoming/subscription-multiplexer.js +169 -0
  128. package/dist/services/incoming/subscription-multiplexer.js.map +1 -0
  129. package/dist/services/signing-sdk/approval-channel-router.d.ts +1 -1
  130. package/dist/services/signing-sdk/approval-channel-router.d.ts.map +1 -1
  131. package/dist/services/signing-sdk/approval-channel-router.js +2 -3
  132. package/dist/services/signing-sdk/approval-channel-router.js.map +1 -1
  133. package/dist/services/signing-sdk/channels/wallet-notification-channel.js +1 -1
  134. package/dist/services/signing-sdk/channels/wallet-notification-channel.js.map +1 -1
  135. package/dist/services/x402/x402-domain-policy.d.ts +6 -1
  136. package/dist/services/x402/x402-domain-policy.d.ts.map +1 -1
  137. package/dist/services/x402/x402-domain-policy.js +6 -2
  138. package/dist/services/x402/x402-domain-policy.js.map +1 -1
  139. package/package.json +4 -4
  140. package/public/admin/assets/index-D06O_cSo.js +1 -0
  141. package/public/admin/index.html +1 -1
  142. package/public/admin/assets/index-BLLOYSZp.js +0 -1
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Worker handler factories for incoming transaction lifecycle management.
3
+ *
4
+ * Provides:
5
+ * 1. Confirmation worker -- transitions DETECTED -> CONFIRMED based on chain-specific thresholds
6
+ * 2. Retention worker -- deletes records older than configurable retention_days
7
+ * 3. Gap recovery handler -- calls pollAll() on subscribers after reconnection
8
+ * 4. Cursor utilities -- read/write incoming_tx_cursors for tracking processing position
9
+ *
10
+ * All handler factories return `async () => void` functions compatible with
11
+ * BackgroundWorkers.register() interval pattern.
12
+ *
13
+ * @see docs/76-incoming-transaction-monitoring.md sections 2.5, 5.3
14
+ */
15
+ // ── EVM Confirmation Thresholds ─────────────────────────────────────
16
+ /**
17
+ * Chain-specific block confirmation thresholds for EVM networks.
18
+ * A DETECTED transaction is upgraded to CONFIRMED when
19
+ * `currentBlock - txBlockNumber >= threshold`.
20
+ */
21
+ export const EVM_CONFIRMATION_THRESHOLDS = {
22
+ mainnet: 12,
23
+ sepolia: 1,
24
+ 'polygon-mainnet': 128,
25
+ 'polygon-amoy': 1,
26
+ 'arbitrum-mainnet': 1,
27
+ 'arbitrum-sepolia': 1,
28
+ 'base-mainnet': 12,
29
+ 'base-sepolia': 1,
30
+ };
31
+ /** Default threshold when network is not in EVM_CONFIRMATION_THRESHOLDS. */
32
+ export const DEFAULT_EVM_CONFIRMATIONS = 12;
33
+ /** Solana commitment level used for confirmation check. */
34
+ export const SOLANA_CONFIRMATION = 'finalized';
35
+ // ── Confirmation Worker ─────────────────────────────────────────────
36
+ /**
37
+ * Creates a handler that upgrades DETECTED transactions to CONFIRMED.
38
+ *
39
+ * - Solana: calls checkSolanaFinalized(txHash). If true -> CONFIRMED.
40
+ * - EVM: computes `currentBlock - txBlock`. If >= threshold -> CONFIRMED.
41
+ * - Per-record error isolation: one failure does not block other records.
42
+ */
43
+ export function createConfirmationWorkerHandler(deps) {
44
+ const { sqlite, getBlockNumber, checkSolanaFinalized } = deps;
45
+ return async () => {
46
+ // Query all DETECTED transactions
47
+ const rows = sqlite
48
+ .prepare(`SELECT id, tx_hash, chain, network, block_number FROM incoming_transactions WHERE status = 'DETECTED'`)
49
+ .all();
50
+ if (rows.length === 0)
51
+ return;
52
+ const updateStmt = sqlite.prepare(`UPDATE incoming_transactions SET status = 'CONFIRMED', confirmed_at = ? WHERE id = ?`);
53
+ // Cache EVM block numbers per network to avoid redundant RPC calls
54
+ const blockCache = new Map();
55
+ for (const row of rows) {
56
+ try {
57
+ if (row.chain === 'solana') {
58
+ // Solana: check finalized commitment
59
+ if (!checkSolanaFinalized)
60
+ continue;
61
+ const finalized = await checkSolanaFinalized(row.tx_hash);
62
+ if (finalized) {
63
+ const confirmedAt = Math.floor(Date.now() / 1000);
64
+ updateStmt.run(confirmedAt, row.id);
65
+ }
66
+ }
67
+ else if (row.chain === 'ethereum') {
68
+ // EVM: compare block numbers
69
+ if (!getBlockNumber || row.block_number == null)
70
+ continue;
71
+ const cacheKey = `${row.chain}:${row.network}`;
72
+ let currentBlock = blockCache.get(cacheKey);
73
+ if (currentBlock === undefined) {
74
+ currentBlock = await getBlockNumber(row.chain, row.network);
75
+ blockCache.set(cacheKey, currentBlock);
76
+ }
77
+ const threshold = EVM_CONFIRMATION_THRESHOLDS[row.network] ??
78
+ DEFAULT_EVM_CONFIRMATIONS;
79
+ const confirmations = currentBlock - BigInt(row.block_number);
80
+ if (confirmations >= BigInt(threshold)) {
81
+ const confirmedAt = Math.floor(Date.now() / 1000);
82
+ updateStmt.run(confirmedAt, row.id);
83
+ }
84
+ }
85
+ }
86
+ catch (err) {
87
+ // Per-record error isolation: log and continue
88
+ console.warn(`Confirmation check failed for tx ${row.id}:`, err);
89
+ }
90
+ }
91
+ };
92
+ }
93
+ // ── Retention Worker ────────────────────────────────────────────────
94
+ /**
95
+ * Creates a handler that deletes incoming_transactions records
96
+ * older than the configured retention period.
97
+ *
98
+ * Uses raw SQLite DELETE for efficiency. getRetentionDays() is called
99
+ * each invocation to support hot-reload of the setting.
100
+ */
101
+ export function createRetentionWorkerHandler(deps) {
102
+ const { sqlite, getRetentionDays } = deps;
103
+ return async () => {
104
+ const retentionDays = getRetentionDays();
105
+ const cutoff = Math.floor(Date.now() / 1000) - retentionDays * 86400;
106
+ const result = sqlite
107
+ .prepare('DELETE FROM incoming_transactions WHERE detected_at < ?')
108
+ .run(cutoff);
109
+ if (result.changes > 0) {
110
+ console.log(`Retention worker: deleted ${result.changes} incoming_transactions older than ${retentionDays} days`);
111
+ }
112
+ };
113
+ }
114
+ // ── Gap Recovery Handler ────────────────────────────────────────────
115
+ /**
116
+ * Creates a handler for recovering missed transactions after reconnection.
117
+ *
118
+ * When a WebSocket reconnects, the gap between last-known cursor and current
119
+ * chain state needs to be filled. This handler finds the relevant subscriber
120
+ * and calls pollAll() to recover missed transactions.
121
+ */
122
+ export function createGapRecoveryHandler(deps) {
123
+ const { subscribers } = deps;
124
+ return async (chain, network, _walletIds) => {
125
+ const key = `${chain}:${network}`;
126
+ const entry = subscribers.get(key);
127
+ if (!entry) {
128
+ // No subscriber for this chain:network -- graceful skip
129
+ return;
130
+ }
131
+ try {
132
+ await entry.subscriber.pollAll();
133
+ }
134
+ catch (err) {
135
+ console.warn(`Gap recovery failed for ${key}:`, err);
136
+ }
137
+ };
138
+ }
139
+ // ── Cursor Utilities ────────────────────────────────────────────────
140
+ /**
141
+ * Save or update the processing cursor for a wallet.
142
+ *
143
+ * Uses INSERT OR REPLACE to handle both new and existing cursors.
144
+ * The cursor value is chain-specific:
145
+ * - Solana: last processed transaction signature
146
+ * - EVM: last processed block number (as string)
147
+ */
148
+ export function updateCursor(sqlite, walletId, chain, network, cursor) {
149
+ const isSolana = chain === 'solana';
150
+ const updatedAt = Math.floor(Date.now() / 1000);
151
+ sqlite
152
+ .prepare(`INSERT OR REPLACE INTO incoming_tx_cursors (wallet_id, chain, network, last_signature, last_block_number, updated_at)
153
+ VALUES (?, ?, ?, ?, ?, ?)`)
154
+ .run(walletId, chain, network, isSolana ? cursor : null, isSolana ? null : parseInt(cursor, 10), updatedAt);
155
+ }
156
+ /**
157
+ * Load the processing cursor for a wallet.
158
+ *
159
+ * Returns the cursor value (signature or block number as string),
160
+ * or null if no cursor exists for the given wallet+chain+network.
161
+ */
162
+ export function loadCursor(sqlite, walletId, chain, network) {
163
+ const row = sqlite
164
+ .prepare(`SELECT last_signature, last_block_number FROM incoming_tx_cursors
165
+ WHERE wallet_id = ? AND chain = ? AND network = ?`)
166
+ .get(walletId, chain, network);
167
+ if (!row)
168
+ return null;
169
+ // Return whichever cursor value is present
170
+ if (row.last_signature)
171
+ return row.last_signature;
172
+ if (row.last_block_number != null)
173
+ return String(row.last_block_number);
174
+ return null;
175
+ }
176
+ //# sourceMappingURL=incoming-tx-workers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"incoming-tx-workers.js","sourceRoot":"","sources":["../../../src/services/incoming/incoming-tx-workers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,uEAAuE;AAEvE;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAA2B;IACjE,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,CAAC;IACV,iBAAiB,EAAE,GAAG;IACtB,cAAc,EAAE,CAAC;IACjB,kBAAkB,EAAE,CAAC;IACrB,kBAAkB,EAAE,CAAC;IACrB,cAAc,EAAE,EAAE;IAClB,cAAc,EAAE,CAAC;CAClB,CAAC;AAEF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAE5C,2DAA2D;AAC3D,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,CAAC;AAyB/C,uEAAuE;AAEvE;;;;;;GAMG;AACH,MAAM,UAAU,+BAA+B,CAC7C,IAA4B;IAE5B,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAAC;IAE9D,OAAO,KAAK,IAAI,EAAE;QAChB,kCAAkC;QAClC,MAAM,IAAI,GAAG,MAAM;aAChB,OAAO,CACN,uGAAuG,CACxG;aACA,GAAG,EAMJ,CAAC;QAEH,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAE9B,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAC/B,sFAAsF,CACvF,CAAC;QAEF,mEAAmE;QACnE,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;QAE7C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC;gBACH,IAAI,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC3B,qCAAqC;oBACrC,IAAI,CAAC,oBAAoB;wBAAE,SAAS;oBACpC,MAAM,SAAS,GAAG,MAAM,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBAC1D,IAAI,SAAS,EAAE,CAAC;wBACd,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;wBAClD,UAAU,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;oBACtC,CAAC;gBACH,CAAC;qBAAM,IAAI,GAAG,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;oBACpC,6BAA6B;oBAC7B,IAAI,CAAC,cAAc,IAAI,GAAG,CAAC,YAAY,IAAI,IAAI;wBAAE,SAAS;oBAE1D,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;oBAC/C,IAAI,YAAY,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;oBAC5C,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;wBAC/B,YAAY,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;wBAC5D,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;oBACzC,CAAC;oBAED,MAAM,SAAS,GACb,2BAA2B,CAAC,GAAG,CAAC,OAAO,CAAC;wBACxC,yBAAyB,CAAC;oBAC5B,MAAM,aAAa,GACjB,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;oBAE1C,IAAI,aAAa,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;wBACvC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;wBAClD,UAAU,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;oBACtC,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,+CAA+C;gBAC/C,OAAO,CAAC,IAAI,CACV,oCAAoC,GAAG,CAAC,EAAE,GAAG,EAC7C,GAAG,CACJ,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,uEAAuE;AAEvE;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAC1C,IAAyB;IAEzB,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAAC;IAE1C,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,aAAa,GAAG,KAAK,CAAC;QACrE,MAAM,MAAM,GAAG,MAAM;aAClB,OAAO,CAAC,yDAAyD,CAAC;aAClE,GAAG,CAAC,MAAM,CAAC,CAAC;QAEf,IAAI,MAAM,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CACT,6BAA6B,MAAM,CAAC,OAAO,qCAAqC,aAAa,OAAO,CACrG,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,uEAAuE;AAEvE;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,IAAqB;IAErB,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IAE7B,OAAO,KAAK,EACV,KAAa,EACb,OAAe,EACf,UAAoB,EACpB,EAAE;QACF,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,OAAO,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,wDAAwD;YACxD,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QACnC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,2BAA2B,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC;QACvD,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,uEAAuE;AAEvE;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAgB,EAChB,QAAgB,EAChB,KAAa,EACb,OAAe,EACf,MAAc;IAEd,MAAM,QAAQ,GAAG,KAAK,KAAK,QAAQ,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAEhD,MAAM;SACH,OAAO,CACN;iCAC2B,CAC5B;SACA,GAAG,CACF,QAAQ,EACR,KAAK,EACL,OAAO,EACP,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EACxB,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,EACtC,SAAS,CACV,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,MAAgB,EAChB,QAAgB,EAChB,KAAa,EACb,OAAe;IAEf,MAAM,GAAG,GAAG,MAAM;SACf,OAAO,CACN;yDACmD,CACpD;SACA,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,CAElB,CAAC;IAEd,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,2CAA2C;IAC3C,IAAI,GAAG,CAAC,cAAc;QAAE,OAAO,GAAG,CAAC,cAAc,CAAC;IAClD,IAAI,GAAG,CAAC,iBAAiB,IAAI,IAAI;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACxE,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Incoming transaction monitoring service module.
3
+ *
4
+ * Re-exports all public types and classes for the incoming TX subsystem.
5
+ */
6
+ export { IncomingTxQueue } from './incoming-tx-queue.js';
7
+ export { SubscriptionMultiplexer } from './subscription-multiplexer.js';
8
+ export type { MultiplexerDeps } from './subscription-multiplexer.js';
9
+ export { createConfirmationWorkerHandler, createRetentionWorkerHandler, createGapRecoveryHandler, updateCursor, loadCursor, EVM_CONFIRMATION_THRESHOLDS, DEFAULT_EVM_CONFIRMATIONS, SOLANA_CONFIRMATION, } from './incoming-tx-workers.js';
10
+ export { IncomingTxMonitorService } from './incoming-tx-monitor-service.js';
11
+ export type { IncomingTxMonitorConfig, IncomingTxMonitorDeps, SubscriberFactory } from './incoming-tx-monitor-service.js';
12
+ export { DustAttackRule, UnknownTokenRule, LargeAmountRule, } from './safety-rules.js';
13
+ export type { IIncomingSafetyRule, SafetyRuleContext, SuspiciousReason, } from './safety-rules.js';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/incoming/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,YAAY,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EACL,+BAA+B,EAC/B,4BAA4B,EAC5B,wBAAwB,EACxB,YAAY,EACZ,UAAU,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,YAAY,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1H,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Incoming transaction monitoring service module.
3
+ *
4
+ * Re-exports all public types and classes for the incoming TX subsystem.
5
+ */
6
+ export { IncomingTxQueue } from './incoming-tx-queue.js';
7
+ export { SubscriptionMultiplexer } from './subscription-multiplexer.js';
8
+ export { createConfirmationWorkerHandler, createRetentionWorkerHandler, createGapRecoveryHandler, updateCursor, loadCursor, EVM_CONFIRMATION_THRESHOLDS, DEFAULT_EVM_CONFIRMATIONS, SOLANA_CONFIRMATION, } from './incoming-tx-workers.js';
9
+ export { IncomingTxMonitorService } from './incoming-tx-monitor-service.js';
10
+ export { DustAttackRule, UnknownTokenRule, LargeAmountRule, } from './safety-rules.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/incoming/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAExE,OAAO,EACL,+BAA+B,EAC/B,4BAA4B,EAC5B,wBAAwB,EACxB,YAAY,EACZ,UAAU,EACV,2BAA2B,EAC3B,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,eAAe,GAChB,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Incoming transaction safety rules.
3
+ *
4
+ * Three rule implementations for detecting suspicious incoming transactions:
5
+ * 1. DustAttackRule -- flags micro-value transfers (potential address poisoning)
6
+ * 2. UnknownTokenRule -- flags transfers of unregistered tokens
7
+ * 3. LargeAmountRule -- flags unusually large incoming amounts
8
+ *
9
+ * Each rule implements IIncomingSafetyRule with a synchronous check() method.
10
+ * Rules return true when the transaction IS suspicious (should be flagged).
11
+ * Safe default: when price data is unavailable, rules return false (not suspicious).
12
+ *
13
+ * @see docs/76-incoming-transaction-monitoring.md section 4.2
14
+ */
15
+ import type { IncomingTransaction } from '@waiaas/core';
16
+ export type SuspiciousReason = 'dust' | 'unknownToken' | 'largeAmount';
17
+ export interface SafetyRuleContext {
18
+ /** Configurable USD threshold below which a transfer is considered dust. */
19
+ dustThresholdUsd: number;
20
+ /** Multiplier of average incoming USD above which a transfer is flagged. */
21
+ amountMultiplier: number;
22
+ /** Whether the token address is in the registered token registry. */
23
+ isRegisteredToken: boolean;
24
+ /** USD price per whole token unit, or null if unavailable. */
25
+ usdPrice: number | null;
26
+ /** Average incoming USD for this wallet, or null if no history. */
27
+ avgIncomingUsd: number | null;
28
+ /** Token decimals (e.g. 9 for SOL, 18 for ETH). */
29
+ decimals: number;
30
+ }
31
+ export interface IIncomingSafetyRule {
32
+ readonly name: SuspiciousReason;
33
+ check(tx: IncomingTransaction, context: SafetyRuleContext): boolean;
34
+ }
35
+ /**
36
+ * Flags transactions with USD value below a configurable dust threshold.
37
+ *
38
+ * Dust attacks send tiny amounts to pollute the recipient's transaction
39
+ * history, hoping they'll copy-paste a malicious address.
40
+ *
41
+ * Safe default: if usdPrice is null (unavailable), returns false.
42
+ */
43
+ export declare class DustAttackRule implements IIncomingSafetyRule {
44
+ readonly name: SuspiciousReason;
45
+ check(tx: IncomingTransaction, ctx: SafetyRuleContext): boolean;
46
+ }
47
+ /**
48
+ * Flags token transfers where the token address is not in the registry.
49
+ *
50
+ * Native transfers (tokenAddress === null) are never flagged by this rule,
51
+ * since SOL/ETH are inherently "known".
52
+ */
53
+ export declare class UnknownTokenRule implements IIncomingSafetyRule {
54
+ readonly name: SuspiciousReason;
55
+ check(tx: IncomingTransaction, ctx: SafetyRuleContext): boolean;
56
+ }
57
+ /**
58
+ * Flags transactions whose USD value exceeds a configurable multiplier
59
+ * of the wallet's average incoming USD amount.
60
+ *
61
+ * Helps detect unexpected large deposits that may indicate stolen funds
62
+ * being routed through monitored wallets.
63
+ *
64
+ * Safe default: if either usdPrice or avgIncomingUsd is null, returns false.
65
+ */
66
+ export declare class LargeAmountRule implements IIncomingSafetyRule {
67
+ readonly name: SuspiciousReason;
68
+ check(tx: IncomingTransaction, ctx: SafetyRuleContext): boolean;
69
+ }
70
+ //# sourceMappingURL=safety-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safety-rules.d.ts","sourceRoot":"","sources":["../../../src/services/incoming/safety-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAIxD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,cAAc,GAAG,aAAa,CAAC;AAEvE,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,gBAAgB,EAAE,MAAM,CAAC;IACzB,4EAA4E;IAC5E,gBAAgB,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,iBAAiB,EAAE,OAAO,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,mEAAmE;IACnE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,KAAK,CAAC,EAAE,EAAE,mBAAmB,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC;CACrE;AAID;;;;;;;GAOG;AACH,qBAAa,cAAe,YAAW,mBAAmB;IACxD,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAU;IAEzC,KAAK,CAAC,EAAE,EAAE,mBAAmB,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO;CAQhE;AAID;;;;;GAKG;AACH,qBAAa,gBAAiB,YAAW,mBAAmB;IAC1D,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAkB;IAEjD,KAAK,CAAC,EAAE,EAAE,mBAAmB,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO;CAMhE;AAID;;;;;;;;GAQG;AACH,qBAAa,eAAgB,YAAW,mBAAmB;IACzD,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAiB;IAEhD,KAAK,CAAC,EAAE,EAAE,mBAAmB,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO;CAQhE"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Incoming transaction safety rules.
3
+ *
4
+ * Three rule implementations for detecting suspicious incoming transactions:
5
+ * 1. DustAttackRule -- flags micro-value transfers (potential address poisoning)
6
+ * 2. UnknownTokenRule -- flags transfers of unregistered tokens
7
+ * 3. LargeAmountRule -- flags unusually large incoming amounts
8
+ *
9
+ * Each rule implements IIncomingSafetyRule with a synchronous check() method.
10
+ * Rules return true when the transaction IS suspicious (should be flagged).
11
+ * Safe default: when price data is unavailable, rules return false (not suspicious).
12
+ *
13
+ * @see docs/76-incoming-transaction-monitoring.md section 4.2
14
+ */
15
+ // ── DustAttackRule ───────────────────────────────────────────────
16
+ /**
17
+ * Flags transactions with USD value below a configurable dust threshold.
18
+ *
19
+ * Dust attacks send tiny amounts to pollute the recipient's transaction
20
+ * history, hoping they'll copy-paste a malicious address.
21
+ *
22
+ * Safe default: if usdPrice is null (unavailable), returns false.
23
+ */
24
+ export class DustAttackRule {
25
+ name = 'dust';
26
+ check(tx, ctx) {
27
+ if (ctx.usdPrice === null)
28
+ return false;
29
+ const amountUsd = (Number(tx.amount) * ctx.usdPrice) / 10 ** ctx.decimals;
30
+ return amountUsd < ctx.dustThresholdUsd;
31
+ }
32
+ }
33
+ // ── UnknownTokenRule ────────────────────────────────────────────
34
+ /**
35
+ * Flags token transfers where the token address is not in the registry.
36
+ *
37
+ * Native transfers (tokenAddress === null) are never flagged by this rule,
38
+ * since SOL/ETH are inherently "known".
39
+ */
40
+ export class UnknownTokenRule {
41
+ name = 'unknownToken';
42
+ check(tx, ctx) {
43
+ // Native transfers are always safe for this rule
44
+ if (tx.tokenAddress === null)
45
+ return false;
46
+ return !ctx.isRegisteredToken;
47
+ }
48
+ }
49
+ // ── LargeAmountRule ─────────────────────────────────────────────
50
+ /**
51
+ * Flags transactions whose USD value exceeds a configurable multiplier
52
+ * of the wallet's average incoming USD amount.
53
+ *
54
+ * Helps detect unexpected large deposits that may indicate stolen funds
55
+ * being routed through monitored wallets.
56
+ *
57
+ * Safe default: if either usdPrice or avgIncomingUsd is null, returns false.
58
+ */
59
+ export class LargeAmountRule {
60
+ name = 'largeAmount';
61
+ check(tx, ctx) {
62
+ if (ctx.usdPrice === null || ctx.avgIncomingUsd === null)
63
+ return false;
64
+ const amountUsd = (Number(tx.amount) * ctx.usdPrice) / 10 ** ctx.decimals;
65
+ return amountUsd > ctx.avgIncomingUsd * ctx.amountMultiplier;
66
+ }
67
+ }
68
+ //# sourceMappingURL=safety-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safety-rules.js","sourceRoot":"","sources":["../../../src/services/incoming/safety-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA4BH,oEAAoE;AAEpE;;;;;;;GAOG;AACH,MAAM,OAAO,cAAc;IAChB,IAAI,GAAqB,MAAM,CAAC;IAEzC,KAAK,CAAC,EAAuB,EAAE,GAAsB;QACnD,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAExC,MAAM,SAAS,GACb,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC;QAE1D,OAAO,SAAS,GAAG,GAAG,CAAC,gBAAgB,CAAC;IAC1C,CAAC;CACF;AAED,mEAAmE;AAEnE;;;;;GAKG;AACH,MAAM,OAAO,gBAAgB;IAClB,IAAI,GAAqB,cAAc,CAAC;IAEjD,KAAK,CAAC,EAAuB,EAAE,GAAsB;QACnD,iDAAiD;QACjD,IAAI,EAAE,CAAC,YAAY,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAE3C,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,mEAAmE;AAEnE;;;;;;;;GAQG;AACH,MAAM,OAAO,eAAe;IACjB,IAAI,GAAqB,aAAa,CAAC;IAEhD,KAAK,CAAC,EAAuB,EAAE,GAAsB;QACnD,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI,IAAI,GAAG,CAAC,cAAc,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAEvE,MAAM,SAAS,GACb,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC;QAE1D,OAAO,SAAS,GAAG,GAAG,CAAC,cAAc,GAAG,GAAG,CAAC,gBAAgB,CAAC;IAC/D,CAAC;CACF"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * SubscriptionMultiplexer -- connection sharing layer for incoming TX subscribers.
3
+ *
4
+ * Manages one IChainSubscriber instance per chain:network pair, allowing
5
+ * multiple wallets to share a single WebSocket connection. Integrates with
6
+ * reconnectLoop from @waiaas/core for resilient reconnection and triggers
7
+ * gap recovery on successful reconnect.
8
+ *
9
+ * Key behaviors:
10
+ * - addWallet() reuses existing connection if chain:network already has one
11
+ * - removeWallet() destroys connection when no wallets remain
12
+ * - reconnectLoop drives WS_ACTIVE -> RECONNECTING -> POLLING_FALLBACK transitions
13
+ * - Gap recovery callback invoked with cursor on successful reconnect
14
+ *
15
+ * References:
16
+ * Design doc 76 section 5.4 (multiplexer)
17
+ * IChainSubscriber 6-method interface from @waiaas/core
18
+ * reconnectLoop/ConnectionState from @waiaas/core
19
+ */
20
+ import type { IChainSubscriber, IncomingTransaction, ConnectionState, ReconnectConfig } from '@waiaas/core';
21
+ type SubscriberFactory = (chain: string, network: string) => IChainSubscriber | Promise<IChainSubscriber>;
22
+ type OnTransactionCallback = (tx: IncomingTransaction) => void;
23
+ type GapRecoveryCallback = (chain: string, network: string, walletIds: string[]) => Promise<void>;
24
+ export interface MultiplexerDeps {
25
+ subscriberFactory: SubscriberFactory;
26
+ onTransaction: OnTransactionCallback;
27
+ onGapRecovery?: GapRecoveryCallback;
28
+ reconnectConfig?: ReconnectConfig;
29
+ }
30
+ export declare class SubscriptionMultiplexer {
31
+ private readonly connections;
32
+ private readonly deps;
33
+ private readonly reconnectConfig;
34
+ constructor(deps: MultiplexerDeps);
35
+ /**
36
+ * Add a wallet to a shared connection for the given chain:network.
37
+ * If no connection exists, creates subscriber, connects, and starts reconnectLoop.
38
+ * If connection already exists, subscribes wallet through the existing subscriber.
39
+ */
40
+ addWallet(chain: string, network: string, walletId: string, walletAddress: string): Promise<void>;
41
+ /**
42
+ * Remove a wallet from its shared connection.
43
+ * Destroys the connection if no wallets remain.
44
+ */
45
+ removeWallet(chain: string, network: string, walletId: string): Promise<void>;
46
+ /**
47
+ * Get the connection state for a chain:network pair.
48
+ * Returns null if no connection exists.
49
+ */
50
+ getConnectionState(chain: string, network: string): ConnectionState | null;
51
+ /**
52
+ * Get summary of all active connections.
53
+ */
54
+ getActiveConnections(): Array<{
55
+ key: string;
56
+ walletCount: number;
57
+ state: ConnectionState;
58
+ }>;
59
+ /**
60
+ * Get subscriber entries for gap recovery.
61
+ * Returns a read-only view compatible with createGapRecoveryHandler deps.
62
+ *
63
+ * Note: pollAll() is not part of IChainSubscriber interface but
64
+ * exists on both SolanaIncomingSubscriber and EvmIncomingSubscriber.
65
+ * The returned type uses structural typing to express this.
66
+ */
67
+ getSubscriberEntries(): Map<string, {
68
+ subscriber: {
69
+ pollAll: () => Promise<void>;
70
+ };
71
+ }>;
72
+ /**
73
+ * Get subscribers for a specific chain prefix (e.g., "solana", "ethereum").
74
+ * Used by polling workers to iterate chain-specific subscribers.
75
+ */
76
+ getSubscribersForChain(chainPrefix: string): Array<{
77
+ key: string;
78
+ subscriber: IChainSubscriber;
79
+ }>;
80
+ /**
81
+ * Stop all connections: abort reconnect loops, unsubscribe wallets, destroy subscribers.
82
+ * Clears the connections Map.
83
+ */
84
+ stopAll(): Promise<void>;
85
+ }
86
+ export {};
87
+ //# sourceMappingURL=subscription-multiplexer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription-multiplexer.d.ts","sourceRoot":"","sources":["../../../src/services/incoming/subscription-multiplexer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,eAAe,EAChB,MAAM,cAAc,CAAC;AAetB,KAAK,iBAAiB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAC1G,KAAK,qBAAqB,GAAG,CAAC,EAAE,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAC/D,KAAK,mBAAmB,GAAG,CACzB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EAAE,KAChB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,MAAM,WAAW,eAAe;IAC9B,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,aAAa,EAAE,qBAAqB,CAAC;IACrC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAID,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6C;IACzE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkB;IACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;gBAEtC,IAAI,EAAE,eAAe;IAKjC;;;;OAIG;IACG,SAAS,CACb,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC;IA8EhB;;;OAGG;IACG,YAAY,CAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAehB;;;OAGG;IACH,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAK1E;;OAEG;IACH,oBAAoB,IAAI,KAAK,CAAC;QAC5B,GAAG,EAAE,MAAM,CAAC;QACZ,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,eAAe,CAAC;KACxB,CAAC;IAQF;;;;;;;OAOG;IACH,oBAAoB,IAAI,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE;YAAE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE,CAAA;KAAE,CAAC;IAOrF;;;OAGG;IACH,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC;QACjD,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,gBAAgB,CAAC;KAC9B,CAAC;IAaF;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAqB/B"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * SubscriptionMultiplexer -- connection sharing layer for incoming TX subscribers.
3
+ *
4
+ * Manages one IChainSubscriber instance per chain:network pair, allowing
5
+ * multiple wallets to share a single WebSocket connection. Integrates with
6
+ * reconnectLoop from @waiaas/core for resilient reconnection and triggers
7
+ * gap recovery on successful reconnect.
8
+ *
9
+ * Key behaviors:
10
+ * - addWallet() reuses existing connection if chain:network already has one
11
+ * - removeWallet() destroys connection when no wallets remain
12
+ * - reconnectLoop drives WS_ACTIVE -> RECONNECTING -> POLLING_FALLBACK transitions
13
+ * - Gap recovery callback invoked with cursor on successful reconnect
14
+ *
15
+ * References:
16
+ * Design doc 76 section 5.4 (multiplexer)
17
+ * IChainSubscriber 6-method interface from @waiaas/core
18
+ * reconnectLoop/ConnectionState from @waiaas/core
19
+ */
20
+ import { reconnectLoop, DEFAULT_RECONNECT_CONFIG } from '@waiaas/core';
21
+ // ── SubscriptionMultiplexer ──────────────────────────────────────
22
+ export class SubscriptionMultiplexer {
23
+ connections = new Map();
24
+ deps;
25
+ reconnectConfig;
26
+ constructor(deps) {
27
+ this.deps = deps;
28
+ this.reconnectConfig = deps.reconnectConfig ?? DEFAULT_RECONNECT_CONFIG;
29
+ }
30
+ /**
31
+ * Add a wallet to a shared connection for the given chain:network.
32
+ * If no connection exists, creates subscriber, connects, and starts reconnectLoop.
33
+ * If connection already exists, subscribes wallet through the existing subscriber.
34
+ */
35
+ async addWallet(chain, network, walletId, walletAddress) {
36
+ const key = `${chain}:${network}`;
37
+ const existing = this.connections.get(key);
38
+ if (existing) {
39
+ // Reuse existing connection -- just subscribe the wallet
40
+ await existing.subscriber.subscribe(walletId, walletAddress, network, this.deps.onTransaction);
41
+ existing.wallets.add(walletId);
42
+ return;
43
+ }
44
+ // Create new connection entry
45
+ const subscriber = await this.deps.subscriberFactory(chain, network);
46
+ const abortController = new AbortController();
47
+ const entry = {
48
+ subscriber,
49
+ wallets: new Set([walletId]),
50
+ state: 'RECONNECTING', // Will transition to WS_ACTIVE on connect
51
+ abortController,
52
+ };
53
+ this.connections.set(key, entry);
54
+ // Subscribe the wallet before connecting
55
+ await subscriber.subscribe(walletId, walletAddress, network, this.deps.onTransaction);
56
+ // Connect the subscriber
57
+ await subscriber.connect();
58
+ entry.state = 'WS_ACTIVE';
59
+ // Start reconnectLoop in fire-and-forget mode
60
+ // The loop monitors the connection and handles reconnection on disconnect
61
+ void (async () => {
62
+ // Wait for the first disconnect before entering the reconnect loop
63
+ try {
64
+ await subscriber.waitForDisconnect();
65
+ }
66
+ catch {
67
+ // Disconnect detected
68
+ }
69
+ if (abortController.signal.aborted)
70
+ return;
71
+ // Now enter the reconnect loop for subsequent reconnections
72
+ await reconnectLoop(subscriber, this.reconnectConfig, (state) => {
73
+ // Guard against updates after connection was removed
74
+ const current = this.connections.get(key);
75
+ if (current !== entry)
76
+ return;
77
+ const previousState = entry.state;
78
+ entry.state = state;
79
+ // On successful reconnect (transition to WS_ACTIVE from non-WS_ACTIVE),
80
+ // trigger gap recovery
81
+ if (state === 'WS_ACTIVE' &&
82
+ previousState !== 'WS_ACTIVE' &&
83
+ this.deps.onGapRecovery) {
84
+ void this.deps.onGapRecovery(chain, network, [...entry.wallets]);
85
+ }
86
+ }, abortController.signal);
87
+ })();
88
+ }
89
+ /**
90
+ * Remove a wallet from its shared connection.
91
+ * Destroys the connection if no wallets remain.
92
+ */
93
+ async removeWallet(chain, network, walletId) {
94
+ const key = `${chain}:${network}`;
95
+ const entry = this.connections.get(key);
96
+ if (!entry)
97
+ return;
98
+ await entry.subscriber.unsubscribe(walletId);
99
+ entry.wallets.delete(walletId);
100
+ if (entry.wallets.size === 0) {
101
+ entry.abortController.abort();
102
+ await entry.subscriber.destroy();
103
+ this.connections.delete(key);
104
+ }
105
+ }
106
+ /**
107
+ * Get the connection state for a chain:network pair.
108
+ * Returns null if no connection exists.
109
+ */
110
+ getConnectionState(chain, network) {
111
+ const key = `${chain}:${network}`;
112
+ return this.connections.get(key)?.state ?? null;
113
+ }
114
+ /**
115
+ * Get summary of all active connections.
116
+ */
117
+ getActiveConnections() {
118
+ return Array.from(this.connections.entries()).map(([key, entry]) => ({
119
+ key,
120
+ walletCount: entry.wallets.size,
121
+ state: entry.state,
122
+ }));
123
+ }
124
+ /**
125
+ * Get subscriber entries for gap recovery.
126
+ * Returns a read-only view compatible with createGapRecoveryHandler deps.
127
+ *
128
+ * Note: pollAll() is not part of IChainSubscriber interface but
129
+ * exists on both SolanaIncomingSubscriber and EvmIncomingSubscriber.
130
+ * The returned type uses structural typing to express this.
131
+ */
132
+ getSubscriberEntries() {
133
+ return this.connections;
134
+ }
135
+ /**
136
+ * Get subscribers for a specific chain prefix (e.g., "solana", "ethereum").
137
+ * Used by polling workers to iterate chain-specific subscribers.
138
+ */
139
+ getSubscribersForChain(chainPrefix) {
140
+ const result = [];
141
+ for (const [key, entry] of this.connections) {
142
+ if (key.startsWith(`${chainPrefix}:`)) {
143
+ result.push({ key, subscriber: entry.subscriber });
144
+ }
145
+ }
146
+ return result;
147
+ }
148
+ /**
149
+ * Stop all connections: abort reconnect loops, unsubscribe wallets, destroy subscribers.
150
+ * Clears the connections Map.
151
+ */
152
+ async stopAll() {
153
+ const destroyPromises = [];
154
+ for (const [, entry] of this.connections) {
155
+ entry.abortController.abort();
156
+ // Unsubscribe all wallets and destroy subscriber
157
+ const walletIds = [...entry.wallets];
158
+ destroyPromises.push((async () => {
159
+ for (const walletId of walletIds) {
160
+ await entry.subscriber.unsubscribe(walletId);
161
+ }
162
+ await entry.subscriber.destroy();
163
+ })());
164
+ }
165
+ await Promise.all(destroyPromises);
166
+ this.connections.clear();
167
+ }
168
+ }
169
+ //# sourceMappingURL=subscription-multiplexer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription-multiplexer.js","sourceRoot":"","sources":["../../../src/services/incoming/subscription-multiplexer.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@
10
10
  * - walletconnect / telegram_bot / rest -> return method (no channel call)
11
11
  * 3. If SDK method but signing_sdk.enabled=false: fall through to global fallback
12
12
  * 4. Global fallback priority (CHAN-06):
13
- * SDK ntfy > SDK Telegram > WalletConnect > Telegram Bot > REST
13
+ * Wallet App (ntfy) > Wallet App (Telegram) > WalletConnect > Telegram Bot > REST
14
14
  *
15
15
  * CHAN-07: When signing_sdk.enabled !== 'true', SDK channels are skipped entirely.
16
16
  *