feat(extensions): add extension loader infrastructure (#3)
Some checks are pending
Docker Compose Actions Workflow / test (push) Waiting to run
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
This commit is contained in:
parent
72c9872b23
commit
77e5772afd
47 changed files with 10187 additions and 4828 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue