Інтеграція LNURL-протоколу
Lightning Network вирішила проблему швидкості Bitcoin-платежів, але створила нову: користувачу потрібно скопіювати invoice з кошелька продавця у свій кошельок — це незручно. LNURL — це набір протоколів поверх Lightning, які дозволяють кошельку автоматично запросити invoice через HTTP, просканувавши QR-код один раз. З точки зору UX це наближається до звичайних платіжних систем.
Сімейство LNURL-протоколів
LNURL — не один протокол, а кілька спеціфікацій (LUD — Lightning URL Definitions). Кожен вирішує свою задачу:
| LUD | Протокол | Призначение |
|---|---|---|
| LUD-01 | LNURL-pay | Оплата: кошельок запитує invoice у сервера продавця |
| LUD-03 | LNURL-withdraw | Вивід: кошельок отримує кошти по посиланню |
| LUD-04 | LNURL-auth | Аутентифікація через Lightning ключ (passwordless login) |
| LUD-06 | LNURL-channel | Відкриття каналу |
| LUD-12 | Lightning Address | Формат [email protected] для LNURL-pay |
Для приєму платежів важливі прежде всього LNURL-pay та Lightning Address.
Як працює LNURL-pay
Весь процес — два HTTP запити між кошельком та сервером:
Крок 1. Користувач сканує QR. Кошельок бачить lnurl1dp68gurn8ghj7um9wfmxjcm99e3k7mf0v9cxj0m385ekvcenxc6r2c35xvukxefcv5mkvv34x5ekzd3ev56nyd3hhgarjv4ehcmn9wsh8xmmrd9skcnjv... (bech32 encoded HTTPS URL). Кошельок декодує та робить GET на цей URL.
Крок 2. Сервер повертає метаданні:
{
"tag": "payRequest",
"callback": "https://merchant.com/lnurl/pay/invoice",
"minSendable": 1000,
"maxSendable": 100000000,
"metadata": "[[\"text/plain\",\"Payment to My Shop\"],[\"image/png;base64\",\"iVBORw0...\"]]"
}
Крок 3. Користувач вводить суму. Кошельок робить GET на callback з параметром amount (у millisatoshi):
GET https://merchant.com/lnurl/pay/invoice?amount=10000
Крок 4. Сервер генерує Lightning invoice через свою LN-ноду та повертає:
{
"pr": "lnbc100n1pj...",
"routes": [],
"successAction": {
"tag": "message",
"message": "Payment confirmed! Order #12345"
}
}
Крок 5. Кошельок оплачує invoice. Після успішної оплати показує successAction.
Реалізація LNURL-pay сервера
Потрібна Lightning нода (LND або Core Lightning) для генерації invoice. Приклад на Node.js з LND через gRPC:
import express from 'express';
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import { bech32 } from 'bech32';
const app = express();
// LNURL-pay крок 1: метаданні
app.get('/lnurl/pay/:paymentId', async (req, res) => {
const { paymentId } = req.params;
const callbackUrl = `https://${req.hostname}/lnurl/pay/${paymentId}/invoice`;
// Кодуємо URL в lnurl bech32 (для QR коду)
const lnurlEncoded = encodeLnurl(callbackUrl);
res.json({
tag: 'payRequest',
callback: callbackUrl,
minSendable: 1000, // 1 sat у millisatoshi
maxSendable: 100_000_000, // 0.001 BTC
metadata: JSON.stringify([
['text/plain', `Payment for order ${paymentId}`],
]),
});
});
// LNURL-pay крок 2: генерація invoice
app.get('/lnurl/pay/:paymentId/invoice', async (req, res) => {
const { paymentId } = req.params;
const amountMsat = parseInt(req.query.amount as string);
if (!amountMsat || amountMsat < 1000) {
return res.status(400).json({ status: 'ERROR', reason: 'Invalid amount' });
}
try {
const invoice = await lndClient.addInvoice({
value_msat: amountMsat,
memo: `Order ${paymentId}`,
expiry: 3600, // 1 година
});
// Зберігаємо в БД: зв'язок invoice з paymentId
await db.saveInvoice({
paymentHash: invoice.r_hash,
paymentId,
amountMsat,
});
res.json({
pr: invoice.payment_request,
routes: [],
successAction: {
tag: 'message',
message: `Order ${paymentId} confirmed!`,
},
});
} catch (err) {
res.status(500).json({ status: 'ERROR', reason: 'Failed to generate invoice' });
}
});
function encodeLnurl(url: string): string {
const words = bech32.toWords(Buffer.from(url, 'utf8'));
return bech32.encode('lnurl', words, 1023).toUpperCase();
}
Lightning Address: [email protected]
Lightning Address (LUD-12) — найзручніший UX. Замість QR коду користувач вводить адресу як email. Кошельок автоматично робить запрос на https://domain.com/.well-known/lnurlp/username.
// Маршрут для Lightning Address
app.get('/.well-known/lnurlp/:username', async (req, res) => {
const { username } = req.params;
const user = await db.getUserByLnAddress(username);
if (!user) {
return res.status(404).json({ status: 'ERROR', reason: 'User not found' });
}
res.json({
tag: 'payRequest',
callback: `https://${req.hostname}/lnurl/lightning-address/${username}`,
minSendable: 1000,
maxSendable: 10_000_000_000,
metadata: JSON.stringify([
['text/identifier', `${username}@${req.hostname}`],
['text/plain', `Payment to ${username}`],
]),
commentAllowed: 144, // підтримка коментарів до 144 символів
});
});
Після цього [email protected] працює як Lightning Address у будь-якому сумісному кошельку (Phoenix, Wallet of Satoshi, Zeus, Breez).
LNURL-auth: passwordless логін
LNURL-auth дозволяє користувачам логінитися через Lightning кошельок без пароля. Кошельок підписує challenge приватним ключем, похідним від Lightning seed.
import crypto from 'crypto';
// Генерація challenge для логіну
app.get('/auth/lnurl', (req, res) => {
const k1 = crypto.randomBytes(32).toString('hex');
// Зберігаємо k1 в Redis з TTL 5 хвилин
redis.setex(`lnurl_auth:${k1}`, 300, 'pending');
const lnurlAuthUrl = `https://${req.hostname}/auth/callback?tag=login&k1=${k1}`;
const encoded = encodeLnurl(lnurlAuthUrl);
res.json({ lnurl: encoded, k1 });
});
// Callback від кошелька (підпис k1)
app.get('/auth/callback', async (req, res) => {
const { k1, sig, key } = req.query as Record<string, string>;
const status = await redis.get(`lnurl_auth:${k1}`);
if (!status) {
return res.json({ status: 'ERROR', reason: 'Unknown k1' });
}
// Верифікація ECDSA підпису (secp256k1)
const isValid = verifyLnurlAuthSignature(k1, sig, key);
if (!isValid) {
return res.json({ status: 'ERROR', reason: 'Invalid signature' });
}
// Помічаємо k1 як аутентифіцирований з публічним ключем
await redis.setex(`lnurl_auth:${k1}`, 300, `authenticated:${key}`);
res.json({ status: 'OK' });
});
Фронтенд polling-ом перевіряє статус k1 — як тільки кошельок підписав, користувач залогінен.
Інфраструктурні вимоги
Lightning нода — обов'язкова. Варіанти: LND (Go, найпоширеніша, gRPC API), Core Lightning / CLN (C, UNIX socket + REST), Eclair (Scala, використовується Acinq/Phoenix). Для production: виділений VPS з 4GB+ RAM, SSD, стабільним інтернетом. Нода повинна мати входящу ліквідність для приєму платежів.
Hosted рішення для швидкого старту: Voltage.cloud (managed LND), Alby Hub (self-custody але спрощено), Strike API (custodial, але не потрібна своя нода). Для серйозного використання з великими обсягами — тільки власна нода.
TLS та домен обов'язкові: LNURL вимагає HTTPS. Самопідписаний сертифікат не пройде — потрібен Let's Encrypt або аналог.
Моніторинг: channel balance (алерт при < 10% входящої ліквідності), invoice expiry, failed payment attempts. LND Metrics експортує Prometheus-сумісні метрики з коробки.







