Initial Commit
This commit is contained in:
159
openspec/archive/2026-05-17-add-cryptocurrency/proposal.md
Normal file
159
openspec/archive/2026-05-17-add-cryptocurrency/proposal.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Add @suaveplan/cryptocurrency — wallet validation, unit conversion, chain registry, transaction parsing
|
||||
|
||||
**Package**: `@suaveplan/cryptocurrency`
|
||||
**Status**: PROPOSED
|
||||
**Type**: migration (upstream `../suaveplan/packages/finance/cryptocurrency`)
|
||||
|
||||
- **Design**: ./design.md
|
||||
- **Spec delta**: ./specs/cryptocurrency/spec.md
|
||||
|
||||
## Why
|
||||
|
||||
`STATUS.md` line 174 lists `@suaveplan/cryptocurrency` as Pending. The upstream package is a small, dependency-light universal-tier utility library (8 implementation files, ~720 LOC of `src/`, zero `@suaveplan/*` runtime deps) covering wallet address validation across 15 chains, EIP-55 checksumming, unit conversion across ETH/BTC/SOL denominations, a static + adapter-backed chain registry, EIP-1559 gas helpers, a token registry, and a transaction calldata/selector decoder. No genesis package covers this surface, and downstream finance / wallet / payment packages will want to depend on it.
|
||||
|
||||
The migration preserves the entire public surface bit-for-bit. The only behavioural deltas are (a) replacing plain `throw new Error(...)` boundaries with `SuaveplanError` + a stable `CRYPTOCURRENCY.*` code registry (per CLAUDE.md rule §63), (b) adding the `defaultMessageResolver`-friendly error contract, and (c) gaining `LoggerLike` injection on `ChainRegistry` (no-op default) for parity with sibling packages. The enterprise audit (`../suaveplan/enterprise/cryptocurrency.md` §3-§4) lists ambitious future additions (ABI-aware calldata decoder, EIP-4844 blob-gas, testnets, on-chain token metadata adapter, ENS resolver) — these are explicitly out of scope for this change and belong in follow-up proposals layered on top of the migrated baseline.
|
||||
|
||||
## What changes
|
||||
|
||||
- ADDED: `validateAddress(address)` — multi-chain address validation with chain auto-detection, returns `AddressValidation` with `valid` / `normalized` (EIP-55 checksummed for EVM) / `chains` / `error`.
|
||||
- ADDED: `validateAddressForChain(address, chainId)` — chain-locked variant; narrows `chains` to `[chainId]` on success.
|
||||
- ADDED: `validateTxHash(hash)` — recognises EVM (0x + 64 hex), UTXO (64 hex), and Solana (base58 87-88 char) hash shapes.
|
||||
- ADDED: `CHAINS` — `Record<ChainId, ChainInfo>` with 15 entries (bitcoin, ethereum, solana, polygon, arbitrum, optimism, avalanche, bsc, base, tron, litecoin, dogecoin, cardano, polkadot, cosmos).
|
||||
- ADDED: `getChain(id)`, `getAllChainIds()`, `getEvmChains()`, `getChainByEvmId(evmChainId)`, `getExplorerAddressUrl(chainId, address)`, `getExplorerTxUrl(chainId, txHash)` — pure synchronous helpers over the built-in `CHAINS` constant.
|
||||
- ADDED: `MemoryChainRegistry` implementing the async `ChainRegistryAdapter` contract; preloads built-in chains or accepts custom seed list; supports `getChain`/`getAllChains`/`getChainByEvmId`/`registerChain`.
|
||||
- ADDED: `ChainRegistry` — async façade over an injected `ChainRegistryAdapter`, defaulting to `MemoryChainRegistry`; adds `getEvmChains()` / `getExplorerAddressUrl()` / `getExplorerTxUrl()` and raises `CryptocurrencyError({ code: CRYPTOCURRENCY.UNKNOWN_CHAIN })` on missing chains.
|
||||
- ADDED: `estimateGas(params)` — EIP-1559 gas estimator returning `GasEstimate` with `gasLimit` / `gasPrice` / `maxFeePerGas` / `maxPriorityFeePerGas` / `totalCost` (bigint throughout); honours `legacyGasPrice` override.
|
||||
- ADDED: `calculateMaxFeePerGas(baseFee, priorityFee)` — EIP-1559 formula `2 * base + priority`.
|
||||
- ADDED: `calculateEffectiveGasPrice(baseFee, maxFee, priorityFee)` — `min(maxFee, baseFee + priorityFee)`.
|
||||
- ADDED: `formatGwei(weiValue)` — bigint → human-readable gwei string with sign + trailing-zero trim.
|
||||
- ADDED: `estimateTxCostEth(gasLimit, gasPriceWei)` — bigint → ETH decimal string.
|
||||
- ADDED: `COMMON_TOKENS` — 8 well-known ERC-20s on Ethereum mainnet (USDT, USDC, DAI, WBTC, WETH, LINK, UNI, MATIC).
|
||||
- ADDED: `TokenRegistry` — `addToken`/`getToken`/`getTokensByChain`/`searchBySymbol`/`searchByName`/`getAllTokens`/`size`; address keys are case-insensitive.
|
||||
- ADDED: `formatTokenBalance(rawBalance, decimals)` — bigint balance formatter with sign + trailing-zero trim.
|
||||
- ADDED: `KNOWN_SELECTORS` — 8 well-known ERC-20 / ERC-721 function signatures (transfer, approve, transferFrom, balanceOf, allowance, totalSupply, safeTransferFrom, setApprovalForAll).
|
||||
- ADDED: `lookupSelector(selector)`, `extractSelector(calldata)`, `decodeCalldata(calldata)`, `decodeTxHash(hash)` — calldata / tx-hash parsing helpers returning `FunctionSignature | undefined` or `TxMetadata`.
|
||||
- ADDED: `convertUnits(value, fromUnit, toUnit, currency)` — generic ETH/BTC/SOL unit conversion (bigint arithmetic).
|
||||
- ADDED: `weiToEther` / `etherToWei` / `gweiToEther` / `satoshiToBitcoin` / `bitcoinToSatoshi` / `lamportToSol` — convenience wrappers around `convertUnits`.
|
||||
- ADDED: `getUnits(currency)` — returns `CryptoUnit[]` listing every denomination for ETH / BTC / SOL.
|
||||
- ADDED: Type exports — `ChainId`, `ChainInfo`, `ChainRegistryAdapter`, `CryptoUnit`, `AddressValidation`, `TxHashValidation`, `TokenInfo`, `GasEstimate`, `GasEstimateParams`, `TxMetadata`, `FunctionSignature`.
|
||||
- ADDED: `CRYPTOCURRENCY_CODES` registry via `defineCodes("CRYPTOCURRENCY", [...])` — 5 codes: `UNKNOWN_CHAIN`, `UNKNOWN_CURRENCY`, `UNKNOWN_UNIT`, `INVALID_ADDRESS`, `INVALID_HASH`.
|
||||
- ADDED: `CryptocurrencyError extends SuaveplanError<CryptocurrencyErrorCode>` with optional `chainId` / `currency` / `unit` discriminants.
|
||||
- ADDED: Optional `LoggerLike` injection on `ChainRegistry` constructor — no-op default; logs `chain.registered` / `chain.lookup.miss` events when wired.
|
||||
- ADDED: Subpath exports `./address/validator`, `./units/converter`, `./chains/registry`, `./chains/chain-registry`, `./chains/chain-data`, `./tokens/token-registry`, `./tokens/token-data`, `./gas/gas-estimator`, `./transaction/tx-decoder`, `./types/crypto.types`, `./error-codes/error-codes` — tree-shakeable.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- **ABI-aware `decodeCalldata` rewrite** (enterprise audit §3 / §4). The naive selector lookup is preserved bit-for-bit; full ABI decoding (dynamic types, tuples) belongs in a follow-up `add-cryptocurrency-abi` change because it materially expands the type surface and pulls in non-trivial codec code. Reason: orthogonal to the address/unit/chain core this migration ships.
|
||||
- **Online selector resolver adapter** (enterprise audit §3). Network-bound; lands with the ABI decoder follow-up so the resolver interface can be co-designed.
|
||||
- **EIP-4844 blob-gas helpers** (enterprise audit §3). Will be added in `add-cryptocurrency-blob-gas`; the gas estimator's `GasEstimate` shape is left forward-compatible.
|
||||
- **Testnet chain entries + zkRollups / Aptos / Sui / Sei / extended Cosmos zones** (enterprise audit §4). The 15-chain mainnet set is preserved; testnets land in `add-cryptocurrency-testnets`.
|
||||
- **On-chain `OnChainTokenRegistry` accepting an RPC client** (enterprise audit §4). Requires an RPC-client abstraction not yet defined in genesis; defer until that interface exists.
|
||||
- **ENS / SNS / UD resolver adapter interface** (enterprise audit §4). Net-new surface; ships in `add-cryptocurrency-names`.
|
||||
- **UTXO / PSBT helpers** (enterprise audit §4). Different problem domain; ships in `add-cryptocurrency-utxo`.
|
||||
- **Property-based tests via `fast-check`** for round-trip conversions. The unit converter is fully covered by the upstream characterisation suite; property tests are a future enhancement, not a parity gate.
|
||||
- **Benchmarks (`src/__benchmarks__/`)** for EIP-55 / bech32 hot paths. Useful but not blocking for a pure utility migration with no IO; defer to enterprise wave.
|
||||
|
||||
## Source mapping
|
||||
|
||||
Upstream source: `../suaveplan/packages/finance/cryptocurrency/src/` (8 implementation files + 7 test files, ~720 LOC). Every public symbol in upstream `src/index.ts` migrates as-is; layout normalises per the no-prefix and feature-folder rules (no folder/file renames are required — upstream already uses feature-folder layout without redundant prefixes).
|
||||
|
||||
Enterprise audit reference: `../suaveplan/enterprise/cryptocurrency.md`.
|
||||
|
||||
| Requirement | Source lines | Notes |
|
||||
|---|---|---|
|
||||
| Pure utility, zero runtime deps, tree-shakeable, `sideEffects: false` | L12 + L49 | verbatim — zero-dep design is the audit's headline constraint |
|
||||
| Address validation across 15 chains | L12 + L18-19 | verbatim public API: `validateAddress`, `validateAddressForChain` |
|
||||
| Auto-detect chain from address format | L12 | verbatim |
|
||||
| EIP-55 checksumming for EVM addresses | L12 | verbatim (simplified positional algorithm preserved; full keccak256 deferred to enterprise wave per L57-58) |
|
||||
| Tx-hash validation across EVM / UTXO / Solana | L12 + L19 | verbatim: `validateTxHash` |
|
||||
| Chain registry with 15 entries, EVM ids, explorer templates, native symbols | L12 + L19-20 | verbatim: `CHAINS`, `ChainInfo`, `ChainRegistry`, `MemoryChainRegistry`, getters |
|
||||
| Async `ChainRegistryAdapter` interface | L12 + L25 | verbatim |
|
||||
| EIP-1559 gas estimation helpers | L12 + L20-21 | verbatim public API: `estimateGas`, `calculateMaxFeePerGas`, `calculateEffectiveGasPrice`, `estimateTxCostEth`, `formatGwei` |
|
||||
| Unit conversion ETH/BTC/SOL + generic `convertUnits` | L12 + L23-24 | verbatim |
|
||||
| `getUnits(currency)` registry of denominations | L23 | verbatim |
|
||||
| Token registry with `COMMON_TOKENS` + `formatTokenBalance` | L12 + L21-22 | verbatim |
|
||||
| Transaction calldata / function selector decoder + `KNOWN_SELECTORS` | L12 + L22-23 | verbatim — naive matcher preserved; ABI decoding scoped out |
|
||||
| All public type exports | L24-25 | verbatim |
|
||||
| Subpath exports per public module | L49 | verbatim — preserves upstream's 7 subpaths, adds `./chains/chain-data`, `./tokens/token-data`, `./types/crypto.types`, `./error-codes/error-codes` to round out tree-shaking |
|
||||
| 100% test coverage | L63 | upstream is ~97.73%; genesis policy is hard 100% (rule §7) |
|
||||
| 100% docs coverage; sibling `.md` per public symbol | L64 + L97 | verbatim |
|
||||
| `@suaveplan/error`-aligned error hierarchy | L59 + L88 | verbatim — replaces plain `throw new Error(...)` boundaries with `CryptocurrencyError` + `CRYPTOCURRENCY.*` codes |
|
||||
| Universal-tier purity (no node/bun deps) | L63 + L65 (`workspace:*` + catalog) + L12 ("zero runtime deps") | verbatim — except `tslib` (per genesis convention) and `@suaveplan/error` + `@suaveplan/types` (genesis error-shape requirement; documented exception below) |
|
||||
|
||||
### Requirements with no source line
|
||||
|
||||
| Requirement | Justification |
|
||||
|---|---|
|
||||
| 100% coverage threshold | Genesis-wide policy (rule §7), not package-specific; upstream tolerates ~97.73% |
|
||||
| Optional `LoggerLike` on `ChainRegistry` | Genesis convention across migrated packages; no-op default keeps zero runtime cost when unwired |
|
||||
| `@suaveplan/error` + `@suaveplan/types` runtime deps | Required to align with the central error registry + injectable contracts. The audit's "zero runtime deps" preference (L12, L67-68) was satisfied upstream by hand-rolled plain `Error`s; genesis policy (CLAUDE.md rule §63) mandates the central error shape, so two small typed-only deps are accepted. Both packages are zero-runtime themselves (`@suaveplan/types`) or near-zero (`@suaveplan/error`). |
|
||||
| Subpath exports for `./chains/chain-data`, `./tokens/token-data`, `./types/crypto.types`, `./error-codes/error-codes` | Tree-shaking completeness; the upstream package omitted these but the data constants and types are valid consumption surfaces |
|
||||
|
||||
### Parity Checklist
|
||||
|
||||
| Capability | Upstream | Migrated | Notes |
|
||||
|---|---|---|---|
|
||||
| `validateAddress`, `validateAddressForChain`, `validateTxHash` | ✅ | ✅ | Behaviour bit-for-bit preserved |
|
||||
| EIP-55 checksum (positional simplified) | ✅ | ✅ | Preserved; full keccak256 deferred to enterprise wave |
|
||||
| 15-chain `CHAINS` constant | ✅ | ✅ | Identical chain entries |
|
||||
| `getChain` / `getAllChainIds` / `getEvmChains` / `getChainByEvmId` / `getExplorerAddressUrl` / `getExplorerTxUrl` | ✅ | ✅ | Identical |
|
||||
| `MemoryChainRegistry` + `ChainRegistry` async adapter façade | ✅ | ⬆️ Enhanced | `throw new Error("Unknown chain: …")` → `CryptocurrencyError({ code: CRYPTOCURRENCY.UNKNOWN_CHAIN, chainId })`; constructor gains optional `LoggerLike` |
|
||||
| `estimateGas` / `calculateMaxFeePerGas` / `calculateEffectiveGasPrice` / `formatGwei` / `estimateTxCostEth` | ✅ | ✅ | Identical bigint math |
|
||||
| `COMMON_TOKENS` + `TokenRegistry` + `formatTokenBalance` | ✅ | ✅ | Identical |
|
||||
| `KNOWN_SELECTORS` + `lookupSelector` / `extractSelector` / `decodeCalldata` / `decodeTxHash` | ✅ | ✅ | Identical (ABI-aware decode scoped out) |
|
||||
| `convertUnits` + ETH/BTC/SOL convenience wrappers + `getUnits` | ✅ | ⬆️ Enhanced | `throw new Error("Unknown …")` → `CryptocurrencyError` with `UNKNOWN_CURRENCY` / `UNKNOWN_UNIT` codes |
|
||||
| Subpath exports per public module | ✅ | ⬆️ Enhanced | Upstream exposes 7 subpaths; genesis exposes 11 (adds `chain-data`, `token-data`, `types`, `error-codes`) |
|
||||
| Plain `throw new Error(...)` at boundaries | ✅ | ⬆️ Replaced | All public errors now `CryptocurrencyError extends SuaveplanError` with stable `CRYPTOCURRENCY.*` codes |
|
||||
| `LoggerLike` injection on `ChainRegistry` | 🚫 upstream | ⬆️ Added | Genesis convention; no-op default |
|
||||
| `tslib` runtime dep | ✅ | ✅ | Preserved per genesis convention |
|
||||
| `@suaveplan/testing` dev dep | ✅ (upstream uses `@suaveplan/testing` root) | ⬆️ Replaced | Uses `@suaveplan/testing/runner` per rule §7 / §11; direct `vitest` / `bun:test` imports banned |
|
||||
| ABI-aware calldata decoder | 🚫 upstream | 🚫 deferred | Enterprise audit §3 wants this; scoped to `add-cryptocurrency-abi` follow-up so the codec API can be co-designed with the resolver interface |
|
||||
| Online selector resolver | 🚫 upstream | 🚫 deferred | Network-bound; bundle with ABI decoder follow-up |
|
||||
| EIP-4844 blob-gas | 🚫 upstream | 🚫 deferred | Net-new helpers; `add-cryptocurrency-blob-gas` follow-up |
|
||||
| Testnet chains / zkRollups / extended Cosmos / non-EVM L1s | 🚫 upstream | 🚫 deferred | `add-cryptocurrency-testnets` follow-up |
|
||||
| OnChainTokenRegistry (RPC-backed) | 🚫 upstream | 🚫 deferred | Requires RPC-client abstraction not yet in genesis |
|
||||
| ENS / SNS / UD resolver interface | 🚫 upstream | 🚫 deferred | `add-cryptocurrency-names` follow-up |
|
||||
| UTXO / PSBT helpers | 🚫 upstream | 🚫 deferred | Different problem domain; `add-cryptocurrency-utxo` follow-up |
|
||||
| Benchmarks under `src/__benchmarks__/` | 🚫 upstream | 🚫 deferred | Defer to enterprise wave; pure-CPU helpers without IO are noise-bound |
|
||||
|
||||
### Reference docs consulted
|
||||
|
||||
- ✅ `../suaveplan/packages/finance/cryptocurrency/src/` — all 8 implementation files read end-to-end
|
||||
- ✅ `../suaveplan/packages/finance/cryptocurrency/src/**/*.test.ts` — all 7 test files read for characterisation
|
||||
- ✅ `../suaveplan/enterprise/cryptocurrency.md` — full enterprise audit
|
||||
- ✅ `../suaveplan/packages/finance/cryptocurrency/package.json` — public exports / dep graph
|
||||
- ✅ `../suaveplan/packages/finance/cryptocurrency/README.md` — public API examples
|
||||
- 🚫 `../suaveplan/openspec/specs/finance/spec.md` — no formal upstream spec exists for this capability; the parity contract is the source itself + the enterprise audit
|
||||
- 🚫 `../suaveplan/openspec/changes/*/proposal.md` touching cryptocurrency — none found
|
||||
|
||||
## Verification approach
|
||||
|
||||
Every requirement in the spec delta is pinned to one or more concrete test files that will exist after this change ships:
|
||||
|
||||
- `src/address/validator.test.ts` — covers address validation, chain auto-detection, EIP-55 checksumming, tx-hash validation
|
||||
- `src/chains/registry.test.ts` — covers synchronous chain getters + explorer URL helpers
|
||||
- `src/chains/chain-registry.test.ts` — covers `MemoryChainRegistry` + `ChainRegistry` async façade + `UNKNOWN_CHAIN` error path
|
||||
- `src/chains/chain-data.test.ts` — covers `CHAINS` constant shape (counts, EVM ids, explorer URLs)
|
||||
- `src/gas/gas-estimator.test.ts` — covers `estimateGas` / `calculateMaxFeePerGas` / `calculateEffectiveGasPrice` / `formatGwei` / `estimateTxCostEth`
|
||||
- `src/tokens/token-registry.test.ts` — covers `TokenRegistry` + `COMMON_TOKENS` + `formatTokenBalance`
|
||||
- `src/tokens/token-data.test.ts` — covers `COMMON_TOKENS` shape invariants
|
||||
- `src/transaction/tx-decoder.test.ts` — covers `KNOWN_SELECTORS` + `lookupSelector` / `extractSelector` / `decodeCalldata` / `decodeTxHash`
|
||||
- `src/units/converter.test.ts` — covers `convertUnits` + ETH/BTC/SOL wrappers + `getUnits` + `UNKNOWN_CURRENCY` / `UNKNOWN_UNIT` errors
|
||||
- `src/error-codes/error-codes.test.ts` — pins every `CRYPTOCURRENCY.*` code to a real call site + `CryptocurrencyError` `cause`-preservation contract
|
||||
|
||||
## Migration impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
No dependents today — `@suaveplan/cryptocurrency` is being added net-new on genesis. Future consumers (`payment`, `wallet`, `currency-providers`, `react-payment`) will land in subsequent migrations against the public contract this proposal locks in.
|
||||
|
||||
Run `bunx turbo ls --filter=...@suaveplan/cryptocurrency` post-migration to confirm zero dependents at archive time.
|
||||
|
||||
### Risk
|
||||
|
||||
- **Low.** Pure utility library; no IO, no global state, no concurrency. Every helper is a pure function or a small class wrapping a `Map`. The only deviations from upstream are (a) error-type substitution (drop-in: same throw points, richer instance) and (b) optional `LoggerLike` injection (additive). Both are covered by new tests.
|
||||
- The simplified positional EIP-55 algorithm preserved from upstream is **not** RFC-correct (a real EIP-55 implementation requires keccak256). The audit (L57-58) flags this; the upstream behaviour is preserved so consumers depending on its checksum output keep working. A future change can swap to keccak256 once `@suaveplan/crypto-hash` is wired in.
|
||||
|
||||
### Migration notes
|
||||
|
||||
None. Net-new package on genesis with zero consumers; the public contract this proposal locks in is the baseline.
|
||||
Reference in New Issue
Block a user