WhatsABI

repository·main·Indexed 21 days ago

https://github.com/shazow/whatsabi

A TypeScript library to guess Ethereum contract ABIs and detect proxies directly from bytecode. It is optimized for browsers and wallets, supporting unverified contracts and integrating with providers like Ethers.js, Viem, and Web3.js. Key features include the `autoload` method for automated ABI resolution, low-level bytecode analysis for selector extraction, and multi-source ABI loading via Sourcify, Etherscan, and Blockscout.

Tokens
10.1K
Snippets
32
Records
40
Agent score
77%

What's inside @shazow/whatsabi

  1. Quick start with whatsabi.autoload()

    main

    The whatsabi.autoload(address, options) method is the primary entry point for resolving a contract's ABI. It automatically loads bytecode from a provider, extracts selectors, and attempts to resolve signatures and ABIs using available loaders. It works with any provider library like Ethers.js, Viem, or Web3.js.

    To use it with Ethers.js:

    import { ethers } from "ethers";
    import { whatsabi } from "@shazow/whatsabi";
    
    const provider = ethers.getDefaultProvider();
    const address = "0x00000000006c3852cbEf3e08E8dF289169EdE581";
    
    const result = await whatsabi.autoload(address, { provider });
    console.log(result.abi);

    To use it with Viem:

    import { createPublicClient, http } from 'viem'
    import { mainnet } from 'viem/chains'
    import { whatsabi } from "@shazow/whatsabi";
     
    const client = createPublicClient({ chain: mainnet, transport: http() })
    const address = "0x...";
    const result = await whatsabi.autoload(address, { provider: client });
  2. Detect and resolve proxy contracts

    main

    WhatsABI can detect if a contract is a proxy and resolve it to its implementation address. You can do this in two ways:

    1. Auto-follow: Set followProxies: true in the autoload options. The returned address will be the resolved implementation address.
    2. Manual follow: If result.followProxies is true, call await result.followProxies() to get a new result object containing the implementation's ABI.
    // Option 1: Auto-follow
    const { abi, address } = await whatsabi.autoload(
        "0x4f8AD938eBA0CD19155a835f617317a6E788c868",
        { provider, followProxies: true }
    );
    console.log("Resolved to:", address);
    
    // Option 2: Manual follow
    let result = await whatsabi.autoload(address, { provider });
    if (result.followProxies) {
        result = await result.followProxies();
        console.log(result.abi);
    }
    const { abi, address } = await whatsabi.autoload(
        "0x4f8AD938eBA0CD19155a835f617317a6E788c868",
        {
            provider,
            followProxies: true,
        },
    );
  3. How whatsabi.autoload() options work

    main

    The autoload method accepts an options object to customize the resolution process:

    • provider: An Ethers.js, Viem, or Web3.js provider/client.
    • abiLoader: (Optional) A loader to fetch the ABI (defaults to whatsabi.loaders.defaultABILoader).
    • signatureLoader: (Optional) A loader to fetch function signatures (defaults to whatsabi.loaders.defaultSignatureLookup).
    • onProgress: (Optional) A callback function (phase: string) => void called during different phases of the process.
    • onError: (Optional) A callback function (phase: string, context: any) => void called when an error occurs.
    • followProxies: (Optional) Boolean. If true, WhatsABI will attempt to detect and resolve proxy contracts.
    • enableExperimentalMetadata: (Optional) Boolean.
    • addressResolver: (Optional) A function to resolve names to addresses.

    Pro-tip: Use whatsabi.loaders.defaultsWithEnv({...}) to quickly configure default loaders using environment variables like CHAIN_ID and ETHERSCAN_API_KEY.

    let result = await whatsabi.autoload(address, {
      provider: provider,
    
      // Use this helper to add default loaders with your own settings via env vars
      ... whatsabi.loaders.defaultsWithEnv({
        CHAIN_ID: 42161,
        ETHERSCAN_API_KEY: "MYSECRETAPIKEY",
      }),
    
      onProgress: (phase) => console.log("autoload progress", phase),
      onError: (phase, context) => console.log("autoload error", phase, context),
    
      followProxies: true,
    });
  4. The Provider interface and BlockTagOrNumber type

    main

    A Provider is a unified interface that combines several specialized provider interfaces. It is the primary way to interact with blockchain data in WhatsABI.

    Required Interfaces

    • StorageProvider: getStorageAt(address, slot, block?)
    • CallProvider: call(transaction, block?)
    • CodeProvider: getCode(address, block?)
    • ENSProvider: getAddress(name)

    BlockTagOrNumber

    You can specify which block to query using the BlockTagOrNumber type:

    • String tags: 'latest', 'earliest', 'pending', 'safe', 'finalized'
    • Numeric values: number or bigint
  5. How MultiABILoader works

    main

    The MultiABILoader implements the ABILoader interface by wrapping an array of loaders. It iterates through the provided loaders and returns the first successful result found.

    • Success: If a loader returns a non-empty ABI or a valid ContractResult, the MultiABILoader returns that result immediately.
    • Misses (404): If a loader returns a 404 error (indicating the contract is not indexed by that specific provider), the MultiABILoader treats it as a miss and continues to the next loader.
    • Failures: If a loader encounters an error that is not a 404, the MultiABILoader tracks the failure. If no loaders succeed and at least one encountered a non-404 error, it throws a MultiABILoaderError containing details about the failures.
    const loader = new whatsabi.loaders.MultiABILoader([
      new whatsabi.loaders.SourcifyABILoader({ chainId: 8453 }),
      new whatsabi.loaders.EtherscanV2ABILoader({
        apiKey: "...",
        chainId: 8453,
      }),
    ]);
  6. How to resolve proxies with WhatsABI

    main

    WhatsABI can automatically detect and resolve upgradeable contract proxies using whatsabi.autoload. If you already have the bytecode, you can optimize the process by using a WithCachedCode provider to avoid unnecessary RPC calls.

    Note that some proxies, like DiamondProxy, require a specific function selector to resolve to a particular facet. These must be handled manually by accessing the proxies array in the result.

    const address = "0x...";
    const bytecode = "0x..."; // Already loaded
    
    // Use a cached provider to save RPC calls
    const cachedCodeProvider = whatsabi.providers.WithCachedCode(provider, {
      [address]: bytecode,
    });
    
    const result = whatsabi.autoload(address, {
      provider: cachedCodeProvider,
      abiLoader: false, // Skip ABI loaders
      signatureLookup: false, // Skip looking up selector signatures
    });
    
    if (result.address !== address) console.log(`Resolved proxy: ${address} -> ${result.address}`);
    if (result.proxies.length > 0) console.log("Proxies detected:", result.proxies);
  7. Generate loader configurations with defaultsWithEnv

    main

    The defaultsWithEnv function returns a configuration object containing a MultiABILoader (Sourcify + EtherscanV2) and a MultiSignatureLookup (OpenChain + FourByte) based on the provided environment variables.

    This object is designed to be spread into whatsabi.autoload().

    Supported Environment Variables (LoaderEnv):

    • CHAIN_ID: A fallback chain ID used for both Sourcify and Etherscan if specific ones aren't provided.
    • SOURCIFY_CHAIN_ID: Specific chain ID for the Sourcify loader.
    • ETHERSCAN_API_KEY: Required for the Etherscan loader.
    • ETHERSCAN_CHAIN_ID: Specific chain ID for the Etherscan loader.

    Usage Examples:

    1. Basic usage with full environment:

    whatsabi.autoload(address, {
      provider,
      ...whatsabi.loaders.defaultsWithEnv(process.env)
    });

    2. Overriding specific chain IDs:

    whatsabi.autoload(address, {
      provider,
      ...whatsabi.loaders.defaultsWithEnv({
        SOURCIFY_CHAIN_ID: 42161,
        ETHERSCAN_CHAIN_ID: 8453,
        ETHERSCAN_API_KEY: "MYSECRETAPIKEY",
      }),
    });
    const { abiLoader, signatureLookup } = whatsabi.loaders.defaultsWithEnv(env);
  8. Known limitations and caveats of WhatsABI

    main

    While WhatsABI is powerful for unverified contracts, users should be aware of the following limitations:

    • Function Modifiers: Detecting Solidity-style modifiers (like view or payable) is currently unreliable.
    • Arguments: There are minimal attempts to guess function arguments, but this is also unreliable.
    • Call Graph Traversal: Currently only supports static jumps. Dynamic jumps are skipped, which contributes to the unreliability of argument/modifier detection.
    • Event Parsing: Event parsing is considered a "best effort" and may be inaccurate.
    • Verified Contracts: If a contract is already verified on a supported platform, autoload will simply fetch the registered ABI, which is highly reliable.
  9. Use whatsabi loaders for signatures and ABIs

    main

    WhatsABI provides several loaders to fetch data from public databases.

    Signature and Event Lookups

    Use whatsabi.loaders.* to look up function names or event signatures from selectors.

    const signatureLookup = new whatsabi.loaders.OpenChainSignatureLookup();
    
    // Lookup function name from selector
    console.log(await signatureLookup.loadFunctions("0x06fdde03")); // -> ["name()"];
    
    // Lookup event name from selector
    console.log(await signatureLookup.loadEvents("0x721c20121297512b72821b97f5326877ea8ecf4bb9948fea5bfcb6453074d37f"));

    Multi-source ABI Loading

    You can use whatsabi.loaders.MultiABILoader to attempt fetching an ABI from multiple sources in sequence until one succeeds.

    const loader = new whatsabi.loaders.MultiABILoader([
      new whatsabi.loaders.SourcifyABILoader(),
      new whatsabi.loaders.EtherscanV2ABILoader({ apiKey: "..." }),
      new whatsabi.loaders.BlockscoutABILoader({ apiKey: "..." }),
    ]);
    
    const { abi, name } = await loader.getContract(address);
    const loader = new whatsabi.loaders.MultiABILoader([
      new whatsabi.loaders.SourcifyABILoader(),
      new whatsabi.loaders.EtherscanV2ABILoader({
        apiKey: "...",
      }),
      new whatsabi.loaders.BlockscoutABILoader({
        apiKey: "...",
      }),
    ]);
    const { abi, name } = await loader.getContract(address);
  10. Use whatsabi low-level bytecode analysis methods

    main

    If you already have the contract bytecode, you can use these direct methods:

    • whatsabi.selectorsFromBytecode(code): Returns an array of callable function selectors (e.g., ["0x06fdde03", ...]).
    • whatsabi.abiFromBytecode(code): Returns an ABI-like list of interfaces (functions and events) guessed from the bytecode.
    const code = await provider.getCode(address);
    
    // Get selectors
    const selectors = whatsabi.selectorsFromBytecode(code);
    
    // Get ABI-like interfaces
    const abi = whatsabi.abiFromBytecode(code);
    const code = await provider.getCode(address);
    const selectors = whatsabi.selectorsFromBytecode(code);
    const abi = whatsabi.abiFromBytecode(code);
  11. Configure `autoload` with `AutoloadConfig`

    main

    To customize the behavior of autoload, pass an AutoloadConfig object. Key configuration options include:

    Required

    • provider: An AnyProvider used to interact with the blockchain.

    Loaders

    • abiLoader: An ABILoader or false to disable. Defaults to defaultABILoader.
    • signatureLookup: A SignatureLookup or false to disable. Defaults to defaultSignatureLookup.

    Settings

    • followProxies: If true, WhatsABI will attempt to automatically follow detected proxies to find the implementation contract. Note: Proxies relative to specific selectors (like DiamondProxies) are not automatically followed.
    • loadContractResult: If true, uses loader.getContract instead of loader.loadABI to fetch a larger superset of verified metadata. This is slower but more complete.
    • enableExperimentalMetadata: If true, includes metadata from static analysis (e.g., event topics) which may be less reliable.

    Hooks

    • onProgress: A callback invoked during phases like resolveName, getCode, abiLoader, signatureLookup, and followProxies.
    • onError: A callback invoked when errors occur. Returning a truthy value will abort the process.
    • addressResolver: A custom function to resolve non-address strings (like ENS names) to hex addresses.
    const result = await whatsabi.autoload(address, {
      provider,
      followProxies: true,
      loadContractResult: true, // Load full contract metadata (slower)
      enableExperimentalMetadata: true, // Include less reliable static analysis results (e.g. event topics),
      // and more! See AutoloadConfig for all the options.
    });
  12. Resolve a DiamondProxy facet using a selector

    main

    Because DiamondProxy (ERC2535) maps different contracts to different function selectors, you cannot resolve it to a single implementation address without providing a selector. Use the resolve method on a DiamondProxyResolver instance, passing the target selector.

    // Assuming 'result' contains a DiamondProxyResolver
    const resolver = result.proxies[0] as whatsabi.proxies.DiamondProxyResolver;
    
    // DiamondProxies require a selector to resolve to a specific facet
    const selector = "0x6e9960c3"; // e.g., function getAdmin() returns (address)
    const implementationAddress = await resolver.resolve(provider, address, selector);