Initial Commit
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user