Vaults
Summary
Section titled “Summary”A vault is a user-defined savings goal — a target amount in a target currency, with a name, colour, and optional icon. Vaults accumulate value from fractions of holdings: you allocate “25% of my BTC” and “100% of this EUR savings account” to your Emergency fund vault, and the vault’s current amount is the sum of those attributed values. Vaults are lightweight overlays on top of holdings — they don’t change the underlying ledger, just give you a goal-tracking view.
Schema
Section titled “Schema”vaults:
vault_holdings (junction):
Uniqueness: (vaultId, holdingId) — a holding can appear in a
vault at most once.
Allocation semantics
Section titled “Allocation semantics”A single holding can be split across multiple vaults — 25% to a house deposit, 75% to retirement. The percentages can sum to anything: the system does not enforce that the sum across vaults equals 100% for a given holding. You can over-allocate (sum > 100%) or under-allocate (sum < 100%) deliberately — Scani treats vaults as a UI overlay, not an accounting view.
A single vault can pull from multiple holdings — your Emergency fund draws from a Wise EUR account, a Coinbase USDC position, and 10% of your BTC.
How currentAmount is computed
Section titled “How currentAmount is computed”When the vault page loads (or after a relevant balance/price update), the vault service computes:
currentAmount = Σ over vault_holdings { convert(holding.balance × (percentage/100), holding.token, vault.currency, now)}The conversion uses the price graph at the
current time. The result is stored on vaults.currentAmount so
dashboards render without a recompute.
Because the vault’s currency is configurable and arbitrary, a vault denominated in EUR pulling from a USDC holding will see daily fluctuation from the EUR/USD rate — that’s intentional and matches what users expect from a goal denominated in their target currency.
Progress
Section titled “Progress”UI computes progress as currentAmount / targetAmount (clamped 0–1
for visualisation; the raw ratio is also surfaced so over-funded
vaults show > 100%).
Vaults vs groups
Section titled “Vaults vs groups”Both are user-defined organisation. The difference:
A holding can belong to multiple vaults (with different percentages) and multiple groups simultaneously, independently.
Lifecycle
Section titled “Lifecycle”- Created via the
vaults.createtRPC mutation. - Holdings attach via the
vaults.attachHoldingmutation, which inserts avault_holdingsrow with the percentage. currentAmountis refreshed by the vault service after any attach / detach / balance / price update. There’s no separate cron for vault recompute — it’s cheap enough to do on demand.