Repository navigation
Releases: 0xRabit/dsh-crypto-portfolio
Releases · 0xRabit/dsh-crypto-portfolio
Release list
v0.2.0
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. SetdataDirin the plugin config (or
PORTFOLIO_PROFILES_DIR) and the data stays where you put it — the plugin also
defaults to$DSH_HOME/storages/crypto-portfoliowhen it detects it is running from
insidenode_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$0because 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 theLDxxxreceipt 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/currentfrom 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_historyre-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-datehistory_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/historywent 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 withperformance.markhooks, 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 intracker/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 inhealth.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:
nohtml2canvas, 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.
#pageSettingsopens the settings page and
#pageSettings/secWalletsopens 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.