first commit
This commit is contained in:
108
docs/money/finance/cryptocurrency/src/transaction/tx-decoder.md
Normal file
108
docs/money/finance/cryptocurrency/src/transaction/tx-decoder.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# tx-decoder
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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<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
|
||||
|
||||
```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.
|
||||
Reference in New Issue
Block a user