aboutsummaryrefslogtreecommitdiffstats
path: root/src/ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/ts')
-rw-r--r--src/ts/0x.js.ts27
-rw-r--r--src/ts/contract_wrappers/contract_wrapper.ts48
-rw-r--r--src/ts/contract_wrappers/exchange_wrapper.ts37
-rw-r--r--src/ts/globals.d.ts33
-rw-r--r--src/ts/types.ts21
-rw-r--r--src/ts/utils/assert.ts21
-rw-r--r--src/ts/utils/constants.ts2
-rw-r--r--src/ts/utils/utils.ts7
-rw-r--r--src/ts/web3_wrapper.ts69
9 files changed, 241 insertions, 24 deletions
diff --git a/src/ts/0x.js.ts b/src/ts/0x.js.ts
index ba922d3db..bd4978c96 100644
--- a/src/ts/0x.js.ts
+++ b/src/ts/0x.js.ts
@@ -1,25 +1,22 @@
+import * as _ from 'lodash';
import * as BigNumber from 'bignumber.js';
import * as BN from 'bn.js';
import * as ethUtil from 'ethereumjs-util';
+import contract = require('truffle-contract');
+import * as Web3 from 'web3';
import * as ethABI from 'ethereumjs-abi';
-import * as _ from 'lodash';
+import {Web3Wrapper} from './web3_wrapper';
import {constants} from './utils/constants';
import {assert} from './utils/assert';
+import {ExchangeWrapper} from './contract_wrappers/exchange_wrapper';
import {ECSignatureSchema} from './schemas/ec_signature_schema';
-import {SolidityTypes} from './types';
-
-/**
- * Elliptic Curve signature
- */
-export interface ECSignature {
- v: number;
- r: string;
- s: string;
-}
+import {SolidityTypes, ECSignature} from './types';
const MAX_DIGITS_IN_UNSIGNED_256_INT = 78;
export class ZeroEx {
+ public web3Wrapper: Web3Wrapper;
+ public exchange: ExchangeWrapper;
/**
* Computes the orderHash given the order parameters and returns it as a hex encoded string.
*/
@@ -74,7 +71,8 @@ export class ZeroEx {
const dataBuff = ethUtil.toBuffer(dataHex);
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
try {
- const pubKey = ethUtil.ecrecover(msgHashBuff,
+ const pubKey = ethUtil.ecrecover(
+ msgHashBuff,
signature.v,
ethUtil.toBuffer(signature.r),
ethUtil.toBuffer(signature.s));
@@ -129,7 +127,6 @@ export class ZeroEx {
const baseUnitAmount = amount.times(unit);
return baseUnitAmount;
}
-
/**
* Converts BigNumber instance to BN
* The only we convert to BN is to remain compatible with `ethABI. soliditySHA3 ` that
@@ -139,4 +136,8 @@ export class ZeroEx {
private static bigNumberToBN(value: BigNumber.BigNumber) {
return new BN(value.toString(), 10);
}
+ 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..9f4cd8039
--- /dev/null
+++ b/src/ts/contract_wrappers/contract_wrapper.ts
@@ -0,0 +1,48 @@
+import * as _ from 'lodash';
+import contract = require('truffle-contract');
+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;
+ }
+ protected async instantiateContractIfExistsAsync(artifact: Artifact, address?: string): Promise<ContractInstance> {
+ const c = await contract(artifact);
+ const providerObj = this.web3Wrapper.getCurrentProvider();
+ c.setProvider(providerObj);
+
+ const networkIdIfExists = await this.web3Wrapper.getNetworkIdIfExistsAsync();
+ const artifactNetworkConfigs = _.isUndefined(networkIdIfExists) ?
+ undefined :
+ artifact.networks[networkIdIfExists];
+ 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 {
+ const contractInstance = _.isUndefined(address) ? await c.deployed() : await c.at(address);
+ return contractInstance;
+ } catch (err) {
+ const errMsg = `${err}`;
+ if (_.includes(errMsg, 'not been deployed to detected network')) {
+ throw new Error(ZeroExError.CONTRACT_DOES_NOT_EXIST);
+ } else {
+ utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`);
+ 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..38043dd55
--- /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(dataHex: string, ecSignature: ECSignature,
+ signerAddressHex: string): Promise<boolean> {
+ assert.isHexString('dataHex', dataHex);
+ assert.doesConformToSchema('ecSignature', ecSignature, ECSignatureSchema);
+ assert.isETHAddressHex('signerAddressHex', signerAddressHex);
+
+ const senderAddressIfExists = await this.web3Wrapper.getSenderAddressIfExistsAsync();
+ assert.assert(!_.isUndefined(senderAddressIfExists), ZeroExError.USER_HAS_NO_ASSOCIATED_ADDRESSES);
+
+ const contractInstance = await this.instantiateContractIfExistsAsync((ExchangeArtifacts as any));
+ const exchangeInstance = contractInstance as ExchangeContract;
+
+ const isValidSignature = await exchangeInstance.isValidSignature.call(
+ signerAddressHex,
+ 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 99baf593f..dee957f2f 100644
--- a/src/ts/globals.d.ts
+++ b/src/ts/globals.d.ts
@@ -1,5 +1,8 @@
declare module 'chai-bignumber';
declare module 'bn.js';
+declare module 'request-promise-native';
+declare module 'web3-provider-engine';
+declare module 'web3-provider-engine/subproviders/rpc';
declare interface Schema {
id: string;
@@ -12,10 +15,18 @@ declare interface Schema {
declare namespace Chai {
interface Assertion {
bignumber: Assertion;
+ eventually: Assertion;
}
}
/* 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,6 +34,28 @@ declare module 'ethereumjs-util' {
const ecrecover: (msgHashBuff: Buffer, v: number, r: Buffer, s: Buffer) => string;
const pubToAddress: (pubKey: string) => Buffer;
const isValidAddress: (address: string) => boolean;
+ const bufferToInt: (buffer: Buffer) => number;
+}
+
+// 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;
}
declare module 'ethereumjs-abi' {
diff --git a/src/ts/types.ts b/src/ts/types.ts
index 04902cca6..4da03a4d3 100644
--- a/src/ts/types.ts
+++ b/src/ts/types.ts
@@ -1,5 +1,4 @@
import * as _ from 'lodash';
-import * as BigNumber from 'bignumber.js';
// 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
@@ -10,6 +9,26 @@ function strEnum(values: string[]): {[key: string]: string} {
}, 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;
+}
+
export const SolidityTypes = strEnum([
'address',
'uint256',
diff --git a/src/ts/utils/assert.ts b/src/ts/utils/assert.ts
index 2f52c6a3b..1baf572d1 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 * as 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/constants.ts b/src/ts/utils/constants.ts
index 60af7b674..ec2fe744a 100644
--- a/src/ts/utils/constants.ts
+++ b/src/ts/utils/constants.ts
@@ -1,3 +1,3 @@
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
-}
+};
diff --git a/src/ts/utils/utils.ts b/src/ts/utils/utils.ts
new file mode 100644
index 000000000..04ac36b54
--- /dev/null
+++ b/src/ts/utils/utils.ts
@@ -0,0 +1,7 @@
+export const utils = {
+ consoleLog(message: string): void {
+ /* 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..3b460e4da
--- /dev/null
+++ b/src/ts/web3_wrapper.ts
@@ -0,0 +1,69 @@
+import * as _ from 'lodash';
+import * as 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|undefined> {
+ const defaultAccount = this.web3.eth.defaultAccount;
+ if (!_.isUndefined(defaultAccount)) {
+ return defaultAccount;
+ }
+ const firstAccount = await this.getFirstAddressIfExistsAsync();
+ return firstAccount;
+ }
+ public async getFirstAddressIfExistsAsync(): Promise<string|undefined> {
+ const addresses = await promisify(this.web3.eth.getAccounts)();
+ if (_.isEmpty(addresses)) {
+ return undefined;
+ }
+ 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(): Promise<number|undefined> {
+ 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 = /^0x0\{0,40\}$/i;
+ const didFindCode = _.isNull(code.match(zeroHexAddressRegex));
+ return didFindCode;
+ }
+ 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(): Promise<number> {
+ const networkId = await promisify(this.web3.version.getNetwork)();
+ return networkId;
+ }
+}