Files
skills/docs/money/finance/cryptocurrency/src/transaction/tx-decoder.md
2026-05-29 08:20:56 +01:00

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 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

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 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.