aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--package.json4
-rw-r--r--src/ts/0x.js.ts22
-rw-r--r--src/ts/contract_wrappers/contract_wrapper.ts51
-rw-r--r--src/ts/contract_wrappers/exchange_wrapper.ts37
-rw-r--r--src/ts/globals.d.ts28
-rw-r--r--src/ts/types.ts30
-rw-r--r--src/ts/utils/assert.ts21
-rw-r--r--src/ts/utils/utils.ts7
-rw-r--r--src/ts/web3_wrapper.ts71
9 files changed, 252 insertions, 19 deletions
diff --git a/package.json b/package.json
index bac3a8a75..3f6a89bbc 100644
--- a/package.json
+++ b/package.json
@@ -52,14 +52,16 @@
"tslint-config-0xproject": "^0.0.2",
"typedoc": "^0.7.1",
"typescript": "^2.3.3",
- "web3-typescript-typings": "0.0.3",
+ "web3-typescript-typings": "0.0.7",
"webpack": "^2.6.0"
},
"dependencies": {
"bignumber.js": "^4.0.2",
+ "es6-promisify": "^5.0.0",
"ethereumjs-util": "^5.1.1",
"jsonschema": "^1.1.1",
"lodash": "^4.17.4",
+ "truffle-contract": "^2.0.0",
"web3": "^0.19.0"
}
}
diff --git a/src/ts/0x.js.ts b/src/ts/0x.js.ts
index ead1f56df..7025f8804 100644
--- a/src/ts/0x.js.ts
+++ b/src/ts/0x.js.ts
@@ -1,21 +1,21 @@
import * as BigNumber from 'bignumber.js';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
+import Web3 from 'web3';
import {assert} from './utils/assert';
+import {utils} from './utils/utils';
+import {ZeroExError} from './types';
+import {Web3Wrapper} from './web3_wrapper';
+import {ExchangeWrapper} from './contract_wrappers/exchange_wrapper';
+import contract = require('truffle-contract');
import {ECSignatureSchema} from './schemas/ec_signature_schema';
-
-/**
- * Elliptic Curve signature
- */
-export interface ECSignature {
- v: number;
- r: string;
- s: string;
-}
+import {ECSignature} from './types';
const MAX_DIGITS_IN_UNSIGNED_256_INT = 78;
export class ZeroEx {
+ public web3Wrapper: Web3Wrapper;
+ public exchange: ContractInstance;
/**
* Verifies that the elliptic curve signature `signature` was generated
* by signing `data` with the private key corresponding to the `signerAddressHex` address.
@@ -83,4 +83,8 @@ export class ZeroEx {
const baseUnitAmount = amount.times(unit);
return baseUnitAmount;
}
+ constructor(web3: Web3) {
+ this.web3Wrapper = new Web3Wrapper(web3);
+ this.exchange = new ExchangeWrapper(this.web3Wrapper);
+ }
}
diff --git a/src/ts/contract_wrappers/contract_wrapper.ts b/src/ts/contract_wrappers/contract_wrapper.ts
new file mode 100644
index 000000000..72bfffe95
--- /dev/null
+++ b/src/ts/contract_wrappers/contract_wrapper.ts
@@ -0,0 +1,51 @@
+import * as _ from 'lodash';
+import {Web3Wrapper} from '../web3_wrapper';
+import {ZeroExError} from '../types';
+import {utils} from '../utils/utils';
+
+export class ContractWrapper {
+ public web3Wrapper: Web3Wrapper;
+ constructor(web3Wrapper: Web3Wrapper) {
+ this.web3Wrapper = web3Wrapper;
+ }
+ // this.exchange = await this.instantiateContractIfExistsAsync(ExchangeArtifacts);
+ protected async instantiateContractIfExistsAsync(artifact: Artifact, address?: string): Promise<ContractInstance> {
+ const c = await contract(artifact);
+ const providerObj = this.web3Wrapper.getCurrentProvider();
+ c.setProvider(providerObj);
+
+ const networkId = await this.web3Wrapper.getNetworkIdIfExistsAsync();
+ const artifactNetworkConfigs = _.isUndefined(networkId) ? undefined : artifact.networks[networkId];
+ let contractAddress;
+ if (!_.isUndefined(address)) {
+ contractAddress = address;
+ } else if (!_.isUndefined(artifactNetworkConfigs)) {
+ contractAddress = artifactNetworkConfigs.address;
+ }
+
+ if (!_.isUndefined(contractAddress)) {
+ const doesContractExist = await this.web3Wrapper.doesContractExistAtAddressAsync(contractAddress);
+ if (!doesContractExist) {
+ throw new Error(ZeroExError.CONTRACT_DOES_NOT_EXIST);
+ }
+ }
+
+ try {
+ let contractInstance;
+ if (_.isUndefined(address)) {
+ contractInstance = await c.deployed();
+ } else {
+ contractInstance = await c.at(address);
+ }
+ return contractInstance;
+ } catch (err) {
+ const errMsg = `${err}`;
+ utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`);
+ if (_.includes(errMsg, 'not been deployed to detected network')) {
+ throw new Error(ZeroExError.CONTRACT_DOES_NOT_EXIST);
+ } else {
+ throw new Error(ZeroExError.UNHANDLED_ERROR);
+ }
+ }
+ }
+}
diff --git a/src/ts/contract_wrappers/exchange_wrapper.ts b/src/ts/contract_wrappers/exchange_wrapper.ts
new file mode 100644
index 000000000..502a1089b
--- /dev/null
+++ b/src/ts/contract_wrappers/exchange_wrapper.ts
@@ -0,0 +1,37 @@
+import * as _ from 'lodash';
+import {Web3Wrapper} from '../web3_wrapper';
+import {ECSignature, ZeroExError, ExchangeContract} from '../types';
+import {assert} from '../utils/assert';
+import {ContractWrapper} from './contract_wrapper';
+import * as ExchangeArtifacts from '../artifacts/Exchange.json';
+import {ECSignatureSchema} from '../schemas/ec_signature_schema';
+
+export class ExchangeWrapper extends ContractWrapper {
+ constructor(web3Wrapper: Web3Wrapper) {
+ super(web3Wrapper);
+ }
+ public async isValidSignatureAsync(maker: string, ecSignature: ECSignature, dataHex: string) {
+ assert.isString('maker', maker);
+ assert.doesConformToSchema('ecSignature', ecSignature, ECSignatureSchema);
+ assert.isHexString('dataHex', dataHex);
+
+ const senderAddressIfExists = this.web3Wrapper.getSenderAddressIfExistsAsync();
+ assert.assert(!_.isUndefined(senderAddressIfExists), ZeroExError.USER_HAS_NO_ASSOCIATED_ADDRESSES);
+
+ // TODO: remove any here
+ const contractInstance = await this.instantiateContractIfExistsAsync((ExchangeArtifacts as any));
+ const exchangeInstance = contractInstance as ExchangeContract;
+
+ const isValidSignature = await exchangeInstance.isValidSignature.call(
+ maker,
+ dataHex,
+ ecSignature.v,
+ ecSignature.r,
+ ecSignature.s,
+ {
+ from: senderAddressIfExists,
+ },
+ );
+ return isValidSignature;
+ }
+}
diff --git a/src/ts/globals.d.ts b/src/ts/globals.d.ts
index 796812c87..04328509a 100644
--- a/src/ts/globals.d.ts
+++ b/src/ts/globals.d.ts
@@ -15,6 +15,13 @@ declare namespace Chai {
}
/* tslint:enable */
+declare module '*.json' {
+ const json: any;
+ /* tslint:disable */
+ export default json;
+ /* tslint:enable */
+}
+
declare module 'ethereumjs-util' {
const toBuffer: (dataHex: string) => Buffer;
const hashPersonalMessage: (msg: Buffer) => Buffer;
@@ -23,3 +30,24 @@ declare module 'ethereumjs-util' {
const pubToAddress: (pubKey: string) => Buffer;
const isValidAddress: (address: string) => boolean;
}
+
+// truffle-contract declarations
+declare interface ContractInstance {}
+declare interface ContractFactory {
+ setProvider: (providerObj: any) => void;
+ deployed: () => ContractInstance;
+ at: (address: string) => ContractInstance;
+}
+declare interface Artifact {
+ networks: {[networkId: number]: any};
+}
+declare function contract(artifacts: Artifact): ContractFactory;
+declare module 'truffle-contract' {
+ export = contract;
+}
+
+// es6-promisify declarations
+declare function promisify(original: any, settings?: any): ((...arg: any[]) => Promise<any>);
+declare module 'es6-promisify' {
+ export = promisify;
+}
diff --git a/src/ts/types.ts b/src/ts/types.ts
new file mode 100644
index 000000000..01380af02
--- /dev/null
+++ b/src/ts/types.ts
@@ -0,0 +1,30 @@
+import * as _ from 'lodash';
+
+// Utility function to create a K:V from a list of strings
+// Adapted from: https://basarat.gitbooks.io/typescript/content/docs/types/literal-types.html
+function strEnum(values: string[]): {[key: string]: string} {
+ return _.reduce(values, (result, key) => {
+ result[key] = key;
+ return result;
+ }, Object.create(null));
+}
+
+export const ZeroExError = strEnum([
+ 'CONTRACT_DOES_NOT_EXIST',
+ 'UNHANDLED_ERROR',
+ 'USER_HAS_NO_ASSOCIATED_ADDRESSES',
+]);
+export type ZeroExError = keyof typeof ZeroExError;
+
+/**
+ * Elliptic Curve signature
+ */
+export interface ECSignature {
+ v: number;
+ r: string;
+ s: string;
+}
+
+export interface ExchangeContract {
+ isValidSignature: any;
+}
diff --git a/src/ts/utils/assert.ts b/src/ts/utils/assert.ts
index 2f52c6a3b..15d3031ff 100644
--- a/src/ts/utils/assert.ts
+++ b/src/ts/utils/assert.ts
@@ -1,30 +1,33 @@
import * as _ from 'lodash';
import * as BigNumber from 'bignumber.js';
-import Web3 = require('web3');
+import Web3 from 'web3';
import {SchemaValidator} from './schema_validator';
const HEX_REGEX = /^0x[0-9A-F]*$/i;
export const assert = {
- isBigNumber(variableName: string, value: BigNumber.BigNumber) {
+ isBigNumber(variableName: string, value: BigNumber.BigNumber): void {
const isBigNumber = _.isObject(value) && value.isBigNumber;
this.assert(isBigNumber, this.typeAssertionMessage(variableName, 'BigNumber', value));
},
- isString(variableName: string, value: string) {
+ isUndefined(value: any, variableName?: string): void {
+ this.assert(_.isUndefined(value), this.typeAssertionMessage(variableName, 'undefined', value));
+ },
+ isString(variableName: string, value: string): void {
this.assert(_.isString(value), this.typeAssertionMessage(variableName, 'string', value));
},
- isHexString(variableName: string, value: string) {
+ isHexString(variableName: string, value: string): void {
this.assert(_.isString(value) && HEX_REGEX.test(value),
this.typeAssertionMessage(variableName, 'HexString', value));
},
- isETHAddressHex(variableName: string, value: string) {
+ isETHAddressHex(variableName: string, value: string): void {
const web3 = new Web3();
this.assert(web3.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value));
},
- isNumber(variableName: string, value: number) {
+ isNumber(variableName: string, value: number): void {
this.assert(_.isFinite(value), this.typeAssertionMessage(variableName, 'number', value));
},
- doesConformToSchema(variableName: string, value: object, schema: Schema) {
+ doesConformToSchema(variableName: string, value: object, schema: Schema): void {
const schemaValidator = new SchemaValidator();
const validationResult = schemaValidator.validate(value, schema);
const hasValidationErrors = validationResult.errors.length > 0;
@@ -33,12 +36,12 @@ Encountered: ${JSON.stringify(value, null, '\t')}
Validation errors: ${validationResult.errors.join(', ')}`;
this.assert(!hasValidationErrors, msg);
},
- assert(condition: boolean, message: string) {
+ assert(condition: boolean, message: string): void {
if (!condition) {
throw new Error(message);
}
},
- typeAssertionMessage(variableName: string, type: string, value: any) {
+ typeAssertionMessage(variableName: string, type: string, value: any): string {
return `Expected ${variableName} to be of type ${type}, encountered: ${value}`;
},
};
diff --git a/src/ts/utils/utils.ts b/src/ts/utils/utils.ts
new file mode 100644
index 000000000..893f82ca3
--- /dev/null
+++ b/src/ts/utils/utils.ts
@@ -0,0 +1,7 @@
+export const utils = {
+ consoleLog(message: string) {
+ /* tslint:disable */
+ console.log(message);
+ /* tslint:enable */
+ },
+};
diff --git a/src/ts/web3_wrapper.ts b/src/ts/web3_wrapper.ts
new file mode 100644
index 000000000..92781687e
--- /dev/null
+++ b/src/ts/web3_wrapper.ts
@@ -0,0 +1,71 @@
+import * as _ from 'lodash';
+import Web3 from 'web3';
+import * as BigNumber from 'bignumber.js';
+import promisify = require('es6-promisify');
+
+export class Web3Wrapper {
+ private web3: Web3;
+ constructor(web3: Web3) {
+ this.web3 = new Web3();
+ this.web3.setProvider(web3.currentProvider);
+ }
+ public isAddress(address: string): boolean {
+ return this.web3.isAddress(address);
+ }
+ public async getSenderAddressIfExistsAsync(): Promise<string> {
+ const defaultAccount = this.web3.eth.defaultAccount;
+ if (!_.isUndefined(defaultAccount)) {
+ return defaultAccount;
+ }
+ const firstAccount = await this.getFirstAddressIfExistsAsync();
+ return firstAccount;
+ }
+ public async getFirstAddressIfExistsAsync(): Promise<string> {
+ const addresses = await promisify(this.web3.eth.getAccounts)();
+ if (_.isEmpty(addresses)) {
+ return '';
+ }
+ return (addresses as string[])[0];
+ }
+ public async getNodeVersionAsync(): Promise<string> {
+ const nodeVersion = await promisify(this.web3.version.getNode)();
+ return nodeVersion;
+ }
+ public getCurrentProvider(): Web3.Provider {
+ return this.web3.currentProvider;
+ }
+ public async getNetworkIdIfExistsAsync() {
+ try {
+ const networkId = await this.getNetworkAsync();
+ return Number(networkId);
+ } catch (err) {
+ return undefined;
+ }
+ }
+ public async getBalanceInEthAsync(owner: string): Promise<BigNumber.BigNumber> {
+ const balanceInWei = await promisify(this.web3.eth.getBalance)(owner);
+ const balanceEth = this.web3.fromWei(balanceInWei, 'ether');
+ return balanceEth;
+ }
+ public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
+ const code = await promisify(this.web3.eth.getCode)(address);
+ // Regex matches 0x0, 0x00, 0x in order to accomodate poorly implemented clients
+ const zeroHexAddressRegex = /^0[xX][0]*$/;
+ const didFindCode = _.isNull(code.match(zeroHexAddressRegex));
+ return didFindCode;
+ }
+ // Note: since `sign` is overloaded to be both a sync and async method, it doesn't play nice
+ // with our callAsync method. We therefore handle it here as a special case.
+ public async signTransactionAsync(address: string, message: string): Promise<string> {
+ const signData = await promisify(this.web3.eth.sign)(address, message);
+ return signData;
+ }
+ public async getBlockTimestampAsync(blockHash: string): Promise<number> {
+ const {timestamp} = await promisify(this.web3.eth.getBlock)(blockHash);
+ return timestamp;
+ }
+ private async getNetworkAsync() {
+ const networkId = await promisify(this.web3.version.getNetwork)();
+ return networkId;
+ }
+}