@wopr-network/platform-core 1.67.0 → 1.68.0
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/auth/better-auth.js +7 -0
- package/dist/billing/crypto/btc/checkout.d.ts +4 -0
- package/dist/billing/crypto/btc/checkout.js +1 -2
- package/dist/billing/crypto/btc/index.d.ts +0 -4
- package/dist/billing/crypto/btc/index.js +0 -2
- package/dist/billing/crypto/evm/__tests__/checkout.test.js +8 -11
- package/dist/billing/crypto/evm/__tests__/eth-checkout.test.js +15 -1
- package/dist/billing/crypto/evm/checkout.d.ts +2 -0
- package/dist/billing/crypto/evm/checkout.js +1 -2
- package/dist/billing/crypto/evm/eth-checkout.d.ts +13 -2
- package/dist/billing/crypto/evm/eth-checkout.js +2 -4
- package/dist/billing/crypto/evm/eth-settler.d.ts +1 -1
- package/dist/billing/crypto/evm/index.d.ts +2 -8
- package/dist/billing/crypto/evm/index.js +0 -3
- package/dist/billing/crypto/evm/types.d.ts +16 -0
- package/dist/billing/crypto/index.d.ts +1 -6
- package/dist/billing/crypto/index.js +2 -3
- package/dist/billing/crypto/types.d.ts +0 -43
- package/dist/billing/crypto/types.js +1 -24
- package/dist/email/client.js +16 -0
- package/package.json +4 -7
- package/src/auth/better-auth.ts +8 -0
- package/src/billing/crypto/btc/checkout.ts +3 -2
- package/src/billing/crypto/btc/index.ts +0 -4
- package/src/billing/crypto/evm/__tests__/checkout.test.ts +10 -12
- package/src/billing/crypto/evm/__tests__/eth-checkout.test.ts +17 -1
- package/src/billing/crypto/evm/__tests__/eth-settler.test.ts +1 -1
- package/src/billing/crypto/evm/checkout.ts +3 -2
- package/src/billing/crypto/evm/eth-checkout.ts +15 -6
- package/src/billing/crypto/evm/eth-settler.ts +1 -1
- package/src/billing/crypto/evm/index.ts +8 -7
- package/src/billing/crypto/evm/types.ts +17 -0
- package/src/billing/crypto/index.ts +14 -12
- package/src/billing/crypto/types.ts +0 -63
- package/src/email/client.ts +18 -0
- package/dist/billing/crypto/__tests__/address-gen.test.d.ts +0 -1
- package/dist/billing/crypto/__tests__/address-gen.test.js +0 -219
- package/dist/billing/crypto/__tests__/key-server.test.d.ts +0 -1
- package/dist/billing/crypto/__tests__/key-server.test.js +0 -742
- package/dist/billing/crypto/__tests__/watcher-service.test.d.ts +0 -1
- package/dist/billing/crypto/__tests__/watcher-service.test.js +0 -174
- package/dist/billing/crypto/address-gen.d.ts +0 -24
- package/dist/billing/crypto/address-gen.js +0 -176
- package/dist/billing/crypto/btc/__tests__/watcher.test.d.ts +0 -1
- package/dist/billing/crypto/btc/__tests__/watcher.test.js +0 -170
- package/dist/billing/crypto/btc/watcher.d.ts +0 -44
- package/dist/billing/crypto/btc/watcher.js +0 -118
- package/dist/billing/crypto/evm/__tests__/eth-watcher.test.d.ts +0 -1
- package/dist/billing/crypto/evm/__tests__/eth-watcher.test.js +0 -167
- package/dist/billing/crypto/evm/__tests__/watcher-confirmations.test.d.ts +0 -1
- package/dist/billing/crypto/evm/__tests__/watcher-confirmations.test.js +0 -159
- package/dist/billing/crypto/evm/__tests__/watcher.test.d.ts +0 -1
- package/dist/billing/crypto/evm/__tests__/watcher.test.js +0 -145
- package/dist/billing/crypto/evm/eth-watcher.d.ts +0 -66
- package/dist/billing/crypto/evm/eth-watcher.js +0 -121
- package/dist/billing/crypto/evm/watcher.d.ts +0 -51
- package/dist/billing/crypto/evm/watcher.js +0 -156
- package/dist/billing/crypto/key-server-entry.d.ts +0 -1
- package/dist/billing/crypto/key-server-entry.js +0 -122
- package/dist/billing/crypto/key-server.d.ts +0 -32
- package/dist/billing/crypto/key-server.js +0 -472
- package/dist/billing/crypto/oracle/__tests__/chainlink.test.d.ts +0 -1
- package/dist/billing/crypto/oracle/__tests__/chainlink.test.js +0 -83
- package/dist/billing/crypto/oracle/__tests__/coingecko.test.d.ts +0 -1
- package/dist/billing/crypto/oracle/__tests__/coingecko.test.js +0 -65
- package/dist/billing/crypto/oracle/__tests__/composite.test.d.ts +0 -1
- package/dist/billing/crypto/oracle/__tests__/composite.test.js +0 -48
- package/dist/billing/crypto/oracle/__tests__/convert.test.d.ts +0 -1
- package/dist/billing/crypto/oracle/__tests__/convert.test.js +0 -61
- package/dist/billing/crypto/oracle/__tests__/fixed.test.d.ts +0 -1
- package/dist/billing/crypto/oracle/__tests__/fixed.test.js +0 -20
- package/dist/billing/crypto/oracle/chainlink.d.ts +0 -26
- package/dist/billing/crypto/oracle/chainlink.js +0 -62
- package/dist/billing/crypto/oracle/coingecko.d.ts +0 -22
- package/dist/billing/crypto/oracle/coingecko.js +0 -71
- package/dist/billing/crypto/oracle/composite.d.ts +0 -14
- package/dist/billing/crypto/oracle/composite.js +0 -34
- package/dist/billing/crypto/oracle/convert.d.ts +0 -30
- package/dist/billing/crypto/oracle/convert.js +0 -51
- package/dist/billing/crypto/oracle/fixed.d.ts +0 -10
- package/dist/billing/crypto/oracle/fixed.js +0 -22
- package/dist/billing/crypto/oracle/index.d.ts +0 -9
- package/dist/billing/crypto/oracle/index.js +0 -6
- package/dist/billing/crypto/oracle/types.d.ts +0 -22
- package/dist/billing/crypto/oracle/types.js +0 -7
- package/dist/billing/crypto/plugin/__tests__/integration.test.d.ts +0 -1
- package/dist/billing/crypto/plugin/__tests__/integration.test.js +0 -58
- package/dist/billing/crypto/plugin/__tests__/interfaces.test.d.ts +0 -1
- package/dist/billing/crypto/plugin/__tests__/interfaces.test.js +0 -46
- package/dist/billing/crypto/plugin/__tests__/registry.test.d.ts +0 -1
- package/dist/billing/crypto/plugin/__tests__/registry.test.js +0 -49
- package/dist/billing/crypto/plugin/index.d.ts +0 -2
- package/dist/billing/crypto/plugin/index.js +0 -1
- package/dist/billing/crypto/plugin/interfaces.d.ts +0 -97
- package/dist/billing/crypto/plugin/interfaces.js +0 -2
- package/dist/billing/crypto/plugin/registry.d.ts +0 -8
- package/dist/billing/crypto/plugin/registry.js +0 -21
- package/dist/billing/crypto/plugin-watcher-service.d.ts +0 -32
- package/dist/billing/crypto/plugin-watcher-service.js +0 -113
- package/dist/billing/crypto/tron/__tests__/address-convert.test.d.ts +0 -1
- package/dist/billing/crypto/tron/__tests__/address-convert.test.js +0 -55
- package/dist/billing/crypto/tron/address-convert.d.ts +0 -14
- package/dist/billing/crypto/tron/address-convert.js +0 -93
- package/dist/billing/crypto/watcher-service.d.ts +0 -55
- package/dist/billing/crypto/watcher-service.js +0 -438
- package/src/billing/crypto/__tests__/address-gen.test.ts +0 -264
- package/src/billing/crypto/__tests__/key-server.test.ts +0 -823
- package/src/billing/crypto/__tests__/watcher-service.test.ts +0 -242
- package/src/billing/crypto/address-gen.ts +0 -185
- package/src/billing/crypto/btc/__tests__/watcher.test.ts +0 -201
- package/src/billing/crypto/btc/watcher.ts +0 -161
- package/src/billing/crypto/evm/__tests__/eth-watcher.test.ts +0 -190
- package/src/billing/crypto/evm/__tests__/watcher-confirmations.test.ts +0 -191
- package/src/billing/crypto/evm/__tests__/watcher.test.ts +0 -167
- package/src/billing/crypto/evm/eth-watcher.ts +0 -182
- package/src/billing/crypto/evm/watcher.ts +0 -204
- package/src/billing/crypto/key-server-entry.ts +0 -144
- package/src/billing/crypto/key-server.ts +0 -617
- package/src/billing/crypto/oracle/__tests__/chainlink.test.ts +0 -107
- package/src/billing/crypto/oracle/__tests__/coingecko.test.ts +0 -75
- package/src/billing/crypto/oracle/__tests__/composite.test.ts +0 -61
- package/src/billing/crypto/oracle/__tests__/convert.test.ts +0 -74
- package/src/billing/crypto/oracle/__tests__/fixed.test.ts +0 -23
- package/src/billing/crypto/oracle/chainlink.ts +0 -86
- package/src/billing/crypto/oracle/coingecko.ts +0 -96
- package/src/billing/crypto/oracle/composite.ts +0 -35
- package/src/billing/crypto/oracle/convert.ts +0 -53
- package/src/billing/crypto/oracle/fixed.ts +0 -25
- package/src/billing/crypto/oracle/index.ts +0 -9
- package/src/billing/crypto/oracle/types.ts +0 -28
- package/src/billing/crypto/plugin/__tests__/integration.test.ts +0 -64
- package/src/billing/crypto/plugin/__tests__/interfaces.test.ts +0 -51
- package/src/billing/crypto/plugin/__tests__/registry.test.ts +0 -58
- package/src/billing/crypto/plugin/index.ts +0 -17
- package/src/billing/crypto/plugin/interfaces.ts +0 -106
- package/src/billing/crypto/plugin/registry.ts +0 -26
- package/src/billing/crypto/plugin-watcher-service.ts +0 -148
- package/src/billing/crypto/tron/__tests__/address-convert.test.ts +0 -67
- package/src/billing/crypto/tron/address-convert.ts +0 -89
- package/src/billing/crypto/watcher-service.ts +0 -549
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { PluginRegistry } from "../registry.js";
|
|
3
|
-
function mockPlugin(id, curve = "secp256k1") {
|
|
4
|
-
return {
|
|
5
|
-
pluginId: id,
|
|
6
|
-
supportedCurve: curve,
|
|
7
|
-
encoders: {},
|
|
8
|
-
createWatcher: () => ({
|
|
9
|
-
init: async () => { },
|
|
10
|
-
poll: async () => [],
|
|
11
|
-
setWatchedAddresses: () => { },
|
|
12
|
-
getCursor: () => 0,
|
|
13
|
-
stop: () => { },
|
|
14
|
-
}),
|
|
15
|
-
createSweeper: () => ({ scan: async () => [], sweep: async () => [] }),
|
|
16
|
-
version: 1,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
describe("PluginRegistry", () => {
|
|
20
|
-
it("registers and retrieves a plugin", () => {
|
|
21
|
-
const reg = new PluginRegistry();
|
|
22
|
-
reg.register(mockPlugin("evm"));
|
|
23
|
-
expect(reg.get("evm")).toBeDefined();
|
|
24
|
-
expect(reg.get("evm")?.pluginId).toBe("evm");
|
|
25
|
-
});
|
|
26
|
-
it("throws on duplicate registration", () => {
|
|
27
|
-
const reg = new PluginRegistry();
|
|
28
|
-
reg.register(mockPlugin("evm"));
|
|
29
|
-
expect(() => reg.register(mockPlugin("evm"))).toThrow("already registered");
|
|
30
|
-
});
|
|
31
|
-
it("returns undefined for unknown plugin", () => {
|
|
32
|
-
const reg = new PluginRegistry();
|
|
33
|
-
expect(reg.get("unknown")).toBeUndefined();
|
|
34
|
-
});
|
|
35
|
-
it("lists all registered plugins", () => {
|
|
36
|
-
const reg = new PluginRegistry();
|
|
37
|
-
reg.register(mockPlugin("evm"));
|
|
38
|
-
reg.register(mockPlugin("solana", "ed25519"));
|
|
39
|
-
expect(reg.list()).toHaveLength(2);
|
|
40
|
-
expect(reg
|
|
41
|
-
.list()
|
|
42
|
-
.map((p) => p.pluginId)
|
|
43
|
-
.sort()).toEqual(["evm", "solana"]);
|
|
44
|
-
});
|
|
45
|
-
it("getOrThrow throws for unknown plugin", () => {
|
|
46
|
-
const reg = new PluginRegistry();
|
|
47
|
-
expect(() => reg.getOrThrow("nope")).toThrow("not registered");
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export type { DepositInfo, EncodingParams, IAddressEncoder, IChainPlugin, IChainWatcher, ICurveDeriver, IPriceOracle, ISweepStrategy, IWatcherCursorStore, KeyPair, PaymentEvent, SweeperOpts, SweepResult, WatcherOpts, } from "./interfaces.js";
|
|
2
|
-
export { PluginRegistry } from "./registry.js";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { PluginRegistry } from "./registry.js";
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
export interface PaymentEvent {
|
|
2
|
-
chain: string;
|
|
3
|
-
token: string;
|
|
4
|
-
from: string;
|
|
5
|
-
to: string;
|
|
6
|
-
rawAmount: string;
|
|
7
|
-
amountUsdCents: number;
|
|
8
|
-
txHash: string;
|
|
9
|
-
blockNumber: number;
|
|
10
|
-
confirmations: number;
|
|
11
|
-
confirmationsRequired: number;
|
|
12
|
-
}
|
|
13
|
-
export interface ICurveDeriver {
|
|
14
|
-
derivePublicKey(chainIndex: number, addressIndex: number): Uint8Array;
|
|
15
|
-
getCurve(): "secp256k1" | "ed25519";
|
|
16
|
-
}
|
|
17
|
-
export interface EncodingParams {
|
|
18
|
-
hrp?: string;
|
|
19
|
-
version?: string;
|
|
20
|
-
[key: string]: string | undefined;
|
|
21
|
-
}
|
|
22
|
-
export interface IAddressEncoder {
|
|
23
|
-
encode(publicKey: Uint8Array, params: EncodingParams): string;
|
|
24
|
-
encodingType(): string;
|
|
25
|
-
}
|
|
26
|
-
export interface KeyPair {
|
|
27
|
-
privateKey: Uint8Array;
|
|
28
|
-
publicKey: Uint8Array;
|
|
29
|
-
address: string;
|
|
30
|
-
index: number;
|
|
31
|
-
}
|
|
32
|
-
export interface DepositInfo {
|
|
33
|
-
index: number;
|
|
34
|
-
address: string;
|
|
35
|
-
nativeBalance: bigint;
|
|
36
|
-
tokenBalances: Array<{
|
|
37
|
-
token: string;
|
|
38
|
-
balance: bigint;
|
|
39
|
-
decimals: number;
|
|
40
|
-
}>;
|
|
41
|
-
}
|
|
42
|
-
export interface SweepResult {
|
|
43
|
-
index: number;
|
|
44
|
-
address: string;
|
|
45
|
-
token: string;
|
|
46
|
-
amount: string;
|
|
47
|
-
txHash: string;
|
|
48
|
-
}
|
|
49
|
-
export interface ISweepStrategy {
|
|
50
|
-
scan(keys: KeyPair[], treasury: string): Promise<DepositInfo[]>;
|
|
51
|
-
sweep(keys: KeyPair[], treasury: string, dryRun: boolean): Promise<SweepResult[]>;
|
|
52
|
-
}
|
|
53
|
-
export interface IPriceOracle {
|
|
54
|
-
getPrice(token: string, feedAddress?: string): Promise<{
|
|
55
|
-
priceMicros: number;
|
|
56
|
-
}>;
|
|
57
|
-
}
|
|
58
|
-
export interface IWatcherCursorStore {
|
|
59
|
-
get(watcherId: string): Promise<number | null>;
|
|
60
|
-
save(watcherId: string, cursor: number): Promise<void>;
|
|
61
|
-
getConfirmationCount(watcherId: string, txKey: string): Promise<number | null>;
|
|
62
|
-
saveConfirmationCount(watcherId: string, txKey: string, count: number): Promise<void>;
|
|
63
|
-
}
|
|
64
|
-
export interface WatcherOpts {
|
|
65
|
-
rpcUrl: string;
|
|
66
|
-
rpcHeaders: Record<string, string>;
|
|
67
|
-
oracle: IPriceOracle;
|
|
68
|
-
cursorStore: IWatcherCursorStore;
|
|
69
|
-
token: string;
|
|
70
|
-
chain: string;
|
|
71
|
-
contractAddress?: string;
|
|
72
|
-
decimals: number;
|
|
73
|
-
confirmations: number;
|
|
74
|
-
}
|
|
75
|
-
export interface SweeperOpts {
|
|
76
|
-
rpcUrl: string;
|
|
77
|
-
rpcHeaders: Record<string, string>;
|
|
78
|
-
token: string;
|
|
79
|
-
chain: string;
|
|
80
|
-
contractAddress?: string;
|
|
81
|
-
decimals: number;
|
|
82
|
-
}
|
|
83
|
-
export interface IChainWatcher {
|
|
84
|
-
init(): Promise<void>;
|
|
85
|
-
poll(): Promise<PaymentEvent[]>;
|
|
86
|
-
setWatchedAddresses(addresses: string[]): void;
|
|
87
|
-
getCursor(): number;
|
|
88
|
-
stop(): void;
|
|
89
|
-
}
|
|
90
|
-
export interface IChainPlugin {
|
|
91
|
-
pluginId: string;
|
|
92
|
-
supportedCurve: "secp256k1" | "ed25519";
|
|
93
|
-
encoders: Record<string, IAddressEncoder>;
|
|
94
|
-
createWatcher(opts: WatcherOpts): IChainWatcher;
|
|
95
|
-
createSweeper(opts: SweeperOpts): ISweepStrategy;
|
|
96
|
-
version: number;
|
|
97
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { IChainPlugin } from "./interfaces.js";
|
|
2
|
-
export declare class PluginRegistry {
|
|
3
|
-
private plugins;
|
|
4
|
-
register(plugin: IChainPlugin): void;
|
|
5
|
-
get(pluginId: string): IChainPlugin | undefined;
|
|
6
|
-
getOrThrow(pluginId: string): IChainPlugin;
|
|
7
|
-
list(): IChainPlugin[];
|
|
8
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export class PluginRegistry {
|
|
2
|
-
plugins = new Map();
|
|
3
|
-
register(plugin) {
|
|
4
|
-
if (this.plugins.has(plugin.pluginId)) {
|
|
5
|
-
throw new Error(`Plugin "${plugin.pluginId}" is already registered`);
|
|
6
|
-
}
|
|
7
|
-
this.plugins.set(plugin.pluginId, plugin);
|
|
8
|
-
}
|
|
9
|
-
get(pluginId) {
|
|
10
|
-
return this.plugins.get(pluginId);
|
|
11
|
-
}
|
|
12
|
-
getOrThrow(pluginId) {
|
|
13
|
-
const plugin = this.plugins.get(pluginId);
|
|
14
|
-
if (!plugin)
|
|
15
|
-
throw new Error(`Plugin "${pluginId}" is not registered`);
|
|
16
|
-
return plugin;
|
|
17
|
-
}
|
|
18
|
-
list() {
|
|
19
|
-
return [...this.plugins.values()];
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,32 +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
|
-
import type { DrizzleDb } from "../../db/index.js";
|
|
12
|
-
import type { ICryptoChargeRepository } from "./charge-store.js";
|
|
13
|
-
import type { IWatcherCursorStore } from "./cursor-store.js";
|
|
14
|
-
import type { IPriceOracle } from "./oracle/types.js";
|
|
15
|
-
import type { IPaymentMethodStore } from "./payment-method-store.js";
|
|
16
|
-
import type { PluginRegistry } from "./plugin/registry.js";
|
|
17
|
-
export interface PluginWatcherServiceOpts {
|
|
18
|
-
db: DrizzleDb;
|
|
19
|
-
chargeStore: ICryptoChargeRepository;
|
|
20
|
-
methodStore: IPaymentMethodStore;
|
|
21
|
-
cursorStore: IWatcherCursorStore;
|
|
22
|
-
oracle: IPriceOracle;
|
|
23
|
-
registry: PluginRegistry;
|
|
24
|
-
pollIntervalMs?: number;
|
|
25
|
-
log?: (msg: string, meta?: Record<string, unknown>) => void;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Boot plugin-driven watchers for all enabled payment methods.
|
|
29
|
-
*
|
|
30
|
-
* Returns a cleanup function that stops all poll timers and watchers.
|
|
31
|
-
*/
|
|
32
|
-
export declare function startPluginWatchers(opts: PluginWatcherServiceOpts): Promise<() => void>;
|
|
@@ -1,113 +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
|
-
import { handlePayment } from "./watcher-service.js";
|
|
12
|
-
/** Map legacy watcher_type values to plugin IDs for backward compatibility. */
|
|
13
|
-
const WATCHER_TYPE_TO_PLUGIN = {
|
|
14
|
-
utxo: "bitcoin",
|
|
15
|
-
evm: "evm",
|
|
16
|
-
};
|
|
17
|
-
function resolvePlugin(registry, method) {
|
|
18
|
-
// Prefer explicit plugin_id, fall back to watcher_type mapping
|
|
19
|
-
const id = method.pluginId ?? WATCHER_TYPE_TO_PLUGIN[method.watcherType];
|
|
20
|
-
return id ? registry.get(id) : undefined;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Boot plugin-driven watchers for all enabled payment methods.
|
|
24
|
-
*
|
|
25
|
-
* Returns a cleanup function that stops all poll timers and watchers.
|
|
26
|
-
*/
|
|
27
|
-
export async function startPluginWatchers(opts) {
|
|
28
|
-
const { db, chargeStore, methodStore, cursorStore, oracle, registry } = opts;
|
|
29
|
-
const pollMs = opts.pollIntervalMs ?? 15_000;
|
|
30
|
-
const log = opts.log ?? (() => { });
|
|
31
|
-
const methods = await methodStore.listEnabled();
|
|
32
|
-
const timers = [];
|
|
33
|
-
const watchers = [];
|
|
34
|
-
for (const method of methods) {
|
|
35
|
-
if (!method.rpcUrl)
|
|
36
|
-
continue;
|
|
37
|
-
const plugin = resolvePlugin(registry, method);
|
|
38
|
-
if (!plugin) {
|
|
39
|
-
log("No plugin found, skipping method", { id: method.id, chain: method.chain, watcherType: method.watcherType });
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
const watcher = plugin.createWatcher({
|
|
43
|
-
rpcUrl: method.rpcUrl,
|
|
44
|
-
rpcHeaders: JSON.parse(method.rpcHeaders ?? "{}"),
|
|
45
|
-
oracle,
|
|
46
|
-
cursorStore,
|
|
47
|
-
token: method.token,
|
|
48
|
-
chain: method.chain,
|
|
49
|
-
contractAddress: method.contractAddress ?? undefined,
|
|
50
|
-
decimals: method.decimals,
|
|
51
|
-
confirmations: method.confirmations,
|
|
52
|
-
});
|
|
53
|
-
try {
|
|
54
|
-
await watcher.init();
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
log("Watcher init failed, skipping", { chain: method.chain, token: method.token, error: String(err) });
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
// Seed watched addresses from active charges
|
|
61
|
-
const active = await chargeStore.listActiveDepositAddresses();
|
|
62
|
-
const addrs = active.filter((a) => a.chain === method.chain && a.token === method.token).map((a) => a.address);
|
|
63
|
-
watcher.setWatchedAddresses(addrs);
|
|
64
|
-
watchers.push(watcher);
|
|
65
|
-
log(`Plugin watcher started (${method.chain}:${method.token})`, {
|
|
66
|
-
plugin: plugin.pluginId,
|
|
67
|
-
addresses: addrs.length,
|
|
68
|
-
});
|
|
69
|
-
let polling = false;
|
|
70
|
-
timers.push(setInterval(async () => {
|
|
71
|
-
if (polling)
|
|
72
|
-
return;
|
|
73
|
-
polling = true;
|
|
74
|
-
try {
|
|
75
|
-
// Refresh watched addresses each cycle
|
|
76
|
-
const fresh = await chargeStore.listActiveDepositAddresses();
|
|
77
|
-
const freshAddrs = fresh
|
|
78
|
-
.filter((a) => a.chain === method.chain && a.token === method.token)
|
|
79
|
-
.map((a) => a.address);
|
|
80
|
-
watcher.setWatchedAddresses(freshAddrs);
|
|
81
|
-
const events = await watcher.poll();
|
|
82
|
-
for (const ev of events) {
|
|
83
|
-
log("Plugin payment", {
|
|
84
|
-
chain: ev.chain,
|
|
85
|
-
token: ev.token,
|
|
86
|
-
to: ev.to,
|
|
87
|
-
txHash: ev.txHash,
|
|
88
|
-
confirmations: ev.confirmations,
|
|
89
|
-
});
|
|
90
|
-
await handlePayment(db, chargeStore, ev.to, ev.rawAmount, {
|
|
91
|
-
txHash: ev.txHash,
|
|
92
|
-
confirmations: ev.confirmations,
|
|
93
|
-
confirmationsRequired: ev.confirmationsRequired,
|
|
94
|
-
amountReceivedCents: ev.amountUsdCents,
|
|
95
|
-
}, log);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
catch (err) {
|
|
99
|
-
log("Plugin poll error", { chain: method.chain, token: method.token, error: String(err) });
|
|
100
|
-
}
|
|
101
|
-
finally {
|
|
102
|
-
polling = false;
|
|
103
|
-
}
|
|
104
|
-
}, pollMs));
|
|
105
|
-
}
|
|
106
|
-
log("All plugin watchers started", { count: watchers.length, pollMs });
|
|
107
|
-
return () => {
|
|
108
|
-
for (const t of timers)
|
|
109
|
-
clearInterval(t);
|
|
110
|
-
for (const w of watchers)
|
|
111
|
-
w.stop();
|
|
112
|
-
};
|
|
113
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { hexToTron, isTronAddress, tronToHex } from "../address-convert.js";
|
|
3
|
-
// Known Tron address / hex pair (Tron foundation address)
|
|
4
|
-
const TRON_ADDR = "TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW";
|
|
5
|
-
const HEX_ADDR = "0x5a523b449890854c8fc460ab602df9f31fe4293f";
|
|
6
|
-
describe("tronToHex", () => {
|
|
7
|
-
it("converts T... to 0x hex", () => {
|
|
8
|
-
const hex = tronToHex(TRON_ADDR);
|
|
9
|
-
expect(hex).toBe(HEX_ADDR);
|
|
10
|
-
});
|
|
11
|
-
it("rejects non-Tron address", () => {
|
|
12
|
-
expect(() => tronToHex("0x1234")).toThrow("Not a Tron address");
|
|
13
|
-
});
|
|
14
|
-
it("rejects invalid checksum", () => {
|
|
15
|
-
// Flip last character
|
|
16
|
-
const bad = `${TRON_ADDR.slice(0, -1)}X`;
|
|
17
|
-
expect(() => tronToHex(bad)).toThrow();
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
describe("hexToTron", () => {
|
|
21
|
-
it("converts 0x hex to T...", () => {
|
|
22
|
-
const tron = hexToTron(HEX_ADDR);
|
|
23
|
-
expect(tron).toBe(TRON_ADDR);
|
|
24
|
-
});
|
|
25
|
-
it("handles hex without 0x prefix", () => {
|
|
26
|
-
const tron = hexToTron(HEX_ADDR.slice(2));
|
|
27
|
-
expect(tron).toBe(TRON_ADDR);
|
|
28
|
-
});
|
|
29
|
-
it("rejects wrong length", () => {
|
|
30
|
-
expect(() => hexToTron("0x1234")).toThrow("Invalid hex address length");
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
describe("roundtrip", () => {
|
|
34
|
-
it("tronToHex → hexToTron is identity", () => {
|
|
35
|
-
const hex = tronToHex(TRON_ADDR);
|
|
36
|
-
const back = hexToTron(hex);
|
|
37
|
-
expect(back).toBe(TRON_ADDR);
|
|
38
|
-
});
|
|
39
|
-
it("hexToTron → tronToHex is identity", () => {
|
|
40
|
-
const tron = hexToTron(HEX_ADDR);
|
|
41
|
-
const back = tronToHex(tron);
|
|
42
|
-
expect(back).toBe(HEX_ADDR);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
describe("isTronAddress", () => {
|
|
46
|
-
it("returns true for T... address", () => {
|
|
47
|
-
expect(isTronAddress(TRON_ADDR)).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
it("returns false for 0x address", () => {
|
|
50
|
-
expect(isTronAddress(HEX_ADDR)).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
it("returns false for BTC address", () => {
|
|
53
|
-
expect(isTronAddress("bc1qtest")).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Convert a Tron T... address to 0x hex (20 bytes, no 0x41 prefix).
|
|
3
|
-
* For feeding addresses to the EVM watcher JSON-RPC filters.
|
|
4
|
-
*/
|
|
5
|
-
export declare function tronToHex(tronAddr: string): string;
|
|
6
|
-
/**
|
|
7
|
-
* Convert a 0x hex address (20 bytes) back to Tron T... Base58Check.
|
|
8
|
-
* For converting watcher event addresses back to DB format.
|
|
9
|
-
*/
|
|
10
|
-
export declare function hexToTron(hexAddr: string): string;
|
|
11
|
-
/**
|
|
12
|
-
* Check if an address is a Tron T... address.
|
|
13
|
-
*/
|
|
14
|
-
export declare function isTronAddress(addr: string): boolean;
|
|
@@ -1,93 +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
|
-
const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
10
|
-
function base58decode(s) {
|
|
11
|
-
// Count leading '1' characters (each represents a 0x00 byte)
|
|
12
|
-
let leadingZeros = 0;
|
|
13
|
-
for (const ch of s) {
|
|
14
|
-
if (ch !== "1")
|
|
15
|
-
break;
|
|
16
|
-
leadingZeros++;
|
|
17
|
-
}
|
|
18
|
-
let num = 0n;
|
|
19
|
-
for (const ch of s) {
|
|
20
|
-
const idx = BASE58_ALPHABET.indexOf(ch);
|
|
21
|
-
if (idx < 0)
|
|
22
|
-
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++)
|
|
28
|
-
dataBytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
29
|
-
// Prepend leading zero bytes
|
|
30
|
-
const result = new Uint8Array(leadingZeros + dataBytes.length);
|
|
31
|
-
result.set(dataBytes, leadingZeros);
|
|
32
|
-
return result;
|
|
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) {
|
|
39
|
-
if (!tronAddr.startsWith("T"))
|
|
40
|
-
throw new Error(`Not a Tron address: ${tronAddr}`);
|
|
41
|
-
const decoded = base58decode(tronAddr);
|
|
42
|
-
// decoded: [0x41, ...20 bytes address..., ...4 bytes checksum]
|
|
43
|
-
// Verify checksum
|
|
44
|
-
const payload = decoded.slice(0, 21);
|
|
45
|
-
const checksum = sha256(sha256(payload)).slice(0, 4);
|
|
46
|
-
for (let i = 0; i < 4; i++) {
|
|
47
|
-
if (decoded[21 + i] !== checksum[i])
|
|
48
|
-
throw new Error(`Invalid checksum for Tron address: ${tronAddr}`);
|
|
49
|
-
}
|
|
50
|
-
// Strip 0x41 prefix, return 20-byte hex with 0x prefix
|
|
51
|
-
const addrBytes = payload.slice(1);
|
|
52
|
-
return `0x${Array.from(addrBytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Convert a 0x hex address (20 bytes) back to Tron T... Base58Check.
|
|
56
|
-
* For converting watcher event addresses back to DB format.
|
|
57
|
-
*/
|
|
58
|
-
export function hexToTron(hexAddr) {
|
|
59
|
-
const hex = hexAddr.startsWith("0x") ? hexAddr.slice(2) : hexAddr;
|
|
60
|
-
if (hex.length !== 40)
|
|
61
|
-
throw new Error(`Invalid hex address length: ${hex.length}`);
|
|
62
|
-
// Build payload: 0x41 + 20 bytes
|
|
63
|
-
const payload = new Uint8Array(21);
|
|
64
|
-
payload[0] = 0x41;
|
|
65
|
-
for (let i = 0; i < 20; i++)
|
|
66
|
-
payload[i + 1] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
67
|
-
// Compute checksum
|
|
68
|
-
const checksum = sha256(sha256(payload)).slice(0, 4);
|
|
69
|
-
const full = new Uint8Array(25);
|
|
70
|
-
full.set(payload);
|
|
71
|
-
full.set(checksum, 21);
|
|
72
|
-
// Base58 encode
|
|
73
|
-
let num = 0n;
|
|
74
|
-
for (const byte of full)
|
|
75
|
-
num = num * 256n + BigInt(byte);
|
|
76
|
-
let encoded = "";
|
|
77
|
-
while (num > 0n) {
|
|
78
|
-
encoded = BASE58_ALPHABET[Number(num % 58n)] + encoded;
|
|
79
|
-
num = num / 58n;
|
|
80
|
-
}
|
|
81
|
-
for (const byte of full) {
|
|
82
|
-
if (byte !== 0)
|
|
83
|
-
break;
|
|
84
|
-
encoded = `1${encoded}`;
|
|
85
|
-
}
|
|
86
|
-
return encoded;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Check if an address is a Tron T... address.
|
|
90
|
-
*/
|
|
91
|
-
export function isTronAddress(addr) {
|
|
92
|
-
return addr.startsWith("T") && addr.length >= 33 && addr.length <= 35;
|
|
93
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Watcher Service — boots chain watchers and sends webhook callbacks.
|
|
3
|
-
*
|
|
4
|
-
* Payment flow:
|
|
5
|
-
* 1. Watcher detects payment → handlePayment()
|
|
6
|
-
* 2. Accumulate native amount (supports partial payments)
|
|
7
|
-
* 3. When totalReceived >= expectedAmount AND confirmations >= required → confirmed + credit
|
|
8
|
-
* 4. Every payment/confirmation change enqueues a webhook delivery
|
|
9
|
-
* 5. Outbox processor retries failed deliveries with exponential backoff
|
|
10
|
-
*
|
|
11
|
-
* Amount comparison is ALWAYS in native crypto units (sats, wei, token base units).
|
|
12
|
-
* The exchange rate is locked at charge creation — no live price comparison.
|
|
13
|
-
*/
|
|
14
|
-
import type { DrizzleDb } from "../../db/index.js";
|
|
15
|
-
import type { ICryptoChargeRepository } from "./charge-store.js";
|
|
16
|
-
import type { IWatcherCursorStore } from "./cursor-store.js";
|
|
17
|
-
import type { IPriceOracle } from "./oracle/types.js";
|
|
18
|
-
import type { IPaymentMethodStore } from "./payment-method-store.js";
|
|
19
|
-
export interface WatcherServiceOpts {
|
|
20
|
-
db: DrizzleDb;
|
|
21
|
-
chargeStore: ICryptoChargeRepository;
|
|
22
|
-
methodStore: IPaymentMethodStore;
|
|
23
|
-
cursorStore: IWatcherCursorStore;
|
|
24
|
-
oracle: IPriceOracle;
|
|
25
|
-
bitcoindUser?: string;
|
|
26
|
-
bitcoindPassword?: string;
|
|
27
|
-
pollIntervalMs?: number;
|
|
28
|
-
deliveryIntervalMs?: number;
|
|
29
|
-
log?: (msg: string, meta?: Record<string, unknown>) => void;
|
|
30
|
-
/** Allowed callback URL prefixes. Default: ["https://"] — enforces HTTPS. */
|
|
31
|
-
allowedCallbackPrefixes?: string[];
|
|
32
|
-
/** Service key sent as Bearer token in webhook deliveries. */
|
|
33
|
-
serviceKey?: string;
|
|
34
|
-
}
|
|
35
|
-
export interface PaymentPayload {
|
|
36
|
-
txHash: string;
|
|
37
|
-
confirmations: number;
|
|
38
|
-
confirmationsRequired: number;
|
|
39
|
-
amountReceivedCents: number;
|
|
40
|
-
[key: string]: unknown;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Handle a payment event. Accumulates partial payments in native units.
|
|
44
|
-
* Fires webhook on every payment/confirmation change with canonical statuses.
|
|
45
|
-
*
|
|
46
|
-
* 3-phase webhook lifecycle:
|
|
47
|
-
* 1. Tx first seen -> status: "partial", confirmations: 0
|
|
48
|
-
* 2. Each new block -> status: "partial", confirmations: current
|
|
49
|
-
* 3. Threshold reached + full payment -> status: "confirmed"
|
|
50
|
-
*
|
|
51
|
-
* @param nativeAmount — received amount in native base units (sats for BTC/DOGE, raw token units for ERC20).
|
|
52
|
-
* Pass "0" for confirmation-only updates (no new payment, just more confirmations).
|
|
53
|
-
*/
|
|
54
|
-
export declare function handlePayment(db: DrizzleDb, chargeStore: ICryptoChargeRepository, address: string, nativeAmount: string, payload: PaymentPayload, log: (msg: string, meta?: Record<string, unknown>) => void): Promise<void>;
|
|
55
|
-
export declare function startWatchers(opts: WatcherServiceOpts): Promise<() => void>;
|