misc docs/helpers

This commit is contained in:
padreug 2025-10-14 00:11:03 +02:00
parent ee5ea3d876
commit e65d5d0121
7 changed files with 704 additions and 0 deletions

View file

@ -0,0 +1,460 @@
# LNbits WebSocket Implementation Guide
## Overview
LNbits provides real-time WebSocket connections for monitoring wallet status, payment confirmations, and transaction updates. This guide covers how to implement and use these WebSocket connections in your applications.
## WebSocket Endpoints
### 1. Payment Monitoring WebSocket
- **URL**: `ws://localhost:5006/api/v1/ws/{wallet_inkey}`
- **HTTPS**: `wss://your-domain.com/api/v1/ws/{wallet_inkey}`
- **Purpose**: Real-time payment notifications and wallet updates
### 2. Generic WebSocket Communication
- **URL**: `ws://localhost:5006/api/v1/ws/{item_id}`
- **Purpose**: Custom real-time communication channels
## Client-Side Implementation
### JavaScript/Browser Implementation
#### Basic WebSocket Connection
```javascript
// Construct WebSocket URL
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const websocketUrl = `${protocol}//${window.location.host}/api/v1/ws`
// Connect to payment monitoring
const ws = new WebSocket(`${websocketUrl}/${wallet.inkey}`)
// Handle incoming messages
ws.onmessage = (event) => {
const data = JSON.parse(event.data)
console.log('Received:', data)
if (data.payment) {
handlePaymentReceived(data.payment)
}
}
// Handle connection events
ws.onopen = () => {
console.log('WebSocket connected')
}
ws.onclose = () => {
console.log('WebSocket disconnected')
}
ws.onerror = (error) => {
console.error('WebSocket error:', error)
}
```
#### Using LNbits Built-in Event System
```javascript
// Using the built-in LNbits event system
LNbits.events.onInvoicePaid(wallet, (data) => {
if (data.payment) {
console.log('Payment confirmed:', data.payment)
// Update UI
updateWalletBalance(data.payment.amount)
showPaymentNotification(data.payment)
}
})
```
#### Vue.js Implementation Example
```javascript
// Vue component method
initWebSocket() {
const protocol = location.protocol === 'http:' ? 'ws://' : 'wss://'
const wsUrl = `${protocol}${document.domain}:${location.port}/api/v1/ws/${this.wallet.inkey}`
this.ws = new WebSocket(wsUrl)
this.ws.addEventListener('message', async ({ data }) => {
const response = JSON.parse(data.toString())
if (response.payment) {
// Handle payment update
await this.handlePaymentUpdate(response.payment)
}
})
this.ws.addEventListener('open', () => {
this.connectionStatus = 'connected'
})
this.ws.addEventListener('close', () => {
this.connectionStatus = 'disconnected'
// Implement reconnection logic
setTimeout(() => this.initWebSocket(), 5000)
})
}
```
### Python Client Implementation
```python
import asyncio
import websockets
import json
async def listen_to_wallet(wallet_inkey, base_url="ws://localhost:5006"):
uri = f"{base_url}/api/v1/ws/{wallet_inkey}"
try:
async with websockets.connect(uri) as websocket:
print(f"Connected to WebSocket: {uri}")
async for message in websocket:
data = json.loads(message)
if 'payment' in data:
payment = data['payment']
print(f"Payment received: {payment['amount']} sat")
print(f"Payment hash: {payment['payment_hash']}")
# Process payment
await handle_payment_received(payment)
except websockets.exceptions.ConnectionClosed:
print("WebSocket connection closed")
except Exception as e:
print(f"WebSocket error: {e}")
async def handle_payment_received(payment):
"""Process incoming payment"""
# Update database
# Send notifications
# Update application state
pass
# Run the WebSocket listener
if __name__ == "__main__":
wallet_inkey = "your_wallet_inkey_here"
asyncio.run(listen_to_wallet(wallet_inkey))
```
### Node.js Client Implementation
```javascript
const WebSocket = require('ws')
class LNbitsWebSocketClient {
constructor(walletInkey, baseUrl = 'ws://localhost:5006') {
this.walletInkey = walletInkey
this.baseUrl = baseUrl
this.ws = null
this.reconnectInterval = 5000
}
connect() {
const url = `${this.baseUrl}/api/v1/ws/${this.walletInkey}`
this.ws = new WebSocket(url)
this.ws.on('open', () => {
console.log(`Connected to LNbits WebSocket: ${url}`)
})
this.ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString())
this.handleMessage(message)
} catch (error) {
console.error('Error parsing WebSocket message:', error)
}
})
this.ws.on('close', () => {
console.log('WebSocket connection closed. Reconnecting...')
setTimeout(() => this.connect(), this.reconnectInterval)
})
this.ws.on('error', (error) => {
console.error('WebSocket error:', error)
})
}
handleMessage(message) {
if (message.payment) {
console.log('Payment received:', message.payment)
this.onPaymentReceived(message.payment)
}
}
onPaymentReceived(payment) {
// Override this method to handle payments
console.log(`Received ${payment.amount} sat`)
}
disconnect() {
if (this.ws) {
this.ws.close()
}
}
}
// Usage
const client = new LNbitsWebSocketClient('your_wallet_inkey_here')
client.onPaymentReceived = (payment) => {
// Custom payment handling
console.log(`Processing payment: ${payment.payment_hash}`)
}
client.connect()
```
## Server-Side Implementation (LNbits Extensions)
### Sending WebSocket Updates
```python
from lnbits.core.services import websocket_manager
async def notify_wallet_update(wallet_inkey: str, payment_data: dict):
"""Send payment update to connected WebSocket clients"""
message = {
"payment": payment_data,
"timestamp": int(time.time())
}
await websocket_manager.send(wallet_inkey, json.dumps(message))
# Example usage in payment processing
async def process_payment_confirmation(payment_hash: str):
payment = await get_payment(payment_hash)
if payment.wallet:
await notify_wallet_update(payment.wallet, {
"payment_hash": payment.payment_hash,
"amount": payment.amount,
"memo": payment.memo,
"status": "confirmed"
})
```
### HTTP Endpoints for WebSocket Updates
```python
# Send data via GET request
@router.get("/notify/{wallet_inkey}/{message}")
async def notify_wallet_get(wallet_inkey: str, message: str):
await websocket_manager.send(wallet_inkey, message)
return {"sent": True, "message": message}
# Send data via POST request
@router.post("/notify/{wallet_inkey}")
async def notify_wallet_post(wallet_inkey: str, data: str):
await websocket_manager.send(wallet_inkey, data)
return {"sent": True, "data": data}
```
## Message Format
### Payment Notification Message
```json
{
"payment": {
"payment_hash": "abc123...",
"amount": 1000,
"memo": "Test payment",
"status": "confirmed",
"timestamp": 1640995200,
"fee": 1,
"wallet_id": "wallet_uuid"
}
}
```
### Custom Message Format
```json
{
"type": "balance_update",
"wallet_id": "wallet_uuid",
"balance": 50000,
"timestamp": 1640995200
}
```
## Best Practices
### 1. Connection Management
- Implement automatic reconnection logic
- Handle connection timeouts gracefully
- Use exponential backoff for reconnection attempts
### 2. Error Handling
```javascript
class WebSocketManager {
constructor(walletInkey) {
this.walletInkey = walletInkey
this.maxReconnectAttempts = 10
this.reconnectAttempts = 0
this.reconnectDelay = 1000
}
connect() {
try {
this.ws = new WebSocket(this.getWebSocketUrl())
this.setupEventHandlers()
} catch (error) {
this.handleConnectionError(error)
}
}
handleConnectionError(error) {
console.error('WebSocket connection error:', error)
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1)
setTimeout(() => {
console.log(`Reconnection attempt ${this.reconnectAttempts}`)
this.connect()
}, delay)
} else {
console.error('Max reconnection attempts reached')
}
}
}
```
### 3. Message Validation
```javascript
function validatePaymentMessage(data) {
if (!data.payment) return false
const payment = data.payment
return (
typeof payment.payment_hash === 'string' &&
typeof payment.amount === 'number' &&
payment.amount > 0 &&
['pending', 'confirmed', 'failed'].includes(payment.status)
)
}
```
### 4. Security Considerations
- Use HTTPS/WSS in production
- Validate wallet permissions before connecting
- Implement rate limiting for WebSocket connections
- Never expose admin keys through WebSocket messages
## Testing WebSocket Connections
### Using wscat (Command Line Tool)
```bash
# Install wscat
npm install -g wscat
# Connect to WebSocket
wscat -c ws://localhost:5006/api/v1/ws/your_wallet_inkey
# Test with SSL
wscat -c wss://your-domain.com/api/v1/ws/your_wallet_inkey
```
### Browser Console Testing
```javascript
// Open browser console and run:
const ws = new WebSocket('ws://localhost:5006/api/v1/ws/your_wallet_inkey')
ws.onmessage = (e) => console.log('Received:', JSON.parse(e.data))
ws.onopen = () => console.log('Connected')
ws.onclose = () => console.log('Disconnected')
```
### Sending Test Messages
```bash
# Using curl to trigger WebSocket message
curl "http://localhost:5006/api/v1/ws/your_wallet_inkey/test_message"
# Using POST
curl -X POST "http://localhost:5006/api/v1/ws/your_wallet_inkey" \
-H "Content-Type: application/json" \
-d '"test message data"'
```
## Troubleshooting
### Common Issues
1. **Connection Refused**
- Verify LNbits server is running on correct port
- Check firewall settings
- Ensure WebSocket endpoint is enabled
2. **Authentication Errors**
- Verify wallet inkey is correct
- Check wallet permissions
- Ensure wallet exists and is active
3. **Message Not Received**
- Check WebSocket connection status
- Verify message format
- Test with browser dev tools
4. **Frequent Disconnections**
- Implement proper reconnection logic
- Check network stability
- Monitor server logs for errors
### Debug Logging
```javascript
// Enable verbose WebSocket logging
const ws = new WebSocket(wsUrl)
ws.addEventListener('open', (event) => {
console.log('WebSocket opened:', event)
})
ws.addEventListener('close', (event) => {
console.log('WebSocket closed:', event.code, event.reason)
})
ws.addEventListener('error', (event) => {
console.error('WebSocket error:', event)
})
```
## Production Deployment
### Nginx Configuration
```nginx
location /api/v1/ws/ {
proxy_pass http://localhost:5006;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
```
### SSL/TLS Configuration
```nginx
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location /api/v1/ws/ {
proxy_pass http://localhost:5006;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# ... other headers
}
}
```
## Conclusion
LNbits WebSocket implementation provides a robust foundation for real-time wallet monitoring and payment processing. By following this guide, you can implement reliable WebSocket connections that enhance user experience with instant payment notifications and live wallet updates.
Remember to implement proper error handling, reconnection logic, and security measures when deploying to production environments.

Binary file not shown.

View file

@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""
Bulk publish Nostr profiles from CSV file
Usage: python publish_profiles_from_csv.py <csv_file> <relay_url>
"""
import sys
import csv
import json
import time
import hashlib
import secp256k1
from websocket import create_connection
def sign_event(event, public_key_hex, private_key):
"""Sign a Nostr event"""
# Create the signature data
signature_data = json.dumps([
0,
public_key_hex,
event["created_at"],
event["kind"],
event["tags"],
event["content"]
], separators=(',', ':'), ensure_ascii=False)
# Calculate event ID
event_id = hashlib.sha256(signature_data.encode()).hexdigest()
event["id"] = event_id
event["pubkey"] = public_key_hex
# Sign the event
signature = private_key.schnorr_sign(bytes.fromhex(event_id), None, raw=True).hex()
event["sig"] = signature
return event
def publish_profile_metadata(private_key_hex, profile_name, relay_url):
"""Publish a Nostr kind 0 metadata event"""
try:
# Convert hex private key to secp256k1 PrivateKey and get public key
private_key = secp256k1.PrivateKey(bytes.fromhex(private_key_hex))
public_key_hex = private_key.pubkey.serialize()[1:].hex() # Remove the 0x02 prefix
print(f"Publishing profile for: {profile_name}")
print(f" Public key: {public_key_hex}")
# Create Nostr kind 0 metadata event
metadata = {
"name": profile_name,
"display_name": profile_name,
"about": f"Profile for {profile_name}"
}
event = {
"kind": 0,
"created_at": int(time.time()),
"tags": [],
"content": json.dumps(metadata, separators=(',', ':'))
}
# Sign the event
signed_event = sign_event(event, public_key_hex, private_key)
# Connect to relay and publish
ws = create_connection(relay_url, timeout=15)
# Send the event
event_message = f'["EVENT",{json.dumps(signed_event)}]'
ws.send(event_message)
# Wait for response
try:
response = ws.recv()
print(f" ✅ Published successfully: {response}")
except Exception as e:
print(f" ⚠️ No immediate response: {e}")
# Close connection
ws.close()
return True
except Exception as e:
print(f" ❌ Failed to publish: {e}")
return False
def main():
if len(sys.argv) != 3:
print("Usage: python publish_profiles_from_csv.py <csv_file> <relay_url>")
print("Example: python publish_profiles_from_csv.py publish-these.csv wss://relay.example.com")
sys.exit(1)
csv_file = sys.argv[1]
relay_url = sys.argv[2]
print(f"Publishing profiles from {csv_file} to {relay_url}")
print("=" * 60)
published_count = 0
failed_count = 0
try:
with open(csv_file, 'r') as file:
csv_reader = csv.DictReader(file)
for row_num, row in enumerate(csv_reader, start=2): # start=2 because header is line 1
username = row['username'].strip()
private_key_hex = row['prvkey'].strip()
if not username or not private_key_hex:
print(f"Row {row_num}: Skipping empty row")
continue
print(f"\nRow {row_num}: Processing {username}")
success = publish_profile_metadata(private_key_hex, username, relay_url)
if success:
published_count += 1
else:
failed_count += 1
# Small delay between publishes to be nice to the relay
time.sleep(1)
except FileNotFoundError:
print(f"❌ File not found: {csv_file}")
sys.exit(1)
except Exception as e:
print(f"❌ Error processing CSV: {e}")
sys.exit(1)
print("\n" + "=" * 60)
print(f"Publishing complete!")
print(f"✅ Successfully published: {published_count}")
print(f"❌ Failed: {failed_count}")
print(f"📊 Total processed: {published_count + failed_count}")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""
Manual Nostr profile metadata publisher
Usage: python test_nostr_connection.py <private_key_hex> <profile_name> <relay_url>
"""
import sys
import json
import time
import hashlib
import secp256k1
from websocket import create_connection
def sign_event(event, public_key_hex, private_key):
"""Sign a Nostr event"""
# Create the signature data
signature_data = json.dumps([
0,
public_key_hex,
event["created_at"],
event["kind"],
event["tags"],
event["content"]
], separators=(',', ':'), ensure_ascii=False)
# Calculate event ID
event_id = hashlib.sha256(signature_data.encode()).hexdigest()
event["id"] = event_id
event["pubkey"] = public_key_hex
# Sign the event
signature = private_key.schnorr_sign(bytes.fromhex(event_id), None, raw=True).hex()
event["sig"] = signature
return event
def publish_profile_metadata(private_key_hex, profile_name, relay_url):
"""Publish a Nostr kind 0 metadata event"""
try:
# Convert hex private key to secp256k1 PrivateKey and get public key
private_key = secp256k1.PrivateKey(bytes.fromhex(private_key_hex))
public_key_hex = private_key.pubkey.serialize()[1:].hex() # Remove the 0x02 prefix
print(f"Private key: {private_key_hex}")
print(f"Public key: {public_key_hex}")
print(f"Profile name: {profile_name}")
print(f"Relay URL: {relay_url}")
print()
# Create Nostr kind 0 metadata event
metadata = {
"name": profile_name,
"display_name": profile_name,
"about": f"Manual profile update for {profile_name}"
}
event = {
"kind": 0,
"created_at": int(time.time()),
"tags": [],
"content": json.dumps(metadata, separators=(',', ':'))
}
# Sign the event
signed_event = sign_event(event, public_key_hex, private_key)
print(f"Signed event: {json.dumps(signed_event, indent=2)}")
print()
# Connect to relay and publish
print(f"Connecting to relay: {relay_url}")
ws = create_connection(relay_url, timeout=15)
print("✅ Connected successfully!")
# Send the event
event_message = f'["EVENT",{json.dumps(signed_event)}]'
print(f"Sending EVENT: {event_message}")
ws.send(event_message)
# Wait for response
try:
response = ws.recv()
print(f"✅ Relay response: {response}")
except Exception as e:
print(f"⚠️ No immediate response: {e}")
# Close connection
ws.close()
print("✅ Connection closed successfully")
print(f"Profile metadata for '{profile_name}' published successfully!")
except Exception as e:
print(f"❌ Failed to publish profile: {e}")
raise
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Usage: python test_nostr_connection.py <private_key_hex> <profile_name> <relay_url>")
print("Example: python test_nostr_connection.py abc123... 'My Name' wss://relay.example.com")
sys.exit(1)
private_key_hex = sys.argv[1]
profile_name = sys.argv[2]
relay_url = sys.argv[3]
publish_profile_metadata(private_key_hex, profile_name, relay_url)