Add initial exchange contract function, set up web3Wrapper, added types and utils
This commit is contained in:
parent
74e7dc46b4
commit
bc8fc53433
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
51
src/ts/contract_wrappers/contract_wrapper.ts
Normal file
51
src/ts/contract_wrappers/contract_wrapper.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
src/ts/contract_wrappers/exchange_wrapper.ts
Normal file
37
src/ts/contract_wrappers/exchange_wrapper.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
28
src/ts/globals.d.ts
vendored
28
src/ts/globals.d.ts
vendored
@ -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;
|
||||
}
|
||||
|
30
src/ts/types.ts
Normal file
30
src/ts/types.ts
Normal file
@ -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;
|
||||
}
|
@ -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}`;
|
||||
},
|
||||
};
|
||||
|
7
src/ts/utils/utils.ts
Normal file
7
src/ts/utils/utils.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const utils = {
|
||||
consoleLog(message: string) {
|
||||
/* tslint:disable */
|
||||
console.log(message);
|
||||
/* tslint:enable */
|
||||
},
|
||||
};
|
71
src/ts/web3_wrapper.ts
Normal file
71
src/ts/web3_wrapper.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user