# Changelog

All notable changes to the **Bitflow Agent Dashboard** are documented here.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/).

**Scope**: this changelog documents the publicly-shipped dashboard artifact (the `index.html` + `assets/` + `terms-of-service/` tree). Server-side, signed-in, or bff.army-only features that don't ship to Path 2 (public repo) or Path 3 (AIBTC installer skill) are intentionally omitted.

**Versioning trajectory**:
- `v0.1.0` — Initial release on bff.army (Path 1 live)
- `v0.1.x` — Iterative improvements; Path 2 (public repo extract) lands as a patch version, Path 3 (installer skill) as a subsequent patch
- `v0.2.x` and beyond — Settings panel, aibtc identity, auto-refresh, status-JSON skill, and the larger v0.2 work
- `v1.0.0` — The dashboard tree gets extracted from this monorepo into the public [`bitflow-agent-dashboard`](https://github.com/BitflowFinance) repo. Tags + GitHub Releases begin there. Until then, this file is the source of truth.

**Contributing entries**: when you ship a change, add a bullet under `[Unreleased]` in the appropriate category (`Added` / `Fixed` / `Changed` / `Removed` / `Deprecated` / `Security`). When cutting a release, rename `[Unreleased]` to the version + date and start a fresh `[Unreleased]` block.

---

## [Unreleased]

### Added
- **Pontis-bridged Runes resolve via CoinGecko coin-ID lookup**. CoinGecko doesn't link the Stacks-wrapped contract (`SP14NS8MV…pontis-bridge-DOG`) to its native DOG•GO•TO•THE•MOON listing, so a contract-address lookup returns nothing. New `COINGECKO_ID_MAP` (Stacks contract → CG coin ID) intercepts the lookup and routes mapped contracts to `/simple/price?ids={cgId}` instead. The bridge is 1:1 collateralized, so treating wrapped + native as one price is honest. Starter map ships with DOG (`dog-go-to-the-moon-rune`); other Pontis-bridged Runes (PUPS, NIKOLA, etc.) can be added as one-line entries.
- `coinGeckoPriceById(cgId)` helper with the same `TTL_PRICES` cache shape as the rest of the price fetchers. The new path runs concurrently for all mapped tokens (Promise.all), not sequentially — separate from the by-contract path which stays capped at 8 to dodge free-tier rate limits.

---

## [0.1.2] — 2026-05-28

Mostly polish + a notable new feature (public shareable URLs) and a fistful of bug fixes surfaced from real test-user feedback.

### Added

**Shareable read-only URLs**
- **`/agent-dashboard/public/{address}`** route — share any wallet's dashboard view as a public read-only URL. Accepts either STX (`SP1AK5ZK…`) or BTC (`bc1qxn29uth…`) in any order, with optional second segment for the opposite type (`/public/SP…/bc1q…` or `/public/bc1q…/SP…`). Auto-detects by prefix.
- Vercel rewrite rule in `vercel.json` routes both single-address and two-address variants to the static `index.html` so the SPA-style dashboard handles parsing client-side.
- Shared view chrome — orange-tinted banner above the main content reads *"👁 Viewing shared wallet · SP1AK5…7MKR · Read-only view · Your own wallets are not affected"* with a `← Back to my dashboard` link.
- Visitor's saved wallets are never read, never written in shared view. `body.is-shared-view` CSS hook hides the wallet picker chevron, Add Wallet, Export/Import row, and the Share button itself.
- BTC-only sharing supported — `loadWalletData` detects the address shape via `/^S[PM][0-9A-Z]+$/` regex and skips Stacks-side fetchers when the address slot holds a BTC address, so the dashboard renders empty states for STX/FT/LP/Activity gracefully instead of 404'ing on Hiro.

**Share button in the nav**
- New `◦―◦―◦` SVG icon (the canonical 3-dots-connected share glyph used across iOS/Android/desktop share sheets) with a mint outline + faint mint background — stands apart from the neutral `↻` Reload icon as the recognizable "broadcast / share this" action.
- On click: builds the URL via `buildShareUrl(stxAddr, btcAddr)` from the active wallet's saved addresses, copies to clipboard via `navigator.clipboard.writeText`. Falls back to a `prompt()` if clipboard API is blocked (Safari incognito, etc.).
- Visual confirmation: button briefly pulses mint with a "COPIED" pill below for ~2 s.
- Suppressed when no specific wallet is active (aggregated "All wallets" view has no meaningful share target), when no wallets exist, and during shared view.

### Changed

**Mobile responsiveness — comprehensive pass**
- `html, body { overflow-x: hidden; max-width: 100vw }` + `overflow-x: clip` on section-level wrappers — root-cause fix for horizontal page scroll that was triggered by the empty-state BFF Bot mascot extending past the viewport edge.
- Empty-state mascot scales down on phones: 320 → 200 px at ≤900 px, 200 → 150 px at ≤480 px. Halo glow tracks proportionally. Title 38 → 28 → 22 px.
- Wallet pill condensed at ≤480 px: avatar 32 → 28 px, name max-width 130 → 100 px, icon-btn 36 → 32 px.
- Modal at ≤560 px: `max-width: calc(100vw - 24px)`, action buttons flex-wrap so Cancel/Save/Remove stack cleanly.
- Capital Flows asset rows at ≤720 px: 6-col grid → 3-col (Asset · Net · Expand). In/Out/USD columns hidden; still visible by expanding the row.
- Balances tables at ≤720 px: drop the redundant secondary column per table so the USD column stays visible without horizontal scroll. Stacks Tokens hides "Name", Runes hides the Symbol icon, LP Tokens hides "Contract". Each table reduces 4 cols → 3 cols and fits the viewport naturally.
- Pool labels in LP Tokens wrap with `word-break: normal` + `overflow-wrap: anywhere` instead of breaking mid-token like "15<br>bps".
- Wallet picker popover at ≤720 px switches to `position: fixed` with 12 px safe gutters on both sides and a dynamic `top` computed from the wallet pill's `getBoundingClientRect().bottom + 8` — guarantees the popover lands directly below the pill regardless of where the nav has wrapped it. No more leftward off-screen overflow.

**Nav reorganization**
- Removed the Import (⬆) button from the nav. Then moved BOTH Export and Import into the wallet picker dropdown as a footer row below `+ Add wallet`, separated by a thin top-border divider. Keeps the nav minimal (just `↻` Refresh + Share + v0.1 pill) and makes wallet-management actions live together in the picker where users naturally go to manage wallets.
- Wallet picker now has `.wp-actions-row` at the bottom with `⬇ Export` / `⬆ Import` side-by-side. Hidden via `body.is-shared-view` selector during shared view (same pattern as `+ Add wallet`).
- The hidden file input `<input type="file">` stays wired in the nav HTML — clicked programmatically when the Import action is clicked from the picker.

**Stale rune price detection (test-user-reported bug)**
- Wallet Value hero was inflated by ~$550K for a test user because Xverse's `rune-balance` endpoint returns stale floor prices for illiquid runes (STAKED-LIQUIDIUM at $91.30/unit, COOK-THE-MEMPOOL at $3.76/unit, etc.) that nobody is actually trading at those levels.
- Heuristic: when `priceChangePercentage24h` is `null`, the price is treated as stale (almost certainly the last recorded floor on some marketplace, possibly months old, often from a single inflated trade).
- Behavior: stale rune USDs render as `—` with a hover tooltip *"Price not available from a verified source"* — same treatment as memecoins without a CoinGecko listing. No special "stale" pill, no struck-through amber number, no novel vocabulary.
- Stale USDs are excluded from `runesTotalUsd`, the BTC + Runes subtotal, and the Wallet Value hero card. The rune balance itself still shows so operators can verify on Magic Eden.

**stSTX fallback price**
- The `FALLBACK_TOKEN_META.ststx-token` entry's `priceRel.multiplier` changed from `1.05` to `1.0`. The fallback only fires if BFF stops returning the `dlmm_10` stSTX/STX pool (normally we get live `priceUsd: 0.269565` directly from BFF, reflecting ~1.17× the STX price as liquid-staking accrues). The hardcoded 1.05 was both already stale at write-time and guaranteed to drift further wrong as PoX rewards compound. Conservative floor (`STX × 1.0`) is safer than confidently-wrong premium.

**Activity 24h hero card now counts ALL on-chain sources**
- Hero card was wired to `d.activity.data` only (BFF feed — swaps + LP moves). When the Activity Tape was extended to also ingest Hiro `transactions_with_transfers` (non-Bitflow contract calls) + Hiro mempool (pending txs), the hero card stayed BFF-only. For wallets whose recent activity was XYK swaps via the older router, dual-stacking distribute-rewards, wallet-to-wallet transfers, or pending mempool txs, the card showed `0` while the Activity Tape + Capital Flows below clearly had recent events.
- Fix: count UNIQUE `tx_id`s across all three sources, normalized to lowercase hex with `0x` prefix stripped so the dedup catches the same tx surfacing from multiple sources (a swap typically appears in both BFF activity AND Hiro's contract_call feed — counted once).
- Sub-line copy updated from "N events total" to "N unique txs total" so it's clear we're counting unique transactions, not duplicate event records.

### Fixed
- **Horizontal page scroll on phones** when viewing the empty state — the bot mascot was positioned at `right: -70px` with `width: 320px`, overflowing the viewport without an `overflow-x` guard. Root-cause fix applied at `html, body` level (see Mobile responsiveness above).
- **Wallet picker popover going off-screen left** on mobile when the wallet pill was anchored near the left edge after nav wrap. Now uses position-fixed with viewport-bounded positioning at ≤720 px.
- **Balances tables hiding the USD column** on mobile under the horizontal-scroll fallback I'd added in an earlier pass. Now drops the redundant secondary column per table instead so USD stays visible.
- "Pool share0.7462%" rendered with no space between label and value in XYK/Stableswap LP cards — added explicit whitespace.

---

## [0.1.1] — 2026-05-28

Initial documentation infrastructure.

### Added
- `CHANGELOG.md` following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) convention. From this point forward, every notable change is tracked here.
- Public changelog page at `/agent-dashboard/changelog/` — fetches `CHANGELOG.md` at runtime and renders it with the dashboard's design system (Geist · `#F78116` accents · dark theme · sticky header · mobile-responsive). Source markdown is also linked directly so power users and tooling can pull the raw file.
- Footer links to the **Changelog**, **Terms**, and **Repo** added to the dashboard footer next to the data-source attributions. Version pill in the footer's `built by…` line is now a link to the changelog (`v0.1 MVP ↗`).

### Removed
- "v0.3 adds exact bin IDs + gas estimate" hint line in the OOR rebalance-detail hover panel on LP+Earnings cards. The internal milestone reference wasn't useful to operators; the panel's footer now just shows the neutral-band note (`neutral band · current ± 2%`).

---

## [0.1.0] — 2026-05-28

Initial public release on [bff.army/agent-dashboard](https://bff.army/agent-dashboard). Path 1 of three planned distribution paths (hosted · public-repo self-host · AIBTC installer skill). The dashboard ships as a single-file HTML artifact with no backend, no telemetry, and no wallet data ever leaving the visitor's browser.

### Added

**Dashboard sections** (eight, scrolling top-to-bottom with sticky nav anchors)
- **Overview** hero card grid: Wallet Value · Net Flow · LP Value · Range Health · Earnings · Activity 24h · BTC Holdings. Period selector (1D · 7D · 30D · ALL) re-fetches earnings + flows only.
- **Balances** — three first-class subsections: Stacks Tokens (Hiro), BTC L1 (Mempool.space, multi-address aggregated), Runes (Xverse). LP receipt tokens split into a dedicated card with pair + bps labels and typed badges (HODLMM / STABLESWAP / XYK).
- **Capital Flows** — wallet-to-wallet transfers only. Strict `tx_type === 'token_transfer'` filter excludes swaps, LP moves, stacking, and other contract activity (those live in Activity Tape). Per-wallet view tags internal transfers between the user's own saved wallets with a "Your wallet" pill. Aggregated view hides internal transfers entirely (zero-sum). Net Flow always external-only in both views.
- **LP + Earnings** — per-position cards for DLMM (HODLMM), Stableswap, and XYK pools with pool-type badges, IN-RANGE / OUT-OF-RANGE / CLOSED status, position value, holding composition, pool APR, and per-period earnings (USD + BTC + Fee/TVL + event count + token breakdown). Closed positions hidden by default with a toggle. Top 5 by value visible + scroll for more.
- **Activity Tape** — public, narrative timeline. Sources merged: BFF activity (primary) + Hiro `transactions_with_transfers` for non-Bitflow contract calls (secondary) + Hiro mempool for in-flight txs (PENDING badge). Dedupe across sources by normalized `tx_id`. Filter chips: All · Swaps · Add/Remove Liq · Rebalance Moves · Failures · Last 24h · Last 7d. Internal scroll caps at 10 visible.
- **Skills** — read-only discovery catalog grouped by category (DeFi Core · DeFi Operations · Strategies). Each skill row shows description + "View on aibtc.com" CTA. AIBTC wordmark inline in the "View all skills" footer button. Community-unaudited disclaimer below the catalog.
- **Header** — BFF.Army logo, Agent Dashboard label, wallet selector pill with PFP, Export/Import buttons, refresh icon, version pill.
- **Footer** — version pill, "built by and for the BFF.Army Community" credit, data-source attributions (Hiro · Bitflow BFF · Mempool · Xverse · CoinGecko).

**Wallet model**
- `role: 'agent'` field with single-checkbox toggle in Add/Edit Wallet modal. Operators default; agent is opt-in. Picker shows differentiated PFPs: BFF Bot + 🤖 orange chip for agents, neutral letter-initial disc for operators. Aggregate "All wallets" row uses Σ glyph.
- `btcAddresses: string[]` — single list supporting Native SegWit (`bc1q…`), Taproot (`bc1p…`), Nested (`3…`), Legacy (`1…`) with auto-detected format badges in the modal. Each linked BTC address fetches Mempool balance + Xverse Runes independently.
- Wallet Export — downloads `bitflow-agent-dashboard-wallets-YYYY-MM-DD.json` with `{ wallets, installedSkills, version, exportedAt }`. Roundtrips `role` and `btcAddresses[]` correctly.
- Wallet Import — file picker → merge (or replace with confirm) → reload.
- Add Wallet UX: pasting an STX address that's already saved merges new BTC addresses into the existing entry instead of blocking.
- Remove wallet moved from the picker rows (too easy to click by accident) into the Edit modal as a danger button with two-step confirm-on-second-click (5-second arming window).
- Automatic migration of legacy `{ btcAddress, btcTaprootAddress }` shape to `btcAddresses[]` on load.

**LP + Earnings DLMM visualization**
- Real prices everywhere — replaces the previously-hardcoded `0.25` placeholder. `currentPrice = tokenX.priceUsd / tokenY.priceUsd` from BFF pool data, with self-calibrated scale per pool (handles wide range of decimal scaling across pool pairs).
- Holding-ratio fallback for tight stablecoin pools where external USD price oracle drifts outside the user's range — active position derived from `valY / (valX + valY)` × range width, keeping the marker inside the user's range when BFF says `coversActiveBin: true`.
- Active-centered 51-cell strip (25 cells + active + 25 cells). Strip half-width = `max(distance to lower bound, distance to upper bound)` so the user's range is always visible on whichever side(s) it occupies.
- Overlap-based user-cell filter (handles degenerate single-bin ranges where `rangeMin === rangeMax`).
- Axis labels use the strip's edges (not the user's range bounds — those were ambiguous on OOR positions).
- Cell hover tooltip shows price RANGE when the cell aggregates multiple bins (e.g. `0.999500 – 1.0005 USDCx/aeUSDC`), single price when 1 bin per cell.
- Bin-per-cell math rendered as clean integer in the footnote (e.g. `≈2 bins per cell · flat distribution`).
- DLMM token-per-side mechanics respected in hover: active cell shows both tokens; cells below active show tokenY only; cells above show tokenX only.
- In-range callout: green ✓ pill with hover detail panel (holding range, active price, bin count, per-token holding amount + percentage).
- Out-of-range callout: red ⚠ pill with hover detail panel — drift %, Δ absolute, holding composition, suggested target band (`current ± 2%`), with the v0.3 note about exact bin IDs.
- Subscript-zero notation for tiny prices: `1.367e-5` renders as `0.0⁴1367` (matches the convention on app.bitflow.finance and across Bitcoin DeFi).
- Single-phrase status: `Holding range … Active price … EARNING.` / `NOT EARNING.` (uppercase verdict).
- Pool flip toggle (`⇄ flip`) is per-card (keyed by `wallet.address + ':' + poolId`) and preserves scroll position on re-render.
- v-0-1 HODLMM positions render as placeholder cards (BFF doesn't track them; pro-rata math is wrong for concentrated liquidity, so we show "Concentrated · details on Bitflow App" instead of dropping them).
- Closed positions hidden from Range Health, LP Value totals, and the default LP+Earnings list. Toggle reveals them.

**Capital Flows**
- Three-card summary strip: External Inflows · External Outflows · Net Flow.
- Per-asset breakdown table with inflow / outflow / net per asset (STX · sBTC · USDCx · FTs).
- Expand-per-row reveals up to 5 most recent transfers per asset with counterparty, amount, USD, timestamp, and explorer link. Export per-asset CSV button.
- Unit toggle: USD · STX · sats equivalent.
- Per-wallet view labels internal transfers (between user's own saved wallets) with the destination/source wallet's label and a "Your wallet" pill. Faint orange row tint helps separate them visually. Aggregated view filters them out.
- Internal-only assets dropped from the aggregated table at render time.
- Net Flow hero card on Overview reflects external-only totals in both views.

**Activity Tape**
- BFF activity (primary) — DLMM swaps, add/remove liquidity, rebalance moves, with USD amounts.
- Hiro transactions (secondary) — non-Bitflow contract calls (dual-stacking distribute-rewards, XYK swaps via older routers, generic interactions, deployments). Dedupe against BFF events by normalized `tx_id`.
- Hiro mempool (tertiary) — in-flight txs render at the top of the tape with an orange "Pending" badge. Mempool `receipt_time` used as timestamp.
- Status badges: success (mint) / Pending (orange) / failed (red). Hiro's verbose `abort_by_post_condition` / `abort_by_response` simplified to "ABORTED" with the full status in a hover tooltip.
- Filter chips work across all three sources: "Swaps" matches `ev.type` OR `fnName.includes('swap')`; "Failures" excludes pending.
- Move-group collapsing: an Add → Withdraw rebalance sequence renders as one expandable card.
- Schema-strict reader: forbidden fields (api_key, secrets, prompts) are never displayed from any source.

**Balances**
- Three-column layout: Stacks Tokens (left, scrollable, paginated), LP Tokens (middle, top N + Load More), BTC + Runes (right, combined card).
- LP Tokens table reads `STX / USDCx · 10 bps [HODLMM]` instead of just the first symbol — full pair label with bin step parsed from contract name. Contract column shortened to `SM1…s-10`.
- BTC subsection combined with Runes inside a single card with separate subtotals.
- Runes table expanded to 4 columns: Symbol · Name · Amount · USD. Amount applies Xverse's `divisibility` scaling (e.g. `16,806,200,000,000` → `168,062` for an 8-decimal rune). USD computed from `currentPrice` (when Xverse provides it).
- BTC L1 row per linked address with format badge (SEGWIT / TAPROOT / NESTED / LEGACY) and Mempool.space link.
- Aggregate "All wallets" view consolidates BTC cards across wallets with per-wallet breakdown row.
- Stacks Tokens enriched via Hiro FT metadata + CoinGecko price for tokens not in any active Bitflow pool.

**Skills**
- Static curated catalog: DeFi Core (wallet · btc · signing-bip-322 · stx · sbtc), DeFi Operations (bitflow · bitflow-swap-aggregator · hodlmm-move-liquidity · hodlmm-bin-guardian · bitflow-hodlmm-deposit · bitflow-hodlmm-withdraw), Strategies (DCA · dual-stacking · dog-intelligence).
- "View on aibtc.com" link per skill (primary CTA).
- AIBTC wordmark SVG inline in the footer "View all skills on AIBTC ↗" CTA.
- Community-unaudited disclaimer below the catalog: *"Skills and Tools shared and/or installed from this directory are created and managed by community members and unaudited. Always inspect SKILL.md before connecting."*

**Empty state + Terms of Service**
- Hero with BFF Bot PFP and warm-orange radial halo behind it (peaks at 30% `#F78116` at center, transparent by 70%, 8px blur — pure halo, no rings or rays).
- "Paste your agent's STX address to begin" prompt with one Stacks input + optional label + optional BTC address.
- Disclaimer bubble (orange-outlined) + storage note (inline with `⚙` icon, no outline) — distinct visual hierarchy: notice vs. tip.
- First-visit mandatory Terms of Service agreement checkbox. Track Agent button is disabled (opacity 0.45, grayscale) until checked. Once accepted, `bitflowAgentTermsAccepted_v1` written to localStorage with `{ ts, version }`. Future visits skip the row.
- Terms of Service page at `/agent-dashboard/terms-of-service` with all 26 sections from the BFF Army User Terms & Policy (May 2026). Sticky header with brand + "← Back to dashboard". Geist-typeset, mobile-responsive at <640px.

**Infrastructure**
- localStorage TTL response cache across all fetchers with per-endpoint TTLs tuned to data volatility:
  - 60 s — STX/FT balances, BFF positions, BFF activity, Hiro transfers, CoinGecko prices
  - 2 min — BFF earnings PnL, BTC Mempool balance
  - 5 min — Xverse runes, BFF /pools list
  - 24 h — Hiro FT metadata
  - 30 s — Hiro mempool (pending txs)
- Cache versioned with `bff_dash_cache_v2_` prefix. Quota-safe: if localStorage fills up, all dashboard cache keys are purged and write retried once.
- The `↻` reload button sets `_forceFresh = true` for the duration of the load, bypassing the cache for an explicit fresh fetch.
- `LAST_REFRESH_KEY` tracks the wall-clock time of the most recent successful load.
- Mint pill next to the Overview eyebrow shows "Refreshed N min ago" with a pulsing mint dot. Fades to orange/amber when ≥5 min stale. Auto-updates every 60 s.

**SEO / OpenGraph**
- Full meta suite: title, description, keywords, theme-color (`#0B0E0B`).
- OpenGraph: type, site_name, title, description, url, image (1200×630), image:alt, locale, **logo** (added per validator feedback).
- Twitter cards: summary_large_image with same image + alt.
- og-image.png losslessly compressed from 737 KB → 468 KB (PNG re-optimization) so WhatsApp's ~600 KB link-preview cap renders the image.

**Beta-deployer filter**
- All contracts under deployer `SP3ESW1QCNQPVXJDGQWT7E45RDCH38QBK9HEJSX4X` (the early HODLMM iteration era — tDOG, tBTC, v-0-0 / v-0-1 DLMM pools) silently filtered from every section: Stacks Tokens, LP Tokens, LP+Earnings, Capital Flows, Activity Tape, hero totals, on-chain compute candidates, token enrichment. Single allowlist (`TEST_DEPLOYERS`) — easy to extend if more test contracts surface later.

### Notes

- This is an MVP. Path 2 (extract to public `bitflow-agent-dashboard` repo with auto-sync from bff-army) and Path 3 (AIBTC installer skill with `wallet`-skill zero-paste auto-seeding) are not yet live. Both are planned as patch releases (`v0.1.x`) before v0.2.0.
- All wallet data lives in browser `localStorage` only — bff.army never sees who added what wallet. Different browser = empty list. The Export/Import buttons in the header are the portability path.
- Read-only tool for educational purposes only. Never share your keys. Not financial advice (NA). Always DYOR. See [Terms of Service](https://bff.army/agent-dashboard/terms-of-service).

---

[Unreleased]: https://github.com/BitflowFinance/bff-army/commits/main/public/agent-dashboard
