1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36import { createPublicClient, webSocket, parseAbiItem } from 'viem';const client = createPublicClient({ transport: webSocket(RPC_WS) });/** * Watches for settlement events. The hard part of chain integration is not * subscribing — it is that a block you already acted on can be reorganised out * from under you, and that your node will drop the socket at 03:00. */client.watchEvent({ address: SETTLEMENT_CONTRACT, event: parseAbiItem('event Settled(bytes32 indexed ref, address payer, uint256 amount)'), onLogs: async (logs) => { for (const log of logs) { // Never act on one confirmation. Record it, and let the confirmation // counter below decide when it is safe to treat as final. await db.settlement.upsert({ where: { txHash: log.transactionHash }, create: { ref: log.args.ref, amountMinor: log.args.amount, confirmations: 1 }, update: { confirmations: { increment: 1 }, removed: false }, }); } }, // A reorg replays logs with removed: true. Anything already released has to be // walked back, which is why release is a separate step from observation. onError: (error) => alerts.page('chain-listener', error),});// The socket will drop. Reconnect, then backfill the gap from the last block we// definitely processed — resubscribing alone loses everything in between.client.onDisconnect(async () => { const from = await db.cursor.get('settlement'); await backfill(from, await client.getBlockNumber());});