Align CLINK and Lightning.Pub packages with actual protocol specs

CLINK Protocol (@lamassu/clink):
- Add CLINKErrorCode enum (codes 1-6) matching Lightning.Pub
- Update OfferRequest to use { offer, amount_sats, payer_data, ... }
- Update OfferResponse to { bolt11 } or { code, error, range }
- Update DebitRequest to { amount_sats, memo, rules }
- Update DebitResponse to { res: 'ok'|'GFY', preimage?, error?, code? }
- Add ManagementRequest/Response with proper action types
- Add ServiceBeacon type for Kind 30078 discovery
- Add DebitRules for recurring payment authorization
- Change all amounts from millisatoshis to satoshis
- Add helper functions: createOfferSuccess, createOfferError, etc.
- Add type guards: isOfferError, isDebitFailure, isManagementFailure

Lightning.Pub RPC (@lamassu/lightning):
- Change from NIP-47 kinds (23194/23195) to Kind 21000 RPC
- Add proper RPC request format: { rpcName, body, authIdentifier, requestId, appId }
- Add RPC response handling: { status: 'OK'|'ERROR', reason?, ...result }
- Add LightningPubEventKind enum
- Add isRPCError type guard
- Remove unused types (ChannelInfo, NodeInfo)
- Update all amounts from millisatoshis to satoshis

This aligns our implementation with the actual Lightning.Pub source code
at /home/padreug/Work/tries/2026-01-22-lamassu-refactor-packages/Lightning.Pub

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-01-23 06:18:04 -05:00
commit d5a77c4311
7 changed files with 754 additions and 312 deletions

View file

@ -6,6 +6,8 @@
* - Handling offer requests and generating invoices * - Handling offer requests and generating invoices
* - Processing debit requests * - Processing debit requests
* - Receiving management commands * - Receiving management commands
*
* Uses NIP-44v2 encryption for all messages.
*/ */
import type { Event, UnsignedEvent } from 'nostr-tools' import type { Event, UnsignedEvent } from 'nostr-tools'
@ -14,16 +16,25 @@ import type { MachineIdentity, NostrClient } from '@lamassu/nostr-client'
import { encryptContent, decryptJSON } from '@lamassu/nostr-client' import { encryptContent, decryptJSON } from '@lamassu/nostr-client'
import { import {
CLINKEventKind, CLINKEventKind,
CLINKErrorCode,
type CLINKOffer, type CLINKOffer,
type OfferRequest, type OfferRequest,
type OfferResponse, type OfferResponse,
type OfferSuccessResponse,
type OfferErrorResponse,
type DebitRequest, type DebitRequest,
type DebitResponse, type DebitResponse,
type ManagementDelegation, type DebitSuccessResponse,
type DebitFailureResponse,
type ManagementRequest,
type ManagementResponse,
type ServiceBeacon,
type GenerateInvoice, type GenerateInvoice,
type PayInvoice, type PayInvoice,
isOfferError,
isDebitFailure,
} from './types.js' } from './types.js'
import { encodeNoffer } from './noffer.js' import { encodeNoffer, decodeNoffer } from './noffer.js'
/** CLINK client options */ /** CLINK client options */
export interface CLINKClientOptions { export interface CLINKClientOptions {
@ -41,23 +52,23 @@ export interface CLINKClientOptions {
payInvoice?: PayInvoice payInvoice?: PayInvoice
} }
/** Offer request handler */ /** Offer request handler - returns invoice or error */
export type OfferRequestHandler = ( export type OfferRequestHandler = (
request: OfferRequest, request: OfferRequest,
senderPubkey: string senderPubkey: string
) => Promise<OfferResponse | null> ) => Promise<OfferResponse | null>
/** Debit request handler */ /** Debit request handler - returns success/failure */
export type DebitRequestHandler = ( export type DebitRequestHandler = (
request: DebitRequest, request: DebitRequest,
senderPubkey: string senderPubkey: string
) => Promise<DebitResponse> ) => Promise<DebitResponse>
/** Management command handler */ /** Management request handler */
export type ManagementHandler = ( export type ManagementHandler = (
delegation: ManagementDelegation, request: ManagementRequest,
senderPubkey: string senderPubkey: string
) => Promise<void> ) => Promise<ManagementResponse | null>
/** /**
* CLINK protocol client for ATM payments * CLINK protocol client for ATM payments
@ -90,40 +101,47 @@ export class CLINKClient {
*/ */
createOffer(options: { createOffer(options: {
priceType: CLINKOffer['priceType'] priceType: CLINKOffer['priceType']
amountMsat?: number amountSats?: number
description?: string description?: string
minAmountMsat?: number minAmountSats?: number
maxAmountMsat?: number maxAmountSats?: number
}): string { }): string {
const offer: CLINKOffer = { const offer: CLINKOffer = {
pubkey: this.identity.publicKey, pubkey: this.identity.publicKey,
relays: this.relays, relays: this.relays,
priceType: options.priceType, priceType: options.priceType,
amountMsat: options.amountMsat, amountSats: options.amountSats,
description: options.description, description: options.description,
minAmountMsat: options.minAmountMsat, minAmountSats: options.minAmountSats,
maxAmountMsat: options.maxAmountMsat, maxAmountSats: options.maxAmountSats,
} }
return encodeNoffer(offer) return encodeNoffer(offer)
} }
/** /**
* Set handler for incoming offer requests * Decode a noffer string
*/
decodeOffer(noffer: string): CLINKOffer {
return decodeNoffer(noffer)
}
/**
* Set handler for incoming offer requests (Kind 21001)
*/ */
onOfferRequest(handler: OfferRequestHandler): void { onOfferRequest(handler: OfferRequestHandler): void {
this.offerHandler = handler this.offerHandler = handler
} }
/** /**
* Set handler for incoming debit requests * Set handler for incoming debit requests (Kind 21002)
*/ */
onDebitRequest(handler: DebitRequestHandler): void { onDebitRequest(handler: DebitRequestHandler): void {
this.debitHandler = handler this.debitHandler = handler
} }
/** /**
* Set handler for management commands * Set handler for management commands (Kind 21003)
*/ */
onManagement(handler: ManagementHandler): void { onManagement(handler: ManagementHandler): void {
this.managementHandler = handler this.managementHandler = handler
@ -159,28 +177,59 @@ export class CLINKClient {
} }
/** /**
* Send an offer request to another pubkey * Request an invoice from a noffer (Kind 21001)
*/ */
async requestOffer(targetPubkey: string, request: OfferRequest): Promise<OfferResponse> { async requestOffer(
const content = encryptContent(this.identity, targetPubkey, request) noffer: string,
amountSats: number,
options?: {
payerData?: Record<string, string>
description?: string
expiresInSeconds?: number
}
): Promise<OfferResponse> {
const offer = decodeNoffer(noffer)
const request: OfferRequest = {
offer: noffer,
amount_sats: amountSats,
payer_data: options?.payerData,
description: options?.description,
expires_in_seconds: options?.expiresInSeconds,
}
const content = encryptContent(this.identity, offer.pubkey, request)
const event = this.createSignedEvent({ const event = this.createSignedEvent({
kind: CLINKEventKind.Offer, kind: CLINKEventKind.Offer,
content, content,
tags: [['p', targetPubkey]], tags: [['p', offer.pubkey]],
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
}) })
await this.nostrClient.publish(event) await this.nostrClient.publish(event)
// Wait for response // Wait for response
return this.waitForResponse<OfferResponse>(targetPubkey, CLINKEventKind.Offer) return this.waitForResponse<OfferResponse>(offer.pubkey, CLINKEventKind.Offer, event.id)
} }
/** /**
* Send a debit request * Send a debit request (Kind 21002)
*/ */
async requestDebit(targetPubkey: string, request: DebitRequest): Promise<DebitResponse> { async requestDebit(
targetPubkey: string,
amountSats: number,
options?: {
memo?: string
rules?: DebitRequest['rules']
}
): Promise<DebitResponse> {
const request: DebitRequest = {
amount_sats: amountSats,
memo: options?.memo,
rules: options?.rules,
}
const content = encryptContent(this.identity, targetPubkey, request) const content = encryptContent(this.identity, targetPubkey, request)
const event = this.createSignedEvent({ const event = this.createSignedEvent({
@ -192,7 +241,50 @@ export class CLINKClient {
await this.nostrClient.publish(event) await this.nostrClient.publish(event)
return this.waitForResponse<DebitResponse>(targetPubkey, CLINKEventKind.Debit) return this.waitForResponse<DebitResponse>(targetPubkey, CLINKEventKind.Debit, event.id)
}
/**
* Send a management request (Kind 21003)
*/
async sendManagementRequest(
targetPubkey: string,
request: ManagementRequest
): Promise<ManagementResponse> {
const content = encryptContent(this.identity, targetPubkey, request)
const event = this.createSignedEvent({
kind: CLINKEventKind.Manage,
content,
tags: [['p', targetPubkey]],
created_at: Math.floor(Date.now() / 1000),
})
await this.nostrClient.publish(event)
return this.waitForResponse<ManagementResponse>(targetPubkey, CLINKEventKind.Manage, event.id)
}
/**
* Discover service beacon (Kind 30078)
*/
async discoverService(servicePubkey: string): Promise<ServiceBeacon | null> {
const events = await this.nostrClient.queryEvents([
{
kinds: [CLINKEventKind.Beacon],
authors: [servicePubkey],
'#d': ['Lightning.Pub'],
limit: 1,
},
])
if (events.length === 0) return null
try {
return JSON.parse(events[0]?.content ?? '{}') as ServiceBeacon
} catch {
return null
}
} }
/** /**
@ -217,7 +309,7 @@ export class CLINKClient {
} }
/** /**
* Handle offer request/response * Handle offer request (Kind 21001)
*/ */
private async handleOfferEvent(event: Event): Promise<void> { private async handleOfferEvent(event: Event): Promise<void> {
if (!this.offerHandler) return if (!this.offerHandler) return
@ -244,7 +336,7 @@ export class CLINKClient {
} }
/** /**
* Handle debit request * Handle debit request (Kind 21002)
*/ */
private async handleDebitEvent(event: Event): Promise<void> { private async handleDebitEvent(event: Event): Promise<void> {
if (!this.debitHandler) return if (!this.debitHandler) return
@ -270,7 +362,7 @@ export class CLINKClient {
} }
/** /**
* Handle management command * Handle management command (Kind 21003)
*/ */
private async handleManageEvent(event: Event): Promise<void> { private async handleManageEvent(event: Event): Promise<void> {
// Only accept from operator // Only accept from operator
@ -281,15 +373,31 @@ export class CLINKClient {
if (!this.managementHandler) return if (!this.managementHandler) return
const delegation = decryptJSON<ManagementDelegation>(this.identity, event.pubkey, event.content) const request = decryptJSON<ManagementRequest>(this.identity, event.pubkey, event.content)
await this.managementHandler(delegation, event.pubkey) const response = await this.managementHandler(request, event.pubkey)
if (!response) return
// Send encrypted response
const content = encryptContent(this.identity, event.pubkey, response)
const responseEvent = this.createSignedEvent({
kind: CLINKEventKind.Manage,
content,
tags: [
['p', event.pubkey],
['e', event.id],
],
created_at: Math.floor(Date.now() / 1000),
})
await this.nostrClient.publish(responseEvent)
} }
/** /**
* Wait for a response event * Wait for a response event
*/ */
private waitForResponse<T>(fromPubkey: string, kind: number): Promise<T> { private waitForResponse<T>(fromPubkey: string, kind: number, requestEventId: string): Promise<T> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
this.nostrClient.unsubscribe(subId) this.nostrClient.unsubscribe(subId)
@ -302,6 +410,7 @@ export class CLINKClient {
kinds: [kind], kinds: [kind],
authors: [fromPubkey], authors: [fromPubkey],
'#p': [this.identity.publicKey], '#p': [this.identity.publicKey],
'#e': [requestEventId],
since: Math.floor(Date.now() / 1000) - 5, since: Math.floor(Date.now() / 1000) - 5,
}, },
], ],
@ -329,3 +438,42 @@ export class CLINKClient {
return finalizeEvent(event, this.identity.privateKey) return finalizeEvent(event, this.identity.privateKey)
} }
} }
// ============================================================================
// Helper functions for creating responses
// ============================================================================
/**
* Create an offer success response
*/
export function createOfferSuccess(bolt11: string): OfferSuccessResponse {
return { bolt11 }
}
/**
* Create an offer error response
*/
export function createOfferError(
code: CLINKErrorCode,
error: string,
range?: { min: number; max: number }
): OfferErrorResponse {
return { code, error, range }
}
/**
* Create a debit success response
*/
export function createDebitSuccess(preimage: string): DebitSuccessResponse {
return { res: 'ok', preimage }
}
/**
* Create a debit failure response
*/
export function createDebitFailure(code: CLINKErrorCode, error: string): DebitFailureResponse {
return { res: 'GFY', code, error }
}
// Re-export type guards
export { isOfferError, isDebitFailure }

View file

@ -3,12 +3,18 @@
* *
* CLINK protocol implementation for Nostr-native Lightning payments. * CLINK protocol implementation for Nostr-native Lightning payments.
* *
* CLINK enables static payment codes (noffers) that work over Nostr, * CLINK enables:
* providing a decentralized alternative to BOLT12 offers. * - Static payment codes (noffers) for receiving payments
* - Offer requests/responses for invoice generation (Kind 21001)
* - Debit authorization for outgoing payments (Kind 21002)
* - Management delegation for remote control (Kind 21003)
* - Service discovery via beacons (Kind 30078)
*
* All CLINK messages use NIP-44v2 encryption.
* *
* @example * @example
* ```typescript * ```typescript
* import { CLINKClient, encodeNoffer, decodeNoffer } from '@lamassu/clink' * import { CLINKClient, encodeNoffer, createOfferSuccess } from '@lamassu/clink'
* *
* // Create a client * // Create a client
* const clink = new CLINKClient({ * const clink = new CLINKClient({
@ -26,42 +32,66 @@
* *
* // Handle incoming offer requests * // Handle incoming offer requests
* clink.onOfferRequest(async (request, senderPubkey) => { * clink.onOfferRequest(async (request, senderPubkey) => {
* const invoice = await generateInvoice(request.amountMsat) * const bolt11 = await generateInvoice(request.amount_sats)
* return { * return createOfferSuccess(bolt11)
* invoice,
* amountMsat: request.amountMsat,
* expiresAt: Date.now() / 1000 + 600,
* }
* }) * })
* *
* clink.startListening() * clink.startListening()
* ``` * ```
*/ */
// Client
export { CLINKClient } from './client.js'
export type {
CLINKClientOptions,
OfferRequestHandler,
DebitRequestHandler,
ManagementHandler,
} from './client.js'
// noffer encoding/decoding
export { encodeNoffer, decodeNoffer, isValidNoffer } from './noffer.js'
// Types // Types
export { export {
// Event kinds
CLINKEventKind, CLINKEventKind,
NofferTLV, CLINKErrorCode,
type PriceType, // Offer types (Kind 21001)
type CLINKOffer,
type OfferRequest, type OfferRequest,
type OfferResponse, type OfferResponse,
type OfferSuccessResponse,
type OfferErrorResponse,
// Debit types (Kind 21002)
type DebitRequest, type DebitRequest,
type DebitResponse, type DebitResponse,
type DebitSuccessResponse,
type DebitFailureResponse,
type DebitRules,
type DebitInterval,
// Management types (Kind 21003)
type ManagementRequest,
type ManagementResponse,
type ManagementSuccessResponse,
type ManagementFailureResponse,
type ManagementAction, type ManagementAction,
type ManagementDelegation, type OfferConfig,
// Beacon types (Kind 30078)
type ServiceBeacon,
// Noffer types
type CLINKOffer,
type PriceType,
NofferTLV,
// Function types
type GenerateInvoice, type GenerateInvoice,
type PayInvoice, type PayInvoice,
// Type guards
isOfferError,
isDebitFailure,
isManagementFailure,
} from './types.js' } from './types.js'
// Noffer encoding/decoding
export { encodeNoffer, decodeNoffer, isValidNoffer } from './noffer.js'
// Client
export {
CLINKClient,
type CLINKClientOptions,
type OfferRequestHandler,
type DebitRequestHandler,
type ManagementHandler,
// Response helpers
createOfferSuccess,
createOfferError,
createDebitSuccess,
createDebitFailure,
} from './client.js'

View file

@ -34,9 +34,9 @@ export function encodeNoffer(offer: CLINKOffer): string {
const priceTypeByte = encodePriceType(offer.priceType) const priceTypeByte = encodePriceType(offer.priceType)
writeTLV(tlvData, NofferTLV.PriceType, [priceTypeByte]) writeTLV(tlvData, NofferTLV.PriceType, [priceTypeByte])
// Amount (TLV 3) - if present // Amount (TLV 3) - if present (in satoshis)
if (offer.amountMsat !== undefined) { if (offer.amountSats !== undefined) {
const amountBytes = encodeUint64BE(offer.amountMsat) const amountBytes = encodeUint64BE(offer.amountSats)
writeTLV(tlvData, NofferTLV.Amount, amountBytes) writeTLV(tlvData, NofferTLV.Amount, amountBytes)
} }
@ -71,7 +71,7 @@ export function decodeNoffer(noffer: string): CLINKOffer {
let pubkey: string | undefined let pubkey: string | undefined
const relays: string[] = [] const relays: string[] = []
let priceType: PriceType = 'spontaneous' let priceType: PriceType = 'spontaneous'
let amountMsat: number | undefined let amountSats: number | undefined
let description: string | undefined let description: string | undefined
let offset = 0 let offset = 0
@ -102,7 +102,7 @@ export function decodeNoffer(noffer: string): CLINKOffer {
priceType = decodePriceType(value[0] ?? 2) priceType = decodePriceType(value[0] ?? 2)
break break
case NofferTLV.Amount: case NofferTLV.Amount:
amountMsat = decodeUint64BE(value) amountSats = decodeUint64BE(value)
break break
case NofferTLV.Description: case NofferTLV.Description:
description = new TextDecoder().decode(value) description = new TextDecoder().decode(value)
@ -123,7 +123,7 @@ export function decodeNoffer(noffer: string): CLINKOffer {
pubkey, pubkey,
relays, relays,
priceType, priceType,
amountMsat, amountSats,
description, description,
} }
} }

View file

@ -5,22 +5,236 @@
* static payment codes (noffers) over Nostr relays. * static payment codes (noffers) over Nostr relays.
* *
* Event kinds: * Event kinds:
* - 21000: Generic RPC request/response
* - 21001: Offer request/response * - 21001: Offer request/response
* - 21002: Debit request/response * - 21002: Debit request/response
* - 21003: Management delegation * - 21003: Management request/response
* - 30078: Service beacon (replaceable)
*
* All CLINK messages use NIP-44v2 encryption.
*/ */
/** CLINK event kinds */ /** CLINK event kinds */
export enum CLINKEventKind { export enum CLINKEventKind {
/** Generic RPC request/response */
RPC = 21000,
/** Offer request/response */ /** Offer request/response */
Offer = 21001, Offer = 21001,
/** Debit request/response (authorized payments) */ /** Debit request/response (authorized payments) */
Debit = 21002, Debit = 21002,
/** Management delegation */ /** Management request/response */
Manage = 21003, Manage = 21003,
/** Service beacon (replaceable event) */
Beacon = 30078,
} }
/** Offer price type */ /** CLINK error codes */
export enum CLINKErrorCode {
/** Request was denied (warning) */
RequestDenied = 1,
/** Temporary failure, may retry */
TemporaryFailure = 2,
/** Request has expired */
ExpiredRequest = 3,
/** Rate limited */
RateLimited = 4,
/** Invalid amount (out of range) */
InvalidAmount = 5,
/** Invalid request format */
InvalidRequest = 6,
}
// ============================================================================
// Kind 21001: Offer (Noffer)
// ============================================================================
/** Offer request (Kind 21001) - sent by payer to service */
export interface OfferRequest {
/** Offer identifier from service beacon or noffer string */
offer: string
/** Amount in satoshis */
amount_sats: number
/** Custom payer metadata */
payer_data?: Record<string, string>
/** Payment description */
description?: string
/** Custom expiry in seconds */
expires_in_seconds?: number
/** Whether this is a zap payment */
zap?: boolean
}
/** Offer success response (Kind 21001) */
export interface OfferSuccessResponse {
/** BOLT-11 Lightning invoice */
bolt11: string
}
/** Offer error response (Kind 21001) */
export interface OfferErrorResponse {
/** Error code */
code: CLINKErrorCode
/** Human-readable error message */
error: string
/** Acceptable amount range (for InvalidAmount errors) */
range?: { min: number; max: number }
}
/** Offer response - either success or error */
export type OfferResponse = OfferSuccessResponse | OfferErrorResponse
/** Type guard for offer error response */
export function isOfferError(response: OfferResponse): response is OfferErrorResponse {
return 'code' in response && 'error' in response
}
// ============================================================================
// Kind 21002: Debit (Ndebit)
// ============================================================================
/** Debit frequency interval */
export type DebitInterval = 'day' | 'week' | 'month'
/** Debit rules for recurring payments */
export interface DebitRules {
/** Expiration rule */
expiration?: {
/** Unix timestamp when authorization expires */
expires_at_unix: number
}
/** Frequency rule for recurring debits */
frequency?: {
/** Number of intervals allowed */
number_of_intervals: number
/** Interval type */
interval: DebitInterval
/** Maximum amount per interval in sats */
amount: number
}
}
/** Debit request (Kind 21002) - sent by service to request payment authorization */
export interface DebitRequest {
/** Amount in satoshis */
amount_sats: number
/** Optional memo/description */
memo?: string
/** Rules for recurring debits */
rules?: DebitRules
}
/** Debit success response (Kind 21002) */
export interface DebitSuccessResponse {
/** Success indicator */
res: 'ok'
/** Payment preimage (proof of payment) */
preimage: string
}
/** Debit failure response (Kind 21002) */
export interface DebitFailureResponse {
/** Failure indicator (Generic Failure) */
res: 'GFY'
/** Error message */
error: string
/** Error code */
code: CLINKErrorCode
}
/** Debit response - either success or failure */
export type DebitResponse = DebitSuccessResponse | DebitFailureResponse
/** Type guard for debit failure */
export function isDebitFailure(response: DebitResponse): response is DebitFailureResponse {
return response.res === 'GFY'
}
// ============================================================================
// Kind 21003: Management (Nmanage)
// ============================================================================
/** Management action types */
export type ManagementAction = 'create' | 'update' | 'delete' | 'list' | 'get'
/** Offer configuration for create/update */
export interface OfferConfig {
/** Human-readable label */
label?: string
/** Fixed price in satoshis */
price_sats?: number
/** Callback URL for payment notifications */
callback_url?: string
/** Expected payer data fields */
payer_data?: string[]
/** Use blinded paths for privacy */
blind?: boolean
}
/** Management request (Kind 21003) */
export interface ManagementRequest {
/** Action to perform */
action: ManagementAction
/** App user identifier/pointer */
pointer?: string
/** Offer configuration (for create/update) */
offer?: OfferConfig
/** Offer ID (for update/delete/get) */
offer_id?: string
}
/** Management success response (Kind 21003) */
export interface ManagementSuccessResponse {
/** Action that was performed */
action: ManagementAction
/** Result data (varies by action) */
result: unknown
}
/** Management failure response (Kind 21003) */
export interface ManagementFailureResponse {
/** Failure indicator */
res: 'GFY'
/** Error message */
error: string
/** Error code */
code: CLINKErrorCode
/** Retry after (seconds) for rate limiting */
retry_after?: number
}
/** Management response */
export type ManagementResponse = ManagementSuccessResponse | ManagementFailureResponse
/** Type guard for management failure */
export function isManagementFailure(
response: ManagementResponse
): response is ManagementFailureResponse {
return 'res' in response && response.res === 'GFY'
}
// ============================================================================
// Kind 30078: Service Beacon
// ============================================================================
/** Service beacon content (Kind 30078) */
export interface ServiceBeacon {
/** Beacon type */
type: 'service' | 'provider'
/** Service name */
name: string
/** Service avatar URL */
avatarUrl?: string
/** Fee structure */
fees?: Record<string, number>
/** Optional relay URL for additional communication */
nextRelay?: string
}
// ============================================================================
// Noffer (Static Payment Code) encoding
// ============================================================================
/** Offer price type for noffer encoding */
export type PriceType = 'fixed' | 'variable' | 'spontaneous' export type PriceType = 'fixed' | 'variable' | 'spontaneous'
/** CLINK Offer (noffer) configuration */ /** CLINK Offer (noffer) configuration */
@ -31,81 +245,14 @@ export interface CLINKOffer {
relays: string[] relays: string[]
/** Pricing model */ /** Pricing model */
priceType: PriceType priceType: PriceType
/** Amount in millisatoshis (for fixed price) */ /** Amount in satoshis (for fixed price) */
amountMsat?: number amountSats?: number
/** Human-readable description */ /** Human-readable description */
description?: string description?: string
/** Minimum amount in msats (for variable/spontaneous) */ /** Minimum amount in sats (for variable/spontaneous) */
minAmountMsat?: number minAmountSats?: number
/** Maximum amount in msats (for variable/spontaneous) */ /** Maximum amount in sats (for variable/spontaneous) */
maxAmountMsat?: number maxAmountSats?: number
}
/** Offer request (Kind 21001) */
export interface OfferRequest {
/** Requested amount in millisatoshis */
amountMsat: number
/** Fiat amount (for ATM context) */
fiatAmount?: number
/** Fiat currency code */
fiatCurrency?: string
/** Sender's callback pubkey for response */
senderPubkey: string
/** Optional message */
message?: string
}
/** Offer response (Kind 21001) */
export interface OfferResponse {
/** BOLT11 invoice to pay */
invoice: string
/** Amount in millisatoshis */
amountMsat: number
/** Optional description */
description?: string
/** Expiry timestamp */
expiresAt: number
}
/** Debit request (Kind 21002) */
export interface DebitRequest {
/** Amount to debit in millisatoshis */
amountMsat: number
/** Recipient's pubkey */
recipientPubkey: string
/** Optional description */
description?: string
/** Unique request ID for idempotency */
requestId: string
}
/** Debit response (Kind 21002) */
export interface DebitResponse {
/** Whether the debit was successful */
success: boolean
/** Payment preimage (proof of payment) */
preimage?: string
/** Error message if failed */
error?: string
/** Matching request ID */
requestId: string
}
/** Management action (Kind 21003) */
export type ManagementAction =
| { type: 'disable' }
| { type: 'enable' }
| { type: 'set_limits'; minMsat: number; maxMsat: number }
| { type: 'revoke' }
/** Management delegation (Kind 21003) */
export interface ManagementDelegation {
/** Target machine/account pubkey */
targetPubkey: string
/** Delegated action */
action: ManagementAction
/** Timestamp */
timestamp: number
} }
/** noffer TLV types */ /** noffer TLV types */
@ -116,14 +263,18 @@ export enum NofferTLV {
Relay = 1, Relay = 1,
/** Price type (1 byte: 0=fixed, 1=variable, 2=spontaneous) */ /** Price type (1 byte: 0=fixed, 1=variable, 2=spontaneous) */
PriceType = 2, PriceType = 2,
/** Amount in msats (8 bytes, big-endian) */ /** Amount in sats (8 bytes, big-endian) */
Amount = 3, Amount = 3,
/** Description (variable length string) */ /** Description (variable length string) */
Description = 4, Description = 4,
} }
// ============================================================================
// Function types for service injection
// ============================================================================
/** Invoice generation function type */ /** Invoice generation function type */
export type GenerateInvoice = (amountMsat: number) => Promise<string> export type GenerateInvoice = (amountSats: number, description?: string) => Promise<string>
/** Payment function type */ /** Payment function type */
export type PayInvoice = (invoice: string) => Promise<{ preimage: string } | { error: string }> export type PayInvoice = (invoice: string) => Promise<{ preimage: string } | { error: string }>

View file

@ -5,52 +5,41 @@
* account system that wraps LND. * account system that wraps LND.
* *
* Lightning.Pub provides: * Lightning.Pub provides:
* - Account management (sublayers) * - Account management
* - Invoice generation * - Invoice generation via RPC (kind 21000)
* - Payment processing * - Payment processing via RPC (kind 21000)
* - CLINK protocol support * - CLINK protocol support (kinds 21001-21003)
*
* This client handles the RPC layer. For CLINK payments,
* use @lamassu/clink.
*/ */
import type { Event } from 'nostr-tools' import type { Event, UnsignedEvent } from 'nostr-tools'
import { finalizeEvent } from 'nostr-tools' import { finalizeEvent } from 'nostr-tools'
import type { MachineIdentity, NostrClient } from '@lamassu/nostr-client' import type { MachineIdentity, NostrClient } from '@lamassu/nostr-client'
import { encryptContent, decryptJSON } from '@lamassu/nostr-client' import { encryptContent, decryptJSON } from '@lamassu/nostr-client'
import type { import {
LightningPubConfig, LightningPubEventKind,
AccountInfo, type LightningPubConfig,
Invoice, type RPCRequest,
PaymentResult, type RPCResponse,
ExchangeRate, type AccountInfo,
InvoiceCallback, type BalanceResponse,
type Invoice,
type CreateInvoiceParams,
type CreateInvoiceResponse,
type LookupInvoiceResponse,
type PaymentResult,
type PayInvoiceResponse,
type ExchangeRate,
type InvoiceCallback,
isRPCError,
} from './types.js' } from './types.js'
/** Event kinds for Lightning.Pub communication */
const LIGHTNING_PUB_KINDS = {
REQUEST: 23194, // NIP-47 compatible
RESPONSE: 23195,
}
/** Request types */
type RequestMethod =
| 'get_info'
| 'get_balance'
| 'make_invoice'
| 'pay_invoice'
| 'lookup_invoice'
| 'list_transactions'
interface Request {
method: RequestMethod
params: Record<string, unknown>
}
interface Response<T = unknown> {
result?: T
error?: { code: number; message: string }
}
/** /**
* Lightning.Pub client for ATM operations * Lightning.Pub client for ATM operations
*
* Uses Kind 21000 for RPC communication with Lightning.Pub service.
*/ */
export class LightningPubClient { export class LightningPubClient {
private config: Required<LightningPubConfig> private config: Required<LightningPubConfig>
@ -58,10 +47,12 @@ export class LightningPubClient {
private identity: MachineIdentity | null = null private identity: MachineIdentity | null = null
private invoiceCallbacks: Map<string, InvoiceCallback> = new Map() private invoiceCallbacks: Map<string, InvoiceCallback> = new Map()
private subscriptionId?: string private subscriptionId?: string
private requestCounter = 0
constructor(config: LightningPubConfig) { constructor(config: LightningPubConfig) {
this.config = { this.config = {
timeout: 30000, timeout: 30000,
appId: 'lamassu-atm',
...config, ...config,
} }
} }
@ -79,51 +70,39 @@ export class LightningPubClient {
* Get account information * Get account information
*/ */
async getAccountInfo(): Promise<AccountInfo> { async getAccountInfo(): Promise<AccountInfo> {
const response = await this.sendRequest<AccountInfo>({ const response = await this.sendRPC<AccountInfo>('GetInfo', {})
method: 'get_info',
params: {},
})
return response return {
pubkey: response.pubkey ?? this.config.accountPubkey,
balanceSats: response.balanceSats ?? 0,
maxSendSats: response.maxSendSats ?? 0,
maxReceiveSats: response.maxReceiveSats ?? 0,
}
} }
/** /**
* Get current balance * Get current balance
*/ */
async getBalance(): Promise<{ balanceSats: number }> { async getBalance(): Promise<{ balanceSats: number }> {
const response = await this.sendRequest<{ balance: number }>({ const response = await this.sendRPC<BalanceResponse>('GetBalance', {})
method: 'get_balance', return { balanceSats: response.balance ?? 0 }
params: {},
})
return { balanceSats: response.balance }
} }
/** /**
* Create a Lightning invoice * Create a Lightning invoice
*/ */
async createInvoice(params: { async createInvoice(params: CreateInvoiceParams): Promise<Invoice> {
amountMsat: number const response = await this.sendRPC<CreateInvoiceResponse>('NewInvoice', {
description?: string amount: params.amountSats,
expirySecs?: number memo: params.description || 'ATM Payment',
}): Promise<Invoice> { expiry: params.expirySecs || 3600,
const response = await this.sendRequest<{ private: params.privateHints ?? false,
payment_request: string
payment_hash: string
expires_at: number
}>({
method: 'make_invoice',
params: {
amount: params.amountMsat,
description: params.description || 'ATM Payment',
expiry: params.expirySecs || 600,
},
}) })
return { return {
paymentRequest: response.payment_request, paymentRequest: response.payment_request,
paymentHash: response.payment_hash, paymentHash: response.payment_hash,
amountMsat: params.amountMsat, amountSats: params.amountSats,
description: params.description, description: params.description,
createdAt: Math.floor(Date.now() / 1000), createdAt: Math.floor(Date.now() / 1000),
expiresAt: response.expires_at, expiresAt: response.expires_at,
@ -136,20 +115,14 @@ export class LightningPubClient {
*/ */
async payInvoice(paymentRequest: string): Promise<PaymentResult> { async payInvoice(paymentRequest: string): Promise<PaymentResult> {
try { try {
const response = await this.sendRequest<{ const response = await this.sendRPC<PayInvoiceResponse>('PayInvoice', {
preimage: string
fee_paid?: number
}>({
method: 'pay_invoice',
params: {
invoice: paymentRequest, invoice: paymentRequest,
},
}) })
return { return {
success: true, success: true,
preimage: response.preimage, preimage: response.preimage,
feeMsat: response.fee_paid, feeSats: response.fee_paid,
} }
} catch (error) { } catch (error) {
return { return {
@ -164,25 +137,14 @@ export class LightningPubClient {
*/ */
async lookupInvoice(paymentHash: string): Promise<Invoice | null> { async lookupInvoice(paymentHash: string): Promise<Invoice | null> {
try { try {
const response = await this.sendRequest<{ const response = await this.sendRPC<LookupInvoiceResponse>('LookupInvoice', {
payment_request: string
amount: number
description: string
created_at: number
expires_at: number
settled: boolean
preimage?: string
}>({
method: 'lookup_invoice',
params: {
payment_hash: paymentHash, payment_hash: paymentHash,
},
}) })
return { return {
paymentRequest: response.payment_request, paymentRequest: response.payment_request,
paymentHash, paymentHash,
amountMsat: response.amount, amountSats: response.amount,
description: response.description, description: response.description,
createdAt: response.created_at, createdAt: response.created_at,
expiresAt: response.expires_at, expiresAt: response.expires_at,
@ -228,42 +190,43 @@ export class LightningPubClient {
} }
/** /**
* Decode a BOLT11 invoice * Decode a BOLT11 invoice (basic parsing)
*
* For production use, consider using a proper bolt11 library.
*/ */
decodeInvoice(paymentRequest: string): { decodeInvoice(paymentRequest: string): {
amountMsat: number | null amountSats: number | null
paymentHash: string paymentHash: string
description: string description: string
expiresAt: number expiresAt: number
} { } {
// Basic invoice parsing (for production, use bolt11 library) // Basic invoice parsing
// This is a simplified version
const amountMatch = paymentRequest.match(/lnbc(\d+)([munp]?)/) const amountMatch = paymentRequest.match(/lnbc(\d+)([munp]?)/)
let amountMsat: number | null = null let amountSats: number | null = null
if (amountMatch) { if (amountMatch) {
const [, amount, multiplier] = amountMatch const [, amount, multiplier] = amountMatch
const baseAmount = parseInt(amount ?? '0', 10) const baseAmount = parseInt(amount ?? '0', 10)
switch (multiplier) { switch (multiplier) {
case 'm': case 'm':
amountMsat = baseAmount * 100_000_000 amountSats = baseAmount * 100_000 // milli-BTC to sats
break break
case 'u': case 'u':
amountMsat = baseAmount * 100_000 amountSats = baseAmount * 100 // micro-BTC to sats
break break
case 'n': case 'n':
amountMsat = baseAmount * 100 amountSats = Math.floor(baseAmount / 10) // nano-BTC to sats
break break
case 'p': case 'p':
amountMsat = baseAmount / 10 amountSats = Math.floor(baseAmount / 10_000) // pico-BTC to sats
break break
default: default:
amountMsat = baseAmount * 100_000_000_000 amountSats = baseAmount * 100_000_000 // BTC to sats
} }
} }
return { return {
amountMsat, amountSats,
paymentHash: '', // Would need proper parsing paymentHash: '', // Would need proper parsing
description: '', description: '',
expiresAt: Math.floor(Date.now() / 1000) + 3600, expiresAt: Math.floor(Date.now() / 1000) + 3600,
@ -271,11 +234,12 @@ export class LightningPubClient {
} }
/** /**
* Get exchange rate (via external service or Lightning.Pub) * Get exchange rate (mock implementation)
*
* For production, integrate with price feeds or Lightning.Pub's rate service.
*/ */
async getExchangeRate(currency: string): Promise<ExchangeRate> { async getExchangeRate(currency: string): Promise<ExchangeRate> {
// For now, return a mock rate // Mock rates - in production, fetch from service
// In production, integrate with price feeds
const mockRates: Record<string, number> = { const mockRates: Record<string, number> = {
USD: 2500, // sats per dollar (example) USD: 2500, // sats per dollar (example)
EUR: 2700, EUR: 2700,
@ -291,7 +255,7 @@ export class LightningPubClient {
} }
/** /**
* Start listening for responses * Start listening for RPC responses
*/ */
private startListening(): void { private startListening(): void {
if (!this.nostrClient || !this.identity) return if (!this.nostrClient || !this.identity) return
@ -300,8 +264,9 @@ export class LightningPubClient {
this.subscriptionId = this.nostrClient.subscribe( this.subscriptionId = this.nostrClient.subscribe(
[ [
{ {
kinds: [LIGHTNING_PUB_KINDS.RESPONSE], kinds: [LightningPubEventKind.RPC],
'#p': [this.identity.publicKey], '#p': [this.identity.publicKey],
authors: [this.config.accountPubkey],
}, },
], ],
{ {
@ -313,25 +278,35 @@ export class LightningPubClient {
/** /**
* Handle incoming response events * Handle incoming response events
*/ */
private handleResponse(event: Event): void { private handleResponse(_event: Event): void {
// Handle async responses if needed // Responses are handled by waitForResponse
// For invoice watching, we use polling instead // This is for async notifications if needed
} }
/** /**
* Send a request to Lightning.Pub * Send an RPC request to Lightning.Pub
*/ */
private async sendRequest<T>(request: Request): Promise<T> { private async sendRPC<T>(rpcName: string, body: unknown): Promise<T> {
if (!this.nostrClient || !this.identity) { if (!this.nostrClient || !this.identity) {
throw new Error('Client not initialized') throw new Error('Client not initialized')
} }
const requestId = this.generateRequestId()
const request: RPCRequest = {
rpcName,
body,
authIdentifier: this.identity.publicKey,
requestId,
appId: this.config.appId,
}
const content = encryptContent(this.identity, this.config.accountPubkey, request) const content = encryptContent(this.identity, this.config.accountPubkey, request)
// finalizeEvent derives pubkey from the secret key // finalizeEvent derives pubkey from the secret key
const event = finalizeEvent( const event = finalizeEvent(
{ {
kind: LIGHTNING_PUB_KINDS.REQUEST, kind: LightningPubEventKind.RPC,
content, content,
tags: [['p', this.config.accountPubkey]], tags: [['p', this.config.accountPubkey]],
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
@ -342,13 +317,13 @@ export class LightningPubClient {
await this.nostrClient.publish(event) await this.nostrClient.publish(event)
// Wait for response // Wait for response
return this.waitForResponse<T>(event.id) return this.waitForResponse<T>(event.id, requestId)
} }
/** /**
* Wait for a response to a specific request * Wait for a response to a specific request
*/ */
private waitForResponse<T>(requestId: string): Promise<T> { private waitForResponse<T>(eventId: string, requestId: string): Promise<T> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.nostrClient || !this.identity) { if (!this.nostrClient || !this.identity) {
reject(new Error('Client not initialized')) reject(new Error('Client not initialized'))
@ -363,9 +338,11 @@ export class LightningPubClient {
const subId = this.nostrClient.subscribe( const subId = this.nostrClient.subscribe(
[ [
{ {
kinds: [LIGHTNING_PUB_KINDS.RESPONSE], kinds: [LightningPubEventKind.RPC],
'#e': [requestId], authors: [this.config.accountPubkey],
'#p': [this.identity!.publicKey], '#p': [this.identity!.publicKey],
'#e': [eventId],
since: Math.floor(Date.now() / 1000) - 5,
}, },
], ],
{ {
@ -374,12 +351,18 @@ export class LightningPubClient {
this.nostrClient!.unsubscribe(subId) this.nostrClient!.unsubscribe(subId)
try { try {
const response = decryptJSON<Response<T>>(this.identity!, event.pubkey, event.content) const response = decryptJSON<RPCResponse<T>>(
this.identity!,
this.config.accountPubkey,
event.content
)
if (response.error) { if (isRPCError(response)) {
reject(new Error(response.error.message)) reject(new Error(response.reason))
} else { } else {
resolve(response.result as T) // Extract result from response (excluding status)
const { status, ...result } = response
resolve(result as T)
} }
} catch (e) { } catch (e) {
reject(e) reject(e)
@ -390,6 +373,14 @@ export class LightningPubClient {
}) })
} }
/**
* Generate a unique request ID
*/
private generateRequestId(): string {
this.requestCounter++
return `${Date.now()}-${this.requestCounter}`
}
/** /**
* Disconnect and clean up * Disconnect and clean up
*/ */

View file

@ -4,19 +4,21 @@
* Lightning.Pub client for Nostr-native Lightning operations. * Lightning.Pub client for Nostr-native Lightning operations.
* *
* Lightning.Pub is an account system that wraps LND and provides * Lightning.Pub is an account system that wraps LND and provides
* CLINK protocol support. This package handles: * CLINK protocol support. This package handles the RPC layer:
* - Invoice generation * - Invoice generation via Kind 21000
* - Payment processing * - Payment processing via Kind 21000
* - Balance queries * - Balance queries
* - Exchange rate fetching * - Exchange rate fetching
* *
* For CLINK payment operations (offers, debits, management),
* use @lamassu/clink instead.
*
* @example * @example
* ```typescript * ```typescript
* import { LightningPubClient } from '@lamassu/lightning' * import { LightningPubClient } from '@lamassu/lightning'
* *
* const client = new LightningPubClient({ * const client = new LightningPubClient({
* serviceUrl: 'https://lightning.pub', * accountPubkey: 'hex-pubkey...',
* accountPubkey: 'npub1...',
* relays: ['wss://relay.example.com'], * relays: ['wss://relay.example.com'],
* }) * })
* *
@ -25,7 +27,7 @@
* *
* // Create an invoice * // Create an invoice
* const invoice = await client.createInvoice({ * const invoice = await client.createInvoice({
* amountMsat: 100000, // 100 sats * amountSats: 100,
* description: 'ATM withdrawal', * description: 'ATM withdrawal',
* }) * })
* *
@ -46,15 +48,32 @@
export { LightningPubClient } from './client.js' export { LightningPubClient } from './client.js'
// Types // Types
export type { export {
LightningPubConfig, // Event kinds
AccountInfo, LightningPubEventKind,
Invoice, // RPC types
InvoiceStatus, type RPCRequest,
PaymentResult, type RPCResponse,
ExchangeRate, type RPCSuccessResponse,
ChannelInfo, type RPCErrorResponse,
NodeInfo, isRPCError,
InvoiceCallback, // Account types
PaymentCallback, type AccountInfo,
type BalanceResponse,
// Invoice types
type Invoice,
type InvoiceStatus,
type CreateInvoiceParams,
type CreateInvoiceResponse,
type LookupInvoiceResponse,
// Payment types
type PaymentResult,
type PayInvoiceResponse,
// Exchange types
type ExchangeRate,
// Config
type LightningPubConfig,
// Callbacks
type InvoiceCallback,
type PaymentCallback,
} from './types.js' } from './types.js'

View file

@ -3,8 +3,67 @@
* *
* Lightning.Pub is a Nostr-native account system that wraps LND * Lightning.Pub is a Nostr-native account system that wraps LND
* and provides CLINK payment support. * and provides CLINK payment support.
*
* Communication uses Kind 21000 for generic RPC requests.
* Payment operations use CLINK protocol (kinds 21001-21003).
*/ */
/** Lightning.Pub event kinds */
export enum LightningPubEventKind {
/** Generic RPC request/response */
RPC = 21000,
}
// ============================================================================
// RPC Request/Response
// ============================================================================
/** Generic RPC request format (Kind 21000) */
export interface RPCRequest {
/** Method name (e.g., "NewInvoice", "GetBalance") */
rpcName: string
/** URL params */
params?: Record<string, string>
/** Query params */
query?: Record<string, string>
/** Request body */
body?: unknown
/** Pubkey of requester (for validation) */
authIdentifier: string
/** Unique request identifier */
requestId: string
/** Application identifier */
appId?: string
}
/** RPC success response */
export interface RPCSuccessResponse<T = unknown> {
/** Success status */
status: 'OK'
/** Result data - spread into the response object */
[key: string]: unknown
}
/** RPC error response */
export interface RPCErrorResponse {
/** Error status */
status: 'ERROR'
/** Error reason */
reason: string
}
/** RPC response - either success or error */
export type RPCResponse<T = unknown> = (RPCSuccessResponse<T> & T) | RPCErrorResponse
/** Type guard for RPC error */
export function isRPCError(response: RPCResponse): response is RPCErrorResponse {
return response.status === 'ERROR'
}
// ============================================================================
// Account & Balance
// ============================================================================
/** Lightning.Pub account information */ /** Lightning.Pub account information */
export interface AccountInfo { export interface AccountInfo {
/** Account public key */ /** Account public key */
@ -17,6 +76,16 @@ export interface AccountInfo {
maxReceiveSats: number maxReceiveSats: number
} }
/** Balance response */
export interface BalanceResponse {
/** Balance in satoshis */
balance: number
}
// ============================================================================
// Invoices
// ============================================================================
/** Invoice status */ /** Invoice status */
export type InvoiceStatus = 'pending' | 'paid' | 'expired' | 'cancelled' export type InvoiceStatus = 'pending' | 'paid' | 'expired' | 'cancelled'
@ -26,8 +95,8 @@ export interface Invoice {
paymentRequest: string paymentRequest: string
/** Payment hash */ /** Payment hash */
paymentHash: string paymentHash: string
/** Amount in millisatoshis */ /** Amount in satoshis */
amountMsat: number amountSats: number
/** Invoice description */ /** Invoice description */
description?: string description?: string
/** Creation timestamp */ /** Creation timestamp */
@ -40,18 +109,74 @@ export interface Invoice {
preimage?: string preimage?: string
} }
/** Create invoice request params */
export interface CreateInvoiceParams {
/** Amount in satoshis */
amountSats: number
/** Invoice description/memo */
description?: string
/** Expiry in seconds (default: 3600) */
expirySecs?: number
/** Include private route hints */
privateHints?: boolean
}
/** Create invoice response (from RPC) */
export interface CreateInvoiceResponse {
/** BOLT-11 payment request */
payment_request: string
/** Payment hash (hex) */
payment_hash: string
/** Expiry timestamp */
expires_at: number
}
/** Lookup invoice response (from RPC) */
export interface LookupInvoiceResponse {
/** BOLT-11 payment request */
payment_request: string
/** Amount in satoshis */
amount: number
/** Invoice description */
description: string
/** Creation timestamp */
created_at: number
/** Expiry timestamp */
expires_at: number
/** Whether invoice has been paid */
settled: boolean
/** Payment preimage (if paid) */
preimage?: string
}
// ============================================================================
// Payments
// ============================================================================
/** Payment result */ /** Payment result */
export interface PaymentResult { export interface PaymentResult {
/** Whether payment succeeded */ /** Whether payment succeeded */
success: boolean success: boolean
/** Payment preimage (proof of payment) */ /** Payment preimage (proof of payment) */
preimage?: string preimage?: string
/** Fee paid in millisatoshis */ /** Fee paid in satoshis */
feeMsat?: number feeSats?: number
/** Error message if failed */ /** Error message if failed */
error?: string error?: string
} }
/** Pay invoice response (from RPC) */
export interface PayInvoiceResponse {
/** Payment preimage */
preimage: string
/** Fee paid in satoshis */
fee_paid?: number
}
// ============================================================================
// Exchange Rates
// ============================================================================
/** Exchange rate information */ /** Exchange rate information */
export interface ExchangeRate { export interface ExchangeRate {
/** Fiat currency code */ /** Fiat currency code */
@ -64,47 +189,25 @@ export interface ExchangeRate {
source: string source: string
} }
// ============================================================================
// Client Configuration
// ============================================================================
/** Lightning.Pub client configuration */ /** Lightning.Pub client configuration */
export interface LightningPubConfig { export interface LightningPubConfig {
/** Lightning.Pub service URL */ /** Lightning.Pub account pubkey */
serviceUrl: string
/** Account pubkey on Lightning.Pub */
accountPubkey: string accountPubkey: string
/** Nostr relays for communication */ /** Nostr relays for communication */
relays: string[] relays: string[]
/** Request timeout in ms */ /** Request timeout in ms (default: 30000) */
timeout?: number timeout?: number
/** Application identifier */
appId?: string
} }
/** Channel information */ // ============================================================================
export interface ChannelInfo { // Callbacks
/** Channel ID */ // ============================================================================
channelId: string
/** Remote pubkey */
remotePubkey: string
/** Local balance in sats */
localBalanceSats: number
/** Remote balance in sats */
remoteBalanceSats: number
/** Channel capacity in sats */
capacitySats: number
/** Whether channel is active */
active: boolean
}
/** Node information */
export interface NodeInfo {
/** Node public key */
pubkey: string
/** Node alias */
alias: string
/** Number of channels */
numChannels: number
/** Total capacity in sats */
totalCapacitySats: number
/** Synced to chain */
syncedToChain: boolean
}
/** Invoice callback for watching payments */ /** Invoice callback for watching payments */
export type InvoiceCallback = (invoice: Invoice) => void export type InvoiceCallback = (invoice: Invoice) => void