abitype

repository·main·Indexed 19 days ago

https://github.com/wevm/abitype

A TypeScript library providing strict types for Ethereum ABIs and EIP-712 Typed Data. It enables type inference and autocomplete directly from ABI objects without requiring a code-generation build step. It includes utilities for converting ABI parameters to TypeScript primitive types, extracting functions, events, and errors from ABIs, and validating ABI structures.

Tokens
15.8K
Snippets
57
Records
70
Agent score
65%

What's inside abitype

  1. Overview of ABIType

    main

    ABIType provides strict TypeScript types for Ethereum ABIs and EIP-712 Typed Data. It allows you to add type inference and autocomplete to your code based on user-provided ABIs without needing a separate type-generation build step (like TypeChain).

    import type {
      AbiParametersToPrimitiveTypes,
      ExtractAbiFunction,
      ExtractAbiFunctionNames,
    } from 'abitype'
    import { erc20Abi } from 'abitype/abis'
    
    type FunctionNames = ExtractAbiFunctionNames<typeof erc20Abi, 'view'>
    //   ^? type FunctionNames = "symbol" | "name" | "allowance" | "balanceOf" | "decimals" | "totalSupply"
    
    type TransferInputTypes = AbiParametersToPrimitiveTypes<
      // ^? type TransferInputTypes = readonly [`0x${string}`, bigint]
      ExtractAbiFunction<typeof erc20Abi, 'transfer'>['inputs']
    >
  2. Use ABIType for ABI type inference and autocomplete

    main

    ABIType allows you to derive TypeScript types directly from your ABI objects. This enables blazing fast autocomplete and type checking for functions, variables, or custom types without needing third-party tools like TypeChain.

    Common use cases include:

    • Typechecking ABIs or EIP-712 Typed Data.
    • Adding type inference to libraries based on user-provided ABIs (similar to how Viem or Wagmi work).
    • Converting ABI parameter types (e.g., 'string') into TypeScript primitive types (e.g. string) using utilities like AbiParametersToPrimitiveTypes.
    import { AbiParametersToPrimitiveTypes, ExtractAbiFunction } from 'abitype'
    import { erc20Abi } from 'abitype/abis'
    
    type TransferInputTypes = AbiParametersToPrimitiveTypes<
      // ^?
    
      ExtractAbiFunction<typeof erc20Abi, 'transfer'>['inputs']
    >
  3. Best practices for typing contract interactions with abitype

    main

    When building typed wrappers for contract interactions (like a readContract function), consider the following implementation details for better developer experience:

    • Const Assertions: Always use as const on your ABI definitions. This ensures TypeScript treats the ABI as the most specific literal type possible, which is required for accurate inference.
    • Argument Handling: If a function has no arguments, args can be an empty array. For a cleaner API, you can conditionally add the args key to the configuration object only when it is non-empty.
    • Return Type Unwrapping: By default, AbiParametersToPrimitiveTypes applied to outputs returns an array. If your implementation knows a function only returns a single value, you can unwrap the array to return the primitive directly.
    • Function Visibility: To restrict readContract to only read-only functions, use ExtractAbiFunctionNames<abi, 'pure' | 'view'>. To support write functions, expand this to include 'nonpayable' | 'payable'.
  4. Enable strict ABI type validation

    main

    Setting strictAbiType: true in the Register interface forces validation of an AbiParameter's type against the corresponding AbiType.

    When to use: Only enable this if parsed types are returning as unknown and you need to debug why.

    Warning: This option will significantly slow down type checking.

    import 'abitype'
    
    declare module 'abitype' {
      export interface Register {
        strictAbiType: true
      }
    }
  5. When to use ABIType

    main

    ABIType is suitable for projects that need to:

    • Typecheck ABIs or EIP-712 Typed Data: Ensure your ABI structures conform to specifications.
    • Add type inference and autocomplete: Build libraries (like Wagmi or Viem) that provide autocomplete based on user-provided ABIs.
    • Convert ABI types to TypeScript types: Use utilities to transform ABI parameter types (e.g., 'string') into TypeScript types (e.g. string).
    • Avoid type generation: Use existing ABIs directly in TypeScript without a build process to generate types.
  6. Prepare ABIs for type safety

    main

    Because ABIs often contain deeply nested arrays and objects, TypeScript's default type widening (e.g., converting a specific string like "transfer" to the general type string) will break type safety.

    You must ensure your ABI is treated as a constant using one of two methods:

    1. TypeScript Const Assertions: Use as const or the <const> syntax. This is the preferred method for pure TypeScript projects.
    2. The narrow function: Use the narrow utility from abitype. This is useful when working with plain JavaScript where const assertions are not available.
    // Method 1: Const assertions
    const erc20Abi = [...] as const
    const erc20Abi = <const>[...]
    
    // Method 2: Using the narrow utility
    import { narrow } from 'abitype'
    const erc20Abi = narrow([...])
  7. Infer return types for contract functions using `AbiParametersToPrimitiveTypes`

    main

    To ensure a function like readContract returns the correct types based on the ABI, you can use the AbiParametersToPrimitiveTypes utility. By passing the outputs of an extracted AbiFunction to this utility, you can map the ABI output definitions to their corresponding TypeScript primitive types.

    In a generic function implementation, you typically:

    1. Extract the specific function type using ExtractAbiFunction.
    2. Use AbiParametersToPrimitiveTypes on the inputs to type the args property.
    3. Use AbiParametersToPrimitiveTypes on the outputs to type the function's return value.
    import {
      Abi,
      AbiFunction,
      AbiParametersToPrimitiveTypes,
      ExtractAbiFunction,
      ExtractAbiFunctionNames,
    } from 'abitype'
    
    declare function readContract<
      abi extends Abi,
      functionName extends ExtractAbiFunctionNames<abi, 'pure' | 'view'>,
      abiFunction extends AbiFunction = ExtractAbiFunction<abi, functionName>,
    >(config: {
      abi: abi
      functionName: functionName | ExtractAbiFunctionNames<abi, 'pure' | 'view'>
      args: AbiParametersToPrimitiveTypes<abiFunction['inputs'], 'inputs'>
    }): AbiParametersToPrimitiveTypes<abiFunction['outputs'], 'outputs'>
    
    // Usage example:
    const res = readContract({
      abi,
      functionName: 'balanceOf',
      args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'],
    })
  8. How to configure ABIType using declaration merging

    main

    ABIType's types are customizable via TypeScript declaration merging. You can extend the Register interface within the abitype module to override default types. This can be done directly in your source code or in a .d.ts file (e.g., abi.d.ts).

    If you are using ABIType through a third-party package like viem, you must target that package's specific abitype module path (e.g., viem/node_modules/abitype) to ensure your customizations are applied correctly.

    import 'abitype'
    
    declare module 'abitype' {
      export interface Register {
        bigIntType: bigint & { foo: 'bar' }
      }
    }
    
    import { ResolvedRegister } from 'abitype'
    type Result = ResolvedRegister['bigIntType']
    //   ^? bigint & { foo: 'bar' }
  9. Understand AbiStateMutability

    main

    The AbiStateMutability type defines the state mutability of a Solidity function. This is used in AbiFunction, AbiConstructor, AbiFallback, and AbiReceive definitions.

    Allowed values are:

    • pure: The function does not read or modify the state.
    • view: The function reads the state but does not modify it.
    • nonpayable: The function can modify the state but cannot receive Ether.
    • payable: The function can modify the state and receive Ether.
  10. Customize ABI type resolution via the Register interface

    main

    You can extend the default ABI type resolution behavior by implementing the Register interface. This allows you to override how specific ABI types (like addresses, integers, or bytes) are mapped to TypeScript types, and to configure constraints for arrays and strictness levels.

    To use this, define an interface that extends Register and pass it to the relevant abitype functions (where supported).

    // Example of defining a custom registration
    export interface MyCustomRegister extends Register {
      addressType: string;
      intType: number;
      strictAbiType: true;
    }