Compare commits

...

15 commits

Author SHA1 Message Date
Patrick Mulligan
5a26676a24 Add NIP-47 (Nostr Wallet Connect) support
Some checks failed
Docker Compose Actions Workflow / test (push) Has been cancelled
Implements NWC protocol alongside the existing CLINK/Ndebit system,
allowing any NWC-compatible wallet (Alby, Amethyst, Damus, etc.) to
connect to a Lightning Pub node.

Supported NIP-47 methods: pay_invoice, make_invoice, get_balance,
get_info, lookup_invoice, list_transactions.

New files:
- NwcConnection entity with per-connection spending limits
- NwcStorage for connection CRUD operations
- NwcManager for NIP-47 request handling and connection management
- Database migration for nwc_connection table

Modified files:
- nostrPool: subscribe to kind 23194 events
- nostrMiddleware: route kind 23194 to NwcManager
- main/index: wire NwcManager, publish kind 13194 info events
- storage: register NwcConnection entity and NwcStorage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 15:10:15 -04:00
Patrick Mulligan
68c71599f8 fix(lnd): allow self-payments for LNURL-withdraw
Some checks failed
Docker Compose Actions Workflow / test (push) Has been cancelled
When the user's wallet (e.g. Zeus) is connected to the same LND node
that LP uses, LNURL-withdraw fails because LND rejects the payment
with "no self-payments allowed". This is safe because LP always
decrements the user's balance before paying and refunds on failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 14:48:55 -04:00
Patrick Mulligan
5aaa3bcc23 feat(extensions): pay from caller's balance via PayAppUserInvoice
When userPubkey is provided, resolve the ApplicationUser and call
applicationManager.PayAppUserInvoice instead of paymentManager.PayInvoice
directly. This ensures notifyAppUserPayment fires, sending
LiveUserOperation events via Nostr for real-time balance updates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 14:48:55 -04:00
Patrick Mulligan
cb9fb78eb8 feat(withdraw): track creator pubkey on withdraw links
Store the Nostr pubkey of the user who creates a withdraw link so the
LNURL callback debits the correct user's balance instead of the app
owner's. Pass userPubkey through from RPC handler to WithdrawManager.

- Add creator_pubkey column (migration v4)
- Store creatorPubkey on link creation
- Pass creator_pubkey to payInvoice on LNURL callback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 14:48:55 -04:00
Patrick Mulligan
1273da9020 feat: route Nostr RPC to extension methods
Initialize extension system before nostrMiddleware so registered
RPC methods are available. Extension methods (e.g. withdraw.createLink)
are intercepted and routed to the extension loader before falling
through to the standard nostrTransport.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 14:48:55 -04:00
Patrick Mulligan
3ee8b6b010 feat(withdraw): add HTTP API for creating withdraw links
Add POST /api/v1/withdraw/create endpoint to allow external apps (ATM,
web clients) to create LNURL-withdraw links via HTTP instead of RPC.

Changes:
- Add handleCreateWithdrawLink HTTP handler
- Fix route ordering: callback routes before wildcard :unique_hash
- Extract app_id from Authorization header (Bearer app_<id>)
- Use is_unique=false for simple single-use ATM links

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-02 14:48:55 -04:00
Patrick Mulligan
f06d50f227 feat(server): add CORS support for extension HTTP routes
Enable CORS on the extension HTTP server to allow cross-origin requests
from ATM apps and other web-based clients.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-02 14:48:55 -04:00
Patrick Mulligan
e998762ca7 feat: integrate extension system with withdraw extension support
- Add extension loader initialization to startup
- Create mainHandlerAdapter to bridge mainHandler with extension context
- Mount extension HTTP routes on separate port (main port + 1)
- Configure EXTENSION_SERVICE_URL for LNURL link generation

The withdraw extension provides LUD-03 LNURL-withdraw support for
creating withdraw links that allow users to pull funds.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-02 14:48:55 -04:00
Patrick Mulligan
8de5e4fd3a feat(extensions): add LNURL-withdraw extension
Implements LUD-03 (LNURL-withdraw) for creating withdraw links
that allow anyone to pull funds from a Lightning wallet.

Features:
- Create withdraw links with min/max amounts
- Quick vouchers: batch creation of single-use codes
- Multi-use links with wait time between uses
- Unique QR codes per use (prevents sharing exploits)
- Webhook notifications on successful withdrawals
- Full LNURL protocol compliance for wallet compatibility

Use cases:
- Faucets
- Gift cards / prepaid cards
- Tips / donations
- User onboarding

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-02 14:48:55 -04:00
77e5772afd feat(extensions): add extension loader infrastructure (#3)
Some checks are pending
Docker Compose Actions Workflow / test (push) Waiting to run
## Summary

- Adds a modular extension system for Lightning.Pub enabling third-party plugins
- Provides isolated SQLite databases per extension for data safety
- Implements ExtensionContext API for accessing Lightning.Pub services (payments, Nostr, storage)
- Supports RPC method registration with automatic namespacing
- Includes HTTP route handling for protocols like LNURL
- Event routing for payment receipts and Nostr events
- Comprehensive documentation with architecture overview and working examples

## Key Components

- `src/extensions/types.ts` - Core extension interfaces
- `src/extensions/loader.ts` - Extension discovery, loading, and lifecycle management
- `src/extensions/context.ts` - Bridge between extensions and Lightning.Pub services
- `src/extensions/database.ts` - SQLite isolation with WAL mode
- `src/extensions/README.md` - Full documentation with examples

## ExtensionContext API

| Method | Description |
|--------|-------------|
| `getApplication()` | Get application info |
| `createInvoice()` | Create Lightning invoice |
| `payInvoice()` | Pay Lightning invoice |
| `getLnurlPayInfo()` | Get LNURL-pay info for a user (enables Lightning Address/zaps) |
| `sendEncryptedDM()` | Send Nostr DM (NIP-44) |
| `publishNostrEvent()` | Publish Nostr event |
| `registerMethod()` | Register RPC method |
| `onPaymentReceived()` | Subscribe to payment callbacks |
| `onNostrEvent()` | Subscribe to Nostr events |

## Test plan

- [x] Review extension loader code for correctness
- [x] Verify TypeScript compilation succeeds
- [x] Test extension discovery from `src/extensions/` directory
- [x] Test RPC method registration and routing
- [x] Test database isolation between extensions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: boufni95 <boufni95@gmail.com>
Co-authored-by: Patrick Mulligan <patjmulligan@protonmail.com>
Reviewed-on: #3
2026-04-02 18:47:55 +00:00
Patrick Mulligan
72c9872b23 fix(watchdog): handle LND restarts without locking outgoing operations
Some checks failed
Docker Compose Actions Workflow / test (push) Has been cancelled
When the payment index advances (e.g. after an LND restart or external
payment), update the cached offset instead of immediately locking.
Only lock if both a history mismatch AND a balance discrepancy are
detected — indicating a real security concern rather than a benign
LND restart.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 15:19:20 -05:00
Patrick Mulligan
5e5e30c7a2 fix(lnd): wait for chain/graph sync before marking LND ready
Warmup() previously only checked that LND responded to GetInfo(), but
did not verify syncedToChain/syncedToGraph. This caused LP to accept
requests while LND was still syncing, leading to "not synced" errors
on every Health() check. Now waits for full sync with a 10min timeout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 15:18:06 -05:00
Patrick Mulligan
611eb4fc04 fix(nostr): close SimplePool after publishing to prevent connection leak
Each sendEvent() call created a new SimplePool() but never closed it,
causing relay WebSocket connections to accumulate indefinitely (~20/min).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 15:16:28 -05:00
Patrick Mulligan
6512e10f08 fix(handlers): await NostrSend calls throughout codebase
Update all NostrSend call sites to properly handle the async nature
of the function now that it returns Promise<void>.

Changes:
- handler.ts: Add async to sendResponse, await nostrSend calls
- debitManager.ts: Add logging for Kind 21002 response sending
- nostrMiddleware.ts: Update nostrSend signature
- tlvFilesStorageProcessor.ts: Update nostrSend signature
- webRTC/index.ts: Add async/await for nostrSend calls

This ensures Kind 21002 (ndebit) responses are properly sent to
wallet clients, fixing the "Debit request failed" issue in ShockWallet.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-04 15:16:28 -05:00
Patrick Mulligan
e9b5dacb3b fix(nostr): update NostrSend type to Promise<void> with error handling
The NostrSend type was incorrectly typed as returning void when it actually
returns Promise<void>. This caused async errors to be silently swallowed.

Changes:
- Update NostrSend type signature to return Promise<void>
- Make NostrSender._nostrSend default to async function
- Add .catch() error handling in NostrSender.Send() to log failures
- Add logging to track event publishing status

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-04 15:16:28 -05:00
68 changed files with 12679 additions and 4870 deletions

View file

@ -2,3 +2,21 @@
.github
build
node_modules
# Runtime state files (should not be baked into image)
*.sqlite
*.sqlite-journal
*.sqlite-wal
*.sqlite-shm
*.db
admin.connect
admin.enroll
admin.npub
app.nprofile
.jwt_secret
# Runtime data directories
metric_cache/
metric_events/
bundler_events/
logs/

View file

@ -1,4 +1,4 @@
FROM node:18
FROM node:20
WORKDIR /app

View file

@ -66,6 +66,7 @@ import { InvoiceSwaps1769529793283 } from './build/src/services/storage/migratio
import { InvoiceSwapsFixes1769805357459 } from './build/src/services/storage/migrations/1769805357459-invoice_swaps_fixes.js'
import { ApplicationUserTopicId1770038768784 } from './build/src/services/storage/migrations/1770038768784-application_user_topic_id.js'
import { SwapTimestamps1771347307798 } from './build/src/services/storage/migrations/1771347307798-swap_timestamps.js'
import { TxSwapTimestamps1771878683383 } from './build/src/services/storage/migrations/1771878683383-tx_swap_timestamps.js'
@ -80,7 +81,8 @@ export default new DataSource({
InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175,
UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098,
TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036,
InvoiceSwaps1769529793283, InvoiceSwapsFixes1769805357459, ApplicationUserTopicId1770038768784, SwapTimestamps1771347307798
InvoiceSwaps1769529793283, InvoiceSwapsFixes1769805357459, ApplicationUserTopicId1770038768784, SwapTimestamps1771347307798,
TxSwapTimestamps1771878683383
],
@ -89,4 +91,4 @@ export default new DataSource({
TrackedProvider, InviteToken, DebitAccess, UserOffer, ManagementGrant, AppUserDevice, UserAccess, AdminSettings, TransactionSwap, InvoiceSwap],
// synchronize: true,
})
//npx typeorm migration:generate ./src/services/storage/migrations/tx_swap_timestamps -d ./datasource.js
//npx typeorm migration:generate ./src/services/storage/migrations/refund_swap_info -d ./datasource.js

2920
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -77,6 +77,7 @@
"zip-a-folder": "^3.1.9"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.13",
"@types/chai": "^4.3.4",
"@types/chai-string": "^1.4.5",
"@types/cors": "^2.8.17",

View file

@ -1431,6 +1431,9 @@ The nostr server will send back a message response, and inside the body there wi
- __failure_reason__: _string_ *this field is optional
- __operation_payment__: _[UserOperation](#UserOperation)_ *this field is optional
- __quote__: _[InvoiceSwapQuote](#InvoiceSwapQuote)_
- __refund_address__: _string_ *this field is optional
- __refund_at_unix__: _number_ *this field is optional
- __refund_tx_id__: _string_ *this field is optional
### InvoiceSwapQuote
- __address__: _string_

View file

@ -391,6 +391,9 @@ type InvoiceSwapOperation struct {
Failure_reason string `json:"failure_reason"`
Operation_payment *UserOperation `json:"operation_payment"`
Quote *InvoiceSwapQuote `json:"quote"`
Refund_address string `json:"refund_address"`
Refund_at_unix int64 `json:"refund_at_unix"`
Refund_tx_id string `json:"refund_tx_id"`
}
type InvoiceSwapQuote struct {
Address string `json:"address"`

View file

@ -2270,15 +2270,21 @@ export type InvoiceSwapOperation = {
failure_reason?: string
operation_payment?: UserOperation
quote: InvoiceSwapQuote
refund_address?: string
refund_at_unix?: number
refund_tx_id?: string
}
export type InvoiceSwapOperationOptionalField = 'completed_at_unix' | 'failure_reason' | 'operation_payment'
export const InvoiceSwapOperationOptionalFields: InvoiceSwapOperationOptionalField[] = ['completed_at_unix', 'failure_reason', 'operation_payment']
export type InvoiceSwapOperationOptionalField = 'completed_at_unix' | 'failure_reason' | 'operation_payment' | 'refund_address' | 'refund_at_unix' | 'refund_tx_id'
export const InvoiceSwapOperationOptionalFields: InvoiceSwapOperationOptionalField[] = ['completed_at_unix', 'failure_reason', 'operation_payment', 'refund_address', 'refund_at_unix', 'refund_tx_id']
export type InvoiceSwapOperationOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: InvoiceSwapOperationOptionalField[]
completed_at_unix_CustomCheck?: (v?: number) => boolean
failure_reason_CustomCheck?: (v?: string) => boolean
operation_payment_Options?: UserOperationOptions
quote_Options?: InvoiceSwapQuoteOptions
refund_address_CustomCheck?: (v?: string) => boolean
refund_at_unix_CustomCheck?: (v?: number) => boolean
refund_tx_id_CustomCheck?: (v?: string) => boolean
}
export const InvoiceSwapOperationValidate = (o?: InvoiceSwapOperation, opts: InvoiceSwapOperationOptions = {}, path: string = 'InvoiceSwapOperation::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
@ -2300,6 +2306,15 @@ export const InvoiceSwapOperationValidate = (o?: InvoiceSwapOperation, opts: Inv
if (quoteErr !== null) return quoteErr
if ((o.refund_address || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('refund_address')) && typeof o.refund_address !== 'string') return new Error(`${path}.refund_address: is not a string`)
if (opts.refund_address_CustomCheck && !opts.refund_address_CustomCheck(o.refund_address)) return new Error(`${path}.refund_address: custom check failed`)
if ((o.refund_at_unix || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('refund_at_unix')) && typeof o.refund_at_unix !== 'number') return new Error(`${path}.refund_at_unix: is not a number`)
if (opts.refund_at_unix_CustomCheck && !opts.refund_at_unix_CustomCheck(o.refund_at_unix)) return new Error(`${path}.refund_at_unix: custom check failed`)
if ((o.refund_tx_id || opts.allOptionalsAreSet || opts.checkOptionalsAreSet?.includes('refund_tx_id')) && typeof o.refund_tx_id !== 'string') return new Error(`${path}.refund_tx_id: is not a string`)
if (opts.refund_tx_id_CustomCheck && !opts.refund_tx_id_CustomCheck(o.refund_tx_id)) return new Error(`${path}.refund_tx_id: custom check failed`)
return null
}

View file

@ -1,4 +1,4 @@
// @generated by protobuf-ts 2.8.1
// @generated by protobuf-ts 2.11.1
// @generated from protobuf file "chainnotifier.proto" (package "chainrpc", syntax proto3)
// tslint:disable
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
@ -31,7 +31,7 @@ export interface IChainNotifierClient {
* a notification is sent once the output script confirms in the given
* transaction.
*
* @generated from protobuf rpc: RegisterConfirmationsNtfn(chainrpc.ConfRequest) returns (stream chainrpc.ConfEvent);
* @generated from protobuf rpc: RegisterConfirmationsNtfn
*/
registerConfirmationsNtfn(input: ConfRequest, options?: RpcOptions): ServerStreamingCall<ConfRequest, ConfEvent>;
/**
@ -43,7 +43,7 @@ export interface IChainNotifierClient {
* A client can specify whether the spend request should be for a particular
* outpoint or for an output script by specifying a zero outpoint.
*
* @generated from protobuf rpc: RegisterSpendNtfn(chainrpc.SpendRequest) returns (stream chainrpc.SpendEvent);
* @generated from protobuf rpc: RegisterSpendNtfn
*/
registerSpendNtfn(input: SpendRequest, options?: RpcOptions): ServerStreamingCall<SpendRequest, SpendEvent>;
/**
@ -58,7 +58,7 @@ export interface IChainNotifierClient {
* point. This allows clients to be idempotent by ensuring that they do not
* missing processing a single block within the chain.
*
* @generated from protobuf rpc: RegisterBlockEpochNtfn(chainrpc.BlockEpoch) returns (stream chainrpc.BlockEpoch);
* @generated from protobuf rpc: RegisterBlockEpochNtfn
*/
registerBlockEpochNtfn(input: BlockEpoch, options?: RpcOptions): ServerStreamingCall<BlockEpoch, BlockEpoch>;
}
@ -86,7 +86,7 @@ export class ChainNotifierClient implements IChainNotifierClient, ServiceInfo {
* a notification is sent once the output script confirms in the given
* transaction.
*
* @generated from protobuf rpc: RegisterConfirmationsNtfn(chainrpc.ConfRequest) returns (stream chainrpc.ConfEvent);
* @generated from protobuf rpc: RegisterConfirmationsNtfn
*/
registerConfirmationsNtfn(input: ConfRequest, options?: RpcOptions): ServerStreamingCall<ConfRequest, ConfEvent> {
const method = this.methods[0], opt = this._transport.mergeOptions(options);
@ -101,7 +101,7 @@ export class ChainNotifierClient implements IChainNotifierClient, ServiceInfo {
* A client can specify whether the spend request should be for a particular
* outpoint or for an output script by specifying a zero outpoint.
*
* @generated from protobuf rpc: RegisterSpendNtfn(chainrpc.SpendRequest) returns (stream chainrpc.SpendEvent);
* @generated from protobuf rpc: RegisterSpendNtfn
*/
registerSpendNtfn(input: SpendRequest, options?: RpcOptions): ServerStreamingCall<SpendRequest, SpendEvent> {
const method = this.methods[1], opt = this._transport.mergeOptions(options);
@ -119,7 +119,7 @@ export class ChainNotifierClient implements IChainNotifierClient, ServiceInfo {
* point. This allows clients to be idempotent by ensuring that they do not
* missing processing a single block within the chain.
*
* @generated from protobuf rpc: RegisterBlockEpochNtfn(chainrpc.BlockEpoch) returns (stream chainrpc.BlockEpoch);
* @generated from protobuf rpc: RegisterBlockEpochNtfn
*/
registerBlockEpochNtfn(input: BlockEpoch, options?: RpcOptions): ServerStreamingCall<BlockEpoch, BlockEpoch> {
const method = this.methods[2], opt = this._transport.mergeOptions(options);

View file

@ -1,4 +1,4 @@
// @generated by protobuf-ts 2.8.1
// @generated by protobuf-ts 2.11.1
// @generated from protobuf file "chainnotifier.proto" (package "chainrpc", syntax proto3)
// tslint:disable
import { ServiceType } from "@protobuf-ts/runtime-rpc";
@ -10,7 +10,6 @@ import type { IBinaryReader } from "@protobuf-ts/runtime";
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
import type { PartialMessage } from "@protobuf-ts/runtime";
import { reflectionMergePartial } from "@protobuf-ts/runtime";
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
import { MessageType } from "@protobuf-ts/runtime";
/**
* @generated from protobuf message chainrpc.ConfRequest
@ -22,7 +21,7 @@ export interface ConfRequest {
* for. If set to a hash of all zeros, then the confirmation notification will
* be requested for the script instead.
*
* @generated from protobuf field: bytes txid = 1;
* @generated from protobuf field: bytes txid = 1
*/
txid: Uint8Array;
/**
@ -32,7 +31,7 @@ export interface ConfRequest {
* hash of all zeros, then a confirmation notification will be requested for
* this script instead.
*
* @generated from protobuf field: bytes script = 2;
* @generated from protobuf field: bytes script = 2
*/
script: Uint8Array;
/**
@ -40,7 +39,7 @@ export interface ConfRequest {
* The number of desired confirmations the transaction/output script should
* reach before dispatching a confirmation notification.
*
* @generated from protobuf field: uint32 num_confs = 3;
* @generated from protobuf field: uint32 num_confs = 3
*/
numConfs: number;
/**
@ -49,7 +48,7 @@ export interface ConfRequest {
* could have been included in a block. This should in most cases be set to the
* broadcast height of the transaction/output script.
*
* @generated from protobuf field: uint32 height_hint = 4;
* @generated from protobuf field: uint32 height_hint = 4
*/
heightHint: number;
/**
@ -57,7 +56,7 @@ export interface ConfRequest {
* If true, then the block that mines the specified txid/script will be
* included in eventual the notification event.
*
* @generated from protobuf field: bool include_block = 5;
* @generated from protobuf field: bool include_block = 5
*/
includeBlock: boolean;
}
@ -68,26 +67,26 @@ export interface ConfDetails {
/**
* The raw bytes of the confirmed transaction.
*
* @generated from protobuf field: bytes raw_tx = 1;
* @generated from protobuf field: bytes raw_tx = 1
*/
rawTx: Uint8Array;
/**
* The hash of the block in which the confirmed transaction was included in.
*
* @generated from protobuf field: bytes block_hash = 2;
* @generated from protobuf field: bytes block_hash = 2
*/
blockHash: Uint8Array;
/**
* The height of the block in which the confirmed transaction was included
* in.
*
* @generated from protobuf field: uint32 block_height = 3;
* @generated from protobuf field: uint32 block_height = 3
*/
blockHeight: number;
/**
* The index of the confirmed transaction within the block.
*
* @generated from protobuf field: uint32 tx_index = 4;
* @generated from protobuf field: uint32 tx_index = 4
*/
txIndex: number;
/**
@ -95,7 +94,7 @@ export interface ConfDetails {
* The raw bytes of the block that mined the transaction. Only included if
* include_block was set in the request.
*
* @generated from protobuf field: bytes raw_block = 5;
* @generated from protobuf field: bytes raw_block = 5
*/
rawBlock: Uint8Array;
}
@ -120,7 +119,7 @@ export interface ConfEvent {
* An event that includes the confirmation details of the request
* (txid/ouput script).
*
* @generated from protobuf field: chainrpc.ConfDetails conf = 1;
* @generated from protobuf field: chainrpc.ConfDetails conf = 1
*/
conf: ConfDetails;
} | {
@ -130,7 +129,7 @@ export interface ConfEvent {
* An event send when the transaction of the request is reorged out of the
* chain.
*
* @generated from protobuf field: chainrpc.Reorg reorg = 2;
* @generated from protobuf field: chainrpc.Reorg reorg = 2
*/
reorg: Reorg;
} | {
@ -144,13 +143,13 @@ export interface Outpoint {
/**
* The hash of the transaction.
*
* @generated from protobuf field: bytes hash = 1;
* @generated from protobuf field: bytes hash = 1
*/
hash: Uint8Array;
/**
* The index of the output within the transaction.
*
* @generated from protobuf field: uint32 index = 2;
* @generated from protobuf field: uint32 index = 2
*/
index: number;
}
@ -168,7 +167,7 @@ export interface SpendRequest {
* So an outpoint must _always_ be specified when registering a spend
* notification for a Taproot output.
*
* @generated from protobuf field: chainrpc.Outpoint outpoint = 1;
* @generated from protobuf field: chainrpc.Outpoint outpoint = 1
*/
outpoint?: Outpoint;
/**
@ -177,7 +176,7 @@ export interface SpendRequest {
* to match block filters. If the outpoint is set to a zero outpoint, then a
* spend notification will be requested for this script instead.
*
* @generated from protobuf field: bytes script = 2;
* @generated from protobuf field: bytes script = 2
*/
script: Uint8Array;
/**
@ -186,7 +185,7 @@ export interface SpendRequest {
* have been spent. This should in most cases be set to the broadcast height of
* the outpoint/output script.
*
* @generated from protobuf field: uint32 height_hint = 3;
* @generated from protobuf field: uint32 height_hint = 3
*/
heightHint: number;
}
@ -197,31 +196,31 @@ export interface SpendDetails {
/**
* The outpoint was that spent.
*
* @generated from protobuf field: chainrpc.Outpoint spending_outpoint = 1;
* @generated from protobuf field: chainrpc.Outpoint spending_outpoint = 1
*/
spendingOutpoint?: Outpoint;
/**
* The raw bytes of the spending transaction.
*
* @generated from protobuf field: bytes raw_spending_tx = 2;
* @generated from protobuf field: bytes raw_spending_tx = 2
*/
rawSpendingTx: Uint8Array;
/**
* The hash of the spending transaction.
*
* @generated from protobuf field: bytes spending_tx_hash = 3;
* @generated from protobuf field: bytes spending_tx_hash = 3
*/
spendingTxHash: Uint8Array;
/**
* The input of the spending transaction that fulfilled the spend request.
*
* @generated from protobuf field: uint32 spending_input_index = 4;
* @generated from protobuf field: uint32 spending_input_index = 4
*/
spendingInputIndex: number;
/**
* The height at which the spending transaction was included in a block.
*
* @generated from protobuf field: uint32 spending_height = 5;
* @generated from protobuf field: uint32 spending_height = 5
*/
spendingHeight: number;
}
@ -239,7 +238,7 @@ export interface SpendEvent {
* An event that includes the details of the spending transaction of the
* request (outpoint/output script).
*
* @generated from protobuf field: chainrpc.SpendDetails spend = 1;
* @generated from protobuf field: chainrpc.SpendDetails spend = 1
*/
spend: SpendDetails;
} | {
@ -249,7 +248,7 @@ export interface SpendEvent {
* An event sent when the spending transaction of the request was
* reorged out of the chain.
*
* @generated from protobuf field: chainrpc.Reorg reorg = 2;
* @generated from protobuf field: chainrpc.Reorg reorg = 2
*/
reorg: Reorg;
} | {
@ -263,13 +262,13 @@ export interface BlockEpoch {
/**
* The hash of the block.
*
* @generated from protobuf field: bytes hash = 1;
* @generated from protobuf field: bytes hash = 1
*/
hash: Uint8Array;
/**
* The height of the block.
*
* @generated from protobuf field: uint32 height = 2;
* @generated from protobuf field: uint32 height = 2
*/
height: number;
}
@ -285,8 +284,12 @@ class ConfRequest$Type extends MessageType<ConfRequest> {
]);
}
create(value?: PartialMessage<ConfRequest>): ConfRequest {
const message = { txid: new Uint8Array(0), script: new Uint8Array(0), numConfs: 0, heightHint: 0, includeBlock: false };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.txid = new Uint8Array(0);
message.script = new Uint8Array(0);
message.numConfs = 0;
message.heightHint = 0;
message.includeBlock = false;
if (value !== undefined)
reflectionMergePartial<ConfRequest>(this, message, value);
return message;
@ -360,8 +363,12 @@ class ConfDetails$Type extends MessageType<ConfDetails> {
]);
}
create(value?: PartialMessage<ConfDetails>): ConfDetails {
const message = { rawTx: new Uint8Array(0), blockHash: new Uint8Array(0), blockHeight: 0, txIndex: 0, rawBlock: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.rawTx = new Uint8Array(0);
message.blockHash = new Uint8Array(0);
message.blockHeight = 0;
message.txIndex = 0;
message.rawBlock = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<ConfDetails>(this, message, value);
return message;
@ -429,14 +436,26 @@ class Reorg$Type extends MessageType<Reorg> {
super("chainrpc.Reorg", []);
}
create(value?: PartialMessage<Reorg>): Reorg {
const message = {};
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<Reorg>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Reorg): Reorg {
return target ?? this.create();
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: Reorg, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
let u = options.writeUnknownFields;
@ -458,8 +477,8 @@ class ConfEvent$Type extends MessageType<ConfEvent> {
]);
}
create(value?: PartialMessage<ConfEvent>): ConfEvent {
const message = { event: { oneofKind: undefined } };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.event = { oneofKind: undefined };
if (value !== undefined)
reflectionMergePartial<ConfEvent>(this, message, value);
return message;
@ -518,8 +537,9 @@ class Outpoint$Type extends MessageType<Outpoint> {
]);
}
create(value?: PartialMessage<Outpoint>): Outpoint {
const message = { hash: new Uint8Array(0), index: 0 };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.hash = new Uint8Array(0);
message.index = 0;
if (value !== undefined)
reflectionMergePartial<Outpoint>(this, message, value);
return message;
@ -573,8 +593,9 @@ class SpendRequest$Type extends MessageType<SpendRequest> {
]);
}
create(value?: PartialMessage<SpendRequest>): SpendRequest {
const message = { script: new Uint8Array(0), heightHint: 0 };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.script = new Uint8Array(0);
message.heightHint = 0;
if (value !== undefined)
reflectionMergePartial<SpendRequest>(this, message, value);
return message;
@ -636,8 +657,11 @@ class SpendDetails$Type extends MessageType<SpendDetails> {
]);
}
create(value?: PartialMessage<SpendDetails>): SpendDetails {
const message = { rawSpendingTx: new Uint8Array(0), spendingTxHash: new Uint8Array(0), spendingInputIndex: 0, spendingHeight: 0 };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.rawSpendingTx = new Uint8Array(0);
message.spendingTxHash = new Uint8Array(0);
message.spendingInputIndex = 0;
message.spendingHeight = 0;
if (value !== undefined)
reflectionMergePartial<SpendDetails>(this, message, value);
return message;
@ -708,8 +732,8 @@ class SpendEvent$Type extends MessageType<SpendEvent> {
]);
}
create(value?: PartialMessage<SpendEvent>): SpendEvent {
const message = { event: { oneofKind: undefined } };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.event = { oneofKind: undefined };
if (value !== undefined)
reflectionMergePartial<SpendEvent>(this, message, value);
return message;
@ -768,8 +792,9 @@ class BlockEpoch$Type extends MessageType<BlockEpoch> {
]);
}
create(value?: PartialMessage<BlockEpoch>): BlockEpoch {
const message = { hash: new Uint8Array(0), height: 0 };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.hash = new Uint8Array(0);
message.height = 0;
if (value !== undefined)
reflectionMergePartial<BlockEpoch>(this, message, value);
return message;

View file

@ -1,9 +1,12 @@
// @generated by protobuf-ts 2.8.1
// @generated by protobuf-ts 2.11.1
// @generated from protobuf file "invoices.proto" (package "invoicesrpc", syntax proto3)
// tslint:disable
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
import { Invoices } from "./invoices.js";
import type { HtlcModifyRequest } from "./invoices.js";
import type { HtlcModifyResponse } from "./invoices.js";
import type { DuplexStreamingCall } from "@protobuf-ts/runtime-rpc";
import type { LookupInvoiceMsg } from "./invoices.js";
import type { SettleInvoiceResp } from "./invoices.js";
import type { SettleInvoiceMsg } from "./invoices.js";
@ -17,6 +20,23 @@ import type { Invoice } from "./lightning.js";
import type { SubscribeSingleInvoiceRequest } from "./invoices.js";
import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc";
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
//
// Comments in this file will be directly parsed into the API
// Documentation as descriptions of the associated method, message, or field.
// These descriptions should go right above the definition of the object, and
// can be in either block or // comment format.
//
// An RPC method can be matched to an lncli command by placing a line in the
// beginning of the description in exactly the following format:
// lncli: `methodname`
//
// Failure to specify the exact name of the command will cause documentation
// generation to fail.
//
// More information on how exactly the gRPC documentation is generated from
// this proto file can be found here:
// https://github.com/lightninglabs/lightning-api
/**
* Invoices is a service that can be used to create, accept, settle and cancel
* invoices.
@ -30,43 +50,70 @@ export interface IInvoicesClient {
* to notify the client of state transitions of the specified invoice.
* Initially the current invoice state is always sent out.
*
* @generated from protobuf rpc: SubscribeSingleInvoice(invoicesrpc.SubscribeSingleInvoiceRequest) returns (stream lnrpc.Invoice);
* @generated from protobuf rpc: SubscribeSingleInvoice
*/
subscribeSingleInvoice(input: SubscribeSingleInvoiceRequest, options?: RpcOptions): ServerStreamingCall<SubscribeSingleInvoiceRequest, Invoice>;
/**
*
* lncli: `cancelinvoice`
* CancelInvoice cancels a currently open invoice. If the invoice is already
* canceled, this call will succeed. If the invoice is already settled, it will
* fail.
*
* @generated from protobuf rpc: CancelInvoice(invoicesrpc.CancelInvoiceMsg) returns (invoicesrpc.CancelInvoiceResp);
* @generated from protobuf rpc: CancelInvoice
*/
cancelInvoice(input: CancelInvoiceMsg, options?: RpcOptions): UnaryCall<CancelInvoiceMsg, CancelInvoiceResp>;
/**
*
* lncli: `addholdinvoice`
* AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
* supplied in the request.
*
* @generated from protobuf rpc: AddHoldInvoice(invoicesrpc.AddHoldInvoiceRequest) returns (invoicesrpc.AddHoldInvoiceResp);
* @generated from protobuf rpc: AddHoldInvoice
*/
addHoldInvoice(input: AddHoldInvoiceRequest, options?: RpcOptions): UnaryCall<AddHoldInvoiceRequest, AddHoldInvoiceResp>;
/**
*
* lncli: `settleinvoice`
* SettleInvoice settles an accepted invoice. If the invoice is already
* settled, this call will succeed.
*
* @generated from protobuf rpc: SettleInvoice(invoicesrpc.SettleInvoiceMsg) returns (invoicesrpc.SettleInvoiceResp);
* @generated from protobuf rpc: SettleInvoice
*/
settleInvoice(input: SettleInvoiceMsg, options?: RpcOptions): UnaryCall<SettleInvoiceMsg, SettleInvoiceResp>;
/**
*
* LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced
* LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced
* using either its payment hash, payment address, or set ID.
*
* @generated from protobuf rpc: LookupInvoiceV2(invoicesrpc.LookupInvoiceMsg) returns (lnrpc.Invoice);
* @generated from protobuf rpc: LookupInvoiceV2
*/
lookupInvoiceV2(input: LookupInvoiceMsg, options?: RpcOptions): UnaryCall<LookupInvoiceMsg, Invoice>;
/**
*
* HtlcModifier is a bidirectional streaming RPC that allows a client to
* intercept and modify the HTLCs that attempt to settle the given invoice. The
* server will send HTLCs of invoices to the client and the client can modify
* some aspects of the HTLC in order to pass the invoice acceptance tests.
*
* @generated from protobuf rpc: HtlcModifier
*/
htlcModifier(options?: RpcOptions): DuplexStreamingCall<HtlcModifyResponse, HtlcModifyRequest>;
}
//
// Comments in this file will be directly parsed into the API
// Documentation as descriptions of the associated method, message, or field.
// These descriptions should go right above the definition of the object, and
// can be in either block or // comment format.
//
// An RPC method can be matched to an lncli command by placing a line in the
// beginning of the description in exactly the following format:
// lncli: `methodname`
//
// Failure to specify the exact name of the command will cause documentation
// generation to fail.
//
// More information on how exactly the gRPC documentation is generated from
// this proto file can be found here:
// https://github.com/lightninglabs/lightning-api
/**
* Invoices is a service that can be used to create, accept, settle and cancel
* invoices.
@ -85,41 +132,41 @@ export class InvoicesClient implements IInvoicesClient, ServiceInfo {
* to notify the client of state transitions of the specified invoice.
* Initially the current invoice state is always sent out.
*
* @generated from protobuf rpc: SubscribeSingleInvoice(invoicesrpc.SubscribeSingleInvoiceRequest) returns (stream lnrpc.Invoice);
* @generated from protobuf rpc: SubscribeSingleInvoice
*/
subscribeSingleInvoice(input: SubscribeSingleInvoiceRequest, options?: RpcOptions): ServerStreamingCall<SubscribeSingleInvoiceRequest, Invoice> {
const method = this.methods[0], opt = this._transport.mergeOptions(options);
return stackIntercept<SubscribeSingleInvoiceRequest, Invoice>("serverStreaming", this._transport, method, opt, input);
}
/**
*
* lncli: `cancelinvoice`
* CancelInvoice cancels a currently open invoice. If the invoice is already
* canceled, this call will succeed. If the invoice is already settled, it will
* fail.
*
* @generated from protobuf rpc: CancelInvoice(invoicesrpc.CancelInvoiceMsg) returns (invoicesrpc.CancelInvoiceResp);
* @generated from protobuf rpc: CancelInvoice
*/
cancelInvoice(input: CancelInvoiceMsg, options?: RpcOptions): UnaryCall<CancelInvoiceMsg, CancelInvoiceResp> {
const method = this.methods[1], opt = this._transport.mergeOptions(options);
return stackIntercept<CancelInvoiceMsg, CancelInvoiceResp>("unary", this._transport, method, opt, input);
}
/**
*
* lncli: `addholdinvoice`
* AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
* supplied in the request.
*
* @generated from protobuf rpc: AddHoldInvoice(invoicesrpc.AddHoldInvoiceRequest) returns (invoicesrpc.AddHoldInvoiceResp);
* @generated from protobuf rpc: AddHoldInvoice
*/
addHoldInvoice(input: AddHoldInvoiceRequest, options?: RpcOptions): UnaryCall<AddHoldInvoiceRequest, AddHoldInvoiceResp> {
const method = this.methods[2], opt = this._transport.mergeOptions(options);
return stackIntercept<AddHoldInvoiceRequest, AddHoldInvoiceResp>("unary", this._transport, method, opt, input);
}
/**
*
* lncli: `settleinvoice`
* SettleInvoice settles an accepted invoice. If the invoice is already
* settled, this call will succeed.
*
* @generated from protobuf rpc: SettleInvoice(invoicesrpc.SettleInvoiceMsg) returns (invoicesrpc.SettleInvoiceResp);
* @generated from protobuf rpc: SettleInvoice
*/
settleInvoice(input: SettleInvoiceMsg, options?: RpcOptions): UnaryCall<SettleInvoiceMsg, SettleInvoiceResp> {
const method = this.methods[3], opt = this._transport.mergeOptions(options);
@ -127,13 +174,26 @@ export class InvoicesClient implements IInvoicesClient, ServiceInfo {
}
/**
*
* LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced
* LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced
* using either its payment hash, payment address, or set ID.
*
* @generated from protobuf rpc: LookupInvoiceV2(invoicesrpc.LookupInvoiceMsg) returns (lnrpc.Invoice);
* @generated from protobuf rpc: LookupInvoiceV2
*/
lookupInvoiceV2(input: LookupInvoiceMsg, options?: RpcOptions): UnaryCall<LookupInvoiceMsg, Invoice> {
const method = this.methods[4], opt = this._transport.mergeOptions(options);
return stackIntercept<LookupInvoiceMsg, Invoice>("unary", this._transport, method, opt, input);
}
/**
*
* HtlcModifier is a bidirectional streaming RPC that allows a client to
* intercept and modify the HTLCs that attempt to settle the given invoice. The
* server will send HTLCs of invoices to the client and the client can modify
* some aspects of the HTLC in order to pass the invoice acceptance tests.
*
* @generated from protobuf rpc: HtlcModifier
*/
htlcModifier(options?: RpcOptions): DuplexStreamingCall<HtlcModifyResponse, HtlcModifyRequest> {
const method = this.methods[5], opt = this._transport.mergeOptions(options);
return stackIntercept<HtlcModifyResponse, HtlcModifyRequest>("duplex", this._transport, method, opt);
}
}

View file

@ -1,7 +1,6 @@
// @generated by protobuf-ts 2.8.1
// @generated by protobuf-ts 2.11.1
// @generated from protobuf file "invoices.proto" (package "invoicesrpc", syntax proto3)
// tslint:disable
import { Invoice } from "./lightning.js";
import { ServiceType } from "@protobuf-ts/runtime-rpc";
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
import type { IBinaryWriter } from "@protobuf-ts/runtime";
@ -11,8 +10,8 @@ import type { IBinaryReader } from "@protobuf-ts/runtime";
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
import type { PartialMessage } from "@protobuf-ts/runtime";
import { reflectionMergePartial } from "@protobuf-ts/runtime";
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
import { MessageType } from "@protobuf-ts/runtime";
import { Invoice } from "./lightning.js";
import { RouteHint } from "./lightning.js";
/**
* @generated from protobuf message invoicesrpc.CancelInvoiceMsg
@ -22,7 +21,7 @@ export interface CancelInvoiceMsg {
* Hash corresponding to the (hold) invoice to cancel. When using
* REST, this field must be encoded as base64.
*
* @generated from protobuf field: bytes payment_hash = 1;
* @generated from protobuf field: bytes payment_hash = 1
*/
paymentHash: Uint8Array;
}
@ -42,13 +41,13 @@ export interface AddHoldInvoiceRequest {
* field of the encoded payment request if the description_hash field is not
* being used.
*
* @generated from protobuf field: string memo = 1;
* @generated from protobuf field: string memo = 1
*/
memo: string;
/**
* The hash of the preimage
*
* @generated from protobuf field: bytes hash = 2;
* @generated from protobuf field: bytes hash = 2
*/
hash: Uint8Array;
/**
@ -57,7 +56,7 @@ export interface AddHoldInvoiceRequest {
*
* The fields value and value_msat are mutually exclusive.
*
* @generated from protobuf field: int64 value = 3;
* @generated from protobuf field: int64 value = 3
*/
value: bigint;
/**
@ -66,7 +65,7 @@ export interface AddHoldInvoiceRequest {
*
* The fields value and value_msat are mutually exclusive.
*
* @generated from protobuf field: int64 value_msat = 10;
* @generated from protobuf field: int64 value_msat = 10
*/
valueMsat: bigint;
/**
@ -75,25 +74,25 @@ export interface AddHoldInvoiceRequest {
* payment (memo) is too long to naturally fit within the description field
* of an encoded payment request.
*
* @generated from protobuf field: bytes description_hash = 4;
* @generated from protobuf field: bytes description_hash = 4
*/
descriptionHash: Uint8Array;
/**
* Payment request expiry time in seconds. Default is 3600 (1 hour).
* Payment request expiry time in seconds. Default is 86400 (24 hours).
*
* @generated from protobuf field: int64 expiry = 5;
* @generated from protobuf field: int64 expiry = 5
*/
expiry: bigint;
/**
* Fallback on-chain address.
*
* @generated from protobuf field: string fallback_addr = 6;
* @generated from protobuf field: string fallback_addr = 6
*/
fallbackAddr: string;
/**
* Delta to use for the time-lock of the CLTV extended to the final hop.
*
* @generated from protobuf field: uint64 cltv_expiry = 7;
* @generated from protobuf field: uint64 cltv_expiry = 7
*/
cltvExpiry: bigint;
/**
@ -101,13 +100,13 @@ export interface AddHoldInvoiceRequest {
* Route hints that can each be individually used to assist in reaching the
* invoice's destination.
*
* @generated from protobuf field: repeated lnrpc.RouteHint route_hints = 8;
* @generated from protobuf field: repeated lnrpc.RouteHint route_hints = 8
*/
routeHints: RouteHint[];
/**
* Whether this invoice should include routing hints for private channels.
*
* @generated from protobuf field: bool private = 9;
* @generated from protobuf field: bool private = 9
*/
private: boolean;
}
@ -121,7 +120,7 @@ export interface AddHoldInvoiceResp {
* details of the invoice, the sender has all the data necessary to send a
* payment to the recipient.
*
* @generated from protobuf field: string payment_request = 1;
* @generated from protobuf field: string payment_request = 1
*/
paymentRequest: string;
/**
@ -131,16 +130,17 @@ export interface AddHoldInvoiceResp {
* SubscribeInvoices call can use this to instantly get notified of all added
* invoices with an add_index greater than this one.
*
* @generated from protobuf field: uint64 add_index = 2;
* @generated from protobuf field: uint64 add_index = 2
*/
addIndex: bigint;
/**
*
* The payment address of the generated invoice. This value should be used
* in all payments for this invoice as we require it for end to end
* The payment address of the generated invoice. This is also called
* the payment secret in specifications (e.g. BOLT 11). This value should
* be used in all payments for this invoice as we require it for end to end
* security.
*
* @generated from protobuf field: bytes payment_addr = 3;
* @generated from protobuf field: bytes payment_addr = 3
*/
paymentAddr: Uint8Array;
}
@ -152,7 +152,7 @@ export interface SettleInvoiceMsg {
* Externally discovered pre-image that should be used to settle the hold
* invoice.
*
* @generated from protobuf field: bytes preimage = 1;
* @generated from protobuf field: bytes preimage = 1
*/
preimage: Uint8Array;
}
@ -169,7 +169,7 @@ export interface SubscribeSingleInvoiceRequest {
* Hash corresponding to the (hold) invoice to subscribe to. When using
* REST, this field must be encoded as base64url.
*
* @generated from protobuf field: bytes r_hash = 2;
* @generated from protobuf field: bytes r_hash = 2
*/
rHash: Uint8Array;
}
@ -185,29 +185,122 @@ export interface LookupInvoiceMsg {
/**
* When using REST, this field must be encoded as base64.
*
* @generated from protobuf field: bytes payment_hash = 1;
* @generated from protobuf field: bytes payment_hash = 1
*/
paymentHash: Uint8Array;
} | {
oneofKind: "paymentAddr";
/**
* @generated from protobuf field: bytes payment_addr = 2;
* @generated from protobuf field: bytes payment_addr = 2
*/
paymentAddr: Uint8Array;
} | {
oneofKind: "setId";
/**
* @generated from protobuf field: bytes set_id = 3;
* @generated from protobuf field: bytes set_id = 3
*/
setId: Uint8Array;
} | {
oneofKind: undefined;
};
/**
* @generated from protobuf field: invoicesrpc.LookupModifier lookup_modifier = 4;
* @generated from protobuf field: invoicesrpc.LookupModifier lookup_modifier = 4
*/
lookupModifier: LookupModifier;
}
/**
* CircuitKey is a unique identifier for an HTLC.
*
* @generated from protobuf message invoicesrpc.CircuitKey
*/
export interface CircuitKey {
/**
* The id of the channel that the is part of this circuit.
*
* @generated from protobuf field: uint64 chan_id = 1
*/
chanId: bigint;
/**
* The index of the incoming htlc in the incoming channel.
*
* @generated from protobuf field: uint64 htlc_id = 2
*/
htlcId: bigint;
}
/**
* @generated from protobuf message invoicesrpc.HtlcModifyRequest
*/
export interface HtlcModifyRequest {
/**
* The invoice the intercepted HTLC is attempting to settle. The HTLCs in
* the invoice are only HTLCs that have already been accepted or settled,
* not including the current intercepted HTLC.
*
* @generated from protobuf field: lnrpc.Invoice invoice = 1
*/
invoice?: Invoice;
/**
* The unique identifier of the HTLC of this intercepted HTLC.
*
* @generated from protobuf field: invoicesrpc.CircuitKey exit_htlc_circuit_key = 2
*/
exitHtlcCircuitKey?: CircuitKey;
/**
* The amount in milli-satoshi that the exit HTLC is attempting to pay.
*
* @generated from protobuf field: uint64 exit_htlc_amt = 3
*/
exitHtlcAmt: bigint;
/**
* The absolute expiry height of the exit HTLC.
*
* @generated from protobuf field: uint32 exit_htlc_expiry = 4
*/
exitHtlcExpiry: number;
/**
* The current block height.
*
* @generated from protobuf field: uint32 current_height = 5
*/
currentHeight: number;
/**
* The wire message custom records of the exit HTLC.
*
* @generated from protobuf field: map<uint64, bytes> exit_htlc_wire_custom_records = 6
*/
exitHtlcWireCustomRecords: {
[key: string]: Uint8Array;
};
}
/**
* @generated from protobuf message invoicesrpc.HtlcModifyResponse
*/
export interface HtlcModifyResponse {
/**
* The circuit key of the HTLC that the client wants to modify.
*
* @generated from protobuf field: invoicesrpc.CircuitKey circuit_key = 1
*/
circuitKey?: CircuitKey;
/**
* The modified amount in milli-satoshi that the exit HTLC is paying. This
* value can be different from the actual on-chain HTLC amount, in case the
* HTLC carries other valuable items, as can be the case with custom channel
* types.
*
* @generated from protobuf field: optional uint64 amt_paid = 2
*/
amtPaid?: bigint;
/**
* This flag indicates whether the HTLCs associated with the invoices should
* be cancelled. The interceptor client may set this field if some
* unexpected behavior is encountered. Setting this will ignore the amt_paid
* field.
*
* @generated from protobuf field: bool cancel_set = 3
*/
cancelSet: boolean;
}
/**
* @generated from protobuf enum invoicesrpc.LookupModifier
*/
@ -245,8 +338,8 @@ class CancelInvoiceMsg$Type extends MessageType<CancelInvoiceMsg> {
]);
}
create(value?: PartialMessage<CancelInvoiceMsg>): CancelInvoiceMsg {
const message = { paymentHash: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.paymentHash = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<CancelInvoiceMsg>(this, message, value);
return message;
@ -290,14 +383,26 @@ class CancelInvoiceResp$Type extends MessageType<CancelInvoiceResp> {
super("invoicesrpc.CancelInvoiceResp", []);
}
create(value?: PartialMessage<CancelInvoiceResp>): CancelInvoiceResp {
const message = {};
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<CancelInvoiceResp>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CancelInvoiceResp): CancelInvoiceResp {
return target ?? this.create();
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: CancelInvoiceResp, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
let u = options.writeUnknownFields;
@ -322,13 +427,22 @@ class AddHoldInvoiceRequest$Type extends MessageType<AddHoldInvoiceRequest> {
{ no: 5, name: "expiry", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ },
{ no: 6, name: "fallback_addr", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 7, name: "cltv_expiry", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
{ no: 8, name: "route_hints", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => RouteHint },
{ no: 8, name: "route_hints", kind: "message", repeat: 2 /*RepeatType.UNPACKED*/, T: () => RouteHint },
{ no: 9, name: "private", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }
]);
}
create(value?: PartialMessage<AddHoldInvoiceRequest>): AddHoldInvoiceRequest {
const message = { memo: "", hash: new Uint8Array(0), value: 0n, valueMsat: 0n, descriptionHash: new Uint8Array(0), expiry: 0n, fallbackAddr: "", cltvExpiry: 0n, routeHints: [], private: false };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.memo = "";
message.hash = new Uint8Array(0);
message.value = 0n;
message.valueMsat = 0n;
message.descriptionHash = new Uint8Array(0);
message.expiry = 0n;
message.fallbackAddr = "";
message.cltvExpiry = 0n;
message.routeHints = [];
message.private = false;
if (value !== undefined)
reflectionMergePartial<AddHoldInvoiceRequest>(this, message, value);
return message;
@ -389,9 +503,6 @@ class AddHoldInvoiceRequest$Type extends MessageType<AddHoldInvoiceRequest> {
/* int64 value = 3; */
if (message.value !== 0n)
writer.tag(3, WireType.Varint).int64(message.value);
/* int64 value_msat = 10; */
if (message.valueMsat !== 0n)
writer.tag(10, WireType.Varint).int64(message.valueMsat);
/* bytes description_hash = 4; */
if (message.descriptionHash.length)
writer.tag(4, WireType.LengthDelimited).bytes(message.descriptionHash);
@ -410,6 +521,9 @@ class AddHoldInvoiceRequest$Type extends MessageType<AddHoldInvoiceRequest> {
/* bool private = 9; */
if (message.private !== false)
writer.tag(9, WireType.Varint).bool(message.private);
/* int64 value_msat = 10; */
if (message.valueMsat !== 0n)
writer.tag(10, WireType.Varint).int64(message.valueMsat);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
@ -430,8 +544,10 @@ class AddHoldInvoiceResp$Type extends MessageType<AddHoldInvoiceResp> {
]);
}
create(value?: PartialMessage<AddHoldInvoiceResp>): AddHoldInvoiceResp {
const message = { paymentRequest: "", addIndex: 0n, paymentAddr: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.paymentRequest = "";
message.addIndex = 0n;
message.paymentAddr = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<AddHoldInvoiceResp>(this, message, value);
return message;
@ -489,8 +605,8 @@ class SettleInvoiceMsg$Type extends MessageType<SettleInvoiceMsg> {
]);
}
create(value?: PartialMessage<SettleInvoiceMsg>): SettleInvoiceMsg {
const message = { preimage: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.preimage = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<SettleInvoiceMsg>(this, message, value);
return message;
@ -534,14 +650,26 @@ class SettleInvoiceResp$Type extends MessageType<SettleInvoiceResp> {
super("invoicesrpc.SettleInvoiceResp", []);
}
create(value?: PartialMessage<SettleInvoiceResp>): SettleInvoiceResp {
const message = {};
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<SettleInvoiceResp>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SettleInvoiceResp): SettleInvoiceResp {
return target ?? this.create();
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: SettleInvoiceResp, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
let u = options.writeUnknownFields;
@ -562,8 +690,8 @@ class SubscribeSingleInvoiceRequest$Type extends MessageType<SubscribeSingleInvo
]);
}
create(value?: PartialMessage<SubscribeSingleInvoiceRequest>): SubscribeSingleInvoiceRequest {
const message = { rHash: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.rHash = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<SubscribeSingleInvoiceRequest>(this, message, value);
return message;
@ -612,8 +740,9 @@ class LookupInvoiceMsg$Type extends MessageType<LookupInvoiceMsg> {
]);
}
create(value?: PartialMessage<LookupInvoiceMsg>): LookupInvoiceMsg {
const message = { invoiceRef: { oneofKind: undefined }, lookupModifier: 0 };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.invoiceRef = { oneofKind: undefined };
message.lookupModifier = 0;
if (value !== undefined)
reflectionMergePartial<LookupInvoiceMsg>(this, message, value);
return message;
@ -678,6 +807,223 @@ class LookupInvoiceMsg$Type extends MessageType<LookupInvoiceMsg> {
* @generated MessageType for protobuf message invoicesrpc.LookupInvoiceMsg
*/
export const LookupInvoiceMsg = new LookupInvoiceMsg$Type();
// @generated message type with reflection information, may provide speed optimized methods
class CircuitKey$Type extends MessageType<CircuitKey> {
constructor() {
super("invoicesrpc.CircuitKey", [
{ no: 1, name: "chan_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
{ no: 2, name: "htlc_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }
]);
}
create(value?: PartialMessage<CircuitKey>): CircuitKey {
const message = globalThis.Object.create((this.messagePrototype!));
message.chanId = 0n;
message.htlcId = 0n;
if (value !== undefined)
reflectionMergePartial<CircuitKey>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CircuitKey): CircuitKey {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* uint64 chan_id */ 1:
message.chanId = reader.uint64().toBigInt();
break;
case /* uint64 htlc_id */ 2:
message.htlcId = reader.uint64().toBigInt();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: CircuitKey, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* uint64 chan_id = 1; */
if (message.chanId !== 0n)
writer.tag(1, WireType.Varint).uint64(message.chanId);
/* uint64 htlc_id = 2; */
if (message.htlcId !== 0n)
writer.tag(2, WireType.Varint).uint64(message.htlcId);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message invoicesrpc.CircuitKey
*/
export const CircuitKey = new CircuitKey$Type();
// @generated message type with reflection information, may provide speed optimized methods
class HtlcModifyRequest$Type extends MessageType<HtlcModifyRequest> {
constructor() {
super("invoicesrpc.HtlcModifyRequest", [
{ no: 1, name: "invoice", kind: "message", T: () => Invoice },
{ no: 2, name: "exit_htlc_circuit_key", kind: "message", T: () => CircuitKey },
{ no: 3, name: "exit_htlc_amt", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
{ no: 4, name: "exit_htlc_expiry", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 5, name: "current_height", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 6, name: "exit_htlc_wire_custom_records", kind: "map", K: 4 /*ScalarType.UINT64*/, V: { kind: "scalar", T: 12 /*ScalarType.BYTES*/ } }
]);
}
create(value?: PartialMessage<HtlcModifyRequest>): HtlcModifyRequest {
const message = globalThis.Object.create((this.messagePrototype!));
message.exitHtlcAmt = 0n;
message.exitHtlcExpiry = 0;
message.currentHeight = 0;
message.exitHtlcWireCustomRecords = {};
if (value !== undefined)
reflectionMergePartial<HtlcModifyRequest>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: HtlcModifyRequest): HtlcModifyRequest {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* lnrpc.Invoice invoice */ 1:
message.invoice = Invoice.internalBinaryRead(reader, reader.uint32(), options, message.invoice);
break;
case /* invoicesrpc.CircuitKey exit_htlc_circuit_key */ 2:
message.exitHtlcCircuitKey = CircuitKey.internalBinaryRead(reader, reader.uint32(), options, message.exitHtlcCircuitKey);
break;
case /* uint64 exit_htlc_amt */ 3:
message.exitHtlcAmt = reader.uint64().toBigInt();
break;
case /* uint32 exit_htlc_expiry */ 4:
message.exitHtlcExpiry = reader.uint32();
break;
case /* uint32 current_height */ 5:
message.currentHeight = reader.uint32();
break;
case /* map<uint64, bytes> exit_htlc_wire_custom_records */ 6:
this.binaryReadMap6(message.exitHtlcWireCustomRecords, reader, options);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
private binaryReadMap6(map: HtlcModifyRequest["exitHtlcWireCustomRecords"], reader: IBinaryReader, options: BinaryReadOptions): void {
let len = reader.uint32(), end = reader.pos + len, key: keyof HtlcModifyRequest["exitHtlcWireCustomRecords"] | undefined, val: HtlcModifyRequest["exitHtlcWireCustomRecords"][any] | undefined;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case 1:
key = reader.uint64().toString();
break;
case 2:
val = reader.bytes();
break;
default: throw new globalThis.Error("unknown map entry field for invoicesrpc.HtlcModifyRequest.exit_htlc_wire_custom_records");
}
}
map[key ?? "0"] = val ?? new Uint8Array(0);
}
internalBinaryWrite(message: HtlcModifyRequest, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* lnrpc.Invoice invoice = 1; */
if (message.invoice)
Invoice.internalBinaryWrite(message.invoice, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
/* invoicesrpc.CircuitKey exit_htlc_circuit_key = 2; */
if (message.exitHtlcCircuitKey)
CircuitKey.internalBinaryWrite(message.exitHtlcCircuitKey, writer.tag(2, WireType.LengthDelimited).fork(), options).join();
/* uint64 exit_htlc_amt = 3; */
if (message.exitHtlcAmt !== 0n)
writer.tag(3, WireType.Varint).uint64(message.exitHtlcAmt);
/* uint32 exit_htlc_expiry = 4; */
if (message.exitHtlcExpiry !== 0)
writer.tag(4, WireType.Varint).uint32(message.exitHtlcExpiry);
/* uint32 current_height = 5; */
if (message.currentHeight !== 0)
writer.tag(5, WireType.Varint).uint32(message.currentHeight);
/* map<uint64, bytes> exit_htlc_wire_custom_records = 6; */
for (let k of globalThis.Object.keys(message.exitHtlcWireCustomRecords))
writer.tag(6, WireType.LengthDelimited).fork().tag(1, WireType.Varint).uint64(k).tag(2, WireType.LengthDelimited).bytes(message.exitHtlcWireCustomRecords[k]).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message invoicesrpc.HtlcModifyRequest
*/
export const HtlcModifyRequest = new HtlcModifyRequest$Type();
// @generated message type with reflection information, may provide speed optimized methods
class HtlcModifyResponse$Type extends MessageType<HtlcModifyResponse> {
constructor() {
super("invoicesrpc.HtlcModifyResponse", [
{ no: 1, name: "circuit_key", kind: "message", T: () => CircuitKey },
{ no: 2, name: "amt_paid", kind: "scalar", opt: true, T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
{ no: 3, name: "cancel_set", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }
]);
}
create(value?: PartialMessage<HtlcModifyResponse>): HtlcModifyResponse {
const message = globalThis.Object.create((this.messagePrototype!));
message.cancelSet = false;
if (value !== undefined)
reflectionMergePartial<HtlcModifyResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: HtlcModifyResponse): HtlcModifyResponse {
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* invoicesrpc.CircuitKey circuit_key */ 1:
message.circuitKey = CircuitKey.internalBinaryRead(reader, reader.uint32(), options, message.circuitKey);
break;
case /* optional uint64 amt_paid */ 2:
message.amtPaid = reader.uint64().toBigInt();
break;
case /* bool cancel_set */ 3:
message.cancelSet = reader.bool();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: HtlcModifyResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* invoicesrpc.CircuitKey circuit_key = 1; */
if (message.circuitKey)
CircuitKey.internalBinaryWrite(message.circuitKey, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
/* optional uint64 amt_paid = 2; */
if (message.amtPaid !== undefined)
writer.tag(2, WireType.Varint).uint64(message.amtPaid);
/* bool cancel_set = 3; */
if (message.cancelSet !== false)
writer.tag(3, WireType.Varint).bool(message.cancelSet);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message invoicesrpc.HtlcModifyResponse
*/
export const HtlcModifyResponse = new HtlcModifyResponse$Type();
/**
* @generated ServiceType for protobuf service invoicesrpc.Invoices
*/
@ -686,5 +1032,6 @@ export const Invoices = new ServiceType("invoicesrpc.Invoices", [
{ name: "CancelInvoice", options: {}, I: CancelInvoiceMsg, O: CancelInvoiceResp },
{ name: "AddHoldInvoice", options: {}, I: AddHoldInvoiceRequest, O: AddHoldInvoiceResp },
{ name: "SettleInvoice", options: {}, I: SettleInvoiceMsg, O: SettleInvoiceResp },
{ name: "LookupInvoiceV2", options: {}, I: LookupInvoiceMsg, O: Invoice }
{ name: "LookupInvoiceV2", options: {}, I: LookupInvoiceMsg, O: Invoice },
{ name: "HtlcModifier", serverStreaming: true, clientStreaming: true, options: {}, I: HtlcModifyResponse, O: HtlcModifyRequest }
]);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,13 @@
// @generated by protobuf-ts 2.8.1
// @generated by protobuf-ts 2.11.1
// @generated from protobuf file "router.proto" (package "routerrpc", syntax proto3)
// tslint:disable
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
import type { ServiceInfo } from "@protobuf-ts/runtime-rpc";
import { Router } from "./router.js";
import type { DeleteAliasesResponse } from "./router.js";
import type { DeleteAliasesRequest } from "./router.js";
import type { AddAliasesResponse } from "./router.js";
import type { AddAliasesRequest } from "./router.js";
import type { UpdateChanStatusResponse } from "./router.js";
import type { UpdateChanStatusRequest } from "./router.js";
import type { ForwardHtlcInterceptRequest } from "./router.js";
@ -39,6 +43,23 @@ import type { Payment } from "./lightning.js";
import type { SendPaymentRequest } from "./router.js";
import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc";
import type { RpcOptions } from "@protobuf-ts/runtime-rpc";
//
// Comments in this file will be directly parsed into the API
// Documentation as descriptions of the associated method, message, or field.
// These descriptions should go right above the definition of the object, and
// can be in either block or // comment format.
//
// An RPC method can be matched to an lncli command by placing a line in the
// beginning of the description in exactly the following format:
// lncli: `methodname`
//
// Failure to specify the exact name of the command will cause documentation
// generation to fail.
//
// More information on how exactly the gRPC documentation is generated from
// this proto file can be found here:
// https://github.com/lightninglabs/lightning-api
/**
* Router is a service that offers advanced interaction with the router
* subsystem of the daemon.
@ -50,17 +71,20 @@ export interface IRouterClient {
*
* SendPaymentV2 attempts to route a payment described by the passed
* PaymentRequest to the final destination. The call returns a stream of
* payment updates.
* payment updates. When using this RPC, make sure to set a fee limit, as the
* default routing fee limit is 0 sats. Without a non-zero fee limit only
* routes without fees will be attempted which often fails with
* FAILURE_REASON_NO_ROUTE.
*
* @generated from protobuf rpc: SendPaymentV2(routerrpc.SendPaymentRequest) returns (stream lnrpc.Payment);
* @generated from protobuf rpc: SendPaymentV2
*/
sendPaymentV2(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, Payment>;
/**
*
* lncli: `trackpayment`
* TrackPaymentV2 returns an update stream for the payment identified by the
* payment hash.
*
* @generated from protobuf rpc: TrackPaymentV2(routerrpc.TrackPaymentRequest) returns (stream lnrpc.Payment);
* @generated from protobuf rpc: TrackPaymentV2
*/
trackPaymentV2(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, Payment>;
/**
@ -72,7 +96,7 @@ export interface IRouterClient {
* payment attempt make sure to subscribe to this method before initiating any
* payments.
*
* @generated from protobuf rpc: TrackPayments(routerrpc.TrackPaymentsRequest) returns (stream lnrpc.Payment);
* @generated from protobuf rpc: TrackPayments
*/
trackPayments(input: TrackPaymentsRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentsRequest, Payment>;
/**
@ -80,7 +104,7 @@ export interface IRouterClient {
* EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
* may cost to send an HTLC to the target end destination.
*
* @generated from protobuf rpc: EstimateRouteFee(routerrpc.RouteFeeRequest) returns (routerrpc.RouteFeeResponse);
* @generated from protobuf rpc: EstimateRouteFee
*/
estimateRouteFee(input: RouteFeeRequest, options?: RpcOptions): UnaryCall<RouteFeeRequest, RouteFeeResponse>;
/**
@ -92,7 +116,7 @@ export interface IRouterClient {
* SendToRouteV2 in that it doesn't return the full HTLC information.
*
* @deprecated
* @generated from protobuf rpc: SendToRoute(routerrpc.SendToRouteRequest) returns (routerrpc.SendToRouteResponse);
* @generated from protobuf rpc: SendToRoute
*/
sendToRoute(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, SendToRouteResponse>;
/**
@ -102,65 +126,71 @@ export interface IRouterClient {
* route manually. This can be used for things like rebalancing, and atomic
* swaps.
*
* @generated from protobuf rpc: SendToRouteV2(routerrpc.SendToRouteRequest) returns (lnrpc.HTLCAttempt);
* @generated from protobuf rpc: SendToRouteV2
*/
sendToRouteV2(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, HTLCAttempt>;
/**
*
* lncli: `resetmc`
* ResetMissionControl clears all mission control state and starts with a clean
* slate.
*
* @generated from protobuf rpc: ResetMissionControl(routerrpc.ResetMissionControlRequest) returns (routerrpc.ResetMissionControlResponse);
* @generated from protobuf rpc: ResetMissionControl
*/
resetMissionControl(input: ResetMissionControlRequest, options?: RpcOptions): UnaryCall<ResetMissionControlRequest, ResetMissionControlResponse>;
/**
*
* lncli: `querymc`
* QueryMissionControl exposes the internal mission control state to callers.
* It is a development feature.
*
* @generated from protobuf rpc: QueryMissionControl(routerrpc.QueryMissionControlRequest) returns (routerrpc.QueryMissionControlResponse);
* @generated from protobuf rpc: QueryMissionControl
*/
queryMissionControl(input: QueryMissionControlRequest, options?: RpcOptions): UnaryCall<QueryMissionControlRequest, QueryMissionControlResponse>;
/**
*
* lncli: `importmc`
* XImportMissionControl is an experimental API that imports the state provided
* to the internal mission control's state, using all results which are more
* recent than our existing values. These values will only be imported
* in-memory, and will not be persisted across restarts.
*
* @generated from protobuf rpc: XImportMissionControl(routerrpc.XImportMissionControlRequest) returns (routerrpc.XImportMissionControlResponse);
* @generated from protobuf rpc: XImportMissionControl
*/
xImportMissionControl(input: XImportMissionControlRequest, options?: RpcOptions): UnaryCall<XImportMissionControlRequest, XImportMissionControlResponse>;
/**
*
* lncli: `getmccfg`
* GetMissionControlConfig returns mission control's current config.
*
* @generated from protobuf rpc: GetMissionControlConfig(routerrpc.GetMissionControlConfigRequest) returns (routerrpc.GetMissionControlConfigResponse);
* @generated from protobuf rpc: GetMissionControlConfig
*/
getMissionControlConfig(input: GetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<GetMissionControlConfigRequest, GetMissionControlConfigResponse>;
/**
*
* lncli: `setmccfg`
* SetMissionControlConfig will set mission control's config, if the config
* provided is valid.
*
* @generated from protobuf rpc: SetMissionControlConfig(routerrpc.SetMissionControlConfigRequest) returns (routerrpc.SetMissionControlConfigResponse);
* @generated from protobuf rpc: SetMissionControlConfig
*/
setMissionControlConfig(input: SetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<SetMissionControlConfigRequest, SetMissionControlConfigResponse>;
/**
* lncli: `queryprob`
* Deprecated. QueryProbability returns the current success probability
* estimate for a given node pair and amount. The call returns a zero success
* probability if no channel is available or if the amount violates min/max
* HTLC constraints.
*
* QueryProbability returns the current success probability estimate for a
* given node pair and amount.
*
* @generated from protobuf rpc: QueryProbability(routerrpc.QueryProbabilityRequest) returns (routerrpc.QueryProbabilityResponse);
* @generated from protobuf rpc: QueryProbability
*/
queryProbability(input: QueryProbabilityRequest, options?: RpcOptions): UnaryCall<QueryProbabilityRequest, QueryProbabilityResponse>;
/**
*
* lncli: `buildroute`
* BuildRoute builds a fully specified route based on a list of hop public
* keys. It retrieves the relevant channel policies from the graph in order to
* calculate the correct fees and time locks.
* Note that LND will use its default final_cltv_delta if no value is supplied.
* Make sure to add the correct final_cltv_delta depending on the invoice
* restriction. Moreover the caller has to make sure to provide the
* payment_addr if the route is paying an invoice which signaled it.
*
* @generated from protobuf rpc: BuildRoute(routerrpc.BuildRouteRequest) returns (routerrpc.BuildRouteResponse);
* @generated from protobuf rpc: BuildRoute
*/
buildRoute(input: BuildRouteRequest, options?: RpcOptions): UnaryCall<BuildRouteRequest, BuildRouteResponse>;
/**
@ -168,7 +198,7 @@ export interface IRouterClient {
* SubscribeHtlcEvents creates a uni-directional stream from the server to
* the client which delivers a stream of htlc events.
*
* @generated from protobuf rpc: SubscribeHtlcEvents(routerrpc.SubscribeHtlcEventsRequest) returns (stream routerrpc.HtlcEvent);
* @generated from protobuf rpc: SubscribeHtlcEvents
*/
subscribeHtlcEvents(input: SubscribeHtlcEventsRequest, options?: RpcOptions): ServerStreamingCall<SubscribeHtlcEventsRequest, HtlcEvent>;
/**
@ -178,7 +208,7 @@ export interface IRouterClient {
* returns a stream of payment status updates.
*
* @deprecated
* @generated from protobuf rpc: SendPayment(routerrpc.SendPaymentRequest) returns (stream routerrpc.PaymentStatus);
* @generated from protobuf rpc: SendPayment
*/
sendPayment(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, PaymentStatus>;
/**
@ -187,7 +217,7 @@ export interface IRouterClient {
* the payment identified by the payment hash.
*
* @deprecated
* @generated from protobuf rpc: TrackPayment(routerrpc.TrackPaymentRequest) returns (stream routerrpc.PaymentStatus);
* @generated from protobuf rpc: TrackPayment
*/
trackPayment(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, PaymentStatus>;
/**
@ -198,20 +228,59 @@ export interface IRouterClient {
* In case of interception, the htlc can be either settled, cancelled or
* resumed later by using the ResolveHoldForward endpoint.
*
* @generated from protobuf rpc: HtlcInterceptor(stream routerrpc.ForwardHtlcInterceptResponse) returns (stream routerrpc.ForwardHtlcInterceptRequest);
* @generated from protobuf rpc: HtlcInterceptor
*/
htlcInterceptor(options?: RpcOptions): DuplexStreamingCall<ForwardHtlcInterceptResponse, ForwardHtlcInterceptRequest>;
/**
*
* lncli: `updatechanstatus`
* UpdateChanStatus attempts to manually set the state of a channel
* (enabled, disabled, or auto). A manual "disable" request will cause the
* channel to stay disabled until a subsequent manual request of either
* "enable" or "auto".
*
* @generated from protobuf rpc: UpdateChanStatus(routerrpc.UpdateChanStatusRequest) returns (routerrpc.UpdateChanStatusResponse);
* @generated from protobuf rpc: UpdateChanStatus
*/
updateChanStatus(input: UpdateChanStatusRequest, options?: RpcOptions): UnaryCall<UpdateChanStatusRequest, UpdateChanStatusResponse>;
/**
*
* XAddLocalChanAliases is an experimental API that creates a set of new
* channel SCID alias mappings. The final total set of aliases in the manager
* after the add operation is returned. This is only a locally stored alias,
* and will not be communicated to the channel peer via any message. Therefore,
* routing over such an alias will only work if the peer also calls this same
* RPC on their end. If an alias already exists, an error is returned
*
* @generated from protobuf rpc: XAddLocalChanAliases
*/
xAddLocalChanAliases(input: AddAliasesRequest, options?: RpcOptions): UnaryCall<AddAliasesRequest, AddAliasesResponse>;
/**
*
* XDeleteLocalChanAliases is an experimental API that deletes a set of alias
* mappings. The final total set of aliases in the manager after the delete
* operation is returned. The deletion will not be communicated to the channel
* peer via any message.
*
* @generated from protobuf rpc: XDeleteLocalChanAliases
*/
xDeleteLocalChanAliases(input: DeleteAliasesRequest, options?: RpcOptions): UnaryCall<DeleteAliasesRequest, DeleteAliasesResponse>;
}
//
// Comments in this file will be directly parsed into the API
// Documentation as descriptions of the associated method, message, or field.
// These descriptions should go right above the definition of the object, and
// can be in either block or // comment format.
//
// An RPC method can be matched to an lncli command by placing a line in the
// beginning of the description in exactly the following format:
// lncli: `methodname`
//
// Failure to specify the exact name of the command will cause documentation
// generation to fail.
//
// More information on how exactly the gRPC documentation is generated from
// this proto file can be found here:
// https://github.com/lightninglabs/lightning-api
/**
* Router is a service that offers advanced interaction with the router
* subsystem of the daemon.
@ -228,20 +297,23 @@ export class RouterClient implements IRouterClient, ServiceInfo {
*
* SendPaymentV2 attempts to route a payment described by the passed
* PaymentRequest to the final destination. The call returns a stream of
* payment updates.
* payment updates. When using this RPC, make sure to set a fee limit, as the
* default routing fee limit is 0 sats. Without a non-zero fee limit only
* routes without fees will be attempted which often fails with
* FAILURE_REASON_NO_ROUTE.
*
* @generated from protobuf rpc: SendPaymentV2(routerrpc.SendPaymentRequest) returns (stream lnrpc.Payment);
* @generated from protobuf rpc: SendPaymentV2
*/
sendPaymentV2(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, Payment> {
const method = this.methods[0], opt = this._transport.mergeOptions(options);
return stackIntercept<SendPaymentRequest, Payment>("serverStreaming", this._transport, method, opt, input);
}
/**
*
* lncli: `trackpayment`
* TrackPaymentV2 returns an update stream for the payment identified by the
* payment hash.
*
* @generated from protobuf rpc: TrackPaymentV2(routerrpc.TrackPaymentRequest) returns (stream lnrpc.Payment);
* @generated from protobuf rpc: TrackPaymentV2
*/
trackPaymentV2(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, Payment> {
const method = this.methods[1], opt = this._transport.mergeOptions(options);
@ -256,7 +328,7 @@ export class RouterClient implements IRouterClient, ServiceInfo {
* payment attempt make sure to subscribe to this method before initiating any
* payments.
*
* @generated from protobuf rpc: TrackPayments(routerrpc.TrackPaymentsRequest) returns (stream lnrpc.Payment);
* @generated from protobuf rpc: TrackPayments
*/
trackPayments(input: TrackPaymentsRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentsRequest, Payment> {
const method = this.methods[2], opt = this._transport.mergeOptions(options);
@ -267,7 +339,7 @@ export class RouterClient implements IRouterClient, ServiceInfo {
* EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it
* may cost to send an HTLC to the target end destination.
*
* @generated from protobuf rpc: EstimateRouteFee(routerrpc.RouteFeeRequest) returns (routerrpc.RouteFeeResponse);
* @generated from protobuf rpc: EstimateRouteFee
*/
estimateRouteFee(input: RouteFeeRequest, options?: RpcOptions): UnaryCall<RouteFeeRequest, RouteFeeResponse> {
const method = this.methods[3], opt = this._transport.mergeOptions(options);
@ -282,7 +354,7 @@ export class RouterClient implements IRouterClient, ServiceInfo {
* SendToRouteV2 in that it doesn't return the full HTLC information.
*
* @deprecated
* @generated from protobuf rpc: SendToRoute(routerrpc.SendToRouteRequest) returns (routerrpc.SendToRouteResponse);
* @generated from protobuf rpc: SendToRoute
*/
sendToRoute(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, SendToRouteResponse> {
const method = this.methods[4], opt = this._transport.mergeOptions(options);
@ -295,86 +367,92 @@ export class RouterClient implements IRouterClient, ServiceInfo {
* route manually. This can be used for things like rebalancing, and atomic
* swaps.
*
* @generated from protobuf rpc: SendToRouteV2(routerrpc.SendToRouteRequest) returns (lnrpc.HTLCAttempt);
* @generated from protobuf rpc: SendToRouteV2
*/
sendToRouteV2(input: SendToRouteRequest, options?: RpcOptions): UnaryCall<SendToRouteRequest, HTLCAttempt> {
const method = this.methods[5], opt = this._transport.mergeOptions(options);
return stackIntercept<SendToRouteRequest, HTLCAttempt>("unary", this._transport, method, opt, input);
}
/**
*
* lncli: `resetmc`
* ResetMissionControl clears all mission control state and starts with a clean
* slate.
*
* @generated from protobuf rpc: ResetMissionControl(routerrpc.ResetMissionControlRequest) returns (routerrpc.ResetMissionControlResponse);
* @generated from protobuf rpc: ResetMissionControl
*/
resetMissionControl(input: ResetMissionControlRequest, options?: RpcOptions): UnaryCall<ResetMissionControlRequest, ResetMissionControlResponse> {
const method = this.methods[6], opt = this._transport.mergeOptions(options);
return stackIntercept<ResetMissionControlRequest, ResetMissionControlResponse>("unary", this._transport, method, opt, input);
}
/**
*
* lncli: `querymc`
* QueryMissionControl exposes the internal mission control state to callers.
* It is a development feature.
*
* @generated from protobuf rpc: QueryMissionControl(routerrpc.QueryMissionControlRequest) returns (routerrpc.QueryMissionControlResponse);
* @generated from protobuf rpc: QueryMissionControl
*/
queryMissionControl(input: QueryMissionControlRequest, options?: RpcOptions): UnaryCall<QueryMissionControlRequest, QueryMissionControlResponse> {
const method = this.methods[7], opt = this._transport.mergeOptions(options);
return stackIntercept<QueryMissionControlRequest, QueryMissionControlResponse>("unary", this._transport, method, opt, input);
}
/**
*
* lncli: `importmc`
* XImportMissionControl is an experimental API that imports the state provided
* to the internal mission control's state, using all results which are more
* recent than our existing values. These values will only be imported
* in-memory, and will not be persisted across restarts.
*
* @generated from protobuf rpc: XImportMissionControl(routerrpc.XImportMissionControlRequest) returns (routerrpc.XImportMissionControlResponse);
* @generated from protobuf rpc: XImportMissionControl
*/
xImportMissionControl(input: XImportMissionControlRequest, options?: RpcOptions): UnaryCall<XImportMissionControlRequest, XImportMissionControlResponse> {
const method = this.methods[8], opt = this._transport.mergeOptions(options);
return stackIntercept<XImportMissionControlRequest, XImportMissionControlResponse>("unary", this._transport, method, opt, input);
}
/**
*
* lncli: `getmccfg`
* GetMissionControlConfig returns mission control's current config.
*
* @generated from protobuf rpc: GetMissionControlConfig(routerrpc.GetMissionControlConfigRequest) returns (routerrpc.GetMissionControlConfigResponse);
* @generated from protobuf rpc: GetMissionControlConfig
*/
getMissionControlConfig(input: GetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<GetMissionControlConfigRequest, GetMissionControlConfigResponse> {
const method = this.methods[9], opt = this._transport.mergeOptions(options);
return stackIntercept<GetMissionControlConfigRequest, GetMissionControlConfigResponse>("unary", this._transport, method, opt, input);
}
/**
*
* lncli: `setmccfg`
* SetMissionControlConfig will set mission control's config, if the config
* provided is valid.
*
* @generated from protobuf rpc: SetMissionControlConfig(routerrpc.SetMissionControlConfigRequest) returns (routerrpc.SetMissionControlConfigResponse);
* @generated from protobuf rpc: SetMissionControlConfig
*/
setMissionControlConfig(input: SetMissionControlConfigRequest, options?: RpcOptions): UnaryCall<SetMissionControlConfigRequest, SetMissionControlConfigResponse> {
const method = this.methods[10], opt = this._transport.mergeOptions(options);
return stackIntercept<SetMissionControlConfigRequest, SetMissionControlConfigResponse>("unary", this._transport, method, opt, input);
}
/**
* lncli: `queryprob`
* Deprecated. QueryProbability returns the current success probability
* estimate for a given node pair and amount. The call returns a zero success
* probability if no channel is available or if the amount violates min/max
* HTLC constraints.
*
* QueryProbability returns the current success probability estimate for a
* given node pair and amount.
*
* @generated from protobuf rpc: QueryProbability(routerrpc.QueryProbabilityRequest) returns (routerrpc.QueryProbabilityResponse);
* @generated from protobuf rpc: QueryProbability
*/
queryProbability(input: QueryProbabilityRequest, options?: RpcOptions): UnaryCall<QueryProbabilityRequest, QueryProbabilityResponse> {
const method = this.methods[11], opt = this._transport.mergeOptions(options);
return stackIntercept<QueryProbabilityRequest, QueryProbabilityResponse>("unary", this._transport, method, opt, input);
}
/**
*
* lncli: `buildroute`
* BuildRoute builds a fully specified route based on a list of hop public
* keys. It retrieves the relevant channel policies from the graph in order to
* calculate the correct fees and time locks.
* Note that LND will use its default final_cltv_delta if no value is supplied.
* Make sure to add the correct final_cltv_delta depending on the invoice
* restriction. Moreover the caller has to make sure to provide the
* payment_addr if the route is paying an invoice which signaled it.
*
* @generated from protobuf rpc: BuildRoute(routerrpc.BuildRouteRequest) returns (routerrpc.BuildRouteResponse);
* @generated from protobuf rpc: BuildRoute
*/
buildRoute(input: BuildRouteRequest, options?: RpcOptions): UnaryCall<BuildRouteRequest, BuildRouteResponse> {
const method = this.methods[12], opt = this._transport.mergeOptions(options);
@ -385,7 +463,7 @@ export class RouterClient implements IRouterClient, ServiceInfo {
* SubscribeHtlcEvents creates a uni-directional stream from the server to
* the client which delivers a stream of htlc events.
*
* @generated from protobuf rpc: SubscribeHtlcEvents(routerrpc.SubscribeHtlcEventsRequest) returns (stream routerrpc.HtlcEvent);
* @generated from protobuf rpc: SubscribeHtlcEvents
*/
subscribeHtlcEvents(input: SubscribeHtlcEventsRequest, options?: RpcOptions): ServerStreamingCall<SubscribeHtlcEventsRequest, HtlcEvent> {
const method = this.methods[13], opt = this._transport.mergeOptions(options);
@ -398,7 +476,7 @@ export class RouterClient implements IRouterClient, ServiceInfo {
* returns a stream of payment status updates.
*
* @deprecated
* @generated from protobuf rpc: SendPayment(routerrpc.SendPaymentRequest) returns (stream routerrpc.PaymentStatus);
* @generated from protobuf rpc: SendPayment
*/
sendPayment(input: SendPaymentRequest, options?: RpcOptions): ServerStreamingCall<SendPaymentRequest, PaymentStatus> {
const method = this.methods[14], opt = this._transport.mergeOptions(options);
@ -410,7 +488,7 @@ export class RouterClient implements IRouterClient, ServiceInfo {
* the payment identified by the payment hash.
*
* @deprecated
* @generated from protobuf rpc: TrackPayment(routerrpc.TrackPaymentRequest) returns (stream routerrpc.PaymentStatus);
* @generated from protobuf rpc: TrackPayment
*/
trackPayment(input: TrackPaymentRequest, options?: RpcOptions): ServerStreamingCall<TrackPaymentRequest, PaymentStatus> {
const method = this.methods[15], opt = this._transport.mergeOptions(options);
@ -424,23 +502,51 @@ export class RouterClient implements IRouterClient, ServiceInfo {
* In case of interception, the htlc can be either settled, cancelled or
* resumed later by using the ResolveHoldForward endpoint.
*
* @generated from protobuf rpc: HtlcInterceptor(stream routerrpc.ForwardHtlcInterceptResponse) returns (stream routerrpc.ForwardHtlcInterceptRequest);
* @generated from protobuf rpc: HtlcInterceptor
*/
htlcInterceptor(options?: RpcOptions): DuplexStreamingCall<ForwardHtlcInterceptResponse, ForwardHtlcInterceptRequest> {
const method = this.methods[16], opt = this._transport.mergeOptions(options);
return stackIntercept<ForwardHtlcInterceptResponse, ForwardHtlcInterceptRequest>("duplex", this._transport, method, opt);
}
/**
*
* lncli: `updatechanstatus`
* UpdateChanStatus attempts to manually set the state of a channel
* (enabled, disabled, or auto). A manual "disable" request will cause the
* channel to stay disabled until a subsequent manual request of either
* "enable" or "auto".
*
* @generated from protobuf rpc: UpdateChanStatus(routerrpc.UpdateChanStatusRequest) returns (routerrpc.UpdateChanStatusResponse);
* @generated from protobuf rpc: UpdateChanStatus
*/
updateChanStatus(input: UpdateChanStatusRequest, options?: RpcOptions): UnaryCall<UpdateChanStatusRequest, UpdateChanStatusResponse> {
const method = this.methods[17], opt = this._transport.mergeOptions(options);
return stackIntercept<UpdateChanStatusRequest, UpdateChanStatusResponse>("unary", this._transport, method, opt, input);
}
/**
*
* XAddLocalChanAliases is an experimental API that creates a set of new
* channel SCID alias mappings. The final total set of aliases in the manager
* after the add operation is returned. This is only a locally stored alias,
* and will not be communicated to the channel peer via any message. Therefore,
* routing over such an alias will only work if the peer also calls this same
* RPC on their end. If an alias already exists, an error is returned
*
* @generated from protobuf rpc: XAddLocalChanAliases
*/
xAddLocalChanAliases(input: AddAliasesRequest, options?: RpcOptions): UnaryCall<AddAliasesRequest, AddAliasesResponse> {
const method = this.methods[18], opt = this._transport.mergeOptions(options);
return stackIntercept<AddAliasesRequest, AddAliasesResponse>("unary", this._transport, method, opt, input);
}
/**
*
* XDeleteLocalChanAliases is an experimental API that deletes a set of alias
* mappings. The final total set of aliases in the manager after the delete
* operation is returned. The deletion will not be communicated to the channel
* peer via any message.
*
* @generated from protobuf rpc: XDeleteLocalChanAliases
*/
xDeleteLocalChanAliases(input: DeleteAliasesRequest, options?: RpcOptions): UnaryCall<DeleteAliasesRequest, DeleteAliasesResponse> {
const method = this.methods[19], opt = this._transport.mergeOptions(options);
return stackIntercept<DeleteAliasesRequest, DeleteAliasesResponse>("unary", this._transport, method, opt, input);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
// @generated by protobuf-ts 2.8.1
// @generated by protobuf-ts 2.11.1
// @generated from protobuf file "signer.proto" (package "signrpc", syntax proto3)
// tslint:disable
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
@ -46,7 +46,7 @@ export interface ISignerClient {
* If we are unable to sign using the specified keys, then an error will be
* returned.
*
* @generated from protobuf rpc: SignOutputRaw(signrpc.SignReq) returns (signrpc.SignResp);
* @generated from protobuf rpc: SignOutputRaw
*/
signOutputRaw(input: SignReq, options?: RpcOptions): UnaryCall<SignReq, SignResp>;
/**
@ -62,7 +62,7 @@ export interface ISignerClient {
* in the TxOut field, the value in that same field, and finally the input
* index.
*
* @generated from protobuf rpc: ComputeInputScript(signrpc.SignReq) returns (signrpc.InputScriptResp);
* @generated from protobuf rpc: ComputeInputScript
*/
computeInputScript(input: SignReq, options?: RpcOptions): UnaryCall<SignReq, InputScriptResp>;
/**
@ -73,7 +73,7 @@ export interface ISignerClient {
* The main difference to SignMessage in the main RPC is that a specific key is
* used to sign the message instead of the node identity private key.
*
* @generated from protobuf rpc: SignMessage(signrpc.SignMessageReq) returns (signrpc.SignMessageResp);
* @generated from protobuf rpc: SignMessage
*/
signMessage(input: SignMessageReq, options?: RpcOptions): UnaryCall<SignMessageReq, SignMessageResp>;
/**
@ -84,7 +84,7 @@ export interface ISignerClient {
* The main difference to VerifyMessage in the main RPC is that the public key
* used to sign the message does not have to be a node known to the network.
*
* @generated from protobuf rpc: VerifyMessage(signrpc.VerifyMessageReq) returns (signrpc.VerifyMessageResp);
* @generated from protobuf rpc: VerifyMessage
*/
verifyMessage(input: VerifyMessageReq, options?: RpcOptions): UnaryCall<VerifyMessageReq, VerifyMessageResp>;
/**
@ -98,7 +98,7 @@ export interface ISignerClient {
* The resulting shared public key is serialized in the compressed format and
* hashed with sha256, resulting in the final key length of 256bit.
*
* @generated from protobuf rpc: DeriveSharedKey(signrpc.SharedKeyRequest) returns (signrpc.SharedKeyResponse);
* @generated from protobuf rpc: DeriveSharedKey
*/
deriveSharedKey(input: SharedKeyRequest, options?: RpcOptions): UnaryCall<SharedKeyRequest, SharedKeyResponse>;
/**
@ -115,7 +115,7 @@ export interface ISignerClient {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2CombineKeys(signrpc.MuSig2CombineKeysRequest) returns (signrpc.MuSig2CombineKeysResponse);
* @generated from protobuf rpc: MuSig2CombineKeys
*/
muSig2CombineKeys(input: MuSig2CombineKeysRequest, options?: RpcOptions): UnaryCall<MuSig2CombineKeysRequest, MuSig2CombineKeysResponse>;
/**
@ -131,7 +131,7 @@ export interface ISignerClient {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2CreateSession(signrpc.MuSig2SessionRequest) returns (signrpc.MuSig2SessionResponse);
* @generated from protobuf rpc: MuSig2CreateSession
*/
muSig2CreateSession(input: MuSig2SessionRequest, options?: RpcOptions): UnaryCall<MuSig2SessionRequest, MuSig2SessionResponse>;
/**
@ -144,7 +144,7 @@ export interface ISignerClient {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2RegisterNonces(signrpc.MuSig2RegisterNoncesRequest) returns (signrpc.MuSig2RegisterNoncesResponse);
* @generated from protobuf rpc: MuSig2RegisterNonces
*/
muSig2RegisterNonces(input: MuSig2RegisterNoncesRequest, options?: RpcOptions): UnaryCall<MuSig2RegisterNoncesRequest, MuSig2RegisterNoncesResponse>;
/**
@ -160,7 +160,7 @@ export interface ISignerClient {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2Sign(signrpc.MuSig2SignRequest) returns (signrpc.MuSig2SignResponse);
* @generated from protobuf rpc: MuSig2Sign
*/
muSig2Sign(input: MuSig2SignRequest, options?: RpcOptions): UnaryCall<MuSig2SignRequest, MuSig2SignResponse>;
/**
@ -174,7 +174,7 @@ export interface ISignerClient {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2CombineSig(signrpc.MuSig2CombineSigRequest) returns (signrpc.MuSig2CombineSigResponse);
* @generated from protobuf rpc: MuSig2CombineSig
*/
muSig2CombineSig(input: MuSig2CombineSigRequest, options?: RpcOptions): UnaryCall<MuSig2CombineSigRequest, MuSig2CombineSigResponse>;
/**
@ -187,7 +187,7 @@ export interface ISignerClient {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2Cleanup(signrpc.MuSig2CleanupRequest) returns (signrpc.MuSig2CleanupResponse);
* @generated from protobuf rpc: MuSig2Cleanup
*/
muSig2Cleanup(input: MuSig2CleanupRequest, options?: RpcOptions): UnaryCall<MuSig2CleanupRequest, MuSig2CleanupResponse>;
}
@ -214,7 +214,7 @@ export class SignerClient implements ISignerClient, ServiceInfo {
* If we are unable to sign using the specified keys, then an error will be
* returned.
*
* @generated from protobuf rpc: SignOutputRaw(signrpc.SignReq) returns (signrpc.SignResp);
* @generated from protobuf rpc: SignOutputRaw
*/
signOutputRaw(input: SignReq, options?: RpcOptions): UnaryCall<SignReq, SignResp> {
const method = this.methods[0], opt = this._transport.mergeOptions(options);
@ -233,7 +233,7 @@ export class SignerClient implements ISignerClient, ServiceInfo {
* in the TxOut field, the value in that same field, and finally the input
* index.
*
* @generated from protobuf rpc: ComputeInputScript(signrpc.SignReq) returns (signrpc.InputScriptResp);
* @generated from protobuf rpc: ComputeInputScript
*/
computeInputScript(input: SignReq, options?: RpcOptions): UnaryCall<SignReq, InputScriptResp> {
const method = this.methods[1], opt = this._transport.mergeOptions(options);
@ -247,7 +247,7 @@ export class SignerClient implements ISignerClient, ServiceInfo {
* The main difference to SignMessage in the main RPC is that a specific key is
* used to sign the message instead of the node identity private key.
*
* @generated from protobuf rpc: SignMessage(signrpc.SignMessageReq) returns (signrpc.SignMessageResp);
* @generated from protobuf rpc: SignMessage
*/
signMessage(input: SignMessageReq, options?: RpcOptions): UnaryCall<SignMessageReq, SignMessageResp> {
const method = this.methods[2], opt = this._transport.mergeOptions(options);
@ -261,7 +261,7 @@ export class SignerClient implements ISignerClient, ServiceInfo {
* The main difference to VerifyMessage in the main RPC is that the public key
* used to sign the message does not have to be a node known to the network.
*
* @generated from protobuf rpc: VerifyMessage(signrpc.VerifyMessageReq) returns (signrpc.VerifyMessageResp);
* @generated from protobuf rpc: VerifyMessage
*/
verifyMessage(input: VerifyMessageReq, options?: RpcOptions): UnaryCall<VerifyMessageReq, VerifyMessageResp> {
const method = this.methods[3], opt = this._transport.mergeOptions(options);
@ -278,7 +278,7 @@ export class SignerClient implements ISignerClient, ServiceInfo {
* The resulting shared public key is serialized in the compressed format and
* hashed with sha256, resulting in the final key length of 256bit.
*
* @generated from protobuf rpc: DeriveSharedKey(signrpc.SharedKeyRequest) returns (signrpc.SharedKeyResponse);
* @generated from protobuf rpc: DeriveSharedKey
*/
deriveSharedKey(input: SharedKeyRequest, options?: RpcOptions): UnaryCall<SharedKeyRequest, SharedKeyResponse> {
const method = this.methods[4], opt = this._transport.mergeOptions(options);
@ -298,7 +298,7 @@ export class SignerClient implements ISignerClient, ServiceInfo {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2CombineKeys(signrpc.MuSig2CombineKeysRequest) returns (signrpc.MuSig2CombineKeysResponse);
* @generated from protobuf rpc: MuSig2CombineKeys
*/
muSig2CombineKeys(input: MuSig2CombineKeysRequest, options?: RpcOptions): UnaryCall<MuSig2CombineKeysRequest, MuSig2CombineKeysResponse> {
const method = this.methods[5], opt = this._transport.mergeOptions(options);
@ -317,7 +317,7 @@ export class SignerClient implements ISignerClient, ServiceInfo {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2CreateSession(signrpc.MuSig2SessionRequest) returns (signrpc.MuSig2SessionResponse);
* @generated from protobuf rpc: MuSig2CreateSession
*/
muSig2CreateSession(input: MuSig2SessionRequest, options?: RpcOptions): UnaryCall<MuSig2SessionRequest, MuSig2SessionResponse> {
const method = this.methods[6], opt = this._transport.mergeOptions(options);
@ -333,7 +333,7 @@ export class SignerClient implements ISignerClient, ServiceInfo {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2RegisterNonces(signrpc.MuSig2RegisterNoncesRequest) returns (signrpc.MuSig2RegisterNoncesResponse);
* @generated from protobuf rpc: MuSig2RegisterNonces
*/
muSig2RegisterNonces(input: MuSig2RegisterNoncesRequest, options?: RpcOptions): UnaryCall<MuSig2RegisterNoncesRequest, MuSig2RegisterNoncesResponse> {
const method = this.methods[7], opt = this._transport.mergeOptions(options);
@ -352,7 +352,7 @@ export class SignerClient implements ISignerClient, ServiceInfo {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2Sign(signrpc.MuSig2SignRequest) returns (signrpc.MuSig2SignResponse);
* @generated from protobuf rpc: MuSig2Sign
*/
muSig2Sign(input: MuSig2SignRequest, options?: RpcOptions): UnaryCall<MuSig2SignRequest, MuSig2SignResponse> {
const method = this.methods[8], opt = this._transport.mergeOptions(options);
@ -369,7 +369,7 @@ export class SignerClient implements ISignerClient, ServiceInfo {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2CombineSig(signrpc.MuSig2CombineSigRequest) returns (signrpc.MuSig2CombineSigResponse);
* @generated from protobuf rpc: MuSig2CombineSig
*/
muSig2CombineSig(input: MuSig2CombineSigRequest, options?: RpcOptions): UnaryCall<MuSig2CombineSigRequest, MuSig2CombineSigResponse> {
const method = this.methods[9], opt = this._transport.mergeOptions(options);
@ -385,7 +385,7 @@ export class SignerClient implements ISignerClient, ServiceInfo {
* considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
* releases. Backward compatibility is not guaranteed!
*
* @generated from protobuf rpc: MuSig2Cleanup(signrpc.MuSig2CleanupRequest) returns (signrpc.MuSig2CleanupResponse);
* @generated from protobuf rpc: MuSig2Cleanup
*/
muSig2Cleanup(input: MuSig2CleanupRequest, options?: RpcOptions): UnaryCall<MuSig2CleanupRequest, MuSig2CleanupResponse> {
const method = this.methods[10], opt = this._transport.mergeOptions(options);

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
// @generated by protobuf-ts 2.8.1
// @generated by protobuf-ts 2.11.1
// @generated from protobuf file "walletkit.proto" (package "walletrpc", syntax proto3)
// tslint:disable
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
@ -43,7 +43,7 @@ import type { RequiredReserveResponse } from "./walletkit.js";
import type { RequiredReserveRequest } from "./walletkit.js";
import type { ListAccountsResponse } from "./walletkit.js";
import type { ListAccountsRequest } from "./walletkit.js";
import type { Transaction } from "./lightning";
import type { Transaction } from "./lightning.js";
import type { GetTransactionRequest } from "./walletkit.js";
import type { AddrResponse } from "./walletkit.js";
import type { AddrRequest } from "./walletkit.js";
@ -92,7 +92,7 @@ export interface IWalletKitClient {
* default, all utxos are listed. To list only the unconfirmed utxos, set
* the unconfirmed_only to true.
*
* @generated from protobuf rpc: ListUnspent(walletrpc.ListUnspentRequest) returns (walletrpc.ListUnspentResponse);
* @generated from protobuf rpc: ListUnspent
*/
listUnspent(input: ListUnspentRequest, options?: RpcOptions): UnaryCall<ListUnspentRequest, ListUnspentResponse>;
/**
@ -103,7 +103,7 @@ export interface IWalletKitClient {
* successive invocations of this RPC. Outputs can be unlocked before their
* expiration through `ReleaseOutput`.
*
* @generated from protobuf rpc: LeaseOutput(walletrpc.LeaseOutputRequest) returns (walletrpc.LeaseOutputResponse);
* @generated from protobuf rpc: LeaseOutput
*/
leaseOutput(input: LeaseOutputRequest, options?: RpcOptions): UnaryCall<LeaseOutputRequest, LeaseOutputResponse>;
/**
@ -112,14 +112,14 @@ export interface IWalletKitClient {
* selection if it remains unspent. The ID should match the one used to
* originally lock the output.
*
* @generated from protobuf rpc: ReleaseOutput(walletrpc.ReleaseOutputRequest) returns (walletrpc.ReleaseOutputResponse);
* @generated from protobuf rpc: ReleaseOutput
*/
releaseOutput(input: ReleaseOutputRequest, options?: RpcOptions): UnaryCall<ReleaseOutputRequest, ReleaseOutputResponse>;
/**
* lncli: `wallet listleases`
* ListLeases lists all currently locked utxos.
*
* @generated from protobuf rpc: ListLeases(walletrpc.ListLeasesRequest) returns (walletrpc.ListLeasesResponse);
* @generated from protobuf rpc: ListLeases
*/
listLeases(input: ListLeasesRequest, options?: RpcOptions): UnaryCall<ListLeasesRequest, ListLeasesResponse>;
/**
@ -128,7 +128,7 @@ export interface IWalletKitClient {
* (account in BIP43) specified. This method should return the next external
* child within this branch.
*
* @generated from protobuf rpc: DeriveNextKey(walletrpc.KeyReq) returns (signrpc.KeyDescriptor);
* @generated from protobuf rpc: DeriveNextKey
*/
deriveNextKey(input: KeyReq, options?: RpcOptions): UnaryCall<KeyReq, KeyDescriptor>;
/**
@ -136,21 +136,21 @@ export interface IWalletKitClient {
* DeriveKey attempts to derive an arbitrary key specified by the passed
* KeyLocator.
*
* @generated from protobuf rpc: DeriveKey(signrpc.KeyLocator) returns (signrpc.KeyDescriptor);
* @generated from protobuf rpc: DeriveKey
*/
deriveKey(input: KeyLocator, options?: RpcOptions): UnaryCall<KeyLocator, KeyDescriptor>;
/**
*
* NextAddr returns the next unused address within the wallet.
*
* @generated from protobuf rpc: NextAddr(walletrpc.AddrRequest) returns (walletrpc.AddrResponse);
* @generated from protobuf rpc: NextAddr
*/
nextAddr(input: AddrRequest, options?: RpcOptions): UnaryCall<AddrRequest, AddrResponse>;
/**
* lncli: `wallet gettx`
* GetTransaction returns details for a transaction found in the wallet.
*
* @generated from protobuf rpc: GetTransaction(walletrpc.GetTransactionRequest) returns (lnrpc.Transaction);
* @generated from protobuf rpc: GetTransaction
*/
getTransaction(input: GetTransactionRequest, options?: RpcOptions): UnaryCall<GetTransactionRequest, Transaction>;
/**
@ -159,7 +159,7 @@ export interface IWalletKitClient {
* name and key scope filter can be provided to filter through all of the
* wallet accounts and return only those matching.
*
* @generated from protobuf rpc: ListAccounts(walletrpc.ListAccountsRequest) returns (walletrpc.ListAccountsResponse);
* @generated from protobuf rpc: ListAccounts
*/
listAccounts(input: ListAccountsRequest, options?: RpcOptions): UnaryCall<ListAccountsRequest, ListAccountsResponse>;
/**
@ -168,7 +168,7 @@ export interface IWalletKitClient {
* in the wallet in order to fee bump anchor channels if necessary. The value
* scales with the number of public anchor channels but is capped at a maximum.
*
* @generated from protobuf rpc: RequiredReserve(walletrpc.RequiredReserveRequest) returns (walletrpc.RequiredReserveResponse);
* @generated from protobuf rpc: RequiredReserve
*/
requiredReserve(input: RequiredReserveRequest, options?: RpcOptions): UnaryCall<RequiredReserveRequest, RequiredReserveResponse>;
/**
@ -177,7 +177,7 @@ export interface IWalletKitClient {
* account name filter can be provided to filter through all of the
* wallet accounts and return the addresses of only those matching.
*
* @generated from protobuf rpc: ListAddresses(walletrpc.ListAddressesRequest) returns (walletrpc.ListAddressesResponse);
* @generated from protobuf rpc: ListAddresses
*/
listAddresses(input: ListAddressesRequest, options?: RpcOptions): UnaryCall<ListAddressesRequest, ListAddressesResponse>;
/**
@ -195,7 +195,7 @@ export interface IWalletKitClient {
* For P2TR addresses this represents a special case. ECDSA is used to create
* a compact signature which makes the public key of the signature recoverable.
*
* @generated from protobuf rpc: SignMessageWithAddr(walletrpc.SignMessageWithAddrRequest) returns (walletrpc.SignMessageWithAddrResponse);
* @generated from protobuf rpc: SignMessageWithAddr
*/
signMessageWithAddr(input: SignMessageWithAddrRequest, options?: RpcOptions): UnaryCall<SignMessageWithAddrRequest, SignMessageWithAddrResponse>;
/**
@ -220,7 +220,7 @@ export interface IWalletKitClient {
* taproot key. The compact ECDSA signature format was used because there
* are still no known compact signature schemes for schnorr signatures.
*
* @generated from protobuf rpc: VerifyMessageWithAddr(walletrpc.VerifyMessageWithAddrRequest) returns (walletrpc.VerifyMessageWithAddrResponse);
* @generated from protobuf rpc: VerifyMessageWithAddr
*/
verifyMessageWithAddr(input: VerifyMessageWithAddrRequest, options?: RpcOptions): UnaryCall<VerifyMessageWithAddrRequest, VerifyMessageWithAddrResponse>;
/**
@ -249,7 +249,7 @@ export interface IWalletKitClient {
* detected by lnd if they happen after the import. Rescans to detect past
* events will be supported later on.
*
* @generated from protobuf rpc: ImportAccount(walletrpc.ImportAccountRequest) returns (walletrpc.ImportAccountResponse);
* @generated from protobuf rpc: ImportAccount
*/
importAccount(input: ImportAccountRequest, options?: RpcOptions): UnaryCall<ImportAccountRequest, ImportAccountResponse>;
/**
@ -264,7 +264,7 @@ export interface IWalletKitClient {
* they happen after the import. Rescans to detect past events will be
* supported later on.
*
* @generated from protobuf rpc: ImportPublicKey(walletrpc.ImportPublicKeyRequest) returns (walletrpc.ImportPublicKeyResponse);
* @generated from protobuf rpc: ImportPublicKey
*/
importPublicKey(input: ImportPublicKeyRequest, options?: RpcOptions): UnaryCall<ImportPublicKeyRequest, ImportPublicKeyResponse>;
/**
@ -281,7 +281,7 @@ export interface IWalletKitClient {
* NOTE: Taproot keys imported through this RPC currently _cannot_ be used for
* funding PSBTs. Only tracking the balance and UTXOs is currently supported.
*
* @generated from protobuf rpc: ImportTapscript(walletrpc.ImportTapscriptRequest) returns (walletrpc.ImportTapscriptResponse);
* @generated from protobuf rpc: ImportTapscript
*/
importTapscript(input: ImportTapscriptRequest, options?: RpcOptions): UnaryCall<ImportTapscriptRequest, ImportTapscriptResponse>;
/**
@ -291,7 +291,7 @@ export interface IWalletKitClient {
* attempt to re-broadcast the transaction on start up, until it enters the
* chain.
*
* @generated from protobuf rpc: PublishTransaction(walletrpc.Transaction) returns (walletrpc.PublishResponse);
* @generated from protobuf rpc: PublishTransaction
*/
publishTransaction(input: Transaction$, options?: RpcOptions): UnaryCall<Transaction$, PublishResponse>;
/**
@ -299,7 +299,7 @@ export interface IWalletKitClient {
* RemoveTransaction attempts to remove the provided transaction from the
* internal transaction store of the wallet.
*
* @generated from protobuf rpc: RemoveTransaction(walletrpc.GetTransactionRequest) returns (walletrpc.RemoveTransactionResponse);
* @generated from protobuf rpc: RemoveTransaction
*/
removeTransaction(input: GetTransactionRequest, options?: RpcOptions): UnaryCall<GetTransactionRequest, RemoveTransactionResponse>;
/**
@ -308,7 +308,7 @@ export interface IWalletKitClient {
* allows the caller to create a transaction that sends to several outputs at
* once. This is ideal when wanting to batch create a set of transactions.
*
* @generated from protobuf rpc: SendOutputs(walletrpc.SendOutputsRequest) returns (walletrpc.SendOutputsResponse);
* @generated from protobuf rpc: SendOutputs
*/
sendOutputs(input: SendOutputsRequest, options?: RpcOptions): UnaryCall<SendOutputsRequest, SendOutputsResponse>;
/**
@ -317,7 +317,7 @@ export interface IWalletKitClient {
* determine the fee (in sat/kw) to attach to a transaction in order to
* achieve the confirmation target.
*
* @generated from protobuf rpc: EstimateFee(walletrpc.EstimateFeeRequest) returns (walletrpc.EstimateFeeResponse);
* @generated from protobuf rpc: EstimateFee
*/
estimateFee(input: EstimateFeeRequest, options?: RpcOptions): UnaryCall<EstimateFeeRequest, EstimateFeeResponse>;
/**
@ -331,7 +331,7 @@ export interface IWalletKitClient {
* remain supported. This is an advanced API that depends on the internals of
* the UtxoSweeper, so things may change.
*
* @generated from protobuf rpc: PendingSweeps(walletrpc.PendingSweepsRequest) returns (walletrpc.PendingSweepsResponse);
* @generated from protobuf rpc: PendingSweeps
*/
pendingSweeps(input: PendingSweepsRequest, options?: RpcOptions): UnaryCall<PendingSweepsRequest, PendingSweepsResponse>;
/**
@ -365,7 +365,7 @@ export interface IWalletKitClient {
* done by specifying an outpoint within the low fee transaction that is under
* the control of the wallet.
*
* @generated from protobuf rpc: BumpFee(walletrpc.BumpFeeRequest) returns (walletrpc.BumpFeeResponse);
* @generated from protobuf rpc: BumpFee
*/
bumpFee(input: BumpFeeRequest, options?: RpcOptions): UnaryCall<BumpFeeRequest, BumpFeeResponse>;
/**
@ -373,7 +373,7 @@ export interface IWalletKitClient {
* BumpForceCloseFee is an endpoint that allows users to bump the fee of a
* channel force close. This only works for channels with option_anchors.
*
* @generated from protobuf rpc: BumpForceCloseFee(walletrpc.BumpForceCloseFeeRequest) returns (walletrpc.BumpForceCloseFeeResponse);
* @generated from protobuf rpc: BumpForceCloseFee
*/
bumpForceCloseFee(input: BumpForceCloseFeeRequest, options?: RpcOptions): UnaryCall<BumpForceCloseFeeRequest, BumpForceCloseFeeResponse>;
/**
@ -382,7 +382,7 @@ export interface IWalletKitClient {
* Note that these sweeps may not be confirmed yet, as we record sweeps on
* broadcast, not confirmation.
*
* @generated from protobuf rpc: ListSweeps(walletrpc.ListSweepsRequest) returns (walletrpc.ListSweepsResponse);
* @generated from protobuf rpc: ListSweeps
*/
listSweeps(input: ListSweepsRequest, options?: RpcOptions): UnaryCall<ListSweepsRequest, ListSweepsResponse>;
/**
@ -392,7 +392,7 @@ export interface IWalletKitClient {
* overwrite the existing transaction label. Labels must not be empty, and
* cannot exceed 500 characters.
*
* @generated from protobuf rpc: LabelTransaction(walletrpc.LabelTransactionRequest) returns (walletrpc.LabelTransactionResponse);
* @generated from protobuf rpc: LabelTransaction
*/
labelTransaction(input: LabelTransactionRequest, options?: RpcOptions): UnaryCall<LabelTransactionRequest, LabelTransactionResponse>;
/**
@ -426,7 +426,7 @@ export interface IWalletKitClient {
* publishing the transaction) or to unlock/release the locked UTXOs in case of
* an error on the caller's side.
*
* @generated from protobuf rpc: FundPsbt(walletrpc.FundPsbtRequest) returns (walletrpc.FundPsbtResponse);
* @generated from protobuf rpc: FundPsbt
*/
fundPsbt(input: FundPsbtRequest, options?: RpcOptions): UnaryCall<FundPsbtRequest, FundPsbtResponse>;
/**
@ -443,7 +443,7 @@ export interface IWalletKitClient {
* input/output/fee value validation, PSBT finalization). Any input that is
* incomplete will be skipped.
*
* @generated from protobuf rpc: SignPsbt(walletrpc.SignPsbtRequest) returns (walletrpc.SignPsbtResponse);
* @generated from protobuf rpc: SignPsbt
*/
signPsbt(input: SignPsbtRequest, options?: RpcOptions): UnaryCall<SignPsbtRequest, SignPsbtResponse>;
/**
@ -460,7 +460,7 @@ export interface IWalletKitClient {
* caller's responsibility to either publish the transaction on success or
* unlock/release any locked UTXOs in case of an error in this method.
*
* @generated from protobuf rpc: FinalizePsbt(walletrpc.FinalizePsbtRequest) returns (walletrpc.FinalizePsbtResponse);
* @generated from protobuf rpc: FinalizePsbt
*/
finalizePsbt(input: FinalizePsbtRequest, options?: RpcOptions): UnaryCall<FinalizePsbtRequest, FinalizePsbtResponse>;
}
@ -500,7 +500,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* default, all utxos are listed. To list only the unconfirmed utxos, set
* the unconfirmed_only to true.
*
* @generated from protobuf rpc: ListUnspent(walletrpc.ListUnspentRequest) returns (walletrpc.ListUnspentResponse);
* @generated from protobuf rpc: ListUnspent
*/
listUnspent(input: ListUnspentRequest, options?: RpcOptions): UnaryCall<ListUnspentRequest, ListUnspentResponse> {
const method = this.methods[0], opt = this._transport.mergeOptions(options);
@ -514,7 +514,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* successive invocations of this RPC. Outputs can be unlocked before their
* expiration through `ReleaseOutput`.
*
* @generated from protobuf rpc: LeaseOutput(walletrpc.LeaseOutputRequest) returns (walletrpc.LeaseOutputResponse);
* @generated from protobuf rpc: LeaseOutput
*/
leaseOutput(input: LeaseOutputRequest, options?: RpcOptions): UnaryCall<LeaseOutputRequest, LeaseOutputResponse> {
const method = this.methods[1], opt = this._transport.mergeOptions(options);
@ -526,7 +526,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* selection if it remains unspent. The ID should match the one used to
* originally lock the output.
*
* @generated from protobuf rpc: ReleaseOutput(walletrpc.ReleaseOutputRequest) returns (walletrpc.ReleaseOutputResponse);
* @generated from protobuf rpc: ReleaseOutput
*/
releaseOutput(input: ReleaseOutputRequest, options?: RpcOptions): UnaryCall<ReleaseOutputRequest, ReleaseOutputResponse> {
const method = this.methods[2], opt = this._transport.mergeOptions(options);
@ -536,7 +536,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* lncli: `wallet listleases`
* ListLeases lists all currently locked utxos.
*
* @generated from protobuf rpc: ListLeases(walletrpc.ListLeasesRequest) returns (walletrpc.ListLeasesResponse);
* @generated from protobuf rpc: ListLeases
*/
listLeases(input: ListLeasesRequest, options?: RpcOptions): UnaryCall<ListLeasesRequest, ListLeasesResponse> {
const method = this.methods[3], opt = this._transport.mergeOptions(options);
@ -548,7 +548,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* (account in BIP43) specified. This method should return the next external
* child within this branch.
*
* @generated from protobuf rpc: DeriveNextKey(walletrpc.KeyReq) returns (signrpc.KeyDescriptor);
* @generated from protobuf rpc: DeriveNextKey
*/
deriveNextKey(input: KeyReq, options?: RpcOptions): UnaryCall<KeyReq, KeyDescriptor> {
const method = this.methods[4], opt = this._transport.mergeOptions(options);
@ -559,7 +559,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* DeriveKey attempts to derive an arbitrary key specified by the passed
* KeyLocator.
*
* @generated from protobuf rpc: DeriveKey(signrpc.KeyLocator) returns (signrpc.KeyDescriptor);
* @generated from protobuf rpc: DeriveKey
*/
deriveKey(input: KeyLocator, options?: RpcOptions): UnaryCall<KeyLocator, KeyDescriptor> {
const method = this.methods[5], opt = this._transport.mergeOptions(options);
@ -569,7 +569,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
*
* NextAddr returns the next unused address within the wallet.
*
* @generated from protobuf rpc: NextAddr(walletrpc.AddrRequest) returns (walletrpc.AddrResponse);
* @generated from protobuf rpc: NextAddr
*/
nextAddr(input: AddrRequest, options?: RpcOptions): UnaryCall<AddrRequest, AddrResponse> {
const method = this.methods[6], opt = this._transport.mergeOptions(options);
@ -579,7 +579,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* lncli: `wallet gettx`
* GetTransaction returns details for a transaction found in the wallet.
*
* @generated from protobuf rpc: GetTransaction(walletrpc.GetTransactionRequest) returns (lnrpc.Transaction);
* @generated from protobuf rpc: GetTransaction
*/
getTransaction(input: GetTransactionRequest, options?: RpcOptions): UnaryCall<GetTransactionRequest, Transaction> {
const method = this.methods[7], opt = this._transport.mergeOptions(options);
@ -591,7 +591,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* name and key scope filter can be provided to filter through all of the
* wallet accounts and return only those matching.
*
* @generated from protobuf rpc: ListAccounts(walletrpc.ListAccountsRequest) returns (walletrpc.ListAccountsResponse);
* @generated from protobuf rpc: ListAccounts
*/
listAccounts(input: ListAccountsRequest, options?: RpcOptions): UnaryCall<ListAccountsRequest, ListAccountsResponse> {
const method = this.methods[8], opt = this._transport.mergeOptions(options);
@ -603,7 +603,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* in the wallet in order to fee bump anchor channels if necessary. The value
* scales with the number of public anchor channels but is capped at a maximum.
*
* @generated from protobuf rpc: RequiredReserve(walletrpc.RequiredReserveRequest) returns (walletrpc.RequiredReserveResponse);
* @generated from protobuf rpc: RequiredReserve
*/
requiredReserve(input: RequiredReserveRequest, options?: RpcOptions): UnaryCall<RequiredReserveRequest, RequiredReserveResponse> {
const method = this.methods[9], opt = this._transport.mergeOptions(options);
@ -615,7 +615,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* account name filter can be provided to filter through all of the
* wallet accounts and return the addresses of only those matching.
*
* @generated from protobuf rpc: ListAddresses(walletrpc.ListAddressesRequest) returns (walletrpc.ListAddressesResponse);
* @generated from protobuf rpc: ListAddresses
*/
listAddresses(input: ListAddressesRequest, options?: RpcOptions): UnaryCall<ListAddressesRequest, ListAddressesResponse> {
const method = this.methods[10], opt = this._transport.mergeOptions(options);
@ -636,7 +636,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* For P2TR addresses this represents a special case. ECDSA is used to create
* a compact signature which makes the public key of the signature recoverable.
*
* @generated from protobuf rpc: SignMessageWithAddr(walletrpc.SignMessageWithAddrRequest) returns (walletrpc.SignMessageWithAddrResponse);
* @generated from protobuf rpc: SignMessageWithAddr
*/
signMessageWithAddr(input: SignMessageWithAddrRequest, options?: RpcOptions): UnaryCall<SignMessageWithAddrRequest, SignMessageWithAddrResponse> {
const method = this.methods[11], opt = this._transport.mergeOptions(options);
@ -664,7 +664,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* taproot key. The compact ECDSA signature format was used because there
* are still no known compact signature schemes for schnorr signatures.
*
* @generated from protobuf rpc: VerifyMessageWithAddr(walletrpc.VerifyMessageWithAddrRequest) returns (walletrpc.VerifyMessageWithAddrResponse);
* @generated from protobuf rpc: VerifyMessageWithAddr
*/
verifyMessageWithAddr(input: VerifyMessageWithAddrRequest, options?: RpcOptions): UnaryCall<VerifyMessageWithAddrRequest, VerifyMessageWithAddrResponse> {
const method = this.methods[12], opt = this._transport.mergeOptions(options);
@ -696,7 +696,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* detected by lnd if they happen after the import. Rescans to detect past
* events will be supported later on.
*
* @generated from protobuf rpc: ImportAccount(walletrpc.ImportAccountRequest) returns (walletrpc.ImportAccountResponse);
* @generated from protobuf rpc: ImportAccount
*/
importAccount(input: ImportAccountRequest, options?: RpcOptions): UnaryCall<ImportAccountRequest, ImportAccountResponse> {
const method = this.methods[13], opt = this._transport.mergeOptions(options);
@ -714,7 +714,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* they happen after the import. Rescans to detect past events will be
* supported later on.
*
* @generated from protobuf rpc: ImportPublicKey(walletrpc.ImportPublicKeyRequest) returns (walletrpc.ImportPublicKeyResponse);
* @generated from protobuf rpc: ImportPublicKey
*/
importPublicKey(input: ImportPublicKeyRequest, options?: RpcOptions): UnaryCall<ImportPublicKeyRequest, ImportPublicKeyResponse> {
const method = this.methods[14], opt = this._transport.mergeOptions(options);
@ -734,7 +734,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* NOTE: Taproot keys imported through this RPC currently _cannot_ be used for
* funding PSBTs. Only tracking the balance and UTXOs is currently supported.
*
* @generated from protobuf rpc: ImportTapscript(walletrpc.ImportTapscriptRequest) returns (walletrpc.ImportTapscriptResponse);
* @generated from protobuf rpc: ImportTapscript
*/
importTapscript(input: ImportTapscriptRequest, options?: RpcOptions): UnaryCall<ImportTapscriptRequest, ImportTapscriptResponse> {
const method = this.methods[15], opt = this._transport.mergeOptions(options);
@ -747,7 +747,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* attempt to re-broadcast the transaction on start up, until it enters the
* chain.
*
* @generated from protobuf rpc: PublishTransaction(walletrpc.Transaction) returns (walletrpc.PublishResponse);
* @generated from protobuf rpc: PublishTransaction
*/
publishTransaction(input: Transaction$, options?: RpcOptions): UnaryCall<Transaction$, PublishResponse> {
const method = this.methods[16], opt = this._transport.mergeOptions(options);
@ -758,7 +758,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* RemoveTransaction attempts to remove the provided transaction from the
* internal transaction store of the wallet.
*
* @generated from protobuf rpc: RemoveTransaction(walletrpc.GetTransactionRequest) returns (walletrpc.RemoveTransactionResponse);
* @generated from protobuf rpc: RemoveTransaction
*/
removeTransaction(input: GetTransactionRequest, options?: RpcOptions): UnaryCall<GetTransactionRequest, RemoveTransactionResponse> {
const method = this.methods[17], opt = this._transport.mergeOptions(options);
@ -770,7 +770,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* allows the caller to create a transaction that sends to several outputs at
* once. This is ideal when wanting to batch create a set of transactions.
*
* @generated from protobuf rpc: SendOutputs(walletrpc.SendOutputsRequest) returns (walletrpc.SendOutputsResponse);
* @generated from protobuf rpc: SendOutputs
*/
sendOutputs(input: SendOutputsRequest, options?: RpcOptions): UnaryCall<SendOutputsRequest, SendOutputsResponse> {
const method = this.methods[18], opt = this._transport.mergeOptions(options);
@ -782,7 +782,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* determine the fee (in sat/kw) to attach to a transaction in order to
* achieve the confirmation target.
*
* @generated from protobuf rpc: EstimateFee(walletrpc.EstimateFeeRequest) returns (walletrpc.EstimateFeeResponse);
* @generated from protobuf rpc: EstimateFee
*/
estimateFee(input: EstimateFeeRequest, options?: RpcOptions): UnaryCall<EstimateFeeRequest, EstimateFeeResponse> {
const method = this.methods[19], opt = this._transport.mergeOptions(options);
@ -799,7 +799,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* remain supported. This is an advanced API that depends on the internals of
* the UtxoSweeper, so things may change.
*
* @generated from protobuf rpc: PendingSweeps(walletrpc.PendingSweepsRequest) returns (walletrpc.PendingSweepsResponse);
* @generated from protobuf rpc: PendingSweeps
*/
pendingSweeps(input: PendingSweepsRequest, options?: RpcOptions): UnaryCall<PendingSweepsRequest, PendingSweepsResponse> {
const method = this.methods[20], opt = this._transport.mergeOptions(options);
@ -836,7 +836,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* done by specifying an outpoint within the low fee transaction that is under
* the control of the wallet.
*
* @generated from protobuf rpc: BumpFee(walletrpc.BumpFeeRequest) returns (walletrpc.BumpFeeResponse);
* @generated from protobuf rpc: BumpFee
*/
bumpFee(input: BumpFeeRequest, options?: RpcOptions): UnaryCall<BumpFeeRequest, BumpFeeResponse> {
const method = this.methods[21], opt = this._transport.mergeOptions(options);
@ -847,7 +847,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* BumpForceCloseFee is an endpoint that allows users to bump the fee of a
* channel force close. This only works for channels with option_anchors.
*
* @generated from protobuf rpc: BumpForceCloseFee(walletrpc.BumpForceCloseFeeRequest) returns (walletrpc.BumpForceCloseFeeResponse);
* @generated from protobuf rpc: BumpForceCloseFee
*/
bumpForceCloseFee(input: BumpForceCloseFeeRequest, options?: RpcOptions): UnaryCall<BumpForceCloseFeeRequest, BumpForceCloseFeeResponse> {
const method = this.methods[22], opt = this._transport.mergeOptions(options);
@ -859,7 +859,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* Note that these sweeps may not be confirmed yet, as we record sweeps on
* broadcast, not confirmation.
*
* @generated from protobuf rpc: ListSweeps(walletrpc.ListSweepsRequest) returns (walletrpc.ListSweepsResponse);
* @generated from protobuf rpc: ListSweeps
*/
listSweeps(input: ListSweepsRequest, options?: RpcOptions): UnaryCall<ListSweepsRequest, ListSweepsResponse> {
const method = this.methods[23], opt = this._transport.mergeOptions(options);
@ -872,7 +872,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* overwrite the existing transaction label. Labels must not be empty, and
* cannot exceed 500 characters.
*
* @generated from protobuf rpc: LabelTransaction(walletrpc.LabelTransactionRequest) returns (walletrpc.LabelTransactionResponse);
* @generated from protobuf rpc: LabelTransaction
*/
labelTransaction(input: LabelTransactionRequest, options?: RpcOptions): UnaryCall<LabelTransactionRequest, LabelTransactionResponse> {
const method = this.methods[24], opt = this._transport.mergeOptions(options);
@ -909,7 +909,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* publishing the transaction) or to unlock/release the locked UTXOs in case of
* an error on the caller's side.
*
* @generated from protobuf rpc: FundPsbt(walletrpc.FundPsbtRequest) returns (walletrpc.FundPsbtResponse);
* @generated from protobuf rpc: FundPsbt
*/
fundPsbt(input: FundPsbtRequest, options?: RpcOptions): UnaryCall<FundPsbtRequest, FundPsbtResponse> {
const method = this.methods[25], opt = this._transport.mergeOptions(options);
@ -929,7 +929,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* input/output/fee value validation, PSBT finalization). Any input that is
* incomplete will be skipped.
*
* @generated from protobuf rpc: SignPsbt(walletrpc.SignPsbtRequest) returns (walletrpc.SignPsbtResponse);
* @generated from protobuf rpc: SignPsbt
*/
signPsbt(input: SignPsbtRequest, options?: RpcOptions): UnaryCall<SignPsbtRequest, SignPsbtResponse> {
const method = this.methods[26], opt = this._transport.mergeOptions(options);
@ -949,7 +949,7 @@ export class WalletKitClient implements IWalletKitClient, ServiceInfo {
* caller's responsibility to either publish the transaction on success or
* unlock/release any locked UTXOs in case of an error in this method.
*
* @generated from protobuf rpc: FinalizePsbt(walletrpc.FinalizePsbtRequest) returns (walletrpc.FinalizePsbtResponse);
* @generated from protobuf rpc: FinalizePsbt
*/
finalizePsbt(input: FinalizePsbtRequest, options?: RpcOptions): UnaryCall<FinalizePsbtRequest, FinalizePsbtResponse> {
const method = this.methods[27], opt = this._transport.mergeOptions(options);

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
// @generated by protobuf-ts 2.8.1
// @generated by protobuf-ts 2.11.1
// @generated from protobuf file "walletunlocker.proto" (package "lnrpc", syntax proto3)
// tslint:disable
import type { RpcTransport } from "@protobuf-ts/runtime-rpc";
@ -50,7 +50,7 @@ export interface IWalletUnlockerClient {
* method should be used to commit the newly generated seed, and create the
* wallet.
*
* @generated from protobuf rpc: GenSeed(lnrpc.GenSeedRequest) returns (lnrpc.GenSeedResponse);
* @generated from protobuf rpc: GenSeed
*/
genSeed(input: GenSeedRequest, options?: RpcOptions): UnaryCall<GenSeedRequest, GenSeedResponse>;
/**
@ -68,7 +68,7 @@ export interface IWalletUnlockerClient {
* seed, then present it to the user. Once it has been verified by the user,
* the seed can be fed into this RPC in order to commit the new wallet.
*
* @generated from protobuf rpc: InitWallet(lnrpc.InitWalletRequest) returns (lnrpc.InitWalletResponse);
* @generated from protobuf rpc: InitWallet
*/
initWallet(input: InitWalletRequest, options?: RpcOptions): UnaryCall<InitWalletRequest, InitWalletResponse>;
/**
@ -76,7 +76,7 @@ export interface IWalletUnlockerClient {
* UnlockWallet is used at startup of lnd to provide a password to unlock
* the wallet database.
*
* @generated from protobuf rpc: UnlockWallet(lnrpc.UnlockWalletRequest) returns (lnrpc.UnlockWalletResponse);
* @generated from protobuf rpc: UnlockWallet
*/
unlockWallet(input: UnlockWalletRequest, options?: RpcOptions): UnaryCall<UnlockWalletRequest, UnlockWalletResponse>;
/**
@ -84,7 +84,7 @@ export interface IWalletUnlockerClient {
* ChangePassword changes the password of the encrypted wallet. This will
* automatically unlock the wallet database if successful.
*
* @generated from protobuf rpc: ChangePassword(lnrpc.ChangePasswordRequest) returns (lnrpc.ChangePasswordResponse);
* @generated from protobuf rpc: ChangePassword
*/
changePassword(input: ChangePasswordRequest, options?: RpcOptions): UnaryCall<ChangePasswordRequest, ChangePasswordResponse>;
}
@ -128,7 +128,7 @@ export class WalletUnlockerClient implements IWalletUnlockerClient, ServiceInfo
* method should be used to commit the newly generated seed, and create the
* wallet.
*
* @generated from protobuf rpc: GenSeed(lnrpc.GenSeedRequest) returns (lnrpc.GenSeedResponse);
* @generated from protobuf rpc: GenSeed
*/
genSeed(input: GenSeedRequest, options?: RpcOptions): UnaryCall<GenSeedRequest, GenSeedResponse> {
const method = this.methods[0], opt = this._transport.mergeOptions(options);
@ -149,7 +149,7 @@ export class WalletUnlockerClient implements IWalletUnlockerClient, ServiceInfo
* seed, then present it to the user. Once it has been verified by the user,
* the seed can be fed into this RPC in order to commit the new wallet.
*
* @generated from protobuf rpc: InitWallet(lnrpc.InitWalletRequest) returns (lnrpc.InitWalletResponse);
* @generated from protobuf rpc: InitWallet
*/
initWallet(input: InitWalletRequest, options?: RpcOptions): UnaryCall<InitWalletRequest, InitWalletResponse> {
const method = this.methods[1], opt = this._transport.mergeOptions(options);
@ -160,7 +160,7 @@ export class WalletUnlockerClient implements IWalletUnlockerClient, ServiceInfo
* UnlockWallet is used at startup of lnd to provide a password to unlock
* the wallet database.
*
* @generated from protobuf rpc: UnlockWallet(lnrpc.UnlockWalletRequest) returns (lnrpc.UnlockWalletResponse);
* @generated from protobuf rpc: UnlockWallet
*/
unlockWallet(input: UnlockWalletRequest, options?: RpcOptions): UnaryCall<UnlockWalletRequest, UnlockWalletResponse> {
const method = this.methods[2], opt = this._transport.mergeOptions(options);
@ -171,7 +171,7 @@ export class WalletUnlockerClient implements IWalletUnlockerClient, ServiceInfo
* ChangePassword changes the password of the encrypted wallet. This will
* automatically unlock the wallet database if successful.
*
* @generated from protobuf rpc: ChangePassword(lnrpc.ChangePasswordRequest) returns (lnrpc.ChangePasswordResponse);
* @generated from protobuf rpc: ChangePassword
*/
changePassword(input: ChangePasswordRequest, options?: RpcOptions): UnaryCall<ChangePasswordRequest, ChangePasswordResponse> {
const method = this.methods[3], opt = this._transport.mergeOptions(options);

View file

@ -1,4 +1,4 @@
// @generated by protobuf-ts 2.8.1
// @generated by protobuf-ts 2.11.1
// @generated from protobuf file "walletunlocker.proto" (package "lnrpc", syntax proto3)
// tslint:disable
import { ServiceType } from "@protobuf-ts/runtime-rpc";
@ -10,7 +10,6 @@ import type { IBinaryReader } from "@protobuf-ts/runtime";
import { UnknownFieldHandler } from "@protobuf-ts/runtime";
import type { PartialMessage } from "@protobuf-ts/runtime";
import { reflectionMergePartial } from "@protobuf-ts/runtime";
import { MESSAGE_TYPE } from "@protobuf-ts/runtime";
import { MessageType } from "@protobuf-ts/runtime";
import { ChanBackupSnapshot } from "./lightning.js";
/**
@ -23,7 +22,7 @@ export interface GenSeedRequest {
* to encrypt the generated aezeed cipher seed. When using REST, this field
* must be encoded as base64.
*
* @generated from protobuf field: bytes aezeed_passphrase = 1;
* @generated from protobuf field: bytes aezeed_passphrase = 1
*/
aezeedPassphrase: Uint8Array;
/**
@ -32,7 +31,7 @@ export interface GenSeedRequest {
* specified, then a fresh set of randomness will be used to create the seed.
* When using REST, this field must be encoded as base64.
*
* @generated from protobuf field: bytes seed_entropy = 2;
* @generated from protobuf field: bytes seed_entropy = 2
*/
seedEntropy: Uint8Array;
}
@ -48,7 +47,7 @@ export interface GenSeedResponse {
* Otherwise, then the daemon will attempt to recover the wallet state linked
* to this cipher seed.
*
* @generated from protobuf field: repeated string cipher_seed_mnemonic = 1;
* @generated from protobuf field: repeated string cipher_seed_mnemonic = 1
*/
cipherSeedMnemonic: string[];
/**
@ -56,7 +55,7 @@ export interface GenSeedResponse {
* enciphered_seed are the raw aezeed cipher seed bytes. This is the raw
* cipher text before run through our mnemonic encoding scheme.
*
* @generated from protobuf field: bytes enciphered_seed = 2;
* @generated from protobuf field: bytes enciphered_seed = 2
*/
encipheredSeed: Uint8Array;
}
@ -71,7 +70,7 @@ export interface InitWalletRequest {
* password is required to unlock the daemon. When using REST, this field
* must be encoded as base64.
*
* @generated from protobuf field: bytes wallet_password = 1;
* @generated from protobuf field: bytes wallet_password = 1
*/
walletPassword: Uint8Array;
/**
@ -80,7 +79,7 @@ export interface InitWalletRequest {
* cipher seed obtained by the user. This may have been generated by the
* GenSeed method, or be an existing seed.
*
* @generated from protobuf field: repeated string cipher_seed_mnemonic = 2;
* @generated from protobuf field: repeated string cipher_seed_mnemonic = 2
*/
cipherSeedMnemonic: string[];
/**
@ -89,7 +88,7 @@ export interface InitWalletRequest {
* to encrypt the generated aezeed cipher seed. When using REST, this field
* must be encoded as base64.
*
* @generated from protobuf field: bytes aezeed_passphrase = 3;
* @generated from protobuf field: bytes aezeed_passphrase = 3
*/
aezeedPassphrase: Uint8Array;
/**
@ -100,7 +99,7 @@ export interface InitWalletRequest {
* window of zero indicates that no addresses should be recovered, such after
* the first initialization of the wallet.
*
* @generated from protobuf field: int32 recovery_window = 4;
* @generated from protobuf field: int32 recovery_window = 4
*/
recoveryWindow: number;
/**
@ -112,7 +111,7 @@ export interface InitWalletRequest {
* funds, lnd begin to carry out the data loss recovery protocol in order to
* recover the funds in each channel from a remote force closed transaction.
*
* @generated from protobuf field: lnrpc.ChanBackupSnapshot channel_backups = 5;
* @generated from protobuf field: lnrpc.ChanBackupSnapshot channel_backups = 5
*/
channelBackups?: ChanBackupSnapshot;
/**
@ -122,7 +121,7 @@ export interface InitWalletRequest {
* admin macaroon returned in the response MUST be stored by the caller of the
* RPC as otherwise all access to the daemon will be lost!
*
* @generated from protobuf field: bool stateless_init = 6;
* @generated from protobuf field: bool stateless_init = 6
*/
statelessInit: boolean;
/**
@ -140,7 +139,7 @@ export interface InitWalletRequest {
* extended_master_key_birthday_timestamp or a "safe" default value will be
* used.
*
* @generated from protobuf field: string extended_master_key = 7;
* @generated from protobuf field: string extended_master_key = 7
*/
extendedMasterKey: string;
/**
@ -153,7 +152,7 @@ export interface InitWalletRequest {
* which case lnd will start scanning from the first SegWit block (481824 on
* mainnet).
*
* @generated from protobuf field: uint64 extended_master_key_birthday_timestamp = 8;
* @generated from protobuf field: uint64 extended_master_key_birthday_timestamp = 8
*/
extendedMasterKeyBirthdayTimestamp: bigint;
/**
@ -164,7 +163,7 @@ export interface InitWalletRequest {
* any of the keys and _needs_ to be run with a remote signer that has the
* corresponding private keys and can serve signing RPC requests.
*
* @generated from protobuf field: lnrpc.WatchOnly watch_only = 9;
* @generated from protobuf field: lnrpc.WatchOnly watch_only = 9
*/
watchOnly?: WatchOnly;
/**
@ -173,7 +172,7 @@ export interface InitWalletRequest {
* provided when initializing the wallet rather than letting lnd generate one
* on its own.
*
* @generated from protobuf field: bytes macaroon_root_key = 10;
* @generated from protobuf field: bytes macaroon_root_key = 10
*/
macaroonRootKey: Uint8Array;
}
@ -189,7 +188,7 @@ export interface InitWalletResponse {
* caller. Otherwise a copy of this macaroon is also persisted on disk by the
* daemon, together with other macaroon files.
*
* @generated from protobuf field: bytes admin_macaroon = 1;
* @generated from protobuf field: bytes admin_macaroon = 1
*/
adminMacaroon: Uint8Array;
}
@ -205,7 +204,7 @@ export interface WatchOnly {
* should be left at its default value of 0 in which case lnd will start
* scanning from the first SegWit block (481824 on mainnet).
*
* @generated from protobuf field: uint64 master_key_birthday_timestamp = 1;
* @generated from protobuf field: uint64 master_key_birthday_timestamp = 1
*/
masterKeyBirthdayTimestamp: bigint;
/**
@ -215,7 +214,7 @@ export interface WatchOnly {
* required by some hardware wallets for proper identification and signing. The
* bytes must be in big-endian order.
*
* @generated from protobuf field: bytes master_key_fingerprint = 2;
* @generated from protobuf field: bytes master_key_fingerprint = 2
*/
masterKeyFingerprint: Uint8Array;
/**
@ -226,7 +225,7 @@ export interface WatchOnly {
* scope (m/1017'/<coin_type>'/<account>'), where account is the key family as
* defined in `keychain/derivation.go` (currently indices 0 to 9).
*
* @generated from protobuf field: repeated lnrpc.WatchOnlyAccount accounts = 3;
* @generated from protobuf field: repeated lnrpc.WatchOnlyAccount accounts = 3
*/
accounts: WatchOnlyAccount[];
}
@ -239,7 +238,7 @@ export interface WatchOnlyAccount {
* Purpose is the first number in the derivation path, must be either 49, 84
* or 1017.
*
* @generated from protobuf field: uint32 purpose = 1;
* @generated from protobuf field: uint32 purpose = 1
*/
purpose: number;
/**
@ -248,7 +247,7 @@ export interface WatchOnlyAccount {
* for purposes 49 and 84. It only needs to be set to 1 for purpose 1017 on
* testnet or regtest.
*
* @generated from protobuf field: uint32 coin_type = 2;
* @generated from protobuf field: uint32 coin_type = 2
*/
coinType: number;
/**
@ -259,14 +258,14 @@ export interface WatchOnlyAccount {
* one account for each of the key families defined in `keychain/derivation.go`
* (currently indices 0 to 9)
*
* @generated from protobuf field: uint32 account = 3;
* @generated from protobuf field: uint32 account = 3
*/
account: number;
/**
*
* The extended public key at depth 3 for the given account.
*
* @generated from protobuf field: string xpub = 4;
* @generated from protobuf field: string xpub = 4
*/
xpub: string;
}
@ -280,7 +279,7 @@ export interface UnlockWalletRequest {
* will be required to decrypt on-disk material that the daemon requires to
* function properly. When using REST, this field must be encoded as base64.
*
* @generated from protobuf field: bytes wallet_password = 1;
* @generated from protobuf field: bytes wallet_password = 1
*/
walletPassword: Uint8Array;
/**
@ -291,7 +290,7 @@ export interface UnlockWalletRequest {
* window of zero indicates that no addresses should be recovered, such after
* the first initialization of the wallet.
*
* @generated from protobuf field: int32 recovery_window = 2;
* @generated from protobuf field: int32 recovery_window = 2
*/
recoveryWindow: number;
/**
@ -303,7 +302,7 @@ export interface UnlockWalletRequest {
* funds, lnd begin to carry out the data loss recovery protocol in order to
* recover the funds in each channel from a remote force closed transaction.
*
* @generated from protobuf field: lnrpc.ChanBackupSnapshot channel_backups = 3;
* @generated from protobuf field: lnrpc.ChanBackupSnapshot channel_backups = 3
*/
channelBackups?: ChanBackupSnapshot;
/**
@ -311,7 +310,7 @@ export interface UnlockWalletRequest {
* stateless_init is an optional argument instructing the daemon NOT to create
* any *.macaroon files in its file system.
*
* @generated from protobuf field: bool stateless_init = 4;
* @generated from protobuf field: bool stateless_init = 4
*/
statelessInit: boolean;
}
@ -329,7 +328,7 @@ export interface ChangePasswordRequest {
* current_password should be the current valid passphrase used to unlock the
* daemon. When using REST, this field must be encoded as base64.
*
* @generated from protobuf field: bytes current_password = 1;
* @generated from protobuf field: bytes current_password = 1
*/
currentPassword: Uint8Array;
/**
@ -337,7 +336,7 @@ export interface ChangePasswordRequest {
* new_password should be the new passphrase that will be needed to unlock the
* daemon. When using REST, this field must be encoded as base64.
*
* @generated from protobuf field: bytes new_password = 2;
* @generated from protobuf field: bytes new_password = 2
*/
newPassword: Uint8Array;
/**
@ -347,7 +346,7 @@ export interface ChangePasswordRequest {
* admin macaroon returned in the response MUST be stored by the caller of the
* RPC as otherwise all access to the daemon will be lost!
*
* @generated from protobuf field: bool stateless_init = 3;
* @generated from protobuf field: bool stateless_init = 3
*/
statelessInit: boolean;
/**
@ -356,7 +355,7 @@ export interface ChangePasswordRequest {
* rotate the macaroon root key when set to true. This will invalidate all
* previously generated macaroons.
*
* @generated from protobuf field: bool new_macaroon_root_key = 4;
* @generated from protobuf field: bool new_macaroon_root_key = 4
*/
newMacaroonRootKey: boolean;
}
@ -373,7 +372,7 @@ export interface ChangePasswordResponse {
* safely by the caller. Otherwise a copy of this macaroon is also persisted on
* disk by the daemon, together with other macaroon files.
*
* @generated from protobuf field: bytes admin_macaroon = 1;
* @generated from protobuf field: bytes admin_macaroon = 1
*/
adminMacaroon: Uint8Array;
}
@ -386,8 +385,9 @@ class GenSeedRequest$Type extends MessageType<GenSeedRequest> {
]);
}
create(value?: PartialMessage<GenSeedRequest>): GenSeedRequest {
const message = { aezeedPassphrase: new Uint8Array(0), seedEntropy: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.aezeedPassphrase = new Uint8Array(0);
message.seedEntropy = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<GenSeedRequest>(this, message, value);
return message;
@ -440,8 +440,9 @@ class GenSeedResponse$Type extends MessageType<GenSeedResponse> {
]);
}
create(value?: PartialMessage<GenSeedResponse>): GenSeedResponse {
const message = { cipherSeedMnemonic: [], encipheredSeed: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.cipherSeedMnemonic = [];
message.encipheredSeed = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<GenSeedResponse>(this, message, value);
return message;
@ -502,8 +503,15 @@ class InitWalletRequest$Type extends MessageType<InitWalletRequest> {
]);
}
create(value?: PartialMessage<InitWalletRequest>): InitWalletRequest {
const message = { walletPassword: new Uint8Array(0), cipherSeedMnemonic: [], aezeedPassphrase: new Uint8Array(0), recoveryWindow: 0, statelessInit: false, extendedMasterKey: "", extendedMasterKeyBirthdayTimestamp: 0n, macaroonRootKey: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.walletPassword = new Uint8Array(0);
message.cipherSeedMnemonic = [];
message.aezeedPassphrase = new Uint8Array(0);
message.recoveryWindow = 0;
message.statelessInit = false;
message.extendedMasterKey = "";
message.extendedMasterKeyBirthdayTimestamp = 0n;
message.macaroonRootKey = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<InitWalletRequest>(this, message, value);
return message;
@ -603,8 +611,8 @@ class InitWalletResponse$Type extends MessageType<InitWalletResponse> {
]);
}
create(value?: PartialMessage<InitWalletResponse>): InitWalletResponse {
const message = { adminMacaroon: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.adminMacaroon = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<InitWalletResponse>(this, message, value);
return message;
@ -648,12 +656,14 @@ class WatchOnly$Type extends MessageType<WatchOnly> {
super("lnrpc.WatchOnly", [
{ no: 1, name: "master_key_birthday_timestamp", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
{ no: 2, name: "master_key_fingerprint", kind: "scalar", T: 12 /*ScalarType.BYTES*/ },
{ no: 3, name: "accounts", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => WatchOnlyAccount }
{ no: 3, name: "accounts", kind: "message", repeat: 2 /*RepeatType.UNPACKED*/, T: () => WatchOnlyAccount }
]);
}
create(value?: PartialMessage<WatchOnly>): WatchOnly {
const message = { masterKeyBirthdayTimestamp: 0n, masterKeyFingerprint: new Uint8Array(0), accounts: [] };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.masterKeyBirthdayTimestamp = 0n;
message.masterKeyFingerprint = new Uint8Array(0);
message.accounts = [];
if (value !== undefined)
reflectionMergePartial<WatchOnly>(this, message, value);
return message;
@ -714,8 +724,11 @@ class WatchOnlyAccount$Type extends MessageType<WatchOnlyAccount> {
]);
}
create(value?: PartialMessage<WatchOnlyAccount>): WatchOnlyAccount {
const message = { purpose: 0, coinType: 0, account: 0, xpub: "" };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.purpose = 0;
message.coinType = 0;
message.account = 0;
message.xpub = "";
if (value !== undefined)
reflectionMergePartial<WatchOnlyAccount>(this, message, value);
return message;
@ -782,8 +795,10 @@ class UnlockWalletRequest$Type extends MessageType<UnlockWalletRequest> {
]);
}
create(value?: PartialMessage<UnlockWalletRequest>): UnlockWalletRequest {
const message = { walletPassword: new Uint8Array(0), recoveryWindow: 0, statelessInit: false };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.walletPassword = new Uint8Array(0);
message.recoveryWindow = 0;
message.statelessInit = false;
if (value !== undefined)
reflectionMergePartial<UnlockWalletRequest>(this, message, value);
return message;
@ -845,14 +860,26 @@ class UnlockWalletResponse$Type extends MessageType<UnlockWalletResponse> {
super("lnrpc.UnlockWalletResponse", []);
}
create(value?: PartialMessage<UnlockWalletResponse>): UnlockWalletResponse {
const message = {};
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
if (value !== undefined)
reflectionMergePartial<UnlockWalletResponse>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UnlockWalletResponse): UnlockWalletResponse {
return target ?? this.create();
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: UnlockWalletResponse, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
let u = options.writeUnknownFields;
@ -876,8 +903,11 @@ class ChangePasswordRequest$Type extends MessageType<ChangePasswordRequest> {
]);
}
create(value?: PartialMessage<ChangePasswordRequest>): ChangePasswordRequest {
const message = { currentPassword: new Uint8Array(0), newPassword: new Uint8Array(0), statelessInit: false, newMacaroonRootKey: false };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.currentPassword = new Uint8Array(0);
message.newPassword = new Uint8Array(0);
message.statelessInit = false;
message.newMacaroonRootKey = false;
if (value !== undefined)
reflectionMergePartial<ChangePasswordRequest>(this, message, value);
return message;
@ -941,8 +971,8 @@ class ChangePasswordResponse$Type extends MessageType<ChangePasswordResponse> {
]);
}
create(value?: PartialMessage<ChangePasswordResponse>): ChangePasswordResponse {
const message = { adminMacaroon: new Uint8Array(0) };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
const message = globalThis.Object.create((this.messagePrototype!));
message.adminMacaroon = new Uint8Array(0);
if (value !== undefined)
reflectionMergePartial<ChangePasswordResponse>(this, message, value);
return message;

View file

@ -1,11 +1,29 @@
syntax = "proto3";
import "lightning.proto";
package invoicesrpc;
import "lightning.proto";
option go_package = "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc";
/*
* Comments in this file will be directly parsed into the API
* Documentation as descriptions of the associated method, message, or field.
* These descriptions should go right above the definition of the object, and
* can be in either block or // comment format.
*
* An RPC method can be matched to an lncli command by placing a line in the
* beginning of the description in exactly the following format:
* lncli: `methodname`
*
* Failure to specify the exact name of the command will cause documentation
* generation to fail.
*
* More information on how exactly the gRPC documentation is generated from
* this proto file can be found here:
* https://github.com/lightninglabs/lightning-api
*/
// Invoices is a service that can be used to create, accept, settle and cancel
// invoices.
service Invoices {
@ -17,30 +35,39 @@ service Invoices {
rpc SubscribeSingleInvoice (SubscribeSingleInvoiceRequest)
returns (stream lnrpc.Invoice);
/*
/* lncli: `cancelinvoice`
CancelInvoice cancels a currently open invoice. If the invoice is already
canceled, this call will succeed. If the invoice is already settled, it will
fail.
*/
rpc CancelInvoice (CancelInvoiceMsg) returns (CancelInvoiceResp);
/*
/* lncli: `addholdinvoice`
AddHoldInvoice creates a hold invoice. It ties the invoice to the hash
supplied in the request.
*/
rpc AddHoldInvoice (AddHoldInvoiceRequest) returns (AddHoldInvoiceResp);
/*
/* lncli: `settleinvoice`
SettleInvoice settles an accepted invoice. If the invoice is already
settled, this call will succeed.
*/
rpc SettleInvoice (SettleInvoiceMsg) returns (SettleInvoiceResp);
/*
LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced
LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced
using either its payment hash, payment address, or set ID.
*/
rpc LookupInvoiceV2 (LookupInvoiceMsg) returns (lnrpc.Invoice);
/*
HtlcModifier is a bidirectional streaming RPC that allows a client to
intercept and modify the HTLCs that attempt to settle the given invoice. The
server will send HTLCs of invoices to the client and the client can modify
some aspects of the HTLC in order to pass the invoice acceptance tests.
*/
rpc HtlcModifier (stream HtlcModifyResponse)
returns (stream HtlcModifyRequest);
}
message CancelInvoiceMsg {
@ -84,7 +111,7 @@ message AddHoldInvoiceRequest {
*/
bytes description_hash = 4;
// Payment request expiry time in seconds. Default is 3600 (1 hour).
// Payment request expiry time in seconds. Default is 86400 (24 hours).
int64 expiry = 5;
// Fallback on-chain address.
@ -120,8 +147,9 @@ message AddHoldInvoiceResp {
uint64 add_index = 2;
/*
The payment address of the generated invoice. This value should be used
in all payments for this invoice as we require it for end to end
The payment address of the generated invoice. This is also called
the payment secret in specifications (e.g. BOLT 11). This value should
be used in all payments for this invoice as we require it for end to end
security.
*/
bytes payment_addr = 3;
@ -173,3 +201,51 @@ message LookupInvoiceMsg {
LookupModifier lookup_modifier = 4;
}
// CircuitKey is a unique identifier for an HTLC.
message CircuitKey {
// The id of the channel that the is part of this circuit.
uint64 chan_id = 1;
// The index of the incoming htlc in the incoming channel.
uint64 htlc_id = 2;
}
message HtlcModifyRequest {
// The invoice the intercepted HTLC is attempting to settle. The HTLCs in
// the invoice are only HTLCs that have already been accepted or settled,
// not including the current intercepted HTLC.
lnrpc.Invoice invoice = 1;
// The unique identifier of the HTLC of this intercepted HTLC.
CircuitKey exit_htlc_circuit_key = 2;
// The amount in milli-satoshi that the exit HTLC is attempting to pay.
uint64 exit_htlc_amt = 3;
// The absolute expiry height of the exit HTLC.
uint32 exit_htlc_expiry = 4;
// The current block height.
uint32 current_height = 5;
// The wire message custom records of the exit HTLC.
map<uint64, bytes> exit_htlc_wire_custom_records = 6;
}
message HtlcModifyResponse {
// The circuit key of the HTLC that the client wants to modify.
CircuitKey circuit_key = 1;
// The modified amount in milli-satoshi that the exit HTLC is paying. This
// value can be different from the actual on-chain HTLC amount, in case the
// HTLC carries other valuable items, as can be the case with custom channel
// types.
optional uint64 amt_paid = 2;
// This flag indicates whether the HTLCs associated with the invoices should
// be cancelled. The interceptor client may set this field if some
// unexpected behavior is encountered. Setting this will ignore the amt_paid
// field.
bool cancel_set = 3;
}

View file

@ -274,12 +274,14 @@ service Lightning {
}
/*
SendPaymentSync is the synchronous non-streaming version of SendPayment.
This RPC is intended to be consumed by clients of the REST proxy.
Additionally, this RPC expects the destination's public key and the payment
hash (if any) to be encoded as hex strings.
Deprecated, use routerrpc.SendPaymentV2. SendPaymentSync is the synchronous
non-streaming version of SendPayment. This RPC is intended to be consumed by
clients of the REST proxy. Additionally, this RPC expects the destination's
public key and the payment hash (if any) to be encoded as hex strings.
*/
rpc SendPaymentSync (SendRequest) returns (SendResponse);
rpc SendPaymentSync (SendRequest) returns (SendResponse) {
option deprecated = true;
}
/* lncli: `sendtoroute`
Deprecated, use routerrpc.SendToRouteV2. SendToRoute is a bi-directional
@ -293,10 +295,13 @@ service Lightning {
}
/*
SendToRouteSync is a synchronous version of SendToRoute. It Will block
until the payment either fails or succeeds.
Deprecated, use routerrpc.SendToRouteV2. SendToRouteSync is a synchronous
version of SendToRoute. It Will block until the payment either fails or
succeeds.
*/
rpc SendToRouteSync (SendToRouteRequest) returns (SendResponse);
rpc SendToRouteSync (SendToRouteRequest) returns (SendResponse) {
option deprecated = true;
}
/* lncli: `addinvoice`
AddInvoice attempts to add a new invoice to the invoice database. Any
@ -643,6 +648,8 @@ message SendCustomMessageRequest {
}
message SendCustomMessageResponse {
// The status of the send operation.
string status = 1;
}
message Utxo {
@ -755,11 +762,35 @@ message GetTransactionsRequest {
// An optional filter to only include transactions relevant to an account.
string account = 3;
/*
The index of a transaction that will be used in a query to determine which
transaction should be returned in the response.
*/
uint32 index_offset = 4;
/*
The maximal number of transactions returned in the response to this query.
This value should be set to 0 to return all transactions.
*/
uint32 max_transactions = 5;
}
message TransactionDetails {
// The list of transactions relevant to the wallet.
repeated Transaction transactions = 1;
/*
The index of the last item in the set of returned transactions. This can be
used to seek further, pagination style.
*/
uint64 last_index = 2;
/*
The index of the last item in the set of returned transactions. This can be
used to seek backwards, pagination style.
*/
uint64 first_index = 3;
}
message FeeLimit {
@ -1317,6 +1348,8 @@ message ConnectPeerRequest {
uint64 timeout = 3;
}
message ConnectPeerResponse {
// The status of the connect operation.
string status = 1;
}
message DisconnectPeerRequest {
@ -1324,6 +1357,8 @@ message DisconnectPeerRequest {
string pub_key = 1;
}
message DisconnectPeerResponse {
// The status of the disconnect operation.
string status = 1;
}
message HTLC {
@ -1346,6 +1381,13 @@ message HTLC {
// Index identifying the htlc on the forwarding channel.
uint64 forwarding_htlc_index = 7;
/*
Whether the HTLC is locked in. An HTLC is considered locked in when the
remote party has sent us the `revoke_and_ack` to irrevocably commit this
HTLC.
*/
bool locked_in = 8;
}
enum CommitmentType {
@ -1388,8 +1430,14 @@ enum CommitmentType {
A channel that uses musig2 for the funding output, and the new tapscript
features where relevant.
*/
// TODO(roasbeef): need script enforce mirror type for the above as well?
SIMPLE_TAPROOT = 5;
/*
Identical to the SIMPLE_TAPROOT channel type, but with extra functionality.
This channel type also commits to additional meta data in the tapscript
leaves for the scripts in a channel.
*/
SIMPLE_TAPROOT_OVERLAY = 6;
}
message ChannelConstraints {
@ -1592,6 +1640,11 @@ message Channel {
the channel's operation.
*/
string memo = 36;
/*
Custom channel data that might be populated in custom channels.
*/
bytes custom_channel_data = 37;
}
message ListChannelsRequest {
@ -1705,6 +1758,10 @@ message ChannelCloseSummary {
// The confirmed SCID for a zero-conf channel.
uint64 zero_conf_confirmed_scid = 15 [jstype = JS_STRING];
// The TLV encoded custom channel data records for this output, which might
// be set for custom channels.
bytes custom_channel_data = 16;
}
enum ResolutionType {
@ -1955,8 +2012,8 @@ message GetInfoResponse {
bool synced_to_graph = 18;
/*
Whether the current node is connected to testnet. This field is
deprecated and the network field should be used instead
Whether the current node is connected to testnet or testnet4. This field is
deprecated and the network field should be used instead.
*/
bool testnet = 10 [deprecated = true];
@ -2028,10 +2085,38 @@ message ChannelOpenUpdate {
ChannelPoint channel_point = 1;
}
message CloseOutput {
// The amount in satoshi of this close output. This amount is the final
// commitment balance of the channel and the actual amount paid out on chain
// might be smaller due to subtracted fees.
int64 amount_sat = 1;
// The pkScript of the close output.
bytes pk_script = 2;
// Whether this output is for the local or remote node.
bool is_local = 3;
// The TLV encoded custom channel data records for this output, which might
// be set for custom channels.
bytes custom_channel_data = 4;
}
message ChannelCloseUpdate {
bytes closing_txid = 1;
bool success = 2;
// The local channel close output. If the local channel balance was dust to
// begin with, this output will not be set.
CloseOutput local_close_output = 3;
// The remote channel close output. If the remote channel balance was dust
// to begin with, this output will not be set.
CloseOutput remote_close_output = 4;
// Any additional outputs that might be added for custom channel types.
repeated CloseOutput additional_outputs = 5;
}
message CloseChannelRequest {
@ -2072,9 +2157,13 @@ message CloseChannelRequest {
// NOTE: This field is only respected if we're the initiator of the channel.
uint64 max_fee_per_vbyte = 7;
// If true, then the rpc call will not block while it awaits a closing txid.
// Consequently this RPC call will not return a closing txid if this value
// is set.
// If true, then the rpc call will not block while it awaits a closing txid
// to be broadcasted to the mempool. To obtain the closing tx one has to
// listen to the stream for the particular updates. Moreover if a coop close
// is specified and this flag is set to true the coop closing flow will be
// initiated even if HTLCs are active on the channel. The channel will wait
// until all HTLCs are resolved and then start the coop closing process. The
// channel will be disabled in the meantime and will disallow any new HTLCs.
bool no_wait = 8;
}
@ -2089,9 +2178,15 @@ message CloseStatusUpdate {
message PendingUpdate {
bytes txid = 1;
uint32 output_index = 2;
int64 fee_per_vbyte = 3;
bool local_close_tx = 4;
}
message InstantUpdate {
// The number of pending HTLCs that are currently active on the channel.
// These HTLCs need to be resolved before the channel can be closed
// cooperatively.
int32 num_pending_htlcs = 1;
}
message ReadyForPsbtFunding {
@ -2709,6 +2804,11 @@ message PendingChannelsResponse {
impacts the channel's operation.
*/
string memo = 13;
/*
Custom channel data that might be populated in custom channels.
*/
bytes custom_channel_data = 34;
}
message PendingOpenChannel {
@ -2880,6 +2980,7 @@ message ChannelEventUpdate {
ChannelPoint inactive_channel = 4;
PendingUpdate pending_open_channel = 6;
ChannelPoint fully_resolved_channel = 7;
ChannelPoint channel_funding_timeout = 8;
}
enum UpdateType {
@ -2889,6 +2990,7 @@ message ChannelEventUpdate {
INACTIVE_CHANNEL = 3;
PENDING_OPEN_CHANNEL = 4;
FULLY_RESOLVED_CHANNEL = 5;
CHANNEL_FUNDING_TIMEOUT = 6;
}
UpdateType type = 5;
@ -2968,6 +3070,12 @@ message ChannelBalanceResponse {
// Sum of channels pending remote balances.
Amount pending_open_remote_balance = 8;
/*
Custom channel data that might be populated if there are custom channels
present.
*/
bytes custom_channel_data = 9;
}
message QueryRoutesRequest {
@ -3293,6 +3401,20 @@ message Route {
The total amount in millisatoshis.
*/
int64 total_amt_msat = 6;
/*
The actual on-chain amount that was sent out to the first hop. This value is
only different from the total_amt_msat field if this is a custom channel
payment and the value transported in the HTLC is different from the BTC
amount in the HTLC. If this value is zero, then this is an old payment that
didn't have this value yet and can be ignored.
*/
int64 first_hop_amount_msat = 7;
/*
Custom channel data that might be populated in custom channels.
*/
bytes custom_channel_data = 8;
}
message NodeInfoRequest {
@ -3478,6 +3600,8 @@ message NetworkInfo {
message StopRequest {
}
message StopResponse {
// The status of the stop operation.
string status = 1;
}
message GraphTopologySubscription {
@ -3922,6 +4046,11 @@ message InvoiceHTLC {
// Details relevant to AMP HTLCs, only populated if this is an AMP HTLC.
AMP amp = 11;
/*
Custom channel data that might be populated in custom channels.
*/
bytes custom_channel_data = 12;
}
// Details specific to AMP HTLCs.
@ -4162,6 +4291,12 @@ message Payment {
uint64 payment_index = 15;
PaymentFailureReason failure_reason = 16;
/*
The custom TLV records that were sent to the first hop as part of the HTLC
wire message for this payment.
*/
map<uint64, bytes> first_hop_custom_records = 17;
}
message HTLCAttempt {
@ -4291,9 +4426,13 @@ message DeleteAllPaymentsRequest {
}
message DeletePaymentResponse {
// The status of the delete operation.
string status = 1;
}
message DeleteAllPaymentsResponse {
// The status of the delete operation.
string status = 1;
}
message AbandonChannelRequest {
@ -4310,6 +4449,8 @@ message AbandonChannelRequest {
}
message AbandonChannelResponse {
// The status of the abandon operation.
string status = 1;
}
message DebugLevelRequest {
@ -4469,6 +4610,15 @@ message PolicyUpdateRequest {
// Optional inbound fee. If unset, the previously set value will be
// retained [EXPERIMENTAL].
InboundFee inbound_fee = 10;
// Under unknown circumstances a channel can exist with a missing edge in
// the graph database. This can cause an 'edge not found' error when calling
// `getchaninfo` and/or cause the default channel policy to be used during
// forwards. Setting this flag will recreate the edge if not found, allowing
// updating this channel policy and fixing the missing edge problem for this
// channel permanently. For fields not set in this command, the default
// policy will be created.
bool create_missing_edge = 11;
}
enum UpdateFailure {
@ -4562,6 +4712,14 @@ message ForwardingEvent {
// The peer alias of the outgoing channel.
string peer_alias_out = 13;
// The ID of the incoming HTLC in the payment circuit. This field is
// optional and is unset for forwarding events happened before v0.20.
optional uint64 incoming_htlc_id = 14;
// The ID of the outgoing HTLC in the payment circuit. This field is
// optional and may be unset for legacy forwarding events.
optional uint64 outgoing_htlc_id = 15;
// TODO(roasbeef): add settlement latency?
// * use FPE on the chan id?
// * also list failures?
@ -4649,12 +4807,15 @@ message RestoreChanBackupRequest {
}
}
message RestoreBackupResponse {
// The number of channels successfully restored.
uint32 num_restored = 1;
}
message ChannelBackupSubscription {
}
message VerifyChanBackupResponse {
repeated string chan_points = 1;
}
message MacaroonPermission {
@ -4977,6 +5138,22 @@ message RPCMiddlewareRequest {
intercept message.
*/
uint64 msg_id = 7;
/*
The metadata pairs that were sent along with the original gRPC request via
the golang context.Context using explicit [gRPC
metadata](https://grpc.io/docs/guides/metadata/). Context values are not
propagated via gRPC and so we send any pairs along explicitly here so that
the interceptor can access them.
*/
map<string, MetadataValues> metadata_pairs = 9;
}
message MetadataValues {
/*
The set of metadata values that correspond to the metadata key.
*/
repeated string values = 1;
}
message StreamAuth {

View file

@ -1,22 +1,43 @@
syntax = "proto3";
import "lightning.proto";
package routerrpc;
import "lightning.proto";
option go_package = "github.com/lightningnetwork/lnd/lnrpc/routerrpc";
/*
* Comments in this file will be directly parsed into the API
* Documentation as descriptions of the associated method, message, or field.
* These descriptions should go right above the definition of the object, and
* can be in either block or // comment format.
*
* An RPC method can be matched to an lncli command by placing a line in the
* beginning of the description in exactly the following format:
* lncli: `methodname`
*
* Failure to specify the exact name of the command will cause documentation
* generation to fail.
*
* More information on how exactly the gRPC documentation is generated from
* this proto file can be found here:
* https://github.com/lightninglabs/lightning-api
*/
// Router is a service that offers advanced interaction with the router
// subsystem of the daemon.
service Router {
/*
SendPaymentV2 attempts to route a payment described by the passed
PaymentRequest to the final destination. The call returns a stream of
payment updates.
payment updates. When using this RPC, make sure to set a fee limit, as the
default routing fee limit is 0 sats. Without a non-zero fee limit only
routes without fees will be attempted which often fails with
FAILURE_REASON_NO_ROUTE.
*/
rpc SendPaymentV2 (SendPaymentRequest) returns (stream lnrpc.Payment);
/*
/* lncli: `trackpayment`
TrackPaymentV2 returns an update stream for the payment identified by the
payment hash.
*/
@ -57,21 +78,21 @@ service Router {
*/
rpc SendToRouteV2 (SendToRouteRequest) returns (lnrpc.HTLCAttempt);
/*
/* lncli: `resetmc`
ResetMissionControl clears all mission control state and starts with a clean
slate.
*/
rpc ResetMissionControl (ResetMissionControlRequest)
returns (ResetMissionControlResponse);
/*
/* lncli: `querymc`
QueryMissionControl exposes the internal mission control state to callers.
It is a development feature.
*/
rpc QueryMissionControl (QueryMissionControlRequest)
returns (QueryMissionControlResponse);
/*
/* lncli: `importmc`
XImportMissionControl is an experimental API that imports the state provided
to the internal mission control's state, using all results which are more
recent than our existing values. These values will only be imported
@ -80,30 +101,36 @@ service Router {
rpc XImportMissionControl (XImportMissionControlRequest)
returns (XImportMissionControlResponse);
/*
/* lncli: `getmccfg`
GetMissionControlConfig returns mission control's current config.
*/
rpc GetMissionControlConfig (GetMissionControlConfigRequest)
returns (GetMissionControlConfigResponse);
/*
/* lncli: `setmccfg`
SetMissionControlConfig will set mission control's config, if the config
provided is valid.
*/
rpc SetMissionControlConfig (SetMissionControlConfigRequest)
returns (SetMissionControlConfigResponse);
/*
QueryProbability returns the current success probability estimate for a
given node pair and amount.
/* lncli: `queryprob`
Deprecated. QueryProbability returns the current success probability
estimate for a given node pair and amount. The call returns a zero success
probability if no channel is available or if the amount violates min/max
HTLC constraints.
*/
rpc QueryProbability (QueryProbabilityRequest)
returns (QueryProbabilityResponse);
/*
/* lncli: `buildroute`
BuildRoute builds a fully specified route based on a list of hop public
keys. It retrieves the relevant channel policies from the graph in order to
calculate the correct fees and time locks.
Note that LND will use its default final_cltv_delta if no value is supplied.
Make sure to add the correct final_cltv_delta depending on the invoice
restriction. Moreover the caller has to make sure to provide the
payment_addr if the route is paying an invoice which signaled it.
*/
rpc BuildRoute (BuildRouteRequest) returns (BuildRouteResponse);
@ -141,7 +168,7 @@ service Router {
rpc HtlcInterceptor (stream ForwardHtlcInterceptResponse)
returns (stream ForwardHtlcInterceptRequest);
/*
/* lncli: `updatechanstatus`
UpdateChanStatus attempts to manually set the state of a channel
(enabled, disabled, or auto). A manual "disable" request will cause the
channel to stay disabled until a subsequent manual request of either
@ -149,6 +176,25 @@ service Router {
*/
rpc UpdateChanStatus (UpdateChanStatusRequest)
returns (UpdateChanStatusResponse);
/*
XAddLocalChanAliases is an experimental API that creates a set of new
channel SCID alias mappings. The final total set of aliases in the manager
after the add operation is returned. This is only a locally stored alias,
and will not be communicated to the channel peer via any message. Therefore,
routing over such an alias will only work if the peer also calls this same
RPC on their end. If an alias already exists, an error is returned
*/
rpc XAddLocalChanAliases (AddAliasesRequest) returns (AddAliasesResponse);
/*
XDeleteLocalChanAliases is an experimental API that deletes a set of alias
mappings. The final total set of aliases in the manager after the delete
operation is returned. The deletion will not be communicated to the channel
peer via any message.
*/
rpc XDeleteLocalChanAliases (DeleteAliasesRequest)
returns (DeleteAliasesResponse);
}
message SendPaymentRequest {
@ -162,13 +208,6 @@ message SendPaymentRequest {
*/
int64 amt = 2;
/*
Number of millisatoshis to send.
The fields amt and amt_msat are mutually exclusive.
*/
int64 amt_msat = 12;
// The hash to use within the payment's HTLC
bytes payment_hash = 3;
@ -178,9 +217,6 @@ message SendPaymentRequest {
*/
int32 final_cltv_delta = 4;
// An optional payment addr to be included within the last hop of the route.
bytes payment_addr = 20;
/*
A bare-bones invoice for a payment within the Lightning Network. With the
details of the invoice, the sender has all the data necessary to send a
@ -191,10 +227,11 @@ message SendPaymentRequest {
string payment_request = 5;
/*
An upper limit on the amount of time we should spend when attempting to
fulfill the payment. This is expressed in seconds. If we cannot make a
successful payment within this time frame, an error will be returned.
This field must be non-zero.
An optional limit, expressed in seconds, on the time to wait before
attempting the first HTLC. Once HTLCs are in flight, the payment will
not be aborted until the HTLCs are either settled or failed. If the field
is not set or is explicitly set to zero, the default value of 60 seconds
will be applied.
*/
int32 timeout_seconds = 6;
@ -208,17 +245,6 @@ message SendPaymentRequest {
*/
int64 fee_limit_sat = 7;
/*
The maximum number of millisatoshis that will be paid as a fee of the
payment. If this field is left to the default value of 0, only zero-fee
routes will be considered. This usually means single hop routes connecting
directly to the destination. To send the payment without a fee limit, use
max int here.
The fields fee_limit_sat and fee_limit_msat are mutually exclusive.
*/
int64 fee_limit_msat = 13;
/*
Deprecated, use outgoing_chan_ids. The channel id of the channel that must
be taken to the first hop. If zero, any channel may be used (unless
@ -227,19 +253,8 @@ message SendPaymentRequest {
uint64 outgoing_chan_id = 8 [jstype = JS_STRING, deprecated = true];
/*
The channel ids of the channels are allowed for the first hop. If empty,
any channel may be used.
*/
repeated uint64 outgoing_chan_ids = 19;
/*
The pubkey of the last hop of the route. If empty, any hop may be used.
*/
bytes last_hop_pubkey = 14;
/*
An optional maximum total time lock for the route. This should not exceed
lnd's `--max-cltv-expiry` setting. If zero, then the value of
An optional maximum total time lock for the route. This should not
exceed lnd's `--max-cltv-expiry` setting. If zero, then the value of
`--max-cltv-expiry` is enforced.
*/
int32 cltv_limit = 9;
@ -258,6 +273,29 @@ message SendPaymentRequest {
*/
map<uint64, bytes> dest_custom_records = 11;
/*
Number of millisatoshis to send.
The fields amt and amt_msat are mutually exclusive.
*/
int64 amt_msat = 12;
/*
The maximum number of millisatoshis that will be paid as a fee of the
payment. If this field is left to the default value of 0, only zero-fee
routes will be considered. This usually means single hop routes connecting
directly to the destination. To send the payment without a fee limit, use
max int here.
The fields fee_limit_sat and fee_limit_msat are mutually exclusive.
*/
int64 fee_limit_msat = 13;
/*
The pubkey of the last hop of the route. If empty, any hop may be used.
*/
bytes last_hop_pubkey = 14;
// If set, circular payments to self are permitted.
bool allow_self_payment = 15;
@ -282,6 +320,18 @@ message SendPaymentRequest {
*/
bool no_inflight_updates = 18;
/*
The channel ids of the channels are allowed for the first hop. If empty,
any channel may be used.
*/
repeated uint64 outgoing_chan_ids = 19;
/*
An optional payment addr to be included within the last hop of the route.
This is also called payment secret in specifications (e.g. BOLT 11).
*/
bytes payment_addr = 20;
/*
The largest payment split that should be attempted when making a payment if
splitting is necessary. Setting this value will effectively cause lnd to
@ -300,6 +350,24 @@ message SendPaymentRequest {
only, to 1 to optimize for reliability only or a value inbetween for a mix.
*/
double time_pref = 23;
/*
If set, the payment loop can be interrupted by manually canceling the
payment context, even before the payment timeout is reached. Note that the
payment may still succeed after cancellation, as in-flight attempts can
still settle afterwards. Canceling will only prevent further attempts from
being sent.
*/
bool cancelable = 24;
/*
An optional field that can be used to pass an arbitrary set of TLV records
to the first hop peer of this payment. This can be used to pass application
specific data during the payment attempt. Record types are required to be in
the custom range >= 65536. When using REST, the values must be encoded as
base64.
*/
map<uint64, bytes> first_hop_custom_records = 25;
}
message TrackPaymentRequest {
@ -323,14 +391,39 @@ message TrackPaymentsRequest {
message RouteFeeRequest {
/*
The destination once wishes to obtain a routing fee quote to.
The destination one wishes to obtain a routing fee quote to. If set, this
parameter requires the amt_sat parameter also to be set. This parameter
combination triggers a graph based routing fee estimation as opposed to a
payment probe based estimate in case a payment request is provided. The
graph based estimation is an algorithm that is executed on the in memory
graph. Hence its runtime is significantly shorter than a payment probe
estimation that sends out actual payments to the network.
*/
bytes dest = 1;
/*
The amount one wishes to send to the target destination.
The amount one wishes to send to the target destination. It is only to be
used in combination with the dest parameter.
*/
int64 amt_sat = 2;
/*
A payment request of the target node that the route fee request is intended
for. Its parameters are input to probe payments that estimate routing fees.
The timeout parameter can be specified to set a maximum time on the probing
attempt. Cannot be used in combination with dest and amt_sat.
*/
string payment_request = 3;
/*
A user preference of how long a probe payment should maximally be allowed to
take, denoted in seconds. The probing payment loop is aborted if this
timeout is reached. Note that the probing process itself can take longer
than the timeout if the HTLC becomes delayed or stuck. Canceling the context
of this call will not cancel the payment loop, the duration is only
controlled by the timeout parameter.
*/
uint32 timeout = 4;
}
message RouteFeeResponse {
@ -346,6 +439,12 @@ message RouteFeeResponse {
value.
*/
int64 time_lock_delay = 2;
/*
An indication whether a probing payment succeeded or whether and why it
failed. FAILURE_REASON_NONE indicates success.
*/
lnrpc.PaymentFailureReason failure_reason = 5;
}
message SendToRouteRequest {
@ -362,6 +461,15 @@ message SendToRouteRequest {
routes, incorrect payment details, or insufficient funds.
*/
bool skip_temp_err = 3;
/*
An optional field that can be used to pass an arbitrary set of TLV records
to the first hop peer of this payment. This can be used to pass application
specific data during the payment attempt. Record types are required to be in
the custom range >= 65536. When using REST, the values must be encoded as
base64.
*/
map<uint64, bytes> first_hop_custom_records = 4;
}
message SendToRouteResponse {
@ -465,6 +573,93 @@ message SetMissionControlConfigResponse {
}
message MissionControlConfig {
/*
Deprecated, use AprioriParameters. The amount of time mission control will
take to restore a penalized node or channel back to 50% success probability,
expressed in seconds. Setting this value to a higher value will penalize
failures for longer, making mission control less likely to route through
nodes and channels that we have previously recorded failures for.
*/
uint64 half_life_seconds = 1 [deprecated = true];
/*
Deprecated, use AprioriParameters. The probability of success mission
control should assign to hop in a route where it has no other information
available. Higher values will make mission control more willing to try hops
that we have no information about, lower values will discourage trying these
hops.
*/
float hop_probability = 2 [deprecated = true];
/*
Deprecated, use AprioriParameters. The importance that mission control
should place on historical results, expressed as a value in [0;1]. Setting
this value to 1 will ignore all historical payments and just use the hop
probability to assess the probability of success for each hop. A zero value
ignores hop probability completely and relies entirely on historical
results, unless none are available.
*/
float weight = 3 [deprecated = true];
/*
The maximum number of payment results that mission control will store.
*/
uint32 maximum_payment_results = 4;
/*
The minimum time that must have passed since the previously recorded failure
before we raise the failure amount.
*/
uint64 minimum_failure_relax_interval = 5;
enum ProbabilityModel {
APRIORI = 0;
BIMODAL = 1;
}
/*
ProbabilityModel defines which probability estimator should be used in
pathfinding. Note that the bimodal estimator is experimental.
*/
ProbabilityModel model = 6;
/*
EstimatorConfig is populated dependent on the estimator type.
*/
oneof EstimatorConfig {
AprioriParameters apriori = 7;
BimodalParameters bimodal = 8;
}
}
message BimodalParameters {
/*
NodeWeight defines how strongly other previous forwardings on channels of a
router should be taken into account when computing a channel's probability
to route. The allowed values are in the range [0, 1], where a value of 0
means that only direct information about a channel is taken into account.
*/
double node_weight = 1;
/*
ScaleMsat describes the scale over which channels statistically have some
liquidity left. The value determines how quickly the bimodal distribution
drops off from the edges of a channel. A larger value (compared to typical
channel capacities) means that the drop off is slow and that channel
balances are distributed more uniformly. A small value leads to the
assumption of very unbalanced channels.
*/
uint64 scale_msat = 2;
/*
DecayTime describes the information decay of knowledge about previous
successes and failures in channels. The smaller the decay time, the quicker
we forget about past forwardings.
*/
uint64 decay_time = 3;
}
message AprioriParameters {
/*
The amount of time mission control will take to restore a penalized node
or channel back to 50% success probability, expressed in seconds. Setting
@ -480,7 +675,7 @@ message MissionControlConfig {
control more willing to try hops that we have no information about, lower
values will discourage trying these hops.
*/
float hop_probability = 2;
double hop_probability = 2;
/*
The importance that mission control should place on historical results,
@ -490,18 +685,15 @@ message MissionControlConfig {
completely and relies entirely on historical results, unless none are
available.
*/
float weight = 3;
double weight = 3;
/*
The maximum number of payment results that mission control will store.
The fraction of a channel's capacity that we consider to have liquidity. For
amounts that come close to or exceed the fraction, an additional penalty is
applied. A value of 1.0 disables the capacity factor. Allowed values are in
[0.75, 1.0].
*/
uint32 maximum_payment_results = 4;
/*
The minimum time that must have passed since the previously recorded failure
before we raise the failure amount.
*/
uint64 minimum_failure_relax_interval = 5;
double capacity_fraction = 4;
}
message QueryProbabilityRequest {
@ -548,8 +740,20 @@ message BuildRouteRequest {
*/
repeated bytes hop_pubkeys = 4;
// An optional payment addr to be included within the last hop of the route.
/*
An optional payment addr to be included within the last hop of the route.
This is also called payment secret in specifications (e.g. BOLT 11).
*/
bytes payment_addr = 5;
/*
An optional field that can be used to pass an arbitrary set of TLV records
to the first hop peer of this payment. This can be used to pass application
specific data during the payment attempt. Record types are required to be in
the custom range >= 65536. When using REST, the values must be encoded as
base64.
*/
map<uint64, bytes> first_hop_custom_records = 6;
}
message BuildRouteResponse {
@ -806,12 +1010,17 @@ message ForwardHtlcInterceptRequest {
// The block height at which this htlc will be auto-failed to prevent the
// channel from force-closing.
int32 auto_fail_height = 10;
// The custom records of the peer's incoming p2p wire message.
map<uint64, bytes> in_wire_custom_records = 11;
}
/**
ForwardHtlcInterceptResponse enables the caller to resolve a previously hold
forward. The caller can choose either to:
- `Resume`: Execute the default behavior (usually forward).
- `ResumeModified`: Execute the default behavior (usually forward) with HTLC
field modifications.
- `Reject`: Fail the htlc backwards.
- `Settle`: Settle this htlc with a given preimage.
*/
@ -842,12 +1051,40 @@ message ForwardHtlcInterceptResponse {
// For backwards-compatibility reasons, TEMPORARY_CHANNEL_FAILURE is the
// default value for this field.
lnrpc.Failure.FailureCode failure_code = 5;
// The amount that was set on the p2p wire message of the incoming HTLC.
// This field is ignored if the action is not RESUME_MODIFIED or the amount
// is zero.
uint64 in_amount_msat = 6;
// The amount to set on the p2p wire message of the resumed HTLC. This field
// is ignored if the action is not RESUME_MODIFIED or the amount is zero.
uint64 out_amount_msat = 7;
// Any custom records that should be set on the p2p wire message message of
// the resumed HTLC. This field is ignored if the action is not
// RESUME_MODIFIED.
//
// This map will merge with the existing set of custom records (if any),
// replacing any conflicting types. Note that there currently is no support
// for deleting existing custom records (they can only be replaced).
map<uint64, bytes> out_wire_custom_records = 8;
}
enum ResolveHoldForwardAction {
// SETTLE is an action that is used to settle an HTLC instead of forwarding
// it.
SETTLE = 0;
// FAIL is an action that is used to fail an HTLC backwards.
FAIL = 1;
// RESUME is an action that is used to resume a forward HTLC.
RESUME = 2;
// RESUME_MODIFIED is an action that is used to resume a hold forward HTLC
// with modifications specified during interception.
RESUME_MODIFIED = 3;
}
message UpdateChanStatusRequest {
@ -864,3 +1101,19 @@ enum ChanStatusAction {
message UpdateChanStatusResponse {
}
message AddAliasesRequest {
repeated lnrpc.AliasMap alias_maps = 1;
}
message AddAliasesResponse {
repeated lnrpc.AliasMap alias_maps = 1;
}
message DeleteAliasesRequest {
repeated lnrpc.AliasMap alias_maps = 1;
}
message DeleteAliasesResponse {
repeated lnrpc.AliasMap alias_maps = 1;
}

View file

@ -1172,6 +1172,12 @@ message PendingSweep {
The deadline height used for this output when perform fee bumping.
*/
uint32 deadline_height = 14;
/*
The block height which the input's locktime will expire at. Zero if the
input has no locktime.
*/
uint32 maturity_height = 15;
}
message PendingSweepsRequest {
@ -1188,9 +1194,8 @@ message BumpFeeRequest {
// The input we're attempting to bump the fee of.
lnrpc.OutPoint outpoint = 1;
// Optional. The deadline in number of blocks that the input should be spent
// within. When not set, for new inputs, the default value (1008) is used;
// for existing inputs, their current values will be retained.
// Optional. The conf target the underlying fee estimator will use to
// estimate the starting fee rate for the fee function.
uint32 target_conf = 2;
/*
@ -1217,7 +1222,7 @@ message BumpFeeRequest {
/*
Optional. Whether this input will be swept immediately. When set to true,
the sweeper will sweep this input without waiting for the next batch.
the sweeper will sweep this input without waiting for the next block.
*/
bool immediate = 6;
@ -1230,6 +1235,12 @@ message BumpFeeRequest {
retained.
*/
uint64 budget = 7;
// Optional. The deadline delta in number of blocks that the output
// should be spent within. This translates internally to the width of the
// fee function that the sweeper will use to bump the fee rate. When the
// deadline is reached, ALL the budget will be spent as fees.
uint32 deadline_delta = 8;
}
message BumpFeeResponse {
@ -1243,7 +1254,8 @@ message BumpForceCloseFeeRequest {
lnrpc.ChannelPoint chan_point = 1;
// Optional. The deadline delta in number of blocks that the anchor output
// should be spent within to bump the closing transaction.
// should be spent within to bump the closing transaction. When the
// deadline is reached, ALL the budget will be spent as fees
uint32 deadline_delta = 2;
/*
@ -1270,6 +1282,10 @@ message BumpForceCloseFeeRequest {
transaction of the force closed channel otherwise the fee bumping will fail.
*/
uint64 budget = 5;
// Optional. The conf target the underlying fee estimator will use to
// estimate the starting fee rate for the fee function.
uint32 target_conf = 6;
}
message BumpForceCloseFeeResponse {
@ -1426,6 +1442,16 @@ message FundPsbtRequest {
// The max fee to total output amount ratio that this psbt should adhere to.
double max_fee_ratio = 12;
// The custom lock ID to use for the inputs in the funded PSBT. The value
// if set must be exactly 32 bytes long. If empty, the default lock ID will
// be used.
bytes custom_lock_id = 13;
// If set, then the inputs in the funded PSBT will be locked for the
// specified duration. The lock duration is specified in seconds. If not
// set, the default lock duration will be used.
uint64 lock_expiration_seconds = 14;
}
message FundPsbtResponse {
/*

View file

@ -926,6 +926,10 @@ message InvoiceSwapOperation {
optional UserOperation operation_payment = 2;
optional string failure_reason = 3;
optional int64 completed_at_unix = 6;
optional string refund_address = 7;
optional int64 refund_at_unix = 8;
optional string refund_tx_id = 9;
}
message InvoiceSwapsList {

731
src/extensions/README.md Normal file
View file

@ -0,0 +1,731 @@
# Lightning.Pub Extension System
A modular extension system that allows third-party functionality to be added to Lightning.Pub without modifying core code.
## Table of Contents
- [Overview](#overview)
- [Architecture](#architecture)
- [Creating an Extension](#creating-an-extension)
- [Extension Lifecycle](#extension-lifecycle)
- [ExtensionContext API](#extensioncontext-api)
- [Database Isolation](#database-isolation)
- [RPC Methods](#rpc-methods)
- [HTTP Routes](#http-routes)
- [Event Handling](#event-handling)
- [Configuration](#configuration)
- [Examples](#examples)
---
## Overview
The extension system provides:
- **Modularity**: Extensions are self-contained modules with their own code and data
- **Isolation**: Each extension gets its own SQLite database
- **Integration**: Extensions can register RPC methods, handle events, and interact with Lightning.Pub's payment and Nostr systems
- **Lifecycle Management**: Automatic discovery, loading, and graceful shutdown
### Built-in Extensions
| Extension | Description |
|-----------|-------------|
| `marketplace` | NIP-15 Nostr marketplace for selling products via Lightning |
| `withdraw` | LNURL-withdraw (LUD-03) for vouchers, faucets, and gifts |
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Lightning.Pub │
├─────────────────────────────────────────────────────────────────┤
│ Extension Loader │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Extension A │ │ Extension B │ │ Extension C │ ... │
│ │ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ │ │
│ │ │Context│ │ │ │Context│ │ │ │Context│ │ │
│ │ └───────┘ │ │ └───────┘ │ │ └───────┘ │ │
│ │ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ │ │
│ │ │ DB │ │ │ │ DB │ │ │ │ DB │ │ │
│ │ └───────┘ │ │ └───────┘ │ │ └───────┘ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Payment Manager │ Nostr Transport │ Application Manager │
└─────────────────────────────────────────────────────────────────┘
```
### Key Components
| Component | File | Description |
|-----------|------|-------------|
| `ExtensionLoader` | `loader.ts` | Discovers, loads, and manages extensions |
| `ExtensionContext` | `context.ts` | Bridge between extensions and Lightning.Pub |
| `ExtensionDatabase` | `database.ts` | Isolated SQLite database per extension |
---
## Creating an Extension
### Directory Structure
```
src/extensions/
└── my-extension/
├── index.ts # Main entry point (required)
├── types.ts # TypeScript interfaces
├── migrations.ts # Database migrations
└── managers/ # Business logic
└── myManager.ts
```
### Minimal Extension
```typescript
// src/extensions/my-extension/index.ts
import { Extension, ExtensionInfo, ExtensionContext, ExtensionDatabase } from '../types.js'
export default class MyExtension implements Extension {
readonly info: ExtensionInfo = {
id: 'my-extension', // Must match directory name
name: 'My Extension',
version: '1.0.0',
description: 'Does something useful',
author: 'Your Name',
minPubVersion: '1.0.0' // Minimum Lightning.Pub version
}
async initialize(ctx: ExtensionContext, db: ExtensionDatabase): Promise<void> {
// Run migrations
await db.execute(`
CREATE TABLE IF NOT EXISTS my_table (
id TEXT PRIMARY KEY,
data TEXT
)
`)
// Register RPC methods
ctx.registerMethod('my-extension.doSomething', async (req, appId) => {
return { result: 'done' }
})
ctx.log('info', 'Extension initialized')
}
async shutdown(): Promise<void> {
// Cleanup resources
}
}
```
### Extension Interface
```typescript
interface Extension {
// Required: Extension metadata
readonly info: ExtensionInfo
// Required: Called once when extension is loaded
initialize(ctx: ExtensionContext, db: ExtensionDatabase): Promise<void>
// Optional: Called when Lightning.Pub shuts down
shutdown?(): Promise<void>
// Optional: Health check for monitoring
healthCheck?(): Promise<boolean>
}
interface ExtensionInfo {
id: string // Unique identifier (lowercase, no spaces)
name: string // Display name
version: string // Semver version
description: string // Short description
author: string // Author name
minPubVersion?: string // Minimum Lightning.Pub version
dependencies?: string[] // Other extension IDs required
}
```
---
## Extension Lifecycle
```
┌──────────────┐
│ Discover │ Scan extensions directory for index.ts files
└──────┬───────┘
┌──────────────┐
│ Load │ Import module, instantiate class
└──────┬───────┘
┌──────────────┐
│ Initialize │ Create database, call initialize()
└──────┬───────┘
┌──────────────┐
│ Ready │ Extension is active, handling requests
└──────┬───────┘
▼ (on shutdown)
┌──────────────┐
│ Shutdown │ Call shutdown(), close database
└──────────────┘
```
### States
| State | Description |
|-------|-------------|
| `loading` | Extension is being loaded |
| `ready` | Extension is active and healthy |
| `error` | Initialization failed |
| `stopped` | Extension has been shut down |
---
## ExtensionContext API
The `ExtensionContext` is passed to your extension during initialization. It provides access to Lightning.Pub functionality.
### Application Management
```typescript
// Get information about an application
const app = await ctx.getApplication(applicationId)
// Returns: { id, name, nostr_public, balance_sats } | null
```
### Payment Operations
```typescript
// Create a Lightning invoice
const invoice = await ctx.createInvoice(amountSats, {
memo: 'Payment for service',
expiry: 3600, // seconds
metadata: { order_id: '123' } // Returned in payment callback
})
// Returns: { id, paymentRequest, paymentHash, expiry }
// Pay a Lightning invoice
const result = await ctx.payInvoice(applicationId, bolt11Invoice, maxFeeSats)
// Returns: { paymentHash, feeSats }
```
### Nostr Operations
```typescript
// Send encrypted DM (NIP-44)
const eventId = await ctx.sendEncryptedDM(applicationId, recipientPubkey, content)
// Publish a Nostr event (signed by application's key)
const eventId = await ctx.publishNostrEvent({
kind: 30017,
pubkey: appPubkey,
created_at: Math.floor(Date.now() / 1000),
tags: [['d', 'identifier']],
content: JSON.stringify(data)
})
```
### RPC Method Registration
```typescript
// Register a method that can be called via RPC
ctx.registerMethod('my-extension.methodName', async (request, applicationId, userPubkey?) => {
// request: The RPC request payload
// applicationId: The calling application's ID
// userPubkey: The user's Nostr pubkey (if authenticated)
return { result: 'success' }
})
```
### Event Subscriptions
```typescript
// Subscribe to payment received events
ctx.onPaymentReceived(async (payment) => {
// payment: { invoiceId, paymentHash, amountSats, metadata }
if (payment.metadata?.extension === 'my-extension') {
// Handle payment for this extension
}
})
// Subscribe to incoming Nostr events
ctx.onNostrEvent(async (event, applicationId) => {
// event: { id, pubkey, kind, tags, content, created_at }
// applicationId: The application this event is for
if (event.kind === 4) { // DM
// Handle incoming message
}
})
```
### Logging
```typescript
ctx.log('debug', 'Detailed debugging info')
ctx.log('info', 'Normal operation info')
ctx.log('warn', 'Warning message')
ctx.log('error', 'Error occurred', errorObject)
```
---
## Database Isolation
Each extension gets its own SQLite database file at:
```
{databaseDir}/{extension-id}.db
```
### Database Interface
```typescript
interface ExtensionDatabase {
// Execute write queries (INSERT, UPDATE, DELETE, CREATE)
execute(sql: string, params?: any[]): Promise<{ changes?: number; lastId?: number }>
// Execute read queries (SELECT)
query<T>(sql: string, params?: any[]): Promise<T[]>
// Run multiple statements in a transaction
transaction<T>(fn: () => Promise<T>): Promise<T>
}
```
### Migration Pattern
```typescript
// migrations.ts
export interface Migration {
version: number
name: string
up: (db: ExtensionDatabase) => Promise<void>
}
export const migrations: Migration[] = [
{
version: 1,
name: 'create_initial_tables',
up: async (db) => {
await db.execute(`
CREATE TABLE items (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
created_at INTEGER NOT NULL
)
`)
}
},
{
version: 2,
name: 'add_status_column',
up: async (db) => {
await db.execute(`ALTER TABLE items ADD COLUMN status TEXT DEFAULT 'active'`)
}
}
]
// Run migrations in initialize()
export async function runMigrations(db: ExtensionDatabase): Promise<void> {
const result = await db.query<{ value: string }>(
`SELECT value FROM _extension_meta WHERE key = 'migration_version'`
).catch(() => [])
const currentVersion = result.length > 0 ? parseInt(result[0].value, 10) : 0
for (const migration of migrations) {
if (migration.version > currentVersion) {
console.log(`Running migration ${migration.version}: ${migration.name}`)
await migration.up(db)
await db.execute(
`INSERT INTO _extension_meta (key, value) VALUES ('migration_version', ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
[String(migration.version)]
)
}
}
}
```
---
## RPC Methods
Extensions register RPC methods that can be called by clients.
### Naming Convention
Methods should be namespaced with the extension ID:
```
{extension-id}.{methodName}
```
Examples:
- `marketplace.createStall`
- `withdraw.createLink`
### Method Handler Signature
```typescript
type RpcMethodHandler = (
request: any, // The request payload
applicationId: string, // The calling application
userPubkey?: string // The authenticated user (if any)
) => Promise<any>
```
### Example
```typescript
ctx.registerMethod('my-extension.createItem', async (req, appId, userPubkey) => {
// Validate request
if (!req.name) {
throw new Error('Name is required')
}
// Create item
const item = await this.manager.create(appId, req)
// Return response
return { item }
})
```
---
## HTTP Routes
Some extensions need HTTP endpoints (e.g., LNURL protocol). Extensions can define routes that the main application mounts.
### Defining Routes
```typescript
interface HttpRoute {
method: 'GET' | 'POST'
path: string
handler: (req: HttpRequest) => Promise<HttpResponse>
}
interface HttpRequest {
params: Record<string, string> // URL path params
query: Record<string, string> // Query string params
body?: any // POST body
headers: Record<string, string>
}
interface HttpResponse {
status: number
body: any
headers?: Record<string, string>
}
```
### Example
```typescript
class MyExtension implements Extension {
getHttpRoutes(): HttpRoute[] {
return [
{
method: 'GET',
path: '/api/v1/my-extension/:id',
handler: async (req) => {
const item = await this.getItem(req.params.id)
return {
status: 200,
body: item,
headers: { 'Content-Type': 'application/json' }
}
}
}
]
}
}
```
---
## Event Handling
### Payment Callbacks
When you create an invoice with metadata, you'll receive that metadata back in the payment callback:
```typescript
// Creating invoice with metadata
const invoice = await ctx.createInvoice(1000, {
metadata: {
extension: 'my-extension',
order_id: 'order-123'
}
})
// Handling payment
ctx.onPaymentReceived(async (payment) => {
if (payment.metadata?.extension === 'my-extension') {
const orderId = payment.metadata.order_id
await this.handlePayment(orderId, payment)
}
})
```
### Nostr Events
Subscribe to Nostr events for your application:
```typescript
ctx.onNostrEvent(async (event, applicationId) => {
// Filter by event kind
if (event.kind === 4) { // Encrypted DM
await this.handleDirectMessage(event, applicationId)
}
})
```
---
## Configuration
### Loader Configuration
```typescript
interface ExtensionLoaderConfig {
extensionsDir: string // Directory containing extensions
databaseDir: string // Directory for extension databases
enabledExtensions?: string[] // Whitelist (if set, only these load)
disabledExtensions?: string[] // Blacklist
}
```
### Usage
```typescript
import { createExtensionLoader } from './extensions'
const loader = createExtensionLoader({
extensionsDir: './src/extensions',
databaseDir: './data/extensions',
disabledExtensions: ['experimental-ext']
}, mainHandler)
await loader.loadAll()
// Call extension methods
const result = await loader.callMethod(
'marketplace.createStall',
{ name: 'My Shop', currency: 'sat', shipping_zones: [] },
applicationId,
userPubkey
)
// Dispatch events
loader.dispatchPaymentReceived(paymentData)
loader.dispatchNostrEvent(event, applicationId)
// Shutdown
await loader.shutdown()
```
---
## Examples
### Example: Simple Counter Extension
```typescript
// src/extensions/counter/index.ts
import { Extension, ExtensionInfo, ExtensionContext, ExtensionDatabase } from '../types.js'
export default class CounterExtension implements Extension {
readonly info: ExtensionInfo = {
id: 'counter',
name: 'Simple Counter',
version: '1.0.0',
description: 'A simple counter for each application',
author: 'Example'
}
private db!: ExtensionDatabase
async initialize(ctx: ExtensionContext, db: ExtensionDatabase): Promise<void> {
this.db = db
await db.execute(`
CREATE TABLE IF NOT EXISTS counters (
application_id TEXT PRIMARY KEY,
count INTEGER NOT NULL DEFAULT 0
)
`)
ctx.registerMethod('counter.increment', async (req, appId) => {
await db.execute(
`INSERT INTO counters (application_id, count) VALUES (?, 1)
ON CONFLICT(application_id) DO UPDATE SET count = count + 1`,
[appId]
)
const result = await db.query<{ count: number }>(
'SELECT count FROM counters WHERE application_id = ?',
[appId]
)
return { count: result[0]?.count || 0 }
})
ctx.registerMethod('counter.get', async (req, appId) => {
const result = await db.query<{ count: number }>(
'SELECT count FROM counters WHERE application_id = ?',
[appId]
)
return { count: result[0]?.count || 0 }
})
ctx.registerMethod('counter.reset', async (req, appId) => {
await db.execute(
'UPDATE counters SET count = 0 WHERE application_id = ?',
[appId]
)
return { count: 0 }
})
}
}
```
### Example: Payment-Triggered Extension
```typescript
// src/extensions/donations/index.ts
import { Extension, ExtensionContext, ExtensionDatabase } from '../types.js'
export default class DonationsExtension implements Extension {
readonly info = {
id: 'donations',
name: 'Donations',
version: '1.0.0',
description: 'Accept donations with thank-you messages',
author: 'Example'
}
private db!: ExtensionDatabase
private ctx!: ExtensionContext
async initialize(ctx: ExtensionContext, db: ExtensionDatabase): Promise<void> {
this.db = db
this.ctx = ctx
await db.execute(`
CREATE TABLE IF NOT EXISTS donations (
id TEXT PRIMARY KEY,
application_id TEXT NOT NULL,
amount_sats INTEGER NOT NULL,
donor_pubkey TEXT,
message TEXT,
created_at INTEGER NOT NULL
)
`)
// Create donation invoice
ctx.registerMethod('donations.createInvoice', async (req, appId) => {
const invoice = await ctx.createInvoice(req.amount_sats, {
memo: req.message || 'Donation',
metadata: {
extension: 'donations',
donor_pubkey: req.donor_pubkey,
message: req.message
}
})
return { invoice: invoice.paymentRequest }
})
// Handle successful payments
ctx.onPaymentReceived(async (payment) => {
if (payment.metadata?.extension !== 'donations') return
// Record donation
await db.execute(
`INSERT INTO donations (id, application_id, amount_sats, donor_pubkey, message, created_at)
VALUES (?, ?, ?, ?, ?, ?)`,
[
payment.paymentHash,
payment.metadata.application_id,
payment.amountSats,
payment.metadata.donor_pubkey,
payment.metadata.message,
Math.floor(Date.now() / 1000)
]
)
// Send thank-you DM if donor has pubkey
if (payment.metadata.donor_pubkey) {
await ctx.sendEncryptedDM(
payment.metadata.application_id,
payment.metadata.donor_pubkey,
`Thank you for your donation of ${payment.amountSats} sats!`
)
}
})
// List donations
ctx.registerMethod('donations.list', async (req, appId) => {
const donations = await db.query(
`SELECT * FROM donations WHERE application_id = ? ORDER BY created_at DESC LIMIT ?`,
[appId, req.limit || 50]
)
return { donations }
})
}
}
```
---
## Best Practices
1. **Namespace your methods**: Always prefix RPC methods with your extension ID
2. **Use migrations**: Never modify existing migration files; create new ones
3. **Handle errors gracefully**: Throw descriptive errors, don't return error objects
4. **Clean up in shutdown**: Close connections, cancel timers, etc.
5. **Log appropriately**: Use debug for verbose info, error for failures
6. **Validate inputs**: Check request parameters before processing
7. **Use transactions**: For multi-step database operations
8. **Document your API**: Include types and descriptions for RPC methods
---
## Troubleshooting
### Extension not loading
1. Check that directory name matches `info.id`
2. Verify `index.ts` has a default export
3. Check for TypeScript/import errors in logs
### Database errors
1. Check migration syntax
2. Verify column types match queries
3. Look for migration version conflicts
### RPC method not found
1. Verify method is registered in `initialize()`
2. Check method name includes extension prefix
3. Ensure extension status is `ready`
### Payment callbacks not firing
1. Verify `metadata.extension` matches your extension ID
2. Check that `onPaymentReceived` is registered in `initialize()`
3. Confirm invoice was created through the extension

324
src/extensions/context.ts Normal file
View file

@ -0,0 +1,324 @@
import {
ExtensionContext,
ExtensionDatabase,
ExtensionInfo,
ApplicationInfo,
CreateInvoiceOptions,
CreatedInvoice,
PaymentReceivedData,
NostrEvent,
UnsignedNostrEvent,
RpcMethodHandler,
LnurlPayInfo
} from './types.js'
/**
* Main Handler interface (from Lightning.Pub)
* This is a minimal interface - the actual MainHandler has more methods
*/
export interface MainHandlerInterface {
// Application management
applicationManager: {
getById(id: string): Promise<any>
PayAppUserInvoice(appId: string, req: {
amount: number
invoice: string
user_identifier: string
debit_npub?: string
}): Promise<{
preimage: string
amount_paid: number
network_fee: number
service_fee: number
}>
}
// Payment operations
paymentManager: {
createInvoice(params: {
applicationId: string
amountSats: number
memo?: string
expiry?: number
metadata?: Record<string, any>
}): Promise<{
id: string
paymentRequest: string
paymentHash: string
expiry: number
}>
payInvoice(params: {
applicationId: string
paymentRequest: string
maxFeeSats?: number
userPubkey?: string
}): Promise<{
paymentHash: string
feeSats: number
}>
/**
* Get LNURL-pay info for a user by their Nostr pubkey
* This enables Lightning Address (LUD-16) and zap (NIP-57) support
*/
getLnurlPayInfoByPubkey(pubkeyHex: string, options?: {
metadata?: string
description?: string
}): Promise<LnurlPayInfo>
}
// Nostr operations
sendNostrEvent(event: any): Promise<string | null>
sendEncryptedDM(applicationId: string, recipientPubkey: string, content: string): Promise<string>
}
/**
* Callback registries for extension events
*/
interface CallbackRegistries {
paymentReceived: Array<(payment: PaymentReceivedData) => Promise<void>>
nostrEvent: Array<(event: NostrEvent, applicationId: string) => Promise<void>>
}
/**
* Registered RPC method
*/
interface RegisteredMethod {
extensionId: string
handler: RpcMethodHandler
}
/**
* Extension Context Implementation
*
* Provides the interface for extensions to interact with Lightning.Pub.
* Each extension gets its own context instance.
*/
export class ExtensionContextImpl implements ExtensionContext {
private callbacks: CallbackRegistries = {
paymentReceived: [],
nostrEvent: []
}
constructor(
private extensionInfo: ExtensionInfo,
private database: ExtensionDatabase,
private mainHandler: MainHandlerInterface,
private methodRegistry: Map<string, RegisteredMethod>
) {}
/**
* Get information about an application
*/
async getApplication(applicationId: string): Promise<ApplicationInfo | null> {
try {
const app = await this.mainHandler.applicationManager.getById(applicationId)
if (!app) return null
return {
id: app.id,
name: app.name,
nostr_public: app.nostr_public,
balance_sats: app.balance || 0
}
} catch (e) {
this.log('error', `Failed to get application ${applicationId}:`, e)
return null
}
}
/**
* Create a Lightning invoice
*/
async createInvoice(amountSats: number, options: CreateInvoiceOptions = {}): Promise<CreatedInvoice> {
// Note: In practice, this needs an applicationId. Extensions typically
// get this from the RPC request context. For now, we'll need to handle
// this in the actual implementation.
throw new Error('createInvoice requires applicationId from request context')
}
/**
* Create invoice with explicit application ID
* This is the internal method used by extensions
*/
async createInvoiceForApp(
applicationId: string,
amountSats: number,
options: CreateInvoiceOptions = {}
): Promise<CreatedInvoice> {
const result = await this.mainHandler.paymentManager.createInvoice({
applicationId,
amountSats,
memo: options.memo,
expiry: options.expiry,
metadata: {
...options.metadata,
extension: this.extensionInfo.id
}
})
return {
id: result.id,
paymentRequest: result.paymentRequest,
paymentHash: result.paymentHash,
expiry: result.expiry
}
}
/**
* Pay a Lightning invoice
* If userPubkey is provided, pays from that user's balance instead of app.owner
*/
async payInvoice(
applicationId: string,
paymentRequest: string,
maxFeeSats?: number,
userPubkey?: string
): Promise<{ paymentHash: string; feeSats: number }> {
return this.mainHandler.paymentManager.payInvoice({
applicationId,
paymentRequest,
maxFeeSats,
userPubkey
})
}
/**
* Send an encrypted DM via Nostr
*/
async sendEncryptedDM(
applicationId: string,
recipientPubkey: string,
content: string
): Promise<string> {
return this.mainHandler.sendEncryptedDM(applicationId, recipientPubkey, content)
}
/**
* Publish a Nostr event
*/
async publishNostrEvent(event: UnsignedNostrEvent): Promise<string | null> {
return this.mainHandler.sendNostrEvent(event)
}
/**
* Get LNURL-pay info for a user by pubkey
* Enables Lightning Address and zap support
*/
async getLnurlPayInfo(pubkeyHex: string, options?: {
metadata?: string
description?: string
}): Promise<LnurlPayInfo> {
return this.mainHandler.paymentManager.getLnurlPayInfoByPubkey(pubkeyHex, options)
}
/**
* Subscribe to payment received callbacks
*/
onPaymentReceived(callback: (payment: PaymentReceivedData) => Promise<void>): void {
this.callbacks.paymentReceived.push(callback)
}
/**
* Subscribe to incoming Nostr events
*/
onNostrEvent(callback: (event: NostrEvent, applicationId: string) => Promise<void>): void {
this.callbacks.nostrEvent.push(callback)
}
/**
* Register an RPC method
*/
registerMethod(name: string, handler: RpcMethodHandler): void {
const fullName = name.startsWith(`${this.extensionInfo.id}.`)
? name
: `${this.extensionInfo.id}.${name}`
if (this.methodRegistry.has(fullName)) {
throw new Error(`RPC method ${fullName} already registered`)
}
this.methodRegistry.set(fullName, {
extensionId: this.extensionInfo.id,
handler
})
this.log('debug', `Registered RPC method: ${fullName}`)
}
/**
* Get the extension's database
*/
getDatabase(): ExtensionDatabase {
return this.database
}
/**
* Log a message
*/
log(level: 'debug' | 'info' | 'warn' | 'error', message: string, ...args: any[]): void {
const prefix = `[Extension:${this.extensionInfo.id}]`
switch (level) {
case 'debug':
console.debug(prefix, message, ...args)
break
case 'info':
console.info(prefix, message, ...args)
break
case 'warn':
console.warn(prefix, message, ...args)
break
case 'error':
console.error(prefix, message, ...args)
break
}
}
// ===== Internal Methods (called by ExtensionLoader) =====
/**
* Dispatch payment received event to extension callbacks
*/
async dispatchPaymentReceived(payment: PaymentReceivedData): Promise<void> {
for (const callback of this.callbacks.paymentReceived) {
try {
await callback(payment)
} catch (e) {
this.log('error', 'Error in payment callback:', e)
}
}
}
/**
* Dispatch Nostr event to extension callbacks
*/
async dispatchNostrEvent(event: NostrEvent, applicationId: string): Promise<void> {
for (const callback of this.callbacks.nostrEvent) {
try {
await callback(event, applicationId)
} catch (e) {
this.log('error', 'Error in Nostr event callback:', e)
}
}
}
/**
* Get registered callbacks for external access
*/
getCallbacks(): CallbackRegistries {
return this.callbacks
}
}
/**
* Create an extension context
*/
export function createExtensionContext(
extensionInfo: ExtensionInfo,
database: ExtensionDatabase,
mainHandler: MainHandlerInterface,
methodRegistry: Map<string, RegisteredMethod>
): ExtensionContextImpl {
return new ExtensionContextImpl(extensionInfo, database, mainHandler, methodRegistry)
}

148
src/extensions/database.ts Normal file
View file

@ -0,0 +1,148 @@
import Database from 'better-sqlite3'
import path from 'path'
import fs from 'fs'
import { ExtensionDatabase } from './types.js'
/**
* Extension Database Implementation
*
* Provides isolated SQLite database access for each extension.
* Uses better-sqlite3 for synchronous, high-performance access.
*/
export class ExtensionDatabaseImpl implements ExtensionDatabase {
private db: Database.Database
private extensionId: string
constructor(extensionId: string, databaseDir: string) {
this.extensionId = extensionId
// Ensure database directory exists
if (!fs.existsSync(databaseDir)) {
fs.mkdirSync(databaseDir, { recursive: true })
}
// Create database file for this extension
const dbPath = path.join(databaseDir, `${extensionId}.db`)
this.db = new Database(dbPath)
// Enable WAL mode for better concurrency
this.db.pragma('journal_mode = WAL')
// Enable foreign keys
this.db.pragma('foreign_keys = ON')
// Create metadata table for tracking migrations
this.db.exec(`
CREATE TABLE IF NOT EXISTS _extension_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
)
`)
}
/**
* Execute a write query (INSERT, UPDATE, DELETE, CREATE, etc.)
*/
async execute(sql: string, params: any[] = []): Promise<{ changes?: number; lastId?: number }> {
try {
const stmt = this.db.prepare(sql)
const result = stmt.run(...params)
return {
changes: result.changes,
lastId: result.lastInsertRowid as number
}
} catch (e) {
console.error(`[Extension:${this.extensionId}] Database execute error:`, e)
throw e
}
}
/**
* Execute a read query (SELECT)
*/
async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {
try {
const stmt = this.db.prepare(sql)
return stmt.all(...params) as T[]
} catch (e) {
console.error(`[Extension:${this.extensionId}] Database query error:`, e)
throw e
}
}
/**
* Execute multiple statements in a transaction
*/
async transaction<T>(fn: () => Promise<T>): Promise<T> {
const runTransaction = this.db.transaction(() => {
// Note: better-sqlite3 transactions are synchronous
// We wrap the async function but it executes synchronously
return fn()
})
return runTransaction() as T
}
/**
* Get a metadata value
*/
async getMeta(key: string): Promise<string | null> {
const rows = await this.query<{ value: string }>(
'SELECT value FROM _extension_meta WHERE key = ?',
[key]
)
return rows.length > 0 ? rows[0].value : null
}
/**
* Set a metadata value
*/
async setMeta(key: string, value: string): Promise<void> {
await this.execute(
`INSERT INTO _extension_meta (key, value) VALUES (?, ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
[key, value]
)
}
/**
* Get current migration version
*/
async getMigrationVersion(): Promise<number> {
const version = await this.getMeta('migration_version')
return version ? parseInt(version, 10) : 0
}
/**
* Set migration version
*/
async setMigrationVersion(version: number): Promise<void> {
await this.setMeta('migration_version', String(version))
}
/**
* Close the database connection
*/
close(): void {
this.db.close()
}
/**
* Get the underlying database for advanced operations
* (Use with caution - bypasses isolation)
*/
getUnderlyingDb(): Database.Database {
return this.db
}
}
/**
* Create an extension database instance
*/
export function createExtensionDatabase(
extensionId: string,
databaseDir: string
): ExtensionDatabaseImpl {
return new ExtensionDatabaseImpl(extensionId, databaseDir)
}

56
src/extensions/index.ts Normal file
View file

@ -0,0 +1,56 @@
/**
* Lightning.Pub Extension System
*
* This module provides the extension infrastructure for Lightning.Pub.
* Extensions can add functionality like marketplaces, subscriptions,
* tipping, and more.
*
* Usage:
*
* ```typescript
* import { createExtensionLoader, ExtensionLoaderConfig } from './extensions'
*
* const config: ExtensionLoaderConfig = {
* extensionsDir: './extensions',
* databaseDir: './data/extensions'
* }
*
* const loader = createExtensionLoader(config, mainHandler)
* await loader.loadAll()
*
* // Call extension methods
* const result = await loader.callMethod(
* 'marketplace.createStall',
* { name: 'My Shop', currency: 'sat', shipping_zones: [...] },
* applicationId
* )
* ```
*/
// Export types
export {
Extension,
ExtensionInfo,
ExtensionContext,
ExtensionDatabase,
ExtensionModule,
ExtensionConstructor,
LoadedExtension,
ExtensionLoaderConfig,
ApplicationInfo,
CreateInvoiceOptions,
CreatedInvoice,
PaymentReceivedData,
NostrEvent,
UnsignedNostrEvent,
RpcMethodHandler
} from './types.js'
// Export loader
export { ExtensionLoader, createExtensionLoader } from './loader.js'
// Export database utilities
export { ExtensionDatabaseImpl, createExtensionDatabase } from './database.js'
// Export context utilities
export { ExtensionContextImpl, createExtensionContext, MainHandlerInterface } from './context.js'

406
src/extensions/loader.ts Normal file
View file

@ -0,0 +1,406 @@
import path from 'path'
import fs from 'fs'
import {
Extension,
ExtensionInfo,
ExtensionModule,
LoadedExtension,
ExtensionLoaderConfig,
RpcMethodHandler,
PaymentReceivedData,
NostrEvent
} from './types.js'
import { ExtensionDatabaseImpl, createExtensionDatabase } from './database.js'
import { ExtensionContextImpl, createExtensionContext, MainHandlerInterface } from './context.js'
/**
* Registered RPC method entry
*/
interface RegisteredMethod {
extensionId: string
handler: RpcMethodHandler
}
/**
* Extension Loader
*
* Discovers, loads, and manages Lightning.Pub extensions.
* Provides lifecycle management and event dispatching.
*/
export class ExtensionLoader {
private config: ExtensionLoaderConfig
private mainHandler: MainHandlerInterface
private extensions: Map<string, LoadedExtension> = new Map()
private contexts: Map<string, ExtensionContextImpl> = new Map()
private methodRegistry: Map<string, RegisteredMethod> = new Map()
private initialized = false
constructor(config: ExtensionLoaderConfig, mainHandler: MainHandlerInterface) {
this.config = config
this.mainHandler = mainHandler
}
/**
* Discover and load all extensions
*/
async loadAll(): Promise<void> {
if (this.initialized) {
throw new Error('Extension loader already initialized')
}
console.log('[Extensions] Loading extensions from:', this.config.extensionsDir)
// Ensure directories exist
if (!fs.existsSync(this.config.extensionsDir)) {
console.log('[Extensions] Extensions directory does not exist, creating...')
fs.mkdirSync(this.config.extensionsDir, { recursive: true })
this.initialized = true
return
}
if (!fs.existsSync(this.config.databaseDir)) {
fs.mkdirSync(this.config.databaseDir, { recursive: true })
}
// Discover extensions
const extensionDirs = await this.discoverExtensions()
console.log(`[Extensions] Found ${extensionDirs.length} extension(s)`)
// Load extensions in dependency order
const loadOrder = await this.resolveDependencies(extensionDirs)
for (const extDir of loadOrder) {
try {
await this.loadExtension(extDir)
} catch (e) {
console.error(`[Extensions] Failed to load extension from ${extDir}:`, e)
}
}
this.initialized = true
console.log(`[Extensions] Loaded ${this.extensions.size} extension(s)`)
}
/**
* Discover extension directories
*/
private async discoverExtensions(): Promise<string[]> {
const entries = fs.readdirSync(this.config.extensionsDir, { withFileTypes: true })
const extensionDirs: string[] = []
for (const entry of entries) {
if (!entry.isDirectory()) continue
const extDir = path.join(this.config.extensionsDir, entry.name)
const indexPath = path.join(extDir, 'index.ts')
const indexJsPath = path.join(extDir, 'index.js')
// Check for index file
if (fs.existsSync(indexPath) || fs.existsSync(indexJsPath)) {
// Check enabled/disabled lists
if (this.config.disabledExtensions?.includes(entry.name)) {
console.log(`[Extensions] Skipping disabled extension: ${entry.name}`)
continue
}
if (this.config.enabledExtensions &&
!this.config.enabledExtensions.includes(entry.name)) {
console.log(`[Extensions] Skipping non-enabled extension: ${entry.name}`)
continue
}
extensionDirs.push(extDir)
}
}
return extensionDirs
}
/**
* Resolve extension dependencies and return load order
*/
private async resolveDependencies(extensionDirs: string[]): Promise<string[]> {
// For now, simple alphabetical order
// TODO: Implement proper dependency resolution with topological sort
return extensionDirs.sort()
}
/**
* Load a single extension
*/
private async loadExtension(extensionDir: string): Promise<void> {
const dirName = path.basename(extensionDir)
console.log(`[Extensions] Loading extension: ${dirName}`)
// Determine index file path
let indexPath = path.join(extensionDir, 'index.js')
if (!fs.existsSync(indexPath)) {
indexPath = path.join(extensionDir, 'index.ts')
}
// Dynamic import
const moduleUrl = `file://${indexPath}`
const module = await import(moduleUrl) as ExtensionModule
if (!module.default) {
throw new Error(`Extension ${dirName} has no default export`)
}
// Instantiate extension
const ExtensionClass = module.default
const instance = new ExtensionClass() as Extension
if (!instance.info) {
throw new Error(`Extension ${dirName} has no info property`)
}
const info = instance.info
// Validate extension ID matches directory name
if (info.id !== dirName) {
console.warn(
`[Extensions] Extension ID '${info.id}' doesn't match directory '${dirName}'`
)
}
// Check for duplicate
if (this.extensions.has(info.id)) {
throw new Error(`Extension ${info.id} already loaded`)
}
// Create isolated database
const database = createExtensionDatabase(info.id, this.config.databaseDir)
// Create context
const context = createExtensionContext(
info,
database,
this.mainHandler,
this.methodRegistry
)
// Track as loading
const loaded: LoadedExtension = {
info,
instance,
database,
status: 'loading',
loadedAt: Date.now()
}
this.extensions.set(info.id, loaded)
this.contexts.set(info.id, context)
try {
// Initialize extension
await instance.initialize(context, database)
loaded.status = 'ready'
console.log(`[Extensions] Extension ${info.id} v${info.version} loaded successfully`)
} catch (e) {
loaded.status = 'error'
loaded.error = e as Error
console.error(`[Extensions] Extension ${info.id} initialization failed:`, e)
throw e
}
}
/**
* Unload a specific extension
*/
async unloadExtension(extensionId: string): Promise<void> {
const loaded = this.extensions.get(extensionId)
if (!loaded) {
throw new Error(`Extension ${extensionId} not found`)
}
console.log(`[Extensions] Unloading extension: ${extensionId}`)
try {
// Call shutdown if available
if (loaded.instance.shutdown) {
await loaded.instance.shutdown()
}
loaded.status = 'stopped'
} catch (e) {
console.error(`[Extensions] Error during ${extensionId} shutdown:`, e)
}
// Close database
if (loaded.database instanceof ExtensionDatabaseImpl) {
loaded.database.close()
}
// Remove registered methods
for (const [name, method] of this.methodRegistry.entries()) {
if (method.extensionId === extensionId) {
this.methodRegistry.delete(name)
}
}
// Remove from maps
this.extensions.delete(extensionId)
this.contexts.delete(extensionId)
}
/**
* Shutdown all extensions
*/
async shutdown(): Promise<void> {
console.log('[Extensions] Shutting down all extensions...')
for (const extensionId of this.extensions.keys()) {
try {
await this.unloadExtension(extensionId)
} catch (e) {
console.error(`[Extensions] Error unloading ${extensionId}:`, e)
}
}
console.log('[Extensions] All extensions shut down')
}
/**
* Get a loaded extension
*/
getExtension(extensionId: string): LoadedExtension | undefined {
return this.extensions.get(extensionId)
}
/**
* Get all loaded extensions
*/
getAllExtensions(): LoadedExtension[] {
return Array.from(this.extensions.values())
}
/**
* Check if an extension is loaded and ready
*/
isReady(extensionId: string): boolean {
const ext = this.extensions.get(extensionId)
return ext?.status === 'ready'
}
/**
* Get all registered RPC methods
*/
getRegisteredMethods(): Map<string, RegisteredMethod> {
return this.methodRegistry
}
/**
* Call an extension RPC method
*/
async callMethod(
methodName: string,
request: any,
applicationId: string,
userPubkey?: string
): Promise<any> {
const method = this.methodRegistry.get(methodName)
if (!method) {
throw new Error(`Unknown method: ${methodName}`)
}
const ext = this.extensions.get(method.extensionId)
if (!ext || ext.status !== 'ready') {
throw new Error(`Extension ${method.extensionId} not ready`)
}
return method.handler(request, applicationId, userPubkey)
}
/**
* Check if a method exists
*/
hasMethod(methodName: string): boolean {
return this.methodRegistry.has(methodName)
}
/**
* Dispatch payment received event to all extensions
*/
async dispatchPaymentReceived(payment: PaymentReceivedData): Promise<void> {
for (const context of this.contexts.values()) {
try {
await context.dispatchPaymentReceived(payment)
} catch (e) {
console.error('[Extensions] Error dispatching payment:', e)
}
}
}
/**
* Dispatch Nostr event to all extensions
*/
async dispatchNostrEvent(event: NostrEvent, applicationId: string): Promise<void> {
for (const context of this.contexts.values()) {
try {
await context.dispatchNostrEvent(event, applicationId)
} catch (e) {
console.error('[Extensions] Error dispatching Nostr event:', e)
}
}
}
/**
* Run health checks on all extensions
*/
async healthCheck(): Promise<Map<string, boolean>> {
const results = new Map<string, boolean>()
for (const [id, ext] of this.extensions.entries()) {
if (ext.status !== 'ready') {
results.set(id, false)
continue
}
try {
if (ext.instance.healthCheck) {
results.set(id, await ext.instance.healthCheck())
} else {
results.set(id, true)
}
} catch (e) {
results.set(id, false)
}
}
return results
}
/**
* Get extension status summary
*/
getStatus(): {
total: number
ready: number
error: number
extensions: Array<{ id: string; name: string; version: string; status: string }>
} {
const extensions = this.getAllExtensions().map(ext => ({
id: ext.info.id,
name: ext.info.name,
version: ext.info.version,
status: ext.status
}))
return {
total: extensions.length,
ready: extensions.filter(e => e.status === 'ready').length,
error: extensions.filter(e => e.status === 'error').length,
extensions
}
}
}
/**
* Create an extension loader instance
*/
export function createExtensionLoader(
config: ExtensionLoaderConfig,
mainHandler: MainHandlerInterface
): ExtensionLoader {
return new ExtensionLoader(config, mainHandler)
}

View file

@ -0,0 +1,155 @@
/**
* MainHandler Adapter for Extension System
*
* Wraps the Lightning.Pub mainHandler to provide the MainHandlerInterface
* required by the extension system.
*/
import { MainHandlerInterface } from './context.js'
import { LnurlPayInfo } from './types.js'
import type Main from '../services/main/index.js'
/**
* Create an adapter that wraps mainHandler for extension use
*/
export function createMainHandlerAdapter(mainHandler: Main): MainHandlerInterface {
return {
applicationManager: {
async getById(id: string) {
// The applicationManager stores apps internally
// We need to access it through the storage layer
try {
const app = await mainHandler.storage.applicationStorage.GetApplication(id)
if (!app) return null
return {
id: app.app_id,
name: app.name,
nostr_public: app.nostr_public_key || '',
balance: app.owner?.balance_sats || 0
}
} catch (e) {
// GetApplication throws if not found
return null
}
},
async PayAppUserInvoice(appId, req) {
return mainHandler.applicationManager.PayAppUserInvoice(appId, req)
}
},
paymentManager: {
async createInvoice(params: {
applicationId: string
amountSats: number
memo?: string
expiry?: number
metadata?: Record<string, any>
}) {
// Get the app to find the user ID
const app = await mainHandler.storage.applicationStorage.GetApplication(params.applicationId)
if (!app) {
throw new Error(`Application not found: ${params.applicationId}`)
}
// Create invoice using the app owner's user ID
const result = await mainHandler.paymentManager.NewInvoice(
app.owner.user_id,
{
amountSats: params.amountSats,
memo: params.memo || ''
},
{
expiry: params.expiry || 3600
}
)
return {
id: result.invoice.split(':')[0] || result.invoice, // Extract ID if present
paymentRequest: result.invoice,
paymentHash: '', // Not directly available from NewInvoice response
expiry: Date.now() + (params.expiry || 3600) * 1000
}
},
async payInvoice(params: {
applicationId: string
paymentRequest: string
maxFeeSats?: number
userPubkey?: string
}) {
// Get the app to find the user ID and app reference
const app = await mainHandler.storage.applicationStorage.GetApplication(params.applicationId)
if (!app) {
throw new Error(`Application not found: ${params.applicationId}`)
}
if (params.userPubkey) {
// Resolve the Nostr user's ApplicationUser to get their identifier
const appUser = await mainHandler.storage.applicationStorage.GetOrCreateNostrAppUser(app, params.userPubkey)
console.log(`[MainHandlerAdapter] Paying via PayAppUserInvoice from Nostr user ${params.userPubkey.slice(0, 8)}... (identifier: ${appUser.identifier})`)
// Use applicationManager.PayAppUserInvoice so notifyAppUserPayment fires
// This sends LiveUserOperation events via Nostr for real-time balance updates
const result = await mainHandler.applicationManager.PayAppUserInvoice(
params.applicationId,
{
invoice: params.paymentRequest,
amount: 0, // Use invoice amount
user_identifier: appUser.identifier
}
)
return {
paymentHash: result.preimage || '',
feeSats: result.network_fee || 0
}
}
// Fallback: pay from app owner's balance (no Nostr user context)
const result = await mainHandler.paymentManager.PayInvoice(
app.owner.user_id,
{
invoice: params.paymentRequest,
amount: 0
},
app,
{}
)
return {
paymentHash: result.preimage || '',
feeSats: result.network_fee || 0
}
},
async getLnurlPayInfoByPubkey(pubkeyHex: string, options?: {
metadata?: string
description?: string
}): Promise<LnurlPayInfo> {
// This would need implementation based on how Lightning.Pub handles LNURL-pay
// For now, throw not implemented
throw new Error('getLnurlPayInfoByPubkey not yet implemented')
}
},
async sendNostrEvent(event: any): Promise<string | null> {
// The mainHandler doesn't directly expose nostrSend
// This would need to be implemented through the nostrMiddleware
// For now, return null (not implemented)
console.warn('[MainHandlerAdapter] sendNostrEvent not fully implemented')
return null
},
async sendEncryptedDM(
applicationId: string,
recipientPubkey: string,
content: string
): Promise<string> {
// This would need implementation using NIP-44 encryption
// For now, throw not implemented
throw new Error('sendEncryptedDM not yet implemented')
}
}
}

286
src/extensions/types.ts Normal file
View file

@ -0,0 +1,286 @@
/**
* Extension System Core Types
*
* These types define the contract between Lightning.Pub and extensions.
*/
/**
* Extension metadata
*/
export interface ExtensionInfo {
id: string // Unique identifier (lowercase, no spaces)
name: string // Display name
version: string // Semver version
description: string // Short description
author: string // Author name or organization
minPubVersion?: string // Minimum Lightning.Pub version required
dependencies?: string[] // Other extension IDs this depends on
}
/**
* Extension database interface
* Provides isolated database access for each extension
*/
export interface ExtensionDatabase {
/**
* Execute a write query (INSERT, UPDATE, DELETE, CREATE, etc.)
*/
execute(sql: string, params?: any[]): Promise<{ changes?: number; lastId?: number }>
/**
* Execute a read query (SELECT)
*/
query<T = any>(sql: string, params?: any[]): Promise<T[]>
/**
* Execute multiple statements in a transaction
*/
transaction<T>(fn: () => Promise<T>): Promise<T>
}
/**
* Application info provided to extensions
*/
export interface ApplicationInfo {
id: string
name: string
nostr_public: string // Application's Nostr pubkey (hex)
balance_sats: number
}
/**
* Invoice creation options
*/
export interface CreateInvoiceOptions {
memo?: string
expiry?: number // Seconds until expiry
metadata?: Record<string, any> // Custom metadata for callbacks
}
/**
* Created invoice result
*/
export interface CreatedInvoice {
id: string // Internal invoice ID
paymentRequest: string // BOLT11 invoice string
paymentHash: string // Payment hash (hex)
expiry: number // Expiry timestamp
}
/**
* Payment received callback data
*/
export interface PaymentReceivedData {
invoiceId: string
paymentHash: string
amountSats: number
metadata?: Record<string, any>
}
/**
* LNURL-pay info response (LUD-06/LUD-16)
* Used for Lightning Address and zap support
*/
export interface LnurlPayInfo {
tag: 'payRequest'
callback: string // URL to call with amount
minSendable: number // Minimum msats
maxSendable: number // Maximum msats
metadata: string // JSON-encoded metadata array
allowsNostr?: boolean // Whether zaps are supported
nostrPubkey?: string // Pubkey for zap receipts (hex)
}
/**
* Nostr event structure (minimal)
*/
export interface NostrEvent {
id: string
pubkey: string
created_at: number
kind: number
tags: string[][]
content: string
sig?: string
}
/**
* Unsigned Nostr event for publishing
*/
export interface UnsignedNostrEvent {
kind: number
pubkey: string
created_at: number
tags: string[][]
content: string
}
/**
* RPC method handler function
*/
export type RpcMethodHandler = (
request: any,
applicationId: string,
userPubkey?: string
) => Promise<any>
/**
* Extension context - interface provided to extensions for interacting with Lightning.Pub
*/
export interface ExtensionContext {
/**
* Get information about an application
*/
getApplication(applicationId: string): Promise<ApplicationInfo | null>
/**
* Create a Lightning invoice
*/
createInvoice(amountSats: number, options?: CreateInvoiceOptions): Promise<CreatedInvoice>
/**
* Pay a Lightning invoice (requires sufficient balance)
* If userPubkey is provided, pays from that user's balance instead of app.owner
*/
payInvoice(applicationId: string, paymentRequest: string, maxFeeSats?: number, userPubkey?: string): Promise<{
paymentHash: string
feeSats: number
}>
/**
* Send an encrypted DM via Nostr (NIP-44)
*/
sendEncryptedDM(applicationId: string, recipientPubkey: string, content: string): Promise<string>
/**
* Publish a Nostr event (signed by application's key)
*/
publishNostrEvent(event: UnsignedNostrEvent): Promise<string | null>
/**
* Get LNURL-pay info for a user (by pubkey)
* Used to enable Lightning Address support (LUD-16) and zaps (NIP-57)
*/
getLnurlPayInfo(pubkeyHex: string, options?: {
metadata?: string // Custom metadata JSON
description?: string // Human-readable description
}): Promise<LnurlPayInfo>
/**
* Subscribe to payment received callbacks
*/
onPaymentReceived(callback: (payment: PaymentReceivedData) => Promise<void>): void
/**
* Subscribe to incoming Nostr events for the application
*/
onNostrEvent(callback: (event: NostrEvent, applicationId: string) => Promise<void>): void
/**
* Register an RPC method
*/
registerMethod(name: string, handler: RpcMethodHandler): void
/**
* Get the extension's isolated database
*/
getDatabase(): ExtensionDatabase
/**
* Log a message (prefixed with extension ID)
*/
log(level: 'debug' | 'info' | 'warn' | 'error', message: string, ...args: any[]): void
}
/**
* HTTP route handler types
* Used by extensions that expose HTTP endpoints (e.g. LNURL, .well-known)
*/
export interface HttpRequest {
method: string
path: string
params: Record<string, string>
query: Record<string, string>
headers: Record<string, string>
body?: any
}
export interface HttpResponse {
status: number
body: any
headers?: Record<string, string>
}
export interface HttpRoute {
method: 'GET' | 'POST'
path: string
handler: (req: HttpRequest) => Promise<HttpResponse>
}
/**
* Extension interface - what extensions must implement
*/
export interface Extension {
/**
* Extension metadata
*/
readonly info: ExtensionInfo
/**
* Initialize the extension
* Called once when the extension is loaded
*/
initialize(ctx: ExtensionContext, db: ExtensionDatabase): Promise<void>
/**
* Shutdown the extension
* Called when Lightning.Pub is shutting down
*/
shutdown?(): Promise<void>
/**
* Health check
* Return true if extension is healthy
*/
healthCheck?(): Promise<boolean>
/**
* Get HTTP routes exposed by this extension
* The main HTTP server will mount these routes
*/
getHttpRoutes?(): HttpRoute[]
}
/**
* Extension constructor type
*/
export type ExtensionConstructor = new () => Extension
/**
* Extension module default export
*/
export interface ExtensionModule {
default: ExtensionConstructor
}
/**
* Loaded extension state
*/
export interface LoadedExtension {
info: ExtensionInfo
instance: Extension
database: ExtensionDatabase
status: 'loading' | 'ready' | 'error' | 'stopped'
error?: Error
loadedAt: number
}
/**
* Extension loader configuration
*/
export interface ExtensionLoaderConfig {
extensionsDir: string // Directory containing extensions
databaseDir: string // Directory for extension databases
enabledExtensions?: string[] // If set, only load these extensions
disabledExtensions?: string[] // Extensions to skip
}

View file

@ -0,0 +1,383 @@
/**
* LNURL-withdraw Extension for Lightning.Pub
*
* Implements LUD-03 (LNURL-withdraw) for creating withdraw links
* that allow anyone to pull funds from a Lightning wallet.
*
* Use cases:
* - Quick vouchers (batch single-use codes)
* - Faucets
* - Gift cards / prepaid cards
* - Tips / donations
*/
import {
Extension,
ExtensionInfo,
ExtensionContext,
ExtensionDatabase,
CreateWithdrawLinkRequest,
UpdateWithdrawLinkRequest,
HttpRoute,
HttpRequest,
HttpResponse
} from './types.js'
import { runMigrations } from './migrations.js'
import { WithdrawManager } from './managers/withdrawManager.js'
/**
* LNURL-withdraw Extension
*/
export default class WithdrawExtension implements Extension {
readonly info: ExtensionInfo = {
id: 'withdraw',
name: 'LNURL Withdraw',
version: '1.0.0',
description: 'Create withdraw links for vouchers, faucets, and gifts (LUD-03)',
author: 'Lightning.Pub',
minPubVersion: '1.0.0'
}
private manager!: WithdrawManager
private baseUrl: string = ''
/**
* Initialize the extension
*/
async initialize(ctx: ExtensionContext, db: ExtensionDatabase): Promise<void> {
// Run migrations
await runMigrations(db)
// Initialize manager
this.manager = new WithdrawManager(db, ctx)
// Register RPC methods
this.registerRpcMethods(ctx)
// Register HTTP routes for LNURL protocol
this.registerHttpRoutes(ctx)
ctx.log('info', 'Extension initialized')
}
/**
* Shutdown the extension
*/
async shutdown(): Promise<void> {
// Cleanup if needed
}
/**
* Set the base URL for LNURL generation
* This should be called by the main application after loading
*/
setBaseUrl(url: string): void {
this.baseUrl = url
this.manager.setBaseUrl(url)
}
/**
* Get HTTP routes for this extension
* These need to be mounted by the main HTTP server
*/
getHttpRoutes(): HttpRoute[] {
return [
// Create withdraw link (HTTP API for ATM/external integrations)
{
method: 'POST',
path: '/api/v1/withdraw/create',
handler: this.handleCreateWithdrawLink.bind(this)
},
// LNURL callback (user submits invoice) - MUST be before :unique_hash routes
{
method: 'GET',
path: '/api/v1/lnurl/cb/:unique_hash',
handler: this.handleLnurlCallback.bind(this)
},
// Initial LNURL request (unique link with use hash)
{
method: 'GET',
path: '/api/v1/lnurl/:unique_hash/:id_unique_hash',
handler: this.handleLnurlUniqueRequest.bind(this)
},
// Initial LNURL request (simple link) - MUST be last (catches all)
{
method: 'GET',
path: '/api/v1/lnurl/:unique_hash',
handler: this.handleLnurlRequest.bind(this)
}
]
}
/**
* Register RPC methods with the extension context
*/
private registerRpcMethods(ctx: ExtensionContext): void {
// Create withdraw link
ctx.registerMethod('withdraw.createLink', async (req, appId, userPubkey) => {
const link = await this.manager.create(appId, req as CreateWithdrawLinkRequest, userPubkey)
const stats = await this.manager.getWithdrawalStats(link.id)
return {
link,
total_withdrawn_sats: stats.total_sats,
withdrawals_count: stats.count
}
})
// Create quick vouchers
ctx.registerMethod('withdraw.createVouchers', async (req, appId) => {
const vouchers = await this.manager.createVouchers(
appId,
req.title,
req.amount,
req.count,
req.description
)
return {
vouchers,
total_amount_sats: req.amount * req.count
}
})
// Get withdraw link
ctx.registerMethod('withdraw.getLink', async (req, appId) => {
const link = await this.manager.get(req.id, appId)
if (!link) throw new Error('Withdraw link not found')
const stats = await this.manager.getWithdrawalStats(link.id)
return {
link,
total_withdrawn_sats: stats.total_sats,
withdrawals_count: stats.count
}
})
// List withdraw links
ctx.registerMethod('withdraw.listLinks', async (req, appId) => {
const links = await this.manager.list(
appId,
req.include_spent || false,
req.limit,
req.offset
)
return { links }
})
// Update withdraw link
ctx.registerMethod('withdraw.updateLink', async (req, appId) => {
const link = await this.manager.update(req.id, appId, req as UpdateWithdrawLinkRequest)
if (!link) throw new Error('Withdraw link not found')
const stats = await this.manager.getWithdrawalStats(link.id)
return {
link,
total_withdrawn_sats: stats.total_sats,
withdrawals_count: stats.count
}
})
// Delete withdraw link
ctx.registerMethod('withdraw.deleteLink', async (req, appId) => {
const success = await this.manager.delete(req.id, appId)
if (!success) throw new Error('Withdraw link not found')
return { success }
})
// List withdrawals
ctx.registerMethod('withdraw.listWithdrawals', async (req, appId) => {
const withdrawals = await this.manager.listWithdrawals(
appId,
req.link_id,
req.limit,
req.offset
)
return { withdrawals }
})
// Get withdrawal stats
ctx.registerMethod('withdraw.getStats', async (req, appId) => {
// Get all links to calculate total stats
const links = await this.manager.list(appId, true)
let totalLinks = links.length
let activeLinks = 0
let spentLinks = 0
let totalWithdrawn = 0
let totalWithdrawals = 0
for (const link of links) {
if (link.used >= link.uses) {
spentLinks++
} else {
activeLinks++
}
const stats = await this.manager.getWithdrawalStats(link.id)
totalWithdrawn += stats.total_sats
totalWithdrawals += stats.count
}
return {
total_links: totalLinks,
active_links: activeLinks,
spent_links: spentLinks,
total_withdrawn_sats: totalWithdrawn,
total_withdrawals: totalWithdrawals
}
})
}
/**
* Register HTTP routes (called by extension context)
*/
private registerHttpRoutes(ctx: ExtensionContext): void {
// HTTP routes are exposed via getHttpRoutes()
// The main application is responsible for mounting them
ctx.log('debug', 'HTTP routes registered for LNURL protocol')
}
// =========================================================================
// HTTP Route Handlers
// =========================================================================
/**
* Handle create withdraw link request (HTTP API)
* POST /api/v1/withdraw/create
*
* Body: {
* title: string
* min_withdrawable: number (sats)
* max_withdrawable: number (sats)
* uses?: number (defaults to 1)
* wait_time?: number (seconds between uses, defaults to 0)
* }
*
* Auth: Bearer token in Authorization header (app_<app_id>)
*
* Returns: {
* link: { lnurl, unique_hash, id, ... }
* }
*/
private async handleCreateWithdrawLink(req: HttpRequest): Promise<HttpResponse> {
try {
const { title, min_withdrawable, max_withdrawable, uses, wait_time } = req.body
// Extract app_id from Authorization header (Bearer app_<app_id>)
const authHeader = req.headers?.authorization || req.headers?.Authorization || ''
let app_id = 'default'
if (authHeader.startsWith('Bearer app_')) {
app_id = authHeader.replace('Bearer app_', '')
}
if (!title || !min_withdrawable) {
return {
status: 400,
body: { status: 'ERROR', reason: 'Missing required fields: title, min_withdrawable' },
headers: { 'Content-Type': 'application/json' }
}
}
const link = await this.manager.create(app_id, {
title,
min_withdrawable,
max_withdrawable: max_withdrawable || min_withdrawable,
uses: uses || 1,
wait_time: wait_time || 0,
is_unique: false // Simple single-use links for ATM
})
// Return in format expected by ATM client
return {
status: 200,
body: {
status: 'OK',
link: {
lnurl: link.lnurl,
unique_hash: link.unique_hash,
id: link.id,
title: link.title,
min_withdrawable: link.min_withdrawable,
max_withdrawable: link.max_withdrawable,
uses: link.uses,
used: link.used
}
},
headers: { 'Content-Type': 'application/json' }
}
} catch (error: any) {
return {
status: 500,
body: { status: 'ERROR', reason: error.message },
headers: { 'Content-Type': 'application/json' }
}
}
}
/**
* Handle initial LNURL request (simple link)
* GET /api/v1/lnurl/:unique_hash
*/
private async handleLnurlRequest(req: HttpRequest): Promise<HttpResponse> {
const { unique_hash } = req.params
const result = await this.manager.handleLnurlRequest(unique_hash)
return {
status: 200,
body: result,
headers: {
'Content-Type': 'application/json'
}
}
}
/**
* Handle initial LNURL request (unique link)
* GET /api/v1/lnurl/:unique_hash/:id_unique_hash
*/
private async handleLnurlUniqueRequest(req: HttpRequest): Promise<HttpResponse> {
const { unique_hash, id_unique_hash } = req.params
const result = await this.manager.handleLnurlRequest(unique_hash, id_unique_hash)
return {
status: 200,
body: result,
headers: {
'Content-Type': 'application/json'
}
}
}
/**
* Handle LNURL callback (user submits invoice)
* GET /api/v1/lnurl/cb/:unique_hash?k1=...&pr=...&id_unique_hash=...
*/
private async handleLnurlCallback(req: HttpRequest): Promise<HttpResponse> {
const { unique_hash } = req.params
const { k1, pr, id_unique_hash } = req.query
if (!k1 || !pr) {
return {
status: 200,
body: { status: 'ERROR', reason: 'Missing k1 or pr parameter' },
headers: { 'Content-Type': 'application/json' }
}
}
const result = await this.manager.handleLnurlCallback(unique_hash, {
k1,
pr,
id_unique_hash
})
return {
status: 200,
body: result,
headers: {
'Content-Type': 'application/json'
}
}
}
}
// Export types for external use
export * from './types.js'
export { WithdrawManager } from './managers/withdrawManager.js'

View file

@ -0,0 +1,717 @@
/**
* Withdraw Link Manager
*
* Handles CRUD operations for withdraw links and processes withdrawals
*/
import {
ExtensionContext,
ExtensionDatabase,
WithdrawLink,
Withdrawal,
CreateWithdrawLinkRequest,
UpdateWithdrawLinkRequest,
WithdrawLinkWithLnurl,
LnurlWithdrawResponse,
LnurlErrorResponse,
LnurlSuccessResponse,
LnurlCallbackParams
} from '../types.js'
import {
generateId,
generateK1,
generateUniqueHash,
generateUseHash,
verifyUseHash,
encodeLnurl,
buildLnurlUrl,
buildUniqueLnurlUrl,
buildCallbackUrl,
satsToMsats
} from '../utils/lnurl.js'
/**
* Database row types
*/
interface WithdrawLinkRow {
id: string
application_id: string
title: string
description: string | null
min_withdrawable: number
max_withdrawable: number
uses: number
used: number
wait_time: number
unique_hash: string
k1: string
is_unique: number
uses_csv: string
open_time: number
creator_pubkey: string | null
webhook_url: string | null
webhook_headers: string | null
webhook_body: string | null
created_at: number
updated_at: number
}
interface WithdrawalRow {
id: string
link_id: string
application_id: string
payment_hash: string
amount_sats: number
fee_sats: number
recipient_node: string | null
webhook_success: number | null
webhook_response: string | null
created_at: number
}
/**
* Convert row to WithdrawLink
*/
function rowToLink(row: WithdrawLinkRow): WithdrawLink {
return {
id: row.id,
application_id: row.application_id,
title: row.title,
description: row.description || undefined,
min_withdrawable: row.min_withdrawable,
max_withdrawable: row.max_withdrawable,
uses: row.uses,
used: row.used,
wait_time: row.wait_time,
unique_hash: row.unique_hash,
k1: row.k1,
is_unique: row.is_unique === 1,
uses_csv: row.uses_csv,
open_time: row.open_time,
creator_pubkey: row.creator_pubkey || undefined,
webhook_url: row.webhook_url || undefined,
webhook_headers: row.webhook_headers || undefined,
webhook_body: row.webhook_body || undefined,
created_at: row.created_at,
updated_at: row.updated_at
}
}
/**
* Convert row to Withdrawal
*/
function rowToWithdrawal(row: WithdrawalRow): Withdrawal {
return {
id: row.id,
link_id: row.link_id,
application_id: row.application_id,
payment_hash: row.payment_hash,
amount_sats: row.amount_sats,
fee_sats: row.fee_sats,
recipient_node: row.recipient_node || undefined,
webhook_success: row.webhook_success === null ? undefined : row.webhook_success === 1,
webhook_response: row.webhook_response || undefined,
created_at: row.created_at
}
}
/**
* WithdrawManager - Handles withdraw link operations
*/
export class WithdrawManager {
private baseUrl: string = ''
constructor(
private db: ExtensionDatabase,
private ctx: ExtensionContext
) {}
/**
* Set the base URL for LNURL generation
*/
setBaseUrl(url: string): void {
this.baseUrl = url.replace(/\/$/, '')
}
/**
* Add LNURL to a withdraw link
*/
private addLnurl(link: WithdrawLink): WithdrawLinkWithLnurl {
const lnurlUrl = buildLnurlUrl(this.baseUrl, link.unique_hash)
return {
...link,
lnurl: encodeLnurl(lnurlUrl),
lnurl_url: lnurlUrl
}
}
// =========================================================================
// CRUD Operations
// =========================================================================
/**
* Create a new withdraw link
*/
async create(applicationId: string, req: CreateWithdrawLinkRequest, creatorPubkey?: string): Promise<WithdrawLinkWithLnurl> {
// Validation
if (req.uses < 1 || req.uses > 250) {
throw new Error('Uses must be between 1 and 250')
}
if (req.min_withdrawable < 1) {
throw new Error('Min withdrawable must be at least 1 sat')
}
if (req.max_withdrawable < req.min_withdrawable) {
throw new Error('Max withdrawable must be >= min withdrawable')
}
if (req.wait_time < 0) {
throw new Error('Wait time cannot be negative')
}
// Validate webhook JSON if provided
if (req.webhook_headers) {
try {
JSON.parse(req.webhook_headers)
} catch {
throw new Error('webhook_headers must be valid JSON')
}
}
if (req.webhook_body) {
try {
JSON.parse(req.webhook_body)
} catch {
throw new Error('webhook_body must be valid JSON')
}
}
const now = Math.floor(Date.now() / 1000)
const id = generateId()
const usesCsv = Array.from({ length: req.uses }, (_, i) => String(i)).join(',')
const link: WithdrawLink = {
id,
application_id: applicationId,
title: req.title.trim(),
description: req.description?.trim(),
min_withdrawable: req.min_withdrawable,
max_withdrawable: req.max_withdrawable,
uses: req.uses,
used: 0,
wait_time: req.wait_time,
unique_hash: generateUniqueHash(),
k1: generateK1(),
is_unique: req.is_unique || false,
uses_csv: usesCsv,
open_time: now,
creator_pubkey: creatorPubkey,
webhook_url: req.webhook_url,
webhook_headers: req.webhook_headers,
webhook_body: req.webhook_body,
created_at: now,
updated_at: now
}
await this.db.execute(
`INSERT INTO withdraw_links (
id, application_id, title, description,
min_withdrawable, max_withdrawable, uses, used, wait_time,
unique_hash, k1, is_unique, uses_csv, open_time,
creator_pubkey,
webhook_url, webhook_headers, webhook_body,
created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
link.id, link.application_id, link.title, link.description || null,
link.min_withdrawable, link.max_withdrawable, link.uses, link.used, link.wait_time,
link.unique_hash, link.k1, link.is_unique ? 1 : 0, link.uses_csv, link.open_time,
link.creator_pubkey || null,
link.webhook_url || null, link.webhook_headers || null, link.webhook_body || null,
link.created_at, link.updated_at
]
)
return this.addLnurl(link)
}
/**
* Create multiple vouchers (single-use withdraw links)
*/
async createVouchers(
applicationId: string,
title: string,
amount: number,
count: number,
description?: string
): Promise<WithdrawLinkWithLnurl[]> {
if (count < 1 || count > 100) {
throw new Error('Count must be between 1 and 100')
}
if (amount < 1) {
throw new Error('Amount must be at least 1 sat')
}
const vouchers: WithdrawLinkWithLnurl[] = []
for (let i = 0; i < count; i++) {
const voucher = await this.create(applicationId, {
title: `${title} #${i + 1}`,
description,
min_withdrawable: amount,
max_withdrawable: amount,
uses: 1,
wait_time: 0,
is_unique: false
})
vouchers.push(voucher)
}
return vouchers
}
/**
* Get a withdraw link by ID
*/
async get(id: string, applicationId: string): Promise<WithdrawLinkWithLnurl | null> {
const rows = await this.db.query<WithdrawLinkRow>(
'SELECT * FROM withdraw_links WHERE id = ? AND application_id = ?',
[id, applicationId]
)
if (rows.length === 0) return null
return this.addLnurl(rowToLink(rows[0]))
}
/**
* Get a withdraw link by unique hash (for LNURL)
*/
async getByHash(uniqueHash: string): Promise<WithdrawLink | null> {
const rows = await this.db.query<WithdrawLinkRow>(
'SELECT * FROM withdraw_links WHERE unique_hash = ?',
[uniqueHash]
)
if (rows.length === 0) return null
return rowToLink(rows[0])
}
/**
* List withdraw links for an application
*/
async list(
applicationId: string,
includeSpent: boolean = false,
limit?: number,
offset?: number
): Promise<WithdrawLinkWithLnurl[]> {
let sql = 'SELECT * FROM withdraw_links WHERE application_id = ?'
const params: any[] = [applicationId]
if (!includeSpent) {
sql += ' AND used < uses'
}
sql += ' ORDER BY created_at DESC'
if (limit) {
sql += ' LIMIT ?'
params.push(limit)
if (offset) {
sql += ' OFFSET ?'
params.push(offset)
}
}
const rows = await this.db.query<WithdrawLinkRow>(sql, params)
return rows.map(row => this.addLnurl(rowToLink(row)))
}
/**
* Update a withdraw link
*/
async update(
id: string,
applicationId: string,
req: UpdateWithdrawLinkRequest
): Promise<WithdrawLinkWithLnurl | null> {
const existing = await this.get(id, applicationId)
if (!existing) return null
// Validation
if (req.uses !== undefined) {
if (req.uses < 1 || req.uses > 250) {
throw new Error('Uses must be between 1 and 250')
}
if (req.uses < existing.used) {
throw new Error('Cannot reduce uses below current used count')
}
}
const minWith = req.min_withdrawable ?? existing.min_withdrawable
const maxWith = req.max_withdrawable ?? existing.max_withdrawable
if (minWith < 1) {
throw new Error('Min withdrawable must be at least 1 sat')
}
if (maxWith < minWith) {
throw new Error('Max withdrawable must be >= min withdrawable')
}
// Handle uses change
let usesCsv = existing.uses_csv
const newUses = req.uses ?? existing.uses
if (newUses !== existing.uses) {
const currentUses = usesCsv.split(',').filter(u => u !== '')
if (newUses > existing.uses) {
// Add more uses
const lastNum = currentUses.length > 0 ? parseInt(currentUses[currentUses.length - 1], 10) : -1
for (let i = lastNum + 1; currentUses.length < (newUses - existing.used); i++) {
currentUses.push(String(i))
}
} else {
// Remove uses (keep first N)
usesCsv = currentUses.slice(0, newUses - existing.used).join(',')
}
usesCsv = currentUses.join(',')
}
const now = Math.floor(Date.now() / 1000)
await this.db.execute(
`UPDATE withdraw_links SET
title = ?, description = ?,
min_withdrawable = ?, max_withdrawable = ?,
uses = ?, wait_time = ?, is_unique = ?, uses_csv = ?,
webhook_url = ?, webhook_headers = ?, webhook_body = ?,
updated_at = ?
WHERE id = ? AND application_id = ?`,
[
req.title ?? existing.title,
req.description ?? existing.description ?? null,
minWith, maxWith,
newUses,
req.wait_time ?? existing.wait_time,
(req.is_unique ?? existing.is_unique) ? 1 : 0,
usesCsv,
req.webhook_url ?? existing.webhook_url ?? null,
req.webhook_headers ?? existing.webhook_headers ?? null,
req.webhook_body ?? existing.webhook_body ?? null,
now,
id, applicationId
]
)
return this.get(id, applicationId)
}
/**
* Delete a withdraw link
*/
async delete(id: string, applicationId: string): Promise<boolean> {
const result = await this.db.execute(
'DELETE FROM withdraw_links WHERE id = ? AND application_id = ?',
[id, applicationId]
)
return (result.changes || 0) > 0
}
// =========================================================================
// LNURL Protocol Handlers
// =========================================================================
/**
* Handle initial LNURL request (user scans QR)
* Returns withdraw parameters
*/
async handleLnurlRequest(
uniqueHash: string,
idUniqueHash?: string
): Promise<LnurlWithdrawResponse | LnurlErrorResponse> {
const link = await this.getByHash(uniqueHash)
if (!link) {
return { status: 'ERROR', reason: 'Withdraw link does not exist.' }
}
if (link.used >= link.uses) {
return { status: 'ERROR', reason: 'Withdraw link is spent.' }
}
// For unique links, require id_unique_hash
if (link.is_unique && !idUniqueHash) {
return { status: 'ERROR', reason: 'This link requires a unique hash.' }
}
// Verify unique hash if provided
if (idUniqueHash) {
const useNumber = verifyUseHash(link.id, link.unique_hash, link.uses_csv, idUniqueHash)
if (!useNumber) {
return { status: 'ERROR', reason: 'Invalid unique hash.' }
}
}
const callbackUrl = buildCallbackUrl(this.baseUrl, link.unique_hash)
return {
tag: 'withdrawRequest',
callback: idUniqueHash ? `${callbackUrl}?id_unique_hash=${idUniqueHash}` : callbackUrl,
k1: link.k1,
minWithdrawable: satsToMsats(link.min_withdrawable),
maxWithdrawable: satsToMsats(link.max_withdrawable),
defaultDescription: link.title
}
}
/**
* Handle LNURL callback (user submits invoice)
* Pays the invoice and records the withdrawal
*/
async handleLnurlCallback(
uniqueHash: string,
params: LnurlCallbackParams
): Promise<LnurlSuccessResponse | LnurlErrorResponse> {
const link = await this.getByHash(uniqueHash)
if (!link) {
return { status: 'ERROR', reason: 'Withdraw link not found.' }
}
if (link.used >= link.uses) {
return { status: 'ERROR', reason: 'Withdraw link is spent.' }
}
if (link.k1 !== params.k1) {
return { status: 'ERROR', reason: 'Invalid k1.' }
}
// Check wait time
const now = Math.floor(Date.now() / 1000)
if (now < link.open_time) {
const waitSecs = link.open_time - now
return { status: 'ERROR', reason: `Please wait ${waitSecs} seconds.` }
}
// For unique links, verify and consume the use hash
if (params.id_unique_hash) {
const useNumber = verifyUseHash(link.id, link.unique_hash, link.uses_csv, params.id_unique_hash)
if (!useNumber) {
return { status: 'ERROR', reason: 'Invalid unique hash.' }
}
} else if (link.is_unique) {
return { status: 'ERROR', reason: 'Unique hash required.' }
}
// Prevent double-spending with hash check
try {
await this.createHashCheck(params.id_unique_hash || uniqueHash, params.k1)
} catch {
return { status: 'ERROR', reason: 'Withdrawal already in progress.' }
}
try {
// Pay the invoice from the creator's balance (if created via Nostr RPC)
const payment = await this.ctx.payInvoice(
link.application_id,
params.pr,
link.max_withdrawable,
link.creator_pubkey
)
// Record the withdrawal
await this.recordWithdrawal(link, payment.paymentHash, link.max_withdrawable, payment.feeSats)
// Increment usage
await this.incrementUsage(link, params.id_unique_hash)
// Clean up hash check
await this.deleteHashCheck(params.id_unique_hash || uniqueHash)
// Dispatch webhook if configured
if (link.webhook_url) {
this.dispatchWebhook(link, payment.paymentHash, params.pr).catch(err => {
console.error('[Withdraw] Webhook error:', err)
})
}
return { status: 'OK' }
} catch (err: any) {
// Clean up hash check on failure
await this.deleteHashCheck(params.id_unique_hash || uniqueHash)
return { status: 'ERROR', reason: `Payment failed: ${err.message}` }
}
}
// =========================================================================
// Helper Methods
// =========================================================================
/**
* Increment link usage and update open_time
*/
private async incrementUsage(link: WithdrawLink, idUniqueHash?: string): Promise<void> {
const now = Math.floor(Date.now() / 1000)
let usesCsv = link.uses_csv
// Remove used hash from uses_csv if unique
if (idUniqueHash) {
const uses = usesCsv.split(',').filter(u => {
const hash = generateUseHash(link.id, link.unique_hash, u.trim())
return hash !== idUniqueHash
})
usesCsv = uses.join(',')
}
await this.db.execute(
`UPDATE withdraw_links SET
used = used + 1,
open_time = ?,
uses_csv = ?,
updated_at = ?
WHERE id = ?`,
[now + link.wait_time, usesCsv, now, link.id]
)
}
/**
* Record a successful withdrawal
*/
private async recordWithdrawal(
link: WithdrawLink,
paymentHash: string,
amountSats: number,
feeSats: number
): Promise<void> {
const now = Math.floor(Date.now() / 1000)
await this.db.execute(
`INSERT INTO withdrawals (
id, link_id, application_id,
payment_hash, amount_sats, fee_sats,
created_at
) VALUES (?, ?, ?, ?, ?, ?, ?)`,
[
generateId(),
link.id,
link.application_id,
paymentHash,
amountSats,
feeSats,
now
]
)
}
/**
* Create hash check to prevent double-spending
*/
private async createHashCheck(hash: string, k1: string): Promise<void> {
const now = Math.floor(Date.now() / 1000)
await this.db.execute(
'INSERT INTO hash_checks (hash, k1, created_at) VALUES (?, ?, ?)',
[hash, k1, now]
)
}
/**
* Delete hash check after completion
*/
private async deleteHashCheck(hash: string): Promise<void> {
await this.db.execute('DELETE FROM hash_checks WHERE hash = ?', [hash])
}
/**
* List withdrawals
*/
async listWithdrawals(
applicationId: string,
linkId?: string,
limit?: number,
offset?: number
): Promise<Withdrawal[]> {
let sql = 'SELECT * FROM withdrawals WHERE application_id = ?'
const params: any[] = [applicationId]
if (linkId) {
sql += ' AND link_id = ?'
params.push(linkId)
}
sql += ' ORDER BY created_at DESC'
if (limit) {
sql += ' LIMIT ?'
params.push(limit)
if (offset) {
sql += ' OFFSET ?'
params.push(offset)
}
}
const rows = await this.db.query<WithdrawalRow>(sql, params)
return rows.map(rowToWithdrawal)
}
/**
* Get withdrawal stats for a link
*/
async getWithdrawalStats(linkId: string): Promise<{ total_sats: number; count: number }> {
const result = await this.db.query<{ total: number; count: number }>(
`SELECT COALESCE(SUM(amount_sats), 0) as total, COUNT(*) as count
FROM withdrawals WHERE link_id = ?`,
[linkId]
)
return {
total_sats: result[0]?.total || 0,
count: result[0]?.count || 0
}
}
/**
* Dispatch webhook notification
*/
private async dispatchWebhook(
link: WithdrawLink,
paymentHash: string,
paymentRequest: string
): Promise<void> {
if (!link.webhook_url) return
try {
const headers: Record<string, string> = {
'Content-Type': 'application/json'
}
if (link.webhook_headers) {
Object.assign(headers, JSON.parse(link.webhook_headers))
}
const body = {
payment_hash: paymentHash,
payment_request: paymentRequest,
lnurlw: link.id,
body: link.webhook_body ? JSON.parse(link.webhook_body) : {}
}
const response = await fetch(link.webhook_url, {
method: 'POST',
headers,
body: JSON.stringify(body)
})
// Update withdrawal record with webhook result
await this.db.execute(
`UPDATE withdrawals SET
webhook_success = ?,
webhook_response = ?
WHERE payment_hash = ?`,
[response.ok ? 1 : 0, await response.text(), paymentHash]
)
} catch (err: any) {
await this.db.execute(
`UPDATE withdrawals SET
webhook_success = 0,
webhook_response = ?
WHERE payment_hash = ?`,
[err.message, paymentHash]
)
}
}
}

View file

@ -0,0 +1,164 @@
/**
* LNURL-withdraw Extension Database Migrations
*/
import { ExtensionDatabase } from '../types.js'
export interface Migration {
version: number
name: string
up: (db: ExtensionDatabase) => Promise<void>
down?: (db: ExtensionDatabase) => Promise<void>
}
export const migrations: Migration[] = [
{
version: 1,
name: 'create_withdraw_links_table',
up: async (db: ExtensionDatabase) => {
await db.execute(`
CREATE TABLE IF NOT EXISTS withdraw_links (
id TEXT PRIMARY KEY,
application_id TEXT NOT NULL,
-- Display
title TEXT NOT NULL,
description TEXT,
-- Amounts (sats)
min_withdrawable INTEGER NOT NULL,
max_withdrawable INTEGER NOT NULL,
-- Usage limits
uses INTEGER NOT NULL DEFAULT 1,
used INTEGER NOT NULL DEFAULT 0,
wait_time INTEGER NOT NULL DEFAULT 0,
-- Security
unique_hash TEXT NOT NULL UNIQUE,
k1 TEXT NOT NULL,
is_unique INTEGER NOT NULL DEFAULT 0,
uses_csv TEXT NOT NULL DEFAULT '',
-- Rate limiting
open_time INTEGER NOT NULL DEFAULT 0,
-- Webhooks
webhook_url TEXT,
webhook_headers TEXT,
webhook_body TEXT,
-- Timestamps
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`)
// Index for looking up by unique_hash (LNURL)
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_withdraw_links_unique_hash
ON withdraw_links(unique_hash)
`)
// Index for listing by application
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_withdraw_links_application
ON withdraw_links(application_id, created_at DESC)
`)
}
},
{
version: 2,
name: 'create_withdrawals_table',
up: async (db: ExtensionDatabase) => {
await db.execute(`
CREATE TABLE IF NOT EXISTS withdrawals (
id TEXT PRIMARY KEY,
link_id TEXT NOT NULL,
application_id TEXT NOT NULL,
-- Payment details
payment_hash TEXT NOT NULL,
amount_sats INTEGER NOT NULL,
fee_sats INTEGER NOT NULL DEFAULT 0,
-- Recipient
recipient_node TEXT,
-- Webhook result
webhook_success INTEGER,
webhook_response TEXT,
-- Timestamp
created_at INTEGER NOT NULL,
FOREIGN KEY (link_id) REFERENCES withdraw_links(id) ON DELETE CASCADE
)
`)
// Index for listing withdrawals by link
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_withdrawals_link
ON withdrawals(link_id, created_at DESC)
`)
// Index for looking up by payment hash
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_withdrawals_payment_hash
ON withdrawals(payment_hash)
`)
}
},
{
version: 3,
name: 'create_hash_checks_table',
up: async (db: ExtensionDatabase) => {
// Temporary table to prevent double-spending during payment processing
await db.execute(`
CREATE TABLE IF NOT EXISTS hash_checks (
hash TEXT PRIMARY KEY,
k1 TEXT NOT NULL,
created_at INTEGER NOT NULL
)
`)
}
},
{
version: 4,
name: 'add_creator_pubkey_column',
up: async (db: ExtensionDatabase) => {
// Store the Nostr pubkey of the user who created the withdraw link
// so that when the LNURL callback fires, we debit the correct user's balance
await db.execute(`
ALTER TABLE withdraw_links ADD COLUMN creator_pubkey TEXT
`)
}
}
]
/**
* Run all pending migrations
*/
export async function runMigrations(db: ExtensionDatabase): Promise<void> {
// Get current version
const versionResult = await db.query<{ value: string }>(
`SELECT value FROM _extension_meta WHERE key = 'migration_version'`
).catch(() => [])
const currentVersion = versionResult.length > 0 ? parseInt(versionResult[0].value, 10) : 0
// Run pending migrations
for (const migration of migrations) {
if (migration.version > currentVersion) {
console.log(`[Withdraw] Running migration ${migration.version}: ${migration.name}`)
await migration.up(db)
// Update version
await db.execute(
`INSERT INTO _extension_meta (key, value) VALUES ('migration_version', ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
[String(migration.version)]
)
}
}
}

View file

@ -0,0 +1,264 @@
/**
* LNURL-withdraw Extension Types
* Implements LUD-03 (LNURL-withdraw) for Lightning.Pub
*/
// Re-export base extension types
export {
Extension,
ExtensionInfo,
ExtensionContext,
ExtensionDatabase,
ApplicationInfo,
RpcMethodHandler
} from '../types.js'
// ============================================================================
// Core Data Types
// ============================================================================
/**
* A withdraw link that can be used to pull funds
*/
export interface WithdrawLink {
id: string
application_id: string
// Display
title: string
description?: string
// Amounts (in sats)
min_withdrawable: number
max_withdrawable: number
// Usage limits
uses: number // Total allowed uses
used: number // Times used so far
wait_time: number // Seconds between uses
// Security
unique_hash: string // For LNURL URL
k1: string // Challenge for callback
is_unique: boolean // Generate unique code per use
uses_csv: string // Comma-separated list of available use IDs
// Rate limiting
open_time: number // Unix timestamp when next use is allowed
// Creator identity (for Nostr RPC-created links)
creator_pubkey?: string // Nostr pubkey of the user who created this link
// Webhook notifications
webhook_url?: string
webhook_headers?: string // JSON string
webhook_body?: string // JSON string
// Timestamps
created_at: number
updated_at: number
}
/**
* Withdrawal record - tracks each successful withdrawal
*/
export interface Withdrawal {
id: string
link_id: string
application_id: string
// Payment details
payment_hash: string
amount_sats: number
fee_sats: number
// Recipient (if known)
recipient_node?: string
// Webhook result
webhook_success?: boolean
webhook_response?: string
// Timestamp
created_at: number
}
/**
* Hash check - prevents double-spending during payment
*/
export interface HashCheck {
hash: string
k1: string
created_at: number
}
// ============================================================================
// LNURL Protocol Types (LUD-03)
// ============================================================================
/**
* LNURL-withdraw response (first call)
* Returned when user scans the QR code
*/
export interface LnurlWithdrawResponse {
tag: 'withdrawRequest'
callback: string // URL to call with invoice
k1: string // Challenge
minWithdrawable: number // Millisats
maxWithdrawable: number // Millisats
defaultDescription: string
}
/**
* LNURL error response
*/
export interface LnurlErrorResponse {
status: 'ERROR'
reason: string
}
/**
* LNURL success response
*/
export interface LnurlSuccessResponse {
status: 'OK'
}
// ============================================================================
// RPC Request/Response Types
// ============================================================================
/**
* Create a new withdraw link
*/
export interface CreateWithdrawLinkRequest {
title: string
description?: string
min_withdrawable: number // sats
max_withdrawable: number // sats
uses: number // 1-250
wait_time: number // seconds between uses
is_unique?: boolean // generate unique code per use
webhook_url?: string
webhook_headers?: string // JSON
webhook_body?: string // JSON
}
/**
* Update an existing withdraw link
*/
export interface UpdateWithdrawLinkRequest {
id: string
title?: string
description?: string
min_withdrawable?: number
max_withdrawable?: number
uses?: number
wait_time?: number
is_unique?: boolean
webhook_url?: string
webhook_headers?: string
webhook_body?: string
}
/**
* Get withdraw link by ID
*/
export interface GetWithdrawLinkRequest {
id: string
}
/**
* List withdraw links
*/
export interface ListWithdrawLinksRequest {
include_spent?: boolean // Include fully used links
limit?: number
offset?: number
}
/**
* Delete withdraw link
*/
export interface DeleteWithdrawLinkRequest {
id: string
}
/**
* Create quick vouchers (batch of single-use links)
*/
export interface CreateVouchersRequest {
title: string
amount: number // sats per voucher
count: number // number of vouchers (1-100)
description?: string
}
/**
* Get withdraw link with LNURL
*/
export interface WithdrawLinkWithLnurl extends WithdrawLink {
lnurl: string // bech32 encoded LNURL
lnurl_url: string // raw callback URL
}
/**
* List withdrawals for a link
*/
export interface ListWithdrawalsRequest {
link_id?: string
limit?: number
offset?: number
}
/**
* Withdraw link response with stats
*/
export interface WithdrawLinkResponse {
link: WithdrawLinkWithLnurl
total_withdrawn_sats: number
withdrawals_count: number
}
/**
* Vouchers response
*/
export interface VouchersResponse {
vouchers: WithdrawLinkWithLnurl[]
total_amount_sats: number
}
// ============================================================================
// HTTP Handler Types
// ============================================================================
/**
* LNURL callback parameters
*/
export interface LnurlCallbackParams {
k1: string // Challenge from initial response
pr: string // Payment request (BOLT11 invoice)
id_unique_hash?: string // For unique links
}
/**
* HTTP route handler
*/
export interface HttpRoute {
method: 'GET' | 'POST'
path: string
handler: (req: HttpRequest) => Promise<HttpResponse>
}
export interface HttpRequest {
params: Record<string, string>
query: Record<string, string>
body?: any
headers: Record<string, string>
}
export interface HttpResponse {
status: number
body: any
headers?: Record<string, string>
}

View file

@ -0,0 +1,131 @@
/**
* LNURL Encoding Utilities
*
* LNURL is a bech32-encoded URL with hrp "lnurl"
* See: https://github.com/lnurl/luds
*/
import { bech32 } from 'bech32'
import crypto from 'crypto'
/**
* Encode a URL as LNURL (bech32)
*/
export function encodeLnurl(url: string): string {
const words = bech32.toWords(Buffer.from(url, 'utf8'))
return bech32.encode('lnurl', words, 2000) // 2000 char limit for URLs
}
/**
* Decode an LNURL to a URL
*/
export function decodeLnurl(lnurl: string): string {
const { prefix, words } = bech32.decode(lnurl, 2000)
if (prefix !== 'lnurl') {
throw new Error('Invalid LNURL prefix')
}
return Buffer.from(bech32.fromWords(words)).toString('utf8')
}
/**
* Generate a URL-safe random ID
*/
export function generateId(length: number = 22): string {
const bytes = crypto.randomBytes(Math.ceil(length * 3 / 4))
return bytes.toString('base64url').slice(0, length)
}
/**
* Generate a k1 challenge (32 bytes hex)
*/
export function generateK1(): string {
return crypto.randomBytes(32).toString('hex')
}
/**
* Generate a unique hash for a link
*/
export function generateUniqueHash(): string {
return generateId(32)
}
/**
* Generate a unique hash for a specific use of a link
* This creates a deterministic hash based on link ID, unique_hash, and use number
*/
export function generateUseHash(linkId: string, uniqueHash: string, useNumber: string): string {
const data = `${linkId}${uniqueHash}${useNumber}`
return crypto.createHash('sha256').update(data).digest('hex').slice(0, 32)
}
/**
* Verify a use hash matches one of the available uses
*/
export function verifyUseHash(
linkId: string,
uniqueHash: string,
usesCsv: string,
providedHash: string
): string | null {
const uses = usesCsv.split(',').filter(u => u.trim() !== '')
for (const useNumber of uses) {
const expectedHash = generateUseHash(linkId, uniqueHash, useNumber.trim())
if (expectedHash === providedHash) {
return useNumber.trim()
}
}
return null
}
/**
* Build the LNURL callback URL for a withdraw link
*/
export function buildLnurlUrl(baseUrl: string, uniqueHash: string): string {
// Remove trailing slash from baseUrl
const base = baseUrl.replace(/\/$/, '')
return `${base}/api/v1/lnurl/${uniqueHash}`
}
/**
* Build the LNURL callback URL for a unique withdraw link
*/
export function buildUniqueLnurlUrl(
baseUrl: string,
uniqueHash: string,
useHash: string
): string {
const base = baseUrl.replace(/\/$/, '')
return `${base}/api/v1/lnurl/${uniqueHash}/${useHash}`
}
/**
* Build the callback URL for the second step (where user sends invoice)
*/
export function buildCallbackUrl(baseUrl: string, uniqueHash: string): string {
const base = baseUrl.replace(/\/$/, '')
return `${base}/api/v1/lnurl/cb/${uniqueHash}`
}
/**
* Sats to millisats
*/
export function satsToMsats(sats: number): number {
return sats * 1000
}
/**
* Millisats to sats
*/
export function msatsToSats(msats: number): number {
return Math.floor(msats / 1000)
}
/**
* Validate a BOLT11 invoice (basic check)
*/
export function isValidBolt11(invoice: string): boolean {
const lower = invoice.toLowerCase()
return lower.startsWith('lnbc') || lower.startsWith('lntb') || lower.startsWith('lnbcrt')
}

View file

@ -1,4 +1,8 @@
import 'dotenv/config'
import express from 'express'
import cors from 'cors'
import path from 'path'
import { fileURLToPath } from 'url'
import NewServer from '../proto/autogenerated/ts/express_server.js'
import GetServerMethods from './services/serverMethods/index.js'
import serverOptions from './auth.js';
@ -8,9 +12,15 @@ import { initMainHandler, initSettings } from './services/main/init.js';
import { nip19 } from 'nostr-tools'
import { LoadStorageSettingsFromEnv } from './services/storage/index.js';
import { AppInfo } from './services/nostr/nostrPool.js';
import { createExtensionLoader, ExtensionLoader } from './extensions/loader.js'
import { createMainHandlerAdapter } from './extensions/mainHandlerAdapter.js'
import type { HttpRoute } from './extensions/withdraw/types.js'
//@ts-ignore
const { nprofileEncode } = nip19
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const start = async () => {
const log = getLogger({})
@ -25,6 +35,42 @@ const start = async () => {
const { mainHandler, localProviderClient, wizard, adminManager } = keepOn
const serverMethods = GetServerMethods(mainHandler)
// Initialize extension system BEFORE nostrMiddleware so RPC methods are available
let extensionLoader: ExtensionLoader | null = null
const mainPort = settingsManager.getSettings().serviceSettings.servicePort
const extensionPort = mainPort + 1
// Extension routes run on a separate port (main port + 1)
// SERVICE_URL for extensions should point to this port for LNURL to work
// In production, use a reverse proxy to route /api/v1/lnurl/* to extension port
const extensionServiceUrl = process.env.EXTENSION_SERVICE_URL || `http://localhost:${extensionPort}`
try {
log("initializing extension system")
const extensionsDir = path.join(__dirname, 'extensions')
const databaseDir = path.join(__dirname, '..', 'data', 'extensions')
const mainHandlerAdapter = createMainHandlerAdapter(mainHandler)
extensionLoader = createExtensionLoader(
{ extensionsDir, databaseDir },
mainHandlerAdapter
)
await extensionLoader.loadAll()
log(`loaded ${extensionLoader.getAllExtensions().length} extension(s)`)
// Set base URL for LNURL generation on withdraw extension
const withdrawExt = extensionLoader.getExtension('withdraw')
if (withdrawExt && withdrawExt.instance && 'setBaseUrl' in withdrawExt.instance) {
(withdrawExt.instance as any).setBaseUrl(extensionServiceUrl)
log(`withdraw extension base URL set to ${extensionServiceUrl}`)
}
} catch (e) {
log(`extension system initialization failed: ${e}`)
}
// Initialize nostr middleware with extension loader for RPC routing
log("initializing nostr middleware")
const relays = settingsManager.getSettings().nostrRelaySettings.relays
const maxEventContentLength = settingsManager.getSettings().nostrRelaySettings.maxEventContentLength
@ -45,7 +91,8 @@ const start = async () => {
{
relays, maxEventContentLength, apps
},
(e, p) => mainHandler.liquidityProvider.onEvent(e, p)
(e, p) => mainHandler.liquidityProvider.onEvent(e, p),
{ extensionLoader: extensionLoader || undefined }
)
exitHandler(() => { Stop(); mainHandler.Stop() })
log("starting server")
@ -58,8 +105,58 @@ const start = async () => {
wizard.AddConnectInfo(appNprofile, relays)
}
adminManager.setAppNprofile(appNprofile)
// Create Express app for extension HTTP routes
const extensionApp = express()
extensionApp.use(cors()) // Enable CORS for all origins (ATM apps, wallets, etc.)
extensionApp.use(express.json())
// Mount extension HTTP routes
if (extensionLoader) {
for (const ext of extensionLoader.getAllExtensions()) {
if (ext.status === 'ready' && 'getHttpRoutes' in ext.instance) {
const routes = (ext.instance as any).getHttpRoutes() as HttpRoute[]
for (const route of routes) {
log(`mounting extension route: ${route.method} ${route.path}`)
const handler = async (req: express.Request, res: express.Response) => {
try {
const httpReq = {
params: req.params,
query: req.query as Record<string, string>,
body: req.body,
headers: req.headers as Record<string, string>
}
const result = await route.handler(httpReq)
res.status(result.status)
if (result.headers) {
for (const [key, value] of Object.entries(result.headers)) {
res.setHeader(key, value)
}
}
res.json(result.body)
} catch (e: any) {
log(`extension route error: ${e.message}`)
res.status(500).json({ status: 'ERROR', reason: e.message })
}
}
if (route.method === 'GET') {
extensionApp.get(route.path, handler)
} else if (route.method === 'POST') {
extensionApp.post(route.path, handler)
}
}
}
}
}
// Start extension routes server
extensionApp.listen(extensionPort, () => {
log(`extension HTTP routes listening on port ${extensionPort}`)
})
// Start main proto server
const Server = NewServer(serverMethods, serverOptions(mainHandler))
Server.Listen(settingsManager.getSettings().serviceSettings.servicePort)
Server.Listen(mainPort)
}
start()

View file

@ -5,9 +5,15 @@ import * as Types from '../proto/autogenerated/ts/types.js'
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
import { ERROR, getLogger } from "./services/helpers/logger.js";
import { NdebitData, NofferData, NmanageRequest } from "@shocknet/clink-sdk";
import type { ExtensionLoader } from "./extensions/loader.js"
type ExportedCalls = { Stop: () => void, Send: NostrSend, Ping: () => Promise<void>, Reset: (settings: NostrSettings) => void }
type ClientEventCallback = (e: { requestId: string }, fromPub: string) => void
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: ClientEventCallback): ExportedCalls => {
export type NostrMiddlewareOptions = {
extensionLoader?: ExtensionLoader
}
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: ClientEventCallback, options?: NostrMiddlewareOptions): ExportedCalls => {
const log = getLogger({})
const nostrTransport = NewNostrTransport(serverMethods, {
NostrUserAuthGuard: async (appId, pub) => {
@ -79,6 +85,13 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
const nmanageReq = j as NmanageRequest
mainHandler.managementManager.handleRequest(nmanageReq, event);
return;
} else if (event.kind === 23194) {
if (event.relayConstraint === 'provider') {
log("got NWC request on provider only relay, ignoring")
return
}
mainHandler.nwcManager.handleNwcRequest(event.content, event)
return
}
if (!j.rpcName) {
if (event.relayConstraint === 'service') {
@ -95,6 +108,31 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
log(ERROR, "authIdentifier does not match", j.authIdentifier || "--", event.pub)
return
}
// Check if this is an extension RPC method
const extensionLoader = options?.extensionLoader
if (extensionLoader && j.rpcName && extensionLoader.hasMethod(j.rpcName)) {
// Route to extension
log(`[Nostr] Routing to extension method: ${j.rpcName}`)
extensionLoader.callMethod(j.rpcName, j.body || {}, event.appId, event.pub)
.then(result => {
const response = { status: 'OK', requestId: j.requestId, ...result }
nostr.Send(
{ type: 'app', appId: event.appId },
{ type: 'content', pub: event.pub, content: JSON.stringify(response) }
)
})
.catch(err => {
log(ERROR, `Extension method ${j.rpcName} failed:`, err.message)
const response = { status: 'ERROR', requestId: j.requestId, reason: err.message }
nostr.Send(
{ type: 'app', appId: event.appId },
{ type: 'content', pub: event.pub, content: JSON.stringify(response) }
)
})
return
}
nostrTransport({ ...j, appId: event.appId }, res => {
nostr.Send({ type: 'app', appId: event.appId }, { type: 'content', pub: event.pub, content: JSON.stringify({ ...res, requestId: j.requestId }) })
}, event.startAtNano, event.startAtMs)
@ -105,7 +143,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
return {
Stop: () => { mainHandler.adminManager.setNostrConnected(false); return nostr.Stop },
Send: (...args) => nostr.Send(...args),
Send: async (...args) => nostr.Send(...args),
Ping: () => nostr.Ping(),
Reset: (settings: NostrSettings) => nostr.Reset(settings)
}

View file

@ -142,15 +142,20 @@ export default class {
return new Promise<void>((res, rej) => {
const interval = setInterval(async () => {
try {
await this.GetInfo()
const info = await this.GetInfo()
if (!info.syncedToChain || !info.syncedToGraph) {
this.log("LND responding but not synced yet, waiting...")
return
}
clearInterval(interval)
this.ready = true
res()
} catch (err) {
this.log(INFO, "LND is not ready yet, will try again in 1 second")
if (Date.now() - now > 1000 * 60) {
rej(new Error("LND not ready after 1 minute"))
}
if (Date.now() - now > 1000 * 60 * 10) {
clearInterval(interval)
rej(new Error("LND not synced after 10 minutes"))
}
}, 1000)
})
@ -202,7 +207,7 @@ export default class {
cooperative: true,
fundingCanceled: true,
localForce: true,
remoteForce: true
remoteForce: true,
}, DeadLineMetadata())
return res.response
}
@ -289,6 +294,8 @@ export default class {
account: "",
endHeight: 0,
startHeight: this.latestKnownBlockHeigh,
indexOffset: 0,
maxTransactions: 0
}, { abort: this.abortController.signal })
stream.responses.onMessage(tx => {
if (tx.blockHeight > this.latestKnownBlockHeigh) {
@ -542,7 +549,7 @@ export default class {
async GetTransactions(startHeight: number): Promise<TransactionDetails> {
this.log(DEBUG, "Getting transactions")
const res = await this.lightning.getTransactions({ startHeight, endHeight: 0, account: "", }, DeadLineMetadata())
const res = await this.lightning.getTransactions({ startHeight, endHeight: 0, account: "", indexOffset: 0, maxTransactions: 0 }, DeadLineMetadata())
return res.response
}
@ -566,6 +573,7 @@ export default class {
inboundFee: undefined,
feeRatePpm: policy.fee_rate_ppm,
minHtlcMsatSpecified: policy.min_htlc_msat > 0,
createMissingEdge: false,
}, DeadLineMetadata())
return res.response
}
@ -651,7 +659,8 @@ export default class {
txidBytes: Buffer.alloc(0)
},
force: false,
satPerByte: 0
satPerByte: 0,
deadlineDelta: 0
}, DeadLineMetadata())
return res.response
}

View file

@ -9,7 +9,7 @@ export const PayInvoiceReq = (invoice: string, amount: number, feeLimit: number)
maxParts: 3,
timeoutSeconds: 50,
allowSelfPayment: false,
allowSelfPayment: true,
amp: false,
amtMsat: 0n,
cltvLimit: 0,
@ -25,5 +25,7 @@ export const PayInvoiceReq = (invoice: string, amount: number, feeLimit: number)
paymentHash: Buffer.alloc(0),
routeHints: [],
timePref: 0,
outgoingChanId: '0'
outgoingChanId: '0',
cancelable: false,
firstHopCustomRecords: {}
})

View file

@ -99,6 +99,9 @@ export class Swaps {
quote: this.mapInvoiceSwapQuote(s),
failure_reason: s.failure_reason,
completed_at_unix: s.completed_at_unix || 1,
refund_address: s.refund_address,
refund_at_unix: s.refund_at_unix,
refund_tx_id: s.refund_tx_id,
}))
return {
current_block_height: currentBlockHeight,
@ -132,6 +135,7 @@ export class Swaps {
if (!result.ok) {
throw new Error(result.error)
}
await this.storage.paymentStorage.UpdateRefundInvoiceSwap(swapOperationId, refundAddress, result.publish.txId)
if (result.publish.done) {
return { published: true, txId: result.publish.txId }
}
@ -169,7 +173,7 @@ export class Swaps {
await this.storage.paymentStorage.FinalizeInvoiceSwap(swapOpId)
this.log("invoice swap completed", { swapOpId, txId })
} else {
await this.storage.paymentStorage.FailInvoiceSwap(swapOpId, result.error, txId)
await this.storage.paymentStorage.FailInvoiceSwap(swapOpId, result.error)
this.log("invoice swap failed", { swapOpId, error: result.error })
}
}, () => payAddress(swap.address, swap.transaction_amount)

View file

@ -183,14 +183,23 @@ export default class {
}
this.log("Deleting user", userId, "progress", i + 1, "/", toDelete.length)
await this.storage.StartTransaction(async tx => {
for (const appUserId of appUserIds) {
for (let j = 0; j < appUserIds.length; j++) {
const appUserId = appUserIds[j]
this.log("Deleting app user", appUserId, "progress", j + 1, "/", appUserIds.length)
this.log("Removing user grants")
await this.storage.managementStorage.removeUserGrants(appUserId, tx)
this.log("Removing user offers")
await this.storage.offerStorage.DeleteUserOffers(appUserId, tx)
this.log("Removing user debit access")
await this.storage.debitStorage.RemoveUserDebitAccess(appUserId, tx)
this.log("Removing user devices")
await this.storage.applicationStorage.RemoveAppUserDevices(appUserId, tx)
}
this.log("Removing user invoices")
await this.storage.paymentStorage.RemoveUserInvoices(userId, tx)
this.log("Removing user products")
await this.storage.productStorage.RemoveUserProducts(userId, tx)
this.log("Removing user ephemeral keys")
await this.storage.paymentStorage.RemoveUserEphemeralKeys(userId, tx)
await this.storage.paymentStorage.RemoveUserInvoicePayments(userId, tx)
await this.storage.paymentStorage.RemoveUserTransactionPayments(userId, tx)

View file

@ -194,7 +194,7 @@ export default class {
const cbUrl = req.http_callback_url || receiver.callback_url || ""
let zapInfo: ZapInfo | undefined = undefined
if (req.invoice_req.zap) {
zapInfo = this.paymentManager.validateZapEvent(req.invoice_req.zap, req.invoice_req.amountSats)
zapInfo = this.paymentManager.validateZapEvent(req.invoice_req.zap, req.invoice_req.amountSats * 1000)
}
const expiry = req.invoice_req.expiry ? Math.min(req.invoice_req.expiry, defaultInvoiceExpiry) : defaultInvoiceExpiry
const opts: InboundOptionals = {
@ -241,6 +241,8 @@ export default class {
const paid = await this.paymentManager.PayInvoice(appUser.user.user_id, req, app, {
ack: pendingOp => { this.notifyAppUserPayment(appUser, pendingOp) }
})
// Refresh appUser balance from DB so notification has accurate latest_balance
appUser.user.balance_sats = paid.latest_balance
this.notifyAppUserPayment(appUser, paid.operation)
getLogger({ appName: app.name })(appUser.identifier, "invoice paid", paid.amount_paid, "sats")
return paid

View file

@ -153,13 +153,14 @@ export class DebitManager {
}
notifyPaymentSuccess = (debitRes: NdebitSuccess, event: { pub: string, id: string, appId: string }) => {
this.logger("✅ [DEBIT REQUEST] Payment successful, sending OK response to", event.pub.slice(0, 16) + "...", "for event", event.id.slice(0, 16) + "...")
this.sendDebitResponse(debitRes, event)
}
sendDebitResponse = (debitRes: NdebitFailure | NdebitSuccess, event: { pub: string, id: string, appId: string }) => {
this.logger("📤 [DEBIT RESPONSE] Sending Kind 21002 response:", JSON.stringify(debitRes), "to", event.pub.slice(0, 16) + "...")
const e = newNdebitResponse(JSON.stringify(debitRes), event)
this.storage.NostrSender().Send({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } })
}
payNdebitInvoice = async (event: NostrEvent, pointerdata: NdebitData): Promise<HandleNdebitRes> => {

View file

@ -26,6 +26,7 @@ import { OfferManager } from "./offerManager.js"
import { parse } from "uri-template"
import webRTC from "../webRTC/index.js"
import { ManagementManager } from "./managementManager.js"
import { NwcManager } from "./nwcManager.js"
import { Agent } from "https"
import { NotificationsManager } from "./notificationsManager.js"
import { ApplicationUser } from '../storage/entity/ApplicationUser.js'
@ -58,6 +59,7 @@ export default class {
debitManager: DebitManager
offerManager: OfferManager
managementManager: ManagementManager
nwcManager: NwcManager
utils: Utils
rugPullTracker: RugPullTracker
unlocker: Unlocker
@ -89,6 +91,7 @@ export default class {
this.debitManager = new DebitManager(this.storage, this.lnd, this.applicationManager)
this.offerManager = new OfferManager(this.storage, this.settings, this.lnd, this.applicationManager, this.productManager, this.liquidityManager)
this.managementManager = new ManagementManager(this.storage, this.settings)
this.nwcManager = new NwcManager(this.storage, this.settings, this.lnd, this.applicationManager)
this.notificationsManager = new NotificationsManager(this.settings)
//this.webRTC = new webRTC(this.storage, this.utils)
}
@ -104,6 +107,9 @@ export default class {
StartBeacons() {
this.applicationManager.StartAppsServiceBeacon((app, fees) => {
this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, fees })
if (app.nostr_public_key) {
this.nwcManager.publishNwcInfo(app.app_id, app.nostr_public_key)
}
})
}
@ -162,7 +168,7 @@ export default class {
NewBlockHandler = async (height: number, skipMetrics?: boolean) => {
let confirmed: (PendingTx & { confs: number; })[]
let log = getLogger({})
log("NewBlockHandler called", JSON.stringify({ height, skipMetrics }))
// log("NewBlockHandler called", JSON.stringify({ height, skipMetrics }))
this.storage.paymentStorage.DeleteExpiredTransactionSwaps(height)
.catch(err => log(ERROR, "failed to delete expired transaction swaps", err.message || err))
this.storage.paymentStorage.DeleteExpiredInvoiceSwaps(height)
@ -178,7 +184,9 @@ export default class {
log(ERROR, "failed to check transactions after new block", err.message || err)
return
}
if (confirmed.length > 0) {
log("NewBlockHandler new confirmed transactions", confirmed.length)
}
await Promise.all(confirmed.map(async c => {
if (c.type === 'outgoing') {
await this.storage.paymentStorage.UpdateUserTransactionPayment(c.tx.serial_id, { confs: c.confs })
@ -533,6 +541,9 @@ export default class {
const fees = this.paymentManager.GetFees()
for (const app of apps) {
await this.UpdateBeacon(app, { type: 'service', name: app.name, avatarUrl: app.avatar_url, nextRelay, fees })
if (app.nostr_public_key) {
this.nwcManager.publishNwcInfo(app.app_id, app.nostr_public_key)
}
}
const defaultNames = ['wallet', 'wallet-test', this.settings.getSettings().serviceSettings.defaultAppName]

View file

@ -0,0 +1,346 @@
import { generateSecretKey, getPublicKey, UnsignedEvent } from 'nostr-tools'
import { bytesToHex } from '@noble/hashes/utils'
import ApplicationManager from "./applicationManager.js"
import Storage from '../storage/index.js'
import LND from "../lnd/lnd.js"
import { ERROR, getLogger } from "../helpers/logger.js"
import { NostrEvent } from '../nostr/nostrPool.js'
import SettingsManager from "./settingsManager.js"
type NwcRequest = {
method: string
params: Record<string, any>
}
type NwcResponse = {
result_type: string
error?: { code: string, message: string }
result?: Record<string, any>
}
const NWC_REQUEST_KIND = 23194
const NWC_RESPONSE_KIND = 23195
const NWC_INFO_KIND = 13194
const SUPPORTED_METHODS = [
'pay_invoice',
'make_invoice',
'get_balance',
'get_info',
'lookup_invoice',
'list_transactions',
]
const NWC_ERRORS = {
UNAUTHORIZED: 'UNAUTHORIZED',
RESTRICTED: 'RESTRICTED',
PAYMENT_FAILED: 'PAYMENT_FAILED',
QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',
INSUFFICIENT_BALANCE: 'INSUFFICIENT_BALANCE',
NOT_IMPLEMENTED: 'NOT_IMPLEMENTED',
NOT_FOUND: 'NOT_FOUND',
INTERNAL: 'INTERNAL',
OTHER: 'OTHER',
} as const
const newNwcResponse = (content: string, event: { pub: string, id: string }): UnsignedEvent => {
return {
content,
created_at: Math.floor(Date.now() / 1000),
kind: NWC_RESPONSE_KIND,
pubkey: "",
tags: [
['p', event.pub],
['e', event.id],
],
}
}
export class NwcManager {
applicationManager: ApplicationManager
storage: Storage
settings: SettingsManager
lnd: LND
logger = getLogger({ component: 'NwcManager' })
constructor(storage: Storage, settings: SettingsManager, lnd: LND, applicationManager: ApplicationManager) {
this.storage = storage
this.settings = settings
this.lnd = lnd
this.applicationManager = applicationManager
}
handleNwcRequest = async (content: string, event: NostrEvent) => {
if (!this.storage.NostrSender().IsReady()) {
this.logger(ERROR, "Nostr sender not ready, dropping NWC request")
return
}
let request: NwcRequest
try {
request = JSON.parse(content)
} catch {
this.logger(ERROR, "invalid NWC request JSON")
this.sendError(event, 'unknown', NWC_ERRORS.OTHER, 'Invalid request JSON')
return
}
const { method, params } = request
if (!method) {
this.sendError(event, 'unknown', NWC_ERRORS.OTHER, 'Missing method')
return
}
const connection = await this.storage.nwcStorage.GetConnection(event.appId, event.pub)
if (!connection) {
this.logger("NWC request from unknown client pubkey:", event.pub)
this.sendError(event, method, NWC_ERRORS.UNAUTHORIZED, 'Unknown connection')
return
}
if (connection.expires_at > 0 && connection.expires_at < Math.floor(Date.now() / 1000)) {
this.logger("NWC connection expired for client:", event.pub)
this.sendError(event, method, NWC_ERRORS.UNAUTHORIZED, 'Connection expired')
return
}
if (connection.permissions && connection.permissions.length > 0 && !connection.permissions.includes(method)) {
this.sendError(event, method, NWC_ERRORS.RESTRICTED, `Method ${method} not permitted`)
return
}
try {
switch (method) {
case 'pay_invoice':
await this.handlePayInvoice(event, params, connection.app_user_id, connection.max_amount, connection.total_spent)
break
case 'make_invoice':
await this.handleMakeInvoice(event, params, connection.app_user_id)
break
case 'get_balance':
await this.handleGetBalance(event, connection.app_user_id)
break
case 'get_info':
await this.handleGetInfo(event)
break
case 'lookup_invoice':
await this.handleLookupInvoice(event, params)
break
case 'list_transactions':
await this.handleListTransactions(event, params, connection.app_user_id)
break
default:
this.sendError(event, method, NWC_ERRORS.NOT_IMPLEMENTED, `Method ${method} not supported`)
}
} catch (e: any) {
this.logger(ERROR, `NWC ${method} failed:`, e.message || e)
this.sendError(event, method, NWC_ERRORS.INTERNAL, e.message || 'Internal error')
}
}
private handlePayInvoice = async (event: NostrEvent, params: Record<string, any>, appUserId: string, maxAmount: number, totalSpent: number) => {
const { invoice } = params
if (!invoice) {
this.sendError(event, 'pay_invoice', NWC_ERRORS.OTHER, 'Missing invoice parameter')
return
}
if (maxAmount > 0) {
const decoded = await this.lnd.DecodeInvoice(invoice)
const amountSats = decoded.numSatoshis
if (amountSats > 0 && (totalSpent + amountSats) > maxAmount) {
this.sendError(event, 'pay_invoice', NWC_ERRORS.QUOTA_EXCEEDED, 'Spending limit exceeded')
return
}
}
try {
const paid = await this.applicationManager.PayAppUserInvoice(event.appId, {
amount: 0,
invoice,
user_identifier: appUserId,
debit_npub: event.pub,
})
await this.storage.nwcStorage.IncrementTotalSpent(event.appId, event.pub, paid.amount_paid + paid.service_fee)
this.sendResult(event, 'pay_invoice', { preimage: paid.preimage })
} catch (e: any) {
this.sendError(event, 'pay_invoice', NWC_ERRORS.PAYMENT_FAILED, e.message || 'Payment failed')
}
}
private handleMakeInvoice = async (event: NostrEvent, params: Record<string, any>, appUserId: string) => {
const amountMsats = params.amount
if (amountMsats === undefined || amountMsats === null) {
this.sendError(event, 'make_invoice', NWC_ERRORS.OTHER, 'Missing amount parameter')
return
}
const amountSats = Math.floor(amountMsats / 1000)
const description = params.description || ''
const expiry = params.expiry || undefined
const result = await this.applicationManager.AddAppUserInvoice(event.appId, {
receiver_identifier: appUserId,
payer_identifier: '',
http_callback_url: '',
invoice_req: {
amountSats,
memo: description,
expiry,
},
})
this.sendResult(event, 'make_invoice', {
type: 'incoming',
invoice: result.invoice,
description,
description_hash: '',
preimage: '',
payment_hash: '',
amount: amountMsats,
fees_paid: 0,
created_at: Math.floor(Date.now() / 1000),
expires_at: Math.floor(Date.now() / 1000) + (expiry || 3600),
metadata: {},
})
}
private handleGetBalance = async (event: NostrEvent, appUserId: string) => {
const app = await this.storage.applicationStorage.GetApplication(event.appId)
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, appUserId)
const balanceMsats = appUser.user.balance_sats * 1000
this.sendResult(event, 'get_balance', { balance: balanceMsats })
}
private handleGetInfo = async (event: NostrEvent) => {
const info = await this.lnd.GetInfo()
this.sendResult(event, 'get_info', {
alias: info.alias,
color: '',
pubkey: info.identityPubkey,
network: 'mainnet',
block_height: info.blockHeight,
block_hash: info.blockHash,
methods: SUPPORTED_METHODS,
})
}
private handleLookupInvoice = async (event: NostrEvent, params: Record<string, any>) => {
const { invoice, payment_hash } = params
if (!invoice && !payment_hash) {
this.sendError(event, 'lookup_invoice', NWC_ERRORS.OTHER, 'Missing invoice or payment_hash parameter')
return
}
if (invoice) {
const found = await this.storage.paymentStorage.GetInvoiceOwner(invoice)
if (!found) {
this.sendError(event, 'lookup_invoice', NWC_ERRORS.NOT_FOUND, 'Invoice not found')
return
}
this.sendResult(event, 'lookup_invoice', {
type: 'incoming',
invoice: found.invoice,
description: '',
description_hash: '',
preimage: '',
payment_hash: '',
amount: found.paid_amount * 1000,
fees_paid: 0,
created_at: Math.floor(found.created_at.getTime() / 1000),
settled_at: found.paid_at_unix > 0 ? found.paid_at_unix : undefined,
metadata: {},
})
} else {
this.sendError(event, 'lookup_invoice', NWC_ERRORS.NOT_IMPLEMENTED, 'Lookup by payment_hash not supported')
}
}
private handleListTransactions = async (event: NostrEvent, params: Record<string, any>, appUserId: string) => {
const app = await this.storage.applicationStorage.GetApplication(event.appId)
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, appUserId)
const from = params.from || 0
const limit = Math.min(params.limit || 50, 50)
const invoices = await this.storage.paymentStorage.GetUserInvoicesFlaggedAsPaid(appUser.user.serial_id, 0, from, limit)
const transactions = invoices.map(inv => ({
type: 'incoming' as const,
invoice: inv.invoice,
description: '',
description_hash: '',
preimage: '',
payment_hash: '',
amount: inv.paid_amount * 1000,
fees_paid: 0,
created_at: Math.floor(inv.created_at.getTime() / 1000),
settled_at: inv.paid_at_unix > 0 ? inv.paid_at_unix : undefined,
metadata: {},
}))
this.sendResult(event, 'list_transactions', { transactions })
}
// --- Connection management methods ---
createConnection = async (appId: string, appUserId: string, permissions?: string[], options?: { maxAmount?: number, expiresAt?: number }) => {
const secretBytes = generateSecretKey()
const clientPubkey = getPublicKey(secretBytes)
const secret = bytesToHex(secretBytes)
await this.storage.nwcStorage.AddConnection({
app_id: appId,
app_user_id: appUserId,
client_pubkey: clientPubkey,
permissions,
max_amount: options?.maxAmount,
expires_at: options?.expiresAt,
})
const app = await this.storage.applicationStorage.GetApplication(appId)
const relays = this.settings.getSettings().nostrRelaySettings.relays
const relay = relays[0] || ''
const uri = `nostr+walletconnect://${app.nostr_public_key}?relay=${encodeURIComponent(relay)}&secret=${secret}`
return { uri, clientPubkey, secret }
}
listConnections = async (appUserId: string) => {
return this.storage.nwcStorage.GetUserConnections(appUserId)
}
revokeConnection = async (appId: string, clientPubkey: string) => {
return this.storage.nwcStorage.DeleteConnectionByPubkey(appId, clientPubkey)
}
// --- Kind 13194 info event ---
publishNwcInfo = (appId: string, appPubkey: string) => {
const content = SUPPORTED_METHODS.join(' ')
const event: UnsignedEvent = {
content,
created_at: Math.floor(Date.now() / 1000),
kind: NWC_INFO_KIND,
pubkey: appPubkey,
tags: [],
}
this.storage.NostrSender().Send({ type: 'app', appId }, { type: 'event', event })
}
// --- Response helpers ---
private sendResult = (event: NostrEvent, resultType: string, result: Record<string, any>) => {
const response: NwcResponse = { result_type: resultType, result }
const e = newNwcResponse(JSON.stringify(response), event)
this.storage.NostrSender().Send(
{ type: 'app', appId: event.appId },
{ type: 'event', event: e, encrypt: { toPub: event.pub } }
)
}
private sendError = (event: NostrEvent, resultType: string, code: string, message: string) => {
const response: NwcResponse = { result_type: resultType, error: { code, message } }
const e = newNwcResponse(JSON.stringify(response), event)
this.storage.NostrSender().Send(
{ type: 'app', appId: event.appId },
{ type: 'event', event: e, encrypt: { toPub: event.pub } }
)
}
}

View file

@ -987,7 +987,9 @@ export default class {
async CheckNewlyConfirmedTxs() {
const pending = await this.storage.paymentStorage.GetPendingTransactions()
let log = getLogger({})
if (pending.incoming.length > 0 || pending.outgoing.length > 0) {
log("CheckNewlyConfirmedTxs ", pending.incoming.length, "incoming", pending.outgoing.length, "outgoing")
}
const confirmedIncoming: (PendingTx & { confs: number })[] = []
const confirmedOutgoing: (PendingTx & { confs: number })[] = []
for (const tx of pending.incoming) {

View file

@ -238,13 +238,17 @@ export class Watchdog {
const knownMaxIndex = Math.max(maxFromDb, this.latestPaymentIndexOffset)
const newLatest = await this.lnd.GetLatestPaymentIndex(knownMaxIndex)
const historyMismatch = newLatest > knownMaxIndex
const deny = await this.checkBalanceUpdate(deltaLnd, deltaUsers)
if (historyMismatch) {
getLogger({ component: 'bark' })("History mismatch detected in absolute update, locking outgoing operations")
this.log("Payment index advanced from", knownMaxIndex, "to", newLatest, "- updating offset (likely LND restart or external payment)")
this.latestPaymentIndexOffset = newLatest
}
const deny = await this.checkBalanceUpdate(deltaLnd, deltaUsers)
if (deny) {
if (historyMismatch) {
getLogger({ component: 'bark' })("Balance mismatch with unexpected payment history, locking outgoing operations")
this.lnd.LockOutgoingOperations()
return
}
if (deny) {
this.log("Balance mismatch detected in absolute update, but history is ok")
}
this.lnd.UnlockOutgoingOperations()

View file

@ -12,12 +12,15 @@ import HtlcTracker from './htlcTracker.js'
import { getLogger } from '../helpers/logger.js'
import { encodeTLV, usageMetricsToTlv } from '../helpers/tlv.js'
import { ChannelCloseSummary_ClosureType } from '../../../proto/lnd/lightning.js'
const cacheTTL = 1000 * 60 * 5 // 5 minutes
export default class Handler {
storage: Storage
lnd: LND
htlcTracker: HtlcTracker
appsMetricsCache: CacheController<Types.AppsMetrics> = new CacheController<Types.AppsMetrics>()
lndForwardingMetricsCache: CacheController<Types.LndForwardingMetrics> = new CacheController<Types.LndForwardingMetrics>()
lndMetricsCache: CacheController<Types.LndMetrics> = new CacheController<Types.LndMetrics>()
logger = getLogger({ component: "metrics" })
constructor(storage: Storage, lnd: LND) {
this.storage = storage
@ -183,8 +186,6 @@ export default class Handler {
})
}
/* addTrackedMetric = (appId: string, method: string, metric: Uint8Array) => {
if (!this.metaReady) {
throw new Error("meta metrics not ready")
@ -203,13 +204,21 @@ export default class Handler {
} */
async GetAppsMetrics(req: Types.AppsMetricsRequest): Promise<Types.AppsMetrics> {
const cached = this.appsMetricsCache.Get(req)
const now = Date.now()
if (cached && now - cached.createdAt < cacheTTL) {
return cached.metrics
}
const dbApps = await this.storage.applicationStorage.GetApplications()
const apps = await Promise.all(dbApps.map(app => this.GetAppMetrics(req, app)))
const unlinked = await this.GetAppMetrics(req, null)
apps.push(unlinked)
return {
const metrics = {
apps
}
this.appsMetricsCache.Set(req, { metrics, createdAt: now })
return metrics
}
async GetAppMetrics(req: Types.AppsMetricsRequest, app: Application | null): Promise<Types.AppMetrics> {
@ -332,6 +341,11 @@ export default class Handler {
}
async GetLndForwardingMetrics(req: Types.LndMetricsRequest): Promise<Types.LndForwardingMetrics> {
const cached = this.lndForwardingMetricsCache.Get(req)
const now = Date.now()
if (cached && now - cached.createdAt < cacheTTL) {
return cached.metrics
}
const fwEvents = await this.lnd.GetForwardingHistory(0, req.from_unix, req.to_unix)
let totalFees = 0
const events: Types.LndForwardingEvent[] = fwEvents.forwardingEvents.map(e => {
@ -340,14 +354,21 @@ export default class Handler {
chan_id_in: e.chanIdIn, chan_id_out: e.chanIdOut, amt_in: Number(e.amtIn), amt_out: Number(e.amtOut), fee: Number(e.fee), at_unix: Number(e.timestampNs)
}
})
return {
const metrics = {
total_fees: totalFees,
events: events
}
this.lndForwardingMetricsCache.Set(req, { metrics, createdAt: now })
return metrics
}
async GetLndMetrics(req: Types.LndMetricsRequest): Promise<Types.LndMetrics> {
const cached = this.lndMetricsCache.Get(req)
const now = Date.now()
if (cached && now - cached.createdAt < cacheTTL) {
return cached.metrics
}
const [chansInfo, pendingChansInfo, closedChansInfo, routing, rootOps, channelsActivity] = await Promise.all([
this.GetChannelsInfo(),
this.GetPendingChannelsInfo(),
@ -392,7 +413,7 @@ export default class Handler {
}
}))
return {
const metrics = {
nodes: [{
chain_balance: chainBalance,
channel_balance: chansBalance,
@ -408,6 +429,8 @@ export default class Handler {
root_ops: rootOps.map(r => ({ amount: r.operation_amount, created_at_unix: r.at_unix || 0, op_id: r.operation_identifier, op_type: mapRootOpType(r.operation_type) })),
}],
}
this.lndMetricsCache.Set(req, { metrics, createdAt: now })
return metrics
}
async AddRootAddressPaid(address: string, txOutput: { hash: string; index: number }, amount: number) {
@ -433,3 +456,31 @@ const mapRootOpType = (opType: string): Types.OperationType => {
default: throw new Error("Unknown operation type")
}
}
type CacheData<T> = {
metrics: T
createdAt: number
}
class CacheController<T> {
private cache: Record<string, CacheData<T>> = {}
Get = (req: Types.AppsMetricsRequest): CacheData<T> | undefined => {
const key = this.getKey(req)
return this.cache[key]
}
Set = (req: Types.AppsMetricsRequest, metrics: CacheData<T>) => {
const key = this.getKey(req)
this.cache[key] = metrics
}
Clear = (req: Types.AppsMetricsRequest) => {
const key = this.getKey(req)
delete this.cache[key]
}
private getKey = (req: Types.AppsMetricsRequest) => {
const start = req.from_unix || 0
const end = req.to_unix || 0
const includeOperations = req.include_operations ? "1" : "0"
return `${start}:${end}:${includeOperations}`
}
}

View file

@ -132,12 +132,12 @@ const handleNostrSettings = (settings: NostrSettings) => {
send(event)
})
} */
const sendToNostr: NostrSend = (initiator, data, relays) => {
const sendToNostr: NostrSend = async (initiator, data, relays) => {
if (!subProcessHandler) {
getLogger({ component: "nostrMiddleware" })(ERROR, "nostr was not initialized")
return
}
subProcessHandler.Send(initiator, data, relays)
await subProcessHandler.Send(initiator, data, relays)
}
send({ type: 'ready' })

View file

@ -1,14 +1,14 @@
import { base64 } from "@scure/base";
import { base64, hex } from "@scure/base";
import { randomBytes } from "@noble/hashes/utils";
import { streamXOR as xchacha20 } from "@stablelib/xchacha20";
import { secp256k1 } from "@noble/curves/secp256k1";
import { secp256k1 } from "@noble/curves/secp256k1.js";
import { sha256 } from "@noble/hashes/sha256";
export type EncryptedData = {
ciphertext: Uint8Array;
nonce: Uint8Array;
}
export const getSharedSecret = (privateKey: string, publicKey: string) => {
const key = secp256k1.getSharedSecret(privateKey, "02" + publicKey);
const key = secp256k1.getSharedSecret(hex.decode(privateKey), hex.decode("02" + publicKey));
return sha256(key.slice(1, 33));
}

View file

@ -16,7 +16,7 @@ export type SendDataContent = { type: "content", content: string, pub: string }
export type SendDataEvent = { type: "event", event: UnsignedEvent, encrypt?: { toPub: string } }
export type SendData = SendDataContent | SendDataEvent
export type SendInitiator = { type: 'app', appId: string } | { type: 'client', clientId: string }
export type NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => void
export type NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => Promise<void>
export type LinkedProviderInfo = { pubkey: string, clientId: string, relayUrl: string }
export type AppInfo = { appId: string, publicKey: string, privateKey: string, name: string, provider?: LinkedProviderInfo }
@ -48,7 +48,7 @@ const splitContent = (content: string, maxLength: number) => {
}
return parts
}
const actionKinds = [21000, 21001, 21002, 21003]
const actionKinds = [21000, 21001, 21002, 21003, 23194]
const beaconKind = 30078
const appTag = "Lightning.Pub"
export class NostrPool {
@ -203,21 +203,26 @@ export class NostrPool {
const signed = finalizeEvent(event, Buffer.from(keys.privateKey, 'hex'))
let sent = false
const log = getLogger({ appName: keys.name })
// const r = relays ? relays : this.getServiceRelays()
this.log(`📤 Publishing Kind ${event.kind} event to ${relays.length} relay(s): ${relays.join(', ')}`)
const pool = new SimplePool()
try {
await Promise.all(pool.publish(relays, signed).map(async p => {
try {
await p
sent = true
} catch (e: any) {
console.log(e)
this.log(ERROR, `Failed to publish Kind ${event.kind} event:`, e.message || e)
log(e)
}
}))
if (!sent) {
this.log(ERROR, `Failed to send Kind ${event.kind} event to any relay`)
log("failed to send event")
} else {
//log("sent event")
this.log(`✅ Kind ${event.kind} event published successfully (id: ${signed.id.slice(0, 16)}...)`)
}
} finally {
pool.close(relays)
}
}

View file

@ -1,7 +1,7 @@
import { NostrSend, SendData, SendInitiator } from "./nostrPool.js"
import { getLogger } from "../helpers/logger.js"
import { ERROR, getLogger } from "../helpers/logger.js"
export class NostrSender {
private _nostrSend: NostrSend = () => { throw new Error('nostr send not initialized yet') }
private _nostrSend: NostrSend = async () => { throw new Error('nostr send not initialized yet') }
private isReady: boolean = false
private onReadyCallbacks: (() => void)[] = []
private pendingSends: { initiator: SendInitiator, data: SendData, relays?: string[] | undefined }[] = []
@ -12,7 +12,12 @@ export class NostrSender {
this.isReady = true
this.onReadyCallbacks.forEach(cb => cb())
this.onReadyCallbacks = []
this.pendingSends.forEach(send => this._nostrSend(send.initiator, send.data, send.relays))
// Process pending sends with proper error handling
this.pendingSends.forEach(send => {
this._nostrSend(send.initiator, send.data, send.relays).catch(e => {
this.log(ERROR, "failed to send pending event", e.message || e)
})
})
this.pendingSends = []
}
OnReady(callback: () => void) {
@ -22,13 +27,16 @@ export class NostrSender {
this.onReadyCallbacks.push(callback)
}
}
Send(initiator: SendInitiator, data: SendData, relays?: string[] | undefined) {
Send(initiator: SendInitiator, data: SendData, relays?: string[] | undefined): void {
if (!this.isReady) {
this.log("tried to send before nostr was ready, caching request")
this.pendingSends.push({ initiator, data, relays })
return
}
this._nostrSend(initiator, data, relays)
// Fire and forget but log errors
this._nostrSend(initiator, data, relays).catch(e => {
this.log(ERROR, "failed to send event", e.message || e)
})
}
IsReady() {
return this.isReady

View file

@ -21,6 +21,7 @@ import { LndNodeInfo } from "../entity/LndNodeInfo.js"
import { TrackedProvider } from "../entity/TrackedProvider.js"
import { InviteToken } from "../entity/InviteToken.js"
import { DebitAccess } from "../entity/DebitAccess.js"
import { NwcConnection } from "../entity/NwcConnection.js"
import { RootOperation } from "../entity/RootOperation.js"
import { UserOffer } from "../entity/UserOffer.js"
import { ManagementGrant } from "../entity/ManagementGrant.js"
@ -71,6 +72,7 @@ export const MainDbEntities = {
'TrackedProvider': TrackedProvider,
'InviteToken': InviteToken,
'DebitAccess': DebitAccess,
'NwcConnection': NwcConnection,
'UserOffer': UserOffer,
'Product': Product,
'ManagementGrant': ManagementGrant,

View file

@ -84,7 +84,7 @@ export default class {
async execNextInQueue() {
this.pendingTx = false
const next = this.transactionsQueue.pop()
const next = this.transactionsQueue.shift()
if (!next) {
this.doneWriting()
return

View file

@ -80,8 +80,14 @@ export class InvoiceSwap {
@Column({ default: "", type: "text" })
lockup_tx_hex: string
/* @Column({ default: "" })
address_paid: string */
@Column({ default: "" })
refund_address: string
@Column({ default: "" })
refund_at_unix: number
@Column({ default: "" })
refund_tx_id: string
@Column({ default: "" })
service_url: string

View file

@ -0,0 +1,37 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, CreateDateColumn, UpdateDateColumn } from "typeorm"
@Entity()
@Index("unique_nwc_connection", ["app_id", "client_pubkey"], { unique: true })
@Index("idx_nwc_app_user", ["app_user_id"])
export class NwcConnection {
@PrimaryGeneratedColumn()
serial_id: number
@Column()
app_id: string
@Column()
app_user_id: string
@Column()
client_pubkey: string
@Column({ type: 'simple-json', default: null, nullable: true })
permissions: string[] | null
@Column({ default: 0 })
max_amount: number
@Column({ default: 0 })
expires_at: number
@Column({ default: 0 })
total_spent: number
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -9,6 +9,7 @@ import MetricsEventStorage from "./tlv/metricsEventStorage.js";
import EventsLogManager from "./eventsLog.js";
import { LiquidityStorage } from "./liquidityStorage.js";
import DebitStorage from "./debitStorage.js"
import NwcStorage from "./nwcStorage.js"
import OfferStorage from "./offerStorage.js"
import { ManagementStorage } from "./managementStorage.js";
import { StorageInterface, TX } from "./db/storageInterface.js";
@ -78,6 +79,7 @@ export default class {
metricsEventStorage: MetricsEventStorage
liquidityStorage: LiquidityStorage
debitStorage: DebitStorage
nwcStorage: NwcStorage
offerStorage: OfferStorage
managementStorage: ManagementStorage
eventsLog: EventsLogManager
@ -103,6 +105,7 @@ export default class {
this.metricsEventStorage = new MetricsEventStorage(this.settings, this.utils.tlvStorageFactory)
this.liquidityStorage = new LiquidityStorage(this.dbs)
this.debitStorage = new DebitStorage(this.dbs)
this.nwcStorage = new NwcStorage(this.dbs)
this.offerStorage = new OfferStorage(this.dbs)
this.managementStorage = new ManagementStorage(this.dbs);
try { if (this.settings.dataDir) fs.mkdirSync(this.settings.dataDir) } catch (e) { }

View file

@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class NwcConnection1770000000000 implements MigrationInterface {
name = 'NwcConnection1770000000000'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "nwc_connection" ("serial_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "app_id" varchar NOT NULL, "app_user_id" varchar NOT NULL, "client_pubkey" varchar NOT NULL, "permissions" text, "max_amount" integer NOT NULL DEFAULT (0), "expires_at" integer NOT NULL DEFAULT (0), "total_spent" integer NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`);
await queryRunner.query(`CREATE UNIQUE INDEX "unique_nwc_connection" ON "nwc_connection" ("app_id", "client_pubkey") `);
await queryRunner.query(`CREATE INDEX "idx_nwc_app_user" ON "nwc_connection" ("app_user_id") `);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "idx_nwc_app_user"`);
await queryRunner.query(`DROP INDEX "unique_nwc_connection"`);
await queryRunner.query(`DROP TABLE "nwc_connection"`);
}
}

View file

@ -0,0 +1,20 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class RefundSwapInfo1773082318982 implements MigrationInterface {
name = 'RefundSwapInfo1773082318982'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "temporary_invoice_swap" ("swap_operation_id" varchar PRIMARY KEY NOT NULL, "app_user_id" varchar NOT NULL, "swap_quote_id" varchar NOT NULL, "swap_tree" varchar NOT NULL, "claim_public_key" varchar NOT NULL, "payment_hash" varchar NOT NULL, "timeout_block_height" integer NOT NULL, "invoice" varchar NOT NULL, "invoice_amount" integer NOT NULL, "transaction_amount" integer NOT NULL, "swap_fee_sats" integer NOT NULL, "chain_fee_sats" integer NOT NULL, "ephemeral_public_key" varchar NOT NULL, "address" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "preimage" varchar NOT NULL DEFAULT (''), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "service_url" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "lockup_tx_hex" text NOT NULL DEFAULT (''), "completed_at_unix" integer NOT NULL DEFAULT (0), "paid_at_unix" integer NOT NULL DEFAULT (0), "refund_address" varchar NOT NULL DEFAULT (''), "refund_at_unix" integer NOT NULL DEFAULT (''), "refund_tx_id" varchar NOT NULL DEFAULT (''))`);
await queryRunner.query(`INSERT INTO "temporary_invoice_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at", "lockup_tx_hex", "completed_at_unix", "paid_at_unix") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at", "lockup_tx_hex", "completed_at_unix", "paid_at_unix" FROM "invoice_swap"`);
await queryRunner.query(`DROP TABLE "invoice_swap"`);
await queryRunner.query(`ALTER TABLE "temporary_invoice_swap" RENAME TO "invoice_swap"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "invoice_swap" RENAME TO "temporary_invoice_swap"`);
await queryRunner.query(`CREATE TABLE "invoice_swap" ("swap_operation_id" varchar PRIMARY KEY NOT NULL, "app_user_id" varchar NOT NULL, "swap_quote_id" varchar NOT NULL, "swap_tree" varchar NOT NULL, "claim_public_key" varchar NOT NULL, "payment_hash" varchar NOT NULL, "timeout_block_height" integer NOT NULL, "invoice" varchar NOT NULL, "invoice_amount" integer NOT NULL, "transaction_amount" integer NOT NULL, "swap_fee_sats" integer NOT NULL, "chain_fee_sats" integer NOT NULL, "ephemeral_public_key" varchar NOT NULL, "address" varchar NOT NULL, "ephemeral_private_key" varchar NOT NULL, "used" boolean NOT NULL DEFAULT (0), "preimage" varchar NOT NULL DEFAULT (''), "failure_reason" varchar NOT NULL DEFAULT (''), "tx_id" varchar NOT NULL DEFAULT (''), "service_url" varchar NOT NULL DEFAULT (''), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')), "lockup_tx_hex" text NOT NULL DEFAULT (''), "completed_at_unix" integer NOT NULL DEFAULT (0), "paid_at_unix" integer NOT NULL DEFAULT (0))`);
await queryRunner.query(`INSERT INTO "invoice_swap"("swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at", "lockup_tx_hex", "completed_at_unix", "paid_at_unix") SELECT "swap_operation_id", "app_user_id", "swap_quote_id", "swap_tree", "claim_public_key", "payment_hash", "timeout_block_height", "invoice", "invoice_amount", "transaction_amount", "swap_fee_sats", "chain_fee_sats", "ephemeral_public_key", "address", "ephemeral_private_key", "used", "preimage", "failure_reason", "tx_id", "service_url", "created_at", "updated_at", "lockup_tx_hex", "completed_at_unix", "paid_at_unix" FROM "temporary_invoice_swap"`);
await queryRunner.query(`DROP TABLE "temporary_invoice_swap"`);
}
}

View file

@ -27,9 +27,11 @@ import { TrackedProviderHeight1766504040000 } from './1766504040000-tracked_prov
import { SwapsServiceUrl1768413055036 } from './1768413055036-swaps_service_url.js'
import { InvoiceSwaps1769529793283 } from './1769529793283-invoice_swaps.js'
import { InvoiceSwapsFixes1769805357459 } from './1769805357459-invoice_swaps_fixes.js'
import { NwcConnection1770000000000 } from './1770000000000-nwc_connection.js'
import { ApplicationUserTopicId1770038768784 } from './1770038768784-application_user_topic_id.js'
import { SwapTimestamps1771347307798 } from './1771347307798-swap_timestamps.js'
import { TxSwapTimestamps1771878683383 } from './1771878683383-tx_swap_timestamps.js'
import { RefundSwapInfo1773082318982 } from './1773082318982-refund_swap_info.js'
import { LndMetrics1703170330183 } from './1703170330183-lnd_metrics.js'
import { ChannelRouting1709316653538 } from './1709316653538-channel_routing.js'
@ -42,16 +44,14 @@ import { ChannelEvents1750777346411 } from './1750777346411-channel_events.js'
import { RootOpPending1771524665409 } from './1771524665409-root_op_pending.js'
export const allMigrations = [Initial1703170309875, LspOrder1718387847693, LiquidityProvider1719335699480, LndNodeInfo1720187506189,
TrackedProvider1720814323679, CreateInviteTokenTable1721751414878, PaymentIndex1721760297610, DebitAccess1726496225078, DebitAccessFixes1726685229264,
DebitToPub1727105758354, UserCbUrl1727112281043, UserOffer1733502626042, ManagementGrant1751307732346, ManagementGrantBanned1751989251513,
InvoiceCallbackUrls1752425992291, OldSomethingLeftover1753106599604, UserReceivingInvoiceIdx1753109184611, AppUserDevice1753285173175,
UserAccess1759426050669, AddBlindToUserOffer1760000000000, ApplicationAvatarUrl1761000001000, AdminSettings1761683639419, TxSwap1762890527098,
TxSwapAddress1764779178945, ClinkRequester1765497600000, TrackedProviderHeight1766504040000, SwapsServiceUrl1768413055036,
InvoiceSwaps1769529793283, InvoiceSwapsFixes1769805357459, ApplicationUserTopicId1770038768784, SwapTimestamps1771347307798,
TxSwapTimestamps1771878683383]
InvoiceSwaps1769529793283, InvoiceSwapsFixes1769805357459, NwcConnection1770000000000, ApplicationUserTopicId1770038768784, SwapTimestamps1771347307798,
TxSwapTimestamps1771878683383, RefundSwapInfo1773082318982]
export const allMetricsMigrations = [LndMetrics1703170330183, ChannelRouting1709316653538, HtlcCount1724266887195, BalanceEvents1724860966825,

View file

@ -0,0 +1,49 @@
import { NwcConnection } from "./entity/NwcConnection.js";
import { StorageInterface } from "./db/storageInterface.js";
type ConnectionToAdd = {
app_id: string
app_user_id: string
client_pubkey: string
permissions?: string[]
max_amount?: number
expires_at?: number
}
export default class {
dbs: StorageInterface
constructor(dbs: StorageInterface) {
this.dbs = dbs
}
async AddConnection(connection: ConnectionToAdd) {
return this.dbs.CreateAndSave<NwcConnection>('NwcConnection', {
app_id: connection.app_id,
app_user_id: connection.app_user_id,
client_pubkey: connection.client_pubkey,
permissions: connection.permissions || null,
max_amount: connection.max_amount || 0,
expires_at: connection.expires_at || 0,
})
}
async GetConnection(appId: string, clientPubkey: string, txId?: string) {
return this.dbs.FindOne<NwcConnection>('NwcConnection', { where: { app_id: appId, client_pubkey: clientPubkey } }, txId)
}
async GetUserConnections(appUserId: string, txId?: string) {
return this.dbs.Find<NwcConnection>('NwcConnection', { where: { app_user_id: appUserId } }, txId)
}
async DeleteConnection(serialId: number, txId?: string) {
return this.dbs.Delete<NwcConnection>('NwcConnection', { serial_id: serialId }, txId)
}
async DeleteConnectionByPubkey(appId: string, clientPubkey: string, txId?: string) {
return this.dbs.Delete<NwcConnection>('NwcConnection', { app_id: appId, client_pubkey: clientPubkey }, txId)
}
async IncrementTotalSpent(appId: string, clientPubkey: string, amount: number, txId?: string) {
return this.dbs.Increment<NwcConnection>('NwcConnection', { app_id: appId, client_pubkey: clientPubkey }, 'total_spent', amount, txId)
}
}

View file

@ -655,6 +655,15 @@ export default class {
return swaps.filter(s => !!s.tx_id)
}
async UpdateRefundInvoiceSwap(swapOperationId: string, refundAddress: string, refundTxId: string, txId?: string) {
const now = Math.floor(Date.now() / 1000)
return this.dbs.Update<InvoiceSwap>('InvoiceSwap', { swap_operation_id: swapOperationId }, {
refund_address: refundAddress,
refund_at_unix: now,
refund_tx_id: refundTxId,
}, txId)
}
async GetRefundableInvoiceSwap(swapOperationId: string, txId?: string) {
const swap = await this.dbs.FindOne<InvoiceSwap>('InvoiceSwap', { where: { swap_operation_id: swapOperationId } }, txId)
if (!swap || !swap.tx_id) {

View file

@ -126,7 +126,7 @@ class TlvFilesStorageProcessor {
throw new Error('Unknown metric type: ' + t)
}
})
this.wrtc.attachNostrSend((initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => {
this.wrtc.attachNostrSend(async (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => {
this.sendResponse({
success: true,
type: 'nostrSend',

View file

@ -27,11 +27,11 @@ export default class webRTC {
attachNostrSend(f: NostrSend) {
this._nostrSend = f
}
private nostrSend: NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => {
private nostrSend: NostrSend = async (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => {
if (!this._nostrSend) {
throw new Error("No nostrSend attached")
}
this._nostrSend(initiator, data, relays)
await this._nostrSend(initiator, data, relays)
}
private sendCandidate = (u: WebRtcUserInfo, candidate: string) => {