Розробка Lightning-бота для Telegram
Lightning Network — це не просто «швидкий Bitcoin». Це окремий протокол зі своєю моделлю стану, платіжними каналами, routing-алгоритмами та специфічними видами атак. Telegram-бот поверх Lightning — це UI-шар над цією інфраструктурою, і помилка в інтеграції коштує користувачам реальних грошей.
Типичний запит: «зробіть бота, щоб користувачі могли отримувати та відправляти сатоші». За цим скриває вибір архітектури ноди, модель кастодіального зберігання, управління ліквідністю каналів та логіка обробки неудачних платежів.
Архітектура: кастодіальна vs non-custodial
Це перший та найважливіший вибір, який визначає все решту.
Кастодіальна модель
Бот тримає єдину Lightning-ноду, користувачі — це просто записи в базі даних з балансами. Платежі між користувачами всередині бота — off-chain операції всередину вашої БД, без реальних Lightning-транзакцій.
Переваги: немає проблем із routing, миттєві внутрішні переводи, проста реалізація. Недостатки: ви кастодіан, вимагається ліцензія у більшості юрисдикцій, користувачі вам довіряють — як біржа, тільки менше регулювання.
Архітектура:
Telegram Bot → Node.js сервіс → PostgreSQL (баланси) → LND/CLN нода (для зовнішніх платежів)
Внутрішній переведення — транзакція в PostgreSQL. Зовнішній вивід — реальний Lightning invoice через ноду. Поповнення — генерація invoice через ноду, мониторинг платежу.
Non-custodial через LSP
Lightning Service Provider (LSP) керує каналами користувача. Користувач тримає власні ключі, LSP забезпечує ліквідність. Протокол LSPS0-LSPS2 стандартизує це взаємодію.
Реалізація складніша: потрібно інтегруватися з LSP API (Breez SDK, LDK-node), керувати channel open/close, пояснювати користувачам концепцію каналів. Для Telegram-бота це зазвичай надмірно.
Практичний вибір для більшості проектів: кастодіальна модель з прозорою комунікацією користувачам, що бот кастодіальний.
Lightning-нода: LND vs Core Lightning
LND (Lightning Network Daemon)
Go, розробка Lightning Labs. gRPC API з хорошою документацією. Найпопулярніший для інтеграцій — більше SDK та прикладів.
import { AuthenticatedLnd } from "lightning";
import { createInvoice, payViaRoutes, getWalletInfo } from "lightning";
// Створення invoice для поповнення
const { request, id } = await createInvoice({
lnd,
tokens: 10000, // сатоші
description: `Deposit for user ${userId}`,
expires_at: new Date(Date.now() + 3600 * 1000).toISOString(),
});
Пакет lightning (npm) — типізована обгортка над LND gRPC. Значно зручніше raw gRPC.
Core Lightning (CLN)
C, розробка Blockstream. Більш модульна архітектура через plugin-систему. JSON-RPC API. Менше екосистема, але продуктивніше на великій кількості каналів.
Для більшості Telegram-ботів з обсягом до 10k користувачів — різниці немає. Вибирайте по знайомості стека та наявності документації.
Управління ліквідністю каналів
Головна операційна проблема Lightning-бота — ліквідність. У Lightning-каналу є inbound capacity (скільки можна отримати) та outbound capacity (скільки можна відправити). Після відкриття каналу вся ліквідність на вашій стороні — ви можете відправляти, але не отримувати.
Для бота, що приймає поповнення користувачів, потрібна inbound ліквідність:
Покупка inbound liquidity — сервіси типу Bitrefill Thor, Lightning Pool (LND), або ручні договорівки з routing-нодами. Ви платите провайдеру, він відкриває канал до вас з балансом на його стороні.
Circular rebalancing — якщо баланс змістився (багато outbound операцій), можна провести круговую платіжку через мережу: відправити через A → B → C → собі. Платите routing fee, але перебалансуєте канал.
// Мониторинг баланса каналу
const channels = await getChannels({ lnd });
for (const channel of channels.channels) {
const localRatio = channel.local_balance / channel.capacity;
if (localRatio < 0.2) {
// Мало outbound — потрібна rebalance
await alertOps(`Channel ${channel.id}: low outbound liquidity`);
}
if (localRatio > 0.8) {
// Мало inbound — не можемо приймати платежі
await alertOps(`Channel ${channel.id}: low inbound liquidity`);
}
}
Для production бота потрібен автоматичний rebalancing або інтеграція з сервісом ліквідності.
Обробка платежів у боті
Webhook vs polling для Telegram
Webhook переважніший: менше latency, немає ліміту на частоту запитів. Вимагає публічний HTTPS endpoint. Для production — обов'язково webhook.
import { Telegraf } from "telegraf";
const bot = new Telegraf(BOT_TOKEN);
bot.command("deposit", async (ctx) => {
const userId = ctx.from.id.toString();
const invoice = await createDepositInvoice(userId, 0); // будь-яка сума
await ctx.reply(
`Ваш Lightning invoice для поповнення:\n\n\`${invoice.request}\`\n\nДійсний 1 годину.`,
{ parse_mode: "Markdown" }
);
});
Мониторинг вхідних платежів
LND надає subscribeToInvoices — stream, що нотифікує при кожному settlement:
const sub = subscribeToInvoices({ lnd });
sub.on("invoice_updated", async (invoice) => {
if (!invoice.is_confirmed) return;
const userId = await getUserByInvoiceId(invoice.id);
if (!userId) return;
await db.transaction(async (trx) => {
await trx("users")
.where({ id: userId })
.increment("balance_sats", invoice.received);
await trx("transactions").insert({
user_id: userId,
type: "deposit",
amount_sats: invoice.received,
lightning_id: invoice.id,
confirmed_at: new Date(),
});
});
await bot.telegram.sendMessage(userId,
`Отримано ${invoice.received} sat. Баланс оновлено.`
);
});
Важливо: операція повинна бути ідемпотентною — якщо воркер упав та перезапустився, повторна обробка одного invoice не повинна зачислити деньги двічі. lightning_id з UNIQUE constraint — проста захист.
Відправлення платежів (вивід)
Користувач вставляє invoice, бот його оплачує:
async function processWithdrawal(userId: string, invoiceStr: string) {
const decoded = await decodePaymentRequest({ lnd, request: invoiceStr });
// Перевірки
if (decoded.tokens > user.balance_sats) throw new Error("Недостатньо коштів");
if (decoded.expires_at < new Date()) throw new Error("Invoice мав");
// Резервуємо баланс ДО відправки
await db("users")
.where({ id: userId })
.decrement("balance_sats", decoded.tokens);
try {
const payment = await pay({ lnd, request: invoiceStr });
// Успішно, записуємо транзакцію
await recordWithdrawal(userId, decoded.tokens, payment.id);
} catch (err) {
// Платіж не пройшов — повертаємо баланс
await db("users")
.where({ id: userId })
.increment("balance_sats", decoded.tokens);
throw new Error(`Платіж не пройшов: ${err.message}`);
}
}
Порядок операцій критичен: спочатку резервуємо, потім відправляємо. Іначе — double spend при паралельних запитах. Повернення при помилці — обов'язково.
Специфічні атаки на Lightning-ботів
Invoice replay: користувач присилає один і той же invoice двічі. Захист — зберігати всі оброблені payment hashes, перевіряти перед обробкою.
Amount mismatch на депозитах: користувач створив invoice на 1000 sat, хтось відправив 999 sat (partial amount). LND за замовчуванням приймає будь-яку суму якщо invoice без tokens. Завжди створюйте amount-less invoice або явно указуйте amount та перевіряйте received.
Timing attack на вивід: паралельні запити на вивід одночасно читають баланс та обидва бачать достатньо коштів. Захист — оптимістичний лок через UPDATE users SET balance = balance - X WHERE balance >= X AND id = Y, перевіряти affected rows.
Стек та деплой
- Нода: LND 0.18.x на окремому сервері/VPS, Bitcoin full node або Neutrino (легкий клієнт)
- Backend: Node.js + TypeScript + Fastify
- База даних: PostgreSQL, таблиці: users, invoices, transactions, channels_log
- Мониторинг: Grafana + LND Prometheus exporter, алерти на channel offline, низьку ліквідність
- Резервні копії: SCB (Static Channel Backups) автоматично після кожної зміни каналу — це обов'язково, без цього при краху ноди потеряєте середства користувачів
Для запуску Lightning-ноди потрібно закласти на канальний депозит: мінімум 0.1 BTC для малого бота, 0.5–1 BTC для production з нормальною ліквідністю.
Сроки розробки
MVP з кастодіальною моделлю, deposit/withdraw, базовим p2p переводом — 3–4 тижні. Production з авто-rebalancing, мониторингом ліквідності, multi-channel управлінням, повним аудитом транзакцій — 8–12 тижнів.







