Skip to content

Releases: 0xRabit/dsh-crypto-portfolio

Release list

v0.2.0

Choose a tag to compare

@0xRabit 0xRabit released this 26 Sep 00:54

The first release after 0.1.0: two more exchanges, a health report you can
tune, a shareable card, per-source tests, honest failure reporting — and a
dashboard that renders ~50× faster.

Supported sources

  • CEX — read-only keys for Binance, Bybit, Backpack, OKX and Bitget.
    OKX reads the trading and funding sub-accounts (deposits sit in funding, and
    the exchange's own total adds them up); Bitget sums the spot account with the
    real USDT/USDC/coin-margined futures accounts, and deliberately ignores the
    S* demo products, which answer with play money on a live key.

Dashboard

  • Your data can live outside the package. Profiles (wallets, API keys, the
    snapshot database) default to <package>/profiles, which is the wrong place for an
    installed plugin: dsh plugin add/update replaces that directory, so a portfolio
    kept there would be lost on the next update. Set dataDir in the plugin config (or
    PORTFOLIO_PROFILES_DIR) and the data stays where you put it — the plugin also
    defaults to $DSH_HOME/storages/crypto-portfolio when it detects it is running from
    inside node_modules, instead of silently storing a portfolio in a cache directory.
  • Binance reads every sub-account, and says why when it cannot. A refresh once
    reported a funded account as $0 because Binance answered a signed request with
    HTTP 400 — the body said -1021 Timestamp for this request is outside of the recvWindow (a proxy held the request past the 5 s window). The query is now signed
    by hand so what is signed is what is sent, the error body is read instead of
    surfacing "400 Bad Request", one retry uses a fresh timestamp, and the window is
    60 s. Reading is also complete: spot (free and locked), Funding, Simple Earn
    (the underlying coin, with the LDxxx receipt that mirrors it de-duplicated), cross
    margin and USD-M futures are summed, and a key without Futures permission degrades
    to a note rather than an error row.
  • Every source has its own Test button. Settings → Data sources probes one row at
    a time (each CEX account, DeBank, Etherscan, every BTC/DOGE/ADA/price/Hyperliquid
    provider, each Solana RPC, Birdeye, SPL prices) and prints the result where the key
    is typed — total and asset count for an exchange, chain count for DeBank, the
    provider's own wording when it refuses. Tolerated sub-accounts are shown as notes,
    so a missing permission is visible before a refresh depends on it.
  • A wallet that failed to update is impossible to miss. The dashboard card gets an
    asterisk with the reason in its tooltip and the wallet panel title counts them
    ("Wallets (14) · 1 failed"); Settings shows the same wallet as a red-bordered row
    with the provider's message. Before this, a failed fetch simply read as "$0".
  • Page loads are ~50× faster. Every path helper (blacklist.json, labels.json,
    wallets.json, …) resolved the active profile by opening and reading
    profiles/.active — ~85 µs per call, against ~1 µs for a bare stat — and a trend
    rebuild called it once per token row. The pointer is now cached against the file's
    (mtime, size) and invalidated by every profile mutation, which alone took
    /api/current from 360 ms to 56 ms and the history call from 5.8 s to 0.9 s.
  • The trend reads aggregates, not every snapshot. get_history re-parsed all
    snapshots and re-filtered every token row on each page load, so its cost grew with
    the portfolio rather than with the number of days. A per-date history_totals
    table (written incrementally on refresh) is now the source, rebuilt once when
    something that rewrites history changes — a new snapshot, the blacklist, or the
    label rules. /api/history went from 5.8 s to 2 ms, and the blacklist still
    retroactively rewrites past totals, which is what the old approach was protecting.
  • The first paint no longer waits for the trend. The dashboard, wallet cards,
    donuts and health report render from the snapshot, and the trend arrives on its
    own; the date list comes from the tiny /api/snapshots. First wallet card:
    7.5 s → 140 ms (measured with performance.mark hooks, which now exist for
    exactly this kind of profiling).
  • Asset health report. A panel directly above the token table grades the
    portfolio on the three questions a reviewer actually asks, one card each:
    volatility risk (💵 stablecoins · ₿ bitcoin · 🪙 everything else, scored on the
    third bucket), concentration (the largest single wallet, drawn as a gauge with
    both thresholds marked on it) and storage security (❄️ cold · 🔥 hot · 🏦
    exchange custody). Every row carries its balance and its share, and each failing
    check adds one plain sentence below the cards. Figures come from the
    blacklist-filtered snapshot, so they can never disagree with the totals above; the
    report is deliberately not filter-aware, because hiding the CEX wallets would
    change the maths. The maths lives in tracker/health.py.
  • The thresholds are yours to set. Settings → Health Thresholds edits the
    warning/danger lines for all three checks (storage is inverted: its safe line sits
    above its warning line) and stores them per profile in health.json. They are
    validated on the way in — range, ordering, junk values — and a corrupt file falls
    back to the defaults rather than taking the report down. The volatility split is
    computed from the token labels, so your own label rules move it too. The hot/cold
    class follows the live wallet list rather than the value frozen into the
    snapshot: marking a wallet cold moves the panel immediately instead of at the next
    refresh (a wallet removed from the live list keeps the class its snapshots
    recorded).
  • Wallets know where their keys live. Each wallet is hot or cold (Settings →
    Wallet Management, or on creation); exchange accounts are always exchange custody.
    Cold wallets are marked with a ❄ on their card. The class is stored with the
    snapshot, so a historical snapshot's health report reflects what was cold at the
    time rather than what is cold now.
  • Share card. A Share button in the header opens a dialog with a live
    preview of a 1200×630 PNG — total, change vs the previous day, top five holdings
    (aggregated by symbol), the asset-label donut with its legend and the three health
    verdicts — plus Download PNG, Copy image, Copy text and share intents
    for X / Facebook / WhatsApp / Telegram. The image is drawn by hand on a canvas:
    no html2canvas, no CDN, and no address ever reaches it. The text is generated
    from the same figures, states nothing it cannot back up, and is editable before
    copying.
  • Author links + a tip jar in Settings. The footer of the settings page carries
    the author's avatar, handle and social links (X / Discord / GitHub) plus a Buy me a
    coffee
    button that opens a small dialog with the tip.md badge and a Binance
    Pay
    QR. The badge is vendored as a local SVG rather than hot-linked, so the page
    still makes no outbound requests of its own.
  • Linkable pages. #pageSettings opens the settings page and
    #pageSettings/secWallets opens that section, so a refresh stays where you were.
  • No silent failures on start. A rejected init() used to leave a blank
    dashboard with nothing in the console; it is now logged and recorded on the page.