ABIType

website·Indexed 33 days ago

https://abitype.dev

Utilities and type definitions for ABI properties and values, covering the Contract ABI Specification and EIP-712 Typed Data for TypeScript projects.

Tokens
4.4K
Snippets
28
Records
29
Agent score
67%

What's inside ABIType

  1. Overview of ABIType

    ABIType provides strict TypeScript type definitions and utilities for Ethereum ABI properties and values. It covers the Contract ABI Specification and EIP-712 Typed Data, allowing developers to add type checking, inference, and autocomplete to their projects without needing external type generation tools like TypeChain.
  2. Install ABIType

    Install the ABIType package using your preferred package manager.
    # Using pnpm
    pnpm add abitype
    
    # Using bun
    bun add abitype
    
    # Using npm
    npm i abitype
    
    # Using yarn
    yarn add abitype
  3. Customize ABIType using declaration merging

    ABIType allows customization of its types via TypeScript declaration merging. To override default types or configuration options, extend the Register interface within the abitype module. This can be done directly in your code or in a .d.ts file. If using ABIType through a third-party package like viem, target the package's internal abitype module path.
    import 'abitype'
    
    declare module 'abitype' {
      export interface Register {
        bigIntType: bigint & { foo: 'bar' }
      }
    }
    
    // Example for third-party packages (e.g., viem)
    declare module 'viem/node_modules/abitype' {
      export interface Register {
        bigIntType: MyCustomBigIntType
      }
    }
  4. Define ABIs to prevent type widening in ABIType

    Because ABIs contain deeply nested structures, TypeScript may widen types (e.g., converting a specific string literal to a general string). To ensure ABIType can extract the most specific types, you must either use TypeScript const assertions or the narrow utility function provided by the library.
    // Option 1: Using const assertions
    const erc20Abi = [...] as const
    const erc20Abi = <const>[...]
    
    // Option 2: Using the narrow function (works in JavaScript too)
    import { narrow } from 'abitype'
    const erc20Abi = narrow([...])
  5. Create a type-safe readContract function using ABIType

    You can use abitype to create a function that infers valid function names and argument types from a contract ABI. This is particularly useful for ensuring that only 'pure' or 'view' functions are called and that the arguments provided match the ABI definition, including support for function overloads.
    import {
      Abi,
      ExtractAbiFunctionNames,
      ExtractAbiFunction,
      AbiParametersToPrimitiveTypes
    } from 'abitype'
    
    declare function readContract<
      abi extends Abi,
      functionName extends ExtractAbiFunctionNames<abi, 'pure' | 'view'>,
    >(
      config: {
        abi: abi
        functionName: functionName | ExtractAbiFunctionNames<abi, 'pure' | 'view'>
        args: AbiParametersToPrimitiveTypes<
          ExtractAbiFunction<abi, functionName>['inputs'],
          'inputs'
        >
      }
    ): unknown
  6. Implement a type-safe readContract function using ABIType

    You can create a type-safe wrapper for reading smart contract methods by combining ExtractAbiFunctionNames, ExtractAbiFunction, and AbiParametersToPrimitiveTypes. This setup allows TypeScript to infer the valid function names, the required argument types (including support for overloaded functions), and the resulting return types based on the provided ABI.

    Key utility functions used:

    • ExtractAbiFunctionNames: Filters the ABI for function names matching specific state mutability (e.g., 'pure' or 'view').
    • ExtractAbiFunction: Retrieves the full function definition from the ABI based on the function name.
    • AbiParametersToPrimitiveTypes: Converts ABI input/output parameters into TypeScript primitive types.
    import {
      Abi,
      AbiFunction,
      AbiParametersToPrimitiveTypes,
      ExtractAbiFunction,
      ExtractAbiFunctionNames,
    } from 'abitype'
    
    // Example ABI with overloaded 'balanceOf' functions
    export const abi = [
      {
        name: 'balanceOf',
        type: 'function',
        stateMutability: 'view',
        inputs: [{ name: 'owner', type: 'address' }],
        outputs: [{ name: 'balance', type: 'uint256' }],
      },
      {
        name: 'balanceOf',
        type: 'function',
        stateMutability: 'view',
        inputs: [
          { name: 'owner', type: 'address' },
          { name: 'collectionId', type: 'uint256' },
        ],
        outputs: [{ name: 'balance', type: 'uint256' }],
      },
      {
        name: 'tokenURI',
        type: 'function',
        stateMutability: 'pure',
        inputs: [{ name: 'id', type: 'uint256' }],
        outputs: [{ name: 'uri', type: 'string' }],
      },
    ] as const
    
    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
    const res = readContract({
      abi,
      functionName: 'balanceOf',
      args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'],
    })
    
  7. Convert EIP-712 Typed Data to TypeScript primitives

    Use TypedDataToPrimitiveTypes to convert an EIP-712 TypedData schema into a corresponding TypeScript object mapping where keys are the type names and values are the inferred primitive types.
    import { TypedDataToPrimitiveTypes } from 'abitype'
    
    const types = {
      Person: [
        { name: 'name', type: 'string' },
        { name: 'wallet', type: 'address' },
      ],
      Mail: [
        { name: 'from', type: 'Person' },
        { name: 'to', type: 'Person' },
        { name: 'contents', type: 'string' },
      ],
    } as const
    
    type Result = TypedDataToPrimitiveTypes<typeof types>
  8. Validate if a type is EIP-712 Typed Data

    The IsTypedData utility type checks if a given type conforms to the EIP-712 TypedData schema, returning true if valid and false if not.
    import { IsTypedData } from 'abitype'
    
    const types = {
      Person: [
        { name: 'name', type: 'string' },
        { name: 'wallet', type: 'address' },
      ],
    } as const
    
    type Result = IsTypedData<typeof types> // true
  9. Use pre-defined ABIs for testing with abitype

    The abitype/abis entrypoint provides a set of common contract ABIs (such as ERC20, WETH, and ENS) that can be used to quickly test code or prototype with known contract interfaces without manually defining the ABI arrays.
    import {
      customSolidityErrorsAbi,
      ensAbi,
      ensRegistryWithFallbackAbi,
      erc20Abi,
      nestedTupleArrayAbi,
      nounsAuctionHouseAbi,
      seaportAbi,
      wagmiMintExampleAbi,
      wethAbi,
      writingEditionsFactoryAbi,
      eip165Abi,
    } from 'abitype/abis'
  10. Use TypedData types for EIP-712

    These types implement the EIP-712 Typed Data Specification for structured data hashing and signing.
    import {
      TypedData,         // EIP-712 Typed Data Specification
      TypedDataDomain,  // EIP-712 Domain separator
      TypedDataParameter, // Entry in TypedData type items
      TypedDataType     // Subset of AbiType excluding 'tuple' and 'function'
    } from 'abitype'
  11. Enable experimental named tuples and strict ABI validation

    ABIType provides two advanced flags in the Register interface:

    • experimental_namedTuples: When true, enables named tuple generation in AbiParametersToPrimitiveTypes for common ABI parameter names. Default: false.
    • strictAbiType: When true, validates AbiParameter's type against AbiType. This is primarily used for debugging why parsed types are returning as unknown. Default: false. Warning: This significantly slows down type checking.
    import 'abitype'
    
    declare module 'abitype' {
      export interface Register {
        experimental_namedTuples: true
        strictAbiType: true
      }
    }