Files

198 lines
7.3 KiB
JavaScript
Raw Permalink Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:20b466379c38e65d9cee77881aaa8890de723421f475561a2047de7bd3f03d95
import * as lnd from './lnd.js';
function toHex(v) {
const s = String(v || '');
if (!s) return '';
if (/^[0-9a-fA-F]+$/.test(s) && s.length % 2 === 0) return s.toLowerCase();
const bin = atob(s.replace(/-/g, '+').replace(/_/g, '/'));
let h = '';
for (let i = 0; i < bin.length; i++) h += bin.charCodeAt(i).toString(16).padStart(2, '0');
return h;
}
const toSats = v => Math.round(Number(v || 0));
const secToIso = s => (Number(s) ? new Date(Number(s) * 1000).toISOString() : null);
const nsToIso = ns => (Number(ns) ? new Date(Math.floor(Number(ns) / 1e6)).toISOString() : null);
const PAGE = 200;
const MAX_PAGES = 50;
async function fetchPayments(connId) {
const out = [];
let offset = 0;
for (let p = 0; p < MAX_PAGES; p++) {
const res = await lnd.listPayments(connId, { max_payments: PAGE, index_offset: offset, include_incomplete: false });
const batch = (res && res.payments) || [];
out.push(...batch);
const next = Number(res && res.last_index_offset) || 0;
if (!batch.length || next <= offset) break;
offset = next;
}
return out;
}
async function fetchInvoices(connId) {
const out = [];
let offset = 0;
for (let p = 0; p < MAX_PAGES; p++) {
const res = await lnd.listInvoices(connId, { num_max_invoices: PAGE, index_offset: offset });
const batch = (res && res.invoices) || [];
out.push(...batch);
const next = Number(res && res.last_index_offset) || 0;
if (!batch.length || next <= offset) break;
offset = next;
}
return out;
}
async function fetchForwarding(connId) {
const out = [];
let offset = 0;
for (let p = 0; p < MAX_PAGES; p++) {
const res = await lnd.forwardingHistory(connId, { num_max_events: PAGE, index_offset: offset });
const batch = (res && res.forwarding_events) || [];
out.push(...batch);
const next = Number(res && res.last_offset_index) || 0;
if (!batch.length || next <= offset) break;
offset = next;
}
return out;
}
export async function normalizeMovements(connId) {
const [payments, invoices, forwards] = await Promise.all([
fetchPayments(connId), fetchInvoices(connId), fetchForwarding(connId),
]);
const okPayments = payments.filter(p => p.status === 'SUCCEEDED');
const okInvoices = invoices.filter(i => i.state === 'SETTLED' || i.settled === true);
const invoiceHashes = new Set(okInvoices.map(i => toHex(i.r_hash)));
const selfHashes = new Set(okPayments.map(p => toHex(p.payment_hash)).filter(h => invoiceHashes.has(h)));
const movements = [];
for (const p of okPayments) {
const hash = toHex(p.payment_hash);
const isSelf = selfHashes.has(hash);
movements.push({
kind: 'payment',
type: isSelf ? 'transfer' : 'sell',
direction: 'out',
hash,
at: nsToIso(p.creation_time_ns) || secToIso(p.creation_date),
sats: toSats(p.value_sat),
fee_sats: toSats(p.fee_sat),
self: isSelf,
memo: null,
});
}
for (const i of okInvoices) {
const hash = toHex(i.r_hash);
if (selfHashes.has(hash)) continue;
movements.push({
kind: 'invoice',
type: 'buy',
direction: 'in',
hash,
at: secToIso(i.settle_date) || secToIso(i.creation_date),
sats: toSats(i.amt_paid_sat || i.value),
fee_sats: 0,
self: false,
memo: i.memo || null,
});
}
for (const f of forwards) {
const fee = toSats(f.fee);
if (!fee) continue;
movements.push({
kind: 'forward',
type: 'interest',
direction: 'route',
hash: null,
ref: `fwd:${f.timestamp_ns || f.timestamp || ''}:${f.chan_id_in || ''}:${f.chan_id_out || ''}`,
at: nsToIso(f.timestamp_ns) || secToIso(f.timestamp),
sats: fee,
fee_sats: 0,
self: false,
memo: null,
});
}
movements.sort((a, b) => (a.at || '').localeCompare(b.at || ''));
return movements;
}
export function nwcMemo(x) {
const beschrijving = typeof x?.description === 'string' ? x.description : '';
const zap = x?.metadata && typeof x.metadata.nostr === 'object' ? x.metadata.nostr : null;
if (zap && !beschrijving.trim().startsWith('{')) return { kind: 9734, ...zap };
return beschrijving || (typeof x?.metadata?.comment === 'string' ? x.metadata.comment : null) || null;
}
export async function normalizeNwcMovements(sourceId) {
const nwc = await import('./nwc.js');
const all = [];
let offset = 0;
for (let p = 0; p < MAX_PAGES; p++) {
const res = await nwc.listTransactions(sourceId, { limit: PAGE, offset, unpaid: false });
const batch = (res && res.transactions) || [];
all.push(...batch);
if (batch.length < PAGE) break;
offset += batch.length;
}
const inHashes = new Set(all.filter(x => x.type === 'incoming').map(x => String(x.payment_hash || '')).filter(Boolean));
const selfHashes = new Set(all.filter(x => x.type === 'outgoing').map(x => String(x.payment_hash || '')).filter(h => inHashes.has(h)));
const movements = [];
for (const x of all) {
const hash = String(x.payment_hash || '');
const isSelf = !!hash && selfHashes.has(hash);
if (x.type === 'incoming' && isSelf) continue;
movements.push({
kind: 'nwc',
type: isSelf ? 'transfer' : (x.type === 'incoming' ? 'buy' : 'sell'),
direction: x.type === 'incoming' ? 'in' : 'out',
hash: hash || null,
at: secToIso(x.settled_at) || secToIso(x.created_at),
sats: Math.round(Number(x.amount || 0) / 1000),
fee_sats: Math.round(Number(x.fees_paid || 0) / 1000),
self: isSelf,
memo: nwcMemo(x),
});
}
movements.sort((a, b) => (a.at || '').localeCompare(b.at || ''));
return movements;
}
export async function nodeBalance(connId) {
const [chain, chan, chans] = await Promise.all([
lnd.blockchainBalance(connId), lnd.channelBalance(connId), lnd.listChannels(connId),
]);
const amt = a => toSats(a && typeof a === 'object' ? a.sat : a);
const onchainConfirmed = toSats(chain && chain.confirmed_balance);
const onchainUnconfirmed = toSats(chain && chain.unconfirmed_balance);
const channelLocal = (chans && Array.isArray(chans.channels) ? chans.channels : [])
.reduce((s, ch) => s + toSats(ch && ch.local_balance) + (ch && ch.initiator ? toSats(ch.commit_fee) : 0), 0);
const unsettledLocal = amt(chan && chan.unsettled_local_balance);
const pendingOpen = amt(chan && chan.pending_open_local_balance) || toSats(chan && chan.pending_open_balance);
const anchor = onchainConfirmed + channelLocal;
const inflight = onchainUnconfirmed + unsettledLocal + pendingOpen;
return { onchainConfirmed, onchainUnconfirmed, channelLocal, unsettledLocal, pendingOpen, anchor, inflight };
}
const ZERO_TXID = /^0+$/;
export async function channelTxids(connId) {
const [open, closed] = await Promise.all([lnd.listChannels(connId), lnd.listClosedChannels(connId)]);
const out = new Set();
const funding = point => String(point || '').split(':')[0].toLowerCase();
for (const ch of (open && open.channels) || []) if (funding(ch.channel_point)) out.add(funding(ch.channel_point));
for (const ch of (closed && closed.channels) || []) {
if (funding(ch.channel_point)) out.add(funding(ch.channel_point));
const sluit = String(ch.closing_tx_hash || '').toLowerCase();
if (sluit && !ZERO_TXID.test(sluit)) out.add(sluit);
}
return out;
}