Beyond Solidity, part 2: Generating Typescript typings from ABIs

Kerman Kohli
4 min readAug 5, 2019

In the previous tutorial, we created our Monorepo with our contracts with a readme template file to let fellow developers know what we’re up to.

In this piece we’re going to crack onto the bits required to generate a JS API that external developers can use to interact with your smart contracts without understanding the architecture.

await myAPI.someMethod("pass info");

Typescript

In programming, the best tool for the job is the one you should use. While I personally think Typescript is great, it definitely comes with additional complexity and should always be weighed for its benefits.

In our case, we want to ensure that we’re not constantly dragging-and-dropping JSON ABI files into our projects and ensuring the types are correct all the time.

The worst bit is Solidity errors are highly un-descriptive. Invalid Solidity arguments passed or revert is the most you’re going to get. Catching errors before your program even compiles is highly useful especially when your errors only show up when you make a blockchain transaction.

Furthermore, integer overflow is a real problem with our API. Most tokens use 18 decimal places. By the time you reach a number such as 100,000 tokens you’re already at 10²³ which is the maximum integer in Javascript. Solidity on the other hand uses 2²⁵⁶ for all its integers.

Types

Here’s when we start using our monorepo to create a share reference for our Typescript types. Create a new folder inside the packages folder called types. Inside types add a tsconfig.json file with the following settings:

Then create a package.json file with the following code:

Make sure to replace @your-project with your actual project’s name.

Next up, create a src folder and then create your index.ts file with the following code:

After you’ve done that run the following command:
npm install && npm run build && lerna bootstrap

Now you can reference these types inside your project simply by adding @your-project/types and ensuring the local symlink is generated by running lerna bootstrap .

Your repo should now look like:

monorepo/
├── package.json
├── packages
│ └── contracts
│ └── package.json
│ └── types
│ └── package.json
│ └── tsconfig.json
│ └── src/index.ts

Base Contract

Essentially the base contract file is class which is then extended via all the contracts in your code. It provides a public web3 instance, an ABI to reference and the currently instantiated contract address. You can customise it to provide any extra functionality you need.

Start by creating a new folder in your packages directory called base-contract . Then create your tsconfig.json with the following code:

Then create your package.json file:

Remember to replace @your-project with your actual project name

Now, inside your src folder create the index.ts file with the following:

As you can see in line 6, we’re importing our types we created before hand! Okay, time to wrap this up. Run the following command again:
npm install && npm run build && lerna bootstrap

We can now reference our base-contract file and types from within our repo.

Your repo should now look like:

monorepo/
├── package.json
├── packages
│ └── contracts
│ └── package.json
│ └── types
│ └── package.json
│ └── tsconfig.json
│ └── src/index.ts
│ └── base-contract
│ └── package.json
│ └── tsconfig.json
│ └── src/index.ts

0x ABI Gen

For those of you that aren’t aware, an ABI file is a JSON file which contains all the functions and event references of the smart contract it references. It’s basically a header file for your contracts. What 0x’s ABI generator tool does is convert the JSON file into a Typescript file which you can use to interact by passing the contract address and an instance of web3/ethers.

Inside your contracts package folder, create a new folder called contract_templates and create a new file called contract.mustache.

You’ll notice that we reference our base-contract and types repo inside this file.

Inside your contract_templates folder create a partials folder. Then create the following files inside it:

By this point your contract_templatesfolder should look like this:

contract_templates/
├── contract.mustache
├── partials
│ └── call.mustache
│ └── params.mustache
│ └── return_type.mustache
│ └── tx.mustache
│ └── typed_params.mustache

Getting close!

Okay so now that we have our types , contract_templates , and base-contract we can do the fun stuff.

Inside the package.json file of your contract repo, add the following to your devDependencies :

"@0xproject/abi-gen": "^1.0.7",    
"@your-project/types": "^0.2.0",
"rimraf": "^2.6.2",

Then inside your dependencies add the following:

"bignumber.js": "^4.1.0",    
"ethereumjs-abi": "^0.6.5",
"mustache": "^3.0.0",
"types-ethereumjs-util": "^0.0.5",
"typescript": "^2.9.2",

After that, add the following to your scripts :

"generate-artifacts-typings": 
"node_modules/@0xproject/abi-gen/bin/abi-gen.js --abis './build/contracts/**/*.json' --out '../artifacts/src/build/wrappers' --template './contract_templates/contract.mustache' --partials './contract_templates/partials/*.mustache'",
"build":
"truffle compile && npm run generate-artifacts-typings",

Run lerna bootstrap and all your local and external dependencies should install.

Now do a npm build and a new artifacts repo should appear in your packages folder with Typescript bindings!

To recap:

We created two new packages, one called types which contains Typescript types that are going to be shared throughout our project. Next, we created our base-contract package which would be referenced by our contract-templates but also our final generated typescript contract typings. When we run npm run build we’re essentially building our typings by referencing our JSON ABI files and the contract_templates inside our contracts repo.

This now gives us the base we need to reference our Typescript typings from our Javascript library. We still have some additional setup to do but we’ll leave it here for this tutorial.

Hope you learned something useful!

--

--