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! 🎉
A CLI tool that allows you to convert an ABI JSON file into fully loaded interface types.
- Features
- Supported Libraries / Frameworks
- Packages
- Installation
- Tsconfig Compile Time Issues
- Usage
- Generator CLI
- Generator Configuration File
- Using with Hardhat/Truffle
- Test Example
- Using Web3
- Using Ethers
- Tests
- Issues
- Contributing
- License
🚀 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.
- Web3 1.x and 2.x
- Ethers 4.x, 5.x, and 6.x
- Truffle
- Hardhat
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. |
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
.
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.
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.
--config
: Path to config file.
-
--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
: Framework to use. Choices are:hardhat
truffle
none
--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
--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 thetypingsOutputFileName
(e.g.,"MyTokenContract"
or"PrefixNameContract"
vs"Contract"
).
--generateClasses
: Whether to generate classes for the generated typings.--classOutputDir
: The output directory for the class. If not set, it will use thetypingsOutputDir
.--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.
--watch
: Watch the ABI files for changes and regenerate typings automatically.
-
--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.
--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
}
scripts
: Show script helpers.generate
: Generate ABI typings.hardhat
: Generate ABI typings for Hardhat projects.truffle
: Generate ABI typings for Truffle projects.
abi-types-generator --version
abi-types-generator -v
abi-types-generator --help
abi-types-generator -h
abi-types-generator scripts
abi-types-generator generate --inputDirOrPath=DIR_OR_FILE_PATH --config=./customConfigs/ethersv5.config.json
abi-types-generator generate --inputDirOrPath=./abis --typingsOutputDir=./types
- 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
abi-types-generator generate --inputDirOrPath=./abis --watch
abi-types-generator hardhat
abi-types-generator truffle
NOTE: This will only work for single file input.
abi-types-generator generate --inputDirOrPath=./myAbi.json --typingsOutputFileName=MyPrefixName
abi-types-generator generate --inputDirOrPath=./abis --preventOverwrite
NOTE: Only supports ts
for now.
abi-types-generator generate --inputDirOrPath=./abis --language=ts
abi-types-generator generate --inputDirOrPath=./abis --eslintConfigPath=./.eslintrc.json --prettierConfigPath=./.prettierrc.json
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"]
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
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.
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' }
});
});
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 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
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 🔥🔥
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();
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
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 🔥🔥
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();
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)
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 |
Please raise any issues in the GitHub repository.
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.
This project is licensed under the ISC License - see the LICENSE file for details.