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