aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-24 13:18:42 -0800
committerAdam Soltys <[email protected]>2026-02-24 13:18:42 -0800
commitad7b09b43323d0ffc0a8ddabb43e4838a184c697 (patch)
treefbf2a59f81771b2938f474c8e7b35a497d2960e4 /test
parent9e97e040617bfa0b40150c0eba298bf186bbeeaf (diff)
downloadcoinos-server-ad7b09b43323d0ffc0a8ddabb43e4838a184c697.tar.gz
coinos-server-ad7b09b43323d0ffc0a8ddabb43e4838a184c697.zip
formatting
Diffstat (limited to 'test')
-rw-r--r--test/forward.test.ts211
-rw-r--r--test/integration/ark-reconcile.test.ts20
-rw-r--r--test/integration/bolt12.test.ts35
-rw-r--r--test/onchain.test.ts128
-rw-r--r--test/preload.ts639
5 files changed, 623 insertions, 410 deletions
diff --git a/test/forward.test.ts b/test/forward.test.ts
index 692c5b1e..cae988b7 100644
--- a/test/forward.test.ts
+++ b/test/forward.test.ts
@@ -111,9 +111,13 @@ const resetAll = () => {
const makeRes = () => {
let sent: any;
return {
- send: (data: any) => { sent = data; },
+ send: (data: any) => {
+ sent = data;
+ },
code: (code: number) => ({
- send: (data: any) => { sent = data; },
+ send: (data: any) => {
+ sent = data;
+ },
}),
getSent: () => sent,
};
@@ -129,7 +133,13 @@ describe("completePayment", () => {
test("forwards payment and debit stays in custodial", async () => {
const user = makeUser();
const inv = makeInvoice({ aid: arkAccountId, forward: bolt11 });
- const p = { amount: 1000, confirmed: true, type: "ark", uid: custodialUid, created: Date.now() };
+ const p = {
+ amount: 1000,
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ created: Date.now(),
+ };
await completePayment(inv, p, user);
@@ -141,7 +151,13 @@ describe("completePayment", () => {
test("forwards and debit stays in custodial when aid matches user", async () => {
const user = makeUser();
const inv = makeInvoice({ aid: custodialUid, forward: bolt11 });
- const p = { amount: 1000, confirmed: true, type: "ark", uid: custodialUid, created: Date.now() };
+ const p = {
+ amount: 1000,
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ created: Date.now(),
+ };
await completePayment(inv, p, user);
@@ -152,9 +168,17 @@ describe("completePayment", () => {
test("handles forward failure gracefully", async () => {
const user = makeUser();
const inv = makeInvoice({ aid: arkAccountId, forward: bolt11 });
- const p = { amount: 1000, confirmed: true, type: "ark", uid: custodialUid, created: Date.now() };
+ const p = {
+ amount: 1000,
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ created: Date.now(),
+ };
- mockLnXpay.mockImplementationOnce(async () => { throw new Error("route not found"); });
+ mockLnXpay.mockImplementationOnce(async () => {
+ throw new Error("route not found");
+ });
const w = await completePayment(inv, p, user);
@@ -166,7 +190,13 @@ describe("completePayment", () => {
test("skips forward when already forwarded", async () => {
const user = makeUser();
const inv = makeInvoice({ aid: arkAccountId, forward: bolt11, forwarded: true });
- const p = { amount: 1000, confirmed: true, type: "ark", uid: custodialUid, created: Date.now() };
+ const p = {
+ amount: 1000,
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ created: Date.now(),
+ };
const w = await completePayment(inv, p, user);
@@ -177,7 +207,13 @@ describe("completePayment", () => {
test("skips forward when payment not confirmed", async () => {
const user = makeUser();
const inv = makeInvoice({ aid: arkAccountId, forward: bolt11 });
- const p = { amount: 1000, confirmed: false, type: "ark", uid: custodialUid, created: Date.now() };
+ const p = {
+ amount: 1000,
+ confirmed: false,
+ type: "ark",
+ uid: custodialUid,
+ created: Date.now(),
+ };
const w = await completePayment(inv, p, user);
@@ -251,7 +287,9 @@ describe("arkReceive", () => {
const inv = makeInvoice({ aid: arkAccountId, forward: bolt11 });
seedInvoice(inv);
- mockLnXpay.mockImplementationOnce(async () => { throw new Error("no route"); });
+ mockLnXpay.mockImplementationOnce(async () => {
+ throw new Error("no route");
+ });
const req = {
body: { iid: inv.id, amount: 1000, hash: "ark-txid-777" },
@@ -353,14 +391,15 @@ describe("arkSync", () => {
// Pre-store a known tx hash and its sync lock
store().kvStore[`arksync:${arkAccountId}:known-hash`] = "1";
store().kvStore[`payment:${arkAccountId}:known-hash`] = JSON.stringify("existing-id");
- store().kvStore["payment:existing-id"] = JSON.stringify({ id: "existing-id", confirmed: false });
+ store().kvStore["payment:existing-id"] = JSON.stringify({
+ id: "existing-id",
+ confirmed: false,
+ });
const req = {
body: {
aid: arkAccountId,
- transactions: [
- { hash: "known-hash", amount: -1000, settled: true, createdAt: Date.now() },
- ],
+ transactions: [{ hash: "known-hash", amount: -1000, settled: true, createdAt: Date.now() }],
},
user: makeUser(),
};
@@ -385,8 +424,30 @@ describe("arkSync reconciliation", () => {
test("creates expired debit when payment sum exceeds wallet balance", async () => {
// Pre-seed two received payments totaling 7128
- const pay1 = { id: "pay-1", aid: arkAccountId, amount: 5000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() - 2000 };
- const pay2 = { id: "pay-2", aid: arkAccountId, amount: 2128, hash: "h2", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() - 1000 };
+ const pay1 = {
+ id: "pay-1",
+ aid: arkAccountId,
+ amount: 5000,
+ hash: "h1",
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ rate: 50000,
+ currency: "USD",
+ created: Date.now() - 2000,
+ };
+ const pay2 = {
+ id: "pay-2",
+ aid: arkAccountId,
+ amount: 2128,
+ hash: "h2",
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ rate: 50000,
+ currency: "USD",
+ created: Date.now() - 1000,
+ };
store().kvStore[`payment:pay-1`] = JSON.stringify(pay1);
store().kvStore[`payment:pay-2`] = JSON.stringify(pay2);
store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1");
@@ -421,7 +482,18 @@ describe("arkSync reconciliation", () => {
test("does not reconcile when sum matches balance", async () => {
// Pre-seed a payment of 5000
- const pay1 = { id: "pay-1", aid: arkAccountId, amount: 5000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() };
+ const pay1 = {
+ id: "pay-1",
+ aid: arkAccountId,
+ amount: 5000,
+ hash: "h1",
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ rate: 50000,
+ currency: "USD",
+ created: Date.now(),
+ };
store().kvStore[`payment:pay-1`] = JSON.stringify(pay1);
store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1");
store().listStore[`${arkAccountId}:payments`] = ["pay-1"];
@@ -445,7 +517,18 @@ describe("arkSync reconciliation", () => {
});
test("does not reconcile when balance is not provided (backward compat)", async () => {
- const pay1 = { id: "pay-1", aid: arkAccountId, amount: 5000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() };
+ const pay1 = {
+ id: "pay-1",
+ aid: arkAccountId,
+ amount: 5000,
+ hash: "h1",
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ rate: 50000,
+ currency: "USD",
+ created: Date.now(),
+ };
store().kvStore[`payment:pay-1`] = JSON.stringify(pay1);
store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1");
store().listStore[`${arkAccountId}:payments`] = ["pay-1"];
@@ -468,7 +551,18 @@ describe("arkSync reconciliation", () => {
test("is idempotent — second sync does not create duplicate", async () => {
// Seed payment sum = 5000, balance = 0
- const pay1 = { id: "pay-1", aid: arkAccountId, amount: 5000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() };
+ const pay1 = {
+ id: "pay-1",
+ aid: arkAccountId,
+ amount: 5000,
+ hash: "h1",
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ rate: 50000,
+ currency: "USD",
+ created: Date.now(),
+ };
store().kvStore[`payment:pay-1`] = JSON.stringify(pay1);
store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1");
store().listStore[`${arkAccountId}:payments`] = ["pay-1"];
@@ -495,8 +589,30 @@ describe("arkSync reconciliation", () => {
test("reconciles partial expiry", async () => {
// 10000 received, 3000 sent, sum = 7000. Actual balance = 2000 (5000 expired)
- const pay1 = { id: "pay-1", aid: arkAccountId, amount: 10000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() };
- const pay2 = { id: "pay-2", aid: arkAccountId, amount: -3000, hash: "h2", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() };
+ const pay1 = {
+ id: "pay-1",
+ aid: arkAccountId,
+ amount: 10000,
+ hash: "h1",
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ rate: 50000,
+ currency: "USD",
+ created: Date.now(),
+ };
+ const pay2 = {
+ id: "pay-2",
+ aid: arkAccountId,
+ amount: -3000,
+ hash: "h2",
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ rate: 50000,
+ currency: "USD",
+ created: Date.now(),
+ };
store().kvStore[`payment:pay-1`] = JSON.stringify(pay1);
store().kvStore[`payment:pay-2`] = JSON.stringify(pay2);
store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1");
@@ -519,7 +635,18 @@ describe("arkSync reconciliation", () => {
test("does not reconcile when sum is less than balance", async () => {
// Sum = 3000, balance = 5000 (e.g., recovered VTXOs already synced)
- const pay1 = { id: "pay-1", aid: arkAccountId, amount: 3000, hash: "h1", confirmed: true, type: "ark", uid: custodialUid, rate: 50000, currency: "USD", created: Date.now() };
+ const pay1 = {
+ id: "pay-1",
+ aid: arkAccountId,
+ amount: 3000,
+ hash: "h1",
+ confirmed: true,
+ type: "ark",
+ uid: custodialUid,
+ rate: 50000,
+ currency: "USD",
+ created: Date.now(),
+ };
store().kvStore[`payment:pay-1`] = JSON.stringify(pay1);
store().kvStore[`payment:${arkAccountId}:h1`] = JSON.stringify("pay-1");
store().listStore[`${arkAccountId}:payments`] = ["pay-1"];
@@ -568,7 +695,13 @@ describe("BOLT12 autowithdraw", () => {
seedUser(user);
const inv = makeInvoice({ type: "lightning", amount: 1000 });
- const p = { amount: 1000, confirmed: true, type: "lightning", uid: custodialUid, created: Date.now() };
+ const p = {
+ amount: 1000,
+ confirmed: true,
+ type: "lightning",
+ uid: custodialUid,
+ created: Date.now(),
+ };
await completePayment(inv, p, user);
@@ -602,7 +735,13 @@ describe("BOLT12 autowithdraw", () => {
const user = makeUser();
const inv = makeInvoice({ forward: bolt12Offer });
- const p = { amount: 1000, confirmed: true, type: "lightning", uid: custodialUid, created: Date.now() };
+ const p = {
+ amount: 1000,
+ confirmed: true,
+ type: "lightning",
+ uid: custodialUid,
+ created: Date.now(),
+ };
await completePayment(inv, p, user);
@@ -621,7 +760,13 @@ describe("BOLT12 autowithdraw", () => {
seedUser(user);
const inv = makeInvoice({ type: "lightning", amount: 1000 });
- const p = { amount: 1000, confirmed: true, type: "lightning", uid: custodialUid, created: Date.now() };
+ const p = {
+ amount: 1000,
+ confirmed: true,
+ type: "lightning",
+ uid: custodialUid,
+ created: Date.now(),
+ };
await completePayment(inv, p, user);
@@ -642,10 +787,12 @@ describe("BOLT12 autowithdraw", () => {
// Simulate a route with 50 sat routing fee (getroutes response format)
mockLnGetroutes.mockImplementation(async () => ({
- routes: [{
- amount_msat: balance * 1000,
- path: [{ amount_msat: (balance + routingFee) * 1000 }],
- }],
+ routes: [
+ {
+ amount_msat: balance * 1000,
+ path: [{ amount_msat: (balance + routingFee) * 1000 }],
+ },
+ ],
}));
mockLnFetchinvoice.mockImplementation(async () => ({
@@ -661,7 +808,13 @@ describe("BOLT12 autowithdraw", () => {
seedUser(user);
const inv = makeInvoice({ type: "lightning", amount: 1000 });
- const p = { amount: 1000, confirmed: true, type: "lightning", uid: custodialUid, created: Date.now() };
+ const p = {
+ amount: 1000,
+ confirmed: true,
+ type: "lightning",
+ uid: custodialUid,
+ created: Date.now(),
+ };
await completePayment(inv, p, user);
diff --git a/test/integration/ark-reconcile.test.ts b/test/integration/ark-reconcile.test.ts
index 5e2061e6..84825475 100644
--- a/test/integration/ark-reconcile.test.ts
+++ b/test/integration/ark-reconcile.test.ts
@@ -126,9 +126,7 @@ describe("ark sync reconciliation", () => {
method: "POST",
body: JSON.stringify({
aid: arkAccountId,
- transactions: [
- { arkTxid: hash, amount: 3000, settled: true, createdAt: Date.now() },
- ],
+ transactions: [{ arkTxid: hash, amount: 3000, settled: true, createdAt: Date.now() }],
balance: 13000,
}),
});
@@ -138,9 +136,7 @@ describe("ark sync reconciliation", () => {
method: "POST",
body: JSON.stringify({
aid: arkAccountId,
- transactions: [
- { arkTxid: hash, amount: 3000, settled: true, createdAt: Date.now() },
- ],
+ transactions: [{ arkTxid: hash, amount: 3000, settled: true, createdAt: Date.now() }],
balance: 13000,
}),
});
@@ -192,9 +188,7 @@ describe("ark sync reconciliation", () => {
method: "POST",
body: JSON.stringify({
aid: arkAccountId,
- transactions: [
- { arkTxid: hash, amount: 5000, settled: true, createdAt: Date.now() },
- ],
+ transactions: [{ arkTxid: hash, amount: 5000, settled: true, createdAt: Date.now() }],
balance: 5000,
}),
});
@@ -204,9 +198,7 @@ describe("ark sync reconciliation", () => {
// Sum is now 5000 (13000 - 13000 + 5000), matches balance=5000
// No reconciliation needed
- expect(
- result.payments.filter((p: any) => p.memo === "expired"),
- ).toHaveLength(0);
+ expect(result.payments.filter((p: any) => p.memo === "expired")).toHaveLength(0);
});
test("partial expiry creates correct debit", async () => {
@@ -240,9 +232,7 @@ describe("ark sync reconciliation", () => {
method: "POST",
body: JSON.stringify({
aid: arkAccountId,
- transactions: [
- { arkTxid: hash, amount: 2000, settled: true, createdAt: Date.now() },
- ],
+ transactions: [{ arkTxid: hash, amount: 2000, settled: true, createdAt: Date.now() }],
balance: 4000, // previous 2000 + new 2000
}),
});
diff --git a/test/integration/bolt12.test.ts b/test/integration/bolt12.test.ts
index 06929c6c..24aa53e0 100644
--- a/test/integration/bolt12.test.ts
+++ b/test/integration/bolt12.test.ts
@@ -24,9 +24,7 @@ const exec = async (cmd: string): Promise<string> => {
const clExec = async (container: string, ...args: string[]): Promise<any> => {
const escaped = args.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
- const result = await exec(
- `docker exec ${container} lightning-cli ${escaped}`,
- );
+ const result = await exec(`docker exec ${container} lightning-cli ${escaped}`);
try {
return JSON.parse(result);
} catch {
@@ -35,18 +33,11 @@ const clExec = async (container: string, ...args: string[]): Promise<any> => {
};
const mine = async (n: number) => {
- const addr = await exec(
- "docker exec bc bitcoin-cli -regtest -rpcwallet=coinos getnewaddress",
- );
- await exec(
- `docker exec bc bitcoin-cli -regtest generatetoaddress ${n} ${addr}`,
- );
+ const addr = await exec("docker exec bc bitcoin-cli -regtest -rpcwallet=coinos getnewaddress");
+ await exec(`docker exec bc bitcoin-cli -regtest generatetoaddress ${n} ${addr}`);
};
-const waitFor = async <T>(
- fn: () => Promise<T>,
- timeout = 30000,
-): Promise<T> => {
+const waitFor = async <T>(fn: () => Promise<T>, timeout = 30000): Promise<T> => {
const start = Date.now();
let lastError: any;
while (Date.now() - start < timeout) {
@@ -185,11 +176,7 @@ describe("BOLT12 autowithdraw", () => {
// Send 100k sats from funder to withdrawer (triggers autowithdraw)
const sendAmount = 100_000;
- const sendResult = await sendInternal(
- funderToken,
- withdrawerName,
- sendAmount,
- );
+ const sendResult = await sendInternal(funderToken, withdrawerName, sendAmount);
expect(sendResult.amount).toBe(-sendAmount);
// Wait for autowithdraw to complete — withdrawer balance should drop near 0
@@ -210,9 +197,7 @@ describe("BOLT12 autowithdraw", () => {
// Check payment records on withdrawer
const payments = await getPayments(withdrawerToken);
- const withdrawal = payments.payments?.find(
- (p: any) => p.amount < 0 && p.type === "lightning",
- );
+ const withdrawal = payments.payments?.find((p: any) => p.amount < 0 && p.type === "lightning");
expect(withdrawal).toBeTruthy();
expect(withdrawal.fee).toBeGreaterThanOrEqual(0);
}, 30000);
@@ -259,9 +244,7 @@ describe("BOLT12 autowithdraw", () => {
// Check payment record has routing fee
const payments = await getPayments(withdrawerToken);
- const withdrawal = payments.payments?.find(
- (p: any) => p.amount < 0 && p.type === "lightning",
- );
+ const withdrawal = payments.payments?.find((p: any) => p.amount < 0 && p.type === "lightning");
expect(withdrawal).toBeTruthy();
// Multi-hop should have non-zero routing fee
expect(withdrawal.fee).toBeGreaterThan(0);
@@ -290,9 +273,7 @@ describe("BOLT12 autowithdraw", () => {
// Get the withdrawal payment record
const payments = await getPayments(withdrawerToken);
- const withdrawal = payments.payments?.find(
- (p: any) => p.amount < 0 && p.type === "lightning",
- );
+ const withdrawal = payments.payments?.find((p: any) => p.amount < 0 && p.type === "lightning");
expect(withdrawal).toBeTruthy();
// For a direct peer, actual routing fee should be 0 or very small
diff --git a/test/onchain.test.ts b/test/onchain.test.ts
index eae77544..e1ebe608 100644
--- a/test/onchain.test.ts
+++ b/test/onchain.test.ts
@@ -102,9 +102,13 @@ const resetAll = () => {
const makeRes = () => {
let sent: any;
return {
- send: (data: any) => { sent = data; },
+ send: (data: any) => {
+ sent = data;
+ },
code: (code: number) => ({
- send: (data: any) => { sent = data; },
+ send: (data: any) => {
+ sent = data;
+ },
}),
getSent: () => sent,
};
@@ -339,13 +343,15 @@ describe("confirm (liquid)", () => {
store().rpcOverride = {
getTransaction: async () => ({
confirmations: 0,
- details: [{
- address: lqAddress,
- amount: 0.0005,
- asset: "test-asset",
- category: "receive",
- vout: 0,
- }],
+ details: [
+ {
+ address: lqAddress,
+ amount: 0.0005,
+ asset: "test-asset",
+ category: "receive",
+ vout: 0,
+ },
+ ],
}),
};
@@ -384,13 +390,15 @@ describe("confirm (liquid)", () => {
store().rpcOverride = {
getTransaction: async () => ({
confirmations: 2,
- details: [{
- address: lqAddress,
- amount: 0.0005,
- asset: "test-asset",
- category: "receive",
- vout: 0,
- }],
+ details: [
+ {
+ address: lqAddress,
+ amount: 0.0005,
+ asset: "test-asset",
+ category: "receive",
+ vout: 0,
+ },
+ ],
}),
};
@@ -418,13 +426,15 @@ describe("confirm (liquid)", () => {
store().rpcOverride = {
getTransaction: async () => ({
confirmations: 1,
- details: [{
- address: lqAddress,
- amount: 0.0005,
- asset: "test-asset",
- category: "send",
- vout: 0,
- }],
+ details: [
+ {
+ address: lqAddress,
+ amount: 0.0005,
+ asset: "test-asset",
+ category: "send",
+ vout: 0,
+ },
+ ],
}),
};
@@ -442,13 +452,15 @@ describe("confirm (liquid)", () => {
store().rpcOverride = {
getTransaction: async () => ({
confirmations: 0,
- details: [{
- address: lqAddress,
- amount: 0.001,
- asset: "wrong-asset-id",
- category: "receive",
- vout: 0,
- }],
+ details: [
+ {
+ address: lqAddress,
+ amount: 0.001,
+ asset: "wrong-asset-id",
+ category: "receive",
+ vout: 0,
+ },
+ ],
}),
};
@@ -471,13 +483,15 @@ describe("confirm (liquid)", () => {
store().rpcOverride = {
getTransaction: async () => ({
confirmations: 0,
- details: [{
- address: lqAddress,
- amount: 0.0005,
- asset: "test-asset",
- category: "receive",
- vout: 0,
- }],
+ details: [
+ {
+ address: lqAddress,
+ amount: 0.0005,
+ asset: "test-asset",
+ category: "receive",
+ vout: 0,
+ },
+ ],
}),
};
@@ -508,13 +522,15 @@ describe("confirm (liquid)", () => {
store().rpcOverride = {
getTransaction: async () => ({
confirmations: 3,
- details: [{
- address: lqAddress,
- amount: 0.0005,
- asset: "test-asset",
- category: "receive",
- vout: 0,
- }],
+ details: [
+ {
+ address: lqAddress,
+ amount: 0.0005,
+ asset: "test-asset",
+ category: "receive",
+ vout: 0,
+ },
+ ],
}),
};
@@ -542,11 +558,13 @@ describe("catchUp", () => {
store().esploraOverride = {
addressTxs: {
- [address]: [{
- txid: "missed-tx-001",
- vout: [{ scriptpubkey_address: address, value: 40000 }],
- status: { confirmed: false },
- }],
+ [address]: [
+ {
+ txid: "missed-tx-001",
+ vout: [{ scriptpubkey_address: address, value: 40000 }],
+ status: { confirmed: false },
+ },
+ ],
},
};
@@ -569,11 +587,13 @@ describe("catchUp", () => {
store().esploraOverride = {
addressTxs: {
- [address]: [{
- txid: "missed-tx-002",
- vout: [{ scriptpubkey_address: address, value: 60000 }],
- status: { confirmed: true },
- }],
+ [address]: [
+ {
+ txid: "missed-tx-002",
+ vout: [{ scriptpubkey_address: address, value: 60000 }],
+ status: { confirmed: true },
+ },
+ ],
},
};
diff --git a/test/preload.ts b/test/preload.ts
index a3d3cbb9..01629d97 100644
--- a/test/preload.ts
+++ b/test/preload.ts
@@ -7,307 +7,376 @@ if (process.env.INTEGRATION) {
// Skip all mocking — use real services
globalThis.__testStore = { kvStore: {}, listStore: {}, setStore: {} };
} else {
+ let kvStore: Record<string, string> = {};
+ let listStore: Record<string, string[]> = {};
+ let setStore: Record<string, Set<string>> = {};
-let kvStore: Record<string, string> = {};
-let listStore: Record<string, string[]> = {};
-let setStore: Record<string, Set<string>> = {};
+ globalThis.__testStore = { kvStore, listStore, setStore };
-globalThis.__testStore = { kvStore, listStore, setStore };
-
-const makeMulti = () => {
- const ops: Array<() => Promise<any>> = [];
- const chain: any = {
- set: (k: string, v: string) => {
- ops.push(async () => { globalThis.__testStore.kvStore[k] = v; });
- return chain;
- },
- lPush: (k: string, v: string) => {
- ops.push(async () => {
- const ls = globalThis.__testStore.listStore;
- if (!ls[k]) ls[k] = [];
- ls[k].unshift(v);
- });
- return chain;
- },
- del: (k: string) => {
- ops.push(async () => { delete globalThis.__testStore.kvStore[k]; });
- return chain;
- },
- lRem: (k: string, _count: number, v: string) => {
- ops.push(async () => {
- const ls = globalThis.__testStore.listStore;
- if (ls[k]) {
- const idx = ls[k].indexOf(v);
- if (idx >= 0) ls[k].splice(idx, 1);
- }
- });
- return chain;
- },
- sRem: (k: string, v: string) => {
- ops.push(async () => globalThis.__testStore.setStore[k]?.delete(v));
- return chain;
- },
- exec: async () => {
- for (const op of ops) await op();
- return [];
- },
- };
- return chain;
-};
-
-mock.module("$lib/db", () => {
- const kv = () => globalThis.__testStore.kvStore;
- const ls = () => globalThis.__testStore.listStore;
- const ss = () => globalThis.__testStore.setStore;
- const g = async (k: string) => {
- const v = kv()[k] ?? null;
- if (v === null) return null;
- try { return JSON.parse(v); } catch { return v; }
- };
- const s = (k: string, v: any) => {
- if (k === "user:null" || k === "user:undefined") throw new Error("null user");
- kv()[k] = JSON.stringify(v);
- };
- const gf = async (k: string) => {
- const v = kv()[k] ?? null;
- if (v === null) return null;
- try { return JSON.parse(v); } catch { return v; }
- };
- return {
- db: {
- get: async (k: string) => kv()[k] ?? null,
- set: async (k: string, v: string, opts?: any) => {
- if (opts?.NX && kv()[k] !== undefined) return null;
- kv()[k] = v;
- return "OK";
+ const makeMulti = () => {
+ const ops: Array<() => Promise<any>> = [];
+ const chain: any = {
+ set: (k: string, v: string) => {
+ ops.push(async () => {
+ globalThis.__testStore.kvStore[k] = v;
+ });
+ return chain;
},
- del: async (k: string) => { delete kv()[k]; return 1; },
- exists: async (k: string) => (kv()[k] !== undefined ? 1 : 0),
- lPush: async (k: string, v: string) => {
- if (!ls()[k]) ls()[k] = [];
- ls()[k].unshift(v);
- return ls()[k].length;
+ lPush: (k: string, v: string) => {
+ ops.push(async () => {
+ const ls = globalThis.__testStore.listStore;
+ if (!ls[k]) ls[k] = [];
+ ls[k].unshift(v);
+ });
+ return chain;
},
- lRem: async (k: string, _count: number, v: string) => {
- if (!ls()[k]) return 0;
- const idx = ls()[k].indexOf(v);
- if (idx >= 0) { ls()[k].splice(idx, 1); return 1; }
- return 0;
+ del: (k: string) => {
+ ops.push(async () => {
+ delete globalThis.__testStore.kvStore[k];
+ });
+ return chain;
},
- lRange: async (k: string, start: number, end: number) => {
- if (!ls()[k]) return [];
- if (end === -1) return ls()[k].slice(start);
- return ls()[k].slice(start, end + 1);
+ lRem: (k: string, _count: number, v: string) => {
+ ops.push(async () => {
+ const ls = globalThis.__testStore.listStore;
+ if (ls[k]) {
+ const idx = ls[k].indexOf(v);
+ if (idx >= 0) ls[k].splice(idx, 1);
+ }
+ });
+ return chain;
},
- lPos: async (k: string, v: string) => {
- if (!ls()[k]) return null;
- const idx = ls()[k].indexOf(v);
- return idx >= 0 ? idx : null;
+ sRem: (k: string, v: string) => {
+ ops.push(async () => globalThis.__testStore.setStore[k]?.delete(v));
+ return chain;
},
- sAdd: async (k: string, ...vals: string[]) => {
- if (!ss()[k]) ss()[k] = new Set();
- for (const v of vals) ss()[k].add(v);
- return vals.length;
+ exec: async () => {
+ for (const op of ops) await op();
+ return [];
},
- sRem: async (k: string, v: string) => ss()[k]?.delete(v) ? 1 : 0,
- sIsMember: async (k: string, v: string) => ss()[k]?.has(v) ?? false,
- sMembers: async (k: string) => (ss()[k] ? [...ss()[k]] : []),
- incrBy: async (k: string, n: number) => {
- const v = Number.parseInt(kv()[k] || "0") + n;
- kv()[k] = String(v);
+ };
+ return chain;
+ };
+
+ mock.module("$lib/db", () => {
+ const kv = () => globalThis.__testStore.kvStore;
+ const ls = () => globalThis.__testStore.listStore;
+ const ss = () => globalThis.__testStore.setStore;
+ const g = async (k: string) => {
+ const v = kv()[k] ?? null;
+ if (v === null) return null;
+ try {
+ return JSON.parse(v);
+ } catch {
return v;
+ }
+ };
+ const s = (k: string, v: any) => {
+ if (k === "user:null" || k === "user:undefined") throw new Error("null user");
+ kv()[k] = JSON.stringify(v);
+ };
+ const gf = async (k: string) => {
+ const v = kv()[k] ?? null;
+ if (v === null) return null;
+ try {
+ return JSON.parse(v);
+ } catch {
+ return v;
+ }
+ };
+ return {
+ db: {
+ get: async (k: string) => kv()[k] ?? null,
+ set: async (k: string, v: string, opts?: any) => {
+ if (opts?.NX && kv()[k] !== undefined) return null;
+ kv()[k] = v;
+ return "OK";
+ },
+ del: async (k: string) => {
+ delete kv()[k];
+ return 1;
+ },
+ exists: async (k: string) => (kv()[k] !== undefined ? 1 : 0),
+ lPush: async (k: string, v: string) => {
+ if (!ls()[k]) ls()[k] = [];
+ ls()[k].unshift(v);
+ return ls()[k].length;
+ },
+ lRem: async (k: string, _count: number, v: string) => {
+ if (!ls()[k]) return 0;
+ const idx = ls()[k].indexOf(v);
+ if (idx >= 0) {
+ ls()[k].splice(idx, 1);
+ return 1;
+ }
+ return 0;
+ },
+ lRange: async (k: string, start: number, end: number) => {
+ if (!ls()[k]) return [];
+ if (end === -1) return ls()[k].slice(start);
+ return ls()[k].slice(start, end + 1);
+ },
+ lPos: async (k: string, v: string) => {
+ if (!ls()[k]) return null;
+ const idx = ls()[k].indexOf(v);
+ return idx >= 0 ? idx : null;
+ },
+ sAdd: async (k: string, ...vals: string[]) => {
+ if (!ss()[k]) ss()[k] = new Set();
+ for (const v of vals) ss()[k].add(v);
+ return vals.length;
+ },
+ sRem: async (k: string, v: string) => (ss()[k]?.delete(v) ? 1 : 0),
+ sIsMember: async (k: string, v: string) => ss()[k]?.has(v) ?? false,
+ sMembers: async (k: string) => (ss()[k] ? [...ss()[k]] : []),
+ incrBy: async (k: string, n: number) => {
+ const v = Number.parseInt(kv()[k] || "0") + n;
+ kv()[k] = String(v);
+ return v;
+ },
+ watch: async () => {},
+ multi: makeMulti,
+ expire: async () => 1,
+ setNX: async (k: string, v: string) => {
+ if (kv()[k]) return false;
+ kv()[k] = v;
+ return true;
+ },
+ zScore: async () => null,
+ zAdd: async () => 1,
+ zCard: async () => 0,
+ zRemRangeByRank: async () => 0,
+ keys: async (pattern: string) => {
+ const ss = globalThis.__testStore.setStore;
+ const prefix = pattern.replace("*", "");
+ return Object.keys(ss).filter((k) => k.startsWith(prefix));
+ },
+ type: async (k: string) => {
+ if (globalThis.__testStore.setStore[k]) return "set";
+ if (globalThis.__testStore.listStore[k]) return "list";
+ if (globalThis.__testStore.kvStore[k] !== undefined) return "string";
+ return "none";
+ },
},
- watch: async () => {},
- multi: makeMulti,
- expire: async () => 1,
- setNX: async (k: string, v: string) => {
- if (kv()[k]) return false;
- kv()[k] = v;
- return true;
- },
- zScore: async () => null,
- zAdd: async () => 1,
- zCard: async () => 0,
- zRemRangeByRank: async () => 0,
- keys: async (pattern: string) => {
- const ss = globalThis.__testStore.setStore;
- const prefix = pattern.replace("*", "");
- return Object.keys(ss).filter((k) => k.startsWith(prefix));
+ g,
+ s,
+ gf,
+ ga: async () => null,
+ archive: { lRange: async () => [] },
+ };
+ });
+
+ mock.module("$config", () => ({
+ default: {
+ bitcoin: {
+ host: "localhost",
+ wallet: "test",
+ user: "u",
+ password: "p",
+ network: "regtest",
+ port: 18443,
},
- type: async (k: string) => {
- if (globalThis.__testStore.setStore[k]) return "set";
- if (globalThis.__testStore.listStore[k]) return "list";
- if (globalThis.__testStore.kvStore[k] !== undefined) return "string";
- return "none";
+ liquid: {
+ host: "localhost",
+ wallet: "test",
+ user: "u",
+ password: "p",
+ btc: "test-asset",
+ port: 7040,
},
+ lightning: "/dev/null",
+ fee: { bitcoin: 0.004, liquid: 0.001, lightning: 0.001 },
+ adminpass: "test",
+ ark: { arkPrivateKey: "0000", arkServerUrl: "http://localhost" },
+ nostr: "ws://localhost:7777",
+ tigerbeetle: { cluster_id: 0n, replica_addresses: ["localhost:3000"] },
+ vapid: { pk: "test", sk: "test" },
+ support: "[email protected]",
+ txWebhookSecret: "test",
},
- g, s, gf,
- ga: async () => null,
- archive: { lRange: async () => [] },
- };
-});
-
-mock.module("$config", () => ({
- default: {
- bitcoin: { host: "localhost", wallet: "test", user: "u", password: "p", network: "regtest", port: 18443 },
- liquid: { host: "localhost", wallet: "test", user: "u", password: "p", btc: "test-asset", port: 7040 },
- lightning: "/dev/null",
- fee: { bitcoin: 0.004, liquid: 0.001, lightning: 0.001 },
- adminpass: "test",
- ark: { arkPrivateKey: "0000", arkServerUrl: "http://localhost" },
- nostr: "ws://localhost:7777",
- tigerbeetle: { cluster_id: 0n, replica_addresses: ["localhost:3000"] },
- vapid: { pk: "test", sk: "test" },
- support: "[email protected]",
- txWebhookSecret: "test",
- },
-}));
-
-mock.module("$lib/tb", () => ({
- getBalance: mock(async () => 10_000_000),
- getCredit: mock(async () => 0),
- tbDebit: mock(async () => 0),
- tbCredit: mock(async () => undefined),
- tbRefund: mock(async () => undefined),
- tbReverse: mock(async () => undefined),
- tbConfirm: mock(async () => undefined),
- tbSetBalance: mock(async () => undefined),
- tbSetPending: mock(async () => undefined),
- fundDebit: mock(async () => ({ err: null })),
- initTigerBeetle: mock(async () => {}),
-}));
+ }));
-mock.module("$lib/ln", () => ({
- default: {
- decode: mock(async () => ({ type: "bolt11", amount_msat: 1_000_000, payee: "test-payee" })),
- listpeerchannels: mock(async () => ({ channels: [] })),
- listpays: mock(async () => ({ pays: [] })),
- xpay: mock(async () => ({ amount_sent_msat: 1_000_000, payment_preimage: "preimage-abc" })),
- getinfo: mock(async () => ({ id: "our-node-id" })),
- listinvoices: mock(async () => ({ invoices: [] })),
- listfunds: mock(async () => ({ channels: [] })),
- keysend: mock(async () => ({})),
- fetchinvoice: mock(async () => ({})),
- getroutes: mock(async () => ({ routes: [] })),
- sendinvoice: mock(async () => ({})),
- },
-}));
+ mock.module("$lib/tb", () => ({
+ getBalance: mock(async () => 10_000_000),
+ getCredit: mock(async () => 0),
+ tbDebit: mock(async () => 0),
+ tbCredit: mock(async () => undefined),
+ tbRefund: mock(async () => undefined),
+ tbReverse: mock(async () => undefined),
+ tbConfirm: mock(async () => undefined),
+ tbSetBalance: mock(async () => undefined),
+ tbSetPending: mock(async () => undefined),
+ fundDebit: mock(async () => ({ err: null })),
+ initTigerBeetle: mock(async () => {}),
+ }));
-mock.module("$lib/logging", () => ({ l: () => {}, warn: mock(() => {}), err: () => {} }));
-mock.module("$lib/notifications", () => ({ notify: () => {}, nwcNotify: () => {} }));
-mock.module("$lib/webhooks", () => ({ callWebhook: mock(() => {}) }));
-mock.module("$lib/sockets", () => ({ emit: mock(() => {}), sendHeartbeat: () => {} }));
-mock.module("$lib/esplora", () => ({
- btcNetwork: { bech32: "bcrt", pubKeyHash: 0x6f, scriptHash: 0xc4, wif: 0xef },
- hdVersions: { private: 0x04358394, public: 0x043587cf },
- getAddressTxs: mock(async (address: string) => {
- const txs = globalThis.__testStore.esploraOverride?.addressTxs?.[address];
- return txs || [];
- }),
- getTxStatus: mock(async (txid: string) => {
- const status = globalThis.__testStore.esploraOverride?.txStatus?.[txid];
- return status || { confirmed: false };
- }),
- getAddressUtxos: mock(async () => []),
- getUtxos: mock(async () => []),
- getTxHex: mock(async () => ""),
- getTx: mock(async () => ({})),
- broadcastTx: mock(async () => ({})),
- getFeeEstimates: mock(async () => ({})),
- deriveAddress: mock(() => ({ address: "bcrt1qmock" })),
- deriveAddresses: mock(() => []),
- parseDescriptor: mock(() => ({})),
- findLastUsedIndex: mock(async () => -1),
-}));
-mock.module("$lib/square", () => ({ squarePayment: () => {} }));
-mock.module("$lib/nostr", () => ({
- handleZap: async () => {}, publish: async () => {},
- serverPubkey: "m", serverPubkey2: "m", serverSecret: "m", serverSecret2: "m",
-}));
-mock.module("$lib/mqtt", () => ({ default: { publish: () => {} } }));
-mock.module("$lib/ark", () => ({
- getArkAddress: async () => "ark-addr", sendArk: async () => "ark-txid", getArkBalance: async () => 0, verifyArkVtxo: async () => true,
-}));
-mock.module("$lib/ecash", () => ({ request: async () => ({}) }));
-mock.module("$lib/lightning", () => ({
- replay: async () => ({}), fixBolt12: () => {}, listenForLightning: () => {},
-}));
-mock.module("$lib/mail", () => ({ mail: async () => {}, templates: {} }));
-mock.module("$lib/auth", () => ({
- requirePin: async () => {}, auth: (_r: any, _s: any, n: any) => n(), optional: (_r: any, _s: any, n: any) => n(), admin: (_r: any, _s: any, n: any) => n(),
-}));
-mock.module("$lib/invoices", () => ({
- generate: mock(async ({ invoice, user }: any) => ({
- id: "gen-inv", hash: "gen-hash", uid: user?.id, received: 0, pending: 0, ...invoice,
- })),
-}));
-mock.module("$lib/api", () => ({ default: { bitcoin: "http://localhost", liquid: "http://localhost" } }));
-mock.module("$lib/store", () => ({ default: { rates: { USD: 50000 } } }));
-mock.module("@coinos/rpc", () => ({
- default: () => new Proxy({}, {
- get: (_target, prop) => async (...args: any[]) => {
- const override = globalThis.__testStore.rpcOverride;
- if (override && typeof override[prop] === "function") {
- return override[prop](...args);
- }
- return {};
+ mock.module("$lib/ln", () => ({
+ default: {
+ decode: mock(async () => ({ type: "bolt11", amount_msat: 1_000_000, payee: "test-payee" })),
+ listpeerchannels: mock(async () => ({ channels: [] })),
+ listpays: mock(async () => ({ pays: [] })),
+ xpay: mock(async () => ({ amount_sent_msat: 1_000_000, payment_preimage: "preimage-abc" })),
+ getinfo: mock(async () => ({ id: "our-node-id" })),
+ listinvoices: mock(async () => ({ invoices: [] })),
+ listfunds: mock(async () => ({ channels: [] })),
+ keysend: mock(async () => ({})),
+ fetchinvoice: mock(async () => ({})),
+ getroutes: mock(async () => ({ routes: [] })),
+ sendinvoice: mock(async () => ({})),
},
- }),
-}));
+ }));
-mock.module("$lib/utils", () => {
- const SATS = 100_000_000;
- const kv = () => globalThis.__testStore.kvStore;
- return {
- SATS,
- btc: (n: number) => Number.parseFloat((n / SATS).toFixed(8)),
- fail: (msg: string) => { throw new Error(msg); },
- fmt: (n: number) => String(n),
- formatReceipt: () => {},
- getInvoice: async (hash: string) => {
- const raw = kv()[`invoice:${hash}`] ?? null;
- if (!raw) return null;
- let iid;
- try { iid = JSON.parse(raw); } catch { iid = raw; }
- if (iid?.id) iid = iid.id;
- else if (iid?.hash) iid = iid.hash;
- const raw2 = kv()[`invoice:${iid}`] ?? null;
- if (!raw2) return null;
- try { return JSON.parse(raw2); } catch { return raw2; }
- },
- getPayment: async (id: string) => {
- const raw = kv()[`payment:${id}`];
- if (!raw) return null;
- try {
- const parsed = JSON.parse(raw);
- if (typeof parsed === "string") {
- const p = kv()[`payment:${parsed}`];
- return p ? JSON.parse(p) : null;
- }
- return parsed;
- } catch { return null; }
- },
- getUser: async (username: string) => {
- const raw = kv()[`user:${username}`];
- return raw ? JSON.parse(raw) : null;
- },
- getAccount: async () => null,
- link: (id: string) => `http://test/${id}`,
- sats: (n: number) => Math.round(n * SATS),
- sleep: async () => {},
- t: () => ({ insufficientFunds: "Insufficient funds" }),
- bail: (res: any, msg: string) => res.code(500).send(msg),
- bip21: () => "",
- fields: [],
- nada: () => {},
- fiat: (n: number, r: number) => (n * r) / SATS,
- f: (s: any) => String(s),
- pick: (O: any, K: string[]) => K.reduce((o: any, k: string) => ((o[k] = O[k]), o), {}),
- prod: false,
- uniq: (a: any[], k: any) => [...new Map(a.map((x: any) => [k(x), x])).values()],
- wait: async () => {},
- time: () => ({ start: () => {}, end: () => {} }),
- };
-});
+ mock.module("$lib/logging", () => ({ l: () => {}, warn: mock(() => {}), err: () => {} }));
+ mock.module("$lib/notifications", () => ({ notify: () => {}, nwcNotify: () => {} }));
+ mock.module("$lib/webhooks", () => ({ callWebhook: mock(() => {}) }));
+ mock.module("$lib/sockets", () => ({ emit: mock(() => {}), sendHeartbeat: () => {} }));
+ mock.module("$lib/esplora", () => ({
+ btcNetwork: { bech32: "bcrt", pubKeyHash: 0x6f, scriptHash: 0xc4, wif: 0xef },
+ hdVersions: { private: 0x04358394, public: 0x043587cf },
+ getAddressTxs: mock(async (address: string) => {
+ const txs = globalThis.__testStore.esploraOverride?.addressTxs?.[address];
+ return txs || [];
+ }),
+ getTxStatus: mock(async (txid: string) => {
+ const status = globalThis.__testStore.esploraOverride?.txStatus?.[txid];
+ return status || { confirmed: false };
+ }),
+ getAddressUtxos: mock(async () => []),
+ getUtxos: mock(async () => []),
+ getTxHex: mock(async () => ""),
+ getTx: mock(async () => ({})),
+ broadcastTx: mock(async () => ({})),
+ getFeeEstimates: mock(async () => ({})),
+ deriveAddress: mock(() => ({ address: "bcrt1qmock" })),
+ deriveAddresses: mock(() => []),
+ parseDescriptor: mock(() => ({})),
+ findLastUsedIndex: mock(async () => -1),
+ }));
+ mock.module("$lib/square", () => ({ squarePayment: () => {} }));
+ mock.module("$lib/nostr", () => ({
+ handleZap: async () => {},
+ publish: async () => {},
+ serverPubkey: "m",
+ serverPubkey2: "m",
+ serverSecret: "m",
+ serverSecret2: "m",
+ }));
+ mock.module("$lib/mqtt", () => ({ default: { publish: () => {} } }));
+ mock.module("$lib/ark", () => ({
+ getArkAddress: async () => "ark-addr",
+ sendArk: async () => "ark-txid",
+ getArkBalance: async () => 0,
+ verifyArkVtxo: async () => true,
+ }));
+ mock.module("$lib/ecash", () => ({ request: async () => ({}) }));
+ mock.module("$lib/lightning", () => ({
+ replay: async () => ({}),
+ fixBolt12: () => {},
+ listenForLightning: () => {},
+ }));
+ mock.module("$lib/mail", () => ({ mail: async () => {}, templates: {} }));
+ mock.module("$lib/auth", () => ({
+ requirePin: async () => {},
+ auth: (_r: any, _s: any, n: any) => n(),
+ optional: (_r: any, _s: any, n: any) => n(),
+ admin: (_r: any, _s: any, n: any) => n(),
+ }));
+ mock.module("$lib/invoices", () => ({
+ generate: mock(async ({ invoice, user }: any) => ({
+ id: "gen-inv",
+ hash: "gen-hash",
+ uid: user?.id,
+ received: 0,
+ pending: 0,
+ ...invoice,
+ })),
+ }));
+ mock.module("$lib/api", () => ({
+ default: { bitcoin: "http://localhost", liquid: "http://localhost" },
+ }));
+ mock.module("$lib/store", () => ({ default: { rates: { USD: 50000 } } }));
+ mock.module("@coinos/rpc", () => ({
+ default: () =>
+ new Proxy(
+ {},
+ {
+ get:
+ (_target, prop) =>
+ async (...args: any[]) => {
+ const override = globalThis.__testStore.rpcOverride;
+ if (override && typeof override[prop] === "function") {
+ return override[prop](...args);
+ }
+ return {};
+ },
+ },
+ ),
+ }));
+ mock.module("$lib/utils", () => {
+ const SATS = 100_000_000;
+ const kv = () => globalThis.__testStore.kvStore;
+ return {
+ SATS,
+ btc: (n: number) => Number.parseFloat((n / SATS).toFixed(8)),
+ fail: (msg: string) => {
+ throw new Error(msg);
+ },
+ fmt: (n: number) => String(n),
+ formatReceipt: () => {},
+ getInvoice: async (hash: string) => {
+ const raw = kv()[`invoice:${hash}`] ?? null;
+ if (!raw) return null;
+ let iid;
+ try {
+ iid = JSON.parse(raw);
+ } catch {
+ iid = raw;
+ }
+ if (iid?.id) iid = iid.id;
+ else if (iid?.hash) iid = iid.hash;
+ const raw2 = kv()[`invoice:${iid}`] ?? null;
+ if (!raw2) return null;
+ try {
+ return JSON.parse(raw2);
+ } catch {
+ return raw2;
+ }
+ },
+ getPayment: async (id: string) => {
+ const raw = kv()[`payment:${id}`];
+ if (!raw) return null;
+ try {
+ const parsed = JSON.parse(raw);
+ if (typeof parsed === "string") {
+ const p = kv()[`payment:${parsed}`];
+ return p ? JSON.parse(p) : null;
+ }
+ return parsed;
+ } catch {
+ return null;
+ }
+ },
+ getUser: async (username: string) => {
+ const raw = kv()[`user:${username}`];
+ return raw ? JSON.parse(raw) : null;
+ },
+ getAccount: async () => null,
+ link: (id: string) => `http://test/${id}`,
+ sats: (n: number) => Math.round(n * SATS),
+ sleep: async () => {},
+ t: () => ({ insufficientFunds: "Insufficient funds" }),
+ bail: (res: any, msg: string) => res.code(500).send(msg),
+ bip21: () => "",
+ fields: [],
+ nada: () => {},
+ fiat: (n: number, r: number) => (n * r) / SATS,
+ f: (s: any) => String(s),
+ pick: (O: any, K: string[]) => K.reduce((o: any, k: string) => ((o[k] = O[k]), o), {}),
+ prod: false,
+ uniq: (a: any[], k: any) => [...new Map(a.map((x: any) => [k(x), x])).values()],
+ wait: async () => {},
+ time: () => ({ start: () => {}, end: () => {} }),
+ };
+ });
} // end INTEGRATION skip