@wopr-network/platform-core 1.66.1 → 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 -363
- 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 -348
- 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 -395
- 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 -444
- 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,363 +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 auth", () => {
|
|
325
|
-
it("rejects unauthenticated request when serviceKey is set", async () => {
|
|
326
|
-
const deps = mockDeps();
|
|
327
|
-
deps.serviceKey = "sk-test-secret";
|
|
328
|
-
const app = createKeyServerApp(deps);
|
|
329
|
-
const res = await app.request("/address", {
|
|
330
|
-
method: "POST",
|
|
331
|
-
headers: { "Content-Type": "application/json" },
|
|
332
|
-
body: JSON.stringify({ chain: "btc" }),
|
|
333
|
-
});
|
|
334
|
-
expect(res.status).toBe(401);
|
|
335
|
-
});
|
|
336
|
-
it("allows authenticated request with correct serviceKey", async () => {
|
|
337
|
-
const deps = mockDeps();
|
|
338
|
-
deps.serviceKey = "sk-test-secret";
|
|
339
|
-
const app = createKeyServerApp(deps);
|
|
340
|
-
const res = await app.request("/address", {
|
|
341
|
-
method: "POST",
|
|
342
|
-
headers: { "Content-Type": "application/json", Authorization: "Bearer sk-test-secret" },
|
|
343
|
-
body: JSON.stringify({ chain: "btc" }),
|
|
344
|
-
});
|
|
345
|
-
expect(res.status).toBe(201);
|
|
346
|
-
});
|
|
347
|
-
it("rejects admin route without adminToken", async () => {
|
|
348
|
-
const deps = mockDeps();
|
|
349
|
-
// no adminToken set — admin routes disabled
|
|
350
|
-
const app = createKeyServerApp(deps);
|
|
351
|
-
const res = await app.request("/admin/next-path?coin_type=0");
|
|
352
|
-
expect(res.status).toBe(403);
|
|
353
|
-
});
|
|
354
|
-
it("allows admin route with correct adminToken", async () => {
|
|
355
|
-
const deps = mockDeps();
|
|
356
|
-
deps.adminToken = "admin-secret";
|
|
357
|
-
const app = createKeyServerApp(deps);
|
|
358
|
-
const res = await app.request("/admin/next-path?coin_type=0", {
|
|
359
|
-
headers: { Authorization: "Bearer admin-secret" },
|
|
360
|
-
});
|
|
361
|
-
expect(res.status).toBe(200);
|
|
362
|
-
});
|
|
363
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { handlePayment } from "../watcher-service.js";
|
|
3
|
-
function mockChargeStore(overrides = {}) {
|
|
4
|
-
return {
|
|
5
|
-
getByDepositAddress: vi.fn().mockResolvedValue({
|
|
6
|
-
referenceId: "btc:test",
|
|
7
|
-
tenantId: "t1",
|
|
8
|
-
amountUsdCents: 5000,
|
|
9
|
-
creditedAt: null,
|
|
10
|
-
chain: "bitcoin",
|
|
11
|
-
depositAddress: "bc1qtest",
|
|
12
|
-
token: "BTC",
|
|
13
|
-
callbackUrl: "https://example.com/hook",
|
|
14
|
-
expectedAmount: "50000",
|
|
15
|
-
receivedAmount: "0",
|
|
16
|
-
confirmations: 0,
|
|
17
|
-
confirmationsRequired: 6,
|
|
18
|
-
...overrides,
|
|
19
|
-
}),
|
|
20
|
-
updateProgress: vi.fn().mockResolvedValue(undefined),
|
|
21
|
-
updateStatus: vi.fn().mockResolvedValue(undefined),
|
|
22
|
-
markCredited: vi.fn().mockResolvedValue(undefined),
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
function mockDb() {
|
|
26
|
-
return {
|
|
27
|
-
insert: vi.fn().mockReturnValue({ values: vi.fn().mockResolvedValue(undefined) }),
|
|
28
|
-
update: vi.fn().mockReturnValue({
|
|
29
|
-
set: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue(undefined) }),
|
|
30
|
-
}),
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
const noop = () => { };
|
|
34
|
-
describe("handlePayment", () => {
|
|
35
|
-
it("fires webhook with confirmations: 0 on first tx detection", async () => {
|
|
36
|
-
const chargeStore = mockChargeStore();
|
|
37
|
-
const db = mockDb();
|
|
38
|
-
const enqueuedPayloads = [];
|
|
39
|
-
db.insert = vi.fn().mockReturnValue({
|
|
40
|
-
values: vi.fn().mockImplementation((val) => {
|
|
41
|
-
if (val.payload)
|
|
42
|
-
enqueuedPayloads.push(JSON.parse(val.payload));
|
|
43
|
-
return Promise.resolve(undefined);
|
|
44
|
-
}),
|
|
45
|
-
});
|
|
46
|
-
await handlePayment(db, chargeStore, "bc1qtest", "50000", {
|
|
47
|
-
txHash: "abc123",
|
|
48
|
-
confirmations: 0,
|
|
49
|
-
confirmationsRequired: 6,
|
|
50
|
-
amountReceivedCents: 5000,
|
|
51
|
-
}, noop);
|
|
52
|
-
expect(enqueuedPayloads).toHaveLength(1);
|
|
53
|
-
expect(enqueuedPayloads[0]).toMatchObject({
|
|
54
|
-
chargeId: "btc:test",
|
|
55
|
-
status: "partial",
|
|
56
|
-
confirmations: 0,
|
|
57
|
-
confirmationsRequired: 6,
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
it("fires webhook on each confirmation increment", async () => {
|
|
61
|
-
const chargeStore = mockChargeStore({ confirmations: 2 });
|
|
62
|
-
const db = mockDb();
|
|
63
|
-
const enqueuedPayloads = [];
|
|
64
|
-
db.insert = vi.fn().mockReturnValue({
|
|
65
|
-
values: vi.fn().mockImplementation((val) => {
|
|
66
|
-
if (val.payload)
|
|
67
|
-
enqueuedPayloads.push(JSON.parse(val.payload));
|
|
68
|
-
return Promise.resolve(undefined);
|
|
69
|
-
}),
|
|
70
|
-
});
|
|
71
|
-
await handlePayment(db, chargeStore, "bc1qtest", "0", // no additional payment, just confirmation update
|
|
72
|
-
{
|
|
73
|
-
txHash: "abc123",
|
|
74
|
-
confirmations: 3,
|
|
75
|
-
confirmationsRequired: 6,
|
|
76
|
-
amountReceivedCents: 5000,
|
|
77
|
-
}, noop);
|
|
78
|
-
expect(enqueuedPayloads).toHaveLength(1);
|
|
79
|
-
expect(enqueuedPayloads[0]).toMatchObject({
|
|
80
|
-
status: "partial",
|
|
81
|
-
confirmations: 3,
|
|
82
|
-
confirmationsRequired: 6,
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
it("fires final webhook with status confirmed at threshold", async () => {
|
|
86
|
-
const chargeStore = mockChargeStore({
|
|
87
|
-
receivedAmount: "50000",
|
|
88
|
-
confirmations: 5,
|
|
89
|
-
});
|
|
90
|
-
const db = mockDb();
|
|
91
|
-
const enqueuedPayloads = [];
|
|
92
|
-
db.insert = vi.fn().mockReturnValue({
|
|
93
|
-
values: vi.fn().mockImplementation((val) => {
|
|
94
|
-
if (val.payload)
|
|
95
|
-
enqueuedPayloads.push(JSON.parse(val.payload));
|
|
96
|
-
return Promise.resolve(undefined);
|
|
97
|
-
}),
|
|
98
|
-
});
|
|
99
|
-
await handlePayment(db, chargeStore, "bc1qtest", "0", {
|
|
100
|
-
txHash: "abc123",
|
|
101
|
-
confirmations: 6,
|
|
102
|
-
confirmationsRequired: 6,
|
|
103
|
-
amountReceivedCents: 5000,
|
|
104
|
-
}, noop);
|
|
105
|
-
expect(enqueuedPayloads).toHaveLength(1);
|
|
106
|
-
expect(enqueuedPayloads[0]).toMatchObject({
|
|
107
|
-
status: "confirmed",
|
|
108
|
-
confirmations: 6,
|
|
109
|
-
confirmationsRequired: 6,
|
|
110
|
-
});
|
|
111
|
-
expect(chargeStore.markCredited).toHaveBeenCalledOnce();
|
|
112
|
-
});
|
|
113
|
-
it("all webhooks use canonical status values only", async () => {
|
|
114
|
-
const chargeStore = mockChargeStore();
|
|
115
|
-
const db = mockDb();
|
|
116
|
-
const enqueuedPayloads = [];
|
|
117
|
-
db.insert = vi.fn().mockReturnValue({
|
|
118
|
-
values: vi.fn().mockImplementation((val) => {
|
|
119
|
-
if (val.payload)
|
|
120
|
-
enqueuedPayloads.push(JSON.parse(val.payload));
|
|
121
|
-
return Promise.resolve(undefined);
|
|
122
|
-
}),
|
|
123
|
-
});
|
|
124
|
-
await handlePayment(db, chargeStore, "bc1qtest", "50000", {
|
|
125
|
-
txHash: "abc123",
|
|
126
|
-
confirmations: 0,
|
|
127
|
-
confirmationsRequired: 6,
|
|
128
|
-
amountReceivedCents: 5000,
|
|
129
|
-
}, noop);
|
|
130
|
-
const validStatuses = ["pending", "partial", "confirmed", "expired", "failed"];
|
|
131
|
-
for (const payload of enqueuedPayloads) {
|
|
132
|
-
expect(validStatuses).toContain(payload.status);
|
|
133
|
-
}
|
|
134
|
-
// Must NEVER contain legacy statuses
|
|
135
|
-
for (const payload of enqueuedPayloads) {
|
|
136
|
-
expect(payload.status).not.toBe("Settled");
|
|
137
|
-
expect(payload.status).not.toBe("Processing");
|
|
138
|
-
expect(payload.status).not.toBe("New");
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
it("updates charge progress via updateProgress()", async () => {
|
|
142
|
-
const chargeStore = mockChargeStore();
|
|
143
|
-
const db = mockDb();
|
|
144
|
-
db.insert = vi.fn().mockReturnValue({
|
|
145
|
-
values: vi.fn().mockResolvedValue(undefined),
|
|
146
|
-
});
|
|
147
|
-
await handlePayment(db, chargeStore, "bc1qtest", "25000", {
|
|
148
|
-
txHash: "abc123",
|
|
149
|
-
confirmations: 2,
|
|
150
|
-
confirmationsRequired: 6,
|
|
151
|
-
amountReceivedCents: 2500,
|
|
152
|
-
}, noop);
|
|
153
|
-
expect(chargeStore.updateProgress).toHaveBeenCalledWith("btc:test", {
|
|
154
|
-
status: "partial",
|
|
155
|
-
amountReceivedCents: 2500,
|
|
156
|
-
confirmations: 2,
|
|
157
|
-
confirmationsRequired: 6,
|
|
158
|
-
txHash: "abc123",
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
it("skips already-credited charges", async () => {
|
|
162
|
-
const chargeStore = mockChargeStore({ creditedAt: "2026-01-01" });
|
|
163
|
-
const db = mockDb();
|
|
164
|
-
await handlePayment(db, chargeStore, "bc1qtest", "50000", { txHash: "abc123", confirmations: 6, confirmationsRequired: 6, amountReceivedCents: 5000 }, noop);
|
|
165
|
-
expect(chargeStore.updateProgress).not.toHaveBeenCalled();
|
|
166
|
-
});
|
|
167
|
-
it("skips unknown addresses", async () => {
|
|
168
|
-
const chargeStore = mockChargeStore();
|
|
169
|
-
chargeStore.getByDepositAddress.mockResolvedValue(null);
|
|
170
|
-
const db = mockDb();
|
|
171
|
-
await handlePayment(db, chargeStore, "bc1qunknown", "50000", { txHash: "abc123", confirmations: 0, confirmationsRequired: 6, amountReceivedCents: 5000 }, noop);
|
|
172
|
-
expect(chargeStore.updateProgress).not.toHaveBeenCalled();
|
|
173
|
-
});
|
|
174
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export interface EncodingParams {
|
|
2
|
-
/** Bech32 human-readable prefix (e.g. "bc", "ltc", "tb"). */
|
|
3
|
-
hrp?: string;
|
|
4
|
-
/** Base58Check version byte as hex string (e.g. "0x1e" for DOGE, "0x41" for TRON). */
|
|
5
|
-
version?: string;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Derive a deposit address from an xpub at a given BIP-44 index.
|
|
9
|
-
* Path: xpub / 0 / index (external chain).
|
|
10
|
-
* No private keys involved.
|
|
11
|
-
*
|
|
12
|
-
* @param xpub - Extended public key
|
|
13
|
-
* @param index - Derivation index (0, 1, 2, ...)
|
|
14
|
-
* @param addressType - Encoding type: "bech32", "p2pkh", "evm"
|
|
15
|
-
* @param params - Chain-specific encoding params from DB (parsed JSON)
|
|
16
|
-
*/
|
|
17
|
-
export declare function deriveAddress(xpub: string, index: number, addressType: string, params?: EncodingParams): string;
|
|
18
|
-
/**
|
|
19
|
-
* Derive the treasury address (internal chain, index 0).
|
|
20
|
-
* Used for sweep destinations.
|
|
21
|
-
*/
|
|
22
|
-
export declare function deriveTreasury(xpub: string, addressType: string, params?: EncodingParams): string;
|
|
23
|
-
/** Validate that a string is an xpub (not xprv). */
|
|
24
|
-
export declare function isValidXpub(key: string): boolean;
|