@wopr-network/platform-core 1.67.0 → 1.67.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/billing/crypto/btc/checkout.d.ts +4 -0
  2. package/dist/billing/crypto/btc/checkout.js +1 -2
  3. package/dist/billing/crypto/btc/index.d.ts +0 -4
  4. package/dist/billing/crypto/btc/index.js +0 -2
  5. package/dist/billing/crypto/evm/__tests__/checkout.test.js +8 -11
  6. package/dist/billing/crypto/evm/__tests__/eth-checkout.test.js +15 -1
  7. package/dist/billing/crypto/evm/checkout.d.ts +2 -0
  8. package/dist/billing/crypto/evm/checkout.js +1 -2
  9. package/dist/billing/crypto/evm/eth-checkout.d.ts +13 -2
  10. package/dist/billing/crypto/evm/eth-checkout.js +2 -4
  11. package/dist/billing/crypto/evm/eth-settler.d.ts +1 -1
  12. package/dist/billing/crypto/evm/index.d.ts +2 -8
  13. package/dist/billing/crypto/evm/index.js +0 -3
  14. package/dist/billing/crypto/evm/types.d.ts +16 -0
  15. package/dist/billing/crypto/index.d.ts +1 -6
  16. package/dist/billing/crypto/index.js +2 -3
  17. package/dist/billing/crypto/types.d.ts +0 -43
  18. package/dist/billing/crypto/types.js +1 -24
  19. package/package.json +1 -5
  20. package/src/billing/crypto/btc/checkout.ts +3 -2
  21. package/src/billing/crypto/btc/index.ts +0 -4
  22. package/src/billing/crypto/evm/__tests__/checkout.test.ts +10 -12
  23. package/src/billing/crypto/evm/__tests__/eth-checkout.test.ts +17 -1
  24. package/src/billing/crypto/evm/__tests__/eth-settler.test.ts +1 -1
  25. package/src/billing/crypto/evm/checkout.ts +3 -2
  26. package/src/billing/crypto/evm/eth-checkout.ts +15 -6
  27. package/src/billing/crypto/evm/eth-settler.ts +1 -1
  28. package/src/billing/crypto/evm/index.ts +8 -7
  29. package/src/billing/crypto/evm/types.ts +17 -0
  30. package/src/billing/crypto/index.ts +14 -12
  31. package/src/billing/crypto/types.ts +0 -63
  32. package/dist/billing/crypto/__tests__/address-gen.test.d.ts +0 -1
  33. package/dist/billing/crypto/__tests__/address-gen.test.js +0 -219
  34. package/dist/billing/crypto/__tests__/key-server.test.d.ts +0 -1
  35. package/dist/billing/crypto/__tests__/key-server.test.js +0 -742
  36. package/dist/billing/crypto/__tests__/watcher-service.test.d.ts +0 -1
  37. package/dist/billing/crypto/__tests__/watcher-service.test.js +0 -174
  38. package/dist/billing/crypto/address-gen.d.ts +0 -24
  39. package/dist/billing/crypto/address-gen.js +0 -176
  40. package/dist/billing/crypto/btc/__tests__/watcher.test.d.ts +0 -1
  41. package/dist/billing/crypto/btc/__tests__/watcher.test.js +0 -170
  42. package/dist/billing/crypto/btc/watcher.d.ts +0 -44
  43. package/dist/billing/crypto/btc/watcher.js +0 -118
  44. package/dist/billing/crypto/evm/__tests__/eth-watcher.test.d.ts +0 -1
  45. package/dist/billing/crypto/evm/__tests__/eth-watcher.test.js +0 -167
  46. package/dist/billing/crypto/evm/__tests__/watcher-confirmations.test.d.ts +0 -1
  47. package/dist/billing/crypto/evm/__tests__/watcher-confirmations.test.js +0 -159
  48. package/dist/billing/crypto/evm/__tests__/watcher.test.d.ts +0 -1
  49. package/dist/billing/crypto/evm/__tests__/watcher.test.js +0 -145
  50. package/dist/billing/crypto/evm/eth-watcher.d.ts +0 -66
  51. package/dist/billing/crypto/evm/eth-watcher.js +0 -121
  52. package/dist/billing/crypto/evm/watcher.d.ts +0 -51
  53. package/dist/billing/crypto/evm/watcher.js +0 -156
  54. package/dist/billing/crypto/key-server-entry.d.ts +0 -1
  55. package/dist/billing/crypto/key-server-entry.js +0 -122
  56. package/dist/billing/crypto/key-server.d.ts +0 -32
  57. package/dist/billing/crypto/key-server.js +0 -472
  58. package/dist/billing/crypto/oracle/__tests__/chainlink.test.d.ts +0 -1
  59. package/dist/billing/crypto/oracle/__tests__/chainlink.test.js +0 -83
  60. package/dist/billing/crypto/oracle/__tests__/coingecko.test.d.ts +0 -1
  61. package/dist/billing/crypto/oracle/__tests__/coingecko.test.js +0 -65
  62. package/dist/billing/crypto/oracle/__tests__/composite.test.d.ts +0 -1
  63. package/dist/billing/crypto/oracle/__tests__/composite.test.js +0 -48
  64. package/dist/billing/crypto/oracle/__tests__/convert.test.d.ts +0 -1
  65. package/dist/billing/crypto/oracle/__tests__/convert.test.js +0 -61
  66. package/dist/billing/crypto/oracle/__tests__/fixed.test.d.ts +0 -1
  67. package/dist/billing/crypto/oracle/__tests__/fixed.test.js +0 -20
  68. package/dist/billing/crypto/oracle/chainlink.d.ts +0 -26
  69. package/dist/billing/crypto/oracle/chainlink.js +0 -62
  70. package/dist/billing/crypto/oracle/coingecko.d.ts +0 -22
  71. package/dist/billing/crypto/oracle/coingecko.js +0 -71
  72. package/dist/billing/crypto/oracle/composite.d.ts +0 -14
  73. package/dist/billing/crypto/oracle/composite.js +0 -34
  74. package/dist/billing/crypto/oracle/convert.d.ts +0 -30
  75. package/dist/billing/crypto/oracle/convert.js +0 -51
  76. package/dist/billing/crypto/oracle/fixed.d.ts +0 -10
  77. package/dist/billing/crypto/oracle/fixed.js +0 -22
  78. package/dist/billing/crypto/oracle/index.d.ts +0 -9
  79. package/dist/billing/crypto/oracle/index.js +0 -6
  80. package/dist/billing/crypto/oracle/types.d.ts +0 -22
  81. package/dist/billing/crypto/oracle/types.js +0 -7
  82. package/dist/billing/crypto/plugin/__tests__/integration.test.d.ts +0 -1
  83. package/dist/billing/crypto/plugin/__tests__/integration.test.js +0 -58
  84. package/dist/billing/crypto/plugin/__tests__/interfaces.test.d.ts +0 -1
  85. package/dist/billing/crypto/plugin/__tests__/interfaces.test.js +0 -46
  86. package/dist/billing/crypto/plugin/__tests__/registry.test.d.ts +0 -1
  87. package/dist/billing/crypto/plugin/__tests__/registry.test.js +0 -49
  88. package/dist/billing/crypto/plugin/index.d.ts +0 -2
  89. package/dist/billing/crypto/plugin/index.js +0 -1
  90. package/dist/billing/crypto/plugin/interfaces.d.ts +0 -97
  91. package/dist/billing/crypto/plugin/interfaces.js +0 -2
  92. package/dist/billing/crypto/plugin/registry.d.ts +0 -8
  93. package/dist/billing/crypto/plugin/registry.js +0 -21
  94. package/dist/billing/crypto/plugin-watcher-service.d.ts +0 -32
  95. package/dist/billing/crypto/plugin-watcher-service.js +0 -113
  96. package/dist/billing/crypto/tron/__tests__/address-convert.test.d.ts +0 -1
  97. package/dist/billing/crypto/tron/__tests__/address-convert.test.js +0 -55
  98. package/dist/billing/crypto/tron/address-convert.d.ts +0 -14
  99. package/dist/billing/crypto/tron/address-convert.js +0 -93
  100. package/dist/billing/crypto/watcher-service.d.ts +0 -55
  101. package/dist/billing/crypto/watcher-service.js +0 -438
  102. package/src/billing/crypto/__tests__/address-gen.test.ts +0 -264
  103. package/src/billing/crypto/__tests__/key-server.test.ts +0 -823
  104. package/src/billing/crypto/__tests__/watcher-service.test.ts +0 -242
  105. package/src/billing/crypto/address-gen.ts +0 -185
  106. package/src/billing/crypto/btc/__tests__/watcher.test.ts +0 -201
  107. package/src/billing/crypto/btc/watcher.ts +0 -161
  108. package/src/billing/crypto/evm/__tests__/eth-watcher.test.ts +0 -190
  109. package/src/billing/crypto/evm/__tests__/watcher-confirmations.test.ts +0 -191
  110. package/src/billing/crypto/evm/__tests__/watcher.test.ts +0 -167
  111. package/src/billing/crypto/evm/eth-watcher.ts +0 -182
  112. package/src/billing/crypto/evm/watcher.ts +0 -204
  113. package/src/billing/crypto/key-server-entry.ts +0 -144
  114. package/src/billing/crypto/key-server.ts +0 -617
  115. package/src/billing/crypto/oracle/__tests__/chainlink.test.ts +0 -107
  116. package/src/billing/crypto/oracle/__tests__/coingecko.test.ts +0 -75
  117. package/src/billing/crypto/oracle/__tests__/composite.test.ts +0 -61
  118. package/src/billing/crypto/oracle/__tests__/convert.test.ts +0 -74
  119. package/src/billing/crypto/oracle/__tests__/fixed.test.ts +0 -23
  120. package/src/billing/crypto/oracle/chainlink.ts +0 -86
  121. package/src/billing/crypto/oracle/coingecko.ts +0 -96
  122. package/src/billing/crypto/oracle/composite.ts +0 -35
  123. package/src/billing/crypto/oracle/convert.ts +0 -53
  124. package/src/billing/crypto/oracle/fixed.ts +0 -25
  125. package/src/billing/crypto/oracle/index.ts +0 -9
  126. package/src/billing/crypto/oracle/types.ts +0 -28
  127. package/src/billing/crypto/plugin/__tests__/integration.test.ts +0 -64
  128. package/src/billing/crypto/plugin/__tests__/interfaces.test.ts +0 -51
  129. package/src/billing/crypto/plugin/__tests__/registry.test.ts +0 -58
  130. package/src/billing/crypto/plugin/index.ts +0 -17
  131. package/src/billing/crypto/plugin/interfaces.ts +0 -106
  132. package/src/billing/crypto/plugin/registry.ts +0 -26
  133. package/src/billing/crypto/plugin-watcher-service.ts +0 -148
  134. package/src/billing/crypto/tron/__tests__/address-convert.test.ts +0 -67
  135. package/src/billing/crypto/tron/address-convert.ts +0 -89
  136. package/src/billing/crypto/watcher-service.ts +0 -549
@@ -1,742 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { createKeyServerApp } from "../key-server.js";
3
- /** Create a mock db that supports transaction() by passing itself to the callback. */
4
- function createMockDb() {
5
- const mockMethod = {
6
- id: "btc",
7
- type: "native",
8
- token: "BTC",
9
- chain: "bitcoin",
10
- xpub: "xpub6CUGRUonZSQ4TWtTMmzXdrXDtypWKiKrhko4egpiMZbpiaQL2jkwSB1icqYh2cfDfVxdx4df189oLKnC5fSwqPfgyP3hooxujYzAu3fDVmz",
11
- nextIndex: 1,
12
- decimals: 8,
13
- addressType: "bech32",
14
- encodingParams: '{"hrp":"bc"}',
15
- watcherType: "utxo",
16
- oracleAssetId: "bitcoin",
17
- confirmations: 6,
18
- };
19
- const db = {
20
- update: vi.fn().mockReturnValue({
21
- set: vi.fn().mockReturnValue({
22
- where: vi.fn().mockReturnValue({
23
- returning: vi.fn().mockResolvedValue([mockMethod]),
24
- }),
25
- }),
26
- }),
27
- insert: vi.fn().mockReturnValue({
28
- values: vi.fn().mockReturnValue({
29
- onConflictDoNothing: vi.fn().mockResolvedValue({ rowCount: 1 }),
30
- }),
31
- }),
32
- select: vi.fn().mockReturnValue({
33
- from: vi.fn().mockReturnValue({
34
- where: vi.fn().mockResolvedValue([]),
35
- }),
36
- }),
37
- // transaction() passes itself as tx — mocks work the same way
38
- transaction: vi.fn().mockImplementation(async (fn) => fn(db)),
39
- };
40
- return db;
41
- }
42
- /** Minimal mock deps for key server tests. */
43
- function mockDeps() {
44
- const chargeStore = {
45
- getByReferenceId: vi.fn().mockResolvedValue({
46
- referenceId: "btc:bc1q...",
47
- status: "New",
48
- depositAddress: "bc1q...",
49
- chain: "bitcoin",
50
- token: "BTC",
51
- amountUsdCents: 5000,
52
- creditedAt: null,
53
- }),
54
- createStablecoinCharge: vi.fn().mockResolvedValue(undefined),
55
- create: vi.fn(),
56
- updateStatus: vi.fn(),
57
- markCredited: vi.fn(),
58
- isCredited: vi.fn(),
59
- getByDepositAddress: vi.fn(),
60
- getNextDerivationIndex: vi.fn(),
61
- listActiveDepositAddresses: vi.fn(),
62
- };
63
- const methodStore = {
64
- listEnabled: vi.fn().mockResolvedValue([
65
- {
66
- id: "btc",
67
- token: "BTC",
68
- chain: "bitcoin",
69
- decimals: 8,
70
- displayName: "Bitcoin",
71
- contractAddress: null,
72
- confirmations: 6,
73
- iconUrl: null,
74
- },
75
- {
76
- id: "base-usdc",
77
- token: "USDC",
78
- chain: "base",
79
- decimals: 6,
80
- displayName: "USDC on Base",
81
- contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
82
- confirmations: 12,
83
- iconUrl: null,
84
- },
85
- ]),
86
- listAll: vi.fn(),
87
- getById: vi.fn().mockResolvedValue({
88
- id: "btc",
89
- type: "native",
90
- token: "BTC",
91
- chain: "bitcoin",
92
- decimals: 8,
93
- displayName: "Bitcoin",
94
- contractAddress: null,
95
- confirmations: 6,
96
- oracleAddress: "0x64c911996D3c6aC71f9b455B1E8E7266BcbD848F",
97
- xpub: null,
98
- displayOrder: 0,
99
- iconUrl: null,
100
- enabled: true,
101
- rpcUrl: null,
102
- }),
103
- listByType: vi.fn(),
104
- upsert: vi.fn().mockResolvedValue(undefined),
105
- setEnabled: vi.fn().mockResolvedValue(undefined),
106
- patchMetadata: vi.fn().mockResolvedValue(true),
107
- };
108
- return {
109
- db: createMockDb(),
110
- chargeStore: chargeStore,
111
- methodStore: methodStore,
112
- oracle: { getPrice: vi.fn().mockResolvedValue({ priceMicros: 65_000_000_000, updatedAt: new Date() }) },
113
- };
114
- }
115
- describe("key-server routes", () => {
116
- it("GET /chains returns enabled payment methods", async () => {
117
- const app = createKeyServerApp(mockDeps());
118
- const res = await app.request("/chains");
119
- expect(res.status).toBe(200);
120
- const body = await res.json();
121
- expect(body).toHaveLength(2);
122
- expect(body[0].token).toBe("BTC");
123
- expect(body[1].token).toBe("USDC");
124
- });
125
- it("POST /address requires chain", async () => {
126
- const app = createKeyServerApp(mockDeps());
127
- const res = await app.request("/address", {
128
- method: "POST",
129
- headers: { "Content-Type": "application/json" },
130
- body: JSON.stringify({}),
131
- });
132
- expect(res.status).toBe(400);
133
- });
134
- it("POST /address derives BTC address", async () => {
135
- const app = createKeyServerApp(mockDeps());
136
- const res = await app.request("/address", {
137
- method: "POST",
138
- headers: { "Content-Type": "application/json" },
139
- body: JSON.stringify({ chain: "btc" }),
140
- });
141
- expect(res.status).toBe(201);
142
- const body = await res.json();
143
- expect(body.address).toMatch(/^bc1q/);
144
- expect(body.index).toBe(0);
145
- expect(body.chain).toBe("bitcoin");
146
- expect(body.token).toBe("BTC");
147
- });
148
- it("GET /charges/:id returns charge status", async () => {
149
- const app = createKeyServerApp(mockDeps());
150
- const res = await app.request("/charges/btc:bc1q...");
151
- expect(res.status).toBe(200);
152
- const body = await res.json();
153
- expect(body.chargeId).toBe("btc:bc1q...");
154
- expect(body.status).toBe("New");
155
- });
156
- it("GET /charges/:id returns 404 for missing charge", async () => {
157
- const deps = mockDeps();
158
- deps.chargeStore.getByReferenceId.mockResolvedValue(null);
159
- const app = createKeyServerApp(deps);
160
- const res = await app.request("/charges/nonexistent");
161
- expect(res.status).toBe(404);
162
- });
163
- it("POST /charges validates amountUsd", async () => {
164
- const app = createKeyServerApp(mockDeps());
165
- const res = await app.request("/charges", {
166
- method: "POST",
167
- headers: { "Content-Type": "application/json" },
168
- body: JSON.stringify({ chain: "btc", amountUsd: -10 }),
169
- });
170
- expect(res.status).toBe(400);
171
- });
172
- it("POST /address retries on shared-xpub address collision", async () => {
173
- const collision = Object.assign(new Error("unique_violation"), { code: "23505" });
174
- let callCount = 0;
175
- const mockMethod = {
176
- id: "eth",
177
- type: "native",
178
- token: "ETH",
179
- chain: "base",
180
- xpub: "xpub6CUGRUonZSQ4TWtTMmzXdrXDtypWKiKrhko4egpiMZbpiaQL2jkwSB1icqYh2cfDfVxdx4df189oLKnC5fSwqPfgyP3hooxujYzAu3fDVmz",
181
- nextIndex: 0,
182
- decimals: 18,
183
- addressType: "evm",
184
- encodingParams: "{}",
185
- watcherType: "evm",
186
- oracleAssetId: "ethereum",
187
- confirmations: 1,
188
- };
189
- const db = {
190
- // Each update call increments nextIndex
191
- update: vi.fn().mockImplementation(() => ({
192
- set: vi.fn().mockReturnValue({
193
- where: vi.fn().mockReturnValue({
194
- returning: vi.fn().mockImplementation(() => {
195
- callCount++;
196
- return Promise.resolve([{ ...mockMethod, nextIndex: callCount }]);
197
- }),
198
- }),
199
- }),
200
- })),
201
- insert: vi.fn().mockImplementation(() => ({
202
- values: vi.fn().mockImplementation(() => {
203
- // First insert collides, second succeeds
204
- if (callCount <= 1)
205
- throw collision;
206
- return { onConflictDoNothing: vi.fn().mockResolvedValue({ rowCount: 1 }) };
207
- }),
208
- })),
209
- select: vi.fn().mockReturnValue({
210
- from: vi.fn().mockReturnValue({
211
- where: vi.fn().mockResolvedValue([]),
212
- }),
213
- }),
214
- transaction: vi.fn().mockImplementation(async (fn) => fn(db)),
215
- };
216
- const deps = mockDeps();
217
- deps.db = db;
218
- const app = createKeyServerApp(deps);
219
- const res = await app.request("/address", {
220
- method: "POST",
221
- headers: { "Content-Type": "application/json" },
222
- body: JSON.stringify({ chain: "eth" }),
223
- });
224
- expect(res.status).toBe(201);
225
- const body = await res.json();
226
- expect(body.address).toMatch(/^0x/);
227
- // Should have called update twice (first collision, then success)
228
- expect(callCount).toBe(2);
229
- expect(body.index).toBe(1); // skipped index 0
230
- });
231
- it("POST /address retries on Drizzle-wrapped collision error (cause.code)", async () => {
232
- // Drizzle wraps PG errors: err.code is undefined, err.cause.code has "23505"
233
- const pgError = Object.assign(new Error("unique_violation"), { code: "23505" });
234
- const drizzleError = Object.assign(new Error("DrizzleQueryError"), { cause: pgError });
235
- let callCount = 0;
236
- const mockMethod = {
237
- id: "eth",
238
- type: "native",
239
- token: "ETH",
240
- chain: "base",
241
- xpub: "xpub6CUGRUonZSQ4TWtTMmzXdrXDtypWKiKrhko4egpiMZbpiaQL2jkwSB1icqYh2cfDfVxdx4df189oLKnC5fSwqPfgyP3hooxujYzAu3fDVmz",
242
- nextIndex: 0,
243
- decimals: 18,
244
- addressType: "evm",
245
- encodingParams: "{}",
246
- watcherType: "evm",
247
- oracleAssetId: "ethereum",
248
- confirmations: 1,
249
- };
250
- const db = {
251
- update: vi.fn().mockImplementation(() => ({
252
- set: vi.fn().mockReturnValue({
253
- where: vi.fn().mockReturnValue({
254
- returning: vi.fn().mockImplementation(() => {
255
- callCount++;
256
- return Promise.resolve([{ ...mockMethod, nextIndex: callCount }]);
257
- }),
258
- }),
259
- }),
260
- })),
261
- insert: vi.fn().mockImplementation(() => ({
262
- values: vi.fn().mockImplementation(() => {
263
- if (callCount <= 1)
264
- throw drizzleError;
265
- return { onConflictDoNothing: vi.fn().mockResolvedValue({ rowCount: 1 }) };
266
- }),
267
- })),
268
- select: vi.fn().mockReturnValue({
269
- from: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue([]) }),
270
- }),
271
- transaction: vi.fn().mockImplementation(async (fn) => fn(db)),
272
- };
273
- const deps = mockDeps();
274
- deps.db = db;
275
- const app = createKeyServerApp(deps);
276
- const res = await app.request("/address", {
277
- method: "POST",
278
- headers: { "Content-Type": "application/json" },
279
- body: JSON.stringify({ chain: "eth" }),
280
- });
281
- expect(res.status).toBe(201);
282
- const body = await res.json();
283
- expect(body.address).toMatch(/^0x/);
284
- expect(callCount).toBe(2);
285
- expect(body.index).toBe(1);
286
- });
287
- it("POST /charges creates a charge", async () => {
288
- const app = createKeyServerApp(mockDeps());
289
- const res = await app.request("/charges", {
290
- method: "POST",
291
- headers: { "Content-Type": "application/json" },
292
- body: JSON.stringify({ chain: "btc", amountUsd: 50 }),
293
- });
294
- expect(res.status).toBe(201);
295
- const body = await res.json();
296
- expect(body.address).toMatch(/^bc1q/);
297
- expect(body.amountUsd).toBe(50);
298
- expect(body.expiresAt).toBeTruthy();
299
- });
300
- it("GET /admin/next-path returns available path", async () => {
301
- const deps = mockDeps();
302
- deps.adminToken = "test-admin";
303
- const app = createKeyServerApp(deps);
304
- const res = await app.request("/admin/next-path?coin_type=0", {
305
- headers: { Authorization: "Bearer test-admin" },
306
- });
307
- expect(res.status).toBe(200);
308
- const body = await res.json();
309
- expect(body.path).toBe("m/44'/0'/0'");
310
- expect(body.status).toBe("available");
311
- });
312
- it("DELETE /admin/chains/:id disables chain", async () => {
313
- const deps = mockDeps();
314
- deps.adminToken = "test-admin";
315
- const app = createKeyServerApp(deps);
316
- const res = await app.request("/admin/chains/doge", {
317
- method: "DELETE",
318
- headers: { Authorization: "Bearer test-admin" },
319
- });
320
- expect(res.status).toBe(204);
321
- expect(deps.methodStore.setEnabled).toHaveBeenCalledWith("doge", false);
322
- });
323
- });
324
- describe("key-server pool endpoints", () => {
325
- /** Create a mock db that supports pool queries. */
326
- function createPoolMockDb(opts) {
327
- const keyRing = opts?.keyRing ?? null;
328
- const poolEntries = opts?.poolEntries ?? [];
329
- const allKeyRings = opts?.allKeyRings ?? (keyRing ? [keyRing] : []);
330
- const db = {};
331
- // Track which table is being queried via from()
332
- db.select = vi.fn().mockReturnValue({
333
- from: vi.fn().mockImplementation((table) => {
334
- const tableName = table[Symbol.for("drizzle:Name")];
335
- if (tableName === "key_rings") {
336
- return {
337
- where: vi.fn().mockResolvedValue(keyRing ? [keyRing] : []),
338
- orderBy: vi.fn().mockResolvedValue(allKeyRings),
339
- };
340
- }
341
- if (tableName === "address_pool") {
342
- return {
343
- where: vi.fn().mockImplementation(() => ({
344
- orderBy: vi.fn().mockImplementation(() => ({
345
- limit: vi.fn().mockResolvedValue(poolEntries.filter((e) => e.assignedTo === null).slice(0, 1)),
346
- })),
347
- // For counting all pool entries for a key ring
348
- length: poolEntries.length,
349
- filter: (fn) => poolEntries.filter(fn),
350
- [Symbol.iterator]: function* () {
351
- yield* poolEntries;
352
- },
353
- })),
354
- };
355
- }
356
- // Default: payment_methods
357
- return {
358
- where: vi.fn().mockResolvedValue([]),
359
- };
360
- }),
361
- });
362
- db.insert = vi.fn().mockReturnValue({
363
- values: vi.fn().mockReturnValue({
364
- onConflictDoNothing: vi.fn().mockResolvedValue({ rowCount: 1 }),
365
- }),
366
- });
367
- db.update = vi.fn().mockReturnValue({
368
- set: vi.fn().mockReturnValue({
369
- where: vi.fn().mockReturnValue({
370
- returning: vi.fn().mockResolvedValue([]),
371
- }),
372
- }),
373
- });
374
- db.transaction = vi.fn().mockImplementation(async (fn) => fn(db));
375
- return db;
376
- }
377
- it("POST /admin/pool/replenish validates required fields", async () => {
378
- const deps = mockDeps();
379
- deps.adminToken = "test-admin";
380
- const app = createKeyServerApp(deps);
381
- const res = await app.request("/admin/pool/replenish", {
382
- method: "POST",
383
- headers: { "Content-Type": "application/json", Authorization: "Bearer test-admin" },
384
- body: JSON.stringify({}),
385
- });
386
- expect(res.status).toBe(400);
387
- });
388
- it("POST /admin/pool/replenish returns 404 for unknown key ring", async () => {
389
- const db = createPoolMockDb({ keyRing: null });
390
- const deps = mockDeps();
391
- deps.adminToken = "test-admin";
392
- deps.db = db;
393
- const app = createKeyServerApp(deps);
394
- const res = await app.request("/admin/pool/replenish", {
395
- method: "POST",
396
- headers: { "Content-Type": "application/json", Authorization: "Bearer test-admin" },
397
- body: JSON.stringify({
398
- key_ring_id: "sol-main",
399
- plugin_id: "solana",
400
- encoding: "base58-solana",
401
- addresses: [{ index: 0, public_key: "abcd", address: "SolAddr1" }],
402
- }),
403
- });
404
- expect(res.status).toBe(404);
405
- });
406
- it("POST /admin/pool/replenish inserts validated addresses", async () => {
407
- const db = createPoolMockDb({
408
- keyRing: { id: "sol-main", derivationMode: "pre-derived" },
409
- poolEntries: [],
410
- });
411
- // Override select().from() to handle both keyRings query and the count query
412
- const selectMock = vi.fn().mockReturnValue({
413
- from: vi.fn().mockImplementation((table) => {
414
- const tableName = table[Symbol.for("drizzle:Name")];
415
- if (tableName === "key_rings") {
416
- return {
417
- where: vi.fn().mockResolvedValue([{ id: "sol-main", derivationMode: "pre-derived" }]),
418
- };
419
- }
420
- // address_pool count query (after insert)
421
- return {
422
- where: vi.fn().mockResolvedValue([
423
- {
424
- id: 1,
425
- keyRingId: "sol-main",
426
- derivationIndex: 0,
427
- publicKey: "ab",
428
- address: "SolAddr0",
429
- assignedTo: null,
430
- },
431
- {
432
- id: 2,
433
- keyRingId: "sol-main",
434
- derivationIndex: 1,
435
- publicKey: "cd",
436
- address: "SolAddr1",
437
- assignedTo: null,
438
- },
439
- ]),
440
- };
441
- }),
442
- });
443
- db.select = selectMock;
444
- const deps = mockDeps();
445
- deps.adminToken = "test-admin";
446
- deps.db = db;
447
- // No registry — skip re-encoding validation
448
- deps.registry = undefined;
449
- const app = createKeyServerApp(deps);
450
- const res = await app.request("/admin/pool/replenish", {
451
- method: "POST",
452
- headers: { "Content-Type": "application/json", Authorization: "Bearer test-admin" },
453
- body: JSON.stringify({
454
- key_ring_id: "sol-main",
455
- plugin_id: "solana",
456
- encoding: "base58-solana",
457
- addresses: [
458
- { index: 0, public_key: "ab", address: "SolAddr0" },
459
- { index: 1, public_key: "cd", address: "SolAddr1" },
460
- ],
461
- }),
462
- });
463
- expect(res.status).toBe(201);
464
- const body = await res.json();
465
- expect(body.inserted).toBe(2);
466
- expect(body.total).toBe(2);
467
- });
468
- it("POST /admin/pool/replenish rejects mismatched address when encoder present", async () => {
469
- const mockEncoder = {
470
- encode: vi.fn().mockReturnValue("CorrectAddress"),
471
- encodingType: vi.fn().mockReturnValue("base58-solana"),
472
- };
473
- const mockPlugin = {
474
- pluginId: "solana",
475
- supportedCurve: "ed25519",
476
- encoders: { "base58-solana": mockEncoder },
477
- createWatcher: vi.fn(),
478
- createSweeper: vi.fn(),
479
- version: 1,
480
- };
481
- const mockRegistry = {
482
- get: vi.fn().mockReturnValue(mockPlugin),
483
- getOrThrow: vi.fn(),
484
- list: vi.fn(),
485
- register: vi.fn(),
486
- };
487
- const db = createPoolMockDb({
488
- keyRing: { id: "sol-main", derivationMode: "pre-derived" },
489
- });
490
- const deps = mockDeps();
491
- deps.adminToken = "test-admin";
492
- deps.db = db;
493
- deps.registry = mockRegistry;
494
- const app = createKeyServerApp(deps);
495
- const res = await app.request("/admin/pool/replenish", {
496
- method: "POST",
497
- headers: { "Content-Type": "application/json", Authorization: "Bearer test-admin" },
498
- body: JSON.stringify({
499
- key_ring_id: "sol-main",
500
- plugin_id: "solana",
501
- encoding: "base58-solana",
502
- addresses: [{ index: 0, public_key: "abcd1234", address: "WrongAddress" }],
503
- }),
504
- });
505
- expect(res.status).toBe(400);
506
- const body = await res.json();
507
- expect(body.error).toContain("Address mismatch");
508
- });
509
- it("GET /admin/pool/status returns pool stats", async () => {
510
- const db = createPoolMockDb({
511
- allKeyRings: [{ id: "sol-main" }],
512
- poolEntries: [
513
- { id: 1, keyRingId: "sol-main", derivationIndex: 0, publicKey: "a", address: "A", assignedTo: null },
514
- { id: 2, keyRingId: "sol-main", derivationIndex: 1, publicKey: "b", address: "B", assignedTo: "tenant:1" },
515
- { id: 3, keyRingId: "sol-main", derivationIndex: 2, publicKey: "c", address: "C", assignedTo: null },
516
- ],
517
- });
518
- // Override select to handle the two different query patterns in pool/status
519
- let selectCallCount = 0;
520
- const poolEntries = [
521
- { id: 1, keyRingId: "sol-main", derivationIndex: 0, publicKey: "a", address: "A", assignedTo: null },
522
- { id: 2, keyRingId: "sol-main", derivationIndex: 1, publicKey: "b", address: "B", assignedTo: "tenant:1" },
523
- { id: 3, keyRingId: "sol-main", derivationIndex: 2, publicKey: "c", address: "C", assignedTo: null },
524
- ];
525
- db.select = vi.fn().mockReturnValue({
526
- from: vi.fn().mockImplementation(() => {
527
- selectCallCount++;
528
- if (selectCallCount === 1) {
529
- // First call: select from keyRings (no where clause)
530
- return [{ id: "sol-main" }];
531
- }
532
- // Second call: select from addressPool where keyRingId = ring.id
533
- return {
534
- where: vi.fn().mockResolvedValue(poolEntries),
535
- };
536
- }),
537
- });
538
- const deps = mockDeps();
539
- deps.adminToken = "test-admin";
540
- deps.db = db;
541
- const app = createKeyServerApp(deps);
542
- const res = await app.request("/admin/pool/status", {
543
- headers: { Authorization: "Bearer test-admin" },
544
- });
545
- expect(res.status).toBe(200);
546
- const body = await res.json();
547
- expect(body.pools).toHaveLength(1);
548
- expect(body.pools[0].key_ring_id).toBe("sol-main");
549
- expect(body.pools[0].total).toBe(3);
550
- expect(body.pools[0].available).toBe(2);
551
- expect(body.pools[0].assigned).toBe(1);
552
- });
553
- it("POST /address uses pool for pre-derived key ring", async () => {
554
- const poolEntry = {
555
- id: 1,
556
- keyRingId: "sol-main",
557
- derivationIndex: 7,
558
- publicKey: "deadbeef",
559
- address: "SolanaAddr7",
560
- assignedTo: null,
561
- createdAt: "2026-01-01",
562
- };
563
- const solMethod = {
564
- id: "sol",
565
- type: "native",
566
- token: "SOL",
567
- chain: "solana",
568
- xpub: null,
569
- keyRingId: "sol-main",
570
- nextIndex: 0,
571
- decimals: 9,
572
- addressType: "base58-solana",
573
- encodingParams: "{}",
574
- watcherType: "solana",
575
- oracleAssetId: "solana",
576
- confirmations: 1,
577
- pluginId: "solana",
578
- encoding: "base58-solana",
579
- };
580
- const keyRing = {
581
- id: "sol-main",
582
- curve: "ed25519",
583
- derivationScheme: "slip10",
584
- derivationMode: "pre-derived",
585
- keyMaterial: "{}",
586
- coinType: 501,
587
- accountIndex: 0,
588
- };
589
- // Build a mock that handles the pool flow:
590
- // 1. select from paymentMethods where id=sol -> solMethod
591
- // 2. select from keyRings where id=sol-main -> keyRing
592
- // 3. transaction: select from addressPool -> poolEntry, update addressPool, insert derivedAddresses
593
- let selectCallCount = 0;
594
- const db = {
595
- select: vi.fn().mockImplementation(() => ({
596
- from: vi.fn().mockImplementation(() => {
597
- selectCallCount++;
598
- if (selectCallCount === 1) {
599
- // paymentMethods lookup
600
- return { where: vi.fn().mockResolvedValue([solMethod]) };
601
- }
602
- if (selectCallCount === 2) {
603
- // keyRings lookup
604
- return { where: vi.fn().mockResolvedValue([keyRing]) };
605
- }
606
- // addressPool query inside transaction
607
- return {
608
- where: vi.fn().mockReturnValue({
609
- orderBy: vi.fn().mockReturnValue({
610
- limit: vi.fn().mockResolvedValue([poolEntry]),
611
- }),
612
- }),
613
- };
614
- }),
615
- })),
616
- update: vi.fn().mockReturnValue({
617
- set: vi.fn().mockReturnValue({
618
- where: vi.fn().mockResolvedValue(undefined),
619
- }),
620
- }),
621
- insert: vi.fn().mockReturnValue({
622
- values: vi.fn().mockResolvedValue(undefined),
623
- }),
624
- transaction: vi.fn().mockImplementation(async (fn) => fn(db)),
625
- };
626
- const deps = mockDeps();
627
- deps.db = db;
628
- const app = createKeyServerApp(deps);
629
- const res = await app.request("/address", {
630
- method: "POST",
631
- headers: { "Content-Type": "application/json" },
632
- body: JSON.stringify({ chain: "sol" }),
633
- });
634
- expect(res.status).toBe(201);
635
- const body = await res.json();
636
- expect(body.address).toBe("SolanaAddr7");
637
- expect(body.index).toBe(7);
638
- expect(body.chain).toBe("solana");
639
- expect(body.token).toBe("SOL");
640
- });
641
- it("POST /address throws when pool is empty", async () => {
642
- const solMethod = {
643
- id: "sol",
644
- type: "native",
645
- token: "SOL",
646
- chain: "solana",
647
- xpub: null,
648
- keyRingId: "sol-main",
649
- nextIndex: 0,
650
- decimals: 9,
651
- addressType: "base58-solana",
652
- encodingParams: "{}",
653
- watcherType: "solana",
654
- oracleAssetId: "solana",
655
- confirmations: 1,
656
- pluginId: "solana",
657
- encoding: "base58-solana",
658
- };
659
- const keyRing = {
660
- id: "sol-main",
661
- curve: "ed25519",
662
- derivationScheme: "slip10",
663
- derivationMode: "pre-derived",
664
- keyMaterial: "{}",
665
- coinType: 501,
666
- accountIndex: 0,
667
- };
668
- let selectCallCount = 0;
669
- const db = {
670
- select: vi.fn().mockImplementation(() => ({
671
- from: vi.fn().mockImplementation(() => {
672
- selectCallCount++;
673
- if (selectCallCount === 1) {
674
- return { where: vi.fn().mockResolvedValue([solMethod]) };
675
- }
676
- if (selectCallCount === 2) {
677
- return { where: vi.fn().mockResolvedValue([keyRing]) };
678
- }
679
- // Empty pool
680
- return {
681
- where: vi.fn().mockReturnValue({
682
- orderBy: vi.fn().mockReturnValue({
683
- limit: vi.fn().mockResolvedValue([]),
684
- }),
685
- }),
686
- };
687
- }),
688
- })),
689
- transaction: vi.fn().mockImplementation(async (fn) => fn(db)),
690
- };
691
- const deps = mockDeps();
692
- deps.db = db;
693
- const app = createKeyServerApp(deps);
694
- const res = await app.request("/address", {
695
- method: "POST",
696
- headers: { "Content-Type": "application/json" },
697
- body: JSON.stringify({ chain: "sol" }),
698
- });
699
- // Hono catches the thrown error and returns 500
700
- expect(res.status).toBe(500);
701
- });
702
- });
703
- describe("key-server auth", () => {
704
- it("rejects unauthenticated request when serviceKey is set", async () => {
705
- const deps = mockDeps();
706
- deps.serviceKey = "sk-test-secret";
707
- const app = createKeyServerApp(deps);
708
- const res = await app.request("/address", {
709
- method: "POST",
710
- headers: { "Content-Type": "application/json" },
711
- body: JSON.stringify({ chain: "btc" }),
712
- });
713
- expect(res.status).toBe(401);
714
- });
715
- it("allows authenticated request with correct serviceKey", async () => {
716
- const deps = mockDeps();
717
- deps.serviceKey = "sk-test-secret";
718
- const app = createKeyServerApp(deps);
719
- const res = await app.request("/address", {
720
- method: "POST",
721
- headers: { "Content-Type": "application/json", Authorization: "Bearer sk-test-secret" },
722
- body: JSON.stringify({ chain: "btc" }),
723
- });
724
- expect(res.status).toBe(201);
725
- });
726
- it("rejects admin route without adminToken", async () => {
727
- const deps = mockDeps();
728
- // no adminToken set — admin routes disabled
729
- const app = createKeyServerApp(deps);
730
- const res = await app.request("/admin/next-path?coin_type=0");
731
- expect(res.status).toBe(403);
732
- });
733
- it("allows admin route with correct adminToken", async () => {
734
- const deps = mockDeps();
735
- deps.adminToken = "admin-secret";
736
- const app = createKeyServerApp(deps);
737
- const res = await app.request("/admin/next-path?coin_type=0", {
738
- headers: { Authorization: "Bearer admin-secret" },
739
- });
740
- expect(res.status).toBe(200);
741
- });
742
- });