Files
AtreidesAssetsFree/server/data/tx-links.js
T

157 lines
7.7 KiB
JavaScript
Raw Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:a4f989c4fcb487fdb8c1ab80d536f8fa5a1a10a2f0357c22596ab060e7b38694
import * as db from '../db.js';
import { ENUMS } from '../../shared/config.js';
import { assertEnum, nowIso } from './_shared.js';
export async function syncAddressLinks() {
const method = 'exact';
assertEnum('method', ENUMS.LINK_METHOD, method);
db.run(`INSERT OR IGNORE INTO WalletTransactionEventLink (walletTransactionId, eventId, method, createdAt)
SELECT w.id, e.id, ?1, ?2
FROM WalletTransaction w
JOIN Event e ON e.externalAddress = w.address
WHERE w.direction = 'in' AND w.address IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM ReconcileProposal p
WHERE p.walletTransactionId = w.id AND p.resolvedEventId = e.id AND p.status = 'ignored')`,
[method, nowIso()]);
let n = db.one(`SELECT changes() AS n`).n;
db.run(`INSERT OR IGNORE INTO WalletTransactionEventLink (walletTransactionId, eventId, method, createdAt)
SELECT w.id, e.id, ?1, ?2
FROM WalletTransaction w
JOIN Event e ON e.source = 'lightning'
AND (e.fromAccountId = w.accountId OR e.toAccountId = w.accountId)
AND (e.importReference = 'ln:' || w.txid
OR (w.txid LIKE 'fwd:%' AND e.importReference = 'lnfwd:' || w.blockTime))
WHERE w.layer = 'lightning'
AND NOT EXISTS (SELECT 1 FROM ReconcileProposal p
WHERE p.walletTransactionId = w.id AND p.resolvedEventId = e.id AND p.status = 'ignored')`,
[method, nowIso()]);
n += db.one(`SELECT changes() AS n`).n;
db.run(`INSERT OR IGNORE INTO WalletTransactionEventLink (walletTransactionId, eventId, method, createdAt)
SELECT w.id, l.eventId, ?1, ?2
FROM WalletTransaction w
JOIN WalletTransaction twin ON twin.accountId = w.accountId AND twin.txid = w.txid
AND twin.layer = 'lightning' AND twin.id <> w.id
JOIN WalletTransactionEventLink l ON l.walletTransactionId = twin.id
WHERE w.layer = 'lightning'
AND NOT EXISTS (SELECT 1 FROM ReconcileProposal p
WHERE p.walletTransactionId = w.id AND p.resolvedEventId = l.eventId AND p.status = 'ignored')`,
[method, nowIso()]);
n += db.one(`SELECT changes() AS n`).n;
const asset = onchainAsset();
if (asset) {
db.run(`INSERT OR IGNORE INTO ReconcileProposal
(accountId, assetId, walletTransactionId, transactionHash, amount, direction, suggestedType, status, resolvedEventId, detectedAt)
SELECT w.accountId, ?1, w.id, w.txid, w.valueSats, w.direction,
CASE WHEN e.type = 'transfer' THEN 'transfer' ELSE 'trade' END,
'auto_linked', e.id, ?2
FROM WalletTransactionEventLink l
JOIN WalletTransaction w ON w.id = l.walletTransactionId
JOIN Event e ON e.id = l.eventId
WHERE l.method = 'exact'
AND NOT EXISTS (SELECT 1 FROM ReconcileProposal p
WHERE p.walletTransactionId = w.id AND p.resolvedEventId = e.id)`,
[asset.id, nowIso()]);
}
const changed = n + db.one(`SELECT changes() AS n`).n;
if (changed > 0) await db.persist();
return n;
}
export function eventLinkMap() {
const m = new Map();
for (const r of db.all(`SELECT eventId, method FROM WalletTransactionEventLink`)) m.set(r.eventId, r.method);
return m;
}
export function walletTxEventMap() {
const m = new Map();
const rows = db.all(`SELECT l.walletTransactionId, l.method, e.id AS eventId, e.type, e.amount, e.unitFee,
e.fromAccountId, e.toAccountId, fa.name AS from_name, ta.name AS to_name
FROM WalletTransactionEventLink l
JOIN Event e ON e.id = l.eventId
LEFT JOIN Account fa ON fa.id = e.fromAccountId
LEFT JOIN Account ta ON ta.id = e.toAccountId`);
for (const r of rows) {
if (!m.has(r.walletTransactionId)) m.set(r.walletTransactionId, []);
m.get(r.walletTransactionId).push(r);
}
return m;
}
export async function linkTxEvent(walletTxId, eventId, method) {
assertEnum('method', ENUMS.LINK_METHOD, method);
db.run(`INSERT OR IGNORE INTO WalletTransactionEventLink (walletTransactionId, eventId, method, createdAt)
VALUES (?,?,?,?)`, [walletTxId, eventId, method, nowIso()]);
await db.persist();
}
export function linkProposalTxSoft(walletTxId, eventId) {
const method = 'soft';
assertEnum('method', ENUMS.LINK_METHOD, method);
db.run(`INSERT OR IGNORE INTO WalletTransactionEventLink (walletTransactionId, eventId, method, createdAt)
VALUES (?,?,?,?)`, [walletTxId, eventId, method, nowIso()]);
}
export async function unlinkTxEvent(walletTxId, eventId) {
db.run(`DELETE FROM WalletTransactionEventLink WHERE walletTransactionId = ? AND eventId = ?`, [walletTxId, eventId]);
db.run(`UPDATE ReconcileProposal SET status = 'ignored'
WHERE walletTransactionId = ? AND resolvedEventId = ? AND status = 'auto_linked'`, [walletTxId, eventId]);
await db.persist();
}
export function onchainAsset() {
return db.one(`SELECT * FROM Asset WHERE symbol = 'BTC'`)
|| db.one(`SELECT * FROM Asset WHERE kind = 'crypto' ORDER BY symbol LIMIT 1`)
|| null;
}
export function linksForTx(walletTxId) {
return db.all(`
SELECT tel.eventId AS id, tel.method,
e.type, e.occurredAt, e.amount,
a.symbol AS asset_symbol, lf.name AS from_name, lt.name AS to_name
FROM WalletTransactionEventLink tel
JOIN Event e ON e.id = tel.eventId
JOIN Asset a ON a.id = e.assetId
LEFT JOIN Account lf ON lf.id = e.fromAccountId
LEFT JOIN Account lt ON lt.id = e.toAccountId
WHERE tel.walletTransactionId = ?
ORDER BY e.occurredAt DESC, e.id DESC`, [walletTxId]);
}
export function linksForEvent(eventId) {
return db.all(`
SELECT tel.walletTransactionId AS id, tel.method,
t.direction, t.valueSats, t.blockTime, t.txid, t.address, ac.name AS account_name
FROM WalletTransactionEventLink tel
JOIN WalletTransaction t ON t.id = tel.walletTransactionId
LEFT JOIN Account ac ON ac.id = t.accountId
WHERE tel.eventId = ?
ORDER BY (t.blockTime IS NULL) DESC, t.blockTime DESC, t.id DESC`, [eventId]);
}
export function eventCandidatesForTx(walletTxId, assetId) {
return db.all(`
SELECT e.id, e.type, e.occurredAt, e.amount,
a.symbol AS asset_symbol, lf.name AS from_name, lt.name AS to_name
FROM Event e
JOIN Asset a ON a.id = e.assetId
LEFT JOIN Account lf ON lf.id = e.fromAccountId
LEFT JOIN Account lt ON lt.id = e.toAccountId
WHERE e.assetId = ?2 AND e.type <> 'reservation'
AND e.id NOT IN (SELECT eventId FROM WalletTransactionEventLink WHERE walletTransactionId = ?1)
ORDER BY ABS(strftime('%s', e.occurredAt) -
strftime('%s', COALESCE((SELECT blockTime FROM WalletTransaction WHERE id = ?1),
(SELECT createdAt FROM WalletTransaction WHERE id = ?1)))) ASC,
e.id DESC
LIMIT 100`, [walletTxId, assetId]);
}
export function txCandidatesForEvent(eventId) {
return db.all(`
SELECT t.id, t.direction, t.valueSats, t.blockTime, t.txid, t.address,
ac.name AS account_name, l.name AS label_name
FROM WalletTransaction t
LEFT JOIN Account ac ON ac.id = t.accountId
LEFT JOIN Label l ON l.id = t.labelId
WHERE t.id NOT IN (SELECT walletTransactionId FROM WalletTransactionEventLink WHERE eventId = ?1)
ORDER BY ABS(strftime('%s', COALESCE(t.blockTime, t.createdAt)) -
strftime('%s', (SELECT occurredAt FROM Event WHERE id = ?1))) ASC,
t.id DESC
LIMIT 100`, [eventId]);
}