151 lines
4.0 KiB
TypeScript
151 lines
4.0 KiB
TypeScript
import type {
|
|
ChainId,
|
|
FunctionSignature,
|
|
TxMetadata,
|
|
} from "../types/crypto.types.js";
|
|
|
|
/** Well-known ERC-20 / ERC-721 function selectors. */
|
|
export const KNOWN_SELECTORS: FunctionSignature[] = [
|
|
{
|
|
selector: "0xa9059cbb",
|
|
name: "transfer",
|
|
signature: "transfer(address,uint256)",
|
|
},
|
|
{
|
|
selector: "0x095ea7b3",
|
|
name: "approve",
|
|
signature: "approve(address,uint256)",
|
|
},
|
|
{
|
|
selector: "0x23b872dd",
|
|
name: "transferFrom",
|
|
signature: "transferFrom(address,address,uint256)",
|
|
},
|
|
{
|
|
selector: "0x70a08231",
|
|
name: "balanceOf",
|
|
signature: "balanceOf(address)",
|
|
},
|
|
{
|
|
selector: "0xdd62ed3e",
|
|
name: "allowance",
|
|
signature: "allowance(address,address)",
|
|
},
|
|
{
|
|
selector: "0x18160ddd",
|
|
name: "totalSupply",
|
|
signature: "totalSupply()",
|
|
},
|
|
{
|
|
selector: "0x42842e0e",
|
|
name: "safeTransferFrom",
|
|
signature: "safeTransferFrom(address,address,uint256)",
|
|
},
|
|
{
|
|
selector: "0xa22cb465",
|
|
name: "setApprovalForAll",
|
|
signature: "setApprovalForAll(address,bool)",
|
|
},
|
|
];
|
|
|
|
const EVM_TX_HASH = /^0x[0-9a-fA-F]{64}$/;
|
|
const UTXO_TX_HASH = /^[0-9a-fA-F]{64}$/;
|
|
const SOLANA_TX_SIG = /^[1-9A-HJ-NP-Za-km-z]{87,88}$/;
|
|
const CALLDATA_PATTERN = /^0x[0-9a-fA-F]{8,}$/;
|
|
|
|
const EVM_CHAINS: ChainId[] = [
|
|
"ethereum",
|
|
"polygon",
|
|
"arbitrum",
|
|
"optimism",
|
|
"avalanche",
|
|
"bsc",
|
|
"base",
|
|
];
|
|
|
|
/**
|
|
* Decode a transaction hash and extract metadata. Determines the hash type
|
|
* (`evm` / `utxo` / `solana` / `unknown`) and which chains it could belong to.
|
|
*/
|
|
export function decodeTxHash(hash: string): TxMetadata {
|
|
if (!hash || typeof hash !== "string") {
|
|
return { hash: "", chains: [], valid: false, hashType: "unknown" };
|
|
}
|
|
|
|
const trimmed = hash.trim();
|
|
|
|
if (EVM_TX_HASH.test(trimmed)) {
|
|
return {
|
|
hash: trimmed,
|
|
chains: [...EVM_CHAINS],
|
|
valid: true,
|
|
hashType: "evm",
|
|
hashHex: trimmed.slice(2).toLowerCase(),
|
|
};
|
|
}
|
|
|
|
if (UTXO_TX_HASH.test(trimmed)) {
|
|
return {
|
|
hash: trimmed,
|
|
chains: ["bitcoin", "litecoin", "dogecoin"],
|
|
valid: true,
|
|
hashType: "utxo",
|
|
hashHex: trimmed.toLowerCase(),
|
|
};
|
|
}
|
|
|
|
if (SOLANA_TX_SIG.test(trimmed)) {
|
|
return {
|
|
hash: trimmed,
|
|
chains: ["solana"],
|
|
valid: true,
|
|
hashType: "solana",
|
|
};
|
|
}
|
|
|
|
return { hash: trimmed, chains: [], valid: false, hashType: "unknown" };
|
|
}
|
|
|
|
/**
|
|
* Look up a function selector in the known-signatures table. The lookup is
|
|
* case-insensitive on the input selector.
|
|
*
|
|
* @example
|
|
* lookupSelector('0xa9059cbb')
|
|
* // { selector: '0xa9059cbb', name: 'transfer', signature: 'transfer(address,uint256)' }
|
|
*/
|
|
export function lookupSelector(
|
|
selector: string
|
|
): FunctionSignature | undefined {
|
|
const normalized = selector.toLowerCase();
|
|
return KNOWN_SELECTORS.find((s) => s.selector === normalized);
|
|
}
|
|
|
|
/**
|
|
* Extract the 4-byte function selector from calldata. Returns `undefined`
|
|
* for empty / non-string / non-hex / too-short input.
|
|
*
|
|
* @example
|
|
* extractSelector('0xa9059cbb000000000000000000000000...')
|
|
* // '0xa9059cbb'
|
|
*/
|
|
export function extractSelector(calldata: string): string | undefined {
|
|
if (!calldata || typeof calldata !== "string") return undefined;
|
|
const trimmed = calldata.trim();
|
|
if (!CALLDATA_PATTERN.test(trimmed)) return undefined;
|
|
return trimmed.slice(0, 10).toLowerCase();
|
|
}
|
|
|
|
/**
|
|
* Decode calldata and return the matching function signature if known.
|
|
* This is a naive matcher — it does not decode the argument bytes, only
|
|
* the 4-byte selector. ABI-aware decoding lands in a follow-up change.
|
|
*/
|
|
export function decodeCalldata(
|
|
calldata: string
|
|
): FunctionSignature | undefined {
|
|
const selector = extractSelector(calldata);
|
|
if (!selector) return undefined;
|
|
return lookupSelector(selector);
|
|
}
|