diff --git a/.vscode/settings.json b/.vscode/settings.json index 1b6457c..adbacfc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", + "prettier.trailingComma": "all" } diff --git a/examples/nwc/list-transactions.js b/examples/nwc/list-transactions.js new file mode 100644 index 0000000..adf9fd8 --- /dev/null +++ b/examples/nwc/list-transactions.js @@ -0,0 +1,37 @@ +import * as crypto from "node:crypto"; // required in node.js +global.crypto = crypto; // required in node.js +import "websocket-polyfill"; // required in node.js + +import * as readline from "node:readline/promises"; +import { stdin as input, stdout as output } from "node:process"; + +import { webln as providers } from "../../dist/index.module.js"; + +const rl = readline.createInterface({ input, output }); + +const nwcUrl = + process.env.NWC_URL || + (await rl.question("Nostr Wallet Connect URL (nostr+walletconnect://...): ")); +rl.close(); + +const webln = new providers.NostrWebLNProvider({ + nostrWalletConnectUrl: nwcUrl, +}); +await webln.enable(); + +const ONE_WEEK_IN_SECONDS = 60 * 60 * 24 * 7; +const response = await webln.listTransactions({ + from: Math.floor(new Date().getTime() / 1000 - ONE_WEEK_IN_SECONDS), + until: Math.ceil(new Date().getTime() / 1000), + limit: 30, + // type: "incoming", +}); + +console.info( + response.transactions.length + "transactions, ", + response.transactions.filter((t) => t.type === "incoming").length + + " incoming", + response, +); + +webln.close(); diff --git a/examples/nwc/lookup-invoice.js b/examples/nwc/lookup-invoice.js index 0d6e382..4ca5f3f 100644 --- a/examples/nwc/lookup-invoice.js +++ b/examples/nwc/lookup-invoice.js @@ -9,9 +9,11 @@ import { webln as providers } from "../../dist/index.module.js"; const rl = readline.createInterface({ input, output }); -const nwcUrl = await rl.question( - "Nostr Wallet Connect URL (nostr+walletconnect://...): ", -); +const nwcUrl = + process.env.NWC_URL || + (await rl.question("Nostr Wallet Connect URL (nostr+walletconnect://...): ")); + +const invoiceOrPaymentHash = await rl.question("Invoice or payment hash: "); rl.close(); const webln = new providers.NostrWebLNProvider({ @@ -20,9 +22,12 @@ const webln = new providers.NostrWebLNProvider({ await webln.enable(); const response = await webln.lookupInvoice({ // provide one of the below - //invoice: 'lnbc10n1pjwxschpp5hg0pw234n9ww9q4uy25pnvu8y4jzpznysasyf7m9fka36t7fahysdqufet5xgzhv43ycn3qv4uxzmtsd3jscqzzsxqyz5vqsp5uw023qhxuxqfj69rvj9yns5gufczad5gqw4uer5cgqhw90slkavs9qyyssqvv2tw6c30ssgtpejc3zk7ns0svuj8547d8wxj0e36hltljx5a8x4qj59mk2y7qlt6qazf2j38fzc8uag3887nslxz6fe3vnyvg0f2xqqnlvcu2', - payment_hash: - "ba1e172a35995ce282bc22a819b3872564208a64876044fb654dbb1d2fc9edc9", + invoice: invoiceOrPaymentHash.startsWith("ln") + ? invoiceOrPaymentHash + : undefined, + payment_hash: !invoiceOrPaymentHash.startsWith("ln") + ? invoiceOrPaymentHash + : undefined, }); console.info(response); diff --git a/examples/nwc/make-invoice.js b/examples/nwc/make-invoice.js index a8a931a..e147cac 100644 --- a/examples/nwc/make-invoice.js +++ b/examples/nwc/make-invoice.js @@ -9,9 +9,9 @@ import { webln as providers } from "../../dist/index.module.js"; const rl = readline.createInterface({ input, output }); -const nwcUrl = await rl.question( - "Nostr Wallet Connect URL (nostr+walletconnect://...): ", -); +const nwcUrl = + process.env.NWC_URL || + (await rl.question("Nostr Wallet Connect URL (nostr+walletconnect://...): ")); rl.close(); const webln = new providers.NostrWebLNProvider({ diff --git a/prettierrc.json b/prettierrc.json index 757fd64..bf357fb 100644 --- a/prettierrc.json +++ b/prettierrc.json @@ -1,3 +1,3 @@ { - "trailingComma": "es5" + "trailingComma": "all" } diff --git a/src/webln/NostrWeblnProvider.ts b/src/webln/NostrWeblnProvider.ts index d86174a..93bbd41 100644 --- a/src/webln/NostrWeblnProvider.ts +++ b/src/webln/NostrWeblnProvider.ts @@ -36,6 +36,37 @@ const NWCs: Record = { }, }; +// TODO: move to webln-types package +export interface ListTransactionsArgs { + from?: number; + until?: number; + limit?: number; + offset?: number; + unpaid?: boolean; + type?: "incoming" | "outgoing"; +} + +// TODO: move to webln-types package +export interface Transaction { + type: string; + invoice: string; + description: string; + description_hash: string; + preimage: string; + payment_hash: string; + amount: number; + fees_paid: number; + settled_at: number; + created_at: number; + expires_at: number; + metadata?: Record; +} + +// TODO: move to webln-types package +export interface ListTransactionsResponse { + transactions: Transaction[]; +} + interface NostrWebLNOptions { authorizationUrl?: string; // the URL to the NWC interface for the user to confirm the session relayUrl: string; @@ -58,6 +89,11 @@ type Nip47GetInfoResponse = { methods: string[]; }; +type Nip47ListTransactionsResponse = { + transactions: Nip47Transaction[]; +}; +type Nip47Transaction = Transaction; + type Nip47PayResponse = { preimage: string; }; @@ -69,6 +105,7 @@ const nip47ToWeblnRequestMap = { pay_invoice: "sendPayment", pay_keysend: "payKeysend", lookup_invoice: "lookupInvoice", + list_transactions: "listTransactions", }; export class NostrWebLNProvider implements WebLNProvider, Nip07Provider { @@ -336,7 +373,7 @@ export class NostrWebLNProvider implements WebLNProvider, Nip07Provider { throw new Error("No amount specified"); } - return this.executeNip47Request( + return this.executeNip47Request( "make_invoice", { amount: amount * 1000, // NIP-47 uses msat @@ -353,14 +390,33 @@ export class NostrWebLNProvider implements WebLNProvider, Nip07Provider { lookupInvoice(args: LookupInvoiceArgs) { this.checkConnected(); + return this.executeNip47Request( + "lookup_invoice", + args, + (result) => !!result.invoice, + (result) => ({ + paymentRequest: result.invoice, + paid: !!result.settled_at, + }), + ); + } + + listTransactions(args: ListTransactionsArgs) { + this.checkConnected(); + + // maybe we can tailor the response to our needs return this.executeNip47Request< - LookupInvoiceResponse, - { invoice: string; paid: boolean } + ListTransactionsResponse, + Nip47ListTransactionsResponse >( - "lookup_invoice", + "list_transactions", args, - (result) => result.invoice !== undefined && result.paid !== undefined, - (result) => ({ paymentRequest: result.invoice, paid: result.paid }), + (response) => !!response.transactions, + (response) => ({ + transactions: response.transactions.map( + mapNip47TransactionToTransaction, + ), + }), ); } @@ -532,7 +588,7 @@ export class NostrWebLNProvider implements WebLNProvider, Nip07Provider { } // @ts-ignore // event is still unknown in nostr-tools if (event.kind == 23195 && response.result) { - //console.log("NIP-47 result", response.result); + // console.info("NIP-47 result", response.result); if (resultValidator(response.result)) { resolve(resultMapper(response.result)); this.notify(weblnMethod, response.result); @@ -572,4 +628,17 @@ export class NostrWebLNProvider implements WebLNProvider, Nip07Provider { } } +function mapNip47TransactionToTransaction( + transaction: Nip47Transaction, +): Transaction { + return { + ...transaction, + // NWC uses msats - convert to sats for webln + amount: Math.floor(transaction.amount / 1000), + fees_paid: transaction.fees_paid + ? Math.floor(transaction.fees_paid / 1000) + : 0, + }; +} + export const NWC = NostrWebLNProvider;