mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 03:19:46 +00:00
refactor: create @ditto/cashu package
This commit is contained in:
parent
1c709b04be
commit
a002b1a005
9 changed files with 218 additions and 124 deletions
|
|
@ -13,7 +13,8 @@
|
||||||
"./packages/ratelimiter",
|
"./packages/ratelimiter",
|
||||||
"./packages/transcode",
|
"./packages/transcode",
|
||||||
"./packages/translators",
|
"./packages/translators",
|
||||||
"./packages/uploaders"
|
"./packages/uploaders",
|
||||||
|
"./packages/cashu"
|
||||||
],
|
],
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"start": "deno run -A --env-file --deny-read=.env packages/ditto/server.ts",
|
"start": "deno run -A --env-file --deny-read=.env packages/ditto/server.ts",
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,25 @@
|
||||||
import { NSecSigner } from '@nostrify/nostrify';
|
import { NSecSigner } from '@nostrify/nostrify';
|
||||||
|
import { NPostgres } from '@nostrify/db';
|
||||||
import { genEvent } from '@nostrify/nostrify/test';
|
import { genEvent } from '@nostrify/nostrify/test';
|
||||||
|
|
||||||
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
||||||
import { bytesToString, stringToBytes } from '@scure/base';
|
import { bytesToString, stringToBytes } from '@scure/base';
|
||||||
import { assertEquals } from '@std/assert';
|
import { assertEquals } from '@std/assert';
|
||||||
|
|
||||||
import { createTestDB } from '@/test.ts';
|
import { DittoPolyPg, TestDB } from '@ditto/db';
|
||||||
|
import { DittoConf } from '@ditto/conf';
|
||||||
|
|
||||||
import { organizeProofs, validateAndParseWallet } from '@/utils/cashu.ts';
|
import { getLastRedeemedNutzap, organizeProofs, validateAndParseWallet } from './cashu.ts';
|
||||||
|
|
||||||
Deno.test('validateAndParseWallet function returns valid data', async () => {
|
Deno.test('validateAndParseWallet function returns valid data', async () => {
|
||||||
await using db = await createTestDB({ pure: true });
|
const conf = new DittoConf(Deno.env);
|
||||||
const store = db.store;
|
const orig = new DittoPolyPg(conf.databaseUrl);
|
||||||
|
|
||||||
|
await using db = new TestDB(orig);
|
||||||
|
await db.migrate();
|
||||||
|
await db.clear();
|
||||||
|
|
||||||
|
const store = new NPostgres(orig.kysely);
|
||||||
|
|
||||||
const sk = generateSecretKey();
|
const sk = generateSecretKey();
|
||||||
const signer = new NSecSigner(sk);
|
const signer = new NSecSigner(sk);
|
||||||
|
|
@ -30,7 +38,7 @@ Deno.test('validateAndParseWallet function returns valid data', async () => {
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
}, sk);
|
}, sk);
|
||||||
await db.store.event(wallet);
|
await store.event(wallet);
|
||||||
|
|
||||||
// Nutzap information
|
// Nutzap information
|
||||||
const nutzapInfo = genEvent({
|
const nutzapInfo = genEvent({
|
||||||
|
|
@ -40,7 +48,7 @@ Deno.test('validateAndParseWallet function returns valid data', async () => {
|
||||||
['mint', 'https://mint.soul.com'],
|
['mint', 'https://mint.soul.com'],
|
||||||
],
|
],
|
||||||
}, sk);
|
}, sk);
|
||||||
await db.store.event(nutzapInfo);
|
await store.event(nutzapInfo);
|
||||||
|
|
||||||
const { data, error } = await validateAndParseWallet(store, signer, pubkey);
|
const { data, error } = await validateAndParseWallet(store, signer, pubkey);
|
||||||
|
|
||||||
|
|
@ -55,8 +63,14 @@ Deno.test('validateAndParseWallet function returns valid data', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test('organizeProofs function is working', async () => {
|
Deno.test('organizeProofs function is working', async () => {
|
||||||
await using db = await createTestDB({ pure: true });
|
const conf = new DittoConf(Deno.env);
|
||||||
const store = db.store;
|
const orig = new DittoPolyPg(conf.databaseUrl);
|
||||||
|
|
||||||
|
await using db = new TestDB(orig);
|
||||||
|
await db.migrate();
|
||||||
|
await db.clear();
|
||||||
|
|
||||||
|
const store = new NPostgres(orig.kysely);
|
||||||
|
|
||||||
const sk = generateSecretKey();
|
const sk = generateSecretKey();
|
||||||
const signer = new NSecSigner(sk);
|
const signer = new NSecSigner(sk);
|
||||||
|
|
@ -98,7 +112,7 @@ Deno.test('organizeProofs function is working', async () => {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
}, sk);
|
}, sk);
|
||||||
await db.store.event(event1);
|
await store.event(event1);
|
||||||
|
|
||||||
const proof1 = {
|
const proof1 = {
|
||||||
'id': '004f7adf2a04356c',
|
'id': '004f7adf2a04356c',
|
||||||
|
|
@ -124,7 +138,7 @@ Deno.test('organizeProofs function is working', async () => {
|
||||||
token1,
|
token1,
|
||||||
),
|
),
|
||||||
}, sk);
|
}, sk);
|
||||||
await db.store.event(event2);
|
await store.event(event2);
|
||||||
|
|
||||||
const proof2 = {
|
const proof2 = {
|
||||||
'id': '004f7adf2a04356c',
|
'id': '004f7adf2a04356c',
|
||||||
|
|
@ -151,7 +165,7 @@ Deno.test('organizeProofs function is working', async () => {
|
||||||
token2,
|
token2,
|
||||||
),
|
),
|
||||||
}, sk);
|
}, sk);
|
||||||
await db.store.event(event3);
|
await store.event(event3);
|
||||||
|
|
||||||
const unspentProofs = await store.query([{ kinds: [7375], authors: [pubkey] }]);
|
const unspentProofs = await store.query([{ kinds: [7375], authors: [pubkey] }]);
|
||||||
|
|
||||||
|
|
@ -169,3 +183,62 @@ Deno.test('organizeProofs function is working', async () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test('getLastRedeemedNutzap function is working', async () => {
|
||||||
|
const conf = new DittoConf(Deno.env);
|
||||||
|
const orig = new DittoPolyPg(conf.databaseUrl);
|
||||||
|
|
||||||
|
await using db = new TestDB(orig);
|
||||||
|
await db.migrate();
|
||||||
|
await db.clear();
|
||||||
|
|
||||||
|
const store = new NPostgres(orig.kysely);
|
||||||
|
|
||||||
|
const sk = generateSecretKey();
|
||||||
|
const signer = new NSecSigner(sk);
|
||||||
|
const pubkey = await signer.getPublicKey();
|
||||||
|
|
||||||
|
const event1 = genEvent({
|
||||||
|
kind: 7376,
|
||||||
|
content: '<nip-44-encrypted>',
|
||||||
|
created_at: Math.floor(Date.now() / 1000), // now
|
||||||
|
tags: [
|
||||||
|
['e', '<event-id-of-created-token>', '', 'redeemed'],
|
||||||
|
],
|
||||||
|
}, sk);
|
||||||
|
await store.event(event1);
|
||||||
|
|
||||||
|
const event2 = genEvent({
|
||||||
|
kind: 7376,
|
||||||
|
content: '<nip-44-encrypted>',
|
||||||
|
created_at: Math.floor((Date.now() - 86400000) / 1000), // yesterday
|
||||||
|
tags: [
|
||||||
|
['e', '<event-id-of-created-token>', '', 'redeemed'],
|
||||||
|
],
|
||||||
|
}, sk);
|
||||||
|
await store.event(event2);
|
||||||
|
|
||||||
|
const event3 = genEvent({
|
||||||
|
kind: 7376,
|
||||||
|
content: '<nip-44-encrypted>',
|
||||||
|
created_at: Math.floor((Date.now() - 86400000) / 1000), // yesterday
|
||||||
|
tags: [
|
||||||
|
['e', '<event-id-of-created-token>', '', 'redeemed'],
|
||||||
|
],
|
||||||
|
}, sk);
|
||||||
|
await store.event(event3);
|
||||||
|
|
||||||
|
const event4 = genEvent({
|
||||||
|
kind: 7376,
|
||||||
|
content: '<nip-44-encrypted>',
|
||||||
|
created_at: Math.floor((Date.now() + 86400000) / 1000), // tomorrow
|
||||||
|
tags: [
|
||||||
|
['e', '<event-id-of-created-token>', '', 'redeemed'],
|
||||||
|
],
|
||||||
|
}, sk);
|
||||||
|
await store.event(event4);
|
||||||
|
|
||||||
|
const event = await getLastRedeemedNutzap(store, pubkey);
|
||||||
|
|
||||||
|
assertEquals(event, event4);
|
||||||
|
});
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { SetRequired } from 'type-fest';
|
import type { Proof } from '@cashu/cashu-ts';
|
||||||
|
import { type NostrEvent, type NostrFilter, type NostrSigner, NSchema as n, type NStore } from '@nostrify/nostrify';
|
||||||
import { getPublicKey } from 'nostr-tools';
|
import { getPublicKey } from 'nostr-tools';
|
||||||
import { NostrEvent, NostrSigner, NSchema as n, NStore } from '@nostrify/nostrify';
|
|
||||||
import { logi } from '@soapbox/logi';
|
|
||||||
import { stringToBytes } from '@scure/base';
|
import { stringToBytes } from '@scure/base';
|
||||||
|
import { logi } from '@soapbox/logi';
|
||||||
|
import type { SetRequired } from 'type-fest';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { errorJson } from '@/utils/log.ts';
|
import { proofSchema, tokenEventSchema } from './schemas.ts';
|
||||||
import { isNostrId } from '@/utils.ts';
|
|
||||||
import { tokenEventSchema } from '@/schemas/cashu.ts';
|
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
wallet: NostrEvent;
|
wallet: NostrEvent;
|
||||||
|
|
@ -139,4 +138,114 @@ async function organizeProofs(
|
||||||
return organizedProofs;
|
return organizedProofs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { organizeProofs, validateAndParseWallet };
|
/** Returns a spending history event that contains the last redeemed nutzap. */
|
||||||
|
async function getLastRedeemedNutzap(
|
||||||
|
store: NStore,
|
||||||
|
pubkey: string,
|
||||||
|
opts?: { signal?: AbortSignal },
|
||||||
|
): Promise<NostrEvent | undefined> {
|
||||||
|
const events = await store.query([{ kinds: [7376], authors: [pubkey] }], { signal: opts?.signal });
|
||||||
|
|
||||||
|
for (const event of events) {
|
||||||
|
const nutzap = event.tags.find(([name]) => name === 'e');
|
||||||
|
const redeemed = nutzap?.[3];
|
||||||
|
if (redeemed === 'redeemed') {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toBeRedeemed are the nutzaps that will be redeemed into a kind 7375 and saved in the kind 7376 tags
|
||||||
|
* The tags format is: [
|
||||||
|
* [ "e", "<9321-event-id>", "<relay-hint>", "redeemed" ], // nutzap event that has been redeemed
|
||||||
|
* [ "p", "<sender-pubkey>" ] // pubkey of the author of the 9321 event (nutzap sender)
|
||||||
|
* ]
|
||||||
|
* https://github.com/nostr-protocol/nips/blob/master/61.md#updating-nutzap-redemption-history
|
||||||
|
*/
|
||||||
|
type MintsToProofs = { [key: string]: { proofs: Proof[]; toBeRedeemed: string[][] } };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets proofs from nutzaps that have not been redeemed yet.
|
||||||
|
* Each proof is associated with a specific mint.
|
||||||
|
* @param store Store used to query for the nutzaps
|
||||||
|
* @param nutzapsFilter Filter used to query for the nutzaps, most useful when
|
||||||
|
* it contains a 'since' field so it saves time and resources
|
||||||
|
* @param relay Relay hint where the new kind 7376 will be saved
|
||||||
|
* @returns MintsToProofs An object where each key is a mint url and the values are an array of proofs
|
||||||
|
* and an array of redeemed tags in this format:
|
||||||
|
* ```
|
||||||
|
* [
|
||||||
|
* ...,
|
||||||
|
* [ "e", "<9321-event-id>", "<relay-hint>", "redeemed" ], // nutzap event that has been redeemed
|
||||||
|
* [ "p", "<sender-pubkey>" ] // pubkey of the author of the 9321 event (nutzap sender)
|
||||||
|
* ]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
async function getMintsToProofs(
|
||||||
|
store: NStore,
|
||||||
|
nutzapsFilter: NostrFilter,
|
||||||
|
relay: string,
|
||||||
|
opts?: { signal?: AbortSignal },
|
||||||
|
): Promise<MintsToProofs> {
|
||||||
|
const mintsToProofs: MintsToProofs = {};
|
||||||
|
|
||||||
|
const nutzaps = await store.query([nutzapsFilter], { signal: opts?.signal });
|
||||||
|
|
||||||
|
for (const event of nutzaps) {
|
||||||
|
try {
|
||||||
|
const mint = event.tags.find(([name]) => name === 'u')?.[1];
|
||||||
|
if (!mint) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const proofs = event.tags.filter(([name]) => name === 'proof').map((tag) => tag[1]).filter(Boolean);
|
||||||
|
if (proofs.length < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mintsToProofs[mint]) {
|
||||||
|
mintsToProofs[mint] = { proofs: [], toBeRedeemed: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = n.json().pipe(
|
||||||
|
proofSchema,
|
||||||
|
).array().safeParse(proofs);
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mintsToProofs[mint].proofs = [...mintsToProofs[mint].proofs, ...parsed.data];
|
||||||
|
mintsToProofs[mint].toBeRedeemed = [
|
||||||
|
...mintsToProofs[mint].toBeRedeemed,
|
||||||
|
[
|
||||||
|
'e', // nutzap event that has been redeemed
|
||||||
|
event.id,
|
||||||
|
relay,
|
||||||
|
'redeemed',
|
||||||
|
],
|
||||||
|
['p', event.pubkey], // pubkey of the author of the 9321 event (nutzap sender)
|
||||||
|
];
|
||||||
|
} catch (e) {
|
||||||
|
logi({ level: 'error', ns: 'ditto.api.cashu.wallet.swap', error: errorJson(e) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mintsToProofs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Serialize an error into JSON for JSON logging. */
|
||||||
|
export function errorJson(error: unknown): Error | null {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return error;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNostrId(value: unknown): boolean {
|
||||||
|
return n.id().safeParse(value).success;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getLastRedeemedNutzap, getMintsToProofs, organizeProofs, validateAndParseWallet };
|
||||||
7
packages/cashu/deno.json
Normal file
7
packages/cashu/deno.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "@ditto/cashu",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"exports": {
|
||||||
|
".": "./mod.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
2
packages/cashu/mod.ts
Normal file
2
packages/cashu/mod.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { getLastRedeemedNutzap, getMintsToProofs, organizeProofs, validateAndParseWallet } from './cashu.ts';
|
||||||
|
export { proofSchema, tokenEventSchema } from './schemas.ts';
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { NSchema as n } from '@nostrify/nostrify';
|
import { NSchema as n } from '@nostrify/nostrify';
|
||||||
import { assertEquals } from '@std/assert';
|
import { assertEquals } from '@std/assert';
|
||||||
import { proofSchema } from '@/schemas/cashu.ts';
|
|
||||||
import { tokenEventSchema } from '@/schemas/cashu.ts';
|
import { proofSchema } from './schemas.ts';
|
||||||
|
import { tokenEventSchema } from './schemas.ts';
|
||||||
|
|
||||||
Deno.test('Parse proof', () => {
|
Deno.test('Parse proof', () => {
|
||||||
const proof =
|
const proof =
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { CashuMint, CashuWallet, MintQuoteState, Proof } from '@cashu/cashu-ts';
|
import { CashuMint, CashuWallet, MintQuoteState, Proof } from '@cashu/cashu-ts';
|
||||||
|
import { organizeProofs, tokenEventSchema, validateAndParseWallet } from '@ditto/cashu';
|
||||||
import { userMiddleware } from '@ditto/mastoapi/middleware';
|
import { userMiddleware } from '@ditto/mastoapi/middleware';
|
||||||
import { DittoRoute } from '@ditto/mastoapi/router';
|
import { DittoRoute } from '@ditto/mastoapi/router';
|
||||||
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
import { generateSecretKey, getPublicKey } from 'nostr-tools';
|
||||||
|
|
@ -14,8 +15,6 @@ import { hydrateEvents } from '@/storages/hydrate.ts';
|
||||||
import { nostrNow } from '@/utils.ts';
|
import { nostrNow } from '@/utils.ts';
|
||||||
import { errorJson } from '@/utils/log.ts';
|
import { errorJson } from '@/utils/log.ts';
|
||||||
import { getAmount } from '@/utils/bolt11.ts';
|
import { getAmount } from '@/utils/bolt11.ts';
|
||||||
import { organizeProofs, validateAndParseWallet } from '@/utils/cashu.ts';
|
|
||||||
import { tokenEventSchema } from '@/schemas/cashu.ts';
|
|
||||||
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
import { DittoEvent } from '@/interfaces/DittoEvent.ts';
|
||||||
|
|
||||||
type Wallet = z.infer<typeof walletSchema>;
|
type Wallet = z.infer<typeof walletSchema>;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { CashuMint, CashuWallet, getEncodedToken, type Proof } from '@cashu/cashu-ts';
|
import { CashuMint, CashuWallet, getEncodedToken } from '@cashu/cashu-ts';
|
||||||
|
import { getLastRedeemedNutzap, getMintsToProofs, validateAndParseWallet } from '@ditto/cashu';
|
||||||
import { HTTPException } from '@hono/hono/http-exception';
|
import { HTTPException } from '@hono/hono/http-exception';
|
||||||
import { NostrEvent, NostrFilter, NSchema as n, NStore } from '@nostrify/nostrify';
|
import { NostrFilter } from '@nostrify/nostrify';
|
||||||
import { logi } from '@soapbox/logi';
|
import { logi } from '@soapbox/logi';
|
||||||
|
|
||||||
import { errorJson } from '@/utils/log.ts';
|
import { errorJson } from '@/utils/log.ts';
|
||||||
import { createEvent } from '@/utils/api.ts';
|
import { createEvent } from '@/utils/api.ts';
|
||||||
import { validateAndParseWallet } from '@/utils/cashu.ts';
|
|
||||||
import { proofSchema } from '@/schemas/cashu.ts';
|
|
||||||
import { MiddlewareHandler } from '@hono/hono/types';
|
import { MiddlewareHandler } from '@hono/hono/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -93,100 +92,3 @@ export const swapNutzapsMiddleware: MiddlewareHandler = async (c, next) => {
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Returns a spending history event that contains the last redeemed nutzap. */
|
|
||||||
async function getLastRedeemedNutzap(
|
|
||||||
store: NStore,
|
|
||||||
pubkey: string,
|
|
||||||
opts?: { signal?: AbortSignal },
|
|
||||||
): Promise<NostrEvent | undefined> {
|
|
||||||
const events = await store.query([{ kinds: [7376], authors: [pubkey] }], { signal: opts?.signal });
|
|
||||||
|
|
||||||
for (const event of events) {
|
|
||||||
const nutzap = event.tags.find(([name]) => name === 'e');
|
|
||||||
const redeemed = nutzap?.[3];
|
|
||||||
if (redeemed === 'redeemed') {
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* toBeRedeemed are the nutzaps that will be redeemed into a kind 7375 and saved in the kind 7376 tags
|
|
||||||
* The tags format is: [
|
|
||||||
* [ "e", "<9321-event-id>", "<relay-hint>", "redeemed" ], // nutzap event that has been redeemed
|
|
||||||
* [ "p", "<sender-pubkey>" ] // pubkey of the author of the 9321 event (nutzap sender)
|
|
||||||
* ]
|
|
||||||
* https://github.com/nostr-protocol/nips/blob/master/61.md#updating-nutzap-redemption-history
|
|
||||||
*/
|
|
||||||
type MintsToProofs = { [key: string]: { proofs: Proof[]; toBeRedeemed: string[][] } };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets proofs from nutzaps that have not been redeemed yet.
|
|
||||||
* Each proof is associated with a specific mint.
|
|
||||||
* @param store Store used to query for the nutzaps
|
|
||||||
* @param nutzapsFilter Filter used to query for the nutzaps, most useful when
|
|
||||||
* it contains a 'since' field so it saves time and resources
|
|
||||||
* @param relay Relay hint where the new kind 7376 will be saved
|
|
||||||
* @returns MintsToProofs An object where each key is a mint url and the values are an array of proofs
|
|
||||||
* and an array of redeemed tags in this format:
|
|
||||||
* ```
|
|
||||||
* [
|
|
||||||
* ...,
|
|
||||||
* [ "e", "<9321-event-id>", "<relay-hint>", "redeemed" ], // nutzap event that has been redeemed
|
|
||||||
* [ "p", "<sender-pubkey>" ] // pubkey of the author of the 9321 event (nutzap sender)
|
|
||||||
* ]
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
async function getMintsToProofs(
|
|
||||||
store: NStore,
|
|
||||||
nutzapsFilter: NostrFilter,
|
|
||||||
relay: string,
|
|
||||||
opts?: { signal?: AbortSignal },
|
|
||||||
): Promise<MintsToProofs> {
|
|
||||||
const mintsToProofs: MintsToProofs = {};
|
|
||||||
|
|
||||||
const nutzaps = await store.query([nutzapsFilter], { signal: opts?.signal });
|
|
||||||
|
|
||||||
for (const event of nutzaps) {
|
|
||||||
try {
|
|
||||||
const mint = event.tags.find(([name]) => name === 'u')?.[1];
|
|
||||||
if (!mint) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const proofs = event.tags.filter(([name]) => name === 'proof').map((tag) => tag[1]).filter(Boolean);
|
|
||||||
if (proofs.length < 1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mintsToProofs[mint]) {
|
|
||||||
mintsToProofs[mint] = { proofs: [], toBeRedeemed: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = n.json().pipe(
|
|
||||||
proofSchema,
|
|
||||||
).array().safeParse(proofs);
|
|
||||||
|
|
||||||
if (!parsed.success) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
mintsToProofs[mint].proofs = [...mintsToProofs[mint].proofs, ...parsed.data];
|
|
||||||
mintsToProofs[mint].toBeRedeemed = [
|
|
||||||
...mintsToProofs[mint].toBeRedeemed,
|
|
||||||
[
|
|
||||||
'e', // nutzap event that has been redeemed
|
|
||||||
event.id,
|
|
||||||
relay,
|
|
||||||
'redeemed',
|
|
||||||
],
|
|
||||||
['p', event.pubkey], // pubkey of the author of the 9321 event (nutzap sender)
|
|
||||||
];
|
|
||||||
} catch (e) {
|
|
||||||
logi({ level: 'error', ns: 'ditto.api.cashu.wallet.swap', error: errorJson(e) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mintsToProofs;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue