Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
# token-data
## Purpose
Provides the `COMMON_TOKENS` constant: a curated list of eight well-known ERC-20 tokens on Ethereum mainnet — USDT, USDC, DAI, WBTC, WETH, LINK, UNI, MATIC. The list is used as the default seed for `TokenRegistry` when constructed with `preload = true` (the default). Consumers that want a richer registry can construct `new TokenRegistry(false)` and call `addToken` themselves, or build on top of `COMMON_TOKENS` by spreading the array.
This module exists as a static export so consumers can dead-code-eliminate it when they don't need a token registry; the entries here are byte-identical to upstream so any consumer relying on the addresses or decimals keeps working.
## Features
- Eight well-known mainnet ERC-20s: USDT, USDC, DAI, WBTC, WETH, LINK, UNI, MATIC.
- Every entry carries `address`, `symbol`, `decimals`, `chainId` (always `"ethereum"`), and `name`.
- Stablecoins use 6 decimals (USDT, USDC), WBTC uses 8, the rest use 18.
- Pure data export — no runtime initialisation, no side effects.
- Mutable array — consumers can spread it to build their own seeded registries.
## Usage: Basic
```ts
import { COMMON_TOKENS } from "@suaveplan/cryptocurrency";
const usdt = COMMON_TOKENS.find((t) => t.symbol === "USDT");
// { address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", symbol: "USDT", decimals: 6, ... }
```
## Usage: Advanced
```ts
import {
COMMON_TOKENS,
type TokenInfo,
TokenRegistry,
} from "@suaveplan/cryptocurrency";
const myTokens: TokenInfo[] = [
...COMMON_TOKENS,
{
address: "0x111111111117dC0aa78b770fA6A738034120C302",
symbol: "1INCH",
decimals: 18,
chainId: "ethereum",
name: "1inch",
},
];
const registry = new TokenRegistry(false);
for (const token of myTokens) registry.addToken(token);
const oneInch = registry.getToken("ethereum", "0x111111111117dC0aa78b770fA6A738034120C302");
```
## API Reference
| Export | Type | Notes |
|---|---|---|
| `COMMON_TOKENS` | `TokenInfo[]` | 8 entries; mainnet ERC-20s only |
## Implementation Notes
- The array is `const`-bound but **not** `as const` — entries are mutable so callers can spread-merge their own overrides without TypeScript widening complaints.
- All addresses are EIP-55-checksummed (the canonical upstream representation) — `TokenRegistry.getToken` normalises lookups to lowercase, so consumers can pass any casing.
- Adding a new token to `COMMON_TOKENS` requires no other code changes — the token is automatically picked up by `TokenRegistry`'s default preload.
- The list is intentionally short. A more comprehensive registry (Uniswap token list, CoinGecko export) belongs in a downstream `@suaveplan/cryptocurrency-tokens` package; keeping the core curated avoids shipping a 200KB JSON blob in the universal package.
- All entries are on `ethereum` chainId; multi-chain entries (USDC on Polygon, USDT on BSC, etc.) are deliberately out of scope for the core list — they belong to chain-specific catalogues.

View File

@@ -0,0 +1,99 @@
# token-registry
## Purpose
Provides the `TokenRegistry` class for adding, retrieving, and searching ERC-20 / token information across chains, plus the `formatTokenBalance` helper for converting raw bigint balances into human-readable decimal strings. The registry is a thin `Map`-backed in-memory store keyed by `${chainId}:${address.toLowerCase()}`, so lookups are case-insensitive on the address — callers don't have to pre-normalise.
`TokenRegistry` is intentionally store-agnostic in its API but in-memory in its implementation. An RPC-backed `OnChainTokenRegistry` that dynamically fetches `decimals()` / `symbol()` from chain via an injected RPC client is called out in the enterprise audit (`enterprise/cryptocurrency.md` §4) but lands in a follow-up change.
## Features
- `addToken(token)` — upsert by chain + address.
- `getToken(chainId, address)` — case-insensitive lookup.
- `getTokensByChain(chainId)` — filtered list of all tokens on a chain.
- `searchBySymbol(query)` — case-insensitive substring match on `symbol`.
- `searchByName(query)` — case-insensitive substring match on `name`.
- `getAllTokens()` — snapshot array of every registered token.
- `size` accessor for fast count.
- Constructor accepts `preload = true` (default) to seed with `COMMON_TOKENS`; `false` starts empty.
- `formatTokenBalance(rawBalance, decimals)` — bigint → decimal string with sign + trailing-zero trim.
## Usage: Basic
```ts
import { formatTokenBalance, TokenRegistry } from "@suaveplan/cryptocurrency";
const registry = new TokenRegistry();
const usdt = registry.getToken(
"ethereum",
"0xdAC17F958D2ee523a2206206994597C13D831ec7"
);
const balance = formatTokenBalance(1500000n, usdt?.decimals ?? 6);
// "1.5"
```
## Usage: Advanced
```ts
import {
formatTokenBalance,
type TokenInfo,
TokenRegistry,
} from "@suaveplan/cryptocurrency";
class PortfolioView {
private readonly registry = new TokenRegistry();
public addCustomTokens(tokens: TokenInfo[]) {
for (const t of tokens) this.registry.addToken(t);
}
public formatHolding(
chainId: TokenInfo["chainId"],
address: string,
rawBalance: bigint
): string {
const token = this.registry.getToken(chainId, address);
if (!token) return `${rawBalance} (unknown token)`;
const balance = formatTokenBalance(rawBalance, token.decimals);
return `${balance} ${token.symbol}`;
}
public stablecoinHoldings(chainId: TokenInfo["chainId"]) {
return this.registry
.getTokensByChain(chainId)
.filter((t) => ["USDT", "USDC", "DAI"].includes(t.symbol));
}
}
```
## API Reference
### `TokenRegistry`
| Method | Signature | Notes |
|---|---|---|
| constructor | `(preload?: boolean = true)` | `false` starts empty |
| `addToken` | `(token: TokenInfo) => void` | upsert by `${chainId}:${address.toLowerCase()}` |
| `getToken` | `(chainId: ChainId, address: string) => TokenInfo | undefined` | case-insensitive address |
| `getTokensByChain` | `(chainId: ChainId) => TokenInfo[]` | filtered snapshot |
| `searchBySymbol` | `(query: string) => TokenInfo[]` | case-insensitive substring |
| `searchByName` | `(query: string) => TokenInfo[]` | case-insensitive substring |
| `getAllTokens` | `() => TokenInfo[]` | snapshot of every entry |
| `size` | accessor `number` | fast count |
### `formatTokenBalance`
| Signature | Returns |
|---|---|
| `(rawBalance: bigint, decimals: number) => string` | decimal string, signed, trimmed |
## Implementation Notes
- The internal `Map` key includes both `chainId` and `address.toLowerCase()` so multi-chain tokens with the same address (USDC on ethereum + USDC on polygon — wait, these have different addresses, but the pattern matters for sidechains) don't collide. The lowercase normalisation means callers never need to pre-checksum.
- `addToken` is an upsert — adding a token with an `(chainId, address)` pair that already exists replaces the entry. This is the contract the test suite pins.
- `searchBySymbol` matches against the uppercased symbol; `searchByName` matches against the lowercased name. Both are substring matches via `String.prototype.includes`, not regex, so the queries are literal — callers cannot pass regex metacharacters and expect them to be honoured.
- `formatTokenBalance` short-circuits to `rawBalance.toString()` when `decimals === 0` (no decimal point needed). For non-zero decimals it pads the fractional part with leading zeros, trims trailing zeros, and re-applies the sign. The `|| "0"` fallback handles the case where the fractional part is entirely zero after trimming.
- `formatTokenBalance` mirrors `formatGwei` and `estimateTxCostEth` in `gas-estimator.ts` — same bigint-only arithmetic, same trim algorithm. The duplication is intentional; refactoring to a shared helper would obscure the per-call-site decimal constant.
- Token-data updates (adding `LINK`, removing `MATIC`) are isolated to `token-data.ts`; this module never references the constants directly.
- An on-chain RPC-backed registry is scoped out per the proposal — it lands in `add-cryptocurrency-on-chain-tokens` once an `RpcClient` abstraction lands in `@suaveplan/types`.