144 lines
6.1 KiB
JavaScript
144 lines
6.1 KiB
JavaScript
// Generated, do not edit. sha256:c2c77c87420dd87960f52c5550a95a9491166b15f248300e49e13aae650240fc
|
|
import { nowUtcIso } from '../../shared/time.js';
|
|
import { fail } from '../../shared/app-error.js';
|
|
import { assetPositions, eventAccountIdsByAsset, eventAssetIdsByAccount, eventsCountByAccount,
|
|
eventsNetByAccount, firstTaxEventDate } from './events.js';
|
|
import { anchorAt, anchorsBetween, createValuation, firstAnchorDate, latestAnchors, listValuations,
|
|
updateValuation, valuationDiff, valuationGap, valuationReconcilable } from './valuations.js';
|
|
import { accountOnchainBalance } from './wallet-addresses.js';
|
|
import { lightningBalances } from './wallet-sources.js';
|
|
import { onchainAsset } from './tx-links.js';
|
|
|
|
export function positionAt(accountId, assetId, atIso) {
|
|
const anker = anchorAt(accountId, assetId, atIso);
|
|
if (!anker) return eventsNetByAccount(accountId, assetId, atIso);
|
|
|
|
const naAnker = eventsNetByAccount(accountId, assetId, atIso) - eventsNetByAccount(accountId, assetId, anker.rateAt);
|
|
return anker.balance + naAnker;
|
|
}
|
|
|
|
export function positionsByAsset(atIso, { ownOnly = false } = {}) {
|
|
const peil = atIso ?? nowUtcIso();
|
|
|
|
const correctie = new Map();
|
|
for (const anker of latestAnchors(peil, { ownOnly })) {
|
|
const diff = valuationDiff(anker);
|
|
if (diff) correctie.set(anker.assetId, (correctie.get(anker.assetId) || 0) + diff);
|
|
}
|
|
const rijen = assetPositions(peil, { ownOnly });
|
|
if (correctie.size === 0) return rijen;
|
|
return rijen.map(r => (correctie.has(r.id) ? { ...r, net: r.net + correctie.get(r.id) } : r));
|
|
}
|
|
|
|
export function unexplainedChange(accountId, assetId, fromIso, untilIso) {
|
|
const stand = positionAt(accountId, assetId, untilIso) - positionAt(accountId, assetId, fromIso);
|
|
const geboekt = eventsNetByAccount(accountId, assetId, untilIso) - eventsNetByAccount(accountId, assetId, fromIso);
|
|
return stand - geboekt;
|
|
}
|
|
|
|
export function firstPositionDate() {
|
|
const gebeurtenis = firstTaxEventDate();
|
|
const anker = firstAnchorDate();
|
|
if (!gebeurtenis) return anker;
|
|
if (!anker) return gebeurtenis;
|
|
return gebeurtenis < anker ? gebeurtenis : anker;
|
|
}
|
|
|
|
export function unexplainedByPair(fromIso, untilIso) {
|
|
const uit = [];
|
|
for (const anker of latestAnchors(untilIso)) {
|
|
const units = unexplainedChange(anker.accountId, anker.assetId, fromIso, untilIso);
|
|
if (!units) continue;
|
|
let openingUnits = 0;
|
|
for (const v of anchorsBetween(anker.accountId, anker.assetId, fromIso, untilIso)) {
|
|
if (valuationReconcilable(v) && !v.gapAcceptedAt) continue;
|
|
openingUnits += valuationGap(v) || 0;
|
|
}
|
|
uit.push({
|
|
accountId: anker.accountId, assetId: anker.assetId, units, openingUnits,
|
|
hasLedger: eventsCountByAccount(anker.accountId, anker.assetId, untilIso) > 0,
|
|
});
|
|
}
|
|
return uit;
|
|
}
|
|
|
|
export function assetPositionsByAccount(assetId, atIso) {
|
|
const peil = atIso ?? nowUtcIso();
|
|
const accounts = new Set(eventAccountIdsByAsset(assetId));
|
|
for (const anker of latestAnchors(peil)) if (anker.assetId === assetId) accounts.add(anker.accountId);
|
|
|
|
const uit = [];
|
|
for (const accountId of accounts) {
|
|
const saldo = positionAt(accountId, assetId, peil);
|
|
if (saldo !== 0) uit.push({ accountId, saldo });
|
|
}
|
|
uit.sort((a, b) => b.saldo - a.saldo || a.accountId - b.accountId);
|
|
|
|
const totaal = positionsByAsset(peil).find(r => r.id === assetId);
|
|
const rest = (totaal ? totaal.net : 0) - uit.reduce((s, r) => s + r.saldo, 0);
|
|
if (rest !== 0) uit.push({ accountId: null, saldo: rest });
|
|
return uit;
|
|
}
|
|
|
|
export function accountPositions(accountId, atIso) {
|
|
const peil = atIso ?? nowUtcIso();
|
|
const assets = new Set(eventAssetIdsByAccount(accountId));
|
|
for (const anker of latestAnchors(peil)) if (anker.accountId === accountId) assets.add(anker.assetId);
|
|
|
|
const uit = [];
|
|
for (const assetId of [...assets].sort((a, b) => a - b)) {
|
|
const saldo = positionAt(accountId, assetId, peil);
|
|
if (saldo !== 0) uit.push({ assetId, saldo });
|
|
}
|
|
return uit;
|
|
}
|
|
|
|
export function accountHoldings(accountIds = [], atIso) {
|
|
const peil = atIso ?? nowUtcIso();
|
|
const uit = [];
|
|
for (const accountId of accountIds) {
|
|
for (const { assetId, saldo } of accountPositions(accountId, peil)) {
|
|
const anker = anchorAt(accountId, assetId, peil);
|
|
uit.push({ accountId, assetId, saldo,
|
|
anchor: anker ? { valuedOn: anker.valuedOn, rateAt: anker.rateAt, balance: anker.balance } : null });
|
|
}
|
|
}
|
|
return uit;
|
|
}
|
|
|
|
function measuredNodeBalance(row) {
|
|
return accountOnchainBalance(row.accountId) + (Number(row.sats) || 0);
|
|
}
|
|
|
|
export function lightningBalanceDiffs() {
|
|
const asset = onchainAsset();
|
|
if (!asset) return [];
|
|
const uit = [];
|
|
for (const row of lightningBalances()) {
|
|
const measured = measuredNodeBalance(row);
|
|
const ledger = positionAt(row.accountId, asset.id, row.scannedAt);
|
|
if (measured !== ledger) {
|
|
uit.push({ accountId: row.accountId, account_name: row.account_name, assetId: asset.id,
|
|
measured, ledger, gap: measured - ledger, scannedAt: row.scannedAt });
|
|
}
|
|
}
|
|
return uit;
|
|
}
|
|
|
|
export async function recordLightningBalance(accountId, { valuedOn, fiatRate } = {}) {
|
|
const row = lightningBalances().find(r => r.accountId === Number(accountId));
|
|
const asset = onchainAsset();
|
|
if (!row || !asset) throw new Error(`Account ${accountId} has no updated lightning source.`);
|
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(String(valuedOn))) throw new Error(`A balance needs its day: '${valuedOn}'.`);
|
|
if (!(Number(fiatRate) > 0)) throw new Error(`A balance needs a rate above zero: '${fiatRate}'.`);
|
|
const v = { assetId: asset.id, accountId: row.accountId, valuedOn, balance: measuredNodeBalance(row),
|
|
fiatRate: Number(fiatRate), rateAt: row.scannedAt, source: 'onchain' };
|
|
const zelfdeDag = listValuations().find(x => x.accountId === v.accountId && x.assetId === v.assetId && x.valuedOn === valuedOn);
|
|
if (zelfdeDag && zelfdeDag.source !== 'onchain') {
|
|
fail('valuations.lightningSameDay', { date: valuedOn },
|
|
`There is already a balance on ${valuedOn} for this account; it is not overwritten.`);
|
|
}
|
|
if (zelfdeDag) { await updateValuation(zelfdeDag.id, v); return zelfdeDag.id; }
|
|
return createValuation(v);
|
|
}
|