// Generated, do not edit. sha256:28bf467843efd410b1dacbe64352649ff4b982578fe5215b9da5061ebec43621 import * as db from '../db.js'; import { nowIso } from './_shared.js'; import { fail } from '../../shared/app-error.js'; import { assertSourceMatchesAccount } from './wallet-sources.js'; import { createLabel } from './labels.js'; import { toSearchName } from '../../shared/search-name.js'; import { CONFIG } from '../../shared/config.js'; import { compactMemo } from '../../shared/zap.js'; const UPSERT = Object.freeze({ onchain: `INSERT INTO WalletTransaction (accountId, sourceId, layer, txid, direction, valueSats, feeSats, blockHeight, blockTime, address, createdAt) VALUES (?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT (accountId, txid) WHERE layer = 'onchain' DO UPDATE SET sourceId=excluded.sourceId, direction=excluded.direction, valueSats=excluded.valueSats, feeSats=excluded.feeSats, blockHeight=excluded.blockHeight, blockTime=excluded.blockTime, address=excluded.address`, lightning: `INSERT INTO WalletTransaction (accountId, sourceId, layer, txid, direction, valueSats, feeSats, blockHeight, blockTime, address, kind, memo, createdAt) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT (sourceId, txid) WHERE layer = 'lightning' DO UPDATE SET direction=excluded.direction, valueSats=excluded.valueSats, feeSats=excluded.feeSats, blockHeight=excluded.blockHeight, blockTime=excluded.blockTime, address=excluded.address, kind=excluded.kind, memo=excluded.memo`, }); const TWIN_LABEL = `UPDATE WalletTransaction SET labelId = ( SELECT twin.labelId FROM WalletTransaction twin WHERE twin.layer = 'lightning' AND twin.accountId = WalletTransaction.accountId AND twin.txid = WalletTransaction.txid AND twin.labelId IS NOT NULL ORDER BY twin.id LIMIT 1) WHERE layer = 'lightning' AND labelId IS NULL AND EXISTS (SELECT 1 FROM WalletTransaction twin WHERE twin.layer = 'lightning' AND twin.accountId = WalletTransaction.accountId AND twin.txid = WalletTransaction.txid AND twin.labelId IS NOT NULL)`; function assertLayerShape(t) { if (!Object.hasOwn(UPSERT, t.layer)) throw new Error(`Unknown wallet movement layer: '${t.layer}'.`); if (t.layer !== 'lightning') return; if (!Number.isInteger(t.sourceId)) throw new Error('A lightning movement needs its source.'); if (!t.blockTime) throw new Error('A lightning movement needs the moment it settled.'); if (t.blockHeight != null) throw new Error('A lightning movement has no block height.'); if (t.kind != null && t.kind !== 'self') throw new Error(`A lightning movement is ordinary or 'self', not '${t.kind}'.`); } export async function upsertWalletTxs(list) { for (const given of list) { const t = { ...given, layer: given.layer ?? 'onchain' }; assertLayerShape(t); assertSourceMatchesAccount('WalletTransaction', t.accountId, t.sourceId); const velden = [t.accountId, t.sourceId ?? null, t.layer, t.txid, t.direction, t.valueSats, t.feeSats ?? null, t.blockHeight ?? null, t.blockTime ?? null, t.address ?? null]; db.run(UPSERT[t.layer], t.layer === 'lightning' ? [...velden, t.kind ?? null, compactMemo(t.memo), nowIso()] : [...velden, nowIso()]); } if (list.some(t => t.layer === 'lightning')) db.run(TWIN_LABEL); await db.persist(); } export function listSourceTxs(sourceId) { return db.all(`SELECT t.*, l.name AS label_name FROM WalletTransaction t LEFT JOIN Label l ON l.id = t.labelId WHERE t.sourceId = ? ORDER BY (t.blockTime IS NULL) DESC, t.blockTime DESC, t.id DESC`, [sourceId]); } const CHANNEL_ROWS = `accountId IN (SELECT value FROM json_each(?1)) AND layer = 'onchain' AND txid IN (SELECT value FROM json_each(?2))`; export async function labelChannelTxs(accountId, txids) { if (!Number.isInteger(accountId)) throw new Error('labelChannelTxs needs an account id.'); if (!Array.isArray(txids) || txids.some(x => typeof x !== 'string' || !/^[0-9a-f]{64}$/.test(x))) { throw new Error('labelChannelTxs takes a list of transaction ids in lowercase hex.'); } if (!txids.length) return 0; const lijst = JSON.stringify(txids); const accounts = JSON.stringify([accountId, ...db.all( `SELECT DISTINCT onchain.accountId FROM WalletSource onchain JOIN WalletSource node ON node.connectionId = onchain.connectionId AND node.kind = 'lnd' WHERE node.accountId = ? AND onchain.kind IN ('xpub','address') AND onchain.accountId <> ?`, [accountId, accountId]).map(r => r.accountId)]); db.run(`UPDATE WalletTransaction SET kind = 'channel' WHERE ${CHANNEL_ROWS} AND kind IS NULL`, [accounts, lijst]); const kandidaten = db.one(`SELECT COUNT(*) AS n FROM WalletTransaction WHERE ${CHANNEL_ROWS} AND labelId IS NULL`, [accounts, lijst]).n; if (!kandidaten) { await db.persist(); return 0; } const naam = CONFIG.LND.CHANNEL_LABEL; const label = db.one(`SELECT id FROM Label WHERE searchName = ?`, [toSearchName(naam)]) || { id: await createLabel({ name: naam }) }; db.run(`UPDATE WalletTransaction SET labelId = ?3 WHERE ${CHANNEL_ROWS} AND labelId IS NULL`, [accounts, lijst, label.id]); await db.persist(); return kandidaten; } export async function setTxLabels(ids, labelId) { if (!Array.isArray(ids) || ids.length === 0) fail('accounts.labelNoTxs', null, 'No transactions given for the label.'); db.run(`UPDATE WalletTransaction SET labelId = ?1 WHERE id IN (SELECT value FROM json_each(?2)) OR (layer = 'lightning' AND EXISTS ( SELECT 1 FROM WalletTransaction chosen WHERE chosen.id IN (SELECT value FROM json_each(?2)) AND chosen.layer = 'lightning' AND chosen.accountId = WalletTransaction.accountId AND chosen.txid = WalletTransaction.txid))`, [labelId ?? null, JSON.stringify(ids)]); await db.persist(); }