Merge branch 'cashu-wallet' into 'main'

feat: endpoint for creating NIP-60 wallet

Closes #289

See merge request soapbox-pub/ditto!629
This commit is contained in:
Alex Gleason 2025-01-30 18:59:11 +00:00
commit 9ced9fb31d
2 changed files with 65 additions and 0 deletions

View file

@ -43,6 +43,7 @@ import { captchaController, captchaVerifyController } from '@/controllers/api/ca
import { import {
adminRelaysController, adminRelaysController,
adminSetRelaysController, adminSetRelaysController,
createCashuWalletController,
deleteZapSplitsController, deleteZapSplitsController,
getZapSplitsController, getZapSplitsController,
nameRequestController, nameRequestController,
@ -400,6 +401,8 @@ app.delete('/api/v1/admin/ditto/zap_splits', requireRole('admin'), deleteZapSpli
app.post('/api/v1/ditto/zap', requireSigner, zapController); app.post('/api/v1/ditto/zap', requireSigner, zapController);
app.get('/api/v1/ditto/statuses/:id{[0-9a-f]{64}}/zapped_by', zappedByController); app.get('/api/v1/ditto/statuses/:id{[0-9a-f]{64}}/zapped_by', zappedByController);
app.post('/api/v1/ditto/wallet/create', requireSigner, createCashuWalletController);
app.post('/api/v1/reports', requireSigner, reportController); app.post('/api/v1/reports', requireSigner, reportController);
app.get('/api/v1/admin/reports', requireSigner, requireRole('admin'), adminReportsController); app.get('/api/v1/admin/reports', requireSigner, requireRole('admin'), adminReportsController);
app.get('/api/v1/admin/reports/:id{[0-9a-f]{64}}', requireSigner, requireRole('admin'), adminReportController); app.get('/api/v1/admin/reports/:id{[0-9a-f]{64}}', requireSigner, requireRole('admin'), adminReportController);

View file

@ -1,4 +1,5 @@
import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify'; import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify';
import { bytesToString } from '@scure/base';
import { z } from 'zod'; import { z } from 'zod';
import { AppController } from '@/app.ts'; import { AppController } from '@/app.ts';
@ -19,6 +20,7 @@ import { accountFromPubkey } from '@/views/mastodon/accounts.ts';
import { renderAccount } from '@/views/mastodon/accounts.ts'; import { renderAccount } from '@/views/mastodon/accounts.ts';
import { Storages } from '@/storages.ts'; import { Storages } from '@/storages.ts';
import { updateListAdminEvent } from '@/utils/api.ts'; import { updateListAdminEvent } from '@/utils/api.ts';
import { generateSecretKey } from 'nostr-tools';
const markerSchema = z.enum(['read', 'write']); const markerSchema = z.enum(['read', 'write']);
@ -342,3 +344,63 @@ export const updateInstanceController: AppController = async (c) => {
return c.json(204); return c.json(204);
}; };
const createCashuWalletSchema = z.object({
description: z.string(),
relays: z.array(z.string().url()),
mints: z.array(z.string().url()).nonempty(), // must contain at least one item
name: z.string(),
});
export const createCashuWalletController: AppController = async (c) => {
const signer = c.get('signer')!;
const store = c.get('store');
const pubkey = await signer.getPublicKey();
const body = await parseBody(c.req.raw);
const { signal } = c.req.raw;
const result = createCashuWalletSchema.safeParse(body);
if (!result.success) {
return c.json({ error: 'Bad request', schema: result.error }, 400);
}
const [event] = await store.query([{ authors: [pubkey], kinds: [37375] }], { signal });
if (event) {
return c.json({ error: 'You already have a wallet 😏', schema: result.error }, 400);
}
const { description, relays, mints, name } = result.data;
relays.push(Conf.relay);
const tags: string[][] = [];
tags.push(['d', Math.random().toString(36).substring(3)]);
tags.push(['name', name]);
tags.push(['description', description]);
tags.push(['unit', 'sat']);
for (const mint of new Set(mints)) {
tags.push(['mint', mint]);
}
for (const relay of new Set(relays)) {
tags.push(['relay', relay]);
}
const sk = generateSecretKey();
const privkey = bytesToString('hex', sk);
const contentTags = [
['privkey', privkey],
];
const encryptedContentTags = await signer.nip44?.encrypt(pubkey, JSON.stringify(contentTags));
// Wallet
await createEvent({
kind: 37375,
content: encryptedContentTags,
tags,
}, c);
return c.json(201);
};