@wopr-network/platform-core 1.64.0 → 1.65.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/billing/crypto/index.d.ts +2 -0
- package/dist/billing/crypto/index.js +1 -0
- package/dist/billing/crypto/key-server.js +3 -0
- package/dist/billing/crypto/payment-method-store.d.ts +3 -0
- package/dist/billing/crypto/payment-method-store.js +9 -0
- package/dist/billing/crypto/plugin/__tests__/integration.test.d.ts +1 -0
- package/dist/billing/crypto/plugin/__tests__/integration.test.js +58 -0
- package/dist/billing/crypto/plugin/__tests__/interfaces.test.d.ts +1 -0
- package/dist/billing/crypto/plugin/__tests__/interfaces.test.js +46 -0
- package/dist/billing/crypto/plugin/__tests__/registry.test.d.ts +1 -0
- package/dist/billing/crypto/plugin/__tests__/registry.test.js +49 -0
- package/dist/billing/crypto/plugin/index.d.ts +2 -0
- package/dist/billing/crypto/plugin/index.js +1 -0
- package/dist/billing/crypto/plugin/interfaces.d.ts +97 -0
- package/dist/billing/crypto/plugin/interfaces.js +2 -0
- package/dist/billing/crypto/plugin/registry.d.ts +8 -0
- package/dist/billing/crypto/plugin/registry.js +21 -0
- package/dist/db/schema/crypto.d.ts +328 -0
- package/dist/db/schema/crypto.js +33 -1
- package/dist/db/schema/snapshots.d.ts +1 -1
- package/drizzle/migrations/0023_key_rings_table.sql +35 -0
- package/drizzle/migrations/0024_backfill_key_rings.sql +75 -0
- package/package.json +5 -1
- package/src/billing/crypto/index.ts +9 -0
- package/src/billing/crypto/key-server.ts +3 -0
- package/src/billing/crypto/payment-method-store.ts +12 -0
- package/src/billing/crypto/plugin/__tests__/integration.test.ts +64 -0
- package/src/billing/crypto/plugin/__tests__/interfaces.test.ts +51 -0
- package/src/billing/crypto/plugin/__tests__/registry.test.ts +58 -0
- package/src/billing/crypto/plugin/index.ts +17 -0
- package/src/billing/crypto/plugin/interfaces.ts +106 -0
- package/src/billing/crypto/plugin/registry.ts +26 -0
- package/src/db/schema/crypto.ts +43 -1
|
@@ -13,6 +13,8 @@ export { handleKeyServerWebhook, handleKeyServerWebhook as handleCryptoWebhook,
|
|
|
13
13
|
export * from "./oracle/index.js";
|
|
14
14
|
export type { IPaymentMethodStore, PaymentMethodRecord } from "./payment-method-store.js";
|
|
15
15
|
export { DrizzlePaymentMethodStore } from "./payment-method-store.js";
|
|
16
|
+
export type { IAddressEncoder, IChainPlugin, IChainWatcher, ICurveDeriver, ISweepStrategy, PaymentEvent, } from "./plugin/index.js";
|
|
17
|
+
export { PluginRegistry } from "./plugin/index.js";
|
|
16
18
|
export type { CryptoCharge, CryptoChargeStatus, CryptoPaymentState } from "./types.js";
|
|
17
19
|
export type { UnifiedCheckoutDeps, UnifiedCheckoutResult } from "./unified-checkout.js";
|
|
18
20
|
export { createUnifiedCheckout, MIN_CHECKOUT_USD as MIN_PAYMENT_USD, MIN_CHECKOUT_USD } from "./unified-checkout.js";
|
|
@@ -7,4 +7,5 @@ export { createKeyServerApp } from "./key-server.js";
|
|
|
7
7
|
export { handleKeyServerWebhook, handleKeyServerWebhook as handleCryptoWebhook, normalizeStatus, } from "./key-server-webhook.js";
|
|
8
8
|
export * from "./oracle/index.js";
|
|
9
9
|
export { DrizzlePaymentMethodStore } from "./payment-method-store.js";
|
|
10
|
+
export { PluginRegistry } from "./plugin/index.js";
|
|
10
11
|
export { createUnifiedCheckout, MIN_CHECKOUT_USD as MIN_PAYMENT_USD, MIN_CHECKOUT_USD } from "./unified-checkout.js";
|
|
@@ -287,6 +287,9 @@ export function createKeyServerApp(deps) {
|
|
|
287
287
|
watcherType: body.watcher_type ?? "evm",
|
|
288
288
|
oracleAssetId: body.oracle_asset_id ?? null,
|
|
289
289
|
confirmations: body.confirmations ?? 6,
|
|
290
|
+
keyRingId: null,
|
|
291
|
+
encoding: null,
|
|
292
|
+
pluginId: null,
|
|
290
293
|
});
|
|
291
294
|
// Record the path allocation (idempotent — ignore if already exists)
|
|
292
295
|
const inserted = (await deps.db
|
|
@@ -19,6 +19,9 @@ export interface PaymentMethodRecord {
|
|
|
19
19
|
watcherType: string;
|
|
20
20
|
oracleAssetId: string | null;
|
|
21
21
|
confirmations: number;
|
|
22
|
+
keyRingId: string | null;
|
|
23
|
+
encoding: string | null;
|
|
24
|
+
pluginId: string | null;
|
|
22
25
|
}
|
|
23
26
|
export interface IPaymentMethodStore {
|
|
24
27
|
/** List all enabled payment methods, ordered by displayOrder. */
|
|
@@ -52,6 +52,9 @@ export class DrizzlePaymentMethodStore {
|
|
|
52
52
|
watcherType: method.watcherType,
|
|
53
53
|
oracleAssetId: method.oracleAssetId,
|
|
54
54
|
confirmations: method.confirmations,
|
|
55
|
+
keyRingId: method.keyRingId,
|
|
56
|
+
encoding: method.encoding,
|
|
57
|
+
pluginId: method.pluginId,
|
|
55
58
|
})
|
|
56
59
|
.onConflictDoUpdate({
|
|
57
60
|
target: paymentMethods.id,
|
|
@@ -73,6 +76,9 @@ export class DrizzlePaymentMethodStore {
|
|
|
73
76
|
watcherType: method.watcherType,
|
|
74
77
|
oracleAssetId: method.oracleAssetId,
|
|
75
78
|
confirmations: method.confirmations,
|
|
79
|
+
keyRingId: method.keyRingId,
|
|
80
|
+
encoding: method.encoding,
|
|
81
|
+
pluginId: method.pluginId,
|
|
76
82
|
},
|
|
77
83
|
});
|
|
78
84
|
}
|
|
@@ -114,5 +120,8 @@ function toRecord(row) {
|
|
|
114
120
|
watcherType: row.watcherType,
|
|
115
121
|
oracleAssetId: row.oracleAssetId,
|
|
116
122
|
confirmations: row.confirmations,
|
|
123
|
+
keyRingId: row.keyRingId,
|
|
124
|
+
encoding: row.encoding,
|
|
125
|
+
pluginId: row.pluginId,
|
|
117
126
|
};
|
|
118
127
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { PluginRegistry } from "../registry.js";
|
|
3
|
+
describe("plugin integration — registry → watcher → events", () => {
|
|
4
|
+
it("full lifecycle: register → create watcher → poll → events", async () => {
|
|
5
|
+
const mockEvent = {
|
|
6
|
+
chain: "test",
|
|
7
|
+
token: "TEST",
|
|
8
|
+
from: "0xsender",
|
|
9
|
+
to: "0xreceiver",
|
|
10
|
+
rawAmount: "1000",
|
|
11
|
+
amountUsdCents: 100,
|
|
12
|
+
txHash: "0xhash",
|
|
13
|
+
blockNumber: 42,
|
|
14
|
+
confirmations: 6,
|
|
15
|
+
confirmationsRequired: 6,
|
|
16
|
+
};
|
|
17
|
+
const plugin = {
|
|
18
|
+
pluginId: "test",
|
|
19
|
+
supportedCurve: "secp256k1",
|
|
20
|
+
encoders: {},
|
|
21
|
+
createWatcher: (_opts) => ({
|
|
22
|
+
init: async () => { },
|
|
23
|
+
poll: async () => [mockEvent],
|
|
24
|
+
setWatchedAddresses: () => { },
|
|
25
|
+
getCursor: () => 42,
|
|
26
|
+
stop: () => { },
|
|
27
|
+
}),
|
|
28
|
+
createSweeper: () => ({ scan: async () => [], sweep: async () => [] }),
|
|
29
|
+
version: 1,
|
|
30
|
+
};
|
|
31
|
+
const registry = new PluginRegistry();
|
|
32
|
+
registry.register(plugin);
|
|
33
|
+
const resolved = registry.getOrThrow("test");
|
|
34
|
+
const watcher = resolved.createWatcher({
|
|
35
|
+
rpcUrl: "http://localhost:8545",
|
|
36
|
+
rpcHeaders: {},
|
|
37
|
+
oracle: {
|
|
38
|
+
getPrice: async () => ({ priceMicros: 3500_000000 }),
|
|
39
|
+
},
|
|
40
|
+
cursorStore: {
|
|
41
|
+
get: async () => null,
|
|
42
|
+
save: async () => { },
|
|
43
|
+
getConfirmationCount: async () => null,
|
|
44
|
+
saveConfirmationCount: async () => { },
|
|
45
|
+
},
|
|
46
|
+
token: "TEST",
|
|
47
|
+
chain: "test",
|
|
48
|
+
decimals: 18,
|
|
49
|
+
confirmations: 6,
|
|
50
|
+
});
|
|
51
|
+
await watcher.init();
|
|
52
|
+
const events = await watcher.poll();
|
|
53
|
+
expect(events).toHaveLength(1);
|
|
54
|
+
expect(events[0].txHash).toBe("0xhash");
|
|
55
|
+
expect(watcher.getCursor()).toBe(42);
|
|
56
|
+
watcher.stop();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/billing/crypto/plugin/__tests__/interfaces.test.ts
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
describe("plugin interfaces — type contracts", () => {
|
|
4
|
+
it("PaymentEvent has required fields", () => {
|
|
5
|
+
const event = {
|
|
6
|
+
chain: "ethereum",
|
|
7
|
+
token: "ETH",
|
|
8
|
+
from: "0xabc",
|
|
9
|
+
to: "0xdef",
|
|
10
|
+
rawAmount: "1000000000000000000",
|
|
11
|
+
amountUsdCents: 350000,
|
|
12
|
+
txHash: "0x123",
|
|
13
|
+
blockNumber: 100,
|
|
14
|
+
confirmations: 6,
|
|
15
|
+
confirmationsRequired: 6,
|
|
16
|
+
};
|
|
17
|
+
expect(event.chain).toBe("ethereum");
|
|
18
|
+
expect(event.amountUsdCents).toBe(350000);
|
|
19
|
+
});
|
|
20
|
+
it("ICurveDeriver contract is satisfiable", () => {
|
|
21
|
+
const deriver = {
|
|
22
|
+
derivePublicKey: (_chain, _index) => new Uint8Array(33),
|
|
23
|
+
getCurve: () => "secp256k1",
|
|
24
|
+
};
|
|
25
|
+
expect(deriver.getCurve()).toBe("secp256k1");
|
|
26
|
+
expect(deriver.derivePublicKey(0, 0)).toBeInstanceOf(Uint8Array);
|
|
27
|
+
});
|
|
28
|
+
it("IAddressEncoder contract is satisfiable", () => {
|
|
29
|
+
const encoder = {
|
|
30
|
+
encode: (_pk, _params) => "bc1qtest",
|
|
31
|
+
encodingType: () => "bech32",
|
|
32
|
+
};
|
|
33
|
+
expect(encoder.encodingType()).toBe("bech32");
|
|
34
|
+
expect(encoder.encode(new Uint8Array(33), { hrp: "bc" })).toBe("bc1qtest");
|
|
35
|
+
});
|
|
36
|
+
it("IChainWatcher contract is satisfiable", () => {
|
|
37
|
+
const watcher = {
|
|
38
|
+
init: async () => { },
|
|
39
|
+
poll: async () => [],
|
|
40
|
+
setWatchedAddresses: () => { },
|
|
41
|
+
getCursor: () => 0,
|
|
42
|
+
stop: () => { },
|
|
43
|
+
};
|
|
44
|
+
expect(watcher.getCursor()).toBe(0);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,2 @@
|
|
|
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";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PluginRegistry } from "./registry.js";
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
}
|