@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.
- package/dist/api/middleware/error-handler.d.ts +1 -1
- package/dist/api/middleware/error-handler.js +2 -2
- package/dist/api/middleware/error-handler.js.map +1 -1
- package/dist/api/routes/admin.d.ts.map +1 -1
- package/dist/api/routes/admin.js +6 -30
- package/dist/api/routes/admin.js.map +1 -1
- package/dist/api/routes/connect-info.d.ts.map +1 -1
- package/dist/api/routes/connect-info.js +6 -0
- package/dist/api/routes/connect-info.js.map +1 -1
- package/dist/api/routes/incoming.d.ts +40 -0
- package/dist/api/routes/incoming.d.ts.map +1 -0
- package/dist/api/routes/incoming.js +281 -0
- package/dist/api/routes/incoming.js.map +1 -0
- package/dist/api/routes/openapi-schemas.d.ts +337 -24
- package/dist/api/routes/openapi-schemas.d.ts.map +1 -1
- package/dist/api/routes/openapi-schemas.js +78 -0
- package/dist/api/routes/openapi-schemas.js.map +1 -1
- package/dist/api/routes/tokens.d.ts.map +1 -1
- package/dist/api/routes/tokens.js +1 -0
- package/dist/api/routes/tokens.js.map +1 -1
- package/dist/api/routes/wallets.d.ts +4 -0
- package/dist/api/routes/wallets.d.ts.map +1 -1
- package/dist/api/routes/wallets.js +173 -1
- package/dist/api/routes/wallets.js.map +1 -1
- package/dist/api/routes/x402.js +1 -1
- package/dist/api/routes/x402.js.map +1 -1
- package/dist/api/server.d.ts +4 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +12 -0
- package/dist/api/server.js.map +1 -1
- package/dist/infrastructure/config/loader.d.ts +43 -0
- package/dist/infrastructure/config/loader.d.ts.map +1 -1
- package/dist/infrastructure/config/loader.js +13 -1
- package/dist/infrastructure/config/loader.js.map +1 -1
- package/dist/infrastructure/database/index.d.ts +1 -1
- package/dist/infrastructure/database/index.d.ts.map +1 -1
- package/dist/infrastructure/database/index.js +1 -1
- package/dist/infrastructure/database/index.js.map +1 -1
- package/dist/infrastructure/database/migrate.d.ts +2 -2
- package/dist/infrastructure/database/migrate.d.ts.map +1 -1
- package/dist/infrastructure/database/migrate.js +123 -6
- package/dist/infrastructure/database/migrate.js.map +1 -1
- package/dist/infrastructure/database/schema.d.ts +400 -1
- package/dist/infrastructure/database/schema.d.ts.map +1 -1
- package/dist/infrastructure/database/schema.js +43 -2
- package/dist/infrastructure/database/schema.js.map +1 -1
- package/dist/infrastructure/oracle/coingecko-oracle.d.ts +9 -1
- package/dist/infrastructure/oracle/coingecko-oracle.d.ts.map +1 -1
- package/dist/infrastructure/oracle/coingecko-oracle.js +40 -23
- package/dist/infrastructure/oracle/coingecko-oracle.js.map +1 -1
- package/dist/infrastructure/oracle/coingecko-platform-ids.d.ts +11 -10
- package/dist/infrastructure/oracle/coingecko-platform-ids.d.ts.map +1 -1
- package/dist/infrastructure/oracle/coingecko-platform-ids.js +20 -12
- package/dist/infrastructure/oracle/coingecko-platform-ids.js.map +1 -1
- package/dist/infrastructure/oracle/index.d.ts +1 -1
- package/dist/infrastructure/oracle/index.d.ts.map +1 -1
- package/dist/infrastructure/oracle/index.js +1 -1
- package/dist/infrastructure/oracle/index.js.map +1 -1
- package/dist/infrastructure/oracle/oracle-chain.d.ts.map +1 -1
- package/dist/infrastructure/oracle/oracle-chain.js +7 -4
- package/dist/infrastructure/oracle/oracle-chain.js.map +1 -1
- package/dist/infrastructure/oracle/price-cache.d.ts +24 -8
- package/dist/infrastructure/oracle/price-cache.d.ts.map +1 -1
- package/dist/infrastructure/oracle/price-cache.js +32 -11
- package/dist/infrastructure/oracle/price-cache.js.map +1 -1
- package/dist/infrastructure/oracle/pyth-feed-ids.d.ts +11 -8
- package/dist/infrastructure/oracle/pyth-feed-ids.d.ts.map +1 -1
- package/dist/infrastructure/oracle/pyth-feed-ids.js +16 -17
- package/dist/infrastructure/oracle/pyth-feed-ids.js.map +1 -1
- package/dist/infrastructure/oracle/pyth-oracle.d.ts.map +1 -1
- package/dist/infrastructure/oracle/pyth-oracle.js +7 -4
- package/dist/infrastructure/oracle/pyth-oracle.js.map +1 -1
- package/dist/infrastructure/settings/hot-reload.d.ts +9 -0
- package/dist/infrastructure/settings/hot-reload.d.ts.map +1 -1
- package/dist/infrastructure/settings/hot-reload.js +34 -5
- package/dist/infrastructure/settings/hot-reload.js.map +1 -1
- package/dist/infrastructure/settings/setting-keys.d.ts +2 -2
- package/dist/infrastructure/settings/setting-keys.d.ts.map +1 -1
- package/dist/infrastructure/settings/setting-keys.js +12 -3
- package/dist/infrastructure/settings/setting-keys.js.map +1 -1
- package/dist/infrastructure/token-registry/token-registry-service.d.ts +1 -0
- package/dist/infrastructure/token-registry/token-registry-service.d.ts.map +1 -1
- package/dist/infrastructure/token-registry/token-registry-service.js +15 -1
- package/dist/infrastructure/token-registry/token-registry-service.js.map +1 -1
- package/dist/lifecycle/daemon.d.ts +3 -1
- package/dist/lifecycle/daemon.d.ts.map +1 -1
- package/dist/lifecycle/daemon.js +84 -4
- package/dist/lifecycle/daemon.js.map +1 -1
- package/dist/notifications/channels/discord.d.ts.map +1 -1
- package/dist/notifications/channels/discord.js +21 -8
- package/dist/notifications/channels/discord.js.map +1 -1
- package/dist/notifications/channels/format-utils.d.ts +11 -0
- package/dist/notifications/channels/format-utils.d.ts.map +1 -0
- package/dist/notifications/channels/format-utils.js +19 -0
- package/dist/notifications/channels/format-utils.js.map +1 -0
- package/dist/notifications/channels/ntfy.d.ts.map +1 -1
- package/dist/notifications/channels/ntfy.js +18 -2
- package/dist/notifications/channels/ntfy.js.map +1 -1
- package/dist/notifications/channels/slack.d.ts.map +1 -1
- package/dist/notifications/channels/slack.js +20 -7
- package/dist/notifications/channels/slack.js.map +1 -1
- package/dist/notifications/channels/telegram.d.ts.map +1 -1
- package/dist/notifications/channels/telegram.js +21 -5
- package/dist/notifications/channels/telegram.js.map +1 -1
- package/dist/notifications/notification-service.d.ts +14 -0
- package/dist/notifications/notification-service.d.ts.map +1 -1
- package/dist/notifications/notification-service.js +89 -2
- package/dist/notifications/notification-service.js.map +1 -1
- package/dist/pipeline/database-policy-engine.d.ts +9 -5
- package/dist/pipeline/database-policy-engine.d.ts.map +1 -1
- package/dist/pipeline/database-policy-engine.js +42 -10
- package/dist/pipeline/database-policy-engine.js.map +1 -1
- package/dist/pipeline/resolve-effective-amount-usd.d.ts +1 -1
- package/dist/pipeline/resolve-effective-amount-usd.d.ts.map +1 -1
- package/dist/pipeline/resolve-effective-amount-usd.js +5 -3
- package/dist/pipeline/resolve-effective-amount-usd.js.map +1 -1
- package/dist/pipeline/stages.d.ts.map +1 -1
- package/dist/pipeline/stages.js +7 -1
- package/dist/pipeline/stages.js.map +1 -1
- package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.d.ts +11 -0
- package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.d.ts.map +1 -0
- package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.js +432 -0
- package/dist/services/incoming/__tests__/incoming-tx-monitor-service.test.js.map +1 -0
- package/dist/services/incoming/__tests__/incoming-tx-queue.test.d.ts +12 -0
- package/dist/services/incoming/__tests__/incoming-tx-queue.test.d.ts.map +1 -0
- package/dist/services/incoming/__tests__/incoming-tx-queue.test.js +419 -0
- package/dist/services/incoming/__tests__/incoming-tx-queue.test.js.map +1 -0
- package/dist/services/incoming/__tests__/incoming-tx-workers.test.d.ts +14 -0
- package/dist/services/incoming/__tests__/incoming-tx-workers.test.d.ts.map +1 -0
- package/dist/services/incoming/__tests__/incoming-tx-workers.test.js +452 -0
- package/dist/services/incoming/__tests__/incoming-tx-workers.test.js.map +1 -0
- package/dist/services/incoming/__tests__/integration-pitfall.test.d.ts +17 -0
- package/dist/services/incoming/__tests__/integration-pitfall.test.d.ts.map +1 -0
- package/dist/services/incoming/__tests__/integration-pitfall.test.js +653 -0
- package/dist/services/incoming/__tests__/integration-pitfall.test.js.map +1 -0
- package/dist/services/incoming/__tests__/integration-resilience.test.d.ts +14 -0
- package/dist/services/incoming/__tests__/integration-resilience.test.d.ts.map +1 -0
- package/dist/services/incoming/__tests__/integration-resilience.test.js +501 -0
- package/dist/services/incoming/__tests__/integration-resilience.test.js.map +1 -0
- package/dist/services/incoming/__tests__/integration-wiring.test.d.ts +15 -0
- package/dist/services/incoming/__tests__/integration-wiring.test.d.ts.map +1 -0
- package/dist/services/incoming/__tests__/integration-wiring.test.js +355 -0
- package/dist/services/incoming/__tests__/integration-wiring.test.js.map +1 -0
- package/dist/services/incoming/__tests__/safety-rules.test.d.ts +10 -0
- package/dist/services/incoming/__tests__/safety-rules.test.d.ts.map +1 -0
- package/dist/services/incoming/__tests__/safety-rules.test.js +165 -0
- package/dist/services/incoming/__tests__/safety-rules.test.js.map +1 -0
- package/dist/services/incoming/__tests__/subscription-multiplexer.test.d.ts +2 -0
- package/dist/services/incoming/__tests__/subscription-multiplexer.test.d.ts.map +1 -0
- package/dist/services/incoming/__tests__/subscription-multiplexer.test.js +267 -0
- package/dist/services/incoming/__tests__/subscription-multiplexer.test.js.map +1 -0
- package/dist/services/incoming/incoming-tx-monitor-service.d.ts +98 -0
- package/dist/services/incoming/incoming-tx-monitor-service.d.ts.map +1 -0
- package/dist/services/incoming/incoming-tx-monitor-service.js +357 -0
- package/dist/services/incoming/incoming-tx-monitor-service.js.map +1 -0
- package/dist/services/incoming/incoming-tx-queue.d.ts +52 -0
- package/dist/services/incoming/incoming-tx-queue.d.ts.map +1 -0
- package/dist/services/incoming/incoming-tx-queue.js +109 -0
- package/dist/services/incoming/incoming-tx-queue.js.map +1 -0
- package/dist/services/incoming/incoming-tx-workers.d.ts +89 -0
- package/dist/services/incoming/incoming-tx-workers.d.ts.map +1 -0
- package/dist/services/incoming/incoming-tx-workers.js +176 -0
- package/dist/services/incoming/incoming-tx-workers.js.map +1 -0
- package/dist/services/incoming/index.d.ts +14 -0
- package/dist/services/incoming/index.d.ts.map +1 -0
- package/dist/services/incoming/index.js +11 -0
- package/dist/services/incoming/index.js.map +1 -0
- package/dist/services/incoming/safety-rules.d.ts +70 -0
- package/dist/services/incoming/safety-rules.d.ts.map +1 -0
- package/dist/services/incoming/safety-rules.js +68 -0
- package/dist/services/incoming/safety-rules.js.map +1 -0
- package/dist/services/incoming/subscription-multiplexer.d.ts +87 -0
- package/dist/services/incoming/subscription-multiplexer.d.ts.map +1 -0
- package/dist/services/incoming/subscription-multiplexer.js +169 -0
- package/dist/services/incoming/subscription-multiplexer.js.map +1 -0
- package/dist/services/signing-sdk/approval-channel-router.d.ts +1 -1
- package/dist/services/signing-sdk/approval-channel-router.d.ts.map +1 -1
- package/dist/services/signing-sdk/approval-channel-router.js +2 -3
- package/dist/services/signing-sdk/approval-channel-router.js.map +1 -1
- package/dist/services/signing-sdk/channels/wallet-notification-channel.js +1 -1
- package/dist/services/signing-sdk/channels/wallet-notification-channel.js.map +1 -1
- package/dist/services/wc-session-service.d.ts +0 -2
- package/dist/services/wc-session-service.d.ts.map +1 -1
- package/dist/services/wc-session-service.js +2 -23
- package/dist/services/wc-session-service.js.map +1 -1
- package/dist/services/x402/x402-domain-policy.d.ts +6 -1
- package/dist/services/x402/x402-domain-policy.d.ts.map +1 -1
- package/dist/services/x402/x402-domain-policy.js +6 -2
- package/dist/services/x402/x402-domain-policy.js.map +1 -1
- package/package.json +4 -4
- package/public/admin/assets/index-D06O_cSo.js +1 -0
- package/public/admin/index.html +1 -1
- 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"}
|