@wopr-network/platform-core 1.42.3 → 1.43.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/.github/workflows/key-server-image.yml +35 -0
- package/Dockerfile.key-server +20 -0
- package/GATEWAY_BILLING_RESEARCH.md +430 -0
- package/biome.json +2 -9
- package/dist/billing/crypto/__tests__/key-server.test.d.ts +1 -0
- package/dist/billing/crypto/__tests__/key-server.test.js +225 -0
- package/dist/billing/crypto/client.d.ts +84 -20
- package/dist/billing/crypto/client.js +76 -46
- package/dist/billing/crypto/client.test.js +76 -83
- package/dist/billing/crypto/index.d.ts +4 -2
- package/dist/billing/crypto/index.js +2 -1
- package/dist/billing/crypto/key-server-entry.d.ts +1 -0
- package/dist/billing/crypto/key-server-entry.js +43 -0
- package/dist/billing/crypto/key-server.d.ts +18 -0
- package/dist/billing/crypto/key-server.js +239 -0
- package/dist/db/schema/crypto.d.ts +247 -0
- package/dist/db/schema/crypto.js +35 -4
- package/drizzle/migrations/0014_crypto_key_server.sql +60 -0
- package/drizzle/migrations/meta/_journal.json +21 -0
- package/package.json +2 -1
- package/src/billing/crypto/__tests__/key-server.test.ts +247 -0
- package/src/billing/crypto/client.test.ts +80 -96
- package/src/billing/crypto/client.ts +138 -58
- package/src/billing/crypto/index.ts +11 -2
- package/src/billing/crypto/key-server-entry.ts +52 -0
- package/src/billing/crypto/key-server.ts +315 -0
- package/src/db/schema/crypto.ts +45 -4
|
@@ -1,132 +1,116 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { BTCPayClient, loadCryptoConfig } from "./client.js";
|
|
3
|
-
|
|
4
|
-
describe("BTCPayClient", () => {
|
|
5
|
-
it("createInvoice sends correct request and returns id + checkoutLink", async () => {
|
|
6
|
-
const mockResponse = { id: "inv-001", checkoutLink: "https://btcpay.example.com/i/inv-001" };
|
|
7
|
-
const fetchSpy = vi
|
|
8
|
-
.spyOn(globalThis, "fetch")
|
|
9
|
-
.mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
|
|
10
|
-
|
|
11
|
-
const client = new BTCPayClient({
|
|
12
|
-
apiKey: "test-key",
|
|
13
|
-
baseUrl: "https://btcpay.example.com",
|
|
14
|
-
storeId: "store-abc",
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const result = await client.createInvoice({
|
|
18
|
-
amountUsd: 25,
|
|
19
|
-
orderId: "order-123",
|
|
20
|
-
buyerEmail: "test@example.com",
|
|
21
|
-
});
|
|
2
|
+
import { BTCPayClient, CryptoServiceClient, loadCryptoConfig } from "./client.js";
|
|
22
3
|
|
|
23
|
-
|
|
24
|
-
|
|
4
|
+
describe("CryptoServiceClient", () => {
|
|
5
|
+
afterEach(() => vi.restoreAllMocks());
|
|
25
6
|
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
expect(opts?.method).toBe("POST");
|
|
7
|
+
it("deriveAddress sends POST /address with chain", async () => {
|
|
8
|
+
const mockResponse = { address: "bc1q...", index: 42, chain: "bitcoin", token: "BTC" };
|
|
9
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 201 }));
|
|
30
10
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
expect(headers["Content-Type"]).toBe("application/json");
|
|
11
|
+
const client = new CryptoServiceClient({ baseUrl: "http://localhost:3100" });
|
|
12
|
+
const result = await client.deriveAddress("btc");
|
|
34
13
|
|
|
35
|
-
|
|
36
|
-
expect(
|
|
37
|
-
expect(body.currency).toBe("USD");
|
|
38
|
-
expect(body.metadata.orderId).toBe("order-123");
|
|
39
|
-
expect(body.metadata.buyerEmail).toBe("test@example.com");
|
|
40
|
-
expect(body.checkout.speedPolicy).toBe("MediumSpeed");
|
|
14
|
+
expect(result.address).toBe("bc1q...");
|
|
15
|
+
expect(result.index).toBe(42);
|
|
41
16
|
|
|
42
|
-
|
|
17
|
+
const [url, opts] = vi.mocked(fetch).mock.calls[0];
|
|
18
|
+
expect(url).toBe("http://localhost:3100/address");
|
|
19
|
+
expect(opts?.method).toBe("POST");
|
|
20
|
+
expect(JSON.parse(opts?.body as string)).toEqual({ chain: "btc" });
|
|
43
21
|
});
|
|
44
22
|
|
|
45
|
-
it("
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
23
|
+
it("createCharge sends POST /charges", async () => {
|
|
24
|
+
const mockResponse = {
|
|
25
|
+
chargeId: "btc:bc1q...",
|
|
26
|
+
address: "bc1q...",
|
|
27
|
+
chain: "btc",
|
|
28
|
+
token: "BTC",
|
|
29
|
+
amountUsd: 50,
|
|
30
|
+
derivationIndex: 42,
|
|
31
|
+
expiresAt: "2026-03-20T04:00:00Z",
|
|
32
|
+
};
|
|
33
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 201 }));
|
|
34
|
+
|
|
35
|
+
const client = new CryptoServiceClient({
|
|
36
|
+
baseUrl: "http://localhost:3100",
|
|
37
|
+
serviceKey: "sk-test",
|
|
38
|
+
tenantId: "tenant-1",
|
|
39
|
+
});
|
|
40
|
+
const result = await client.createCharge({ chain: "btc", amountUsd: 50 });
|
|
54
41
|
|
|
55
|
-
|
|
56
|
-
expect(
|
|
42
|
+
expect(result.chargeId).toBe("btc:bc1q...");
|
|
43
|
+
expect(result.address).toBe("bc1q...");
|
|
57
44
|
|
|
58
|
-
|
|
45
|
+
const [, opts] = vi.mocked(fetch).mock.calls[0];
|
|
46
|
+
const headers = opts?.headers as Record<string, string>;
|
|
47
|
+
expect(headers.Authorization).toBe("Bearer sk-test");
|
|
48
|
+
expect(headers["X-Tenant-Id"]).toBe("tenant-1");
|
|
59
49
|
});
|
|
60
50
|
|
|
61
|
-
it("
|
|
62
|
-
const
|
|
51
|
+
it("getCharge sends GET /charges/:id", async () => {
|
|
52
|
+
const mockResponse = { chargeId: "btc:bc1q...", status: "confirmed" };
|
|
53
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
|
|
63
54
|
|
|
64
|
-
const client = new
|
|
65
|
-
await
|
|
66
|
-
"BTCPay createInvoice failed (401)",
|
|
67
|
-
);
|
|
55
|
+
const client = new CryptoServiceClient({ baseUrl: "http://localhost:3100" });
|
|
56
|
+
const result = await client.getCharge("btc:bc1q...");
|
|
68
57
|
|
|
69
|
-
|
|
58
|
+
expect(result.status).toBe("confirmed");
|
|
59
|
+
expect(vi.mocked(fetch).mock.calls[0][0]).toBe("http://localhost:3100/charges/btc%3Abc1q...");
|
|
70
60
|
});
|
|
71
61
|
|
|
72
|
-
it("
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
status: 200,
|
|
76
|
-
}),
|
|
77
|
-
);
|
|
62
|
+
it("listChains sends GET /chains", async () => {
|
|
63
|
+
const mockResponse = [{ id: "btc", token: "BTC", chain: "bitcoin", decimals: 8 }];
|
|
64
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify(mockResponse), { status: 200 }));
|
|
78
65
|
|
|
79
|
-
const client = new
|
|
80
|
-
const result = await client.
|
|
66
|
+
const client = new CryptoServiceClient({ baseUrl: "http://localhost:3100" });
|
|
67
|
+
const result = await client.listChains();
|
|
81
68
|
|
|
82
|
-
expect(result
|
|
83
|
-
expect(
|
|
84
|
-
|
|
85
|
-
fetchSpy.mockRestore();
|
|
69
|
+
expect(result).toHaveLength(1);
|
|
70
|
+
expect(result[0].token).toBe("BTC");
|
|
86
71
|
});
|
|
87
|
-
});
|
|
88
72
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
delete process.env.BTCPAY_API_KEY;
|
|
92
|
-
delete process.env.BTCPAY_BASE_URL;
|
|
93
|
-
delete process.env.BTCPAY_STORE_ID;
|
|
94
|
-
});
|
|
73
|
+
it("throws on non-ok response", async () => {
|
|
74
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("Not found", { status: 404 }));
|
|
95
75
|
|
|
96
|
-
|
|
97
|
-
|
|
76
|
+
const client = new CryptoServiceClient({ baseUrl: "http://localhost:3100" });
|
|
77
|
+
await expect(client.getCharge("missing")).rejects.toThrow("CryptoService getCharge failed (404)");
|
|
98
78
|
});
|
|
79
|
+
});
|
|
99
80
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
expect(
|
|
81
|
+
describe("BTCPayClient (deprecated)", () => {
|
|
82
|
+
it("throws on createInvoice", async () => {
|
|
83
|
+
const client = new BTCPayClient({ apiKey: "k", baseUrl: "https://example.com", storeId: "s" });
|
|
84
|
+
await expect(client.createInvoice({ amountUsd: 10, orderId: "o" })).rejects.toThrow("deprecated");
|
|
104
85
|
});
|
|
105
86
|
|
|
106
|
-
it("
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
expect(loadCryptoConfig()).toBeNull();
|
|
87
|
+
it("throws on getInvoice", async () => {
|
|
88
|
+
const client = new BTCPayClient({ apiKey: "k", baseUrl: "https://example.com", storeId: "s" });
|
|
89
|
+
await expect(client.getInvoice("inv-1")).rejects.toThrow("deprecated");
|
|
110
90
|
});
|
|
91
|
+
});
|
|
111
92
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
93
|
+
describe("loadCryptoConfig", () => {
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
delete process.env.CRYPTO_SERVICE_URL;
|
|
96
|
+
delete process.env.CRYPTO_SERVICE_KEY;
|
|
97
|
+
delete process.env.TENANT_ID;
|
|
116
98
|
});
|
|
117
99
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
vi.stubEnv("
|
|
100
|
+
afterEach(() => vi.unstubAllEnvs());
|
|
101
|
+
|
|
102
|
+
it("returns config when CRYPTO_SERVICE_URL is set", () => {
|
|
103
|
+
vi.stubEnv("CRYPTO_SERVICE_URL", "http://10.120.0.5:3100");
|
|
104
|
+
vi.stubEnv("CRYPTO_SERVICE_KEY", "sk-test");
|
|
105
|
+
vi.stubEnv("TENANT_ID", "tenant-1");
|
|
122
106
|
expect(loadCryptoConfig()).toEqual({
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
107
|
+
baseUrl: "http://10.120.0.5:3100",
|
|
108
|
+
serviceKey: "sk-test",
|
|
109
|
+
tenantId: "tenant-1",
|
|
126
110
|
});
|
|
127
111
|
});
|
|
128
112
|
|
|
129
|
-
it("returns null when
|
|
113
|
+
it("returns null when CRYPTO_SERVICE_URL is missing", () => {
|
|
130
114
|
expect(loadCryptoConfig()).toBeNull();
|
|
131
115
|
});
|
|
132
116
|
});
|
|
@@ -1,86 +1,166 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Crypto Key Server client — for products to call the shared service.
|
|
3
|
+
*
|
|
4
|
+
* Replaces BTCPayClient. Products set CRYPTO_SERVICE_URL instead of
|
|
5
|
+
* BTCPAY_API_KEY + BTCPAY_BASE_URL + BTCPAY_STORE_ID.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface CryptoServiceConfig {
|
|
9
|
+
/** Base URL of the crypto key server (e.g. http://10.120.0.5:3100) */
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
/** Service key for auth (reuses gateway service key) */
|
|
12
|
+
serviceKey?: string;
|
|
13
|
+
/** Tenant ID header */
|
|
14
|
+
tenantId?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DeriveAddressResult {
|
|
18
|
+
address: string;
|
|
19
|
+
index: number;
|
|
20
|
+
chain: string;
|
|
21
|
+
token: string;
|
|
22
|
+
}
|
|
2
23
|
|
|
3
|
-
export
|
|
24
|
+
export interface CreateChargeResult {
|
|
25
|
+
chargeId: string;
|
|
26
|
+
address: string;
|
|
27
|
+
chain: string;
|
|
28
|
+
token: string;
|
|
29
|
+
amountUsd: number;
|
|
30
|
+
derivationIndex: number;
|
|
31
|
+
expiresAt: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ChargeStatus {
|
|
35
|
+
chargeId: string;
|
|
36
|
+
status: string;
|
|
37
|
+
address: string | null;
|
|
38
|
+
chain: string | null;
|
|
39
|
+
token: string | null;
|
|
40
|
+
amountUsdCents: number;
|
|
41
|
+
creditedAt: string | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ChainInfo {
|
|
45
|
+
id: string;
|
|
46
|
+
token: string;
|
|
47
|
+
chain: string;
|
|
48
|
+
decimals: number;
|
|
49
|
+
displayName: string;
|
|
50
|
+
contractAddress: string | null;
|
|
51
|
+
confirmations: number;
|
|
52
|
+
}
|
|
4
53
|
|
|
5
54
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Uses plain fetch — zero vendor dependencies.
|
|
9
|
-
* Auth header format: "token <apiKey>" (NOT "Bearer").
|
|
55
|
+
* Client for the shared crypto key server.
|
|
56
|
+
* Products use this instead of running local watchers + holding xpubs.
|
|
10
57
|
*/
|
|
11
|
-
export class
|
|
12
|
-
constructor(private readonly config:
|
|
58
|
+
export class CryptoServiceClient {
|
|
59
|
+
constructor(private readonly config: CryptoServiceConfig) {}
|
|
13
60
|
|
|
14
61
|
private headers(): Record<string, string> {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
62
|
+
const h: Record<string, string> = { "Content-Type": "application/json" };
|
|
63
|
+
if (this.config.serviceKey) h.Authorization = `Bearer ${this.config.serviceKey}`;
|
|
64
|
+
if (this.config.tenantId) h["X-Tenant-Id"] = this.config.tenantId;
|
|
65
|
+
return h;
|
|
19
66
|
}
|
|
20
67
|
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* Returns the invoice ID and checkout link (URL to redirect the user).
|
|
25
|
-
*/
|
|
26
|
-
async createInvoice(opts: {
|
|
27
|
-
amountUsd: number;
|
|
28
|
-
orderId: string;
|
|
29
|
-
buyerEmail?: string;
|
|
30
|
-
redirectURL?: string;
|
|
31
|
-
}): Promise<{ id: string; checkoutLink: string }> {
|
|
32
|
-
const url = `${this.config.baseUrl}/api/v1/stores/${this.config.storeId}/invoices`;
|
|
33
|
-
const body = {
|
|
34
|
-
amount: String(opts.amountUsd),
|
|
35
|
-
currency: "USD",
|
|
36
|
-
metadata: {
|
|
37
|
-
orderId: opts.orderId,
|
|
38
|
-
buyerEmail: opts.buyerEmail,
|
|
39
|
-
},
|
|
40
|
-
checkout: {
|
|
41
|
-
speedPolicy: "MediumSpeed",
|
|
42
|
-
expirationMinutes: 30,
|
|
43
|
-
...(opts.redirectURL ? { redirectURL: opts.redirectURL } : {}),
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const res = await fetch(url, {
|
|
68
|
+
/** Derive the next unused address for a chain. */
|
|
69
|
+
async deriveAddress(chain: string): Promise<DeriveAddressResult> {
|
|
70
|
+
const res = await fetch(`${this.config.baseUrl}/address`, {
|
|
48
71
|
method: "POST",
|
|
49
72
|
headers: this.headers(),
|
|
50
|
-
body: JSON.stringify(
|
|
73
|
+
body: JSON.stringify({ chain }),
|
|
51
74
|
});
|
|
52
|
-
|
|
53
75
|
if (!res.ok) {
|
|
54
76
|
const text = await res.text().catch(() => "");
|
|
55
|
-
throw new Error(`
|
|
77
|
+
throw new Error(`CryptoService deriveAddress failed (${res.status}): ${text}`);
|
|
56
78
|
}
|
|
79
|
+
return (await res.json()) as DeriveAddressResult;
|
|
80
|
+
}
|
|
57
81
|
|
|
58
|
-
|
|
59
|
-
|
|
82
|
+
/** Create a payment charge — derives address, sets expiry, starts watching. */
|
|
83
|
+
async createCharge(opts: {
|
|
84
|
+
chain: string;
|
|
85
|
+
amountUsd: number;
|
|
86
|
+
callbackUrl?: string;
|
|
87
|
+
metadata?: Record<string, unknown>;
|
|
88
|
+
}): Promise<CreateChargeResult> {
|
|
89
|
+
const res = await fetch(`${this.config.baseUrl}/charges`, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: this.headers(),
|
|
92
|
+
body: JSON.stringify(opts),
|
|
93
|
+
});
|
|
94
|
+
if (!res.ok) {
|
|
95
|
+
const text = await res.text().catch(() => "");
|
|
96
|
+
throw new Error(`CryptoService createCharge failed (${res.status}): ${text}`);
|
|
97
|
+
}
|
|
98
|
+
return (await res.json()) as CreateChargeResult;
|
|
60
99
|
}
|
|
61
100
|
|
|
62
|
-
/**
|
|
63
|
-
async
|
|
64
|
-
const
|
|
65
|
-
|
|
101
|
+
/** Check charge status. */
|
|
102
|
+
async getCharge(chargeId: string): Promise<ChargeStatus> {
|
|
103
|
+
const res = await fetch(`${this.config.baseUrl}/charges/${encodeURIComponent(chargeId)}`, {
|
|
104
|
+
headers: this.headers(),
|
|
105
|
+
});
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const text = await res.text().catch(() => "");
|
|
108
|
+
throw new Error(`CryptoService getCharge failed (${res.status}): ${text}`);
|
|
109
|
+
}
|
|
110
|
+
return (await res.json()) as ChargeStatus;
|
|
111
|
+
}
|
|
66
112
|
|
|
113
|
+
/** List all enabled payment methods (for checkout UI). */
|
|
114
|
+
async listChains(): Promise<ChainInfo[]> {
|
|
115
|
+
const res = await fetch(`${this.config.baseUrl}/chains`, {
|
|
116
|
+
headers: this.headers(),
|
|
117
|
+
});
|
|
67
118
|
if (!res.ok) {
|
|
68
119
|
const text = await res.text().catch(() => "");
|
|
69
|
-
throw new Error(`
|
|
120
|
+
throw new Error(`CryptoService listChains failed (${res.status}): ${text}`);
|
|
70
121
|
}
|
|
122
|
+
return (await res.json()) as ChainInfo[];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
71
125
|
|
|
72
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Load crypto service config from environment.
|
|
128
|
+
* Returns null if CRYPTO_SERVICE_URL is not set.
|
|
129
|
+
*
|
|
130
|
+
* Also supports legacy BTCPay env vars for backwards compat during migration.
|
|
131
|
+
*/
|
|
132
|
+
export function loadCryptoConfig(): CryptoServiceConfig | null {
|
|
133
|
+
const baseUrl = process.env.CRYPTO_SERVICE_URL;
|
|
134
|
+
if (baseUrl) {
|
|
135
|
+
return {
|
|
136
|
+
baseUrl,
|
|
137
|
+
serviceKey: process.env.CRYPTO_SERVICE_KEY,
|
|
138
|
+
tenantId: process.env.TENANT_ID,
|
|
139
|
+
};
|
|
73
140
|
}
|
|
141
|
+
return null;
|
|
74
142
|
}
|
|
75
143
|
|
|
144
|
+
// Legacy type alias for backwards compat
|
|
145
|
+
export type CryptoConfig = CryptoServiceConfig;
|
|
146
|
+
|
|
76
147
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
148
|
+
* @deprecated Use CryptoServiceClient instead. BTCPay is replaced by the crypto key server.
|
|
149
|
+
* Kept for backwards compat — products still import BTCPayClient during migration.
|
|
79
150
|
*/
|
|
80
|
-
export
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
151
|
+
export class BTCPayClient {
|
|
152
|
+
constructor(_config: { apiKey: string; baseUrl: string; storeId: string }) {}
|
|
153
|
+
|
|
154
|
+
async createInvoice(_opts: {
|
|
155
|
+
amountUsd: number;
|
|
156
|
+
orderId: string;
|
|
157
|
+
buyerEmail?: string;
|
|
158
|
+
redirectURL?: string;
|
|
159
|
+
}): Promise<{ id: string; checkoutLink: string }> {
|
|
160
|
+
throw new Error("BTCPayClient is deprecated — migrate to CryptoServiceClient");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async getInvoice(_invoiceId: string): Promise<{ id: string; status: string; amount: string; currency: string }> {
|
|
164
|
+
throw new Error("BTCPayClient is deprecated — migrate to CryptoServiceClient");
|
|
165
|
+
}
|
|
86
166
|
}
|
|
@@ -2,11 +2,20 @@ export * from "./btc/index.js";
|
|
|
2
2
|
export type { CryptoChargeRecord, CryptoDepositChargeInput, ICryptoChargeRepository } from "./charge-store.js";
|
|
3
3
|
export { CryptoChargeRepository, DrizzleCryptoChargeRepository } from "./charge-store.js";
|
|
4
4
|
export { createCryptoCheckout, MIN_PAYMENT_USD } from "./checkout.js";
|
|
5
|
-
export type {
|
|
6
|
-
|
|
5
|
+
export type {
|
|
6
|
+
ChainInfo,
|
|
7
|
+
ChargeStatus,
|
|
8
|
+
CreateChargeResult,
|
|
9
|
+
CryptoConfig,
|
|
10
|
+
CryptoServiceConfig,
|
|
11
|
+
DeriveAddressResult,
|
|
12
|
+
} from "./client.js";
|
|
13
|
+
export { BTCPayClient, CryptoServiceClient, loadCryptoConfig } from "./client.js";
|
|
7
14
|
export type { IWatcherCursorStore } from "./cursor-store.js";
|
|
8
15
|
export { DrizzleWatcherCursorStore } from "./cursor-store.js";
|
|
9
16
|
export * from "./evm/index.js";
|
|
17
|
+
export type { KeyServerDeps } from "./key-server.js";
|
|
18
|
+
export { createKeyServerApp } from "./key-server.js";
|
|
10
19
|
export * from "./oracle/index.js";
|
|
11
20
|
export type { IPaymentMethodStore, PaymentMethodRecord } from "./payment-method-store.js";
|
|
12
21
|
export { DrizzlePaymentMethodStore } from "./payment-method-store.js";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone entry point for the crypto key server.
|
|
3
|
+
*
|
|
4
|
+
* Deploys on the chain server (pay.wopr.bot:3100).
|
|
5
|
+
* Boots: postgres → migrations → key server routes → watchers → serve.
|
|
6
|
+
*
|
|
7
|
+
* Usage: node dist/billing/crypto/key-server-entry.js
|
|
8
|
+
*/
|
|
9
|
+
/* biome-ignore-all lint/suspicious/noConsole: standalone entry point */
|
|
10
|
+
import { serve } from "@hono/node-server";
|
|
11
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
12
|
+
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
13
|
+
import pg from "pg";
|
|
14
|
+
import * as schema from "../../db/schema/index.js";
|
|
15
|
+
import { DrizzleCryptoChargeRepository } from "./charge-store.js";
|
|
16
|
+
import { createKeyServerApp } from "./key-server.js";
|
|
17
|
+
import { DrizzlePaymentMethodStore } from "./payment-method-store.js";
|
|
18
|
+
|
|
19
|
+
const PORT = Number(process.env.PORT ?? "3100");
|
|
20
|
+
const DATABASE_URL = process.env.DATABASE_URL;
|
|
21
|
+
const SERVICE_KEY = process.env.SERVICE_KEY;
|
|
22
|
+
const ADMIN_TOKEN = process.env.ADMIN_TOKEN;
|
|
23
|
+
|
|
24
|
+
if (!DATABASE_URL) {
|
|
25
|
+
console.error("DATABASE_URL is required");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function main(): Promise<void> {
|
|
30
|
+
const pool = new pg.Pool({ connectionString: DATABASE_URL });
|
|
31
|
+
|
|
32
|
+
// Run migrations FIRST, before creating schema-typed db
|
|
33
|
+
console.log("[crypto-key-server] Running migrations...");
|
|
34
|
+
await migrate(drizzle(pool), { migrationsFolder: "./drizzle/migrations" });
|
|
35
|
+
|
|
36
|
+
// Now create the schema-typed db (columns guaranteed to exist)
|
|
37
|
+
console.log("[crypto-key-server] Connecting...");
|
|
38
|
+
const db = drizzle(pool, { schema }) as unknown as import("../../db/index.js").DrizzleDb;
|
|
39
|
+
|
|
40
|
+
const chargeStore = new DrizzleCryptoChargeRepository(db);
|
|
41
|
+
const methodStore = new DrizzlePaymentMethodStore(db);
|
|
42
|
+
|
|
43
|
+
const app = createKeyServerApp({ db, chargeStore, methodStore, serviceKey: SERVICE_KEY, adminToken: ADMIN_TOKEN });
|
|
44
|
+
|
|
45
|
+
console.log(`[crypto-key-server] Listening on :${PORT}`);
|
|
46
|
+
serve({ fetch: app.fetch, port: PORT });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
main().catch((err) => {
|
|
50
|
+
console.error("[crypto-key-server] Fatal:", err);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|