diff --git a/.env.example b/.env.example index 78ed5b3..d16c82f 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,8 @@ BASE_URL="http://localhost:3000" DAILY_WALLET_LIMIT=10 #APP_NAME_PREFIX = "Alby Jim " #PASSWORD="My super secret password" +NOSTR_NIP57_PRIVATE_KEY="" +HTTP_NOSTR_URL="https://api.getalby.com/nwc" # Info NAME="Uncle Jim Demo Server" diff --git a/README.md b/README.md index 32bc63e..adecb02 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,9 @@ returns: } ``` -`GET /api/info` +## Nostr Zaps + +Zaps are supported by receiving payment notifications through [http-nostr](https://github.com/getAlby/http-nostr) ## Development diff --git a/app/.well-known/lnurlp/[username]/callback/route.ts b/app/.well-known/lnurlp/[username]/callback/route.ts index d685d5c..57c6240 100644 --- a/app/.well-known/lnurlp/[username]/callback/route.ts +++ b/app/.well-known/lnurlp/[username]/callback/route.ts @@ -1,6 +1,8 @@ import { NextRequest } from "next/server"; import { findWalletConnection } from "@/app/db"; import { nwc } from "@getalby/sdk"; +import { validateZapRequest } from "nostr-tools/nip57"; +import { NWC_POOL } from "@/app/nwc/nwcPool"; export async function GET( request: NextRequest, @@ -13,6 +15,7 @@ export async function GET( const searchParams = request.nextUrl.searchParams; const amount = searchParams.get("amount"); const comment = searchParams.get("comment") || ""; + const nostr = searchParams.get("nostr") || ""; if (!amount) { throw new Error("No amount provided"); @@ -22,11 +25,28 @@ export async function GET( username: params.username, }); + // subscribe to existing lightning addresses + if (!connection.subscribed) { + await NWC_POOL.subscribeUser(connection); + } + const nwcClient = new nwc.NWCClient({ nostrWalletConnectUrl: connection.id }); + let zapRequest: Event | undefined; + if (nostr) { + const zapValidationError = validateZapRequest(nostr); + if (zapValidationError) { + throw new Error(zapValidationError); + } + zapRequest = JSON.parse(nostr); + } + const transaction = await nwcClient.makeInvoice({ amount: +amount, description: comment, + metadata: { + nostr: zapRequest, + }, }); return Response.json({ diff --git a/app/.well-known/lnurlp/[username]/route.ts b/app/.well-known/lnurlp/[username]/route.ts index 4cc4433..ba8d11b 100644 --- a/app/.well-known/lnurlp/[username]/route.ts +++ b/app/.well-known/lnurlp/[username]/route.ts @@ -1,4 +1,6 @@ import { getDomain, getBaseUrl } from "@/app/utils"; +import { getPublicKey } from "nostr-tools"; +import { hexToBytes } from "@noble/hashes/utils"; export async function GET( request: Request, @@ -8,6 +10,7 @@ export async function GET( throw new Error("No username provided"); } const domain = getDomain(); + const NOSTR_NIP57_PRIVATE_KEY = process.env.NOSTR_NIP57_PRIVATE_KEY; return Response.json({ status: "OK", @@ -17,5 +20,11 @@ export async function GET( minSendable: 1000, maxSendable: 10000000000, metadata: `[["text/identifier","${params.username}@${domain}"],["text/plain","Sats for Alby Jim user ${params.username}"]]`, + ...(NOSTR_NIP57_PRIVATE_KEY + ? { + allowsNostr: true, + nostrPubkey: getPublicKey(hexToBytes(NOSTR_NIP57_PRIVATE_KEY)), + } + : {}), }); } diff --git a/app/actions.ts b/app/actions.ts index 7fe08f8..19baac9 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -1,6 +1,7 @@ "use server"; import { saveConnectionSecret, UsernameTakenError } from "./db"; +import { NWC_POOL } from "./nwc/nwcPool"; import { getAlbyHubUrl, getDailyWalletLimit, getDomain } from "./utils"; export type Reserves = { @@ -116,10 +117,13 @@ export async function createWallet( } appId = appInfo.id; - const { username } = await saveConnectionSecret( + const connectionSecret = await saveConnectionSecret( request?.username, newApp.pairingUri ); + await NWC_POOL.subscribeUser(connectionSecret); + + const { username } = connectionSecret; const domain = getDomain(); const lightningAddress = username + "@" + domain; @@ -135,7 +139,7 @@ export async function createWallet( error: undefined, }; } catch (error) { - console.error(error); + console.error("failed to create wallet", { error }); // only expose known errors if (error instanceof UsernameTakenError) { @@ -184,7 +188,7 @@ export async function getReserves(): Promise { totalChannelCapacity, }; } catch (error) { - console.error(error); + console.error("failed to get reserves", { error }); return undefined; } } diff --git a/app/api/http_nostr/route.ts b/app/api/http_nostr/route.ts new file mode 100644 index 0000000..db0c1d8 --- /dev/null +++ b/app/api/http_nostr/route.ts @@ -0,0 +1,52 @@ +import { findWalletConnection } from "@/app/db"; +import { Event } from "nostr-tools"; +import { decrypt } from "nostr-tools/nip04"; +import { nwc } from "@getalby/sdk"; +import { NWC_POOL } from "@/app/nwc/nwcPool"; + +export async function POST(request: Request) { + let body: Event | undefined; + try { + body = await request.json(); + if (!body) { + throw new Error("no body in request"); + } + + // get wallet + const pTagValue = body.tags.find((tag) => tag[0] === "p")?.[1]; + if (!pTagValue) { + throw new Error("p tag not found"); + } + const wallet = await findWalletConnection({ pubkey: pTagValue }); + if (!wallet) { + throw new Error("could not find wallet by pubkey"); + } + + // deserialize content + const parsedNWCUrl = nwc.NWCClient.parseWalletConnectUrl(wallet.id); + const secretKey = parsedNWCUrl.secret; + if (!secretKey) { + throw new Error("no secret key"); + } + // TODO: update to NIP-44 + const decryptedContent = await decrypt( + secretKey, + parsedNWCUrl.walletPubkey, + body.content + ); + + const notification = JSON.parse(decryptedContent) as nwc.Nip47Notification; + if (notification.notification_type !== "payment_received") { + console.debug("skipping notification that is not payment_received"); + return Response.json({}); + } + + // console.debug("publishing zap", { notification }); + + await NWC_POOL.publishZap(wallet, notification.notification); + } catch (error) { + console.error("failed to process http-nostr request", { body }); + } + + return Response.json({}); +} diff --git a/app/db.ts b/app/db.ts index de48ac8..4be1653 100644 --- a/app/db.ts +++ b/app/db.ts @@ -2,6 +2,7 @@ import { PrismaClient } from "@prisma/client"; import { nwc } from "@getalby/sdk"; import { getPublicKey } from "nostr-tools"; import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library"; +import { hexToBytes } from "@noble/hashes/utils"; const prisma = new PrismaClient(); @@ -14,12 +15,12 @@ export class UsernameTakenError extends Error { export async function saveConnectionSecret( username: string | undefined, connectionSecret: string -): Promise<{ username: string }> { +) { const parsed = nwc.NWCClient.parseWalletConnectUrl(connectionSecret); if (!parsed.secret) { throw new Error("no secret found in connection secret"); } - const pubkey = getPublicKey(parsed.secret); + const pubkey = getPublicKey(hexToBytes(parsed.secret)); username = username || pubkey.substring(0, 6); try { @@ -30,9 +31,9 @@ export async function saveConnectionSecret( pubkey, }, }); - return { username: result.username }; + return result; } catch (error) { - console.error("failed to save wallet", error); + console.error("failed to save wallet", { error }); if ( error instanceof PrismaClientKnownRequestError && error.code === "P2002" // unique constraint @@ -43,10 +44,25 @@ export async function saveConnectionSecret( } } -export async function findWalletConnection(query: { username: string }) { +export async function findWalletConnection( + query: { username: string } | { pubkey: string } +) { return prisma.connectionSecret.findUniqueOrThrow({ + where: query, + }); +} + +export async function markConnectionSecretSubscribed(id: string) { + return prisma.connectionSecret.update({ where: { - username: query.username, + id, + }, + data: { + subscribed: true, }, }); } + +export async function getAllConnections() { + return prisma.connectionSecret.findMany(); +} diff --git a/app/nwc/nwcPool.ts b/app/nwc/nwcPool.ts new file mode 100644 index 0000000..dac3232 --- /dev/null +++ b/app/nwc/nwcPool.ts @@ -0,0 +1,134 @@ +import { Event, finalizeEvent, getPublicKey, SimplePool } from "nostr-tools"; +import { makeZapReceipt } from "nostr-tools/nip57"; +import { nwc } from "@getalby/sdk"; +import { hexToBytes } from "@noble/hashes/utils"; +import { markConnectionSecretSubscribed } from "../db"; +import { ConnectionSecret } from "@prisma/client"; +import { getBaseUrl } from "../utils"; + +class NWCPool { + private readonly pool: SimplePool; + + constructor() { + this.pool = new SimplePool(); + } + + async subscribeUser(connectionSecret: ConnectionSecret) { + try { + const HTTP_NOSTR_URL = process.env.HTTP_NOSTR_URL; + if (!HTTP_NOSTR_URL) { + throw new Error("No HTTP_NOSTR_URL set"); + } + + console.debug("subscribing to user", { + username: connectionSecret.username, + }); + + const parsedNwcUrl = nwc.NWCClient.parseWalletConnectUrl( + connectionSecret.id + ); + if (!parsedNwcUrl.secret) { + throw new Error("no secret in NWC URL"); + } + + const result = await fetch(`${HTTP_NOSTR_URL}/nip47/notifications`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + webhookUrl: `${getBaseUrl()}/api/http_nostr`, + walletPubkey: parsedNwcUrl.walletPubkey, + connectionPubkey: getPublicKey(hexToBytes(parsedNwcUrl.secret)), + }), + }); + if (!result.ok) { + throw new Error( + "Failed to subscribe to http-nostr notifications: " + + result.status + + " " + + (await result.text()) + ); + } + + await markConnectionSecretSubscribed(connectionSecret.id); + } catch (error) { + console.error("failed to subscribe user", { error }); + } + } + + async publishZap( + connectionSecret: ConnectionSecret, + transaction: nwc.Nip47Transaction + ) { + const metadata = transaction.metadata; + const requestEvent = metadata?.nostr as Event; + + if (!requestEvent) { + return; + } + + const NOSTR_NIP57_PRIVATE_KEY = process.env.NOSTR_NIP57_PRIVATE_KEY; + + if (!NOSTR_NIP57_PRIVATE_KEY) { + throw new Error("no zapper private key set"); + } + + const zapReceipt = makeZapReceipt({ + zapRequest: JSON.stringify(requestEvent), + preimage: transaction.preimage, + bolt11: transaction.invoice, + paidAt: new Date(transaction.settled_at * 1000), + }); + const relays = requestEvent.tags + .find((tag) => tag[0] === "relays") + ?.slice(1); + if (!relays || !relays.length) { + console.error("no relays specified in zap request", { + username: connectionSecret.username, + transaction, + }); + return; + } + + const signedEvent = finalizeEvent( + zapReceipt, + hexToBytes(NOSTR_NIP57_PRIVATE_KEY) + ); + + const results = await Promise.allSettled( + this.pool.publish(relays, signedEvent) + ); + + const successfulRelays: string[] = []; + const failedRelays: string[] = []; + + results.forEach((result, index) => { + const relay = relays[index]; + if (result.status === "fulfilled") { + successfulRelays.push(relay); + } else { + failedRelays.push(relay); + } + }); + + if (failedRelays.length === relays.length) { + console.error("failed to publish zap", { + username: connectionSecret.username, + event_id: signedEvent.id, + payment_hash: transaction.payment_hash, + failed_relays: relays, + }); + return; + } + + console.debug("published zap", { + username: connectionSecret.username, + event_id: signedEvent.id, + payment_hash: transaction.payment_hash, + successful_relays: successfulRelays, + failed_relays: failedRelays, + }); + } +} +export const NWC_POOL = new NWCPool(); diff --git a/app/page.tsx b/app/page.tsx index 95862b3..5c32c14 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -45,7 +45,7 @@ export default function Home() { window.localStorage.setItem("wallet", JSON.stringify(wallet)); } } catch (error) { - console.error(error); + console.error("failed to create wallet", { error }); alert("Something went wrong: " + error); } setLoading(false); diff --git a/package.json b/package.json index 931e8a9..cc169b2 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "fly:start": "DATABASE_URL=\"file:/data/jim.db\" yarn start" }, "dependencies": { - "@getalby/bitcoin-connect-react": "^3.6.2", + "@getalby/bitcoin-connect-react": "^3.6.3", "@prisma/client": "5.17.0", "next": "14.2.5", "react": "^18", diff --git a/prisma/migrations/20250101092842_connection_secret_subscribed/migration.sql b/prisma/migrations/20250101092842_connection_secret_subscribed/migration.sql new file mode 100644 index 0000000..6a0cc0c --- /dev/null +++ b/prisma/migrations/20250101092842_connection_secret_subscribed/migration.sql @@ -0,0 +1,16 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_ConnectionSecret" ( + "id" TEXT NOT NULL PRIMARY KEY, + "pubkey" TEXT NOT NULL, + "username" TEXT NOT NULL, + "subscribed" BOOLEAN NOT NULL DEFAULT false +); +INSERT INTO "new_ConnectionSecret" ("id", "pubkey", "username") SELECT "id", "pubkey", "username" FROM "ConnectionSecret"; +DROP TABLE "ConnectionSecret"; +ALTER TABLE "new_ConnectionSecret" RENAME TO "ConnectionSecret"; +CREATE UNIQUE INDEX "ConnectionSecret_pubkey_key" ON "ConnectionSecret"("pubkey"); +CREATE UNIQUE INDEX "ConnectionSecret_username_key" ON "ConnectionSecret"("username"); +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f6f0e54..28a08ca 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -15,4 +15,5 @@ model ConnectionSecret { id String @id pubkey String @unique username String @unique + subscribed Boolean @default(false) } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c70c5ac..714ae45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,36 +50,36 @@ shell-quote "^1.8.1" yargs "^17.7.2" -"@getalby/bitcoin-connect-react@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@getalby/bitcoin-connect-react/-/bitcoin-connect-react-3.6.2.tgz#8f9416b9e761a25664571ee8be6f17bb7983c75e" - integrity sha512-5XeqBoiT7BuZHwGWSsCAMOrEqDrgJyyyOFnBi/hZZ+AsMBXRVqhgwst07Zbf4t7bNyXvphEYRTgisMvDQ42ErQ== +"@getalby/bitcoin-connect-react@^3.6.3": + version "3.6.3" + resolved "https://registry.yarnpkg.com/@getalby/bitcoin-connect-react/-/bitcoin-connect-react-3.6.3.tgz#74a60d5dfb89e2c2bbb356a242129d341900205a" + integrity sha512-tDomhNtXl94Z2YNQa52UpZUfZhdSwLEWgaOg6bCoLEJO0SqemUcKOrIIB/Y6DYm5XliktD7bLtvB4rJoFE74QQ== dependencies: - "@getalby/bitcoin-connect" "^3.6.2" + "@getalby/bitcoin-connect" "^3.6.3" -"@getalby/bitcoin-connect@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@getalby/bitcoin-connect/-/bitcoin-connect-3.6.2.tgz#2395b4a91d00dcfec5b9c2e6f5e998f1b2c66e03" - integrity sha512-JttIpbKWbkS5WA62S2xHQ2I6Ld5azJi99BLVcgjJoM6szpE4tlSuZi+oASduG8XTFMsiQ1GU57nqfiVXrv9ZyA== +"@getalby/bitcoin-connect@^3.6.3": + version "3.6.3" + resolved "https://registry.yarnpkg.com/@getalby/bitcoin-connect/-/bitcoin-connect-3.6.3.tgz#357c0f3c17b49c487d08a40fee85714e78361fbc" + integrity sha512-mS3hmKGF8P7RH06DFtawc6T738iwz+wGz28XR46tMDKGfZjPrpcCG7R8Wy7n0w1JBgf7Nec79edQ5cDM1Pbrrw== dependencies: - "@getalby/lightning-tools" "^5.0.3" - "@getalby/sdk" "^3.6.1" - "@lightninglabs/lnc-web" "^0.3.1-alpha" + "@getalby/lightning-tools" "^5.1.0" + "@getalby/sdk" "^3.8.0" + "@lightninglabs/lnc-web" "^0.3.2-alpha" qrcode-generator "^1.4.4" - zustand "^4.5.4" + zustand "^4.5.5" -"@getalby/lightning-tools@^5.0.3": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@getalby/lightning-tools/-/lightning-tools-5.0.3.tgz#4cc6ef1253a30fb4913af89b842645e0c04994bf" - integrity sha512-QG3/SBI5n2py5IgsjP3K+c8eq55eiI3PQB12yo9Pot0b5hcN7TNNoTKn0fgLJjO1iEVCUkF513kDOpjjXwK0hQ== +"@getalby/lightning-tools@^5.1.0": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@getalby/lightning-tools/-/lightning-tools-5.1.2.tgz#8a018e98d5c13097dd98d93192cf5e4e455f4c20" + integrity sha512-BwGm8eGbPh59BVa1gI5yJMantBl/Fdps6X4p1ZACnmxz9vDINX8/3aFoOnDlF7yyA2boXWCsReVQSr26Q2yjiQ== -"@getalby/sdk@^3.6.1": - version "3.6.1" - resolved "https://registry.yarnpkg.com/@getalby/sdk/-/sdk-3.6.1.tgz#b25dfd254572a44072a47cf677fcf647b0b19515" - integrity sha512-hg3WvRXQUf9S9NE1QZaz50IrOOnBr0QehlTRBENX2wRbZPSdpuyVOc4LWyI9tme8hOabeLbNpEl6iMZ7gZENwQ== +"@getalby/sdk@^3.8.0": + version "3.8.2" + resolved "https://registry.yarnpkg.com/@getalby/sdk/-/sdk-3.8.2.tgz#84a184c46fdebf18652d6c06b92f07ed36129d3d" + integrity sha512-0F4ub/e+t93V9wzR5Vr+Xdfhhy5kK+ZKls/J3yX2YBT27X1Rd3QIPLCTUFCb4RaV6a/e17aZAVJF8Af7r9BeAg== dependencies: - eventemitter3 "^5.0.1" - nostr-tools "^1.17.0" + emittery "^1.0.3" + nostr-tools "2.9.4" "@humanwhocodes/config-array@^0.11.14": version "0.11.14" @@ -144,17 +144,17 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@lightninglabs/lnc-core@0.3.1-alpha": - version "0.3.1-alpha" - resolved "https://registry.yarnpkg.com/@lightninglabs/lnc-core/-/lnc-core-0.3.1-alpha.tgz#cfd6c0857a20013fb1819b40bd1158a2edc8bcf0" - integrity sha512-I/hThdItLWJ6RU8Z27ZIXhpBS2JJuD3+TjtaQXX2CabaUYXlcN4sk+Kx8N/zG/fk8qZvjlRWum4vHu4ZX554Fg== +"@lightninglabs/lnc-core@0.3.2-alpha": + version "0.3.2-alpha" + resolved "https://registry.yarnpkg.com/@lightninglabs/lnc-core/-/lnc-core-0.3.2-alpha.tgz#bde028a858a77d78af4885df8bd95b036103b736" + integrity sha512-H6tG+X9txCIdxTR+GPsbImzP2Juo+6Uvq/Ipaijd7xPISzgEU4J4GNE5PEHuIZqbnBo1RmpuXnFG6dmsl3PTzQ== -"@lightninglabs/lnc-web@^0.3.1-alpha": - version "0.3.1-alpha" - resolved "https://registry.yarnpkg.com/@lightninglabs/lnc-web/-/lnc-web-0.3.1-alpha.tgz#bbeccff8f2b0071468681642d47c97f8c953020b" - integrity sha512-yL5SgBkl6kd6ISzJHGlSN7TXbiDoo1pfGvTOIdVWYVyXtEeW8PT+x6YGOmyQXGFT2OOf7fC7PfP9VnskDPuFaA== +"@lightninglabs/lnc-web@^0.3.2-alpha": + version "0.3.2-alpha" + resolved "https://registry.yarnpkg.com/@lightninglabs/lnc-web/-/lnc-web-0.3.2-alpha.tgz#ea07a2ff366690b38bbe1f7ce8903bc54390fed4" + integrity sha512-3aCBugBf0NzczpJqmHn03Oq2Ju9W5n0+nOdAe+Y/Zhf6YLXdqG1PTJ2J+7TXncpiogfPYDCw95tVQqSi4Zi/ZA== dependencies: - "@lightninglabs/lnc-core" "0.3.1-alpha" + "@lightninglabs/lnc-core" "0.3.2-alpha" crypto-js "4.2.0" "@next/env@14.2.5": @@ -214,12 +214,19 @@ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz#ed199a920efb510cfe941cd75ed38a7be21e756f" integrity sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g== -"@noble/ciphers@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.2.0.tgz#a12cda60f3cf1ab5d7c77068c3711d2366649ed7" - integrity sha512-6YBxJDAapHSdd3bLDv6x2wRPwq4QFMUaB3HvljNBUTThDd12eSm7/3F+2lnfzx2jvM+S6Nsy0jEt9QbPqSwqRw== +"@noble/ciphers@^0.5.1": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.5.3.tgz#48b536311587125e0d0c1535f73ec8375cd76b23" + integrity sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w== + +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" -"@noble/curves@1.1.0", "@noble/curves@~1.1.0": +"@noble/curves@~1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== @@ -231,6 +238,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": version "1.3.3" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" @@ -968,6 +980,11 @@ ejs@^3.1.9: dependencies: jake "^10.8.5" +emittery@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-1.0.3.tgz#c9d2a9c689870f15251bb13b31c67715c26d69ac" + integrity sha512-tJdCJitoy2lrC2ldJcqN4vkqJ00lT+tOWNT1hBJjO/3FDMJa5TTIiYGCKGkn/WfCyOzUMObeohbVTj00fhiLiA== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -1335,11 +1352,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2134,17 +2146,24 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -nostr-tools@^1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.17.0.tgz#b6f62e32fedfd9e68ec0a7ce57f74c44fc768e8c" - integrity sha512-LZmR8GEWKZeElbFV5Xte75dOeE9EFUW/QLI1Ncn3JKn0kFddDKEfBbFN8Mu4TMs+L4HR/WTPha2l+PPuRnJcMw== +nostr-tools@2.9.4: + version "2.9.4" + resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-2.9.4.tgz#ec0e1faa95bf9e5fee30b36c842a270135f40183" + integrity sha512-Powumwkp+EWbdK1T8IsEX4daTLQhtWJvitfZ6OP2BdU1jJZvNlUp3SQB541UYw4uc9jgLbxZW6EZSdZoSfIygQ== dependencies: - "@noble/ciphers" "0.2.0" - "@noble/curves" "1.1.0" + "@noble/ciphers" "^0.5.1" + "@noble/curves" "1.2.0" "@noble/hashes" "1.3.1" "@scure/base" "1.1.1" "@scure/bip32" "1.3.1" "@scure/bip39" "1.2.1" + optionalDependencies: + nostr-wasm v0.1.0 + +nostr-wasm@v0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/nostr-wasm/-/nostr-wasm-0.1.0.tgz#17af486745feb2b7dd29503fdd81613a24058d94" + integrity sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA== object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" @@ -3112,7 +3131,7 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zustand@^4.5.4: +zustand@^4.5.5: version "4.5.5" resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.5.tgz#f8c713041543715ec81a2adda0610e1dc82d4ad1" integrity sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==