Skip to content

Commit

Permalink
Merge pull request #88 from krzkaczor/kk/target-web3-1.0.0
Browse files Browse the repository at this point in the history
Web3.js 1.0.0 support
  • Loading branch information
krzkaczor authored Sep 12, 2018
2 parents 3c8297f + ad1d3a4 commit 652a9c3
Show file tree
Hide file tree
Showing 18 changed files with 2,665 additions and 27 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ coverage
test/integration/targets/legacy/wrappers
test/integration/targets/truffle/@types
test/integration/targets/truffle/build
test/integration/targets/web3-1.0.0/types/web3-contracts
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

* static typing - you will never call not existing method again
* IDE support - works with any IDE supporting Typescript
* works with multiple libraries - use `truffle` or `Web3.js 0.20.x`, `Web3.js 1.0` support coming soon
* works with multiple libraries - use `truffle`,`Web3.js 1.0`, `Web3.js 0.20.x`
* frictionless - works with simple, JSON ABI files as well as with Truffle style ABIs

## Installation
Expand All @@ -42,12 +42,12 @@ yarn add --dev typechain
### CLI

```
typechain --target=(truffle|legacy) [glob]
typechain --target=(truffle|web3-1.0.0|legacy) [glob]
```

* `glob` - pattern that will be used to find ABIs, remember about adding quotes: `typechain
"**/*.json"`
* `--target` - `truffle` or `legacy`
* `--target` - `truffle`, `web3-1.0.0` or `legacy`
* `--outDir` - put all generated files to a specific dir

Typechain always will rewrite existing files. You should not commit them. Read more in FAQ section.
Expand Down Expand Up @@ -97,7 +97,7 @@ use Typescript).

TypeChain is code generator - provide ABI file and you will get Typescript class with flexible
interface for interacting with blockchain. Depending on the target parameter it can generate typings for
truffle or web3.js 0.20.x.
truffle, web3 1.0.0 or web3 0.20.x (legacy target).

### Step by step guide

Expand All @@ -120,7 +120,11 @@ Truffle target is great when you use truffle contracts already. Check out [truff

Now you can simply use your contracts as you did before and get full type safety, yay!

### Legacy
### Web3-1.0.0

Generates typings for contracts compatible with latest Web3.js version. It requires official typings from `@types/web3` installed. For now it needs explicit cast as shown [here](https://github.com/krzkaczor/TypeChain/pull/88/files#diff-540a9b8840419be93ddb8d4b53325637R8), this will be fixed after improving official typings.

### Legacy (Web3 0.2.x)

This was default and only target for typechain 0.2.x. It requires `Web3.js 0.20.x` to be installed in your project and it generates promise based wrappers. It's nice upgrade comparing to raw callbacks but in the near future Typechain will support `Web3js 1.0` target.

Expand Down
69 changes: 68 additions & 1 deletion __snapshots__/DumbContract.spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,42 @@ export class DumbContract extends TC.TypeChainContract {
stateMutability: "payable",
type: "function",
},
{
constant: true,
inputs: [{ name: "offset", type: "int256" }],
name: "returnSigned",
outputs: [{ name: "", type: "int256" }],
payable: false,
stateMutability: "pure",
type: "function",
},
{
constant: true,
inputs: [{ name: "boolParam", type: "bool" }],
name: "callWithBoolean",
outputs: [{ name: "", type: "bool" }],
payable: false,
stateMutability: "pure",
type: "function",
},
{
constant: true,
inputs: [{ name: "arrayParam", type: "uint256[]" }],
name: "callWithArray2",
outputs: [{ name: "", type: "uint256[]" }],
payable: false,
stateMutability: "pure",
type: "function",
},
{
constant: true,
inputs: [{ name: "a", type: "address" }],
name: "testAddress",
outputs: [{ name: "", type: "address" }],
payable: false,
stateMutability: "pure",
type: "function",
},
{
constant: true,
inputs: [],
Expand All @@ -59,11 +95,20 @@ export class DumbContract extends TC.TypeChainContract {
stateMutability: "view",
type: "function",
},
{
constant: true,
inputs: [{ name: "a", type: "string" }],
name: "testString",
outputs: [{ name: "", type: "string" }],
payable: false,
stateMutability: "pure",
type: "function",
},
{
constant: false,
inputs: [{ name: "byteParam", type: "bytes32" }],
name: "callWithBytes",
outputs: [{ name: "", type: "bool" }],
outputs: [{ name: "", type: "bytes32" }],
payable: false,
stateMutability: "nonpayable",
type: "function",
Expand Down Expand Up @@ -177,6 +222,28 @@ export class DumbContract extends TC.TypeChainContract {
return TC.promisify(this.rawWeb3Contract.byteArray, []);
}
public returnSigned(offset: BigNumber | number): Promise<BigNumber> {
return TC.promisify(this.rawWeb3Contract.returnSigned, [offset.toString()]);
}
public callWithBoolean(boolParam: boolean): Promise<boolean> {
return TC.promisify(this.rawWeb3Contract.callWithBoolean, [boolParam.toString()]);
}
public callWithArray2(arrayParam: BigNumber[]): Promise<BigNumber[]> {
return TC.promisify(this.rawWeb3Contract.callWithArray2, [
arrayParam.map(val => val.toString()),
]);
}
public testAddress(a: BigNumber | string): Promise<string> {
return TC.promisify(this.rawWeb3Contract.testAddress, [a.toString()]);
}
public testString(a: string): Promise<string> {
return TC.promisify(this.rawWeb3Contract.testString, [a.toString()]);
}
public counterArray(arg0: BigNumber | number): Promise<BigNumber> {
return TC.promisify(this.rawWeb3Contract.counterArray, [arg0.toString()]);
}
Expand Down
7 changes: 5 additions & 2 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { TsGeneratorPlugin, TFileDesc, TContext, TOutput } from "ts-generator";
import { TypechainLegacy } from "./targets/legacy";
import { Truffle } from "./targets/truffle";
import { Web3 } from "./targets/web3";

export type TTypechainTarget = "truffle" | "legacy";
export type TTypechainTarget = "truffle" | "web3-1.0.0" | "legacy";

export interface ITypechainCfg {
target: TTypechainTarget;
Expand All @@ -23,11 +24,13 @@ export class Typechain extends TsGeneratorPlugin {
}

private findRealImpl(ctx: TContext<ITypechainCfg>) {
switch (this.ctx.rawConfig.target) {
switch (ctx.rawConfig.target) {
case "legacy":
return new TypechainLegacy(ctx);
case "truffle":
return new Truffle(ctx);
case "web3-1.0.0":
return new Web3(ctx);
default:
throw new Error(`Unsupported target ${this.ctx.rawConfig.target}!`);
}
Expand Down
160 changes: 160 additions & 0 deletions lib/targets/web3/generation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import {
Contract,
AbiParameter,
ConstantFunctionDeclaration,
FunctionDeclaration,
ConstantDeclaration,
EventDeclaration,
} from "../../parser/abiParser";
import {
EvmType,
IntegerType,
UnsignedIntegerType,
AddressType,
VoidType,
BytesType,
BooleanType,
ArrayType,
StringType,
} from "../../parser/typeParser";

export function codegen(contract: Contract) {
const template = `
import Contract, { CustomOptions, contractOptions } from "web3/eth/contract";
import { TransactionObject, BlockType } from "web3/eth/types";
import { Callback, EventLog } from "web3/types";
import { EventEmitter } from "events";
import { Provider } from "web3/providers";
export class ${contract.name} {
constructor(
jsonInterface: any[],
address?: string,
options?: CustomOptions
);
options: contractOptions;
methods: {
${contract.constantFunctions.map(generateFunction).join("\n")}
${contract.functions.map(generateFunction).join("\n")}
${contract.constants.map(generateConstants).join("\n")}
};
deploy(options: {
data: string;
arguments: any[];
}): TransactionObject<Contract>;
events: {
${contract.events.map(generateEvents).join("\n")}
allEvents: (
options?: {
filter?: object;
fromBlock?: BlockType;
topics?: string[];
},
cb?: Callback<EventLog>
) => EventEmitter;
};
getPastEvents(
event: string,
options?: {
filter?: object;
fromBlock?: BlockType;
toBlock?: BlockType;
topics?: string[];
},
cb?: Callback<EventLog[]>
): Promise<EventLog[]>;
setProvider(provider: Provider): void;
}
`;

return template;
}

function generateFunction(fn: ConstantFunctionDeclaration | FunctionDeclaration): string {
return `
${fn.name}(${generateInputTypes(fn.inputs)}): TransactionObject<${generateOutputTypes(
fn.outputs,
)}>;
`;
}

function generateConstants(fn: ConstantDeclaration): string {
return `${fn.name}(): TransactionObject<${generateOutputTypes([fn.output])}>;`;
}

function generateInputTypes(input: Array<AbiParameter>): string {
if (input.length === 0) {
return "";
}
return (
input
.map((input, index) => `${input.name || `arg${index}`}: ${generateInputType(input.type)}`)
.join(", ") + ", "
);
}

function generateOutputTypes(outputs: Array<EvmType>): string {
if (outputs.length === 1) {
return generateOutputType(outputs[0]);
} else {
return `{ ${outputs.map((t, i) => `${i}: ${generateOutputType(t)}`).join(", ")}}`;
}
}

function generateEvents(event: EventDeclaration) {
return `
${event.name}(
options?: {
filter?: object;
fromBlock?: BlockType;
topics?: string[];
},
cb?: Callback<EventLog>): EventEmitter;
`;
}

function generateInputType(evmType: EvmType): string {
switch (evmType.constructor) {
case IntegerType:
return "number | string";
case UnsignedIntegerType:
return "number | string";
case AddressType:
return "string";
case BytesType:
return "string | number[]";
case ArrayType:
return `(${generateInputType((evmType as ArrayType).itemType)})[]`;
case BooleanType:
return "boolean";
case StringType:
return "string";

default:
throw new Error(`Unrecognized type ${evmType}`);
}
}

function generateOutputType(evmType: EvmType): string {
switch (evmType.constructor) {
case IntegerType:
return "string";
case UnsignedIntegerType:
return "string";
case AddressType:
return "string";
case VoidType:
return "void";
case BytesType:
return "string";
case ArrayType:
return `(${generateOutputType((evmType as ArrayType).itemType)})[]`;
case BooleanType:
return "boolean";
case StringType:
return "string";

default:
throw new Error(`Unrecognized type ${evmType}`);
}
}
43 changes: 43 additions & 0 deletions lib/targets/web3/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Contract } from "../../parser/abiParser";
import { TsGeneratorPlugin, TContext, TFileDesc } from "ts-generator";
import { join } from "path";
import { extractAbi, parse } from "../../parser/abiParser";
import { getFilename } from "../shared";
import { codegen } from "./generation";

export interface IWeb3Cfg {
outDir?: string;
}

const DEFAULT_OUT_PATH = "./types/truffle-contracts/";

export class Web3 extends TsGeneratorPlugin {
name = "Web3";

private readonly outDirAbs: string;

constructor(ctx: TContext<IWeb3Cfg>) {
super(ctx);

const { cwd, rawConfig } = ctx;

this.outDirAbs = join(cwd, rawConfig.outDir || DEFAULT_OUT_PATH);
}

transformFile(file: TFileDesc): TFileDesc | void {
const abi = extractAbi(file.contents);
const isEmptyAbi = abi.length === 0;
if (isEmptyAbi) {
return;
}

const name = getFilename(file.path);

const contract = parse(abi, name);

return {
path: join(this.outDirAbs, "index.d.ts"),
contents: codegen(contract),
};
}
}
Loading

0 comments on commit 652a9c3

Please sign in to comment.