Documentation & methodology

Read-only by design

CryptoVault tracks investments. It never executes trades, moves funds, or asks for withdrawal, transfer or trading permissions, private keys or seed phrases. Exchange API keys must be read-only; wallet tracking uses public addresses only. Credentials you store (exchange keys, provider API keys) are encrypted with AES-256-GCM at rest and are never sent back to the browser — only a masked hint such as ••••1a2b is shown.

Accounting methodology

Every number in the app is derived from your transaction ledger by a single, deterministic accounting engine. Whenever a transaction is created, edited, imported or deleted — or you switch cost-basis method — the engine replays your full history in time order and rebuilds holdings, open lots, disposals, realized P&L and portfolio history in one database transaction. Nothing is updated incrementally, so there is no stale state.

  • Pools. Cost basis is tracked per account × asset (wallet-by-wallet), in the portfolio's currency.
  • Exact decimals. Quantities, prices, fees and money are stored as NUMERIC(38,18) and calculated with 40-significant-digit decimal arithmetic. Intermediate results are never rounded; rounding happens only when displaying or exporting.
  • Realized P&L = proceeds − cost basis of the units disposed. It is recorded per matched lot, so partial disposals and multiple lots are exact.
  • Unrealized P&L = current quantity × latest stored price − remaining cost basis. Holdings without a price are excluded from value and flagged; prices are never guessed.
  • Income (rewards, staking, airdrops, interest, dividends) is reported separately from trading P&L. Received units take the stated fair value as cost basis.
  • Total P&L = unrealized + realized + income. Return = total P&L ÷ (remaining cost basis + cost basis of everything disposed).

Fees

  • Buy: fees are added to the acquisition cost.
  • Sell / trade: fees reduce proceeds.
  • Fee paid in another asset (e.g. BNB): those units are disposed at their own cost basis (zero gain) and that basis moves into the transaction.
  • Fee in the acquired asset: reduces the units received; the full cost stays on the remaining units.
  • Transfers, deposits, withdrawals, adjustments: fees are expensed as a realized loss.
  • FEE transaction: units spent as a standalone fee are a realized loss equal to their cost basis.

Ledger issues

The engine never silently fixes bad data. It records issues instead — e.g. selling more units than held (the shortfall gets zero basis), a deposit without a value (zero basis), a buy without price, or an unmatched transfer — and shows them on the Transactions page, dashboard and tax report.

Cost-basis methods

MethodHow disposals are matchedImplications
FIFOOldest open acquisition lot first, splitting lots for partial disposals.Keeps acquisition dates per lot (holding periods). Required by many capital-gains regimes.
Average costOne pooled lot per account/asset; each disposal uses the running average cost.Smoother gains; the acquisition date of the pool is the earliest contributing lot. Buys after a sale only average with what remains — historical totals are never divided by current holdings.

Change the method in Settings; the entire ledger is recalculated immediately. Additional methods (e.g. HIFO, specific identification) can be added to the engine without schema changes because disposals already record lot-level matches.

Transaction types

TypeEffect
BUYAcquire units for cash; creates a lot.
SELLDispose units for cash; realizes gain/loss.
TRADESwap: disposes the asset given up at fair value (the stated total) and acquires the new asset at that value.
DEPOSITUnits arriving from outside — not income, not a sale. Enter a value to set basis.
WITHDRAWALUnits leaving to an untracked place; removes units and basis, no gain.
TRANSFER_OUT / TRANSFER_INMove between your own accounts. The pair is linked; original lots (dates and basis) move with the units. Never a sale. A smaller received quantity (network fee) keeps the full basis.
FEEUnits spent as a fee; realized loss of their basis.
REWARD, STAKING_REWARD, AIRDROP, INTEREST, DIVIDENDIncome at fair value; units (if any) acquire that value as basis.
ADJUSTMENTPositive adds units at the value given; negative removes units without a sale.

Currencies & exchange rates

Each transaction stores its own currency and the exchange rate into its portfolio's currency at the time of the transaction (historical daily rate, or a rate you enter). If no rate within three days exists, the nearest stored rate is used and the transaction is flagged estimated; if none exists at all it is flagged missing. Different currencies are never treated as equal.

Dashboards and reports are shown in your base currency, converting portfolio-currency amounts with the latest stored rates. FX rates come from ExchangeRate-API's open endpoint (daily, USD base) and are stored in the database.

Performance methodology

  • Daily value = Σ end-of-day quantity × that day's stored close (carried up to 5 days over weekends/holidays), converted to the display currency.
  • External cash flows: buys (+cost), sells (−net proceeds), deposits, withdrawals, adjustments and transfers to/from other portfolios (± market value). Transfers between accounts of the same portfolio are not flows; income is return, not a flow.
  • Time-weighted return chains daily returns (V_t − V_t−1 − F_t) ÷ (V_t−1 + F_t), so deposits and withdrawals never count as performance.
  • Money-weighted return is the annualised internal rate of return of the flows plus the ending value.
  • Days where a held asset has no stored price are marked incomplete; values on those days are understated.

Market data & price refresh

ProviderUsed forKey
CoinGeckoCrypto quotes (batched up to 100 ids), 365-day daily history, coin searchOptional (demo/pro)
Kraken public APICrypto quote fallback (batched pairs), daily OHLC historyNone
Alpha VantageStock/ETF quotes and daily historyRequired
FinnhubStock quote fallbackRequired
ExchangeRate-API (open)Daily FX ratesNone

A durable, database-backed scheduler (a Neon Function with a cron trigger every 15 minutes, or any cron calling /internal/cron) refreshes only assets that are held, watched or alerted and whose quote is older than their provider's interval, groups them by provider, batches requests, respects per-provider rate limits, stores append-only price history, and falls back to the next provider by priority. Provider calls and errors are logged and visible in Admin. Quotes older than the freshness window are labelled stale; every price shows its last-updated time. Open dashboards receive new prices live through Neon Realtime.

CSV import

  1. Upload a UTF-8 .csv (default limit 4 MB / 20,000 rows). The original file is archived in object storage.
  2. Pick a format: the CryptoVault template, Coinbase transaction history, or map any columns yourself.
  3. Map columns: date, type, asset and quantity are required; price, total, currency, fee, fee asset, account, transaction ID, hash, notes and counter asset/quantity are optional. Choose date format and decimal comma if needed.
  4. Validate: every row is checked; errors are listed by row. Symbols must resolve to exactly one catalog asset — ambiguous or unknown symbols are shown so you can pick the right asset.
  5. Duplicates are skipped: rows matching an existing transaction (same account, asset, type, quantity and timestamp, or the same external ID) or another row in the file.
  6. Confirm: all valid rows are inserted in one database transaction, then the ledger is recalculated. An import can be undone from the import history.

Template columns: date,type,asset,quantity,price,total,currency,fee,fee_asset,account,transaction_id,notes,counter_asset,counter_quantity. Type accepts the names above plus common synonyms (Receive, Send, Convert, Staking Income…). Timestamps without a timezone are treated as UTC.

Exchange & wallet connections

Kraken (read-only): create an API key with only Query Funds, Query Closed Orders & Trades and Query Ledger Entries. CryptoVault calls only Balance, TradesHistory and Ledgers; the client refuses any other private endpoint. Trades and deposits/withdrawals/staking are imported with duplicate protection; sync runs every 6 hours or on demand. Other exchanges: import their CSV export.

Public addresses (Bitcoin, Ethereum, Solana): the native-coin balance is read from public RPC/indexer endpoints and shown next to your ledger balance for reconciliation. Address tracking does not create transactions (that needs a full-history indexer).

Tax-ready export

The tax export lists every disposal matched to its acquisition lot: disposal and acquisition dates, holding days, quantity, proceeds, cost basis, gain/loss, per-unit prices, currency, disposal kind (sale/trade/fee), accounting method, transaction references, account, fees, exchange rate and data-quality flags. Choose a calendar year or any custom period; download as Excel-compatible CSV (UTF-8 BOM), plain CSV or JSON (with summary and limitations). Use the Reports page's Print button to save any report as PDF.

Tax-ready is not tax-certified. CryptoVault organises your own records into lots using the method you choose. It does not apply any jurisdiction's rules (holding-period classes, wash-sale or bed-and-breakfast rules, pooling, exemptions) or rates, and it is not tax advice. Missing prices, estimated exchange rates and ledger issues are listed on every tax report.

Known limitations

  • Gains are calculated per account (wallet-by-wallet). Jurisdictions that require universal pooling across wallets may differ.
  • Free provider tiers limit history (CoinGecko: 365 days; Alpha Vantage: ~100 trading days, 25 requests/day).
  • Exchange sync is implemented for Kraken only. Wallet tracking reads balances, not full transaction history.
  • Crypto-to-crypto trades imported from Kraken have no fiat value in the API response; their basis is carried over (zero gain) and flagged.
  • This documentation is in English; the application interface is available in English, Bangla and Arabic.