@wopr-network/platform-core 1.67.0 → 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 -742
  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 -472
  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 -823
  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 -617
  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,64 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import type { IChainPlugin, PaymentEvent, WatcherOpts } from "../interfaces.js";
3
- import { PluginRegistry } from "../registry.js";
4
-
5
- describe("plugin integration — registry → watcher → events", () => {
6
- it("full lifecycle: register → create watcher → poll → events", async () => {
7
- const mockEvent: PaymentEvent = {
8
- chain: "test",
9
- token: "TEST",
10
- from: "0xsender",
11
- to: "0xreceiver",
12
- rawAmount: "1000",
13
- amountUsdCents: 100,
14
- txHash: "0xhash",
15
- blockNumber: 42,
16
- confirmations: 6,
17
- confirmationsRequired: 6,
18
- };
19
-
20
- const plugin: IChainPlugin = {
21
- pluginId: "test",
22
- supportedCurve: "secp256k1",
23
- encoders: {},
24
- createWatcher: (_opts: WatcherOpts) => ({
25
- init: async () => {},
26
- poll: async () => [mockEvent],
27
- setWatchedAddresses: () => {},
28
- getCursor: () => 42,
29
- stop: () => {},
30
- }),
31
- createSweeper: () => ({ scan: async () => [], sweep: async () => [] }),
32
- version: 1,
33
- };
34
-
35
- const registry = new PluginRegistry();
36
- registry.register(plugin);
37
-
38
- const resolved = registry.getOrThrow("test");
39
- const watcher = resolved.createWatcher({
40
- rpcUrl: "http://localhost:8545",
41
- rpcHeaders: {},
42
- oracle: {
43
- getPrice: async () => ({ priceMicros: 3500_000000 }),
44
- },
45
- cursorStore: {
46
- get: async () => null,
47
- save: async () => {},
48
- getConfirmationCount: async () => null,
49
- saveConfirmationCount: async () => {},
50
- },
51
- token: "TEST",
52
- chain: "test",
53
- decimals: 18,
54
- confirmations: 6,
55
- });
56
-
57
- await watcher.init();
58
- const events = await watcher.poll();
59
- expect(events).toHaveLength(1);
60
- expect(events[0].txHash).toBe("0xhash");
61
- expect(watcher.getCursor()).toBe(42);
62
- watcher.stop();
63
- });
64
- });
@@ -1,51 +0,0 @@
1
- // src/billing/crypto/plugin/__tests__/interfaces.test.ts
2
- import { describe, expect, it } from "vitest";
3
- import type { EncodingParams, IAddressEncoder, IChainWatcher, ICurveDeriver, PaymentEvent } from "../interfaces.js";
4
-
5
- describe("plugin interfaces — type contracts", () => {
6
- it("PaymentEvent has required fields", () => {
7
- const event: PaymentEvent = {
8
- chain: "ethereum",
9
- token: "ETH",
10
- from: "0xabc",
11
- to: "0xdef",
12
- rawAmount: "1000000000000000000",
13
- amountUsdCents: 350000,
14
- txHash: "0x123",
15
- blockNumber: 100,
16
- confirmations: 6,
17
- confirmationsRequired: 6,
18
- };
19
- expect(event.chain).toBe("ethereum");
20
- expect(event.amountUsdCents).toBe(350000);
21
- });
22
-
23
- it("ICurveDeriver contract is satisfiable", () => {
24
- const deriver: ICurveDeriver = {
25
- derivePublicKey: (_chain: number, _index: number) => new Uint8Array(33),
26
- getCurve: () => "secp256k1",
27
- };
28
- expect(deriver.getCurve()).toBe("secp256k1");
29
- expect(deriver.derivePublicKey(0, 0)).toBeInstanceOf(Uint8Array);
30
- });
31
-
32
- it("IAddressEncoder contract is satisfiable", () => {
33
- const encoder: IAddressEncoder = {
34
- encode: (_pk: Uint8Array, _params: EncodingParams) => "bc1qtest",
35
- encodingType: () => "bech32",
36
- };
37
- expect(encoder.encodingType()).toBe("bech32");
38
- expect(encoder.encode(new Uint8Array(33), { hrp: "bc" })).toBe("bc1qtest");
39
- });
40
-
41
- it("IChainWatcher contract is satisfiable", () => {
42
- const watcher: IChainWatcher = {
43
- init: async () => {},
44
- poll: async () => [],
45
- setWatchedAddresses: () => {},
46
- getCursor: () => 0,
47
- stop: () => {},
48
- };
49
- expect(watcher.getCursor()).toBe(0);
50
- });
51
- });
@@ -1,58 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import type { IChainPlugin } from "../interfaces.js";
3
- import { PluginRegistry } from "../registry.js";
4
-
5
- function mockPlugin(id: string, curve: "secp256k1" | "ed25519" = "secp256k1"): IChainPlugin {
6
- return {
7
- pluginId: id,
8
- supportedCurve: curve,
9
- encoders: {},
10
- createWatcher: () => ({
11
- init: async () => {},
12
- poll: async () => [],
13
- setWatchedAddresses: () => {},
14
- getCursor: () => 0,
15
- stop: () => {},
16
- }),
17
- createSweeper: () => ({ scan: async () => [], sweep: async () => [] }),
18
- version: 1,
19
- };
20
- }
21
-
22
- describe("PluginRegistry", () => {
23
- it("registers and retrieves a plugin", () => {
24
- const reg = new PluginRegistry();
25
- reg.register(mockPlugin("evm"));
26
- expect(reg.get("evm")).toBeDefined();
27
- expect(reg.get("evm")?.pluginId).toBe("evm");
28
- });
29
-
30
- it("throws on duplicate registration", () => {
31
- const reg = new PluginRegistry();
32
- reg.register(mockPlugin("evm"));
33
- expect(() => reg.register(mockPlugin("evm"))).toThrow("already registered");
34
- });
35
-
36
- it("returns undefined for unknown plugin", () => {
37
- const reg = new PluginRegistry();
38
- expect(reg.get("unknown")).toBeUndefined();
39
- });
40
-
41
- it("lists all registered plugins", () => {
42
- const reg = new PluginRegistry();
43
- reg.register(mockPlugin("evm"));
44
- reg.register(mockPlugin("solana", "ed25519"));
45
- expect(reg.list()).toHaveLength(2);
46
- expect(
47
- reg
48
- .list()
49
- .map((p) => p.pluginId)
50
- .sort(),
51
- ).toEqual(["evm", "solana"]);
52
- });
53
-
54
- it("getOrThrow throws for unknown plugin", () => {
55
- const reg = new PluginRegistry();
56
- expect(() => reg.getOrThrow("nope")).toThrow("not registered");
57
- });
58
- });
@@ -1,17 +0,0 @@
1
- export type {
2
- DepositInfo,
3
- EncodingParams,
4
- IAddressEncoder,
5
- IChainPlugin,
6
- IChainWatcher,
7
- ICurveDeriver,
8
- IPriceOracle,
9
- ISweepStrategy,
10
- IWatcherCursorStore,
11
- KeyPair,
12
- PaymentEvent,
13
- SweeperOpts,
14
- SweepResult,
15
- WatcherOpts,
16
- } from "./interfaces.js";
17
- export { PluginRegistry } from "./registry.js";
@@ -1,106 +0,0 @@
1
- // src/billing/crypto/plugin/interfaces.ts
2
-
3
- export interface PaymentEvent {
4
- chain: string;
5
- token: string;
6
- from: string;
7
- to: string;
8
- rawAmount: string;
9
- amountUsdCents: number;
10
- txHash: string;
11
- blockNumber: number;
12
- confirmations: number;
13
- confirmationsRequired: number;
14
- }
15
-
16
- export interface ICurveDeriver {
17
- derivePublicKey(chainIndex: number, addressIndex: number): Uint8Array;
18
- getCurve(): "secp256k1" | "ed25519";
19
- }
20
-
21
- export interface EncodingParams {
22
- hrp?: string;
23
- version?: string;
24
- [key: string]: string | undefined;
25
- }
26
-
27
- export interface IAddressEncoder {
28
- encode(publicKey: Uint8Array, params: EncodingParams): string;
29
- encodingType(): string;
30
- }
31
-
32
- export interface KeyPair {
33
- privateKey: Uint8Array;
34
- publicKey: Uint8Array;
35
- address: string;
36
- index: number;
37
- }
38
-
39
- export interface DepositInfo {
40
- index: number;
41
- address: string;
42
- nativeBalance: bigint;
43
- tokenBalances: Array<{ token: string; balance: bigint; decimals: number }>;
44
- }
45
-
46
- export interface SweepResult {
47
- index: number;
48
- address: string;
49
- token: string;
50
- amount: string;
51
- txHash: string;
52
- }
53
-
54
- export interface ISweepStrategy {
55
- scan(keys: KeyPair[], treasury: string): Promise<DepositInfo[]>;
56
- sweep(keys: KeyPair[], treasury: string, dryRun: boolean): Promise<SweepResult[]>;
57
- }
58
-
59
- export interface IPriceOracle {
60
- getPrice(token: string, feedAddress?: string): Promise<{ priceMicros: number }>;
61
- }
62
-
63
- export interface IWatcherCursorStore {
64
- get(watcherId: string): Promise<number | null>;
65
- save(watcherId: string, cursor: number): Promise<void>;
66
- getConfirmationCount(watcherId: string, txKey: string): Promise<number | null>;
67
- saveConfirmationCount(watcherId: string, txKey: string, count: number): Promise<void>;
68
- }
69
-
70
- export interface WatcherOpts {
71
- rpcUrl: string;
72
- rpcHeaders: Record<string, string>;
73
- oracle: IPriceOracle;
74
- cursorStore: IWatcherCursorStore;
75
- token: string;
76
- chain: string;
77
- contractAddress?: string;
78
- decimals: number;
79
- confirmations: number;
80
- }
81
-
82
- export interface SweeperOpts {
83
- rpcUrl: string;
84
- rpcHeaders: Record<string, string>;
85
- token: string;
86
- chain: string;
87
- contractAddress?: string;
88
- decimals: number;
89
- }
90
-
91
- export interface IChainWatcher {
92
- init(): Promise<void>;
93
- poll(): Promise<PaymentEvent[]>;
94
- setWatchedAddresses(addresses: string[]): void;
95
- getCursor(): number;
96
- stop(): void;
97
- }
98
-
99
- export interface IChainPlugin {
100
- pluginId: string;
101
- supportedCurve: "secp256k1" | "ed25519";
102
- encoders: Record<string, IAddressEncoder>;
103
- createWatcher(opts: WatcherOpts): IChainWatcher;
104
- createSweeper(opts: SweeperOpts): ISweepStrategy;
105
- version: number;
106
- }
@@ -1,26 +0,0 @@
1
- import type { IChainPlugin } from "./interfaces.js";
2
-
3
- export class PluginRegistry {
4
- private plugins = new Map<string, IChainPlugin>();
5
-
6
- register(plugin: IChainPlugin): void {
7
- if (this.plugins.has(plugin.pluginId)) {
8
- throw new Error(`Plugin "${plugin.pluginId}" is already registered`);
9
- }
10
- this.plugins.set(plugin.pluginId, plugin);
11
- }
12
-
13
- get(pluginId: string): IChainPlugin | undefined {
14
- return this.plugins.get(pluginId);
15
- }
16
-
17
- getOrThrow(pluginId: string): IChainPlugin {
18
- const plugin = this.plugins.get(pluginId);
19
- if (!plugin) throw new Error(`Plugin "${pluginId}" is not registered`);
20
- return plugin;
21
- }
22
-
23
- list(): IChainPlugin[] {
24
- return [...this.plugins.values()];
25
- }
26
- }
@@ -1,148 +0,0 @@
1
- /**
2
- * Plugin-driven watcher service — replaces the hardcoded watcher-service.ts.
3
- *
4
- * Instead of importing BtcWatcher/EvmWatcher/EthWatcher directly,
5
- * this delegates to IChainPlugin.createWatcher() from the plugin registry.
6
- * Adding a new chain = register a plugin + INSERT a payment_methods row.
7
- *
8
- * Payment flow is unchanged:
9
- * plugin.poll() -> PaymentEvent[] -> handlePayment() -> credit + webhook
10
- */
11
-
12
- import type { DrizzleDb } from "../../db/index.js";
13
- import type { ICryptoChargeRepository } from "./charge-store.js";
14
- import type { IWatcherCursorStore } from "./cursor-store.js";
15
- import type { IPriceOracle } from "./oracle/types.js";
16
- import type { IPaymentMethodStore, PaymentMethodRecord } from "./payment-method-store.js";
17
- import type { IChainPlugin, IChainWatcher } from "./plugin/interfaces.js";
18
- import type { PluginRegistry } from "./plugin/registry.js";
19
- import { handlePayment } from "./watcher-service.js";
20
-
21
- export interface PluginWatcherServiceOpts {
22
- db: DrizzleDb;
23
- chargeStore: ICryptoChargeRepository;
24
- methodStore: IPaymentMethodStore;
25
- cursorStore: IWatcherCursorStore;
26
- oracle: IPriceOracle;
27
- registry: PluginRegistry;
28
- pollIntervalMs?: number;
29
- log?: (msg: string, meta?: Record<string, unknown>) => void;
30
- }
31
-
32
- /** Map legacy watcher_type values to plugin IDs for backward compatibility. */
33
- const WATCHER_TYPE_TO_PLUGIN: Record<string, string> = {
34
- utxo: "bitcoin",
35
- evm: "evm",
36
- };
37
-
38
- function resolvePlugin(registry: PluginRegistry, method: PaymentMethodRecord): IChainPlugin | undefined {
39
- // Prefer explicit plugin_id, fall back to watcher_type mapping
40
- const id = method.pluginId ?? WATCHER_TYPE_TO_PLUGIN[method.watcherType];
41
- return id ? registry.get(id) : undefined;
42
- }
43
-
44
- /**
45
- * Boot plugin-driven watchers for all enabled payment methods.
46
- *
47
- * Returns a cleanup function that stops all poll timers and watchers.
48
- */
49
- export async function startPluginWatchers(opts: PluginWatcherServiceOpts): Promise<() => void> {
50
- const { db, chargeStore, methodStore, cursorStore, oracle, registry } = opts;
51
- const pollMs = opts.pollIntervalMs ?? 15_000;
52
- const log = opts.log ?? (() => {});
53
-
54
- const methods = await methodStore.listEnabled();
55
- const timers: ReturnType<typeof setInterval>[] = [];
56
- const watchers: IChainWatcher[] = [];
57
-
58
- for (const method of methods) {
59
- if (!method.rpcUrl) continue;
60
-
61
- const plugin = resolvePlugin(registry, method);
62
- if (!plugin) {
63
- log("No plugin found, skipping method", { id: method.id, chain: method.chain, watcherType: method.watcherType });
64
- continue;
65
- }
66
-
67
- const watcher = plugin.createWatcher({
68
- rpcUrl: method.rpcUrl,
69
- rpcHeaders: JSON.parse(method.rpcHeaders ?? "{}"),
70
- oracle,
71
- cursorStore,
72
- token: method.token,
73
- chain: method.chain,
74
- contractAddress: method.contractAddress ?? undefined,
75
- decimals: method.decimals,
76
- confirmations: method.confirmations,
77
- });
78
-
79
- try {
80
- await watcher.init();
81
- } catch (err) {
82
- log("Watcher init failed, skipping", { chain: method.chain, token: method.token, error: String(err) });
83
- continue;
84
- }
85
-
86
- // Seed watched addresses from active charges
87
- const active = await chargeStore.listActiveDepositAddresses();
88
- const addrs = active.filter((a) => a.chain === method.chain && a.token === method.token).map((a) => a.address);
89
- watcher.setWatchedAddresses(addrs);
90
-
91
- watchers.push(watcher);
92
- log(`Plugin watcher started (${method.chain}:${method.token})`, {
93
- plugin: plugin.pluginId,
94
- addresses: addrs.length,
95
- });
96
-
97
- let polling = false;
98
- timers.push(
99
- setInterval(async () => {
100
- if (polling) return;
101
- polling = true;
102
- try {
103
- // Refresh watched addresses each cycle
104
- const fresh = await chargeStore.listActiveDepositAddresses();
105
- const freshAddrs = fresh
106
- .filter((a) => a.chain === method.chain && a.token === method.token)
107
- .map((a) => a.address);
108
- watcher.setWatchedAddresses(freshAddrs);
109
-
110
- const events = await watcher.poll();
111
- for (const ev of events) {
112
- log("Plugin payment", {
113
- chain: ev.chain,
114
- token: ev.token,
115
- to: ev.to,
116
- txHash: ev.txHash,
117
- confirmations: ev.confirmations,
118
- });
119
- await handlePayment(
120
- db,
121
- chargeStore,
122
- ev.to,
123
- ev.rawAmount,
124
- {
125
- txHash: ev.txHash,
126
- confirmations: ev.confirmations,
127
- confirmationsRequired: ev.confirmationsRequired,
128
- amountReceivedCents: ev.amountUsdCents,
129
- },
130
- log,
131
- );
132
- }
133
- } catch (err) {
134
- log("Plugin poll error", { chain: method.chain, token: method.token, error: String(err) });
135
- } finally {
136
- polling = false;
137
- }
138
- }, pollMs),
139
- );
140
- }
141
-
142
- log("All plugin watchers started", { count: watchers.length, pollMs });
143
-
144
- return () => {
145
- for (const t of timers) clearInterval(t);
146
- for (const w of watchers) w.stop();
147
- };
148
- }
@@ -1,67 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { hexToTron, isTronAddress, tronToHex } from "../address-convert.js";
3
-
4
- // Known Tron address / hex pair (Tron foundation address)
5
- const TRON_ADDR = "TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW";
6
- const HEX_ADDR = "0x5a523b449890854c8fc460ab602df9f31fe4293f";
7
-
8
- describe("tronToHex", () => {
9
- it("converts T... to 0x hex", () => {
10
- const hex = tronToHex(TRON_ADDR);
11
- expect(hex).toBe(HEX_ADDR);
12
- });
13
-
14
- it("rejects non-Tron address", () => {
15
- expect(() => tronToHex("0x1234")).toThrow("Not a Tron address");
16
- });
17
-
18
- it("rejects invalid checksum", () => {
19
- // Flip last character
20
- const bad = `${TRON_ADDR.slice(0, -1)}X`;
21
- expect(() => tronToHex(bad)).toThrow();
22
- });
23
- });
24
-
25
- describe("hexToTron", () => {
26
- it("converts 0x hex to T...", () => {
27
- const tron = hexToTron(HEX_ADDR);
28
- expect(tron).toBe(TRON_ADDR);
29
- });
30
-
31
- it("handles hex without 0x prefix", () => {
32
- const tron = hexToTron(HEX_ADDR.slice(2));
33
- expect(tron).toBe(TRON_ADDR);
34
- });
35
-
36
- it("rejects wrong length", () => {
37
- expect(() => hexToTron("0x1234")).toThrow("Invalid hex address length");
38
- });
39
- });
40
-
41
- describe("roundtrip", () => {
42
- it("tronToHex → hexToTron is identity", () => {
43
- const hex = tronToHex(TRON_ADDR);
44
- const back = hexToTron(hex);
45
- expect(back).toBe(TRON_ADDR);
46
- });
47
-
48
- it("hexToTron → tronToHex is identity", () => {
49
- const tron = hexToTron(HEX_ADDR);
50
- const back = tronToHex(tron);
51
- expect(back).toBe(HEX_ADDR);
52
- });
53
- });
54
-
55
- describe("isTronAddress", () => {
56
- it("returns true for T... address", () => {
57
- expect(isTronAddress(TRON_ADDR)).toBe(true);
58
- });
59
-
60
- it("returns false for 0x address", () => {
61
- expect(isTronAddress(HEX_ADDR)).toBe(false);
62
- });
63
-
64
- it("returns false for BTC address", () => {
65
- expect(isTronAddress("bc1qtest")).toBe(false);
66
- });
67
- });
@@ -1,89 +0,0 @@
1
- /**
2
- * Tron address conversion — T... Base58Check ↔ 0x hex.
3
- *
4
- * Tron addresses are 21 bytes: 0x41 prefix + 20-byte address.
5
- * The JSON-RPC layer strips the 0x41 and returns standard 0x-prefixed hex.
6
- * We need to convert between the two at the watcher boundary.
7
- */
8
- import { sha256 } from "@noble/hashes/sha2.js";
9
-
10
- const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
11
-
12
- function base58decode(s: string): Uint8Array {
13
- // Count leading '1' characters (each represents a 0x00 byte)
14
- let leadingZeros = 0;
15
- for (const ch of s) {
16
- if (ch !== "1") break;
17
- leadingZeros++;
18
- }
19
- let num = 0n;
20
- for (const ch of s) {
21
- const idx = BASE58_ALPHABET.indexOf(ch);
22
- if (idx < 0) throw new Error(`Invalid base58 character: ${ch}`);
23
- num = num * 58n + BigInt(idx);
24
- }
25
- const hex = num.toString(16).padStart(2, "0");
26
- const dataBytes = new Uint8Array(hex.length / 2);
27
- for (let i = 0; i < dataBytes.length; i++) dataBytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
28
- // Prepend leading zero bytes
29
- const result = new Uint8Array(leadingZeros + dataBytes.length);
30
- result.set(dataBytes, leadingZeros);
31
- return result;
32
- }
33
-
34
- /**
35
- * Convert a Tron T... address to 0x hex (20 bytes, no 0x41 prefix).
36
- * For feeding addresses to the EVM watcher JSON-RPC filters.
37
- */
38
- export function tronToHex(tronAddr: string): string {
39
- if (!tronAddr.startsWith("T")) throw new Error(`Not a Tron address: ${tronAddr}`);
40
- const decoded = base58decode(tronAddr);
41
- // decoded: [0x41, ...20 bytes address..., ...4 bytes checksum]
42
- // Verify checksum
43
- const payload = decoded.slice(0, 21);
44
- const checksum = sha256(sha256(payload)).slice(0, 4);
45
- for (let i = 0; i < 4; i++) {
46
- if (decoded[21 + i] !== checksum[i]) throw new Error(`Invalid checksum for Tron address: ${tronAddr}`);
47
- }
48
- // Strip 0x41 prefix, return 20-byte hex with 0x prefix
49
- const addrBytes = payload.slice(1);
50
- return `0x${Array.from(addrBytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
51
- }
52
-
53
- /**
54
- * Convert a 0x hex address (20 bytes) back to Tron T... Base58Check.
55
- * For converting watcher event addresses back to DB format.
56
- */
57
- export function hexToTron(hexAddr: string): string {
58
- const hex = hexAddr.startsWith("0x") ? hexAddr.slice(2) : hexAddr;
59
- if (hex.length !== 40) throw new Error(`Invalid hex address length: ${hex.length}`);
60
- // Build payload: 0x41 + 20 bytes
61
- const payload = new Uint8Array(21);
62
- payload[0] = 0x41;
63
- for (let i = 0; i < 20; i++) payload[i + 1] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
64
- // Compute checksum
65
- const checksum = sha256(sha256(payload)).slice(0, 4);
66
- const full = new Uint8Array(25);
67
- full.set(payload);
68
- full.set(checksum, 21);
69
- // Base58 encode
70
- let num = 0n;
71
- for (const byte of full) num = num * 256n + BigInt(byte);
72
- let encoded = "";
73
- while (num > 0n) {
74
- encoded = BASE58_ALPHABET[Number(num % 58n)] + encoded;
75
- num = num / 58n;
76
- }
77
- for (const byte of full) {
78
- if (byte !== 0) break;
79
- encoded = `1${encoded}`;
80
- }
81
- return encoded;
82
- }
83
-
84
- /**
85
- * Check if an address is a Tron T... address.
86
- */
87
- export function isTronAddress(addr: string): boolean {
88
- return addr.startsWith("T") && addr.length >= 33 && addr.length <= 35;
89
- }