diff --git a/src/controllers/api/cashu.test.ts b/src/controllers/api/cashu.test.ts index bba10765..f367cc10 100644 --- a/src/controllers/api/cashu.test.ts +++ b/src/controllers/api/cashu.test.ts @@ -18,10 +18,7 @@ interface AppEnv extends HonoEnv { }; } -Deno.test('PUT /wallet must be successful', { - sanitizeOps: false, // postgres.js calls 'setTimeout' without calling 'clearTimeout' - sanitizeResources: false, // postgres.js calls 'setTimeout' without calling 'clearTimeout' -}, async () => { +Deno.test('PUT /wallet must be successful', async () => { await using db = await createTestDB(); const store = db.store; @@ -97,10 +94,7 @@ Deno.test('PUT /wallet must be successful', { ]); }); -Deno.test('PUT /wallet must NOT be successful: wrong request body/schema', { - sanitizeOps: false, // postgres.js calls 'setTimeout' without calling 'clearTimeout' - sanitizeResources: false, // postgres.js calls 'setTimeout' without calling 'clearTimeout' -}, async () => { +Deno.test('PUT /wallet must NOT be successful: wrong request body/schema', async () => { await using db = await createTestDB(); const store = db.store; @@ -132,10 +126,7 @@ Deno.test('PUT /wallet must NOT be successful: wrong request body/schema', { assertObjectMatch(body, { error: 'Bad schema' }); }); -Deno.test('PUT /wallet must NOT be successful: wallet already exists', { - sanitizeOps: false, // postgres.js calls 'setTimeout' without calling 'clearTimeout' - sanitizeResources: false, // postgres.js calls 'setTimeout' without calling 'clearTimeout' -}, async () => { +Deno.test('PUT /wallet must NOT be successful: wallet already exists', async () => { await using db = await createTestDB(); const store = db.store; @@ -169,10 +160,7 @@ Deno.test('PUT /wallet must NOT be successful: wallet already exists', { assertEquals(body2, { error: 'You already have a wallet 😏' }); }); -Deno.test('GET /wallet must be successful', { - sanitizeOps: false, // postgres.js calls 'setTimeout' without calling 'clearTimeout' - sanitizeResources: false, // postgres.js calls 'setTimeout' without calling 'clearTimeout' -}, async () => { +Deno.test('GET /wallet must be successful', async () => { await using db = await createTestDB(); const store = db.store; @@ -284,7 +272,7 @@ Deno.test('GET /wallet must be successful', { }); }); -Deno.test('GET /mints must be successful', {}, async () => { +Deno.test('GET /mints must be successful', async () => { const app = new Hono().route('/', cashuApp); const response = await app.request('/mints', { diff --git a/src/controllers/api/cashu.ts b/src/controllers/api/cashu.ts index 58a150de..19a29658 100644 --- a/src/controllers/api/cashu.ts +++ b/src/controllers/api/cashu.ts @@ -6,9 +6,8 @@ import { z } from 'zod'; import { Conf } from '@/config.ts'; import { createEvent, parseBody } from '@/utils/api.ts'; -import { signerMiddleware } from '@/middleware/signerMiddleware.ts'; import { requireNip44Signer } from '@/middleware/requireSigner.ts'; -import { storeMiddleware } from '@/middleware/storeMiddleware.ts'; +import { requireStore } from '@/middleware/storeMiddleware.ts'; import { walletSchema } from '@/schema.ts'; import { swapNutzapsMiddleware } from '@/middleware/swapNutzapsMiddleware.ts'; import { isNostrId } from '@/utils.ts'; @@ -17,7 +16,7 @@ import { errorJson } from '@/utils/log.ts'; type Wallet = z.infer; -const app = new Hono().use('*', storeMiddleware, signerMiddleware); +const app = new Hono().use('*', requireStore); // app.delete('/wallet') -> 204 @@ -46,7 +45,7 @@ const createCashuWalletAndNutzapInfoSchema = z.object({ * https://github.com/nostr-protocol/nips/blob/master/61.md#nutzap-informational-event */ app.put('/wallet', requireNip44Signer, async (c) => { - const signer = c.get('signer'); + const signer = c.var.signer; const store = c.get('store'); const pubkey = await signer.getPublicKey(); const body = await parseBody(c.req.raw); diff --git a/src/middleware/storeMiddleware.ts b/src/middleware/storeMiddleware.ts index 37d04856..f69712a3 100644 --- a/src/middleware/storeMiddleware.ts +++ b/src/middleware/storeMiddleware.ts @@ -4,6 +4,13 @@ import { NostrSigner, NStore } from '@nostrify/nostrify'; import { UserStore } from '@/storages/UserStore.ts'; import { Storages } from '@/storages.ts'; +export const requireStore: MiddlewareHandler<{ Variables: { store: NStore } }> = async (c, next) => { + if (!c.get('store')) { + throw new Error('Store is required'); + } + await next(); +}; + /** Store middleware. */ export const storeMiddleware: MiddlewareHandler<{ Variables: { signer?: NostrSigner; store: NStore } }> = async ( c, diff --git a/src/middleware/swapNutzapsMiddleware.ts b/src/middleware/swapNutzapsMiddleware.ts index 286965c6..b24dee80 100644 --- a/src/middleware/swapNutzapsMiddleware.ts +++ b/src/middleware/swapNutzapsMiddleware.ts @@ -2,7 +2,7 @@ import { CashuMint, CashuWallet, getEncodedToken, type Proof } from '@cashu/cash import { MiddlewareHandler } from '@hono/hono'; import { HTTPException } from '@hono/hono/http-exception'; import { getPublicKey } from 'nostr-tools'; -import { NostrFilter, NostrSigner, NStore } from '@nostrify/nostrify'; +import { NostrFilter, NostrSigner, NSchema as n, NStore } from '@nostrify/nostrify'; import { SetRequired } from 'type-fest'; import { stringToBytes } from '@scure/base'; import { logi } from '@soapbox/logi'; @@ -11,6 +11,7 @@ import { isNostrId } from '@/utils.ts'; import { errorJson } from '@/utils/log.ts'; import { Conf } from '@/config.ts'; import { createEvent } from '@/utils/api.ts'; +import { z } from 'zod'; /** * Swap nutzaps into wallet (create new events) if the user has a wallet, otheriwse, just fallthrough. @@ -111,7 +112,22 @@ export const swapNutzapsMiddleware: MiddlewareHandler< mintsToProofs[mint] = { proofs: [], redeemed: [] }; } - mintsToProofs[mint].proofs = [...mintsToProofs[mint].proofs, ...JSON.parse(proof)]; + const parsed = n.json().pipe( + z.object({ + id: z.string(), + amount: z.number(), + secret: z.string(), + C: z.string(), + dleq: z.object({ s: z.string(), e: z.string(), r: z.string().optional() }).optional(), + dleqValid: z.boolean().optional(), + }).array(), + ).safeParse(proof); + + if (!parsed.success) { + continue; + } + + mintsToProofs[mint].proofs = [...mintsToProofs[mint].proofs, ...parsed.data]; mintsToProofs[mint].redeemed = [ ...mintsToProofs[mint].redeemed, [ @@ -122,8 +138,8 @@ export const swapNutzapsMiddleware: MiddlewareHandler< ], ['p', event.pubkey], // pubkey of the author of the 9321 event (nutzap sender) ]; - } catch (e: any) { - logi({ level: 'error', ns: 'ditto.api.cashu.wallet.swap', error: e }); + } catch (e) { + logi({ level: 'error', ns: 'ditto.api.cashu.wallet.swap', error: errorJson(e) }); } } @@ -162,8 +178,8 @@ export const swapNutzapsMiddleware: MiddlewareHandler< ), tags: mintsToProofs[mint].redeemed, }, c); - } catch (e: any) { - logi({ level: 'error', ns: 'ditto.api.cashu.wallet.swap', error: e }); + } catch (e) { + logi({ level: 'error', ns: 'ditto.api.cashu.wallet.swap', error: errorJson(e) }); } } }