dexterm-cli 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/dist/packages/agents/src/audit.js +122 -0
- package/dist/packages/agents/src/bus.js +379 -0
- package/dist/packages/agents/src/executor.js +177 -0
- package/dist/packages/agents/src/gate.js +176 -0
- package/dist/packages/agents/src/identity.js +179 -0
- package/dist/packages/agents/src/index.js +15 -0
- package/dist/packages/agents/src/proposals.js +153 -0
- package/dist/packages/agents/src/schema.js +87 -0
- package/dist/packages/agents/src/sdk.js +140 -0
- package/dist/packages/agents/src/tiers.js +114 -0
- package/dist/packages/agents/src/tools.js +69 -0
- package/dist/packages/agents/src/ws.js +158 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +76 -0
- package/dist/packages/cli/assets/claude/plugin/.claude-plugin/plugin.json +5 -0
- package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +16 -0
- package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +67 -0
- package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +64 -0
- package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +63 -0
- package/dist/packages/cli/assets/claude/plugin/skills/trench-scan/SKILL.md +44 -0
- package/dist/packages/cli/assets/claude/schemas/larp.schema.json +117 -0
- package/dist/packages/cli/assets/claude/schemas/trench-scan.schema.json +71 -0
- package/dist/packages/cli/assets/claude/schemas/verdict.schema.json +115 -0
- package/dist/packages/cli/bin/dexterm.mjs +11 -0
- package/dist/packages/cli/src/agent-cmd.js +303 -0
- package/dist/packages/cli/src/agent-roster.js +26 -0
- package/dist/packages/cli/src/analyst/backend.js +526 -0
- package/dist/packages/cli/src/analyst/results.js +38 -0
- package/dist/packages/cli/src/analyst/session.js +319 -0
- package/dist/packages/cli/src/audit.js +51 -0
- package/dist/packages/cli/src/backtest-cmd.js +149 -0
- package/dist/packages/cli/src/boot-state.js +62 -0
- package/dist/packages/cli/src/custom.js +261 -0
- package/dist/packages/cli/src/display.js +191 -0
- package/dist/packages/cli/src/doctor.js +361 -0
- package/dist/packages/cli/src/feed.js +634 -0
- package/dist/packages/cli/src/fixtures.js +275 -0
- package/dist/packages/cli/src/follow-cmd.js +221 -0
- package/dist/packages/cli/src/images-config.js +59 -0
- package/dist/packages/cli/src/index.js +31 -0
- package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
- package/dist/packages/cli/src/larp-record.js +21 -0
- package/dist/packages/cli/src/live/copy.js +80 -0
- package/dist/packages/cli/src/live/flow.js +233 -0
- package/dist/packages/cli/src/live/index.js +509 -0
- package/dist/packages/cli/src/live/intel.js +227 -0
- package/dist/packages/cli/src/live/newpairs.js +334 -0
- package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
- package/dist/packages/cli/src/live/screens/token-live.js +459 -0
- package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
- package/dist/packages/cli/src/live/token.js +585 -0
- package/dist/packages/cli/src/live/trade.js +929 -0
- package/dist/packages/cli/src/live/watchlist.js +178 -0
- package/dist/packages/cli/src/main.js +5583 -0
- package/dist/packages/cli/src/narrative-cmd.js +76 -0
- package/dist/packages/cli/src/pack-cmd.js +323 -0
- package/dist/packages/cli/src/plan-cmd.js +278 -0
- package/dist/packages/cli/src/popup.js +810 -0
- package/dist/packages/cli/src/rules-cmd.js +131 -0
- package/dist/packages/cli/src/rules.js +339 -0
- package/dist/packages/cli/src/scan/command.js +57 -0
- package/dist/packages/cli/src/scan/engine.js +419 -0
- package/dist/packages/cli/src/scan/observe.js +64 -0
- package/dist/packages/cli/src/scan/ui.js +98 -0
- package/dist/packages/cli/src/screener/filter.js +241 -0
- package/dist/packages/cli/src/screener/presets.js +80 -0
- package/dist/packages/cli/src/screener/rows.js +146 -0
- package/dist/packages/cli/src/screener/rug.js +50 -0
- package/dist/packages/cli/src/screener/sort.js +52 -0
- package/dist/packages/cli/src/screener/view.js +183 -0
- package/dist/packages/cli/src/screens/analyst-session.js +343 -0
- package/dist/packages/cli/src/screens/analyst.js +326 -0
- package/dist/packages/cli/src/screens/boot.js +74 -0
- package/dist/packages/cli/src/screens/bubbles.js +255 -0
- package/dist/packages/cli/src/screens/coin.js +33 -0
- package/dist/packages/cli/src/screens/follows.js +201 -0
- package/dist/packages/cli/src/screens/kol.js +90 -0
- package/dist/packages/cli/src/screens/narrative.js +90 -0
- package/dist/packages/cli/src/screens/newpairs.js +150 -0
- package/dist/packages/cli/src/screens/overlay.js +257 -0
- package/dist/packages/cli/src/screens/plans.js +126 -0
- package/dist/packages/cli/src/screens/positions.js +176 -0
- package/dist/packages/cli/src/screens/rules.js +170 -0
- package/dist/packages/cli/src/screens/screener.js +459 -0
- package/dist/packages/cli/src/screens/social-panel.js +107 -0
- package/dist/packages/cli/src/screens/ticket.js +342 -0
- package/dist/packages/cli/src/screens/token.js +312 -0
- package/dist/packages/cli/src/screens/trending.js +181 -0
- package/dist/packages/cli/src/screens/watchlist.js +104 -0
- package/dist/packages/cli/src/social/demo.js +96 -0
- package/dist/packages/cli/src/social/view.js +250 -0
- package/dist/packages/cli/src/state.js +463 -0
- package/dist/packages/cli/src/theme-cmd.js +126 -0
- package/dist/packages/cli/src/trade.js +319 -0
- package/dist/packages/cli/src/voice-hud.js +42 -0
- package/dist/packages/cli/src/wallet-cmd.js +440 -0
- package/dist/packages/cli/src/wallet-command.js +31 -0
- package/dist/packages/cli/src/widgets.js +208 -0
- package/dist/packages/cli/src/workspace-cmd.js +68 -0
- package/dist/packages/cli/src/x-cmd.js +154 -0
- package/dist/packages/custom/src/config.js +213 -0
- package/dist/packages/custom/src/index.js +10 -0
- package/dist/packages/custom/src/keymap.js +107 -0
- package/dist/packages/custom/src/layout.js +235 -0
- package/dist/packages/custom/src/packs.js +633 -0
- package/dist/packages/custom/src/rules.js +704 -0
- package/dist/packages/custom/src/schema.js +515 -0
- package/dist/packages/custom/src/themes.js +176 -0
- package/dist/packages/custom/src/widget-html.js +222 -0
- package/dist/packages/custom/src/workspace.js +141 -0
- package/dist/packages/data/src/analyst-actions.js +133 -0
- package/dist/packages/data/src/budget.js +88 -0
- package/dist/packages/data/src/cache.js +142 -0
- package/dist/packages/data/src/candles.js +168 -0
- package/dist/packages/data/src/clock.js +64 -0
- package/dist/packages/data/src/credits.js +137 -0
- package/dist/packages/data/src/detail.js +97 -0
- package/dist/packages/data/src/fast-lane.js +321 -0
- package/dist/packages/data/src/fixtures.js +135 -0
- package/dist/packages/data/src/health.js +245 -0
- package/dist/packages/data/src/http.js +277 -0
- package/dist/packages/data/src/images/cache.js +141 -0
- package/dist/packages/data/src/images/decode-worker.js +28 -0
- package/dist/packages/data/src/images/decode.js +81 -0
- package/dist/packages/data/src/images/fetch.js +162 -0
- package/dist/packages/data/src/images/gif.js +214 -0
- package/dist/packages/data/src/images/index.js +10 -0
- package/dist/packages/data/src/images/jpeg.js +544 -0
- package/dist/packages/data/src/images/png.js +310 -0
- package/dist/packages/data/src/images/service.js +334 -0
- package/dist/packages/data/src/images/types.js +27 -0
- package/dist/packages/data/src/index.js +208 -0
- package/dist/packages/data/src/intel/helius-history.js +59 -0
- package/dist/packages/data/src/intel/holder-class.js +125 -0
- package/dist/packages/data/src/intel/rules.js +421 -0
- package/dist/packages/data/src/intel/scan.js +170 -0
- package/dist/packages/data/src/larp/evidence.js +288 -0
- package/dist/packages/data/src/larp/links.js +137 -0
- package/dist/packages/data/src/larp/robots.js +90 -0
- package/dist/packages/data/src/larp/safe-fetch.js +389 -0
- package/dist/packages/data/src/limiter.js +341 -0
- package/dist/packages/data/src/model.js +12 -0
- package/dist/packages/data/src/paper.js +331 -0
- package/dist/packages/data/src/rpc-config.js +126 -0
- package/dist/packages/data/src/rpc-pool.js +166 -0
- package/dist/packages/data/src/social/chain.js +83 -0
- package/dist/packages/data/src/social/dedupe.js +205 -0
- package/dist/packages/data/src/social/feed.js +462 -0
- package/dist/packages/data/src/social/firewall.js +110 -0
- package/dist/packages/data/src/social/index.js +11 -0
- package/dist/packages/data/src/social/kol.js +269 -0
- package/dist/packages/data/src/social/model.js +11 -0
- package/dist/packages/data/src/social/rank.js +184 -0
- package/dist/packages/data/src/social/rss.js +113 -0
- package/dist/packages/data/src/social/summary.js +104 -0
- package/dist/packages/data/src/social/venues.js +186 -0
- package/dist/packages/data/src/sources/dexscreener.js +170 -0
- package/dist/packages/data/src/sources/geckoterminal.js +133 -0
- package/dist/packages/data/src/sources/goplus.js +161 -0
- package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
- package/dist/packages/data/src/sources/jupiter.js +198 -0
- package/dist/packages/data/src/sources/pda.js +113 -0
- package/dist/packages/data/src/sources/pump-curve.js +58 -0
- package/dist/packages/data/src/sources/pumpportal.js +247 -0
- package/dist/packages/data/src/sources/rugcheck.js +71 -0
- package/dist/packages/data/src/sources/solana-rpc.js +299 -0
- package/dist/packages/data/src/sources/solana-ws.js +287 -0
- package/dist/packages/data/src/store.js +289 -0
- package/dist/packages/data/src/trade/bridge.js +464 -0
- package/dist/packages/data/src/trade/caps.js +323 -0
- package/dist/packages/data/src/trade/copy/auto.js +385 -0
- package/dist/packages/data/src/trade/copy/follows.js +103 -0
- package/dist/packages/data/src/trade/copy/index.js +9 -0
- package/dist/packages/data/src/trade/copy/mirror.js +91 -0
- package/dist/packages/data/src/trade/copy/stats.js +120 -0
- package/dist/packages/data/src/trade/copy/tracker.js +158 -0
- package/dist/packages/data/src/trade/copy/trades.js +87 -0
- package/dist/packages/data/src/trade/copy/types.js +6 -0
- package/dist/packages/data/src/trade/executor.js +933 -0
- package/dist/packages/data/src/trade/kill-switch.js +47 -0
- package/dist/packages/data/src/trade/link-store.js +52 -0
- package/dist/packages/data/src/trade/plans/confirm.js +157 -0
- package/dist/packages/data/src/trade/plans/engine.js +784 -0
- package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
- package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
- package/dist/packages/data/src/trade/plans/executor.js +11 -0
- package/dist/packages/data/src/trade/plans/index.js +12 -0
- package/dist/packages/data/src/trade/plans/journal.js +77 -0
- package/dist/packages/data/src/trade/plans/mandate.js +129 -0
- package/dist/packages/data/src/trade/plans/plan.js +179 -0
- package/dist/packages/data/src/trade/plans/service.js +168 -0
- package/dist/packages/data/src/trade/plans/types.js +6 -0
- package/dist/packages/data/src/trade/rpc-exec.js +229 -0
- package/dist/packages/data/src/trade/signer/client.js +197 -0
- package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
- package/dist/packages/data/src/trade/signer/host.js +97 -0
- package/dist/packages/data/src/trade/signer/main.js +121 -0
- package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
- package/dist/packages/data/src/trade/signer/protocol.js +92 -0
- package/dist/packages/data/src/trade/signer/service.js +400 -0
- package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
- package/dist/packages/data/src/trade/sol-usd.js +86 -0
- package/dist/packages/data/src/trade/swap-build.js +108 -0
- package/dist/packages/data/src/trade/token-delta.js +201 -0
- package/dist/packages/data/src/trade/tx-decode.js +632 -0
- package/dist/packages/data/src/util.js +24 -0
- package/dist/packages/data/src/watchlist.js +176 -0
- package/dist/packages/data/src/x/api.js +256 -0
- package/dist/packages/data/src/x/budget.js +66 -0
- package/dist/packages/data/src/x/config.js +49 -0
- package/dist/packages/data/src/x/index.js +10 -0
- package/dist/packages/data/src/x/mock-server.js +217 -0
- package/dist/packages/data/src/x/oauth.js +242 -0
- package/dist/packages/data/src/x/pkce.js +21 -0
- package/dist/packages/data/src/x/source.js +285 -0
- package/dist/packages/data/src/x/token-store.js +134 -0
- package/dist/packages/data/src/x/transport.js +71 -0
- package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
- package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
- package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
- package/dist/packages/mcp/assets/cases.json +1194 -0
- package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
- package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
- package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
- package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
- package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
- package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
- package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
- package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
- package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
- package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
- package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
- package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
- package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
- package/dist/packages/mcp/src/dossier.js +78 -0
- package/dist/packages/mcp/src/firewall.js +21 -0
- package/dist/packages/mcp/src/index.js +22 -0
- package/dist/packages/mcp/src/server.js +849 -0
- package/dist/packages/mcp/src/user-content.js +434 -0
- package/dist/packages/packs/src/backtest.js +183 -0
- package/dist/packages/packs/src/digest.js +95 -0
- package/dist/packages/packs/src/firewall.js +57 -0
- package/dist/packages/packs/src/index.js +19 -0
- package/dist/packages/packs/src/larp-eval.js +100 -0
- package/dist/packages/packs/src/narrative.js +309 -0
- package/dist/packages/packs/src/reason.js +135 -0
- package/dist/packages/packs/src/rug-model.js +137 -0
- package/dist/packages/packs/src/schema.js +54 -0
- package/dist/packages/packs/src/token-dossier.js +174 -0
- package/dist/packages/packs/src/verdicts.js +17 -0
- package/dist/packages/packs/src/wallet-dossier.js +105 -0
- package/dist/packages/popup/src/config.js +71 -0
- package/dist/packages/popup/src/dock.js +271 -0
- package/dist/packages/popup/src/host.js +210 -0
- package/dist/packages/popup/src/index.js +12 -0
- package/dist/packages/popup/src/loopback.js +53 -0
- package/dist/packages/popup/src/open.js +183 -0
- package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
- package/dist/packages/popup/src/pages/bubbles.css +23 -0
- package/dist/packages/popup/src/pages/bubbles.html +36 -0
- package/dist/packages/popup/src/pages/bubbles.js +214 -0
- package/dist/packages/popup/src/pages/chart-lib.js +262 -0
- package/dist/packages/popup/src/pages/chart.css +50 -0
- package/dist/packages/popup/src/pages/chart.html +56 -0
- package/dist/packages/popup/src/pages/chart.js +554 -0
- package/dist/packages/popup/src/pages/common.css +34 -0
- package/dist/packages/popup/src/pages/common.js +187 -0
- package/dist/packages/popup/src/pages/feeds.html +20 -0
- package/dist/packages/popup/src/pages/feeds.js +77 -0
- package/dist/packages/popup/src/pages/narrative.html +20 -0
- package/dist/packages/popup/src/pages/narrative.js +61 -0
- package/dist/packages/popup/src/pages/plans.css +30 -0
- package/dist/packages/popup/src/pages/plans.html +26 -0
- package/dist/packages/popup/src/pages/plans.js +82 -0
- package/dist/packages/popup/src/pages/roster.css +41 -0
- package/dist/packages/popup/src/pages/roster.html +25 -0
- package/dist/packages/popup/src/pages/roster.js +75 -0
- package/dist/packages/popup/src/pages/token.html +20 -0
- package/dist/packages/popup/src/pages/token.js +112 -0
- package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
- package/dist/packages/popup/src/pages/widget.css +5 -0
- package/dist/packages/popup/src/pages/widget.html +21 -0
- package/dist/packages/popup/src/pages/widget.js +85 -0
- package/dist/packages/popup/src/protocol.js +31 -0
- package/dist/packages/popup/src/selftest.js +156 -0
- package/dist/packages/popup/src/server.js +582 -0
- package/dist/packages/popup/src/sinkproxy.js +167 -0
- package/dist/packages/popup/src/widget-html.js +231 -0
- package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
- package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
- package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
- package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
- package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
- package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
- package/dist/packages/recorder/src/cli.js +113 -0
- package/dist/packages/recorder/src/export.js +71 -0
- package/dist/packages/recorder/src/index.js +20 -0
- package/dist/packages/recorder/src/labels.js +151 -0
- package/dist/packages/recorder/src/open.js +29 -0
- package/dist/packages/recorder/src/recorder.js +553 -0
- package/dist/packages/recorder/src/schedule.js +43 -0
- package/dist/packages/recorder/src/schema.js +135 -0
- package/dist/packages/recorder/src/snapshot.js +191 -0
- package/dist/packages/recorder/src/sweep.js +52 -0
- package/dist/packages/recorder/src/verdicts.js +52 -0
- package/dist/packages/tui/src/bubbles.js +415 -0
- package/dist/packages/tui/src/canvas.js +370 -0
- package/dist/packages/tui/src/chrome.js +85 -0
- package/dist/packages/tui/src/color.js +133 -0
- package/dist/packages/tui/src/flush.js +62 -0
- package/dist/packages/tui/src/fmt.js +188 -0
- package/dist/packages/tui/src/fx-trade.js +278 -0
- package/dist/packages/tui/src/fx.js +527 -0
- package/dist/packages/tui/src/images/blocks.js +141 -0
- package/dist/packages/tui/src/images/coin.js +22 -0
- package/dist/packages/tui/src/images/detect.js +140 -0
- package/dist/packages/tui/src/images/index.js +7 -0
- package/dist/packages/tui/src/images/layer.js +146 -0
- package/dist/packages/tui/src/images/pixels.js +157 -0
- package/dist/packages/tui/src/images/protocols.js +87 -0
- package/dist/packages/tui/src/index.js +15 -0
- package/dist/packages/tui/src/keymap.js +355 -0
- package/dist/packages/tui/src/layout.js +11 -0
- package/dist/packages/tui/src/sound.js +177 -0
- package/dist/packages/tui/src/theme.js +182 -0
- package/dist/packages/tui/src/widgets.js +598 -0
- package/dist/packages/voice/src/controller.js +395 -0
- package/dist/packages/voice/src/doctor.js +63 -0
- package/dist/packages/voice/src/engines.js +44 -0
- package/dist/packages/voice/src/index.js +16 -0
- package/dist/packages/voice/src/router.js +502 -0
- package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
- package/dist/packages/voice/src/stt/apple.js +179 -0
- package/dist/packages/voice/src/stt/fake.js +34 -0
- package/dist/packages/voice/src/stt/whisper.js +102 -0
- package/dist/packages/voice/src/summary.js +44 -0
- package/dist/packages/voice/src/tts/fake.js +30 -0
- package/dist/packages/voice/src/tts/say.js +96 -0
- package/dist/packages/voice/src/types.js +3 -0
- package/package.json +42 -4
- package/README.md +0 -3
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { mix } from "./color.js";
|
|
2
|
+
import { shortAddr } from "./fmt.js";
|
|
3
|
+
export const KIND_LETTER = { 'dev-linked': 'D', dev: 'D', bundle: 'B', funder: 'F', transfer: 'T', sniper: 'S', pool: 'P', cex: 'X', whale: 'W', plain: '·' };
|
|
4
|
+
export const LEGEND = [['dev', 'dev'], ['bundle', 'bundle'], ['funder', 'linked'], ['sniper', 'sniper'], ['pool', 'pool/LP'], ['cex', 'exchange'], ['plain', 'unlinked']];
|
|
5
|
+
export function kindColor(t, kind) {
|
|
6
|
+
switch (kind) {
|
|
7
|
+
case 'dev':
|
|
8
|
+
case 'dev-linked': return t.warn;
|
|
9
|
+
case 'bundle': return t.pump;
|
|
10
|
+
case 'funder': return t.up;
|
|
11
|
+
case 'transfer': return t.dbc;
|
|
12
|
+
case 'sniper': return t.down;
|
|
13
|
+
case 'pool': return t.accent;
|
|
14
|
+
case 'cex':
|
|
15
|
+
case 'whale': return t.muted;
|
|
16
|
+
default: return mix(t.panel, t.up, 0.45);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Display kind of one holder: its cluster's kind when linked, else the strongest of its own labels. */
|
|
20
|
+
export function holderKind(labels, cluster) {
|
|
21
|
+
if (cluster && cluster.members.length > 1 && cluster.kind !== 'single')
|
|
22
|
+
return cluster.kind;
|
|
23
|
+
const ls = new Set(labels.map((l) => l.label));
|
|
24
|
+
if (ls.has('dev'))
|
|
25
|
+
return 'dev';
|
|
26
|
+
if (ls.has('sniper'))
|
|
27
|
+
return 'sniper';
|
|
28
|
+
if (ls.has('pool') || ls.has('LP') || ls.has('contract') || ls.has('unfunded-authority?'))
|
|
29
|
+
return 'pool';
|
|
30
|
+
if (ls.has('cex'))
|
|
31
|
+
return 'cex';
|
|
32
|
+
if (ls.has('multi-token-top20'))
|
|
33
|
+
return 'whale';
|
|
34
|
+
return 'plain';
|
|
35
|
+
}
|
|
36
|
+
export function bubbleVM(a, token) {
|
|
37
|
+
const clusters = a.clusters.map((c) => ({ name: c.name, kind: c.kind, members: c.members.slice(), pct: c.pct, labels: c.labels ?? [], evidence: c.evidence ?? [] }));
|
|
38
|
+
const byOwner = new Map();
|
|
39
|
+
for (const c of clusters)
|
|
40
|
+
for (const m of c.members)
|
|
41
|
+
byOwner.set(m, c);
|
|
42
|
+
const holders = a.holders.map((h) => {
|
|
43
|
+
const c = byOwner.get(h.owner) ?? null;
|
|
44
|
+
return { owner: h.owner, pct: h.pct ?? 0, labels: h.labels ?? [], funder: h.funder ?? null, walletFirstSeen: h.walletFirstSeen ?? null, cluster: c && c.members.length > 1 ? c.name : null, kind: holderKind(h.labels ?? [], c) };
|
|
45
|
+
});
|
|
46
|
+
return { symbol: token.symbol.replace(/^\$/, ''), name: token.name ?? '', mint: token.mint, holders, clusters, holderCount: token.holderCount ?? null, coverage: a.insights?.coverage ?? null };
|
|
47
|
+
}
|
|
48
|
+
function placeCluster(members) {
|
|
49
|
+
const hub = members[0];
|
|
50
|
+
const out = [{ ...hub, dx: 0, dy: 0 }];
|
|
51
|
+
if (members.length === 1)
|
|
52
|
+
return { items: out, R: hub.r + 0.6 };
|
|
53
|
+
let ringR = hub.r;
|
|
54
|
+
let k = 1;
|
|
55
|
+
let maxReach = hub.r;
|
|
56
|
+
while (k < members.length) {
|
|
57
|
+
const ringMembers = [];
|
|
58
|
+
const d0 = ringR + 0.8;
|
|
59
|
+
let angleUsed = 0, biggest = 0;
|
|
60
|
+
while (k < members.length) {
|
|
61
|
+
const m = members[k];
|
|
62
|
+
const d = d0 + m.r;
|
|
63
|
+
const need = 2 * Math.asin(Math.min(1, (m.r + 0.35) / d));
|
|
64
|
+
if (ringMembers.length && angleUsed + need > 2 * Math.PI)
|
|
65
|
+
break;
|
|
66
|
+
ringMembers.push({ m, d, need });
|
|
67
|
+
angleUsed += need;
|
|
68
|
+
biggest = Math.max(biggest, m.r);
|
|
69
|
+
k++;
|
|
70
|
+
}
|
|
71
|
+
const slack = Math.max(0, 2 * Math.PI - angleUsed) / ringMembers.length;
|
|
72
|
+
let a = -Math.PI / 2 + (ringMembers.length % 2 ? 0 : slack / 2) + (out.length * 0.37);
|
|
73
|
+
for (const { m, d, need } of ringMembers) {
|
|
74
|
+
a += need / 2 + slack / 2;
|
|
75
|
+
out.push({ ...m, dx: Math.cos(a) * d, dy: Math.sin(a) * d });
|
|
76
|
+
maxReach = Math.max(maxReach, d + m.r);
|
|
77
|
+
a += need / 2 + slack / 2;
|
|
78
|
+
}
|
|
79
|
+
ringR = d0 + 2 * biggest;
|
|
80
|
+
}
|
|
81
|
+
return { items: out, R: maxReach + 0.6 };
|
|
82
|
+
}
|
|
83
|
+
function packClusters(cls, W, H2, gap = 1.0) {
|
|
84
|
+
const placed = [];
|
|
85
|
+
const cx0 = W / 2, cy0 = H2 / 2;
|
|
86
|
+
const inside = (x, y, R) => x - R >= 0 && x + R <= W && y - R >= 0 && y + R <= H2;
|
|
87
|
+
const free = (x, y, R) => placed.every((p) => Math.hypot(p.x - x, p.y - y) >= p.R + R + gap - 1e-6);
|
|
88
|
+
for (const c of cls) {
|
|
89
|
+
if (!placed.length) {
|
|
90
|
+
if (!inside(cx0, cy0, c.R))
|
|
91
|
+
return null;
|
|
92
|
+
placed.push({ ...c, x: cx0, y: cy0 });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
let best = null, bestD = Infinity;
|
|
96
|
+
for (const p of placed) {
|
|
97
|
+
const steps = 48;
|
|
98
|
+
for (let s = 0; s < steps; s++) {
|
|
99
|
+
const th = (s / steps) * 2 * Math.PI;
|
|
100
|
+
const d = p.R + c.R + gap;
|
|
101
|
+
const x = p.x + Math.cos(th) * d, y = p.y + Math.sin(th) * d;
|
|
102
|
+
if (!inside(x, y, c.R) || !free(x, y, c.R))
|
|
103
|
+
continue;
|
|
104
|
+
const dd = (x - cx0) ** 2 + ((y - cy0) * 1.15) ** 2;
|
|
105
|
+
if (dd < bestD) {
|
|
106
|
+
bestD = dd;
|
|
107
|
+
best = { x, y };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!best)
|
|
112
|
+
return null;
|
|
113
|
+
placed.push({ ...c, x: best.x, y: best.y });
|
|
114
|
+
}
|
|
115
|
+
return placed;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Lay out holders in a W×H cell area (pixel space W × 2H). `focus` = a cluster name → only that cluster, filling
|
|
119
|
+
* the area (zoom). Falls back to a tiny grid when nothing packs (never throws).
|
|
120
|
+
*/
|
|
121
|
+
export function layoutBubbles(vm, W, H, opts = {}) {
|
|
122
|
+
const H2 = H * 2;
|
|
123
|
+
const idxs = vm.holders.map((_, i) => i).filter((i) => !opts.focus || vm.holders[i].cluster === opts.focus);
|
|
124
|
+
if (!idxs.length || W < 4 || H < 2)
|
|
125
|
+
return { bubbles: [], clusters: [], k: 0, W, H2 };
|
|
126
|
+
const total = idxs.reduce((s, i) => s + Math.max(0.01, vm.holders[i].pct), 0);
|
|
127
|
+
const fill = opts.fill ?? (opts.focus ? 0.62 : 0.5);
|
|
128
|
+
let k = Math.sqrt((fill * W * H2) / (Math.PI * total));
|
|
129
|
+
const rMax = Math.min(W / 5.2, H2 / 2.5);
|
|
130
|
+
for (let iter = 0; iter < 14; iter++) {
|
|
131
|
+
const byCluster = new Map();
|
|
132
|
+
for (const i of idxs) {
|
|
133
|
+
const h = vm.holders[i];
|
|
134
|
+
const r = Math.max(1, Math.min(rMax, k * Math.sqrt(Math.max(0.01, h.pct))));
|
|
135
|
+
const key = h.cluster || `single:${i}`;
|
|
136
|
+
const arr = byCluster.get(key) || [];
|
|
137
|
+
arr.push({ i, r });
|
|
138
|
+
byCluster.set(key, arr);
|
|
139
|
+
}
|
|
140
|
+
const cls = [];
|
|
141
|
+
for (const [key, members] of byCluster) {
|
|
142
|
+
members.sort((a, b) => b.r - a.r);
|
|
143
|
+
const pc = placeCluster(members);
|
|
144
|
+
const c = vm.clusters.find((x) => x.name === key) || null;
|
|
145
|
+
cls.push({ key, name: c ? c.name : null, kind: c ? c.kind : vm.holders[members[0].i].kind, pct: c ? c.pct : vm.holders[members[0].i].pct, items: pc.items, R: pc.R, multi: members.length > 1 });
|
|
146
|
+
}
|
|
147
|
+
cls.sort((a, b) => b.R - a.R);
|
|
148
|
+
const placed = packClusters(cls, W, H2);
|
|
149
|
+
if (placed) {
|
|
150
|
+
const bubbles = [], clusters = [];
|
|
151
|
+
for (const p of placed) {
|
|
152
|
+
for (const it of p.items)
|
|
153
|
+
bubbles.push({ idx: it.i, cx: p.x + it.dx, cy: p.y + it.dy, r: it.r });
|
|
154
|
+
clusters.push({ name: p.name, kind: p.kind, pct: p.pct, cx: p.x, cy: p.y, R: p.R, members: p.items.map((it) => it.i), multi: p.multi });
|
|
155
|
+
}
|
|
156
|
+
return { bubbles, clusters, k, W, H2 };
|
|
157
|
+
}
|
|
158
|
+
k *= 0.86;
|
|
159
|
+
}
|
|
160
|
+
const cols = Math.max(1, Math.floor(W / 4));
|
|
161
|
+
const bubbles = idxs.map((i, n) => ({ idx: i, cx: 2 + (n % cols) * 4, cy: 2 + Math.floor(n / cols) * 4, r: 1 }));
|
|
162
|
+
return { bubbles, clusters: [], k, W, H2, overflow: true };
|
|
163
|
+
}
|
|
164
|
+
// ---------------------------------------------------------------- drawing
|
|
165
|
+
const INK = '#031008';
|
|
166
|
+
/** Draw the packed bubbles into the cell area (x, y, w, h). Returns the pixel ownership grid (for hit tests). */
|
|
167
|
+
export function drawBubbles(c, x, y, w, h, vm, layout, opts = {}) {
|
|
168
|
+
const t = c.theme;
|
|
169
|
+
const bg = opts.bg || t.panel;
|
|
170
|
+
const { W, H2 } = layout;
|
|
171
|
+
const pix = new Int16Array(W * H2).fill(-1);
|
|
172
|
+
const colorOf = (i) => (i === opts.selected ? t.borderFocus : kindColor(t, vm.holders[i].kind));
|
|
173
|
+
for (const b of layout.bubbles) {
|
|
174
|
+
const r2 = b.r * b.r;
|
|
175
|
+
for (let py = Math.max(0, Math.floor(b.cy - b.r)); py < Math.min(H2, Math.ceil(b.cy + b.r)); py++) {
|
|
176
|
+
for (let px = Math.max(0, Math.floor(b.cx - b.r)); px < Math.min(W, Math.ceil(b.cx + b.r)); px++) {
|
|
177
|
+
const dx = px + 0.5 - b.cx, dy = py + 0.5 - b.cy;
|
|
178
|
+
if (dx * dx + dy * dy <= r2)
|
|
179
|
+
pix[py * W + px] = b.idx;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// cluster rings (soft dots on blank cells), drawn before bubbles so bubbles win
|
|
184
|
+
for (const cl of layout.clusters) {
|
|
185
|
+
if (!cl.multi)
|
|
186
|
+
continue;
|
|
187
|
+
const col = mix(bg, kindColor(t, cl.kind), 0.75);
|
|
188
|
+
for (let s = 0; s < 72; s++) {
|
|
189
|
+
const th = (s / 72) * 2 * Math.PI;
|
|
190
|
+
const px = Math.round(cl.cx + (cl.R + 0.4) * Math.cos(th) - 0.5);
|
|
191
|
+
const py = cl.cy + (cl.R + 0.4) * Math.sin(th);
|
|
192
|
+
if (px < 0 || px >= W || py < 0 || py >= H2)
|
|
193
|
+
continue;
|
|
194
|
+
const cx = x + px, cy = y + Math.floor(py / 2);
|
|
195
|
+
if (cx >= c.w || cy >= c.h)
|
|
196
|
+
continue;
|
|
197
|
+
const i = c.idx(cx, cy);
|
|
198
|
+
if (c.ch[i] === ' ' && pix[Math.floor(py) * W + px] === -1)
|
|
199
|
+
c.put(cx, cy, '·', { fg: col, bg, soft: true });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// bubbles: pairs of half-rows → ▀ ▄ █
|
|
203
|
+
for (let row = 0; row < h; row++) {
|
|
204
|
+
for (let col = 0; col < w; col++) {
|
|
205
|
+
const top = pix[(2 * row) * W + col], bot = pix[(2 * row + 1) * W + col];
|
|
206
|
+
if (top < 0 && bot < 0)
|
|
207
|
+
continue;
|
|
208
|
+
const cx = x + col, cy = y + row;
|
|
209
|
+
if (top >= 0 && bot >= 0) {
|
|
210
|
+
if (top === bot)
|
|
211
|
+
c.put(cx, cy, '█', { fg: colorOf(top), bg, soft: true });
|
|
212
|
+
else
|
|
213
|
+
c.put(cx, cy, '▀', { fg: colorOf(top), bg: colorOf(bot), soft: true });
|
|
214
|
+
}
|
|
215
|
+
else if (top >= 0)
|
|
216
|
+
c.put(cx, cy, '▀', { fg: colorOf(top), bg, soft: true });
|
|
217
|
+
else
|
|
218
|
+
c.put(cx, cy, '▄', { fg: colorOf(bot), bg, soft: true });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// dotted edges from the selected holder to its cluster mates (blank cells only)
|
|
222
|
+
if (opts.selected != null) {
|
|
223
|
+
const sb = layout.bubbles.find((b) => b.idx === opts.selected);
|
|
224
|
+
const cl = sb && layout.clusters.find((k) => k.members.includes(opts.selected));
|
|
225
|
+
if (sb && cl && cl.multi) {
|
|
226
|
+
for (const m of cl.members) {
|
|
227
|
+
if (m === opts.selected)
|
|
228
|
+
continue;
|
|
229
|
+
const mb = layout.bubbles.find((b) => b.idx === m);
|
|
230
|
+
if (mb)
|
|
231
|
+
line(c, x, y, W, H2, pix, sb, mb, t.accent, bg);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// labels inside bubbles (hard cells)
|
|
236
|
+
for (const b of layout.bubbles) {
|
|
237
|
+
if (b.r < 2.5)
|
|
238
|
+
continue;
|
|
239
|
+
const hd = vm.holders[b.idx];
|
|
240
|
+
const color = colorOf(b.idx);
|
|
241
|
+
const widthAtCentre = Math.floor(2 * b.r) - 1;
|
|
242
|
+
const row = Math.floor(b.cy / 2);
|
|
243
|
+
const lines = [];
|
|
244
|
+
const addr = shortAddr(hd.owner, 4);
|
|
245
|
+
const pct = `${hd.pct.toFixed(hd.pct >= 10 ? 1 : 2)}%`;
|
|
246
|
+
if (widthAtCentre >= addr.length)
|
|
247
|
+
lines.push(addr);
|
|
248
|
+
else if (widthAtCentre >= pct.length)
|
|
249
|
+
lines.push(pct);
|
|
250
|
+
if (b.r >= 4 && lines[0] === addr && widthAtCentre >= pct.length)
|
|
251
|
+
lines.push(pct);
|
|
252
|
+
if (b.r >= 6 && hd.kind !== 'plain')
|
|
253
|
+
lines.push(hd.kind.toUpperCase().slice(0, widthAtCentre));
|
|
254
|
+
const startRow = row - Math.floor((lines.length - 1) / 2);
|
|
255
|
+
lines.forEach((s, li) => {
|
|
256
|
+
const ry = startRow + li;
|
|
257
|
+
if (ry < 0 || ry >= h)
|
|
258
|
+
return;
|
|
259
|
+
const x0 = Math.round(b.cx - s.length / 2);
|
|
260
|
+
for (let k = 0; k < s.length; k++) {
|
|
261
|
+
const px = x0 + k;
|
|
262
|
+
if (px < 0 || px >= W)
|
|
263
|
+
continue;
|
|
264
|
+
if (pix[(2 * ry) * W + px] !== b.idx || pix[(2 * ry + 1) * W + px] !== b.idx)
|
|
265
|
+
continue; // stay inside the disc
|
|
266
|
+
c.put(x + px, y + ry, s[k], { fg: INK, bg: color, b: li === 0, hard: true });
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// cluster tags (above the ring when there is room, else below), only on blank cells
|
|
271
|
+
for (const cl of layout.clusters) {
|
|
272
|
+
if (!cl.multi)
|
|
273
|
+
continue;
|
|
274
|
+
const color = kindColor(t, cl.kind);
|
|
275
|
+
const tag = `${KIND_LETTER[cl.kind] || '·'}·${cl.name} ${cl.members.length}w ${cl.pct.toFixed(1)}%`;
|
|
276
|
+
const x0 = Math.round(cl.cx - tag.length / 2);
|
|
277
|
+
const above = Math.floor((cl.cy - cl.R) / 2) - 1, below = Math.floor((cl.cy + cl.R) / 2) + 1;
|
|
278
|
+
for (const ry of [above, below]) {
|
|
279
|
+
if (ry < 0 || ry >= h)
|
|
280
|
+
continue;
|
|
281
|
+
let ok = x0 >= 0 && x0 + tag.length <= W;
|
|
282
|
+
for (let k = 0; ok && k < tag.length; k++) {
|
|
283
|
+
const i = c.idx(x + x0 + k, y + ry);
|
|
284
|
+
if (c.ch[i] !== ' ' && c.ch[i] !== '·')
|
|
285
|
+
ok = false;
|
|
286
|
+
}
|
|
287
|
+
if (!ok)
|
|
288
|
+
continue;
|
|
289
|
+
c.text(x + x0, y + ry, tag, { fg: color, bg, b: true });
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return pix;
|
|
294
|
+
}
|
|
295
|
+
function line(c, x, y, W, H2, pix, a, b, fg, bg) {
|
|
296
|
+
let x0 = Math.round(a.cx), y0 = Math.round(a.cy / 2);
|
|
297
|
+
const x1 = Math.round(b.cx), y1 = Math.round(b.cy / 2);
|
|
298
|
+
const dx = Math.abs(x1 - x0), dy = -Math.abs(y1 - y0), sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1;
|
|
299
|
+
let err = dx + dy;
|
|
300
|
+
for (let n = 0; n < 400; n++) {
|
|
301
|
+
if (x0 >= 0 && x0 < W && y0 >= 0 && y0 * 2 < H2) {
|
|
302
|
+
const covered = pix[(2 * y0) * W + x0] !== -1 || pix[(2 * y0 + 1) * W + x0] !== -1;
|
|
303
|
+
const i = c.idx(x + x0, y + y0);
|
|
304
|
+
if (!covered && c.ch[i] === ' ')
|
|
305
|
+
c.put(x + x0, y + y0, '∙', { fg, bg, soft: true });
|
|
306
|
+
}
|
|
307
|
+
if (x0 === x1 && y0 === y1)
|
|
308
|
+
break;
|
|
309
|
+
const e2 = 2 * err;
|
|
310
|
+
if (e2 >= dy) {
|
|
311
|
+
err += dy;
|
|
312
|
+
x0 += sx;
|
|
313
|
+
}
|
|
314
|
+
if (e2 <= dx) {
|
|
315
|
+
err += dx;
|
|
316
|
+
y0 += sy;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function squarify(children, rect) {
|
|
321
|
+
const out = [];
|
|
322
|
+
let i = 0;
|
|
323
|
+
const rem = { ...rect };
|
|
324
|
+
while (i < children.length && rem.w > 0 && rem.h > 0) {
|
|
325
|
+
const vertical = rem.w >= rem.h;
|
|
326
|
+
const side = vertical ? rem.h : rem.w;
|
|
327
|
+
let row = [], sum = 0, bestWorst = Infinity;
|
|
328
|
+
while (i < children.length) {
|
|
329
|
+
const v = children[i].value;
|
|
330
|
+
const cand = [...row, children[i]], s = sum + v, thick = s / side;
|
|
331
|
+
let worst = 0;
|
|
332
|
+
for (const ch of cand) {
|
|
333
|
+
const len = ch.value / thick;
|
|
334
|
+
worst = Math.max(worst, len / thick, thick / len);
|
|
335
|
+
}
|
|
336
|
+
if (row.length && worst > bestWorst)
|
|
337
|
+
break;
|
|
338
|
+
row = cand;
|
|
339
|
+
sum = s;
|
|
340
|
+
bestWorst = worst;
|
|
341
|
+
i++;
|
|
342
|
+
}
|
|
343
|
+
const thick = sum / side;
|
|
344
|
+
let off = 0;
|
|
345
|
+
for (const ch of row) {
|
|
346
|
+
const len = ch.value / thick;
|
|
347
|
+
out.push(vertical ? { item: ch.item, x: rem.x, y: rem.y + off, w: thick, h: len } : { item: ch.item, x: rem.x + off, y: rem.y, w: len, h: thick });
|
|
348
|
+
off += len;
|
|
349
|
+
}
|
|
350
|
+
if (vertical) {
|
|
351
|
+
rem.x += thick;
|
|
352
|
+
rem.w -= thick;
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
rem.y += thick;
|
|
356
|
+
rem.h -= thick;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return out;
|
|
360
|
+
}
|
|
361
|
+
export function drawTreemap(c, x, y, w, h, vm, opts = {}) {
|
|
362
|
+
const t = c.theme;
|
|
363
|
+
const bg = opts.bg || t.panel;
|
|
364
|
+
const items = vm.clusters.slice().sort((a, b) => b.pct - a.pct).map((cl) => ({ item: cl, value: Math.max(0.05, cl.pct) }));
|
|
365
|
+
const total = items.reduce((s, it) => s + it.value, 0);
|
|
366
|
+
if (!total)
|
|
367
|
+
return;
|
|
368
|
+
const area = w * (h * 2);
|
|
369
|
+
for (const it of items)
|
|
370
|
+
it.value = (it.value / total) * area;
|
|
371
|
+
const rects = squarify(items, { x: 0, y: 0, w, h: h * 2 });
|
|
372
|
+
for (const r of rects) {
|
|
373
|
+
const rx = Math.round(r.x), ry = Math.round(r.y / 2);
|
|
374
|
+
const rw = Math.max(1, Math.round(r.x + r.w) - rx), rh = Math.max(1, Math.round((r.y + r.h) / 2) - ry);
|
|
375
|
+
if (rx >= w || ry >= h)
|
|
376
|
+
continue;
|
|
377
|
+
const cl = r.item;
|
|
378
|
+
const sel = opts.selected != null && cl.members.includes(vm.holders[opts.selected]?.owner);
|
|
379
|
+
const color = sel ? t.borderFocus : kindColor(t, cl.kind);
|
|
380
|
+
const fillBg = mix(bg, color, sel ? 0.4 : 0.22);
|
|
381
|
+
c.fill(x + rx, y + ry, Math.min(rw, w - rx), Math.min(rh, h - ry), ' ', { bg: fillBg });
|
|
382
|
+
for (let k = 0; k < Math.min(rh, h - ry); k++)
|
|
383
|
+
c.put(x + rx, y + ry + k, '▌', { fg: color, bg: fillBg, soft: true });
|
|
384
|
+
if (rw >= 6) {
|
|
385
|
+
const head = cl.members.length > 1 ? `${KIND_LETTER[cl.kind] || '·'}·${cl.name}` : shortAddr(cl.members[0], 3);
|
|
386
|
+
c.text(x + rx + 1, y + ry, head, { fg: color, bg: fillBg, b: true }, Math.min(rw, w - rx) - 1);
|
|
387
|
+
if (rh >= 2)
|
|
388
|
+
c.text(x + rx + 1, y + ry + 1, `${cl.pct.toFixed(1)}%${cl.members.length > 1 ? ` ${cl.members.length}w` : ''}`, { fg: t.text, bg: fillBg }, Math.min(rw, w - rx) - 1);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/** The legend row: glyph + word per kind (colour-blind second encoding). Returns the x after the last entry. */
|
|
393
|
+
export function legendRow(c, x, y, w, bg) {
|
|
394
|
+
const t = c.theme;
|
|
395
|
+
let cx = x;
|
|
396
|
+
for (const [kind, word] of LEGEND) {
|
|
397
|
+
const s = `${kind === 'plain' ? '○' : '●'} ${word} `;
|
|
398
|
+
if (cx + s.length > x + w)
|
|
399
|
+
break;
|
|
400
|
+
cx = c.text(cx, y, s, { fg: kindColor(t, kind), bg });
|
|
401
|
+
}
|
|
402
|
+
return cx;
|
|
403
|
+
}
|
|
404
|
+
/** "sample top 20 accts of 142.5K holders · 55 % supply · launch 300 txs" (every people-metric prints its sample). */
|
|
405
|
+
export function sampleCaption(vm, fmtNum) {
|
|
406
|
+
const cov = vm.coverage;
|
|
407
|
+
if (!cov)
|
|
408
|
+
return 'sample unknown';
|
|
409
|
+
return `sample top ${cov.topHoldersFetched} accts${vm.holderCount ? ` of ${fmtNum(vm.holderCount)} holders` : ''} · ${cov.supplyCoveredPct}% supply · funders ${cov.fundingLookups}/${cov.topHoldersFetched} · launch ${cov.launchTxs} txs`;
|
|
410
|
+
}
|
|
411
|
+
/** The holder index under a cell (x, y) of the drawn map, or -1 (hover / click hit test). */
|
|
412
|
+
export function holderAt(pix, W, col, row) {
|
|
413
|
+
const top = pix[(2 * row) * W + col], bot = pix[(2 * row + 1) * W + col];
|
|
414
|
+
return top >= 0 ? top : bot >= 0 ? bot : -1;
|
|
415
|
+
}
|