@wopr-network/platform-core 1.66.1 → 1.67.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 (136) hide show
  1. package/dist/billing/crypto/btc/checkout.d.ts +4 -0
  2. package/dist/billing/crypto/btc/checkout.js +1 -2
  3. package/dist/billing/crypto/btc/index.d.ts +0 -4
  4. package/dist/billing/crypto/btc/index.js +0 -2
  5. package/dist/billing/crypto/evm/__tests__/checkout.test.js +8 -11
  6. package/dist/billing/crypto/evm/__tests__/eth-checkout.test.js +15 -1
  7. package/dist/billing/crypto/evm/checkout.d.ts +2 -0
  8. package/dist/billing/crypto/evm/checkout.js +1 -2
  9. package/dist/billing/crypto/evm/eth-checkout.d.ts +13 -2
  10. package/dist/billing/crypto/evm/eth-checkout.js +2 -4
  11. package/dist/billing/crypto/evm/eth-settler.d.ts +1 -1
  12. package/dist/billing/crypto/evm/index.d.ts +2 -8
  13. package/dist/billing/crypto/evm/index.js +0 -3
  14. package/dist/billing/crypto/evm/types.d.ts +16 -0
  15. package/dist/billing/crypto/index.d.ts +1 -6
  16. package/dist/billing/crypto/index.js +2 -3
  17. package/dist/billing/crypto/types.d.ts +0 -43
  18. package/dist/billing/crypto/types.js +1 -24
  19. package/package.json +1 -5
  20. package/src/billing/crypto/btc/checkout.ts +3 -2
  21. package/src/billing/crypto/btc/index.ts +0 -4
  22. package/src/billing/crypto/evm/__tests__/checkout.test.ts +10 -12
  23. package/src/billing/crypto/evm/__tests__/eth-checkout.test.ts +17 -1
  24. package/src/billing/crypto/evm/__tests__/eth-settler.test.ts +1 -1
  25. package/src/billing/crypto/evm/checkout.ts +3 -2
  26. package/src/billing/crypto/evm/eth-checkout.ts +15 -6
  27. package/src/billing/crypto/evm/eth-settler.ts +1 -1
  28. package/src/billing/crypto/evm/index.ts +8 -7
  29. package/src/billing/crypto/evm/types.ts +17 -0
  30. package/src/billing/crypto/index.ts +14 -12
  31. package/src/billing/crypto/types.ts +0 -63
  32. package/dist/billing/crypto/__tests__/address-gen.test.d.ts +0 -1
  33. package/dist/billing/crypto/__tests__/address-gen.test.js +0 -219
  34. package/dist/billing/crypto/__tests__/key-server.test.d.ts +0 -1
  35. package/dist/billing/crypto/__tests__/key-server.test.js +0 -363
  36. package/dist/billing/crypto/__tests__/watcher-service.test.d.ts +0 -1
  37. package/dist/billing/crypto/__tests__/watcher-service.test.js +0 -174
  38. package/dist/billing/crypto/address-gen.d.ts +0 -24
  39. package/dist/billing/crypto/address-gen.js +0 -176
  40. package/dist/billing/crypto/btc/__tests__/watcher.test.d.ts +0 -1
  41. package/dist/billing/crypto/btc/__tests__/watcher.test.js +0 -170
  42. package/dist/billing/crypto/btc/watcher.d.ts +0 -44
  43. package/dist/billing/crypto/btc/watcher.js +0 -118
  44. package/dist/billing/crypto/evm/__tests__/eth-watcher.test.d.ts +0 -1
  45. package/dist/billing/crypto/evm/__tests__/eth-watcher.test.js +0 -167
  46. package/dist/billing/crypto/evm/__tests__/watcher-confirmations.test.d.ts +0 -1
  47. package/dist/billing/crypto/evm/__tests__/watcher-confirmations.test.js +0 -159
  48. package/dist/billing/crypto/evm/__tests__/watcher.test.d.ts +0 -1
  49. package/dist/billing/crypto/evm/__tests__/watcher.test.js +0 -145
  50. package/dist/billing/crypto/evm/eth-watcher.d.ts +0 -66
  51. package/dist/billing/crypto/evm/eth-watcher.js +0 -121
  52. package/dist/billing/crypto/evm/watcher.d.ts +0 -51
  53. package/dist/billing/crypto/evm/watcher.js +0 -156
  54. package/dist/billing/crypto/key-server-entry.d.ts +0 -1
  55. package/dist/billing/crypto/key-server-entry.js +0 -122
  56. package/dist/billing/crypto/key-server.d.ts +0 -32
  57. package/dist/billing/crypto/key-server.js +0 -348
  58. package/dist/billing/crypto/oracle/__tests__/chainlink.test.d.ts +0 -1
  59. package/dist/billing/crypto/oracle/__tests__/chainlink.test.js +0 -83
  60. package/dist/billing/crypto/oracle/__tests__/coingecko.test.d.ts +0 -1
  61. package/dist/billing/crypto/oracle/__tests__/coingecko.test.js +0 -65
  62. package/dist/billing/crypto/oracle/__tests__/composite.test.d.ts +0 -1
  63. package/dist/billing/crypto/oracle/__tests__/composite.test.js +0 -48
  64. package/dist/billing/crypto/oracle/__tests__/convert.test.d.ts +0 -1
  65. package/dist/billing/crypto/oracle/__tests__/convert.test.js +0 -61
  66. package/dist/billing/crypto/oracle/__tests__/fixed.test.d.ts +0 -1
  67. package/dist/billing/crypto/oracle/__tests__/fixed.test.js +0 -20
  68. package/dist/billing/crypto/oracle/chainlink.d.ts +0 -26
  69. package/dist/billing/crypto/oracle/chainlink.js +0 -62
  70. package/dist/billing/crypto/oracle/coingecko.d.ts +0 -22
  71. package/dist/billing/crypto/oracle/coingecko.js +0 -71
  72. package/dist/billing/crypto/oracle/composite.d.ts +0 -14
  73. package/dist/billing/crypto/oracle/composite.js +0 -34
  74. package/dist/billing/crypto/oracle/convert.d.ts +0 -30
  75. package/dist/billing/crypto/oracle/convert.js +0 -51
  76. package/dist/billing/crypto/oracle/fixed.d.ts +0 -10
  77. package/dist/billing/crypto/oracle/fixed.js +0 -22
  78. package/dist/billing/crypto/oracle/index.d.ts +0 -9
  79. package/dist/billing/crypto/oracle/index.js +0 -6
  80. package/dist/billing/crypto/oracle/types.d.ts +0 -22
  81. package/dist/billing/crypto/oracle/types.js +0 -7
  82. package/dist/billing/crypto/plugin/__tests__/integration.test.d.ts +0 -1
  83. package/dist/billing/crypto/plugin/__tests__/integration.test.js +0 -58
  84. package/dist/billing/crypto/plugin/__tests__/interfaces.test.d.ts +0 -1
  85. package/dist/billing/crypto/plugin/__tests__/interfaces.test.js +0 -46
  86. package/dist/billing/crypto/plugin/__tests__/registry.test.d.ts +0 -1
  87. package/dist/billing/crypto/plugin/__tests__/registry.test.js +0 -49
  88. package/dist/billing/crypto/plugin/index.d.ts +0 -2
  89. package/dist/billing/crypto/plugin/index.js +0 -1
  90. package/dist/billing/crypto/plugin/interfaces.d.ts +0 -97
  91. package/dist/billing/crypto/plugin/interfaces.js +0 -2
  92. package/dist/billing/crypto/plugin/registry.d.ts +0 -8
  93. package/dist/billing/crypto/plugin/registry.js +0 -21
  94. package/dist/billing/crypto/plugin-watcher-service.d.ts +0 -32
  95. package/dist/billing/crypto/plugin-watcher-service.js +0 -113
  96. package/dist/billing/crypto/tron/__tests__/address-convert.test.d.ts +0 -1
  97. package/dist/billing/crypto/tron/__tests__/address-convert.test.js +0 -55
  98. package/dist/billing/crypto/tron/address-convert.d.ts +0 -14
  99. package/dist/billing/crypto/tron/address-convert.js +0 -93
  100. package/dist/billing/crypto/watcher-service.d.ts +0 -55
  101. package/dist/billing/crypto/watcher-service.js +0 -438
  102. package/src/billing/crypto/__tests__/address-gen.test.ts +0 -264
  103. package/src/billing/crypto/__tests__/key-server.test.ts +0 -395
  104. package/src/billing/crypto/__tests__/watcher-service.test.ts +0 -242
  105. package/src/billing/crypto/address-gen.ts +0 -185
  106. package/src/billing/crypto/btc/__tests__/watcher.test.ts +0 -201
  107. package/src/billing/crypto/btc/watcher.ts +0 -161
  108. package/src/billing/crypto/evm/__tests__/eth-watcher.test.ts +0 -190
  109. package/src/billing/crypto/evm/__tests__/watcher-confirmations.test.ts +0 -191
  110. package/src/billing/crypto/evm/__tests__/watcher.test.ts +0 -167
  111. package/src/billing/crypto/evm/eth-watcher.ts +0 -182
  112. package/src/billing/crypto/evm/watcher.ts +0 -204
  113. package/src/billing/crypto/key-server-entry.ts +0 -144
  114. package/src/billing/crypto/key-server.ts +0 -444
  115. package/src/billing/crypto/oracle/__tests__/chainlink.test.ts +0 -107
  116. package/src/billing/crypto/oracle/__tests__/coingecko.test.ts +0 -75
  117. package/src/billing/crypto/oracle/__tests__/composite.test.ts +0 -61
  118. package/src/billing/crypto/oracle/__tests__/convert.test.ts +0 -74
  119. package/src/billing/crypto/oracle/__tests__/fixed.test.ts +0 -23
  120. package/src/billing/crypto/oracle/chainlink.ts +0 -86
  121. package/src/billing/crypto/oracle/coingecko.ts +0 -96
  122. package/src/billing/crypto/oracle/composite.ts +0 -35
  123. package/src/billing/crypto/oracle/convert.ts +0 -53
  124. package/src/billing/crypto/oracle/fixed.ts +0 -25
  125. package/src/billing/crypto/oracle/index.ts +0 -9
  126. package/src/billing/crypto/oracle/types.ts +0 -28
  127. package/src/billing/crypto/plugin/__tests__/integration.test.ts +0 -64
  128. package/src/billing/crypto/plugin/__tests__/interfaces.test.ts +0 -51
  129. package/src/billing/crypto/plugin/__tests__/registry.test.ts +0 -58
  130. package/src/billing/crypto/plugin/index.ts +0 -17
  131. package/src/billing/crypto/plugin/interfaces.ts +0 -106
  132. package/src/billing/crypto/plugin/registry.ts +0 -26
  133. package/src/billing/crypto/plugin-watcher-service.ts +0 -148
  134. package/src/billing/crypto/tron/__tests__/address-convert.test.ts +0 -67
  135. package/src/billing/crypto/tron/address-convert.ts +0 -89
  136. package/src/billing/crypto/watcher-service.ts +0 -549
@@ -1,182 +0,0 @@
1
- import type { IWatcherCursorStore } from "../cursor-store.js";
2
- import { nativeToCents } from "../oracle/convert.js";
3
- import type { IPriceOracle } from "../oracle/types.js";
4
- import type { EvmChain } from "./types.js";
5
-
6
- type RpcCall = (method: string, params: unknown[]) => Promise<unknown>;
7
-
8
- /** Event emitted on each confirmation increment for a native ETH deposit. */
9
- export interface EthPaymentEvent {
10
- readonly chain: EvmChain;
11
- readonly from: string;
12
- readonly to: string;
13
- /** Raw value in wei (BigInt as string for serialization). */
14
- readonly valueWei: string;
15
- /** USD cents equivalent at detection time (integer). */
16
- readonly amountUsdCents: number;
17
- readonly txHash: string;
18
- readonly blockNumber: number;
19
- /** Current confirmation count (latest block - tx block). */
20
- readonly confirmations: number;
21
- /** Required confirmations for this chain. */
22
- readonly confirmationsRequired: number;
23
- }
24
-
25
- export interface EthWatcherOpts {
26
- chain: EvmChain;
27
- rpcCall: RpcCall;
28
- oracle: IPriceOracle;
29
- fromBlock: number;
30
- onPayment: (event: EthPaymentEvent) => void | Promise<void>;
31
- watchedAddresses?: string[];
32
- cursorStore?: IWatcherCursorStore;
33
- /** Required confirmations (from DB). */
34
- confirmations: number;
35
- }
36
-
37
- interface RpcTransaction {
38
- hash: string;
39
- from: string;
40
- to: string | null;
41
- value: string;
42
- blockNumber: string;
43
- }
44
-
45
- /**
46
- * Native ETH transfer watcher.
47
- *
48
- * Unlike the ERC-20 EvmWatcher which uses eth_getLogs for Transfer events,
49
- * this scans blocks for transactions where `to` matches a watched deposit
50
- * address and `value > 0`.
51
- *
52
- * Scans up to latest block (not just confirmed) to detect pending txs.
53
- * Emits events on each confirmation increment. Only advances cursor
54
- * past fully-confirmed blocks.
55
- */
56
- export class EthWatcher {
57
- private _cursor: number;
58
- private readonly chain: EvmChain;
59
- private readonly rpc: RpcCall;
60
- private readonly oracle: IPriceOracle;
61
- private readonly onPayment: EthWatcherOpts["onPayment"];
62
- private readonly confirmations: number;
63
- private readonly cursorStore?: IWatcherCursorStore;
64
- private readonly watcherId: string;
65
- private _watchedAddresses: Set<string>;
66
-
67
- constructor(opts: EthWatcherOpts) {
68
- this.chain = opts.chain;
69
- this.rpc = opts.rpcCall;
70
- this.oracle = opts.oracle;
71
- this._cursor = opts.fromBlock;
72
- this.onPayment = opts.onPayment;
73
- this.confirmations = opts.confirmations;
74
- this.cursorStore = opts.cursorStore;
75
- this.watcherId = `eth:${opts.chain}`;
76
- this._watchedAddresses = new Set((opts.watchedAddresses ?? []).map((a) => a.toLowerCase()));
77
- }
78
-
79
- /** Load cursor from DB. Call once at startup before first poll. */
80
- async init(): Promise<void> {
81
- if (!this.cursorStore) return;
82
- const saved = await this.cursorStore.get(this.watcherId);
83
- if (saved !== null) this._cursor = saved;
84
- }
85
-
86
- setWatchedAddresses(addresses: string[]): void {
87
- this._watchedAddresses = new Set(addresses.map((a) => a.toLowerCase()));
88
- }
89
-
90
- get cursor(): number {
91
- return this._cursor;
92
- }
93
-
94
- /**
95
- * Poll for native ETH transfers to watched addresses, including unconfirmed blocks.
96
- *
97
- * Scans from cursor to latest block. Emits events with current confirmation count.
98
- * Re-emits on each confirmation increment. Only advances cursor past fully-confirmed blocks.
99
- */
100
- async poll(): Promise<void> {
101
- if (this._watchedAddresses.size === 0) return;
102
-
103
- const latestHex = (await this.rpc("eth_blockNumber", [])) as string;
104
- const latest = Number.parseInt(latestHex, 16);
105
- const confirmed = latest - this.confirmations;
106
-
107
- if (latest < this._cursor) return;
108
-
109
- const { priceMicros } = await this.oracle.getPrice("ETH");
110
-
111
- // Scan up to latest (not just confirmed) to detect pending txs.
112
- // Fetch blocks in batches to avoid bursting RPC rate limits on fast chains (e.g. Tron 3s blocks).
113
- const BATCH_SIZE = 5;
114
- for (let batchStart = this._cursor; batchStart <= latest; batchStart += BATCH_SIZE) {
115
- const batchEnd = Math.min(batchStart + BATCH_SIZE - 1, latest);
116
- const blockNums = Array.from({ length: batchEnd - batchStart + 1 }, (_, i) => batchStart + i);
117
-
118
- const blocks = await Promise.all(
119
- blockNums.map((bn) =>
120
- this.rpc("eth_getBlockByNumber", [`0x${bn.toString(16)}`, true]).then(
121
- (b) => ({ blockNum: bn, block: b as { transactions: RpcTransaction[] } | null, error: null }),
122
- (err: unknown) => ({ blockNum: bn, block: null, error: err }),
123
- ),
124
- ),
125
- );
126
-
127
- // Stop processing at the first failed block so the cursor doesn't advance past it.
128
- const firstFailIdx = blocks.findIndex((b) => b.error !== null || !b.block);
129
- const safeBlocks = firstFailIdx === -1 ? blocks : blocks.slice(0, firstFailIdx);
130
- for (const { blockNum, block } of safeBlocks) {
131
- if (!block) break;
132
-
133
- const confs = latest - blockNum;
134
-
135
- for (const tx of block.transactions) {
136
- if (!tx.to) continue;
137
- const to = tx.to.toLowerCase();
138
- if (!this._watchedAddresses.has(to)) continue;
139
-
140
- const valueWei = BigInt(tx.value);
141
- if (valueWei === 0n) continue;
142
-
143
- // Skip if we already emitted at this confirmation count
144
- if (this.cursorStore) {
145
- const lastConf = await this.cursorStore.getConfirmationCount(this.watcherId, tx.hash);
146
- if (lastConf !== null && confs <= lastConf) continue;
147
- }
148
-
149
- const amountUsdCents = nativeToCents(valueWei, priceMicros, 18);
150
-
151
- const event: EthPaymentEvent = {
152
- chain: this.chain,
153
- from: tx.from.toLowerCase(),
154
- to,
155
- valueWei: valueWei.toString(),
156
- amountUsdCents,
157
- txHash: tx.hash,
158
- blockNumber: blockNum,
159
- confirmations: confs,
160
- confirmationsRequired: this.confirmations,
161
- };
162
-
163
- await this.onPayment(event);
164
-
165
- if (this.cursorStore) {
166
- await this.cursorStore.saveConfirmationCount(this.watcherId, tx.hash, confs);
167
- }
168
- }
169
-
170
- // Only advance cursor past fully-confirmed blocks
171
- if (blockNum <= confirmed) {
172
- this._cursor = blockNum + 1;
173
- if (this.cursorStore) {
174
- await this.cursorStore.save(this.watcherId, this._cursor);
175
- }
176
- }
177
- }
178
-
179
- if (firstFailIdx !== -1) break;
180
- }
181
- }
182
- }
@@ -1,204 +0,0 @@
1
- import type { IWatcherCursorStore } from "../cursor-store.js";
2
- import { centsFromTokenAmount } from "./config.js";
3
- import type { EvmChain, EvmPaymentEvent, StablecoinToken } from "./types.js";
4
-
5
- const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
6
-
7
- type RpcCall = (method: string, params: unknown[]) => Promise<unknown>;
8
-
9
- export interface EvmWatcherOpts {
10
- chain: EvmChain;
11
- token: StablecoinToken;
12
- rpcCall: RpcCall;
13
- fromBlock: number;
14
- onPayment: (event: EvmPaymentEvent) => void | Promise<void>;
15
- /** Active deposit addresses to watch. Filters eth_getLogs by topic[2] (to address). */
16
- watchedAddresses?: string[];
17
- cursorStore?: IWatcherCursorStore;
18
- /** Contract address for the ERC20 token (from DB). */
19
- contractAddress: string;
20
- /** Token decimals (from DB). */
21
- decimals: number;
22
- /** Required confirmations (from DB). */
23
- confirmations: number;
24
- }
25
-
26
- interface RpcLog {
27
- address: string;
28
- topics: string[];
29
- data: string;
30
- blockNumber: string;
31
- transactionHash: string;
32
- logIndex: string;
33
- }
34
-
35
- export class EvmWatcher {
36
- private _cursor: number;
37
- private readonly chain: EvmChain;
38
- private readonly token: StablecoinToken;
39
- private readonly rpc: RpcCall;
40
- private readonly onPayment: EvmWatcherOpts["onPayment"];
41
- private readonly confirmations: number;
42
- private readonly contractAddress: string;
43
- private readonly decimals: number;
44
- private readonly cursorStore?: IWatcherCursorStore;
45
- private readonly watcherId: string;
46
- private _watchedAddresses: string[];
47
-
48
- constructor(opts: EvmWatcherOpts) {
49
- this.chain = opts.chain;
50
- this.token = opts.token;
51
- this.rpc = opts.rpcCall;
52
- this._cursor = opts.fromBlock;
53
- this.onPayment = opts.onPayment;
54
- this.cursorStore = opts.cursorStore;
55
- this.watcherId = `evm:${opts.chain}:${opts.token}`;
56
- this._watchedAddresses = (opts.watchedAddresses ?? []).map((a) => a.toLowerCase());
57
-
58
- this.confirmations = opts.confirmations;
59
- this.contractAddress = opts.contractAddress.toLowerCase();
60
- this.decimals = opts.decimals;
61
- }
62
-
63
- /** Load cursor from DB. Call once at startup before first poll. */
64
- async init(): Promise<void> {
65
- if (!this.cursorStore) return;
66
- const saved = await this.cursorStore.get(this.watcherId);
67
- if (saved !== null) this._cursor = saved;
68
- }
69
-
70
- /** Update the set of watched deposit addresses (e.g. after a new checkout). */
71
- setWatchedAddresses(addresses: string[]): void {
72
- this._watchedAddresses = addresses.map((a) => a.toLowerCase());
73
- }
74
-
75
- get cursor(): number {
76
- return this._cursor;
77
- }
78
-
79
- /**
80
- * Poll for Transfer events, including pending (unconfirmed) blocks.
81
- *
82
- * Two-phase scan:
83
- * 1. Scan cursor..latest for new/updated txs, emit with current confirmation count
84
- * 2. Re-check pending txs automatically since cursor doesn't advance past unconfirmed blocks
85
- *
86
- * Cursor only advances past fully-confirmed blocks.
87
- */
88
- async poll(): Promise<void> {
89
- if (this._watchedAddresses.length === 0) return; // nothing to watch
90
-
91
- const latestHex = (await this.rpc("eth_blockNumber", [])) as string;
92
- const latest = Number.parseInt(latestHex, 16);
93
- const confirmed = latest - this.confirmations;
94
-
95
- if (latest < this._cursor) return;
96
-
97
- // Filter by topic[2] (to address) when watched addresses are set.
98
- const toFilter =
99
- this._watchedAddresses.length > 0
100
- ? this._watchedAddresses.map((a) => `0x000000000000000000000000${a.slice(2)}`)
101
- : null;
102
-
103
- // Scan from cursor to latest (not just confirmed) to detect pending txs
104
- const logs = (await this.rpc("eth_getLogs", [
105
- {
106
- address: this.contractAddress,
107
- topics: [TRANSFER_TOPIC, null, toFilter],
108
- fromBlock: `0x${this._cursor.toString(16)}`,
109
- toBlock: `0x${latest.toString(16)}`,
110
- },
111
- ])) as RpcLog[];
112
-
113
- // Group logs by block
114
- const logsByBlock = new Map<number, RpcLog[]>();
115
- for (const log of logs) {
116
- const bn = Number.parseInt(log.blockNumber, 16);
117
- const arr = logsByBlock.get(bn);
118
- if (arr) arr.push(log);
119
- else logsByBlock.set(bn, [log]);
120
- }
121
-
122
- // Process all blocks (including unconfirmed), emit with confirmation count
123
- const blockNums = [...logsByBlock.keys()].sort((a, b) => a - b);
124
- for (const blockNum of blockNums) {
125
- const confs = latest - blockNum;
126
-
127
- for (const log of logsByBlock.get(blockNum) ?? []) {
128
- const txKey = `${log.transactionHash}:${log.logIndex}`;
129
-
130
- // Skip if we already emitted at this confirmation count
131
- if (this.cursorStore) {
132
- const lastConf = await this.cursorStore.getConfirmationCount(this.watcherId, txKey);
133
- if (lastConf !== null && confs <= lastConf) continue;
134
- }
135
-
136
- const to = `0x${log.topics[2].slice(26)}`.toLowerCase();
137
- const from = `0x${log.topics[1].slice(26)}`.toLowerCase();
138
- const rawAmount = BigInt(log.data);
139
- const amountUsdCents = centsFromTokenAmount(rawAmount, this.decimals);
140
-
141
- const event: EvmPaymentEvent = {
142
- chain: this.chain,
143
- token: this.token,
144
- from,
145
- to,
146
- rawAmount: rawAmount.toString(),
147
- amountUsdCents,
148
- txHash: log.transactionHash,
149
- blockNumber: blockNum,
150
- logIndex: Number.parseInt(log.logIndex, 16),
151
- confirmations: confs,
152
- confirmationsRequired: this.confirmations,
153
- };
154
-
155
- await this.onPayment(event);
156
-
157
- // Track confirmation count
158
- if (this.cursorStore) {
159
- await this.cursorStore.saveConfirmationCount(this.watcherId, txKey, confs);
160
- }
161
- }
162
-
163
- // Only advance cursor past fully-confirmed blocks
164
- if (blockNum <= confirmed) {
165
- this._cursor = blockNum + 1;
166
- if (this.cursorStore) {
167
- await this.cursorStore.save(this.watcherId, this._cursor);
168
- }
169
- }
170
- }
171
-
172
- // Advance cursor if no logs found but confirmed blocks exist
173
- if (blockNums.length === 0 && confirmed >= this._cursor) {
174
- this._cursor = confirmed + 1;
175
- if (this.cursorStore) {
176
- await this.cursorStore.save(this.watcherId, this._cursor);
177
- }
178
- }
179
- }
180
- }
181
-
182
- /** Create an RPC caller for a given URL (plain JSON-RPC over fetch). */
183
- export function createRpcCaller(rpcUrl: string, extraHeaders?: Record<string, string>): RpcCall {
184
- let id = 0;
185
- const headers: Record<string, string> = { "Content-Type": "application/json", ...extraHeaders };
186
- return async (method: string, params: unknown[]): Promise<unknown> => {
187
- const res = await fetch(rpcUrl, {
188
- method: "POST",
189
- headers,
190
- body: JSON.stringify({ jsonrpc: "2.0", id: ++id, method, params }),
191
- });
192
- if (!res.ok) {
193
- const body = await res.text().catch(() => "");
194
- const hasApiKey = "TRON-PRO-API-KEY" in headers;
195
- console.error(
196
- `[rpc] ${method} ${res.status} auth=${hasApiKey} url=${rpcUrl.replace(/apikey=[^&]+/, "apikey=***")} body=${body.slice(0, 200)}`,
197
- );
198
- throw new Error(`RPC ${method} failed: ${res.status}`);
199
- }
200
- const data = (await res.json()) as { result?: unknown; error?: { message: string } };
201
- if (data.error) throw new Error(`RPC ${method} error: ${data.error.message}`);
202
- return data.result;
203
- };
204
- }
@@ -1,144 +0,0 @@
1
- /**
2
- * Standalone entry point for the crypto key server.
3
- *
4
- * Deploys on the chain server (pay.wopr.bot:3100).
5
- * Boots: postgres → migrations → key server routes → watchers → serve.
6
- *
7
- * Usage: node dist/billing/crypto/key-server-entry.js
8
- */
9
- /* biome-ignore-all lint/suspicious/noConsole: standalone entry point */
10
- import { serve } from "@hono/node-server";
11
- import {
12
- bitcoinPlugin,
13
- dogecoinPlugin,
14
- evmPlugin,
15
- litecoinPlugin,
16
- solanaPlugin,
17
- tronPlugin,
18
- } from "@wopr-network/crypto-plugins";
19
- import { drizzle } from "drizzle-orm/node-postgres";
20
- import { migrate } from "drizzle-orm/node-postgres/migrator";
21
- import pg from "pg";
22
- import * as schema from "../../db/schema/index.js";
23
- import { DrizzleCryptoChargeRepository } from "./charge-store.js";
24
- import { DrizzleWatcherCursorStore } from "./cursor-store.js";
25
- import { createRpcCaller } from "./evm/watcher.js";
26
- import { createKeyServerApp } from "./key-server.js";
27
- import { ChainlinkOracle } from "./oracle/chainlink.js";
28
- import { CoinGeckoOracle } from "./oracle/coingecko.js";
29
- import { CompositeOracle } from "./oracle/composite.js";
30
- import { FixedPriceOracle } from "./oracle/fixed.js";
31
- import { DrizzlePaymentMethodStore } from "./payment-method-store.js";
32
- import { PluginRegistry } from "./plugin/registry.js";
33
- import { startPluginWatchers } from "./plugin-watcher-service.js";
34
- import { startWatchers } from "./watcher-service.js";
35
-
36
- const PORT = Number(process.env.PORT ?? "3100");
37
- const DATABASE_URL = process.env.DATABASE_URL;
38
- const SERVICE_KEY = process.env.SERVICE_KEY;
39
- const ADMIN_TOKEN = process.env.ADMIN_TOKEN;
40
- const BITCOIND_USER = process.env.BITCOIND_USER ?? "btcpay";
41
- const BITCOIND_PASSWORD = process.env.BITCOIND_PASSWORD ?? "";
42
- const BASE_RPC_URL = process.env.BASE_RPC_URL ?? "https://mainnet.base.org";
43
-
44
- if (!DATABASE_URL) {
45
- console.error("DATABASE_URL is required");
46
- process.exit(1);
47
- }
48
-
49
- async function main(): Promise<void> {
50
- const pool = new pg.Pool({ connectionString: DATABASE_URL });
51
-
52
- // Run migrations FIRST, before creating schema-typed db
53
- console.log("[crypto-key-server] Running migrations...");
54
- await migrate(drizzle(pool), { migrationsFolder: "./drizzle/migrations" });
55
-
56
- // Now create the schema-typed db (columns guaranteed to exist)
57
- console.log("[crypto-key-server] Connecting...");
58
- const db = drizzle(pool, { schema }) as unknown as import("../../db/index.js").DrizzleDb;
59
-
60
- const chargeStore = new DrizzleCryptoChargeRepository(db);
61
- const methodStore = new DrizzlePaymentMethodStore(db);
62
-
63
- // Composite oracle: Chainlink on-chain (BTC, ETH on Base) + CoinGecko fallback (DOGE, LTC, etc.)
64
- // Every volatile asset needs reliable USD pricing — the ledger credits nanodollars.
65
- const chainlink = BASE_RPC_URL
66
- ? new ChainlinkOracle({ rpcCall: createRpcCaller(BASE_RPC_URL) })
67
- : new FixedPriceOracle();
68
- // Build token→CoinGecko ID map from DB (zero-deploy chain additions)
69
- const allMethods = await methodStore.listAll();
70
- const dbTokenIds: Record<string, string> = {};
71
- for (const m of allMethods) {
72
- if (m.oracleAssetId) dbTokenIds[m.token] = m.oracleAssetId;
73
- }
74
- const coingecko = new CoinGeckoOracle({ tokenIds: dbTokenIds });
75
- const oracle = new CompositeOracle(chainlink, coingecko);
76
-
77
- // Build plugin registry — one plugin per chain family
78
- const registry = new PluginRegistry();
79
- registry.register(bitcoinPlugin);
80
- registry.register(litecoinPlugin);
81
- registry.register(dogecoinPlugin);
82
- registry.register(evmPlugin);
83
- registry.register(tronPlugin);
84
- registry.register(solanaPlugin);
85
- console.log(
86
- `[crypto-key-server] Registered ${registry.list().length} chain plugins:`,
87
- registry.list().map((p) => p.pluginId),
88
- );
89
-
90
- const app = createKeyServerApp({
91
- db,
92
- chargeStore,
93
- methodStore,
94
- oracle,
95
- serviceKey: SERVICE_KEY,
96
- adminToken: ADMIN_TOKEN,
97
- registry,
98
- });
99
-
100
- // Boot plugin-driven watchers — polls for payments, sends webhooks.
101
- // Falls back to legacy startWatchers() if USE_LEGACY_WATCHERS=1 is set.
102
- const cursorStore = new DrizzleWatcherCursorStore(db);
103
- const useLegacy = process.env.USE_LEGACY_WATCHERS === "1";
104
- const stopWatchers = useLegacy
105
- ? await startWatchers({
106
- db,
107
- chargeStore,
108
- methodStore,
109
- cursorStore,
110
- oracle,
111
- bitcoindUser: BITCOIND_USER,
112
- bitcoindPassword: BITCOIND_PASSWORD,
113
- serviceKey: SERVICE_KEY,
114
- log: (msg, meta) => console.log(`[watcher] ${msg}`, meta ?? ""),
115
- })
116
- : await startPluginWatchers({
117
- db,
118
- chargeStore,
119
- methodStore,
120
- cursorStore,
121
- oracle,
122
- registry,
123
- log: (msg, meta) => console.log(`[watcher] ${msg}`, meta ?? ""),
124
- });
125
-
126
- const server = serve({ fetch: app.fetch, port: PORT });
127
- console.log(`[crypto-key-server] Listening on :${PORT}`);
128
-
129
- // Graceful shutdown — stop accepting requests, drain watchers, close pool
130
- const shutdown = async () => {
131
- console.log("[crypto-key-server] Shutting down...");
132
- stopWatchers();
133
- server.close();
134
- await pool.end();
135
- process.exit(0);
136
- };
137
- process.on("SIGTERM", shutdown);
138
- process.on("SIGINT", shutdown);
139
- }
140
-
141
- main().catch((err) => {
142
- console.error("[crypto-key-server] Fatal:", err);
143
- process.exit(1);
144
- });