Интеграция 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. Сервер возвращает metadata:
{
"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: metadata
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-совместимые метрики из коробки.







