// 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; }