From 0cb5917101cc6ff6c9d126d28170542ef186fc68 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Fri, 17 Nov 2023 15:54:01 +0700 Subject: [PATCH] feat: decode invoice --- README.md | 8 ++++++-- examples/decode-invoice.js | 35 +++++++++++++++++++++++++++++++++++ src/client.ts | 28 ++++++++++++++++++++++++++-- src/types.ts | 31 +++++++++++++++++++++++++------ 4 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 examples/decode-invoice.js diff --git a/README.md b/README.md index f2b5378..de8e1ab 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,7 @@ Please have a look a the Alby OAuth2 Wallet API: - outgoingInvoices - getInvoice - createInvoice +- decodeInvoice - keysend - sendPayment - sendBoostagram @@ -454,9 +455,12 @@ console.log(response.keysends); For quick invoice decoding without an API request please see Alby's [Lightning Tools package](https://github.com/getAlby/js-lightning-tools#basic-invoice-decoding). -For more invoice details you can use the Alby Wallet API. +For more invoice details you can use the Alby Wallet API: -**COMING SOON** +```js +const decodedInvoice = await client.decodeInvoice(paymentRequest); +const {payment_hash, amount, description, ...} = decodedInvoice; +``` ## fetch() dependency diff --git a/examples/decode-invoice.js b/examples/decode-invoice.js new file mode 100644 index 0000000..e2771aa --- /dev/null +++ b/examples/decode-invoice.js @@ -0,0 +1,35 @@ +import * as readline from "node:readline/promises"; +import { stdin as input, stdout as output } from "node:process"; +import { auth, Client } from "../dist/index.module.js"; + +const rl = readline.createInterface({ input, output }); + +const paymentRequest = + "lnbc10u1pj4t6w0pp54wm83znxp8xly6qzuff2z7u6585rnlcw9uduf2haa42qcz09f5wqdq023jhxapqd4jk6mccqzzsxqyz5vqsp5mlvjs8nktpz98s5dcrhsuelrz94kl2vjukvu789yzkewast6m00q9qyyssqupynqdv7e5y8nlul0trva5t97g7v3gwx7akhu2dvu4pn66eu2pr5zkcnegp8myz3wrpj9ht06pwyfn4dvpmnr96ejq6ygex43ymaffqq3gud4d"; + +const authClient = new auth.OAuth2User({ + client_id: process.env.CLIENT_ID, + client_secret: process.env.CLIENT_SECRET, + callback: "http://localhost:8080", + scopes: ["invoices:read"], // this scope isn't needed, but at least one scope is required to get an access token + token: { + access_token: undefined, + refresh_token: undefined, + expires_at: undefined, + }, // initialize with existing token +}); + +console.log(`Open the following URL and authenticate the app:`); +console.log(authClient.generateAuthURL()); +console.log("----\n"); + +const code = await rl.question("Code: (localhost:8080?code=[THIS CODE]: "); +rl.close(); + +await authClient.requestAccessToken(code); +console.log(authClient.token); +const client = new Client(authClient); + +const response = await client.decodeInvoice(paymentRequest); + +console.log(JSON.stringify(response, null, 2)); diff --git a/src/client.ts b/src/client.ts index a34fd62..8b98c9a 100644 --- a/src/client.ts +++ b/src/client.ts @@ -8,6 +8,7 @@ import { CreateSwapResponse, CreateWebhookEndpointParams, CreateWebhookEndpointResponse, + DecodedInvoice, GetAccountBalanceResponse, GetAccountInformationResponse, GetInvoicesRequestParams, @@ -37,6 +38,7 @@ export class Client { } accountBalance( + // eslint-disable-next-line @typescript-eslint/ban-types params: {}, request_options?: Partial, ): Promise { @@ -50,7 +52,11 @@ export class Client { }); } - accountSummary(params: {}, request_options?: Partial) { + accountSummary( + // eslint-disable-next-line @typescript-eslint/ban-types + params: {}, + request_options?: Partial, + ) { return rest({ auth: this.auth, ...this.defaultRequestOptions, @@ -62,6 +68,7 @@ export class Client { } accountInformation( + // eslint-disable-next-line @typescript-eslint/ban-types params: {}, request_options?: Partial, ): Promise { @@ -75,7 +82,11 @@ export class Client { }); } - accountValue4Value(params: {}, request_options?: Partial) { + accountValue4Value( + // eslint-disable-next-line @typescript-eslint/ban-types + params: {}, + request_options?: Partial, + ) { return rest({ auth: this.auth, ...this.defaultRequestOptions, @@ -141,6 +152,19 @@ export class Client { }); } + decodeInvoice( + paymentRequest: string, + request_options?: Partial, + ): Promise { + return rest({ + auth: this.auth, + ...this.defaultRequestOptions, + ...request_options, + endpoint: `/decode/bolt11/${paymentRequest}`, + method: "GET", + }); + } + createInvoice( invoice: InvoiceRequestParams, request_options?: Partial, diff --git a/src/types.ts b/src/types.ts index edc2fe4..701da69 100644 --- a/src/types.ts +++ b/src/types.ts @@ -63,11 +63,11 @@ export abstract class AuthClient { } // https://stackoverflow.com/a/50375286 -export type UnionToIntersection = ( - U extends any ? (k: U) => void : never -) extends (k: infer I) => void - ? I - : never; +export type UnionToIntersection = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (U extends any ? (k: U) => void : never) extends (k: infer I) => void + ? I + : never; export type GetSuccess = { [K in SuccessStatus & keyof T]: GetContent; @@ -85,7 +85,7 @@ export type ExtractAlbyResponse = "responses" extends keyof T ? GetSuccess : never; -export type GetInvoicesRequestParams = { +export type GetInvoicesRequestParams = { q?: { since?: string; created_at_lt?: string; @@ -257,4 +257,23 @@ export type GetAccountInformationResponse = { nostr_pubkey?: string; }; +export type DecodedInvoice = { + currency: string; + /** + * unix timestamp in seconds + */ + created_at: number; + /** + * expiry from the created_at time in seconds (not a timestamp) + */ + expiry: number; + payee: string; + msatoshi: number; + description: string; + payment_hash: string; + min_final_cltv_expiry: number; + amount: number; + payee_alias: string; +}; + export { AlbyResponseError, RequestOptions };