Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web3.js 1.0.0 support #88

Merged
merged 7 commits into from
Sep 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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