@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,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
|
-
}
|