As you might have read, we decided to use Typescript as the main language for our projects at Neufund. It brings improved type safety and provides development confidence not otherwise available in Javascript.

Even though Typescript can ensure correctness of your code, it’s helpless when 3rd party libraries come into play. For external dependencies written in Javascript, you need to provide typings, i.e. type information about available functions and types. Some libraries (especially those written in TS) have typings build in. For others you can find typings on npm. But the situation becomes even more complicated when you work with dynamically generated types such as smart contract wrappers.

With TypeChain, Typescript loves Ethereum Smart Contracts

Dynamic typing hell

Usually to interact with a smart contract on the Ethereum blockchain you use Web3js: you pass an ABI and an address, you call methods, and create transactions regarding the given smart contract. Unfortunately, such dynamic interfaces — created during runtime — can’t be expressed in the Typescript type system.

Of course, on top of that it’s hard to work with such wrappers, there is no code completion, and API is not intuitive.

“What was the name of that function again? Is this a constant function or do I need to create a transaction?”

Personally, I lost a lot of time debugging cryptic error messages when I simply forgot to specify gas amount.

We can do better than this.

Enter TypeChain

TypeChain is here to solve all these problems. It uses provided ABI files to generate typed wrappers for smart contracts. It still uses Web3js under the hood, but on the surface it provides robust, type safe API with support for promises and much more.

They see me typin’, they hatin’

Let’s take a look at a practical example. Here is our smart contract:

contract CounterContract {

uint public counter; function DumbContract() public {

counter = 0;

} function counterWithOffset(uint offset) public constant returns (uint sum) {

return counter + offset;

} function countup(uint by) public {

counter += by;

}

}

Generated wrapper:

import { BigNumber } from "bignumber.js";

import {

TypeChainContract,

promisify,

ITxParams,

IPayableTxParams,

DeferredTransactionWrapper,

} from "./typechain-runtime"; export class DumbContract extends TypechainContract {

public readonly rawWeb3Contract: any; public constructor(web3: any, address: string | BigNumber) {

const abi = [

//ABI

];

super(web3, address, abi);

} static async createAndValidate(web3: any, address: string | BigNumber): Promise<DumbContract> {

const contract = new DumbContract(web3, address);

const code = await promisify(web3.eth.getCode, [address]);

if (code === "0x0") {

throw new Error(`Contract at ${address} doesn't exist!`);

}

return contract;

} public get counter(): Promise<BigNumber> {

return promisify(this.rawWeb3Contract.counter, []);

}

public counterWithOffset(offset: BigNumber | number): Promise<BigNumber> {

return promisify(this.rawWeb3Contract.counterWithOffset, [offset.toString()]);

} public countupTx(by: BigNumber | number): DeferredTransactionWrapper<ITxParams> {

return new DeferredTransactionWrapper<ITxParams>(this, "countup", [by.toString()]);

}

}

You can use it like this:

// create new instance of contract wrapper and assert that contract exists on blockchain (useful during development) const dumbContract = await DumbContract.createAndValidate(web3, contractAddress); // call getter

await dumbContract.counter; // call constant method

await dumbContract.counterWithOffset(2); // create transaction

await dumbContract.countupTx(2).send({

from: accounts[0],

gas: 10000,

}); // you can easily get tx data

await dumbContract.countupTx(2).getData();

Thanks to our generated wrappers, code completion just works and Typescript compiler, as well as tslint, can do their jobs to ensure type safety (btw. check out TypeStrict project for achieving maximum type safety).

Be aware: we strongly advise against committing generated files to your version control system! Make TypeChain part of your build process (e.g. by using post-install hook) and do not edit generated files directly. When you do this, if you ever change ABIs, the Typescript compiler will find any breaking changes for you. How cool is that? :)

Customizing generated wrappers

Often ABI files don’t convey all the necessary information about a contract interface and you need to customize generated files. The same rule applies here: do not edit files directly. Instead, extend the generated contract with your — more specific — implementation.

We are also evaluating different, more declarative ways of customizing generated wrappers, like API files which would add semantics to ABI files. You could for example specify that a given uint represents a timestamp and should be parsed as moment date.

We hope for community input about this as we move forward. This is where you come in: help us by suggesting improvements and providing feedback!

Summary

TypeChain saves programmers from writing lots of repetitive code and provides type safety at the same time. During the creation of our commitment app (which is, btw, finishing in less then a week) we used early versions of TypeChain. It has already proved itself to be useful and now we hope that other teams can benefit from our efforts.

For more information, head to our Github page — TypeChain.