5.9 KiB
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 returnsTxMetadata { hash, chains, valid, hashType, hashHex? }.extractSelector(calldata)— extracts the first 10 characters of the trimmed lowercased calldata when it looks like a function call; returnsundefinedotherwise.lookupSelector(selector)— case-insensitive lookup against theKNOWN_SELECTORStable.decodeCalldata(calldata)— composition ofextractSelectorthenlookupSelector.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
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
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<string, FunctionSignature>();
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
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 the0xprefix were optional. The Solana base58 pattern excludes ambiguous characters (0,O,I,l) so it cannot collide with the hex patterns. hashHexis 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.extractSelectorrequires0x+ 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_SELECTORSis a mutable array (notas 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.freezewould 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-selectorspackage; 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-abialong with the pluggable online resolver interface — seeenterprise/cryptocurrency.md§3. - The
hashType: "unknown"branch returnschains: []andvalid: false— callers should dispatch onvalidfirst, then onhashTypefor the success cases.