@waiaas/daemon 2.5.0 → 2.6.0-rc.1

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 (193) 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/connect-info.d.ts.map +1 -1
  8. package/dist/api/routes/connect-info.js +6 -0
  9. package/dist/api/routes/connect-info.js.map +1 -1
  10. package/dist/api/routes/incoming.d.ts +40 -0
  11. package/dist/api/routes/incoming.d.ts.map +1 -0
  12. package/dist/api/routes/incoming.js +281 -0
  13. package/dist/api/routes/incoming.js.map +1 -0
  14. package/dist/api/routes/openapi-schemas.d.ts +337 -24
  15. package/dist/api/routes/openapi-schemas.d.ts.map +1 -1
  16. package/dist/api/routes/openapi-schemas.js +78 -0
  17. package/dist/api/routes/openapi-schemas.js.map +1 -1
  18. package/dist/api/routes/tokens.d.ts.map +1 -1
  19. package/dist/api/routes/tokens.js +1 -0
  20. package/dist/api/routes/tokens.js.map +1 -1
  21. package/dist/api/routes/wallets.d.ts +4 -0
  22. package/dist/api/routes/wallets.d.ts.map +1 -1
  23. package/dist/api/routes/wallets.js +173 -1
  24. package/dist/api/routes/wallets.js.map +1 -1
  25. package/dist/api/routes/x402.js +1 -1
  26. package/dist/api/routes/x402.js.map +1 -1
  27. package/dist/api/server.d.ts +4 -0
  28. package/dist/api/server.d.ts.map +1 -1
  29. package/dist/api/server.js +12 -0
  30. package/dist/api/server.js.map +1 -1
  31. package/dist/infrastructure/config/loader.d.ts +43 -0
  32. package/dist/infrastructure/config/loader.d.ts.map +1 -1
  33. package/dist/infrastructure/config/loader.js +13 -1
  34. package/dist/infrastructure/config/loader.js.map +1 -1
  35. package/dist/infrastructure/database/index.d.ts +1 -1
  36. package/dist/infrastructure/database/index.d.ts.map +1 -1
  37. package/dist/infrastructure/database/index.js +1 -1
  38. package/dist/infrastructure/database/index.js.map +1 -1
  39. package/dist/infrastructure/database/migrate.d.ts +2 -2
  40. package/dist/infrastructure/database/migrate.d.ts.map +1 -1
  41. package/dist/infrastructure/database/migrate.js +123 -6
  42. package/dist/infrastructure/database/migrate.js.map +1 -1
  43. package/dist/infrastructure/database/schema.d.ts +400 -1
  44. package/dist/infrastructure/database/schema.d.ts.map +1 -1
  45. package/dist/infrastructure/database/schema.js +43 -2
  46. package/dist/infrastructure/database/schema.js.map +1 -1
  47. package/dist/infrastructure/oracle/coingecko-oracle.d.ts +9 -1
  48. package/dist/infrastructure/oracle/coingecko-oracle.d.ts.map +1 -1
  49. package/dist/infrastructure/oracle/coingecko-oracle.js +40 -23
  50. package/dist/infrastructure/oracle/coingecko-oracle.js.map +1 -1
  51. package/dist/infrastructure/oracle/coingecko-platform-ids.d.ts +11 -10
  52. package/dist/infrastructure/oracle/coingecko-platform-ids.d.ts.map +1 -1
  53. package/dist/infrastructure/oracle/coingecko-platform-ids.js +20 -12
  54. package/dist/infrastructure/oracle/coingecko-platform-ids.js.map +1 -1
  55. package/dist/infrastructure/oracle/index.d.ts +1 -1
  56. package/dist/infrastructure/oracle/index.d.ts.map +1 -1
  57. package/dist/infrastructure/oracle/index.js +1 -1
  58. package/dist/infrastructure/oracle/index.js.map +1 -1
  59. package/dist/infrastructure/oracle/oracle-chain.d.ts.map +1 -1
  60. package/dist/infrastructure/oracle/oracle-chain.js +7 -4
  61. package/dist/infrastructure/oracle/oracle-chain.js.map +1 -1
  62. package/dist/infrastructure/oracle/price-cache.d.ts +24 -8
  63. package/dist/infrastructure/oracle/price-cache.d.ts.map +1 -1
  64. package/dist/infrastructure/oracle/price-cache.js +32 -11
  65. package/dist/infrastructure/oracle/price-cache.js.map +1 -1
  66. package/dist/infrastructure/oracle/pyth-feed-ids.d.ts +11 -8
  67. package/dist/infrastructure/oracle/pyth-feed-ids.d.ts.map +1 -1
  68. package/dist/infrastructure/oracle/pyth-feed-ids.js +16 -17
  69. package/dist/infrastructure/oracle/pyth-feed-ids.js.map +1 -1
  70. package/dist/infrastructure/oracle/pyth-oracle.d.ts.map +1 -1
  71. package/dist/infrastructure/oracle/pyth-oracle.js +7 -4
  72. package/dist/infrastructure/oracle/pyth-oracle.js.map +1 -1
  73. package/dist/infrastructure/settings/hot-reload.d.ts +9 -0
  74. package/dist/infrastructure/settings/hot-reload.d.ts.map +1 -1
  75. package/dist/infrastructure/settings/hot-reload.js +34 -5
  76. package/dist/infrastructure/settings/hot-reload.js.map +1 -1
  77. package/dist/infrastructure/settings/setting-keys.d.ts +2 -2
  78. package/dist/infrastructure/settings/setting-keys.d.ts.map +1 -1
  79. package/dist/infrastructure/settings/setting-keys.js +12 -3
  80. package/dist/infrastructure/settings/setting-keys.js.map +1 -1
  81. package/dist/infrastructure/token-registry/token-registry-service.d.ts +1 -0
  82. package/dist/infrastructure/token-registry/token-registry-service.d.ts.map +1 -1
  83. package/dist/infrastructure/token-registry/token-registry-service.js +15 -1
  84. package/dist/infrastructure/token-registry/token-registry-service.js.map +1 -1
  85. package/dist/lifecycle/daemon.d.ts +3 -1
  86. package/dist/lifecycle/daemon.d.ts.map +1 -1
  87. package/dist/lifecycle/daemon.js +84 -4
  88. package/dist/lifecycle/daemon.js.map +1 -1
  89. package/dist/notifications/channels/discord.d.ts.map +1 -1
  90. package/dist/notifications/channels/discord.js +21 -8
  91. package/dist/notifications/channels/discord.js.map +1 -1
  92. package/dist/notifications/channels/format-utils.d.ts +11 -0
  93. package/dist/notifications/channels/format-utils.d.ts.map +1 -0
  94. package/dist/notifications/channels/format-utils.js +19 -0
  95. package/dist/notifications/channels/format-utils.js.map +1 -0
  96. package/dist/notifications/channels/ntfy.d.ts.map +1 -1
  97. package/dist/notifications/channels/ntfy.js +18 -2
  98. package/dist/notifications/channels/ntfy.js.map +1 -1
  99. package/dist/notifications/channels/slack.d.ts.map +1 -1
  100. package/dist/notifications/channels/slack.js +20 -7
  101. package/dist/notifications/channels/slack.js.map +1 -1
  102. package/dist/notifications/channels/telegram.d.ts.map +1 -1
  103. package/dist/notifications/channels/telegram.js +21 -5
  104. package/dist/notifications/channels/telegram.js.map +1 -1
  105. package/dist/notifications/notification-service.d.ts +14 -0
  106. package/dist/notifications/notification-service.d.ts.map +1 -1
  107. package/dist/notifications/notification-service.js +89 -2
  108. package/dist/notifications/notification-service.js.map +1 -1
  109. package/dist/pipeline/database-policy-engine.d.ts +9 -5
  110. package/dist/pipeline/database-policy-engine.d.ts.map +1 -1
  111. package/dist/pipeline/database-policy-engine.js +42 -10
  112. package/dist/pipeline/database-policy-engine.js.map +1 -1
  113. package/dist/pipeline/resolve-effective-amount-usd.d.ts +1 -1
  114. package/dist/pipeline/resolve-effective-amount-usd.d.ts.map +1 -1
  115. package/dist/pipeline/resolve-effective-amount-usd.js +5 -3
  116. package/dist/pipeline/resolve-effective-amount-usd.js.map +1 -1
  117. package/dist/pipeline/stages.d.ts.map +1 -1
  118. package/dist/pipeline/stages.js +7 -1
  119. package/dist/pipeline/stages.js.map +1 -1
  120. package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.d.ts +11 -0
  121. package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.d.ts.map +1 -0
  122. package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.js +432 -0
  123. package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.js.map +1 -0
  124. package/dist/services/incoming/__tests__/incoming-tx-queue.test.d.ts +12 -0
  125. package/dist/services/incoming/__tests__/incoming-tx-queue.test.d.ts.map +1 -0
  126. package/dist/services/incoming/__tests__/incoming-tx-queue.test.js +419 -0
  127. package/dist/services/incoming/__tests__/incoming-tx-queue.test.js.map +1 -0
  128. package/dist/services/incoming/__tests__/incoming-tx-workers.test.d.ts +14 -0
  129. package/dist/services/incoming/__tests__/incoming-tx-workers.test.d.ts.map +1 -0
  130. package/dist/services/incoming/__tests__/incoming-tx-workers.test.js +452 -0
  131. package/dist/services/incoming/__tests__/incoming-tx-workers.test.js.map +1 -0
  132. package/dist/services/incoming/__tests__/integration-pitfall.test.d.ts +17 -0
  133. package/dist/services/incoming/__tests__/integration-pitfall.test.d.ts.map +1 -0
  134. package/dist/services/incoming/__tests__/integration-pitfall.test.js +653 -0
  135. package/dist/services/incoming/__tests__/integration-pitfall.test.js.map +1 -0
  136. package/dist/services/incoming/__tests__/integration-resilience.test.d.ts +14 -0
  137. package/dist/services/incoming/__tests__/integration-resilience.test.d.ts.map +1 -0
  138. package/dist/services/incoming/__tests__/integration-resilience.test.js +501 -0
  139. package/dist/services/incoming/__tests__/integration-resilience.test.js.map +1 -0
  140. package/dist/services/incoming/__tests__/integration-wiring.test.d.ts +15 -0
  141. package/dist/services/incoming/__tests__/integration-wiring.test.d.ts.map +1 -0
  142. package/dist/services/incoming/__tests__/integration-wiring.test.js +355 -0
  143. package/dist/services/incoming/__tests__/integration-wiring.test.js.map +1 -0
  144. package/dist/services/incoming/__tests__/safety-rules.test.d.ts +10 -0
  145. package/dist/services/incoming/__tests__/safety-rules.test.d.ts.map +1 -0
  146. package/dist/services/incoming/__tests__/safety-rules.test.js +165 -0
  147. package/dist/services/incoming/__tests__/safety-rules.test.js.map +1 -0
  148. package/dist/services/incoming/__tests__/subscription-multiplexer.test.d.ts +2 -0
  149. package/dist/services/incoming/__tests__/subscription-multiplexer.test.d.ts.map +1 -0
  150. package/dist/services/incoming/__tests__/subscription-multiplexer.test.js +267 -0
  151. package/dist/services/incoming/__tests__/subscription-multiplexer.test.js.map +1 -0
  152. package/dist/services/incoming/incoming-tx-monitor-service.d.ts +98 -0
  153. package/dist/services/incoming/incoming-tx-monitor-service.d.ts.map +1 -0
  154. package/dist/services/incoming/incoming-tx-monitor-service.js +357 -0
  155. package/dist/services/incoming/incoming-tx-monitor-service.js.map +1 -0
  156. package/dist/services/incoming/incoming-tx-queue.d.ts +52 -0
  157. package/dist/services/incoming/incoming-tx-queue.d.ts.map +1 -0
  158. package/dist/services/incoming/incoming-tx-queue.js +109 -0
  159. package/dist/services/incoming/incoming-tx-queue.js.map +1 -0
  160. package/dist/services/incoming/incoming-tx-workers.d.ts +89 -0
  161. package/dist/services/incoming/incoming-tx-workers.d.ts.map +1 -0
  162. package/dist/services/incoming/incoming-tx-workers.js +176 -0
  163. package/dist/services/incoming/incoming-tx-workers.js.map +1 -0
  164. package/dist/services/incoming/index.d.ts +14 -0
  165. package/dist/services/incoming/index.d.ts.map +1 -0
  166. package/dist/services/incoming/index.js +11 -0
  167. package/dist/services/incoming/index.js.map +1 -0
  168. package/dist/services/incoming/safety-rules.d.ts +70 -0
  169. package/dist/services/incoming/safety-rules.d.ts.map +1 -0
  170. package/dist/services/incoming/safety-rules.js +68 -0
  171. package/dist/services/incoming/safety-rules.js.map +1 -0
  172. package/dist/services/incoming/subscription-multiplexer.d.ts +87 -0
  173. package/dist/services/incoming/subscription-multiplexer.d.ts.map +1 -0
  174. package/dist/services/incoming/subscription-multiplexer.js +169 -0
  175. package/dist/services/incoming/subscription-multiplexer.js.map +1 -0
  176. package/dist/services/signing-sdk/approval-channel-router.d.ts +1 -1
  177. package/dist/services/signing-sdk/approval-channel-router.d.ts.map +1 -1
  178. package/dist/services/signing-sdk/approval-channel-router.js +2 -3
  179. package/dist/services/signing-sdk/approval-channel-router.js.map +1 -1
  180. package/dist/services/signing-sdk/channels/wallet-notification-channel.js +1 -1
  181. package/dist/services/signing-sdk/channels/wallet-notification-channel.js.map +1 -1
  182. package/dist/services/wc-session-service.d.ts +0 -2
  183. package/dist/services/wc-session-service.d.ts.map +1 -1
  184. package/dist/services/wc-session-service.js +2 -23
  185. package/dist/services/wc-session-service.js.map +1 -1
  186. package/dist/services/x402/x402-domain-policy.d.ts +6 -1
  187. package/dist/services/x402/x402-domain-policy.d.ts.map +1 -1
  188. package/dist/services/x402/x402-domain-policy.js +6 -2
  189. package/dist/services/x402/x402-domain-policy.js.map +1 -1
  190. package/package.json +4 -4
  191. package/public/admin/assets/index-D06O_cSo.js +1 -0
  192. package/public/admin/index.html +1 -1
  193. package/public/admin/assets/index-BLLOYSZp.js +0 -1
@@ -0,0 +1,357 @@
1
+ /**
2
+ * IncomingTxMonitorService -- top-level orchestrator for incoming transaction monitoring.
3
+ *
4
+ * Wires together:
5
+ * - IncomingTxQueue (memory buffer + batch flush)
6
+ * - SubscriptionMultiplexer (shared chain connections per chain:network)
7
+ * - BackgroundWorkers (6 periodic tasks: flush, retention, 2x confirmation, 2x polling)
8
+ * - Safety rules (DustAttackRule, UnknownTokenRule, LargeAmountRule)
9
+ * - EventBus (transaction:incoming, transaction:incoming:suspicious events)
10
+ * - KillSwitchService (notification suppression when SUSPENDED/LOCKED)
11
+ * - NotificationService (per-wallet per-event-type cooldown)
12
+ *
13
+ * Lifecycle:
14
+ * - start(): create queue + multiplexer, load wallets, register workers
15
+ * - stop(): drain queue, stop all subscriptions, clear cooldowns
16
+ * - updateConfig(): merge partial config (used by HotReloadOrchestrator)
17
+ * - syncSubscriptions(): reconcile DB wallets with active multiplexer subscriptions
18
+ *
19
+ * @see docs/76-incoming-transaction-monitoring.md
20
+ */
21
+ import { IncomingTxQueue } from './incoming-tx-queue.js';
22
+ import { SubscriptionMultiplexer } from './subscription-multiplexer.js';
23
+ import { createConfirmationWorkerHandler, createRetentionWorkerHandler, createGapRecoveryHandler, updateCursor, } from './incoming-tx-workers.js';
24
+ import { DustAttackRule, UnknownTokenRule, LargeAmountRule, } from './safety-rules.js';
25
+ // ── IncomingTxMonitorService ────────────────────────────────────
26
+ export class IncomingTxMonitorService {
27
+ sqlite;
28
+ workers;
29
+ eventBus;
30
+ killSwitchService;
31
+ notificationService;
32
+ subscriberFactory;
33
+ queue;
34
+ multiplexer;
35
+ safetyRules;
36
+ notifyCooldown = new Map();
37
+ config;
38
+ constructor(deps) {
39
+ this.sqlite = deps.sqlite;
40
+ this.workers = deps.workers;
41
+ this.eventBus = deps.eventBus;
42
+ this.killSwitchService = deps.killSwitchService ?? null;
43
+ this.notificationService = deps.notificationService ?? null;
44
+ this.subscriberFactory = deps.subscriberFactory;
45
+ this.config = { ...deps.config };
46
+ // Initialize safety rules
47
+ this.safetyRules = [
48
+ new DustAttackRule(),
49
+ new UnknownTokenRule(),
50
+ new LargeAmountRule(),
51
+ ];
52
+ // Create queue
53
+ this.queue = new IncomingTxQueue();
54
+ }
55
+ /**
56
+ * Start the monitoring service.
57
+ *
58
+ * 1. Create multiplexer with gap recovery handler
59
+ * 2. Load wallets with monitor_incoming = 1 from DB
60
+ * 3. Subscribe each wallet via multiplexer
61
+ * 4. Register 6 background workers
62
+ */
63
+ async start() {
64
+ // Create gap recovery handler (needs reference to multiplexer connections)
65
+ // We'll create it after multiplexer since it needs the internal connections map
66
+ // For now, use a thin wrapper
67
+ // Create multiplexer
68
+ this.multiplexer = new SubscriptionMultiplexer({
69
+ subscriberFactory: this.subscriberFactory,
70
+ onTransaction: (tx) => {
71
+ this.queue.push(tx);
72
+ },
73
+ onGapRecovery: async (chain, network, walletIds) => {
74
+ // Wire to createGapRecoveryHandler using multiplexer's subscriber access.
75
+ // this.multiplexer is captured via closure on 'this' -- safe because
76
+ // onGapRecovery is never called during construction (only on reconnect).
77
+ const handler = createGapRecoveryHandler({
78
+ subscribers: this.multiplexer.getSubscriberEntries(),
79
+ });
80
+ await handler(chain, network, walletIds);
81
+ },
82
+ });
83
+ // Load wallets with monitor_incoming = 1
84
+ const wallets = this.sqlite
85
+ .prepare(`SELECT id, chain, network, public_key FROM wallets WHERE monitor_incoming = 1`)
86
+ .all();
87
+ // Subscribe each wallet
88
+ for (const wallet of wallets) {
89
+ try {
90
+ await this.multiplexer.addWallet(wallet.chain, wallet.network, wallet.id, wallet.public_key);
91
+ }
92
+ catch (err) {
93
+ console.warn(`IncomingTxMonitor: failed to subscribe wallet ${wallet.id}:`, err);
94
+ }
95
+ }
96
+ // Register 6 background workers
97
+ this.registerWorkers();
98
+ console.debug(`IncomingTxMonitorService started: ${wallets.length} wallets subscribed`);
99
+ }
100
+ /**
101
+ * Stop the monitoring service.
102
+ *
103
+ * 1. Drain queue (final flush of remaining items)
104
+ * 2. Stop all subscriber connections
105
+ * 3. Clear cooldown map
106
+ */
107
+ async stop() {
108
+ // 1. Final queue drain
109
+ this.queue.drain(this.sqlite);
110
+ // 2. Destroy all multiplexer connections
111
+ if (this.multiplexer) {
112
+ await this.multiplexer.stopAll();
113
+ }
114
+ // 3. Clear cooldowns
115
+ this.notifyCooldown.clear();
116
+ console.debug('IncomingTxMonitorService stopped');
117
+ }
118
+ /**
119
+ * Update configuration (used by HotReloadOrchestrator).
120
+ */
121
+ updateConfig(partial) {
122
+ this.config = { ...this.config, ...partial };
123
+ }
124
+ /**
125
+ * Re-read wallets with monitor_incoming=1 from DB and reconcile
126
+ * with current multiplexer subscriptions.
127
+ */
128
+ async syncSubscriptions() {
129
+ const dbWallets = this.sqlite
130
+ .prepare(`SELECT id, chain, network, public_key FROM wallets WHERE monitor_incoming = 1`)
131
+ .all();
132
+ // Add any new wallets to multiplexer (addWallet handles dedup internally)
133
+ for (const wallet of dbWallets) {
134
+ try {
135
+ await this.multiplexer.addWallet(wallet.chain, wallet.network, wallet.id, wallet.public_key);
136
+ }
137
+ catch (err) {
138
+ console.warn(`syncSubscriptions: failed to add wallet ${wallet.id}:`, err);
139
+ }
140
+ }
141
+ }
142
+ // ── Internal: flush handler logic ──────────────────────────────
143
+ /**
144
+ * Core flush worker: flush queue, evaluate safety rules, emit events, send notifications.
145
+ */
146
+ createFlushHandler() {
147
+ return async () => {
148
+ const inserted = this.queue.flush(this.sqlite);
149
+ if (inserted.length === 0)
150
+ return;
151
+ for (const tx of inserted) {
152
+ // 1. Evaluate safety rules
153
+ const context = this.buildSafetyRuleContext(tx);
154
+ const suspiciousReasons = this.safetyRules
155
+ .filter((rule) => rule.check(tx, context))
156
+ .map((rule) => rule.name);
157
+ const isSuspicious = suspiciousReasons.length > 0;
158
+ if (isSuspicious) {
159
+ this.sqlite
160
+ .prepare('UPDATE incoming_transactions SET is_suspicious = 1 WHERE id = ?')
161
+ .run(tx.id);
162
+ }
163
+ // 2. Emit events (always, regardless of KillSwitch)
164
+ this.eventBus.emit('transaction:incoming', {
165
+ walletId: tx.walletId,
166
+ txHash: tx.txHash,
167
+ fromAddress: tx.fromAddress,
168
+ amount: tx.amount,
169
+ tokenAddress: tx.tokenAddress,
170
+ chain: tx.chain,
171
+ network: tx.network,
172
+ status: tx.status,
173
+ timestamp: tx.detectedAt,
174
+ });
175
+ if (isSuspicious) {
176
+ this.eventBus.emit('transaction:incoming:suspicious', {
177
+ walletId: tx.walletId,
178
+ txHash: tx.txHash,
179
+ fromAddress: tx.fromAddress,
180
+ amount: tx.amount,
181
+ tokenAddress: tx.tokenAddress,
182
+ chain: tx.chain,
183
+ network: tx.network,
184
+ status: tx.status,
185
+ timestamp: tx.detectedAt,
186
+ suspiciousReasons,
187
+ });
188
+ }
189
+ // 3. Send notifications (suppressed by KillSwitch, subject to cooldown)
190
+ const killState = this.killSwitchService?.getState();
191
+ if (!killState || killState.state === 'ACTIVE') {
192
+ const eventType = isSuspicious
193
+ ? 'TX_INCOMING_SUSPICIOUS'
194
+ : 'TX_INCOMING';
195
+ if (!this.isCooldownActive(tx.walletId, eventType)) {
196
+ this.notificationService?.notify(eventType, tx.walletId, {
197
+ walletId: tx.walletId,
198
+ txHash: tx.txHash,
199
+ amount: tx.amount,
200
+ fromAddress: tx.fromAddress,
201
+ chain: tx.chain,
202
+ display_amount: '',
203
+ ...(isSuspicious && suspiciousReasons ? { reasons: suspiciousReasons.join(', ') } : {}),
204
+ });
205
+ this.recordCooldown(tx.walletId, eventType);
206
+ }
207
+ }
208
+ // 4. Update cursor
209
+ updateCursor(this.sqlite, tx.walletId, tx.chain, tx.network, tx.blockNumber != null ? String(tx.blockNumber) : tx.txHash);
210
+ }
211
+ };
212
+ }
213
+ /**
214
+ * Build safety rule context for a transaction.
215
+ * Uses best-effort data -- null for unavailable price/average data.
216
+ */
217
+ buildSafetyRuleContext(tx) {
218
+ // Token registry lookup: check if tokenAddress is registered
219
+ let isRegisteredToken = true;
220
+ if (tx.tokenAddress !== null) {
221
+ try {
222
+ const row = this.sqlite
223
+ .prepare(`SELECT 1 FROM token_registry WHERE address = ? AND chain = ? LIMIT 1`)
224
+ .get(tx.tokenAddress, tx.chain);
225
+ isRegisteredToken = !!row;
226
+ }
227
+ catch {
228
+ // Table may not exist or query may fail -- safe default
229
+ isRegisteredToken = true;
230
+ }
231
+ }
232
+ // Decimals: try to get from token registry, fallback to chain default
233
+ let decimals = tx.chain === 'solana' ? 9 : 18;
234
+ if (tx.tokenAddress !== null) {
235
+ try {
236
+ const tokenRow = this.sqlite
237
+ .prepare(`SELECT decimals FROM token_registry WHERE address = ? AND chain = ? LIMIT 1`)
238
+ .get(tx.tokenAddress, tx.chain);
239
+ if (tokenRow) {
240
+ decimals = tokenRow.decimals;
241
+ }
242
+ }
243
+ catch {
244
+ // Use default
245
+ }
246
+ }
247
+ // USD price: not available in this phase (requires PriceOracle integration)
248
+ // Will be wired in Phase 227
249
+ const usdPrice = null;
250
+ // Average incoming USD: not computed yet (requires historical aggregation)
251
+ // Will be wired in Phase 227
252
+ const avgIncomingUsd = null;
253
+ return {
254
+ dustThresholdUsd: this.config.dustThresholdUsd,
255
+ amountMultiplier: this.config.amountMultiplier,
256
+ isRegisteredToken,
257
+ usdPrice,
258
+ avgIncomingUsd,
259
+ decimals,
260
+ };
261
+ }
262
+ // ── Internal: cooldown logic ───────────────────────────────────
263
+ isCooldownActive(walletId, eventType) {
264
+ const key = `${walletId}:${eventType}`;
265
+ const lastNotified = this.notifyCooldown.get(key);
266
+ if (lastNotified === undefined)
267
+ return false;
268
+ const now = Math.floor(Date.now() / 1000);
269
+ return now - lastNotified < this.config.cooldownMinutes * 60;
270
+ }
271
+ recordCooldown(walletId, eventType) {
272
+ const key = `${walletId}:${eventType}`;
273
+ this.notifyCooldown.set(key, Math.floor(Date.now() / 1000));
274
+ }
275
+ // ── Internal: worker registration ──────────────────────────────
276
+ registerWorkers() {
277
+ // 1. Flush worker (5s)
278
+ this.workers.register('incoming-tx-flush', {
279
+ interval: 5_000,
280
+ handler: this.createFlushHandler(),
281
+ });
282
+ // 2. Retention worker (1 hour)
283
+ this.workers.register('incoming-tx-retention', {
284
+ interval: 3_600_000,
285
+ handler: createRetentionWorkerHandler({
286
+ sqlite: this.sqlite,
287
+ getRetentionDays: () => this.config.retentionDays,
288
+ }),
289
+ });
290
+ // 3. Confirmation worker for Solana (30s)
291
+ // Build checkSolanaFinalized callback from multiplexer's Solana subscriber.
292
+ // Uses structural typing to access checkFinalized() without importing the concrete class.
293
+ const checkSolanaFinalized = async (txHash) => {
294
+ const entries = this.multiplexer.getSubscribersForChain('solana');
295
+ if (entries.length === 0)
296
+ return false;
297
+ const sub = entries[0].subscriber;
298
+ return sub.checkFinalized(txHash);
299
+ };
300
+ this.workers.register('incoming-tx-confirm-solana', {
301
+ interval: 30_000,
302
+ handler: createConfirmationWorkerHandler({
303
+ sqlite: this.sqlite,
304
+ checkSolanaFinalized,
305
+ }),
306
+ });
307
+ // 4. Confirmation worker for EVM (30s)
308
+ // Build getBlockNumber callback from multiplexer's EVM subscriber.
309
+ // Routes to the correct subscriber for the given chain:network pair.
310
+ const getBlockNumber = async (_chain, network) => {
311
+ const entries = this.multiplexer.getSubscribersForChain('ethereum');
312
+ const entry = entries.find((e) => e.key === `ethereum:${network}`);
313
+ if (!entry)
314
+ throw new Error(`No EVM subscriber for network ${network}`);
315
+ const sub = entry.subscriber;
316
+ return sub.getBlockNumber();
317
+ };
318
+ this.workers.register('incoming-tx-confirm-evm', {
319
+ interval: 30_000,
320
+ handler: createConfirmationWorkerHandler({
321
+ sqlite: this.sqlite,
322
+ getBlockNumber,
323
+ }),
324
+ });
325
+ // 5. Polling worker for Solana (configurable interval)
326
+ this.workers.register('incoming-tx-poll-solana', {
327
+ interval: this.config.pollIntervalSec * 1000,
328
+ handler: async () => {
329
+ const entries = this.multiplexer.getSubscribersForChain('solana');
330
+ for (const { subscriber } of entries) {
331
+ try {
332
+ await subscriber.pollAll();
333
+ }
334
+ catch (err) {
335
+ console.warn('Solana polling worker error:', err);
336
+ }
337
+ }
338
+ },
339
+ });
340
+ // 6. Polling worker for EVM (configurable interval)
341
+ this.workers.register('incoming-tx-poll-evm', {
342
+ interval: this.config.pollIntervalSec * 1000,
343
+ handler: async () => {
344
+ const entries = this.multiplexer.getSubscribersForChain('ethereum');
345
+ for (const { subscriber } of entries) {
346
+ try {
347
+ await subscriber.pollAll();
348
+ }
349
+ catch (err) {
350
+ console.warn('EVM polling worker error:', err);
351
+ }
352
+ }
353
+ },
354
+ });
355
+ }
356
+ }
357
+ //# sourceMappingURL=incoming-tx-monitor-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"incoming-tx-monitor-service.js","sourceRoot":"","sources":["../../../src/services/incoming/incoming-tx-monitor-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * IncomingTxQueue: Memory buffer for incoming transactions.
3
+ *
4
+ * Collects incoming transactions from chain subscriber callbacks and
5
+ * batch-flushes them to SQLite in periodic intervals. Prevents SQLITE_BUSY
6
+ * contention from concurrent WebSocket callbacks.
7
+ *
8
+ * Features:
9
+ * - Map-based in-memory deduplication by txHash:walletId composite key
10
+ * - Bounded memory (MAX_QUEUE_SIZE = 10,000) with oldest-first eviction
11
+ * - Batch INSERT with ON CONFLICT DO NOTHING for DB-level safety
12
+ * - flush() returns only actually-inserted records
13
+ *
14
+ * @see docs/76-incoming-transaction-monitoring.md section 2.6
15
+ */
16
+ import type { Database } from 'better-sqlite3';
17
+ import type { IncomingTransaction } from '@waiaas/core';
18
+ export declare class IncomingTxQueue {
19
+ private queue;
20
+ /** Current number of queued items. */
21
+ get size(): number;
22
+ /**
23
+ * Add a transaction to the queue. Synchronous, O(1).
24
+ *
25
+ * Deduplicates by txHash:walletId composite key -- if the same key
26
+ * already exists in the queue, the push is silently ignored.
27
+ *
28
+ * If queue is at MAX_QUEUE_SIZE, the oldest entry (first Map key)
29
+ * is evicted before the new entry is added.
30
+ */
31
+ push(tx: IncomingTransaction): void;
32
+ /**
33
+ * Batch-flush up to MAX_BATCH items from the queue to SQLite.
34
+ *
35
+ * Extracts items from the queue, generates UUID v7 IDs, and inserts
36
+ * them atomically using a SQLite transaction with ON CONFLICT DO NOTHING.
37
+ *
38
+ * @returns Only the actually-inserted IncomingTransaction items
39
+ * (excludes ON CONFLICT skipped ones).
40
+ */
41
+ flush(sqlite: Database): IncomingTransaction[];
42
+ /**
43
+ * Flush the entire queue (for graceful shutdown).
44
+ *
45
+ * Calls flush() in a loop until the queue is empty,
46
+ * collecting all successfully inserted items.
47
+ *
48
+ * @returns All inserted IncomingTransaction items across all flush cycles.
49
+ */
50
+ drain(sqlite: Database): IncomingTransaction[];
51
+ }
52
+ //# sourceMappingURL=incoming-tx-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"incoming-tx-queue.d.ts","sourceRoot":"","sources":["../../../src/services/incoming/incoming-tx-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAgBxD,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAA0C;IAEvD,sCAAsC;IACtC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,EAAE,mBAAmB,GAAG,IAAI;IAgBnC;;;;;;;;OAQG;IACH,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG,mBAAmB,EAAE;IA4C9C;;;;;;;OAOG;IACH,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG,mBAAmB,EAAE;CAQ/C"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * IncomingTxQueue: Memory buffer for incoming transactions.
3
+ *
4
+ * Collects incoming transactions from chain subscriber callbacks and
5
+ * batch-flushes them to SQLite in periodic intervals. Prevents SQLITE_BUSY
6
+ * contention from concurrent WebSocket callbacks.
7
+ *
8
+ * Features:
9
+ * - Map-based in-memory deduplication by txHash:walletId composite key
10
+ * - Bounded memory (MAX_QUEUE_SIZE = 10,000) with oldest-first eviction
11
+ * - Batch INSERT with ON CONFLICT DO NOTHING for DB-level safety
12
+ * - flush() returns only actually-inserted records
13
+ *
14
+ * @see docs/76-incoming-transaction-monitoring.md section 2.6
15
+ */
16
+ import { generateId } from '../../infrastructure/database/id.js';
17
+ /** Maximum items extracted per flush() call. */
18
+ const MAX_BATCH = 100;
19
+ /** Maximum queue size before oldest entries are evicted. */
20
+ const MAX_QUEUE_SIZE = 10_000;
21
+ /**
22
+ * INSERT statement for incoming_transactions table.
23
+ * 13 columns matching DB v21 schema. ON CONFLICT(tx_hash, wallet_id) DO NOTHING
24
+ * provides DB-level dedup safety net.
25
+ */
26
+ const INSERT_SQL = `INSERT INTO incoming_transactions (id, wallet_id, chain, network, tx_hash, from_address, amount, token_address, status, block_number, detected_at, confirmed_at, is_suspicious) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(tx_hash, wallet_id) DO NOTHING`;
27
+ export class IncomingTxQueue {
28
+ queue = new Map();
29
+ /** Current number of queued items. */
30
+ get size() {
31
+ return this.queue.size;
32
+ }
33
+ /**
34
+ * Add a transaction to the queue. Synchronous, O(1).
35
+ *
36
+ * Deduplicates by txHash:walletId composite key -- if the same key
37
+ * already exists in the queue, the push is silently ignored.
38
+ *
39
+ * If queue is at MAX_QUEUE_SIZE, the oldest entry (first Map key)
40
+ * is evicted before the new entry is added.
41
+ */
42
+ push(tx) {
43
+ if (this.queue.size >= MAX_QUEUE_SIZE) {
44
+ // Drop oldest entry (first key in insertion order) to prevent memory leak
45
+ const firstKey = this.queue.keys().next().value;
46
+ if (firstKey !== undefined) {
47
+ this.queue.delete(firstKey);
48
+ }
49
+ console.warn('IncomingTxQueue overflow: dropping oldest entry');
50
+ }
51
+ const key = `${tx.txHash}:${tx.walletId}`;
52
+ if (!this.queue.has(key)) {
53
+ this.queue.set(key, tx);
54
+ }
55
+ }
56
+ /**
57
+ * Batch-flush up to MAX_BATCH items from the queue to SQLite.
58
+ *
59
+ * Extracts items from the queue, generates UUID v7 IDs, and inserts
60
+ * them atomically using a SQLite transaction with ON CONFLICT DO NOTHING.
61
+ *
62
+ * @returns Only the actually-inserted IncomingTransaction items
63
+ * (excludes ON CONFLICT skipped ones).
64
+ */
65
+ flush(sqlite) {
66
+ if (this.queue.size === 0)
67
+ return [];
68
+ // Extract up to MAX_BATCH items from queue
69
+ const batch = [];
70
+ for (const [key, tx] of this.queue) {
71
+ batch.push(tx);
72
+ this.queue.delete(key);
73
+ if (batch.length >= MAX_BATCH)
74
+ break;
75
+ }
76
+ // Prepare INSERT statement
77
+ const stmt = sqlite.prepare(INSERT_SQL);
78
+ // Run all inserts atomically, tracking which rows were actually inserted
79
+ const insertMany = sqlite.transaction((txs) => {
80
+ const inserted = [];
81
+ for (const tx of txs) {
82
+ const id = generateId();
83
+ const result = stmt.run(id, tx.walletId, tx.chain, tx.network, tx.txHash, tx.fromAddress, tx.amount, tx.tokenAddress, tx.status, tx.blockNumber, tx.detectedAt, tx.confirmedAt, tx.isSuspicious ? 1 : 0);
84
+ if (result.changes > 0) {
85
+ inserted.push({ ...tx, id });
86
+ }
87
+ }
88
+ return inserted;
89
+ });
90
+ return insertMany(batch);
91
+ }
92
+ /**
93
+ * Flush the entire queue (for graceful shutdown).
94
+ *
95
+ * Calls flush() in a loop until the queue is empty,
96
+ * collecting all successfully inserted items.
97
+ *
98
+ * @returns All inserted IncomingTransaction items across all flush cycles.
99
+ */
100
+ drain(sqlite) {
101
+ const allInserted = [];
102
+ while (this.queue.size > 0) {
103
+ const inserted = this.flush(sqlite);
104
+ allInserted.push(...inserted);
105
+ }
106
+ return allInserted;
107
+ }
108
+ }
109
+ //# sourceMappingURL=incoming-tx-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"incoming-tx-queue.js","sourceRoot":"","sources":["../../../src/services/incoming/incoming-tx-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
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
+ import type { Database } from 'better-sqlite3';
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 declare const EVM_CONFIRMATION_THRESHOLDS: Record<string, number>;
22
+ /** Default threshold when network is not in EVM_CONFIRMATION_THRESHOLDS. */
23
+ export declare const DEFAULT_EVM_CONFIRMATIONS = 12;
24
+ /** Solana commitment level used for confirmation check. */
25
+ export declare const SOLANA_CONFIRMATION = "finalized";
26
+ interface ConfirmationWorkerDeps {
27
+ /** Raw SQLite handle for queries and updates. */
28
+ sqlite: Database;
29
+ /** Returns the current block number for the given EVM chain:network. */
30
+ getBlockNumber?: (chain: string, network: string) => Promise<bigint>;
31
+ /** Checks if a Solana transaction has reached finalized commitment. */
32
+ checkSolanaFinalized?: (txHash: string) => Promise<boolean>;
33
+ }
34
+ interface RetentionWorkerDeps {
35
+ /** Raw SQLite handle for DELETE operations. */
36
+ sqlite: Database;
37
+ /** Returns current retention period in days (supports hot-reload). */
38
+ getRetentionDays: () => number;
39
+ }
40
+ interface GapRecoveryDeps {
41
+ /** Map of connection key ("chain:network") to subscriber with pollAll(). */
42
+ subscribers: Map<string, {
43
+ subscriber: {
44
+ pollAll: () => Promise<void>;
45
+ };
46
+ }>;
47
+ }
48
+ /**
49
+ * Creates a handler that upgrades DETECTED transactions to CONFIRMED.
50
+ *
51
+ * - Solana: calls checkSolanaFinalized(txHash). If true -> CONFIRMED.
52
+ * - EVM: computes `currentBlock - txBlock`. If >= threshold -> CONFIRMED.
53
+ * - Per-record error isolation: one failure does not block other records.
54
+ */
55
+ export declare function createConfirmationWorkerHandler(deps: ConfirmationWorkerDeps): () => Promise<void>;
56
+ /**
57
+ * Creates a handler that deletes incoming_transactions records
58
+ * older than the configured retention period.
59
+ *
60
+ * Uses raw SQLite DELETE for efficiency. getRetentionDays() is called
61
+ * each invocation to support hot-reload of the setting.
62
+ */
63
+ export declare function createRetentionWorkerHandler(deps: RetentionWorkerDeps): () => Promise<void>;
64
+ /**
65
+ * Creates a handler for recovering missed transactions after reconnection.
66
+ *
67
+ * When a WebSocket reconnects, the gap between last-known cursor and current
68
+ * chain state needs to be filled. This handler finds the relevant subscriber
69
+ * and calls pollAll() to recover missed transactions.
70
+ */
71
+ export declare function createGapRecoveryHandler(deps: GapRecoveryDeps): (chain: string, network: string, walletIds: string[]) => Promise<void>;
72
+ /**
73
+ * Save or update the processing cursor for a wallet.
74
+ *
75
+ * Uses INSERT OR REPLACE to handle both new and existing cursors.
76
+ * The cursor value is chain-specific:
77
+ * - Solana: last processed transaction signature
78
+ * - EVM: last processed block number (as string)
79
+ */
80
+ export declare function updateCursor(sqlite: Database, walletId: string, chain: string, network: string, cursor: string): void;
81
+ /**
82
+ * Load the processing cursor for a wallet.
83
+ *
84
+ * Returns the cursor value (signature or block number as string),
85
+ * or null if no cursor exists for the given wallet+chain+network.
86
+ */
87
+ export declare function loadCursor(sqlite: Database, walletId: string, chain: string, network: string): string | null;
88
+ export {};
89
+ //# sourceMappingURL=incoming-tx-workers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"incoming-tx-workers.d.ts","sourceRoot":"","sources":["../../../src/services/incoming/incoming-tx-workers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAI/C;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAS9D,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C,2DAA2D;AAC3D,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAI/C,UAAU,sBAAsB;IAC9B,iDAAiD;IACjD,MAAM,EAAE,QAAQ,CAAC;IACjB,wEAAwE;IACxE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrE,uEAAuE;IACvE,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7D;AAED,UAAU,mBAAmB;IAC3B,+CAA+C;IAC/C,MAAM,EAAE,QAAQ,CAAC;IACjB,sEAAsE;IACtE,gBAAgB,EAAE,MAAM,MAAM,CAAC;CAChC;AAED,UAAU,eAAe;IACvB,4EAA4E;IAC5E,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE;YAAE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CAC5E;AAID;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,sBAAsB,GAC3B,MAAM,OAAO,CAAC,IAAI,CAAC,CAmErB;AAID;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,mBAAmB,GACxB,MAAM,OAAO,CAAC,IAAI,CAAC,CAgBrB;AAID;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,eAAe,GACpB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAqBxE;AAID;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,QAAQ,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,IAAI,CAiBN;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,QAAQ,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAgBf"}