diff --git a/SKILL.md b/SKILL.md index 8702191..1bdfcc1 100644 --- a/SKILL.md +++ b/SKILL.md @@ -16,11 +16,16 @@ Use this skill to understand how to build apps that require bitcoin lightning wa - [NWC Client: Interact with a wallet to do things like sending and receive payments, listen to payment notifications, fetch balance and transaction list and wallet info](./references/nwc-client/nwc-client.md) - [Lightning Tools: Request invoices from a lightning address, parse BOLT-11 invoices, verify a preimage for a BOLT-11 invoice, LNURL-Verify, do bitcoin <-> fiat conversions](./references/lightning-tools/lightning-tools.md) - [Bitcoin Connect: Browser-only UI components for connecting wallets and accepting payments in React, Vue, or pure HTML web apps](./references/bitcoin-connect/bitcoin-connect.md) +- [HTTP-Nostr: Bridge for serverless environments (Vercel, AWS Lambda) to handle notifications via Webhooks instead of WebSockets](./references/http-nostr/http-nostr.md) ## Prefer Typescript When the user says to use "JS" or "Javascript" or "NodeJS" or something similar, use typescript unless the user explicitly says to not use typescript or the project does not support it. +## Environment Constraints +- **Persistent (Node.js/VPS/Docker):** Use standard `client.subscribeNotifications()` (WebSockets). +- **Serverless (Vercel/Lambda):** WebSockets are NOT supported. You MUST use the [HTTP-Nostr Webhook API](./references/http-nostr/http-nostr.md) via `https://api.getalby.com/nwc`. Do not instruct the user to self-host databases or Go servers unless explicitly requested. + ## Imports Do NOT import from the dist directory. @@ -33,6 +38,7 @@ Based on what functionality you require, read the relevant typings: - [Lightning Tools](./references/lightning-tools/index.d.ts) - [Bitcoin Connect](./references/bitcoin-connect/bundle.d.ts) - [Bitcoin Connect React](./references/bitcoin-connect/react.bundle.d.ts) +- [HTTP-Nostr Bridge](./references/http-nostr/index.d.ts) ## Testing Wallets @@ -46,4 +52,4 @@ It is recommended to write tests so that the agent can test its own work and fix ## Production Wallet -If they do not have a wallet yet [here are some options](./references/production-wallets.md) \ No newline at end of file +If they do not have a wallet yet [here are some options](./references/production-wallets.md). diff --git a/references/http-nostr/general-nostr-events.md b/references/http-nostr/general-nostr-events.md new file mode 100644 index 0000000..b614ff1 --- /dev/null +++ b/references/http-nostr/general-nostr-events.md @@ -0,0 +1,53 @@ +# General Nostr Events & Subscriptions (HTTP-Nostr) + +**IMPORTANT: read the [typings](./index.d.ts) to better understand how this works.** + +This reference covers publishing arbitrary events to the Nostr network, subscribing to custom event filters via webhooks, and managing/cleaning up active database subscriptions. + +## 1. Publish Generic Event +Publishes any signed Nostr event (outside of NIP-47 specific requests) to a specified relay. + +```ts +/** + * Endpoint: POST /publish + */ +async publishEvent(req: PublishRequest): Promise { + return this.post("/publish", req); +} + +``` + +## 2. Subscribe to Custom Events + +Creates a webhook subscription based on custom Nostr filters (kinds, authors, tags, etc.). The bridge will push matching events to the `webhookUrl`. + +```ts +/** + * Endpoint: POST /subscriptions + */ +async subscribeGeneral(req: SubscriptionRequest): Promise { + return this.post("/subscriptions", req); +} + +``` + +## 3. Delete Subscriptions (Cleanup) + +Stops an active subscription and removes it from the HTTP-Nostr PostgreSQL database. **Always do this when a subscription is no longer needed to free up resources**. + +```ts +/** + * Endpoint: DELETE /subscriptions/:id + */ +async stopSubscription(id: string): Promise { + const res = await fetch(`${this.baseUrl}/subscriptions/${id}`, { + method: "DELETE", + }); + if (!res.ok) { + const err = await res.json(); + throw new Error(`HTTP-Nostr Error [${res.status}]: ${err.message || res.statusText}`); + } + return res.json() as Promise; +} + +``` \ No newline at end of file diff --git a/references/http-nostr/http-nostr.md b/references/http-nostr/http-nostr.md new file mode 100644 index 0000000..9c5f82e --- /dev/null +++ b/references/http-nostr/http-nostr.md @@ -0,0 +1,34 @@ +# HTTP-Nostr (Serverless Bridge) + +HTTP-Nostr is a bridge for serverless environments where persistent WebSockets are not supported. It converts NIP-47 Nostr events into HTTP Webhooks. + +## ☁️ Deployment Options + +You have two paths for using this bridge. **Always default to Option A (Hosted) unless the user explicitly asks to self-host.** + +### Option A: Alby Hosted API + +Alby provides a fully managed, public instance of the HTTP-Nostr bridge. + +- **Zero Setup Required.** +- **Base URL:** `https://api.getalby.com/nwc` +- **Usage:** Simply initialize the `HttpNostrClient` with this URL. + +### Option B: Self-Hosted + +If the user wants total sovereignty, they can run the bridge on their own infrastructure. + +- **Requirements:** Go executable and a PostgreSQL database. +- **Setup:** + 1. `git clone https://github.com/getAlby/http-nostr.git` + 2. Configure `.env` with `PORT`, `DATABASE_URI`, and `ENCRYPTION_KEY` (16/24/32 bytes). + 3. Run: `go run cmd/server/main.go`. +- **Base URL:** `http://localhost:8080` (or their custom domain). + +## API Reference & Implementation Guides + +To implement features using this bridge, refer to the strict TypeScript definitions and domain-specific guides below: + +- **[TypeScript Definitions (`index.d.ts`)](./index.d.ts)**: Contains the exact request and response interfaces for all endpoints. **Always read this first**. +- **[NIP-47 Wallet Actions](./nip47-wallet-actions.md)**: Methods for Lightning wallet operations, including fetching capabilities (`/nip47/info`), sending payments (`/nip47`), and setting up webhook notifications (`/nip47/notifications`). +- **[General Nostr Events](./general-nostr-events.md)**: Methods to publish arbitrary events (`/publish`), subscribe to custom filters (`/subscriptions`), and clean up active database subscriptions (`DELETE /subscriptions/:id`). diff --git a/references/http-nostr/index.d.ts b/references/http-nostr/index.d.ts new file mode 100644 index 0000000..dd55358 --- /dev/null +++ b/references/http-nostr/index.d.ts @@ -0,0 +1,109 @@ +// Types here are gotten directly from http-nostr/internal/nostr/models.go file, +// but rewritten in TypeScript. + +// --- Base Nostr Types --- +export interface NostrEvent { + id: string; + pubkey: string; + created_at: number; + kind: number; + tags: string[][]; + content: string; + sig: string; +} + +export interface NostrFilter { + ids?: string[]; + authors?: string[]; + kinds?: number[]; + since?: number; + until?: number; + limit?: number; + search?: string; + [key: `#${string}`]: string[] | undefined; // Supports #e, #p, etc. +} + +// --- Request & Response Interfaces --- + +export interface ErrorResponse { + message: string; + error: string; +} + +export interface InfoRequest { + relayUrl?: string; + walletPubkey: string; +} + +export interface InfoResponse { + event: NostrEvent; +} + +export interface NIP47Request { + relayUrl?: string; + walletPubkey: string; + event: NostrEvent; // SignedEvent +} + +export interface NIP47WebhookRequest { + relayUrl?: string; + walletPubkey: string; + webhookUrl: string; + event: NostrEvent; // SignedEvent +} + +export interface NIP47NotificationRequest { + relayUrl?: string; + webhookUrl: string; + walletPubkey: string; + connectionPubkey: string; + version?: string; +} + +export interface NIP47PushNotificationRequest { + relayUrl?: string; + pushToken: string; + walletPubkey: string; + connectionPubkey: string; + isIOS?: boolean; + version?: string; +} + +export interface NIP47Response { + event?: NostrEvent; + state: string; // "PUBLISHED", "ALREADY_PROCESSED", "WEBHOOK_RECEIVED" +} + +export interface PublishRequest { + relayUrl?: string; + event: NostrEvent; // SignedEvent +} + +export interface PublishResponse { + eventId: string; + relayUrl: string; + state: string; +} + +export interface SubscriptionRequest { + relayUrl?: string; + webhookUrl: string; + filter: NostrFilter; +} + +export interface SubscriptionResponse { + subscription_id: string; + webhookUrl: string; +} + +export interface PushSubscriptionResponse { + subscriptionId: string; + pushToken: string; + walletPubkey: string; + appPubkey: string; +} + +export interface StopSubscriptionResponse { + message: string; + state: string; // "CLOSED", "ALREADY_CLOSED" +} diff --git a/references/http-nostr/nip47-wallet-actions.md b/references/http-nostr/nip47-wallet-actions.md new file mode 100644 index 0000000..1e31b61 --- /dev/null +++ b/references/http-nostr/nip47-wallet-actions.md @@ -0,0 +1,73 @@ +# NIP-47 Wallet Actions (HTTP-Nostr) + +**IMPORTANT: read the [typings](./index.d.ts) to better understand how this works.** + +This reference covers interacting with a user's Lightning Wallet via the HTTP-Nostr bridge. Use these typed methods to handle fetching wallet capabilities, sending payments, and listening to wallet-specific notifications (Kind 23196). + +## 1. Fetch NWC Capabilities +Check if a wallet connection has permissions (like `pay_invoice` or `get_balance`) before attempting a transaction. + +```ts +/** + * Endpoint: POST /nip47/info + */ +async getInfo(req: InfoRequest): Promise { + return this.post("/nip47/info", req); +} + +``` + +## 2. Publish NWC Request (Synchronous) + +Publishes a signed NIP-47 request event (e.g., `pay_invoice`) and waits for the relay to return the response immediately. + +```ts +/** + * Endpoint: POST /nip47 + */ +async publishNip47(req: NIP47Request): Promise { + return this.post("/nip47", req); +} + +``` + +## 3. Publish NWC Request (Asynchronous / Webhook) + +Best for serverless environments. Fires the request and instructs the bridge to send the response to your `webhookUrl` to avoid function timeouts. + +```ts +/** + * Endpoint: POST /nip47/webhook + */ +async publishNip47Webhook(req: NIP47WebhookRequest): Promise { + return this.post("/nip47/webhook", req); +} + +``` + +## 4. Subscribe to NWC Notifications + +Registers a webhook to receive incoming wallet notifications (NIP-47 Kind `23196`), such as received payments. + +```ts +/** + * Endpoint: POST /nip47/notifications + */ +async subscribeNotifications(req: NIP47NotificationRequest): Promise { + return this.post("/nip47/notifications", req); +} + +``` + +## 5. Subscribe to NWC Push Notifications (Mobile/Expo) + +Registers a mobile device push token to receive wallet notifications directly via Expo. + +```ts +/** + * Endpoint: POST /nip47/notifications/push + */ +async subscribePushNotifications(req: NIP47PushNotificationRequest): Promise { + return this.post("/nip47/notifications/push", req); +} +``` \ No newline at end of file