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:
@@ -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.
|
||||
@@ -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`.
|
||||
Reference in New Issue
Block a user