Skip to content

niZmosis/abi-toolkit

 
 

Repository files navigation

abi-toolkit

npm version downloads

Generate TypeScript typings for all your Ethereum ABI contract methods and events with 1-liner integrations for web3 and ethers. Never have runtime errors again - bring them into compile-time errors in 2 minutes! 🎉

GIF Demo

A CLI tool that allows you to convert an ABI JSON file into fully loaded interface types.

Table of Contents

Features

🚀 Generate TypeScript Typings from ABI Files: Convert your Ethereum smart contract ABIs into fully typed TypeScript interfaces, including support for complex types like nested tuples and multi-dimensional arrays. Supporting both JSON and JSONFragment[] files.

🔎 Automatic Regeneration with Watch Mode: Enable watch mode to automatically regenerate typings whenever your ABI files change, ensuring your types are always up-to-date.

⚙️ Highly Configurable via CLI and Config Files: Customize the tool using command-line arguments or a configuration file, allowing for flexible integration into any project setup.

🔌 Supports Multiple Ethereum Libraries and Frameworks:

  • Libraries:
    • Web3.js (1.x and 2.x)
    • Ethers.js (v4, v5, and v6)
  • Frameworks:
    • Truffle
    • Hardhat

🧩 Comprehensive Type Support: Handles all Solidity types, including advanced types like nested tuples and multi-dimensional arrays, to ensure accurate type definitions.

📚 Automatic Documentation Generation: Generates detailed documentation for each contract method and event directly from the ABI, including parameter types, state mutability, and more.

🛠 Index File Generation: Optionally generate an index file that exports all generated typings for easier imports and better project organization.

Code Formatting with Prettier and ESLint: Automatically formats and lints generated code using Prettier and ESLint, with support for custom configuration paths and options.

📦 Zero Runtime Dependencies: Produces pure TypeScript interfaces without adding any runtime dependencies to your project.

🔄 ESM Export Alias Support: Supports ECMAScript Module (ESM) export aliases for all generated typing files, allowing for flexible import strategies.

🗂 Selective File Inclusion/Exclusion: Specify which ABI files to include or exclude, giving you granular control over the generation process.

🔐 Overwrite Protection: Optionally prevent overwriting existing files to safeguard manual changes.

🌐 Multi-Language Support: Designed to support multiple target languages, starting with TypeScript, with plans for future expansion.

Performance Optimized: Efficiently handles large projects with many contracts, ensuring fast generation times.

🛡 Event Typing and Filtering: Generates accurate typings for contract events, including event filters and listener methods.

🤝 Easy Integration with Build Tools: Seamlessly integrate into your build process or scripts, enhancing your development workflow.

💎 Open Source and Community-Driven: Join the community on GitHub to contribute, report issues, or request features.

Supported Libraries / Frameworks

  • Web3 1.x and 2.x
  • Ethers 4.x, 5.x, and 6.x
  • Truffle
  • Hardhat

Packages

Package Description
@abi-toolkit/core Core module for CLI and programmatic usage.
@abi-toolkit/types Type definitions for the entire toolkit.
@abi-toolkit/utils A collection of helper functions and utilities.
@abi-toolkit/converter-typescript Generates typings and classes for the Typescript language.

Installation

Install the core package:

npm i @abi-toolkit/core -D
# or
yarn add @abi-toolkit/core -D
# or
pnpm add @abi-toolkit/core -D
# or
bun add @abi-toolkit/core -d

Install the language package you want to use:

npm i @abi-toolkit/converter-typescript -D
# or
yarn add @abi-toolkit/converter-typescript -D
# or
pnpm add @abi-toolkit/converter-typescript -D
# or
bun add @abi-toolkit/converter-typescript -d

Note: Installing @multicall-toolkit/core will install utils and types.

Tsconfig compile time issues

If you get compile time errors due to it waiting web3 dependencies when using ethers please set skipLibCheck: true in the tsconfig.json compiler options and this should fix that issue.

Usage

There are two ways to use this package: via the CLI or via a configuration file.

NOTE: If using a configuration file, you can also use the CLI flags to override the configs options.

Configuration

  • --config: Path to config file.

Library Options

  • --library: Library to use. Choices are:

    • web3
    • ethers_v4
    • ethers_v5
    • ethers_v6

    NOTE: If not supplied it will fallback to web3

  • --libraryImportAlias: Override the library import from name (e.g., from "import { Contract } from 'ethers';" to "import { Contract } from 'ethersv5';").

Framework Options

  • --framework: Framework to use. Choices are:
    • hardhat
    • truffle
    • none

Generation Options

  • --inputDirOrPath: Directory of ABI files or a single ABI file path.
  • --makeOutputDir: Make the output directory if it does not exist.
  • --makeIndexFile: Generate an index (barrel) file exporting all the generated typings.
  • --preventOverwrite: Prevent overwriting existing files.
  • --verbatimModuleSyntax: Use verbatim module syntax (eg: 'import type { Contract } from "ethers"').
  • --language: Language to generate. Choices are:
    • typescript

ABI Options

  • --typingsOutputDir: Output directory. Defaults to ./ethereum-abi-types.
  • --typingsOutputFileName: The file name to use for the generated typings. Only used for single file input. Defaults to name of the ABI file.
  • --typingsOutputFileSuffix: The suffix to append to the file name of the generated typings. e.g., (my-abi.types.ts vs my-abi.ts). Defaults to "types".
  • --typingsPrefixTypes: Whether to prefix the name of the type with the typingsOutputFileName (e.g., "MyTokenContract" or "PrefixNameContract" vs "Contract").

Class Options

  • --generateClasses: Whether to generate classes for the generated typings.
  • --classOutputDir: The output directory for the class. If not set, it will use the typingsOutputDir.
  • --classOutputFileName: The file name to use for the generated class. Only used for single file input. Defaults to name of the ABI file.
  • --classOutputFileSuffix: The suffix to append to the file name of the generated classes. e.g., (my-abi.contract.ts vs my-abi.ts). Defaults to "contract".
  • --classMulticall: Whether to integrate multicall-toolkit into the class.

Watching Options

  • --watch: Watch the ABI files for changes and regenerate typings automatically.

File Inclusion/Exclusion

  • --includeFiles: List of file paths to include, ignoring all other files (e.g., --includeFiles=["./path/to/contract1.json"]).

  • --excludeFiles: List of file paths to exclude, including all other files (e.g., --excludeFiles=["./path/to/contract2.json"]).

    NOTE: Include will supersede exclude.

Formatting Options

  • --eslintConfigPath: ESLint config file path.
  • --prettierConfigPath: Prettier config file path.
  • --eslintOptions: ESLint options override (provide as a JSON string).
  • --prettierOptions: Prettier options override (provide as a JSON string).

We use prettier to format all files, to make sure it matches your coding style just make sure you have a .prettierrc defined in the root of your project and it will use that. If it can not find a prettier config it will use the default prettier config:

{
  parser: 'typescript',
  semi: false,
  trailingComma: "all",
  singleQuote: true,
  printWidth: 80,
  tabWidth: 2,
  useTabs: false,
  bracketSpacing: true
}

Generator CLI

Available Commands

  • scripts: Show script helpers.
  • generate: Generate ABI typings.
  • hardhat: Generate ABI typings for Hardhat projects.
  • truffle: Generate ABI typings for Truffle projects.

CLI Examples

Checking Version

abi-types-generator --version
abi-types-generator -v

Showing Help

abi-types-generator --help
abi-types-generator -h

Showing Script Helpers

abi-types-generator scripts

Custom Configuration File

abi-types-generator generate --inputDirOrPath=DIR_OR_FILE_PATH --config=./customConfigs/ethersv5.config.json

Specify Output Directory

abi-types-generator generate --inputDirOrPath=./abis --typingsOutputDir=./types

Use a Specific Library

  • web3: Use Web3.js library.
  • ethers_v4: Use Ethers.js version 4.
  • ethers_v5: Use Ethers.js version 5.
  • ethers_v6: Use Ethers.js version 6.
abi-types-generator generate --inputDirOrPath=./abis --library=ethers_v5

Watch for Changes

abi-types-generator generate --inputDirOrPath=./abis --watch

Generate Typings for Truffle or Hardhat

abi-types-generator hardhat
abi-types-generator truffle

Generate Typings with Custom Prefix

NOTE: This will only work for single file input.

abi-types-generator generate --inputDirOrPath=./myAbi.json --typingsOutputFileName=MyPrefixName

Prevent Overwriting

abi-types-generator generate --inputDirOrPath=./abis --preventOverwrite

Specify Language

NOTE: Only supports ts for now.

abi-types-generator generate --inputDirOrPath=./abis --language=ts

ESLint and Prettier Configurations

abi-types-generator generate --inputDirOrPath=./abis --eslintConfigPath=./.eslintrc.json --prettierConfigPath=./.prettierrc.json

File Inclusions and Exclusions

NOTE: Include will supersede exclude.

abi-types-generator generate --inputDirOrPath=./abis --includeFiles=["./abis/Contract1.json","./abis/Contract2.json"]
abi-types-generator generate --inputDirOrPath=./abis --excludeFiles=["./abis/Contract3.json"]

Generator Configuration File

You can use a configuration file to set default options. Create a file named eat.config.json in your project root:

Minimal:

{
  "$schema": "./node_modules/@abi-toolkit/core/schemas/abi-toolkit-1.0.0.json",
  "inputDirOrPath": "./abis",
  "typingsOutputDir": "./types",
  "library": "web3"
}

Full:

{
  "$schema": "./node_modules/@abi-toolkit/core/schemas/abi-toolkit-1.0.0.json",
  "inputDirOrPath": "./abis",
  "typingsOutputDir": "./types",
  "library": "ethers_v5",
  "libraryImportAlias": "",
  "framework": "none",
  "makeOutputDir": true,
  "makeIndexFile": true,
  "typingsOutputFileName": "",
  "typingsPrefixTypes": false,
  "watch": false,
  "includeFiles": [],
  "excludeFiles": [],
  "language": "ts",
  "preventOverwrite": false,
  "verbatimModuleSyntax": true,
  "eslintConfigPath": "",
  "prettierConfigPath": "",
  "eslintOptions": {},
  "prettierOptions": {},
}

Once you have a configuration file, you can use it with the CLI:

abi-types-generator generate

Or use the --config option to specify a path to a configuration file:

abi-types-generator generate --config ./custom-config.json

You can override any option in the configuration file with the CLI:

abi-types-generator generate --inputDirOrPath=./otherAbis --typingsOutputDir=./types --library=web3 --watch

Using with Hardhat/Truffle

CLI

First you create a script in your package.json that runs the abi-types-generator script after it compiles every time.

You may add options in the script or in the configuration file.

Shorthand:

{
  "scripts": {
    "compile": "npx hardhat compile && abi-types-generator hardhat"
  }
}

Normal:

{
  "scripts": {
    "compile": "npx hardhat compile && abi-types-generator generate --framework=hardhat"
  }
}

Or use the eat.config.json file, or the --config option to specify a path to a configuration file:

// package.json
{
  "scripts": {
    "compile": "npx hardhat compile && abi-types-generator generate"
  }
}
// or
{
  "scripts": {
    "compile": "npx hardhat compile && abi-types-generator generate --config=./custom.config.json"
  }
}
// eat.config.json or custom config file
{
  "framework": "hardhat",
  // other options
}

If your contracts are ready to compile, you can run:

npm run compile
# or
yarn compile
# or
pnpm compile
# or
bun run compile

Your types are now created within the root of your hardhat project, in a folder called ethereum-abi-types and you can use them throughout your tests/scripts or anything Typescript related.


Test example

import { expect } from 'chai';
import { ethers } from 'hardhat';
import {
  ContractContext as MyVeryFirstContract,
  GetFooResponse,
  GetFooRequest,
} from '../ethereum-abi-types/MyVeryFirstContract';

describe('Example test', function () {
  let contract: MyVeryFirstContract;
  beforeEach(async () => {
    const contractFactory = await ethers.getContractFactory(
      'MyVeryFirstContract'
    );

    // thats it you now have full typings on your contract
    contract =
      (await contractFactory.deploy()) as unknown as MyVeryFirstContract;
  });

  it('I love to write unit tests', async () => {
    const foo: GetFooRequest = { fooBoo: true };
    const result: GetFooResponse = await contract.getFoo(foo);

    expect(result).to.equal(
      { fooResponse: 'boo' }
  });
});

Using Web3

Web3 NPM Package

NOTE: If the ABI changes and I run the CLI command again or have a --watch on the file, when you try to compile it will flag any errors with your typings for you.

Uniswap full example (Web3)

Uniswap Contract Strongly Typed Example

Below is just a fake contract example just so you can understand how the typings improve your development.

The cli tool will generate all your typings for you and expose them in the generated file. Its super easy to start using strongly typed interfaces for all your ABI calls.

Lets say we run the cli command:

abi-types-generator --inputDirOrPath=./abi-examples/fake-contract-abi.json  --output=./generated-typings --typingsOutputFileName=fake-contract

This will generate an ts file of ./generated-typings/fake-contract.ts which has all your strongly typed methods and events.

All you meed to do is cast your new web3.eth.Contract code to an ContractContext which is exposed in where you defined the --output path to. In this example it is ./generated-typings/fake-contract.ts

Example (Web3)

import Web3 from 'web3';
import { AbiExamples } from '../../abi-examples';
import { ContractContext } from './generated-typings/fake-contract';

const web3 = new Web3(
  'https://mainnet.infura.io/v3/280bb5b627394709938a7cc0b71a4a58'
);

// Has to cast to unknown as we have made some typings changes to the
// contract interfaces which conflicts with `web3` typings.
// This all work great but the compiler gets confused.
// Casting to unknown first then the `ContractContext` solves this.
const contract = new web3.eth.Contract(
  AbiExamples.YOUR_ABI,
  AbiExamples.YOUR_CONTRACT_ADDRESS
) as unknown as ContractContext;

Easy as that 🔥🔥

Full example (Web3)

import Web3 from 'web3';
import { AbiExamples } from '../../abi-examples';
import {
  ContractContext,
  TupleInputOnlyRequest,
  TupleNoInputNamesResponse,
} from './generated-typings/fake-contract';

const example = async () => {
  const mockEthereumAddress = '0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b';

  const web3 = new Web3(
    'https://mainnet.infura.io/v3/280bb5b627394709938a7cc0b71a4a58'
  );

  // Has to cast to unknown as we have made some typings changes to the
  // contract interfaces which conflicts with `web3` typings.
  // This all work great but the compiler gets confused.
  // Casting to unknown first then the `ContractContext` solves this.
  const contract = new web3.eth.Contract(
    AbiExamples.YOUR_ABI,
    AbiExamples.YOUR_CONTRACT_ADDRESS
  ) as unknown as ContractContext;

  // you now have full typings on `contract.methods` which has generated docs
  const simpleCall = await contract.methods
    .easyExample(true, mockEthereumAddress, new Date().getTime())
    .call();

  console.log(simpleCall);

  // build up a proper typed request object with the interface importable
  // from the typings file generated
  const tupleExampleRequest: TupleInputOnlyRequest = {
    address: mockEthereumAddress,
    timestamps: [
      new Date().getTime(),
      new Date().getTime(),
      new Date().getTime(),
    ],
  };

  // encode abi method all exposed
  const data = contract.methods.tupleInputOnly(tupleExampleRequest).encodeABI();
  console.log(data);

  // any none constant methods will have the correct interface on them as well
  // aka you cant call `.call()` here and the compile will show you this.
  // will also expose the event emitters for your typings to still work with web3
  contract.methods
    .tupleInputOnly(tupleExampleRequest)
    .send({ from: mockEthereumAddress })
    .on('transactionHash', (hash) => {
      console.log(hash);
    });

  const result: TupleNoInputNamesResponse = await contract.methods
    .tupleNoInputNames(mockEthereumAddress, mockEthereumAddress)
    .call();

  console.log(result);

  // full typings on your events with even the filter indexes which will
  // not compile if supply it incorrectly and only expose the correct ones for you
  contract.events
    .Event1({ filter: { token: '0x00' } })
    .on('changed', (event) => {
      console.log(event);
    });

  // can any past events only allowing you to query events which actually exist
  const event = await contract.getPastEvents('Event1', {
    filter: { token: '0x00' },
  });

  console.log(event);
};

example();

Using Ethers

Ethers.js NPM Package

Uniswap full example (Ethers)

Uniswap Contract Strongly Typed Example

Below is just a fake contract example just so you can understand how the typings improve your development.

The cli tool will generate all your typings for you and expose them in the generated file. Its super easy to start using strongly typed interfaces for all your ABI calls.

Lets say we run the cli command:

Ethers v4

abi-types-generator ./abi-examples/fake-contract-abi.json  --output=./generated-typings --name=fake-contract --provider=ethers

Ethers v5

abi-types-generator ./abi-examples/fake-contract-abi.json  --output=./generated-typings --name=fake-contract --provider=ethers_v5

This will generate an ts file of ./generated-typings/fake-contract.ts which has all your strongly typed methods and events.

All you meed to do is cast your new ethers.Contract code to an ContractContext which is exposed in where you defined the --output path to. In this example it is ./generated-typings/fake-contract.ts

Example (Ethers)

import { ethers } from 'ethers';
import { AbiExamples } from '../../abi-examples';
import { ContractContext } from './generated-typings/fake-contract';

// Connect to the network
const customHttpProvider = new ethers.providers.JsonRpcProvider(
  'https://mainnet.infura.io/v3/280bb5b627394709938a7cc0b71a4a58'
);

// Has to cast to unknown as we have made some typings changes to the
// contract interfaces which conflicts with `ethers` typings.
// This all work great but the compiler gets confused.
// Casting to unknown first then the `ContractContext` solves this.
const contract = new ethers.Contract(
  AbiExamples.YOUR_CONTRACT_ADDRESS,
  AbiExamples.YOUR_ABI,
  customHttpProvider
) as unknown as ContractContext;

Easy as that 🔥🔥

Full example (Ethers)

import { ethers, utils } from 'ethers';
import { AbiExamples } from '../../abi-examples';
import {
  ContractContext,
  TupleInputOnlyRequest,
  TupleNoInputNamesResponse,
} from './generated-typings/fake-contract';

const example = async () => {
  const mockEthereumAddress = '0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b';

  // Connect to the network
  const customHttpProvider = new ethers.providers.JsonRpcProvider(
    'https://mainnet.infura.io/v3/280bb5b627394709938a7cc0b71a4a58'
  );

  // Has to cast to unknown as we have made some typings changes to the
  // contract interfaces which conflicts with `ethers` typings.
  // This all work great but the compiler gets confused.
  // Casting to unknown first then the `ContractContext` solves this.
  const contract = new ethers.Contract(
    AbiExamples.YOUR_CONTRACT_ADDRESS,
    AbiExamples.YOUR_ABI,
    customHttpProvider
  ) as unknown as ContractContext;

  // you now have full typings on `contract.x` which has generated docs
  const simpleCall = await contract.easyExample(
    true,
    mockEthereumAddress,
    new Date().getTime()
  );

  console.log(simpleCall);

  // you can use the same Ethers.js flows to send and sign transactions
  // `contract.connect` will return a `ContractContext` so will still have
  // all the typings exposed for you
  const privateKey =
    '0x0123456789012345678901234567890123456789012345678901234567890123';
  const wallet = new ethers.Wallet(privateKey, customHttpProvider);

  // Create a new instance of the Contract with a Signer, which allows
  // update methods
  const contractWithSigner = contract.connect(wallet);

  // build up a proper typed request object with the interface importable
  // from the typings file generated
  const tupleExampleRequest: TupleInputOnlyRequest = {
    address: mockEthereumAddress,
    timestamps: [
      new Date().getTime(),
      new Date().getTime(),
      new Date().getTime(),
    ],
  };

  // strongly typed optional overrides as well for both `calls` and `transactions`
  const tx = await contractWithSigner.tupleInputOnly(tupleExampleRequest, {
    // The maximum units of gas for the transaction to use
    gasLimit: 23000,

    // The price (in wei) per unit of gas
    gasPrice: utils.parseUnits('9.0', 'gwei'),

    // The nonce to use in the transaction
    nonce: 123,

    // The amount to send with the transaction (i.e. msg.value)
    value: utils.parseEther('1.0'),

    // The chain ID (or network ID) to use
    chainId: 1,
  });
  console.log(tx.hash);
  // "0xaf0068dcf728afa5accd02172867627da4e6f946dfb8174a7be31f01b11d5364"

  // The operation is NOT complete yet; we must wait until it is mined
  await tx.wait();

  const result: TupleNoInputNamesResponse = await contract.tupleNoInputNames(
    mockEthereumAddress,
    mockEthereumAddress
  );

  console.log(result);

  // full typings on your events
  contract.on(
    'Event1',
    (author: string, oldValue: string, newValue: string, event: any) => {
      // Called when anyone changes the value

      console.log(author);
      // "0x14791697260E4c9A71f18484C9f997B308e59325"

      console.log(oldValue);
      // "Hello World"

      console.log(newValue);
      // "I like turtles."

      console.log(event.blockNumber);
      // 4115004
    }
  );

  // filter that matches my signer as the author
  const filter = contract.filters.Event1(wallet.address);

  // full typings on filter interfaces as well
  contract.filters.Event1(
    filter,
    (author: string, oldValue: string, newValue: string, event: any) => {
      // Called ONLY when your account changes the value

      console.log(author);
      // "0x14791697260E4c9A71f18484C9f997B308e59325"

      console.log(oldValue);
      // "Hello World"

      console.log(newValue);
      // "I like turtles."

      console.log(event.blockNumber);
      // 4115004
    }
  );
};

example();

Tests

The whole repo is covered in tests output below.

Test Files  7 passed (7)
Tests  216 passed (216)
Start at  11:32:54
Duration  723ms (transform 644ms, setup 0ms, collect 2.66s, tests 72ms, environment 1ms, prepare 570ms)

Related Toolkits

Check out my other projects and forks for blockchain development!

Toolkit Description
provider-toolkit Web3 provider management and configuration tools
dex-toolkit A powerful and flexible toolkit designed for seamless integration with multiple decentralized exchanges (DEXs) across various blockchain networks
multicall-toolkit Batch contract calls and state aggregation utilities
transaction-toolkit Transaction building, simulation, and management tools
connector-toolkit Wallet connection and account management utilities

Issues

Please raise any issues in the GitHub repository.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Check out the TODO.md file for a list of future features and improvements.

License

This project is licensed under the ISC License - see the LICENSE file for details.

About

Generate typings based on your ABIs, with provider wrappers exposed.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 99.6%
  • JavaScript 0.4%