# tx-decoder _(no submodules)_ ## Purpose Provides naive transaction-hash classification and 4-byte function-selector decoding for EVM calldata. `decodeTxHash` classifies a transaction hash into one of `"evm" | "utxo" | "solana" | "unknown"` and returns the set of chains the hash could plausibly belong to. `extractSelector`, `lookupSelector`, and `decodeCalldata` work together to identify a function call from raw calldata when the called function is in the curated `KNOWN_SELECTORS` table (eight well-known ERC-20 / ERC-721 functions). This module is a deliberate MVP: it does not decode argument bytes (ABI-aware decoding is scoped out per `enterprise/cryptocurrency.md` §3) and does not consult an online selector database (`4byte.directory` / `openchain.xyz`). Both gaps land in the `add-cryptocurrency-abi` follow-up change. ## Features - `decodeTxHash(hash)` — classifies EVM (0x + 64 hex), UTXO (64 hex), Solana (base58 87-88 char) hashes and returns `TxMetadata { hash, chains, valid, hashType, hashHex? }`. - `extractSelector(calldata)` — extracts the first 10 characters of the trimmed lowercased calldata when it looks like a function call; returns `undefined` otherwise. - `lookupSelector(selector)` — case-insensitive lookup against the `KNOWN_SELECTORS` table. - `decodeCalldata(calldata)` — composition of `extractSelector` then `lookupSelector`. - `KNOWN_SELECTORS` — curated list of 8 well-known ERC-20 / ERC-721 function signatures. - All inputs are trimmed; all matching is case-insensitive on hex. ## Usage: Basic ```ts import { decodeCalldata, decodeTxHash, extractSelector, } from "@suaveplan/cryptocurrency"; const tx = decodeTxHash(`0x${"a".repeat(64)}`); // { hash: "0xaa...", chains: ["ethereum", ...], valid: true, hashType: "evm", hashHex: "aa..." } const sel = extractSelector("0xa9059cbb000000000000000000000000..."); // "0xa9059cbb" const fn = decodeCalldata("0xa9059cbb000000000000000000000000..."); // { selector: "0xa9059cbb", name: "transfer", signature: "transfer(address,uint256)" } ``` ## Usage: Advanced ```ts import { decodeCalldata, type FunctionSignature, KNOWN_SELECTORS, lookupSelector, } from "@suaveplan/cryptocurrency"; // Extend the curated table with project-specific signatures via in-memory composition function makeSelectorResolver(extras: FunctionSignature[]) { const merged = new Map(); for (const s of [...KNOWN_SELECTORS, ...extras]) { merged.set(s.selector.toLowerCase(), s); } return (selectorOrCalldata: string): FunctionSignature \| undefined => { const baseline = decodeCalldata(selectorOrCalldata) ?? lookupSelector(selectorOrCalldata); if (baseline) return baseline; // Try the merged table as fallback for the project-specific extras const normalised = selectorOrCalldata.toLowerCase().slice(0, 10); return merged.get(normalised); }; } const resolve = makeSelectorResolver([ { selector: "0xdeadbeef", name: "myCustom", signature: "myCustom(bytes32,address)", }, ]); ``` This pattern keeps the package's curated `KNOWN_SELECTORS` immutable while letting projects layer their own signatures on top — no need for a mutable global registry inside the package. ## API Reference | Export | Signature | Notes | |---|---|---| | `KNOWN_SELECTORS` | `FunctionSignature[]` | 8 well-known selectors (transfer, approve, transferFrom, balanceOf, allowance, totalSupply, safeTransferFrom, setApprovalForAll) | | `decodeTxHash` | `(hash: string) => TxMetadata` | classifies EVM / UTXO / Solana hashes | | `lookupSelector` | `(selector: string) => FunctionSignature \| undefined` | case-insensitive on selector | | `extractSelector` | `(calldata: string) => string \| undefined` | first 10 chars of `0x` + ≥ 8 hex chars | | `decodeCalldata` | `(calldata: string) => FunctionSignature \| undefined` | composition of extract + lookup | ### `TxMetadata` shape ```ts interface TxMetadata { hash: string; chains: ChainId[]; valid: boolean; hashType: "evm" | "utxo" | "solana" | "unknown"; hashHex?: string; // populated for evm / utxo paths } ``` ## Implementation Notes - The hash matchers are anchored regexes (`^...$`) to prevent partial matches. EVM (`0x` + 64 hex) is checked first because it would also match the UTXO pattern if the `0x` prefix were optional. The Solana base58 pattern excludes ambiguous characters (`0`, `O`, `I`, `l`) so it cannot collide with the hex patterns. - `hashHex` is omitted for the Solana path (base58 is not hex). For the EVM path, the prefix is stripped; for the UTXO path, the whole input is lowercased. - `extractSelector` requires `0x` + at least 8 hex characters — calldata containing only a selector (`"0xa9059cbb"`, 10 chars) qualifies; anything shorter is rejected. The slice always takes 10 chars (`"0x"` + 8 hex), even if more are present. - `KNOWN_SELECTORS` is a mutable array (not `as const`) so consumers can spread it into custom collections. Sharing the array reference with callers that mutate it would corrupt the package's behaviour, but that is a misuse not worth defending against — `Object.freeze` would lose the spread-friendly mutability without preventing reassignment of indexed entries' fields anyway. - The curated selector list is deliberately small. A comprehensive registry (4byte.directory export, openchain.xyz dump) belongs in a downstream `@suaveplan/cryptocurrency-selectors` package; keeping the core curated avoids shipping a ~50MB JSON blob. - ABI-aware argument decoding (dynamic types, tuples, nested arrays) is **not** in this module. It lands in `add-cryptocurrency-abi` along with the pluggable online resolver interface — see `enterprise/cryptocurrency.md` §3. - The `hashType: "unknown"` branch returns `chains: []` and `valid: false` — callers should dispatch on `valid` first, then on `hashType` for the success cases.