A flexible Lightning Service Provider (LSP) built on top of LDK Server. Provides Just-In-Time (JIT) liquidity for users lacking inbound channel capacity.
- JIT Receive (Just-In-Time Liquidity): Users without inbound capacity can request a quote, pay an invoice, and automatically receive either:
- A new channel opened to them (with zero-conf)
- A splice-in to an existing channel
- Dynamic Fee Estimation: Real-time onchain fee rates fetched from your configured Esplora instance
- Inbound Buffer: Channels are opened with extra capacity beyond what the user paid for, giving them spare inbound liquidity
- Zero-confirmation (zeroconf): New channels are usable immediately
- HTTP API: RESTful API for JIT receive requests
- RabbitMQ Events: Async payment notifications trigger automatic channel opens/splices
┌─────────────┐ HTTP API ┌──────────────┐
│ Client │ ◄───────────────► │ LDK-LSP │
│ (Alby Hub) │ │ (this repo) │
└─────────────┘ └──────┬───────┘
│
│ gRPC
│
┌──────────▼──────────┐
│ LDK Server │
│ (Lightning Node) │
└─────────────────────┘
│
│ Esplora
│
┌──────────▼──────────┐
│ Esplora Server │
│ (Fee estimates + │
│ Blockchain data) │
└─────────────────────┘
LDK-LSP acts as a layer on top of LDK Server:
- Uses
ldk-server-clientto communicate with the Lightning node via gRPC - Provides an HTTP API for external services to request JIT liquidity
- Manages channel lifecycle including automatic zeroconf handling
- Consumes RabbitMQ events from ldk-server for real-time payment notifications
- Rust 1.70+
- LDK Server instance running (see LDK Server)
- Esplora instance (for fee estimation and blockchain data)
- RabbitMQ (for event notifications)
git clone https://github.com/yourusername/ldk-lsp.git
cd ldk-lsp
cargo build --releaseCreate a configuration file at ./ldk-lsp.toml:
[node]
alias = "My LSP"
network = "regtest" # or "testnet", "mainnet"
data_dir = "./data"
[ldk_server]
host = "127.0.0.1"
port = 3009 # Default ldk-server port
[ldk_server.rabbitmq]
connection_string = "amqp://guest:guest@localhost:5672/%2F"
exchange_name = "ldk-server"
[lsp]
# JIT Receive settings
jit_receive_base_fee = 1000 # Base fee in sats
jit_receive_fee_ppm = 5000 # 0.5% fee (parts per million)
jit_min_receive_amount = 10000 # Minimum 10k sats
jit_inbound_buffer = 50000 # Extra 50k sats capacity added
# Fee estimation (uses same Esplora as ldk-server)
fee_confirmation_target = 6 # Target ~1 hour confirmation
# Channel limits
min_channel_size = 100000 # 100k sats minimum
max_channel_size = 100000000 # 1 BTC maximum
enable_zeroconf = true
enable_splicing = true
[api]
bind_address = "127.0.0.1:8080"
enable_cors = true
[database]
url = "sqlite:ldk-lsp.db"./target/release/ldk-lsp- User requests quote: Client provides their node ID, host, port, and amount they want to receive
- LSP detects channel status:
- If no channel exists → will open new channel
- If channel exists → will splice additional capacity
- Fee calculation:
- Base fee + PPM fee on amount
- Dynamic onchain fee estimate (from Esplora)
- Total invoice amount = amount + all fees
- Invoice payment: User pays the BOLT11 invoice
- Automatic execution: When payment is detected via RabbitMQ, LSP automatically:
- Opens a new channel (with zeroconf), or
- Splices in additional funds to existing channel
- Channel capacity: User receives
amount + jit_inbound_bufferin channel capacity
GET /healthResponse:
{
"success": true,
"data": {
"status": "healthy",
"version": "0.1.0",
"ldk_server_connected": true
}
}Request inbound liquidity to receive payments.
POST /v1/receive/quote
Content-Type: application/json
{
"node_id": "0288f...",
"host": "192.168.1.100",
"port": 9735,
"amount": 50000
}Response:
{
"success": true,
"data": {
"receive_id": "550e8400-e29b-41d4-a716-446655440000",
"amount": 50000,
"fees": {
"base": 1000,
"ppm": 250,
"onchain": 4800,
"total": 6050,
"fee_rate": 24
},
"total_invoice_amount": 56050,
"is_splice": false,
"channel_id": null,
"lsp_node_id": "My LSP",
"expiry": "2024-01-15T10:30:00Z",
"invoice": "lnbc5605010n1p3...",
"payment_hash": "abcd1234..."
}
}Notes:
is_splice:trueif splicing existing channel,falseif opening new channelfee_rate: The sat/vbyte rate used for onchain fee calculationtotal_invoice_amount: What user pays (amount + fees)- Channel will have
amount + jit_inbound_buffercapacity
GET /v1/receive/{receive_id}Response:
{
"success": true,
"data": {
"receive_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "PendingPayment",
"amount": 50000,
"is_splice": false,
"channel_id": null,
"fees": {
"base": 1000,
"ppm": 250,
"onchain": 4800,
"total": 6050,
"fee_rate": 24
},
"total_invoice_amount": 56050,
"created_at": "2024-01-15T10:20:00Z",
"failure_reason": null
}
}Status values:
PendingPayment: Waiting for invoice to be paidPaymentReceived: Payment detected, action startingChannelOpening: New channel opening initiatedSpliceInitiated: Splice-in initiatedCompleted: Channel/splice ready for useFailed: Something went wrong (seefailure_reason)
cargo testcargo doc --no-deps --opensrc/
├── main.rs # Application entry point
├── lib.rs # Library root
├── config.rs # Configuration management
├── fee.rs # Dynamic fee estimation from Esplora
├── node/ # LDK Server integration
│ ├── mod.rs # Node wrapper
│ ├── client.rs # gRPC client
│ └── events.rs # Event handling
├── lsp/ # LSP core functionality
│ ├── mod.rs # Main LSP service
│ ├── zeroconf.rs # Zeroconf channel handling
│ └── splicing.rs # Channel splicing
├── api/ # HTTP API
│ ├── mod.rs # API server setup
│ ├── receive.rs # JIT receive endpoints
│ ├── splicing.rs # Splicing endpoints
│ └── health.rs # Health checks
├── db/ # Database
│ ├── mod.rs # Database connection
│ ├── models.rs # Data models
│ └── queries.rs # Database queries
└── rabbitmq.rs # RabbitMQ event consumer
| Setting | Default | Description |
|---|---|---|
jit_receive_base_fee |
1000 | Base service fee in satoshis |
jit_receive_fee_ppm |
5000 | PPM fee on receive amount (5000 = 0.5%) |
jit_min_receive_amount |
10000 | Minimum amount user can request (sats) |
jit_inbound_buffer |
50000 | Extra inbound capacity added to channel (sats) |
fee_confirmation_target |
6 | Confirmation target for fee estimation (blocks) |
Fee Calculation:
Total Fee = base_fee + (amount * ppm / 1,000,000) + (200 vbytes * fee_rate)
Invoice Amount = amount + Total Fee
Channel Capacity = amount + inbound_buffer
- Basic project structure
- LDK Server integration (gRPC client)
- Configuration system
- Zeroconf channel support
- Channel splicing support
- JIT Receive (JIT liquidity)
- Dynamic fee estimation from Esplora
- Inbound buffer for extra capacity
- RabbitMQ event consumption
- Admin dashboard (web UI)
- Advanced fee scheduling
- Liquidity management tools
- Metrics and monitoring
Contributions are welcome! Please read our Contributing Guide for details.
This project is licensed under the MIT OR Apache-2.0 license.