diff options
author | Fabio Berger <me@fabioberger.com> | 2018-01-30 20:26:46 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-01-30 20:26:46 +0800 |
commit | c6dece6bd1e5f5afa56b290557eb7a6245c76cb6 (patch) | |
tree | ad7a33ffe5d80c0eb41ae10fbc8314f193e52383 /packages | |
parent | 93a5b3f457c1211676296840c285759007a55500 (diff) | |
download | dexon-sol-tools-c6dece6bd1e5f5afa56b290557eb7a6245c76cb6.tar dexon-sol-tools-c6dece6bd1e5f5afa56b290557eb7a6245c76cb6.tar.gz dexon-sol-tools-c6dece6bd1e5f5afa56b290557eb7a6245c76cb6.tar.bz2 dexon-sol-tools-c6dece6bd1e5f5afa56b290557eb7a6245c76cb6.tar.lz dexon-sol-tools-c6dece6bd1e5f5afa56b290557eb7a6245c76cb6.tar.xz dexon-sol-tools-c6dece6bd1e5f5afa56b290557eb7a6245c76cb6.tar.zst dexon-sol-tools-c6dece6bd1e5f5afa56b290557eb7a6245c76cb6.zip |
Add config file specifically in prettier command and fix files
Diffstat (limited to 'packages')
376 files changed, 34256 insertions, 34256 deletions
diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index 67d6452b5..465a2a753 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -1,94 +1,94 @@ { - "name": "0x.js", - "version": "0.30.2", - "description": "A javascript library for interacting with the 0x protocol", - "keywords": ["0x.js", "0xproject", "ethereum", "tokens", "exchange"], - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", - "scripts": { - "prebuild": "run-s clean generate_contract_wrappers", - "build": "run-p build:umd:prod build:commonjs; exit 0;", - "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR", - "upload_docs_json": - "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json", - "generate_contract_wrappers": - "node ../abi-gen/lib/index.js --abiGlob 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --templates contract_templates --output src/contract_wrappers/generated", - "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", - "test:circleci": "run-s test:coverage report_test_coverage", - "test": "run-s clean test:commonjs", - "test:coverage": "nyc npm run test --all", - "report_test_coverage": "nyc report --reporter=text-lcov | coveralls", - "update_contracts": - "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../0x.js/src/artifacts; done;", - "clean": "shx rm -rf _bundles lib test_temp", - "build:umd:prod": "NODE_ENV=production webpack", - "build:commonjs": "tsc && copyfiles -u 2 './src/artifacts/**/*.json' ./lib/src/artifacts;", - "test:commonjs": "run-s build:commonjs run_mocha", - "run_mocha": "mocha lib/test/**/*_test.js --timeout 10000 --bail --exit" - }, - "config": { - "artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken" - }, - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js" - }, - "license": "Apache-2.0", - "engines": { - "node": ">=6.0.0" - }, - "devDependencies": { - "@0xproject/abi-gen": "^0.1.4", - "@0xproject/dev-utils": "^0.0.7", - "@0xproject/tslint-config": "^0.4.4", - "@types/bintrees": "^1.0.2", - "@types/jsonschema": "^1.1.1", - "@types/lodash": "^4.14.86", - "@types/mocha": "^2.2.42", - "@types/node": "^8.0.53", - "@types/sinon": "^2.2.2", - "@types/uuid": "^3.4.2", - "awesome-typescript-loader": "^3.1.3", - "chai": "^4.0.1", - "chai-as-promised": "^7.1.0", - "chai-as-promised-typescript-typings": "^0.0.6", - "chai-bignumber": "^2.0.1", - "chai-typescript-typings": "^0.0.2", - "copyfiles": "^1.2.0", - "coveralls": "^3.0.0", - "dirty-chai": "^2.0.1", - "json-loader": "^0.5.4", - "mocha": "^4.0.1", - "npm-run-all": "^4.1.2", - "nyc": "^11.0.1", - "opn-cli": "^3.1.0", - "request": "^2.81.0", - "request-promise-native": "^1.0.4", - "shx": "^0.2.2", - "sinon": "^4.0.0", - "source-map-support": "^0.5.0", - "truffle-hdwallet-provider": "^0.0.3", - "tslint": "5.8.0", - "typedoc": "~0.8.0", - "typescript": "~2.6.1", - "web3-provider-engine": "^13.0.1", - "web3-typescript-typings": "^0.9.6", - "webpack": "^3.1.0" - }, - "dependencies": { - "@0xproject/assert": "^0.0.13", - "@0xproject/json-schemas": "^0.7.5", - "@0xproject/types": "^0.1.6", - "@0xproject/utils": "^0.2.2", - "@0xproject/web3-wrapper": "^0.1.7", - "bintrees": "^1.0.2", - "bn.js": "^4.11.8", - "ethereumjs-abi": "^0.6.4", - "ethereumjs-blockstream": "^2.0.6", - "ethereumjs-util": "^5.1.1", - "js-sha3": "^0.6.1", - "lodash": "^4.17.4", - "uuid": "^3.1.0", - "web3": "^0.20.0" - } + "name": "0x.js", + "version": "0.30.2", + "description": "A javascript library for interacting with the 0x protocol", + "keywords": ["0x.js", "0xproject", "ethereum", "tokens", "exchange"], + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "prebuild": "run-s clean generate_contract_wrappers", + "build": "run-p build:umd:prod build:commonjs; exit 0;", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR", + "upload_docs_json": + "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json", + "generate_contract_wrappers": + "node ../abi-gen/lib/index.js --abiGlob 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --templates contract_templates --output src/contract_wrappers/generated", + "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", + "test:circleci": "run-s test:coverage report_test_coverage", + "test": "run-s clean test:commonjs", + "test:coverage": "nyc npm run test --all", + "report_test_coverage": "nyc report --reporter=text-lcov | coveralls", + "update_contracts": + "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../0x.js/src/artifacts; done;", + "clean": "shx rm -rf _bundles lib test_temp", + "build:umd:prod": "NODE_ENV=production webpack", + "build:commonjs": "tsc && copyfiles -u 2 './src/artifacts/**/*.json' ./lib/src/artifacts;", + "test:commonjs": "run-s build:commonjs run_mocha", + "run_mocha": "mocha lib/test/**/*_test.js --timeout 10000 --bail --exit" + }, + "config": { + "artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js" + }, + "license": "Apache-2.0", + "engines": { + "node": ">=6.0.0" + }, + "devDependencies": { + "@0xproject/abi-gen": "^0.1.4", + "@0xproject/dev-utils": "^0.0.7", + "@0xproject/tslint-config": "^0.4.4", + "@types/bintrees": "^1.0.2", + "@types/jsonschema": "^1.1.1", + "@types/lodash": "^4.14.86", + "@types/mocha": "^2.2.42", + "@types/node": "^8.0.53", + "@types/sinon": "^2.2.2", + "@types/uuid": "^3.4.2", + "awesome-typescript-loader": "^3.1.3", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-as-promised-typescript-typings": "^0.0.6", + "chai-bignumber": "^2.0.1", + "chai-typescript-typings": "^0.0.2", + "copyfiles": "^1.2.0", + "coveralls": "^3.0.0", + "dirty-chai": "^2.0.1", + "json-loader": "^0.5.4", + "mocha": "^4.0.1", + "npm-run-all": "^4.1.2", + "nyc": "^11.0.1", + "opn-cli": "^3.1.0", + "request": "^2.81.0", + "request-promise-native": "^1.0.4", + "shx": "^0.2.2", + "sinon": "^4.0.0", + "source-map-support": "^0.5.0", + "truffle-hdwallet-provider": "^0.0.3", + "tslint": "5.8.0", + "typedoc": "~0.8.0", + "typescript": "~2.6.1", + "web3-provider-engine": "^13.0.1", + "web3-typescript-typings": "^0.9.6", + "webpack": "^3.1.0" + }, + "dependencies": { + "@0xproject/assert": "^0.0.13", + "@0xproject/json-schemas": "^0.7.5", + "@0xproject/types": "^0.1.6", + "@0xproject/utils": "^0.2.2", + "@0xproject/web3-wrapper": "^0.1.7", + "bintrees": "^1.0.2", + "bn.js": "^4.11.8", + "ethereumjs-abi": "^0.6.4", + "ethereumjs-blockstream": "^2.0.6", + "ethereumjs-util": "^5.1.1", + "js-sha3": "^0.6.1", + "lodash": "^4.17.4", + "uuid": "^3.1.0", + "web3": "^0.20.0" + } } diff --git a/packages/0x.js/src/0x.ts b/packages/0x.js/src/0x.ts index dec26e3eb..f8a484c5d 100644 --- a/packages/0x.js/src/0x.ts +++ b/packages/0x.js/src/0x.ts @@ -13,13 +13,13 @@ import { TokenWrapper } from './contract_wrappers/token_wrapper'; import { OrderStateWatcher } from './order_watcher/order_state_watcher'; import { zeroExConfigSchema } from './schemas/zero_ex_config_schema'; import { - ECSignature, - Order, - SignedOrder, - TransactionReceiptWithDecodedLogs, - Web3Provider, - ZeroExConfig, - ZeroExError, + ECSignature, + Order, + SignedOrder, + TransactionReceiptWithDecodedLogs, + Web3Provider, + ZeroExConfig, + ZeroExError, } from './types'; import { AbiDecoder } from './utils/abi_decoder'; import { assert } from './utils/assert'; @@ -33,318 +33,318 @@ import { utils } from './utils/utils'; * and all calls to the library should be made through a ZeroEx instance. */ export class ZeroEx { - /** - * When creating an order without a specified taker or feeRecipient you must supply the Solidity - * address null type (as opposed to Javascripts `null`, `undefined` or empty string). We expose - * this constant for your convenience. - */ - public static NULL_ADDRESS = constants.NULL_ADDRESS; + /** + * When creating an order without a specified taker or feeRecipient you must supply the Solidity + * address null type (as opposed to Javascripts `null`, `undefined` or empty string). We expose + * this constant for your convenience. + */ + public static NULL_ADDRESS = constants.NULL_ADDRESS; - /** - * An instance of the ExchangeWrapper class containing methods for interacting with the 0x Exchange smart contract. - */ - public exchange: ExchangeWrapper; - /** - * An instance of the TokenRegistryWrapper class containing methods for interacting with the 0x - * TokenRegistry smart contract. - */ - public tokenRegistry: TokenRegistryWrapper; - /** - * An instance of the TokenWrapper class containing methods for interacting with any ERC20 token smart contract. - */ - public token: TokenWrapper; - /** - * An instance of the EtherTokenWrapper class containing methods for interacting with the - * wrapped ETH ERC20 token smart contract. - */ - public etherToken: EtherTokenWrapper; - /** - * An instance of the TokenTransferProxyWrapper class containing methods for interacting with the - * tokenTransferProxy smart contract. - */ - public proxy: TokenTransferProxyWrapper; - /** - * An instance of the OrderStateWatcher class containing methods for watching a set of orders for relevant - * blockchain state changes. - */ - public orderStateWatcher: OrderStateWatcher; - private _web3Wrapper: Web3Wrapper; - private _abiDecoder: AbiDecoder; - /** - * Verifies that the elliptic curve signature `signature` was generated - * by signing `data` with the private key corresponding to the `signerAddress` address. - * @param data The hex encoded data signed by the supplied signature. - * @param signature An object containing the elliptic curve signature parameters. - * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. - * @return Whether the signature is valid for the supplied signerAddress and data. - */ - public static isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean { - assert.isHexString('data', data); - assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema); - assert.isETHAddressHex('signerAddress', signerAddress); + /** + * An instance of the ExchangeWrapper class containing methods for interacting with the 0x Exchange smart contract. + */ + public exchange: ExchangeWrapper; + /** + * An instance of the TokenRegistryWrapper class containing methods for interacting with the 0x + * TokenRegistry smart contract. + */ + public tokenRegistry: TokenRegistryWrapper; + /** + * An instance of the TokenWrapper class containing methods for interacting with any ERC20 token smart contract. + */ + public token: TokenWrapper; + /** + * An instance of the EtherTokenWrapper class containing methods for interacting with the + * wrapped ETH ERC20 token smart contract. + */ + public etherToken: EtherTokenWrapper; + /** + * An instance of the TokenTransferProxyWrapper class containing methods for interacting with the + * tokenTransferProxy smart contract. + */ + public proxy: TokenTransferProxyWrapper; + /** + * An instance of the OrderStateWatcher class containing methods for watching a set of orders for relevant + * blockchain state changes. + */ + public orderStateWatcher: OrderStateWatcher; + private _web3Wrapper: Web3Wrapper; + private _abiDecoder: AbiDecoder; + /** + * Verifies that the elliptic curve signature `signature` was generated + * by signing `data` with the private key corresponding to the `signerAddress` address. + * @param data The hex encoded data signed by the supplied signature. + * @param signature An object containing the elliptic curve signature parameters. + * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. + * @return Whether the signature is valid for the supplied signerAddress and data. + */ + public static isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean { + assert.isHexString('data', data); + assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema); + assert.isETHAddressHex('signerAddress', signerAddress); - const isValidSignature = signatureUtils.isValidSignature(data, signature, signerAddress); - return isValidSignature; - } - /** - * Generates a pseudo-random 256-bit salt. - * The salt can be included in an 0x order, ensuring that the order generates a unique orderHash - * and will not collide with other outstanding orders that are identical in all other parameters. - * @return A pseudo-random 256-bit number that can be used as a salt. - */ - public static generatePseudoRandomSalt(): BigNumber { - // BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places. - // Source: https://mikemcl.github.io/bignumber.js/#random - const randomNumber = BigNumber.random(constants.MAX_DIGITS_IN_UNSIGNED_256_INT); - const factor = new BigNumber(10).pow(constants.MAX_DIGITS_IN_UNSIGNED_256_INT - 1); - const salt = randomNumber.times(factor).round(); - return salt; - } - /** - * Checks if the supplied hex encoded order hash is valid. - * Note: Valid means it has the expected format, not that an order with the orderHash exists. - * Use this method when processing orderHashes submitted as user input. - * @param orderHash Hex encoded orderHash. - * @return Whether the supplied orderHash has the expected format. - */ - public static isValidOrderHash(orderHash: string): boolean { - // Since this method can be called to check if any arbitrary string conforms to an orderHash's - // format, we only assert that we were indeed passed a string. - assert.isString('orderHash', orderHash); - const schemaValidator = new SchemaValidator(); - const isValidOrderHash = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid; - return isValidOrderHash; - } - /** - * A unit amount is defined as the amount of a token above the specified decimal places (integer part). - * E.g: If a currency has 18 decimal places, 1e18 or one quintillion of the currency is equivalent - * to 1 unit. - * @param amount The amount in baseUnits that you would like converted to units. - * @param decimals The number of decimal places the unit amount has. - * @return The amount in units. - */ - public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber { - assert.isValidBaseUnitAmount('amount', amount); - assert.isNumber('decimals', decimals); + const isValidSignature = signatureUtils.isValidSignature(data, signature, signerAddress); + return isValidSignature; + } + /** + * Generates a pseudo-random 256-bit salt. + * The salt can be included in an 0x order, ensuring that the order generates a unique orderHash + * and will not collide with other outstanding orders that are identical in all other parameters. + * @return A pseudo-random 256-bit number that can be used as a salt. + */ + public static generatePseudoRandomSalt(): BigNumber { + // BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places. + // Source: https://mikemcl.github.io/bignumber.js/#random + const randomNumber = BigNumber.random(constants.MAX_DIGITS_IN_UNSIGNED_256_INT); + const factor = new BigNumber(10).pow(constants.MAX_DIGITS_IN_UNSIGNED_256_INT - 1); + const salt = randomNumber.times(factor).round(); + return salt; + } + /** + * Checks if the supplied hex encoded order hash is valid. + * Note: Valid means it has the expected format, not that an order with the orderHash exists. + * Use this method when processing orderHashes submitted as user input. + * @param orderHash Hex encoded orderHash. + * @return Whether the supplied orderHash has the expected format. + */ + public static isValidOrderHash(orderHash: string): boolean { + // Since this method can be called to check if any arbitrary string conforms to an orderHash's + // format, we only assert that we were indeed passed a string. + assert.isString('orderHash', orderHash); + const schemaValidator = new SchemaValidator(); + const isValidOrderHash = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid; + return isValidOrderHash; + } + /** + * A unit amount is defined as the amount of a token above the specified decimal places (integer part). + * E.g: If a currency has 18 decimal places, 1e18 or one quintillion of the currency is equivalent + * to 1 unit. + * @param amount The amount in baseUnits that you would like converted to units. + * @param decimals The number of decimal places the unit amount has. + * @return The amount in units. + */ + public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber { + assert.isValidBaseUnitAmount('amount', amount); + assert.isNumber('decimals', decimals); - const aUnit = new BigNumber(10).pow(decimals); - const unit = amount.div(aUnit); - return unit; - } - /** - * A baseUnit is defined as the smallest denomination of a token. An amount expressed in baseUnits - * is the amount expressed in the smallest denomination. - * E.g: 1 unit of a token with 18 decimal places is expressed in baseUnits as 1000000000000000000 - * @param amount The amount of units that you would like converted to baseUnits. - * @param decimals The number of decimal places the unit amount has. - * @return The amount in baseUnits. - */ - public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber { - assert.isBigNumber('amount', amount); - assert.isNumber('decimals', decimals); + const aUnit = new BigNumber(10).pow(decimals); + const unit = amount.div(aUnit); + return unit; + } + /** + * A baseUnit is defined as the smallest denomination of a token. An amount expressed in baseUnits + * is the amount expressed in the smallest denomination. + * E.g: 1 unit of a token with 18 decimal places is expressed in baseUnits as 1000000000000000000 + * @param amount The amount of units that you would like converted to baseUnits. + * @param decimals The number of decimal places the unit amount has. + * @return The amount in baseUnits. + */ + public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber { + assert.isBigNumber('amount', amount); + assert.isNumber('decimals', decimals); - const unit = new BigNumber(10).pow(decimals); - const baseUnitAmount = amount.times(unit); - const hasDecimals = baseUnitAmount.decimalPlaces() !== 0; - if (hasDecimals) { - throw new Error(`Invalid unit amount: ${amount.toString()} - Too many decimal places`); - } - return baseUnitAmount; - } - /** - * Computes the orderHash for a supplied order. - * @param order An object that conforms to the Order or SignedOrder interface definitions. - * @return The resulting orderHash from hashing the supplied order. - */ - @decorators.syncZeroExErrorHandler - public static getOrderHashHex(order: Order | SignedOrder): string { - assert.doesConformToSchema('order', order, schemas.orderSchema); - const orderHashHex = utils.getOrderHashHex(order); - return orderHashHex; - } - /** - * Instantiates a new ZeroEx instance that provides the public interface to the 0x.js library. - * @param provider The Web3.js Provider instance you would like the 0x.js library to use for interacting with - * the Ethereum network. - * @param config The configuration object. Look up the type for the description. - * @return An instance of the 0x.js ZeroEx class. - */ - constructor(provider: Web3Provider, config: ZeroExConfig) { - assert.isWeb3Provider('provider', provider); - assert.doesConformToSchema('config', config, zeroExConfigSchema); - const artifactJSONs = _.values(artifacts); - const abiArrays = _.map(artifactJSONs, artifact => artifact.abi); - this._abiDecoder = new AbiDecoder(abiArrays); - const defaults = { - gasPrice: config.gasPrice, - }; - this._web3Wrapper = new Web3Wrapper(provider, defaults); - this.proxy = new TokenTransferProxyWrapper( - this._web3Wrapper, - config.networkId, - config.tokenTransferProxyContractAddress, - ); - this.token = new TokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.proxy); - this.exchange = new ExchangeWrapper( - this._web3Wrapper, - config.networkId, - this._abiDecoder, - this.token, - config.exchangeContractAddress, - config.zrxContractAddress, - ); - this.tokenRegistry = new TokenRegistryWrapper( - this._web3Wrapper, - config.networkId, - config.tokenRegistryContractAddress, - ); - this.etherToken = new EtherTokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.token); - this.orderStateWatcher = new OrderStateWatcher( - this._web3Wrapper, - this._abiDecoder, - this.token, - this.exchange, - config.orderWatcherConfig, - ); - } - /** - * Sets a new web3 provider for 0x.js. Updating the provider will stop all - * subscriptions so you will need to re-subscribe to all events relevant to your app after this call. - * @param provider The Web3Provider you would like the 0x.js library to use from now on. - * @param networkId The id of the network your provider is connected to - */ - public setProvider(provider: Web3Provider, networkId: number): void { - this._web3Wrapper.setProvider(provider); - (this.exchange as any)._invalidateContractInstances(); - (this.exchange as any)._setNetworkId(networkId); - (this.tokenRegistry as any)._invalidateContractInstance(); - (this.tokenRegistry as any)._setNetworkId(networkId); - (this.token as any)._invalidateContractInstances(); - (this.token as any)._setNetworkId(networkId); - (this.proxy as any)._invalidateContractInstance(); - (this.proxy as any)._setNetworkId(networkId); - (this.etherToken as any)._invalidateContractInstance(); - (this.etherToken as any)._setNetworkId(networkId); - } - /** - * Get user Ethereum addresses available through the supplied web3 provider available for sending transactions. - * @return An array of available user Ethereum addresses. - */ - public async getAvailableAddressesAsync(): Promise<string[]> { - const availableAddresses = await this._web3Wrapper.getAvailableAddressesAsync(); - return availableAddresses; - } - /** - * Signs an orderHash and returns it's elliptic curve signature. - * This method currently supports TestRPC, Geth and Parity above and below V1.6.6 - * @param orderHash Hex encoded orderHash to sign. - * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address - * must be available via the Web3.Provider supplied to 0x.js. - * @param shouldAddPersonalMessagePrefix Some signers add the personal message prefix `\x19Ethereum Signed Message` - * themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client - * (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix - * before sending the request. - * @return An object containing the Elliptic curve signature parameters generated by signing the orderHash. - */ - public async signOrderHashAsync( - orderHash: string, - signerAddress: string, - shouldAddPersonalMessagePrefix: boolean, - ): Promise<ECSignature> { - assert.isHexString('orderHash', orderHash); - await assert.isSenderAddressAsync('signerAddress', signerAddress, this._web3Wrapper); + const unit = new BigNumber(10).pow(decimals); + const baseUnitAmount = amount.times(unit); + const hasDecimals = baseUnitAmount.decimalPlaces() !== 0; + if (hasDecimals) { + throw new Error(`Invalid unit amount: ${amount.toString()} - Too many decimal places`); + } + return baseUnitAmount; + } + /** + * Computes the orderHash for a supplied order. + * @param order An object that conforms to the Order or SignedOrder interface definitions. + * @return The resulting orderHash from hashing the supplied order. + */ + @decorators.syncZeroExErrorHandler + public static getOrderHashHex(order: Order | SignedOrder): string { + assert.doesConformToSchema('order', order, schemas.orderSchema); + const orderHashHex = utils.getOrderHashHex(order); + return orderHashHex; + } + /** + * Instantiates a new ZeroEx instance that provides the public interface to the 0x.js library. + * @param provider The Web3.js Provider instance you would like the 0x.js library to use for interacting with + * the Ethereum network. + * @param config The configuration object. Look up the type for the description. + * @return An instance of the 0x.js ZeroEx class. + */ + constructor(provider: Web3Provider, config: ZeroExConfig) { + assert.isWeb3Provider('provider', provider); + assert.doesConformToSchema('config', config, zeroExConfigSchema); + const artifactJSONs = _.values(artifacts); + const abiArrays = _.map(artifactJSONs, artifact => artifact.abi); + this._abiDecoder = new AbiDecoder(abiArrays); + const defaults = { + gasPrice: config.gasPrice, + }; + this._web3Wrapper = new Web3Wrapper(provider, defaults); + this.proxy = new TokenTransferProxyWrapper( + this._web3Wrapper, + config.networkId, + config.tokenTransferProxyContractAddress, + ); + this.token = new TokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.proxy); + this.exchange = new ExchangeWrapper( + this._web3Wrapper, + config.networkId, + this._abiDecoder, + this.token, + config.exchangeContractAddress, + config.zrxContractAddress, + ); + this.tokenRegistry = new TokenRegistryWrapper( + this._web3Wrapper, + config.networkId, + config.tokenRegistryContractAddress, + ); + this.etherToken = new EtherTokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.token); + this.orderStateWatcher = new OrderStateWatcher( + this._web3Wrapper, + this._abiDecoder, + this.token, + this.exchange, + config.orderWatcherConfig, + ); + } + /** + * Sets a new web3 provider for 0x.js. Updating the provider will stop all + * subscriptions so you will need to re-subscribe to all events relevant to your app after this call. + * @param provider The Web3Provider you would like the 0x.js library to use from now on. + * @param networkId The id of the network your provider is connected to + */ + public setProvider(provider: Web3Provider, networkId: number): void { + this._web3Wrapper.setProvider(provider); + (this.exchange as any)._invalidateContractInstances(); + (this.exchange as any)._setNetworkId(networkId); + (this.tokenRegistry as any)._invalidateContractInstance(); + (this.tokenRegistry as any)._setNetworkId(networkId); + (this.token as any)._invalidateContractInstances(); + (this.token as any)._setNetworkId(networkId); + (this.proxy as any)._invalidateContractInstance(); + (this.proxy as any)._setNetworkId(networkId); + (this.etherToken as any)._invalidateContractInstance(); + (this.etherToken as any)._setNetworkId(networkId); + } + /** + * Get user Ethereum addresses available through the supplied web3 provider available for sending transactions. + * @return An array of available user Ethereum addresses. + */ + public async getAvailableAddressesAsync(): Promise<string[]> { + const availableAddresses = await this._web3Wrapper.getAvailableAddressesAsync(); + return availableAddresses; + } + /** + * Signs an orderHash and returns it's elliptic curve signature. + * This method currently supports TestRPC, Geth and Parity above and below V1.6.6 + * @param orderHash Hex encoded orderHash to sign. + * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address + * must be available via the Web3.Provider supplied to 0x.js. + * @param shouldAddPersonalMessagePrefix Some signers add the personal message prefix `\x19Ethereum Signed Message` + * themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client + * (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix + * before sending the request. + * @return An object containing the Elliptic curve signature parameters generated by signing the orderHash. + */ + public async signOrderHashAsync( + orderHash: string, + signerAddress: string, + shouldAddPersonalMessagePrefix: boolean, + ): Promise<ECSignature> { + assert.isHexString('orderHash', orderHash); + await assert.isSenderAddressAsync('signerAddress', signerAddress, this._web3Wrapper); - let msgHashHex = orderHash; - if (shouldAddPersonalMessagePrefix) { - const orderHashBuff = ethUtil.toBuffer(orderHash); - const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff); - msgHashHex = ethUtil.bufferToHex(msgHashBuff); - } + let msgHashHex = orderHash; + if (shouldAddPersonalMessagePrefix) { + const orderHashBuff = ethUtil.toBuffer(orderHash); + const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff); + msgHashHex = ethUtil.bufferToHex(msgHashBuff); + } - const signature = await this._web3Wrapper.signTransactionAsync(signerAddress, msgHashHex); + const signature = await this._web3Wrapper.signTransactionAsync(signerAddress, msgHashHex); - // HACK: There is no consensus on whether the signatureHex string should be formatted as - // v + r + s OR r + s + v, and different clients (even different versions of the same client) - // return the signature params in different orders. In order to support all client implementations, - // we parse the signature in both ways, and evaluate if either one is a valid signature. - const validVParamValues = [27, 28]; - const ecSignatureVRS = signatureUtils.parseSignatureHexAsVRS(signature); - if (_.includes(validVParamValues, ecSignatureVRS.v)) { - const isValidVRSSignature = ZeroEx.isValidSignature(orderHash, ecSignatureVRS, signerAddress); - if (isValidVRSSignature) { - return ecSignatureVRS; - } - } + // HACK: There is no consensus on whether the signatureHex string should be formatted as + // v + r + s OR r + s + v, and different clients (even different versions of the same client) + // return the signature params in different orders. In order to support all client implementations, + // we parse the signature in both ways, and evaluate if either one is a valid signature. + const validVParamValues = [27, 28]; + const ecSignatureVRS = signatureUtils.parseSignatureHexAsVRS(signature); + if (_.includes(validVParamValues, ecSignatureVRS.v)) { + const isValidVRSSignature = ZeroEx.isValidSignature(orderHash, ecSignatureVRS, signerAddress); + if (isValidVRSSignature) { + return ecSignatureVRS; + } + } - const ecSignatureRSV = signatureUtils.parseSignatureHexAsRSV(signature); - if (_.includes(validVParamValues, ecSignatureRSV.v)) { - const isValidRSVSignature = ZeroEx.isValidSignature(orderHash, ecSignatureRSV, signerAddress); - if (isValidRSVSignature) { - return ecSignatureRSV; - } - } + const ecSignatureRSV = signatureUtils.parseSignatureHexAsRSV(signature); + if (_.includes(validVParamValues, ecSignatureRSV.v)) { + const isValidRSVSignature = ZeroEx.isValidSignature(orderHash, ecSignatureRSV, signerAddress); + if (isValidRSVSignature) { + return ecSignatureRSV; + } + } - throw new Error(ZeroExError.InvalidSignature); - } - /** - * Waits for a transaction to be mined and returns the transaction receipt. - * @param txHash Transaction hash - * @param pollingIntervalMs How often (in ms) should we check if the transaction is mined. - * @param timeoutMs How long (in ms) to poll for transaction mined until aborting. - * @return Transaction receipt with decoded log args. - */ - public async awaitTransactionMinedAsync( - txHash: string, - pollingIntervalMs = 1000, - timeoutMs?: number, - ): Promise<TransactionReceiptWithDecodedLogs> { - let timeoutExceeded = false; - if (timeoutMs) { - setTimeout(() => (timeoutExceeded = true), timeoutMs); - } + throw new Error(ZeroExError.InvalidSignature); + } + /** + * Waits for a transaction to be mined and returns the transaction receipt. + * @param txHash Transaction hash + * @param pollingIntervalMs How often (in ms) should we check if the transaction is mined. + * @param timeoutMs How long (in ms) to poll for transaction mined until aborting. + * @return Transaction receipt with decoded log args. + */ + public async awaitTransactionMinedAsync( + txHash: string, + pollingIntervalMs = 1000, + timeoutMs?: number, + ): Promise<TransactionReceiptWithDecodedLogs> { + let timeoutExceeded = false; + if (timeoutMs) { + setTimeout(() => (timeoutExceeded = true), timeoutMs); + } - const txReceiptPromise = new Promise( - (resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => { - const intervalId = intervalUtils.setAsyncExcludingInterval( - async () => { - if (timeoutExceeded) { - intervalUtils.clearAsyncExcludingInterval(intervalId); - return reject(ZeroExError.TransactionMiningTimeout); - } + const txReceiptPromise = new Promise( + (resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => { + const intervalId = intervalUtils.setAsyncExcludingInterval( + async () => { + if (timeoutExceeded) { + intervalUtils.clearAsyncExcludingInterval(intervalId); + return reject(ZeroExError.TransactionMiningTimeout); + } - const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash); - if (!_.isNull(transactionReceipt)) { - intervalUtils.clearAsyncExcludingInterval(intervalId); - const logsWithDecodedArgs = _.map( - transactionReceipt.logs, - this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder), - ); - const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = { - ...transactionReceipt, - logs: logsWithDecodedArgs, - }; - resolve(transactionReceiptWithDecodedLogArgs); - } - }, - pollingIntervalMs, - (err: Error) => { - intervalUtils.clearAsyncExcludingInterval(intervalId); - reject(err); - }, - ); - }, - ); + const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash); + if (!_.isNull(transactionReceipt)) { + intervalUtils.clearAsyncExcludingInterval(intervalId); + const logsWithDecodedArgs = _.map( + transactionReceipt.logs, + this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder), + ); + const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = { + ...transactionReceipt, + logs: logsWithDecodedArgs, + }; + resolve(transactionReceiptWithDecodedLogArgs); + } + }, + pollingIntervalMs, + (err: Error) => { + intervalUtils.clearAsyncExcludingInterval(intervalId); + reject(err); + }, + ); + }, + ); - return txReceiptPromise; - } - /* + return txReceiptPromise; + } + /* * HACK: `TokenWrapper` needs a token transfer proxy address. `TokenTransferProxy` address is fetched from * an `ExchangeWrapper`. `ExchangeWrapper` needs `TokenWrapper` to validate orders, creating a dependency cycle. * In order to break this - we create this function here and pass it as a parameter to the `TokenWrapper` * and `ProxyWrapper`. */ - private async _getTokenTransferProxyAddressAsync(): Promise<string> { - const tokenTransferProxyAddress = await (this.exchange as any)._getTokenTransferProxyAddressAsync(); - return tokenTransferProxyAddress; - } + private async _getTokenTransferProxyAddressAsync(): Promise<string> { + const tokenTransferProxyAddress = await (this.exchange as any)._getTokenTransferProxyAddressAsync(); + return tokenTransferProxyAddress; + } } diff --git a/packages/0x.js/src/artifacts.ts b/packages/0x.js/src/artifacts.ts index 7d4bd6757..cbacd7d56 100644 --- a/packages/0x.js/src/artifacts.ts +++ b/packages/0x.js/src/artifacts.ts @@ -8,11 +8,11 @@ import * as ZRXArtifact from './artifacts/ZRX.json'; import { Artifact } from './types'; export const artifacts = { - ZRXArtifact: (ZRXArtifact as any) as Artifact, - DummyTokenArtifact: (DummyTokenArtifact as any) as Artifact, - TokenArtifact: (TokenArtifact as any) as Artifact, - ExchangeArtifact: (ExchangeArtifact as any) as Artifact, - EtherTokenArtifact: (EtherTokenArtifact as any) as Artifact, - TokenRegistryArtifact: (TokenRegistryArtifact as any) as Artifact, - TokenTransferProxyArtifact: (TokenTransferProxyArtifact as any) as Artifact, + ZRXArtifact: (ZRXArtifact as any) as Artifact, + DummyTokenArtifact: (DummyTokenArtifact as any) as Artifact, + TokenArtifact: (TokenArtifact as any) as Artifact, + ExchangeArtifact: (ExchangeArtifact as any) as Artifact, + EtherTokenArtifact: (EtherTokenArtifact as any) as Artifact, + TokenRegistryArtifact: (TokenRegistryArtifact as any) as Artifact, + TokenTransferProxyArtifact: (TokenTransferProxyArtifact as any) as Artifact, }; diff --git a/packages/0x.js/src/artifacts/DummyToken.json b/packages/0x.js/src/artifacts/DummyToken.json index f96d63c21..f64a8cd3d 100644 --- a/packages/0x.js/src/artifacts/DummyToken.json +++ b/packages/0x.js/src/artifacts/DummyToken.json @@ -1,22 +1,22 @@ { - "contract_name": "DummyToken", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_target", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "setBalance", - "outputs": [], - "payable": false, - "type": "function" - } - ] + "contract_name": "DummyToken", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_target", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "setBalance", + "outputs": [], + "payable": false, + "type": "function" + } + ] } diff --git a/packages/0x.js/src/artifacts/EtherToken.json b/packages/0x.js/src/artifacts/EtherToken.json index ca5f9c035..26cca57cd 100644 --- a/packages/0x.js/src/artifacts/EtherToken.json +++ b/packages/0x.js/src/artifacts/EtherToken.json @@ -1,287 +1,287 @@ { - "contract_name": "EtherToken", - "abi": [ - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "amount", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "", - "type": "uint8" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "deposit", - "outputs": [], - "payable": true, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "payable": true, - "type": "fallback" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": true, - "name": "_spender", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Deposit", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Withdrawal", - "type": "event" - } - ], - "networks": { - "1": { - "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "3": { - "address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a" - }, - "4": { - "address": "0xc778417e063141139fce010982780140aa0cd5ab" - }, - "42": { - "address": "0x653e49e301e508a13237c0ddc98ae7d4cd2667a1" - }, - "50": { - "address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c" - } - } + "contract_name": "EtherToken", + "abi": [ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "deposit", + "outputs": [], + "payable": true, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "payable": true, + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_spender", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + } + ], + "networks": { + "1": { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "3": { + "address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a" + }, + "4": { + "address": "0xc778417e063141139fce010982780140aa0cd5ab" + }, + "42": { + "address": "0x653e49e301e508a13237c0ddc98ae7d4cd2667a1" + }, + "50": { + "address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c" + } + } } diff --git a/packages/0x.js/src/artifacts/Exchange.json b/packages/0x.js/src/artifacts/Exchange.json index 44bb41d4d..af8db7360 100644 --- a/packages/0x.js/src/artifacts/Exchange.json +++ b/packages/0x.js/src/artifacts/Exchange.json @@ -1,610 +1,610 @@ { - "contract_name": "Exchange", - "abi": [ - { - "constant": true, - "inputs": [ - { - "name": "numerator", - "type": "uint256" - }, - { - "name": "denominator", - "type": "uint256" - }, - { - "name": "target", - "type": "uint256" - } - ], - "name": "isRoundingError", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "name": "filled", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "name": "cancelled", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "fillTakerTokenAmount", - "type": "uint256" - }, - { - "name": "shouldThrowOnInsufficientBalanceOrAllowance", - "type": "bool" - }, - { - "name": "v", - "type": "uint8[]" - }, - { - "name": "r", - "type": "bytes32[]" - }, - { - "name": "s", - "type": "bytes32[]" - } - ], - "name": "fillOrdersUpTo", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - }, - { - "name": "cancelTakerTokenAmount", - "type": "uint256" - } - ], - "name": "cancelOrder", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "ZRX_TOKEN_CONTRACT", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "fillTakerTokenAmounts", - "type": "uint256[]" - }, - { - "name": "v", - "type": "uint8[]" - }, - { - "name": "r", - "type": "bytes32[]" - }, - { - "name": "s", - "type": "bytes32[]" - } - ], - "name": "batchFillOrKillOrders", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - }, - { - "name": "fillTakerTokenAmount", - "type": "uint256" - }, - { - "name": "v", - "type": "uint8" - }, - { - "name": "r", - "type": "bytes32" - }, - { - "name": "s", - "type": "bytes32" - } - ], - "name": "fillOrKillOrder", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "orderHash", - "type": "bytes32" - } - ], - "name": "getUnavailableTakerTokenAmount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "signer", - "type": "address" - }, - { - "name": "hash", - "type": "bytes32" - }, - { - "name": "v", - "type": "uint8" - }, - { - "name": "r", - "type": "bytes32" - }, - { - "name": "s", - "type": "bytes32" - } - ], - "name": "isValidSignature", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "numerator", - "type": "uint256" - }, - { - "name": "denominator", - "type": "uint256" - }, - { - "name": "target", - "type": "uint256" - } - ], - "name": "getPartialAmount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "TOKEN_TRANSFER_PROXY_CONTRACT", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "fillTakerTokenAmounts", - "type": "uint256[]" - }, - { - "name": "shouldThrowOnInsufficientBalanceOrAllowance", - "type": "bool" - }, - { - "name": "v", - "type": "uint8[]" - }, - { - "name": "r", - "type": "bytes32[]" - }, - { - "name": "s", - "type": "bytes32[]" - } - ], - "name": "batchFillOrders", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "cancelTakerTokenAmounts", - "type": "uint256[]" - } - ], - "name": "batchCancelOrders", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - }, - { - "name": "fillTakerTokenAmount", - "type": "uint256" - }, - { - "name": "shouldThrowOnInsufficientBalanceOrAllowance", - "type": "bool" - }, - { - "name": "v", - "type": "uint8" - }, - { - "name": "r", - "type": "bytes32" - }, - { - "name": "s", - "type": "bytes32" - } - ], - "name": "fillOrder", - "outputs": [ - { - "name": "filledTakerTokenAmount", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - } - ], - "name": "getOrderHash", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "EXTERNAL_QUERY_GAS_LIMIT", - "outputs": [ - { - "name": "", - "type": "uint16" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "VERSION", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "type": "function" - }, - { - "inputs": [ - { - "name": "_zrxToken", - "type": "address" - }, - { - "name": "_tokenTransferProxy", - "type": "address" - } - ], - "payable": false, - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "maker", - "type": "address" - }, - { - "indexed": false, - "name": "taker", - "type": "address" - }, - { - "indexed": true, - "name": "feeRecipient", - "type": "address" - }, - { - "indexed": false, - "name": "makerToken", - "type": "address" - }, - { - "indexed": false, - "name": "takerToken", - "type": "address" - }, - { - "indexed": false, - "name": "filledMakerTokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "filledTakerTokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "paidMakerFee", - "type": "uint256" - }, - { - "indexed": false, - "name": "paidTakerFee", - "type": "uint256" - }, - { - "indexed": true, - "name": "tokens", - "type": "bytes32" - }, - { - "indexed": false, - "name": "orderHash", - "type": "bytes32" - } - ], - "name": "LogFill", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "maker", - "type": "address" - }, - { - "indexed": true, - "name": "feeRecipient", - "type": "address" - }, - { - "indexed": false, - "name": "makerToken", - "type": "address" - }, - { - "indexed": false, - "name": "takerToken", - "type": "address" - }, - { - "indexed": false, - "name": "cancelledMakerTokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "cancelledTakerTokenAmount", - "type": "uint256" - }, - { - "indexed": true, - "name": "tokens", - "type": "bytes32" - }, - { - "indexed": false, - "name": "orderHash", - "type": "bytes32" - } - ], - "name": "LogCancel", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "errorId", - "type": "uint8" - }, - { - "indexed": true, - "name": "orderHash", - "type": "bytes32" - } - ], - "name": "LogError", - "type": "event" - } - ], - "networks": { - "1": { - "address": "0x12459c951127e0c374ff9105dda097662a027093" - }, - "3": { - "address": "0x479cc461fecd078f766ecc58533d6f69580cf3ac" - }, - "4": { - "address": "0x1d16ef40fac01cec8adac2ac49427b9384192c05" - }, - "42": { - "address": "0x90fe2af704b34e0224bf2299c838e04d4dcf1364" - }, - "50": { - "address": "0x48bacb9266a570d521063ef5dd96e61686dbe788" - } - } + "contract_name": "Exchange", + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "numerator", + "type": "uint256" + }, + { + "name": "denominator", + "type": "uint256" + }, + { + "name": "target", + "type": "uint256" + } + ], + "name": "isRoundingError", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "name": "filled", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "name": "cancelled", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "fillTakerTokenAmount", + "type": "uint256" + }, + { + "name": "shouldThrowOnInsufficientBalanceOrAllowance", + "type": "bool" + }, + { + "name": "v", + "type": "uint8[]" + }, + { + "name": "r", + "type": "bytes32[]" + }, + { + "name": "s", + "type": "bytes32[]" + } + ], + "name": "fillOrdersUpTo", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + }, + { + "name": "cancelTakerTokenAmount", + "type": "uint256" + } + ], + "name": "cancelOrder", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ZRX_TOKEN_CONTRACT", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "fillTakerTokenAmounts", + "type": "uint256[]" + }, + { + "name": "v", + "type": "uint8[]" + }, + { + "name": "r", + "type": "bytes32[]" + }, + { + "name": "s", + "type": "bytes32[]" + } + ], + "name": "batchFillOrKillOrders", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + }, + { + "name": "fillTakerTokenAmount", + "type": "uint256" + }, + { + "name": "v", + "type": "uint8" + }, + { + "name": "r", + "type": "bytes32" + }, + { + "name": "s", + "type": "bytes32" + } + ], + "name": "fillOrKillOrder", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "getUnavailableTakerTokenAmount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "signer", + "type": "address" + }, + { + "name": "hash", + "type": "bytes32" + }, + { + "name": "v", + "type": "uint8" + }, + { + "name": "r", + "type": "bytes32" + }, + { + "name": "s", + "type": "bytes32" + } + ], + "name": "isValidSignature", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "numerator", + "type": "uint256" + }, + { + "name": "denominator", + "type": "uint256" + }, + { + "name": "target", + "type": "uint256" + } + ], + "name": "getPartialAmount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "TOKEN_TRANSFER_PROXY_CONTRACT", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "fillTakerTokenAmounts", + "type": "uint256[]" + }, + { + "name": "shouldThrowOnInsufficientBalanceOrAllowance", + "type": "bool" + }, + { + "name": "v", + "type": "uint8[]" + }, + { + "name": "r", + "type": "bytes32[]" + }, + { + "name": "s", + "type": "bytes32[]" + } + ], + "name": "batchFillOrders", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "cancelTakerTokenAmounts", + "type": "uint256[]" + } + ], + "name": "batchCancelOrders", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + }, + { + "name": "fillTakerTokenAmount", + "type": "uint256" + }, + { + "name": "shouldThrowOnInsufficientBalanceOrAllowance", + "type": "bool" + }, + { + "name": "v", + "type": "uint8" + }, + { + "name": "r", + "type": "bytes32" + }, + { + "name": "s", + "type": "bytes32" + } + ], + "name": "fillOrder", + "outputs": [ + { + "name": "filledTakerTokenAmount", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + } + ], + "name": "getOrderHash", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "EXTERNAL_QUERY_GAS_LIMIT", + "outputs": [ + { + "name": "", + "type": "uint16" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "type": "function" + }, + { + "inputs": [ + { + "name": "_zrxToken", + "type": "address" + }, + { + "name": "_tokenTransferProxy", + "type": "address" + } + ], + "payable": false, + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "maker", + "type": "address" + }, + { + "indexed": false, + "name": "taker", + "type": "address" + }, + { + "indexed": true, + "name": "feeRecipient", + "type": "address" + }, + { + "indexed": false, + "name": "makerToken", + "type": "address" + }, + { + "indexed": false, + "name": "takerToken", + "type": "address" + }, + { + "indexed": false, + "name": "filledMakerTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "filledTakerTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "paidMakerFee", + "type": "uint256" + }, + { + "indexed": false, + "name": "paidTakerFee", + "type": "uint256" + }, + { + "indexed": true, + "name": "tokens", + "type": "bytes32" + }, + { + "indexed": false, + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "LogFill", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "maker", + "type": "address" + }, + { + "indexed": true, + "name": "feeRecipient", + "type": "address" + }, + { + "indexed": false, + "name": "makerToken", + "type": "address" + }, + { + "indexed": false, + "name": "takerToken", + "type": "address" + }, + { + "indexed": false, + "name": "cancelledMakerTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "cancelledTakerTokenAmount", + "type": "uint256" + }, + { + "indexed": true, + "name": "tokens", + "type": "bytes32" + }, + { + "indexed": false, + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "LogCancel", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "errorId", + "type": "uint8" + }, + { + "indexed": true, + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "LogError", + "type": "event" + } + ], + "networks": { + "1": { + "address": "0x12459c951127e0c374ff9105dda097662a027093" + }, + "3": { + "address": "0x479cc461fecd078f766ecc58533d6f69580cf3ac" + }, + "4": { + "address": "0x1d16ef40fac01cec8adac2ac49427b9384192c05" + }, + "42": { + "address": "0x90fe2af704b34e0224bf2299c838e04d4dcf1364" + }, + "50": { + "address": "0x48bacb9266a570d521063ef5dd96e61686dbe788" + } + } } diff --git a/packages/0x.js/src/artifacts/Token.json b/packages/0x.js/src/artifacts/Token.json index bd4208275..3b5a86ae0 100644 --- a/packages/0x.js/src/artifacts/Token.json +++ b/packages/0x.js/src/artifacts/Token.json @@ -1,172 +1,172 @@ { - "contract_name": "Token", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "success", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "supply", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "success", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "balance", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "success", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "remaining", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": true, - "name": "_spender", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - } - ] + "contract_name": "Token", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "supply", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "remaining", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_spender", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + } + ] } diff --git a/packages/0x.js/src/artifacts/TokenRegistry.json b/packages/0x.js/src/artifacts/TokenRegistry.json index 490ea0c34..0f583628c 100644 --- a/packages/0x.js/src/artifacts/TokenRegistry.json +++ b/packages/0x.js/src/artifacts/TokenRegistry.json @@ -1,547 +1,547 @@ { - "contract_name": "TokenRegistry", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_index", - "type": "uint256" - } - ], - "name": "removeToken", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_name", - "type": "string" - } - ], - "name": "getTokenAddressByName", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_symbol", - "type": "string" - } - ], - "name": "getTokenAddressBySymbol", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_swarmHash", - "type": "bytes" - } - ], - "name": "setTokenSwarmHash", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_token", - "type": "address" - } - ], - "name": "getTokenMetaData", - "outputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "uint8" - }, - { - "name": "", - "type": "bytes" - }, - { - "name": "", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_name", - "type": "string" - }, - { - "name": "_symbol", - "type": "string" - }, - { - "name": "_decimals", - "type": "uint8" - }, - { - "name": "_ipfsHash", - "type": "bytes" - }, - { - "name": "_swarmHash", - "type": "bytes" - } - ], - "name": "addToken", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_name", - "type": "string" - } - ], - "name": "setTokenName", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "tokens", - "outputs": [ - { - "name": "token", - "type": "address" - }, - { - "name": "name", - "type": "string" - }, - { - "name": "symbol", - "type": "string" - }, - { - "name": "decimals", - "type": "uint8" - }, - { - "name": "ipfsHash", - "type": "bytes" - }, - { - "name": "swarmHash", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "tokenAddresses", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_name", - "type": "string" - } - ], - "name": "getTokenByName", - "outputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "uint8" - }, - { - "name": "", - "type": "bytes" - }, - { - "name": "", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getTokenAddresses", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_ipfsHash", - "type": "bytes" - } - ], - "name": "setTokenIpfsHash", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_symbol", - "type": "string" - } - ], - "name": "getTokenBySymbol", - "outputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "uint8" - }, - { - "name": "", - "type": "bytes" - }, - { - "name": "", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_symbol", - "type": "string" - } - ], - "name": "setTokenSymbol", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "name", - "type": "string" - }, - { - "indexed": false, - "name": "symbol", - "type": "string" - }, - { - "indexed": false, - "name": "decimals", - "type": "uint8" - }, - { - "indexed": false, - "name": "ipfsHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "swarmHash", - "type": "bytes" - } - ], - "name": "LogAddToken", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "name", - "type": "string" - }, - { - "indexed": false, - "name": "symbol", - "type": "string" - }, - { - "indexed": false, - "name": "decimals", - "type": "uint8" - }, - { - "indexed": false, - "name": "ipfsHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "swarmHash", - "type": "bytes" - } - ], - "name": "LogRemoveToken", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldName", - "type": "string" - }, - { - "indexed": false, - "name": "newName", - "type": "string" - } - ], - "name": "LogTokenNameChange", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldSymbol", - "type": "string" - }, - { - "indexed": false, - "name": "newSymbol", - "type": "string" - } - ], - "name": "LogTokenSymbolChange", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldIpfsHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "newIpfsHash", - "type": "bytes" - } - ], - "name": "LogTokenIpfsHashChange", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldSwarmHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "newSwarmHash", - "type": "bytes" - } - ], - "name": "LogTokenSwarmHashChange", - "type": "event" - } - ], - "networks": { - "1": { - "address": "0x926a74c5c36adf004c87399e65f75628b0f98d2c" - }, - "3": { - "address": "0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed" - }, - "4": { - "address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6" - }, - "42": { - "address": "0xf18e504561f4347bea557f3d4558f559dddbae7f" - }, - "50": { - "address": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082" - } - } + "contract_name": "TokenRegistry", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_index", + "type": "uint256" + } + ], + "name": "removeToken", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_name", + "type": "string" + } + ], + "name": "getTokenAddressByName", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_symbol", + "type": "string" + } + ], + "name": "getTokenAddressBySymbol", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_swarmHash", + "type": "bytes" + } + ], + "name": "setTokenSwarmHash", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_token", + "type": "address" + } + ], + "name": "getTokenMetaData", + "outputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "uint8" + }, + { + "name": "", + "type": "bytes" + }, + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_name", + "type": "string" + }, + { + "name": "_symbol", + "type": "string" + }, + { + "name": "_decimals", + "type": "uint8" + }, + { + "name": "_ipfsHash", + "type": "bytes" + }, + { + "name": "_swarmHash", + "type": "bytes" + } + ], + "name": "addToken", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_name", + "type": "string" + } + ], + "name": "setTokenName", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "tokens", + "outputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + }, + { + "name": "decimals", + "type": "uint8" + }, + { + "name": "ipfsHash", + "type": "bytes" + }, + { + "name": "swarmHash", + "type": "bytes" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "tokenAddresses", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_name", + "type": "string" + } + ], + "name": "getTokenByName", + "outputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "uint8" + }, + { + "name": "", + "type": "bytes" + }, + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTokenAddresses", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_ipfsHash", + "type": "bytes" + } + ], + "name": "setTokenIpfsHash", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_symbol", + "type": "string" + } + ], + "name": "getTokenBySymbol", + "outputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "uint8" + }, + { + "name": "", + "type": "bytes" + }, + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_symbol", + "type": "string" + } + ], + "name": "setTokenSymbol", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "name", + "type": "string" + }, + { + "indexed": false, + "name": "symbol", + "type": "string" + }, + { + "indexed": false, + "name": "decimals", + "type": "uint8" + }, + { + "indexed": false, + "name": "ipfsHash", + "type": "bytes" + }, + { + "indexed": false, + "name": "swarmHash", + "type": "bytes" + } + ], + "name": "LogAddToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "name", + "type": "string" + }, + { + "indexed": false, + "name": "symbol", + "type": "string" + }, + { + "indexed": false, + "name": "decimals", + "type": "uint8" + }, + { + "indexed": false, + "name": "ipfsHash", + "type": "bytes" + }, + { + "indexed": false, + "name": "swarmHash", + "type": "bytes" + } + ], + "name": "LogRemoveToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "oldName", + "type": "string" + }, + { + "indexed": false, + "name": "newName", + "type": "string" + } + ], + "name": "LogTokenNameChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "oldSymbol", + "type": "string" + }, + { + "indexed": false, + "name": "newSymbol", + "type": "string" + } + ], + "name": "LogTokenSymbolChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "oldIpfsHash", + "type": "bytes" + }, + { + "indexed": false, + "name": "newIpfsHash", + "type": "bytes" + } + ], + "name": "LogTokenIpfsHashChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "oldSwarmHash", + "type": "bytes" + }, + { + "indexed": false, + "name": "newSwarmHash", + "type": "bytes" + } + ], + "name": "LogTokenSwarmHashChange", + "type": "event" + } + ], + "networks": { + "1": { + "address": "0x926a74c5c36adf004c87399e65f75628b0f98d2c" + }, + "3": { + "address": "0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed" + }, + "4": { + "address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6" + }, + "42": { + "address": "0xf18e504561f4347bea557f3d4558f559dddbae7f" + }, + "50": { + "address": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082" + } + } } diff --git a/packages/0x.js/src/artifacts/TokenTransferProxy.json b/packages/0x.js/src/artifacts/TokenTransferProxy.json index 5e45e4903..8cf551ddb 100644 --- a/packages/0x.js/src/artifacts/TokenTransferProxy.json +++ b/packages/0x.js/src/artifacts/TokenTransferProxy.json @@ -1,187 +1,187 @@ { - "contract_name": "TokenTransferProxy", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "token", - "type": "address" - }, - { - "name": "from", - "type": "address" - }, - { - "name": "to", - "type": "address" - }, - { - "name": "value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "target", - "type": "address" - } - ], - "name": "addAuthorizedAddress", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "authorities", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "target", - "type": "address" - } - ], - "name": "removeAuthorizedAddress", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "authorized", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getAuthorizedAddresses", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "target", - "type": "address" - }, - { - "indexed": true, - "name": "caller", - "type": "address" - } - ], - "name": "LogAuthorizedAddressAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "target", - "type": "address" - }, - { - "indexed": true, - "name": "caller", - "type": "address" - } - ], - "name": "LogAuthorizedAddressRemoved", - "type": "event" - } - ], - "networks": { - "1": { - "address": "0x8da0d80f5007ef1e431dd2127178d224e32c2ef4" - }, - "3": { - "address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6" - }, - "4": { - "address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d" - }, - "42": { - "address": "0x087eed4bc1ee3de49befbd66c662b434b15d49d4" - }, - "50": { - "address": "0x1dc4c1cefef38a777b15aa20260a54e584b16c48" - } - } + "contract_name": "TokenTransferProxy", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "from", + "type": "address" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "addAuthorizedAddress", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "authorities", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "removeAuthorizedAddress", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "authorized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAuthorizedAddresses", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "LogAuthorizedAddressAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "LogAuthorizedAddressRemoved", + "type": "event" + } + ], + "networks": { + "1": { + "address": "0x8da0d80f5007ef1e431dd2127178d224e32c2ef4" + }, + "3": { + "address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6" + }, + "4": { + "address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d" + }, + "42": { + "address": "0x087eed4bc1ee3de49befbd66c662b434b15d49d4" + }, + "50": { + "address": "0x1dc4c1cefef38a777b15aa20260a54e584b16c48" + } + } } diff --git a/packages/0x.js/src/artifacts/ZRX.json b/packages/0x.js/src/artifacts/ZRX.json index 44f478ab4..e40b8f268 100644 --- a/packages/0x.js/src/artifacts/ZRX.json +++ b/packages/0x.js/src/artifacts/ZRX.json @@ -1,20 +1,20 @@ { - "contract_name": "ZRX", - "networks": { - "1": { - "address": "0xe41d2489571d322189246dafa5ebde1f4699f498" - }, - "3": { - "address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d" - }, - "4": { - "address": "0x00f58d6d585f84b2d7267940cede30ce2fe6eae8" - }, - "42": { - "address": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570" - }, - "50": { - "address": "0x1d7022f5b17d2f8b695918fb48fa1089c9f85401" - } - } + "contract_name": "ZRX", + "networks": { + "1": { + "address": "0xe41d2489571d322189246dafa5ebde1f4699f498" + }, + "3": { + "address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d" + }, + "4": { + "address": "0x00f58d6d585f84b2d7267940cede30ce2fe6eae8" + }, + "42": { + "address": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570" + }, + "50": { + "address": "0x1d7022f5b17d2f8b695918fb48fa1089c9f85401" + } + } } diff --git a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts index d1b753f45..27551c01d 100644 --- a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts @@ -5,203 +5,203 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; import { - Artifact, - BlockParamLiteral, - BlockRange, - ContractEventArgs, - ContractEvents, - EventCallback, - IndexedFilterValues, - InternalZeroExError, - LogWithDecodedArgs, - RawLog, - ZeroExError, + Artifact, + BlockParamLiteral, + BlockRange, + ContractEventArgs, + ContractEvents, + EventCallback, + IndexedFilterValues, + InternalZeroExError, + LogWithDecodedArgs, + RawLog, + ZeroExError, } from '../types'; import { AbiDecoder } from '../utils/abi_decoder'; import { constants } from '../utils/constants'; import { filterUtils } from '../utils/filter_utils'; const CONTRACT_NAME_TO_NOT_FOUND_ERROR: { - [contractName: string]: ZeroExError; + [contractName: string]: ZeroExError; } = { - ZRX: ZeroExError.ZRXContractDoesNotExist, - EtherToken: ZeroExError.EtherTokenContractDoesNotExist, - Token: ZeroExError.TokenContractDoesNotExist, - TokenRegistry: ZeroExError.TokenRegistryContractDoesNotExist, - TokenTransferProxy: ZeroExError.TokenTransferProxyContractDoesNotExist, - Exchange: ZeroExError.ExchangeContractDoesNotExist, + ZRX: ZeroExError.ZRXContractDoesNotExist, + EtherToken: ZeroExError.EtherTokenContractDoesNotExist, + Token: ZeroExError.TokenContractDoesNotExist, + TokenRegistry: ZeroExError.TokenRegistryContractDoesNotExist, + TokenTransferProxy: ZeroExError.TokenTransferProxyContractDoesNotExist, + Exchange: ZeroExError.ExchangeContractDoesNotExist, }; export class ContractWrapper { - protected _web3Wrapper: Web3Wrapper; - private _networkId: number; - private _abiDecoder?: AbiDecoder; - private _blockAndLogStreamerIfExists: BlockAndLogStreamer | undefined; - private _blockAndLogStreamInterval: NodeJS.Timer; - private _filters: { [filterToken: string]: Web3.FilterObject }; - private _filterCallbacks: { - [filterToken: string]: EventCallback<ContractEventArgs>; - }; - private _onLogAddedSubscriptionToken: string | undefined; - private _onLogRemovedSubscriptionToken: string | undefined; - constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder?: AbiDecoder) { - this._web3Wrapper = web3Wrapper; - this._networkId = networkId; - this._abiDecoder = abiDecoder; - this._filters = {}; - this._filterCallbacks = {}; - this._blockAndLogStreamerIfExists = undefined; - this._onLogAddedSubscriptionToken = undefined; - this._onLogRemovedSubscriptionToken = undefined; - } - protected unsubscribeAll(): void { - const filterTokens = _.keys(this._filterCallbacks); - _.each(filterTokens, filterToken => { - this._unsubscribe(filterToken); - }); - } - protected _unsubscribe(filterToken: string, err?: Error): void { - if (_.isUndefined(this._filters[filterToken])) { - throw new Error(ZeroExError.SubscriptionNotFound); - } - if (!_.isUndefined(err)) { - const callback = this._filterCallbacks[filterToken]; - callback(err, undefined); - } - delete this._filters[filterToken]; - delete this._filterCallbacks[filterToken]; - if (_.isEmpty(this._filters)) { - this._stopBlockAndLogStream(); - } - } - protected _subscribe<ArgsType extends ContractEventArgs>( - address: string, - eventName: ContractEvents, - indexFilterValues: IndexedFilterValues, - abi: Web3.ContractAbi, - callback: EventCallback<ArgsType>, - ): string { - const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi); - if (_.isUndefined(this._blockAndLogStreamerIfExists)) { - this._startBlockAndLogStream(); - } - const filterToken = filterUtils.generateUUID(); - this._filters[filterToken] = filter; - this._filterCallbacks[filterToken] = callback as EventCallback<ContractEventArgs>; - return filterToken; - } - protected async _getLogsAsync<ArgsType extends ContractEventArgs>( - address: string, - eventName: ContractEvents, - blockRange: BlockRange, - indexFilterValues: IndexedFilterValues, - abi: Web3.ContractAbi, - ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { - const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi, blockRange); - const logs = await this._web3Wrapper.getLogsAsync(filter); - const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this)); - return logsWithDecodedArguments; - } - protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>( - log: Web3.LogEntry, - ): LogWithDecodedArgs<ArgsType> | RawLog { - if (_.isUndefined(this._abiDecoder)) { - throw new Error(InternalZeroExError.NoAbiDecoder); - } - const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log); - return logWithDecodedArgs; - } - protected async _instantiateContractIfExistsAsync( - artifact: Artifact, - addressIfExists?: string, - ): Promise<Web3.ContractInstance> { - let contractAddress: string; - if (_.isUndefined(addressIfExists)) { - if (_.isUndefined(artifact.networks[this._networkId])) { - throw new Error(ZeroExError.ContractNotDeployedOnNetwork); - } - contractAddress = artifact.networks[this._networkId].address.toLowerCase(); - } else { - contractAddress = addressIfExists; - } - const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress); - if (!doesContractExist) { - throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contract_name]); - } - const contractInstance = this._web3Wrapper.getContractInstance(artifact.abi, contractAddress); - return contractInstance; - } - protected _getContractAddress(artifact: Artifact, addressIfExists?: string): string { - if (_.isUndefined(addressIfExists)) { - const contractAddress = artifact.networks[this._networkId].address; - if (_.isUndefined(contractAddress)) { - throw new Error(ZeroExError.ExchangeContractDoesNotExist); - } - return contractAddress; - } else { - return addressIfExists; - } - } - private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, log: Web3.LogEntry): void { - _.forEach(this._filters, (filter: Web3.FilterObject, filterToken: string) => { - if (filterUtils.matchesFilter(log, filter)) { - const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>; - const logEvent = { - log: decodedLog, - isRemoved, - }; - this._filterCallbacks[filterToken](null, logEvent); - } - }); - } - private _startBlockAndLogStream(): void { - if (!_.isUndefined(this._blockAndLogStreamerIfExists)) { - throw new Error(ZeroExError.SubscriptionAlreadyPresent); - } - this._blockAndLogStreamerIfExists = new BlockAndLogStreamer( - this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper), - this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper), - ); - const catchAllLogFilter = {}; - this._blockAndLogStreamerIfExists.addLogFilter(catchAllLogFilter); - this._blockAndLogStreamInterval = intervalUtils.setAsyncExcludingInterval( - this._reconcileBlockAsync.bind(this), - constants.DEFAULT_BLOCK_POLLING_INTERVAL, - this._onReconcileBlockError.bind(this), - ); - let isRemoved = false; - this._onLogAddedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogAdded( - this._onLogStateChanged.bind(this, isRemoved), - ); - isRemoved = true; - this._onLogRemovedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogRemoved( - this._onLogStateChanged.bind(this, isRemoved), - ); - } - private _onReconcileBlockError(err: Error): void { - const filterTokens = _.keys(this._filterCallbacks); - _.each(filterTokens, filterToken => { - this._unsubscribe(filterToken, err); - }); - } - private _setNetworkId(networkId: number): void { - this._networkId = networkId; - } - private _stopBlockAndLogStream(): void { - if (_.isUndefined(this._blockAndLogStreamerIfExists)) { - throw new Error(ZeroExError.SubscriptionNotFound); - } - this._blockAndLogStreamerIfExists.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string); - this._blockAndLogStreamerIfExists.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string); - intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval); - delete this._blockAndLogStreamerIfExists; - } - private async _reconcileBlockAsync(): Promise<void> { - const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest); - // We need to coerce to Block type cause Web3.Block includes types for mempool blocks - if (!_.isUndefined(this._blockAndLogStreamerIfExists)) { - // If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined - await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlock as any) as Block); - } - } + protected _web3Wrapper: Web3Wrapper; + private _networkId: number; + private _abiDecoder?: AbiDecoder; + private _blockAndLogStreamerIfExists: BlockAndLogStreamer | undefined; + private _blockAndLogStreamInterval: NodeJS.Timer; + private _filters: { [filterToken: string]: Web3.FilterObject }; + private _filterCallbacks: { + [filterToken: string]: EventCallback<ContractEventArgs>; + }; + private _onLogAddedSubscriptionToken: string | undefined; + private _onLogRemovedSubscriptionToken: string | undefined; + constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder?: AbiDecoder) { + this._web3Wrapper = web3Wrapper; + this._networkId = networkId; + this._abiDecoder = abiDecoder; + this._filters = {}; + this._filterCallbacks = {}; + this._blockAndLogStreamerIfExists = undefined; + this._onLogAddedSubscriptionToken = undefined; + this._onLogRemovedSubscriptionToken = undefined; + } + protected unsubscribeAll(): void { + const filterTokens = _.keys(this._filterCallbacks); + _.each(filterTokens, filterToken => { + this._unsubscribe(filterToken); + }); + } + protected _unsubscribe(filterToken: string, err?: Error): void { + if (_.isUndefined(this._filters[filterToken])) { + throw new Error(ZeroExError.SubscriptionNotFound); + } + if (!_.isUndefined(err)) { + const callback = this._filterCallbacks[filterToken]; + callback(err, undefined); + } + delete this._filters[filterToken]; + delete this._filterCallbacks[filterToken]; + if (_.isEmpty(this._filters)) { + this._stopBlockAndLogStream(); + } + } + protected _subscribe<ArgsType extends ContractEventArgs>( + address: string, + eventName: ContractEvents, + indexFilterValues: IndexedFilterValues, + abi: Web3.ContractAbi, + callback: EventCallback<ArgsType>, + ): string { + const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi); + if (_.isUndefined(this._blockAndLogStreamerIfExists)) { + this._startBlockAndLogStream(); + } + const filterToken = filterUtils.generateUUID(); + this._filters[filterToken] = filter; + this._filterCallbacks[filterToken] = callback as EventCallback<ContractEventArgs>; + return filterToken; + } + protected async _getLogsAsync<ArgsType extends ContractEventArgs>( + address: string, + eventName: ContractEvents, + blockRange: BlockRange, + indexFilterValues: IndexedFilterValues, + abi: Web3.ContractAbi, + ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { + const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi, blockRange); + const logs = await this._web3Wrapper.getLogsAsync(filter); + const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this)); + return logsWithDecodedArguments; + } + protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>( + log: Web3.LogEntry, + ): LogWithDecodedArgs<ArgsType> | RawLog { + if (_.isUndefined(this._abiDecoder)) { + throw new Error(InternalZeroExError.NoAbiDecoder); + } + const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log); + return logWithDecodedArgs; + } + protected async _instantiateContractIfExistsAsync( + artifact: Artifact, + addressIfExists?: string, + ): Promise<Web3.ContractInstance> { + let contractAddress: string; + if (_.isUndefined(addressIfExists)) { + if (_.isUndefined(artifact.networks[this._networkId])) { + throw new Error(ZeroExError.ContractNotDeployedOnNetwork); + } + contractAddress = artifact.networks[this._networkId].address.toLowerCase(); + } else { + contractAddress = addressIfExists; + } + const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress); + if (!doesContractExist) { + throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contract_name]); + } + const contractInstance = this._web3Wrapper.getContractInstance(artifact.abi, contractAddress); + return contractInstance; + } + protected _getContractAddress(artifact: Artifact, addressIfExists?: string): string { + if (_.isUndefined(addressIfExists)) { + const contractAddress = artifact.networks[this._networkId].address; + if (_.isUndefined(contractAddress)) { + throw new Error(ZeroExError.ExchangeContractDoesNotExist); + } + return contractAddress; + } else { + return addressIfExists; + } + } + private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, log: Web3.LogEntry): void { + _.forEach(this._filters, (filter: Web3.FilterObject, filterToken: string) => { + if (filterUtils.matchesFilter(log, filter)) { + const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>; + const logEvent = { + log: decodedLog, + isRemoved, + }; + this._filterCallbacks[filterToken](null, logEvent); + } + }); + } + private _startBlockAndLogStream(): void { + if (!_.isUndefined(this._blockAndLogStreamerIfExists)) { + throw new Error(ZeroExError.SubscriptionAlreadyPresent); + } + this._blockAndLogStreamerIfExists = new BlockAndLogStreamer( + this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper), + this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper), + ); + const catchAllLogFilter = {}; + this._blockAndLogStreamerIfExists.addLogFilter(catchAllLogFilter); + this._blockAndLogStreamInterval = intervalUtils.setAsyncExcludingInterval( + this._reconcileBlockAsync.bind(this), + constants.DEFAULT_BLOCK_POLLING_INTERVAL, + this._onReconcileBlockError.bind(this), + ); + let isRemoved = false; + this._onLogAddedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogAdded( + this._onLogStateChanged.bind(this, isRemoved), + ); + isRemoved = true; + this._onLogRemovedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogRemoved( + this._onLogStateChanged.bind(this, isRemoved), + ); + } + private _onReconcileBlockError(err: Error): void { + const filterTokens = _.keys(this._filterCallbacks); + _.each(filterTokens, filterToken => { + this._unsubscribe(filterToken, err); + }); + } + private _setNetworkId(networkId: number): void { + this._networkId = networkId; + } + private _stopBlockAndLogStream(): void { + if (_.isUndefined(this._blockAndLogStreamerIfExists)) { + throw new Error(ZeroExError.SubscriptionNotFound); + } + this._blockAndLogStreamerIfExists.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string); + this._blockAndLogStreamerIfExists.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string); + intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval); + delete this._blockAndLogStreamerIfExists; + } + private async _reconcileBlockAsync(): Promise<void> { + const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest); + // We need to coerce to Block type cause Web3.Block includes types for mempool blocks + if (!_.isUndefined(this._blockAndLogStreamerIfExists)) { + // If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined + await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlock as any) as Block); + } + } } diff --git a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts index d412971be..b03571636 100644 --- a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts @@ -5,14 +5,14 @@ import * as _ from 'lodash'; import { artifacts } from '../artifacts'; import { - BlockRange, - EtherTokenContractEventArgs, - EtherTokenEvents, - EventCallback, - IndexedFilterValues, - LogWithDecodedArgs, - TransactionOpts, - ZeroExError, + BlockRange, + EtherTokenContractEventArgs, + EtherTokenEvents, + EventCallback, + IndexedFilterValues, + LogWithDecodedArgs, + TransactionOpts, + ZeroExError, } from '../types'; import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; @@ -26,159 +26,159 @@ import { TokenWrapper } from './token_wrapper'; * The caller can convert ETH into the equivalent number of wrapped ETH ERC20 tokens and back. */ export class EtherTokenWrapper extends ContractWrapper { - private _etherTokenContractsByAddress: { - [address: string]: EtherTokenContract; - } = {}; - private _tokenWrapper: TokenWrapper; - constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder: AbiDecoder, tokenWrapper: TokenWrapper) { - super(web3Wrapper, networkId, abiDecoder); - this._tokenWrapper = tokenWrapper; - } - /** - * Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens - * to the depositor address. These wrapped ETH tokens can be used in 0x trades and are redeemable for 1-to-1 - * for ETH. - * @param etherTokenAddress EtherToken address you wish to deposit into. - * @param amountInWei Amount of ETH in Wei the caller wishes to deposit. - * @param depositor The hex encoded user Ethereum address that would like to make the deposit. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async depositAsync( - etherTokenAddress: string, - amountInWei: BigNumber, - depositor: string, - txOpts: TransactionOpts = {}, - ): Promise<string> { - assert.isValidBaseUnitAmount('amountInWei', amountInWei); - await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper); + private _etherTokenContractsByAddress: { + [address: string]: EtherTokenContract; + } = {}; + private _tokenWrapper: TokenWrapper; + constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder: AbiDecoder, tokenWrapper: TokenWrapper) { + super(web3Wrapper, networkId, abiDecoder); + this._tokenWrapper = tokenWrapper; + } + /** + * Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens + * to the depositor address. These wrapped ETH tokens can be used in 0x trades and are redeemable for 1-to-1 + * for ETH. + * @param etherTokenAddress EtherToken address you wish to deposit into. + * @param amountInWei Amount of ETH in Wei the caller wishes to deposit. + * @param depositor The hex encoded user Ethereum address that would like to make the deposit. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async depositAsync( + etherTokenAddress: string, + amountInWei: BigNumber, + depositor: string, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isValidBaseUnitAmount('amountInWei', amountInWei); + await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper); - const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(depositor); - assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit); + const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(depositor); + assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit); - const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress); - const txHash = await wethContract.deposit.sendTransactionAsync({ - from: depositor, - value: amountInWei, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); - return txHash; - } - /** - * Withdraw ETH to the withdrawer's address from the wrapped ETH smart contract in exchange for the - * equivalent number of wrapped ETH tokens. - * @param etherTokenAddress EtherToken address you wish to withdraw from. - * @param amountInWei Amount of ETH in Wei the caller wishes to withdraw. - * @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawl. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async withdrawAsync( - etherTokenAddress: string, - amountInWei: BigNumber, - withdrawer: string, - txOpts: TransactionOpts = {}, - ): Promise<string> { - assert.isValidBaseUnitAmount('amountInWei', amountInWei); - await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper); + const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress); + const txHash = await wethContract.deposit.sendTransactionAsync({ + from: depositor, + value: amountInWei, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }); + return txHash; + } + /** + * Withdraw ETH to the withdrawer's address from the wrapped ETH smart contract in exchange for the + * equivalent number of wrapped ETH tokens. + * @param etherTokenAddress EtherToken address you wish to withdraw from. + * @param amountInWei Amount of ETH in Wei the caller wishes to withdraw. + * @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawl. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async withdrawAsync( + etherTokenAddress: string, + amountInWei: BigNumber, + withdrawer: string, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isValidBaseUnitAmount('amountInWei', amountInWei); + await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper); - const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync(etherTokenAddress, withdrawer); - assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal); + const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync(etherTokenAddress, withdrawer); + assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal); - const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress); - const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, { - from: withdrawer, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); - return txHash; - } - /** - * Gets historical logs without creating a subscription - * @param etherTokenAddress An address of the ether token that emmited the logs. - * @param eventName The ether token contract event you would like to subscribe to. - * @param blockRange Block range to get logs from. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{_owner: aUserAddressHex}` - * @return Array of logs that match the parameters - */ - public async getLogsAsync<ArgsType extends EtherTokenContractEventArgs>( - etherTokenAddress: string, - eventName: EtherTokenEvents, - blockRange: BlockRange, - indexFilterValues: IndexedFilterValues, - ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { - assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); - assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); - assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - const logs = await this._getLogsAsync<ArgsType>( - etherTokenAddress, - eventName, - blockRange, - indexFilterValues, - artifacts.EtherTokenArtifact.abi, - ); - return logs; - } - /** - * Subscribe to an event type emitted by the Token contract. - * @param etherTokenAddress The hex encoded address where the ether token is deployed. - * @param eventName The ether token contract event you would like to subscribe to. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{_owner: aUserAddressHex}` - * @param callback Callback that gets called when a log is added/removed - * @return Subscription token used later to unsubscribe - */ - public subscribe<ArgsType extends EtherTokenContractEventArgs>( - etherTokenAddress: string, - eventName: EtherTokenEvents, - indexFilterValues: IndexedFilterValues, - callback: EventCallback<ArgsType>, - ): string { - assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); - assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - assert.isFunction('callback', callback); - const subscriptionToken = this._subscribe<ArgsType>( - etherTokenAddress, - eventName, - indexFilterValues, - artifacts.EtherTokenArtifact.abi, - callback, - ); - return subscriptionToken; - } - /** - * Cancel a subscription - * @param subscriptionToken Subscription token returned by `subscribe()` - */ - public unsubscribe(subscriptionToken: string): void { - this._unsubscribe(subscriptionToken); - } - /** - * Cancels all existing subscriptions - */ - public unsubscribeAll(): void { - super.unsubscribeAll(); - } - private _invalidateContractInstance(): void { - this.unsubscribeAll(); - this._etherTokenContractsByAddress = {}; - } - private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> { - let etherTokenContract = this._etherTokenContractsByAddress[etherTokenAddress]; - if (!_.isUndefined(etherTokenContract)) { - return etherTokenContract; - } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( - artifacts.EtherTokenArtifact, - etherTokenAddress, - ); - const contractInstance = new EtherTokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); - etherTokenContract = contractInstance; - this._etherTokenContractsByAddress[etherTokenAddress] = etherTokenContract; - return etherTokenContract; - } + const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress); + const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, { + from: withdrawer, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }); + return txHash; + } + /** + * Gets historical logs without creating a subscription + * @param etherTokenAddress An address of the ether token that emmited the logs. + * @param eventName The ether token contract event you would like to subscribe to. + * @param blockRange Block range to get logs from. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_owner: aUserAddressHex}` + * @return Array of logs that match the parameters + */ + public async getLogsAsync<ArgsType extends EtherTokenContractEventArgs>( + etherTokenAddress: string, + eventName: EtherTokenEvents, + blockRange: BlockRange, + indexFilterValues: IndexedFilterValues, + ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { + assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); + assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); + assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + const logs = await this._getLogsAsync<ArgsType>( + etherTokenAddress, + eventName, + blockRange, + indexFilterValues, + artifacts.EtherTokenArtifact.abi, + ); + return logs; + } + /** + * Subscribe to an event type emitted by the Token contract. + * @param etherTokenAddress The hex encoded address where the ether token is deployed. + * @param eventName The ether token contract event you would like to subscribe to. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_owner: aUserAddressHex}` + * @param callback Callback that gets called when a log is added/removed + * @return Subscription token used later to unsubscribe + */ + public subscribe<ArgsType extends EtherTokenContractEventArgs>( + etherTokenAddress: string, + eventName: EtherTokenEvents, + indexFilterValues: IndexedFilterValues, + callback: EventCallback<ArgsType>, + ): string { + assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); + assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + assert.isFunction('callback', callback); + const subscriptionToken = this._subscribe<ArgsType>( + etherTokenAddress, + eventName, + indexFilterValues, + artifacts.EtherTokenArtifact.abi, + callback, + ); + return subscriptionToken; + } + /** + * Cancel a subscription + * @param subscriptionToken Subscription token returned by `subscribe()` + */ + public unsubscribe(subscriptionToken: string): void { + this._unsubscribe(subscriptionToken); + } + /** + * Cancels all existing subscriptions + */ + public unsubscribeAll(): void { + super.unsubscribeAll(); + } + private _invalidateContractInstance(): void { + this.unsubscribeAll(); + this._etherTokenContractsByAddress = {}; + } + private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> { + let etherTokenContract = this._etherTokenContractsByAddress[etherTokenAddress]; + if (!_.isUndefined(etherTokenContract)) { + return etherTokenContract; + } + const web3ContractInstance = await this._instantiateContractIfExistsAsync( + artifacts.EtherTokenArtifact, + etherTokenAddress, + ); + const contractInstance = new EtherTokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); + etherTokenContract = contractInstance; + this._etherTokenContractsByAddress[etherTokenAddress] = etherTokenContract; + return etherTokenContract; + } } diff --git a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts index 668893d45..2b6117729 100644 --- a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts @@ -6,27 +6,27 @@ import * as Web3 from 'web3'; import { artifacts } from '../artifacts'; import { - BlockParamLiteral, - BlockRange, - DecodedLogArgs, - ECSignature, - EventCallback, - ExchangeContractErrCodes, - ExchangeContractErrs, - ExchangeContractEventArgs, - ExchangeEvents, - IndexedFilterValues, - LogErrorContractEventArgs, - LogWithDecodedArgs, - MethodOpts, - Order, - OrderAddresses, - OrderCancellationRequest, - OrderFillRequest, - OrderTransactionOpts, - OrderValues, - SignedOrder, - ValidateOrderFillableOpts, + BlockParamLiteral, + BlockRange, + DecodedLogArgs, + ECSignature, + EventCallback, + ExchangeContractErrCodes, + ExchangeContractErrs, + ExchangeContractEventArgs, + ExchangeEvents, + IndexedFilterValues, + LogErrorContractEventArgs, + LogWithDecodedArgs, + MethodOpts, + Order, + OrderAddresses, + OrderCancellationRequest, + OrderFillRequest, + OrderTransactionOpts, + OrderValues, + SignedOrder, + ValidateOrderFillableOpts, } from '../types'; import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; @@ -42,7 +42,7 @@ import { TokenWrapper } from './token_wrapper'; const SHOULD_VALIDATE_BY_DEFAULT = true; interface ExchangeContractErrCodesToMsgs { - [exchangeContractErrCodes: number]: string; + [exchangeContractErrCodes: number]: string; } /** @@ -50,864 +50,864 @@ interface ExchangeContractErrCodesToMsgs { * events of the 0x Exchange smart contract. */ export class ExchangeWrapper extends ContractWrapper { - private _exchangeContractIfExists?: ExchangeContract; - private _orderValidationUtils: OrderValidationUtils; - private _tokenWrapper: TokenWrapper; - private _exchangeContractErrCodesToMsg: ExchangeContractErrCodesToMsgs = { - [ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.OrderFillExpired, - [ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.OrderFillExpired, - [ExchangeContractErrCodes.ERROR_FILL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero, - [ExchangeContractErrCodes.ERROR_CANCEL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero, - [ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.OrderFillRoundingError, - [ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FillBalanceAllowanceError, - }; - private _contractAddressIfExists?: string; - private _zrxContractAddressIfExists?: string; - private static _getOrderAddressesAndValues(order: Order): [OrderAddresses, OrderValues] { - const orderAddresses: OrderAddresses = [ - order.maker, - order.taker, - order.makerTokenAddress, - order.takerTokenAddress, - order.feeRecipient, - ]; - const orderValues: OrderValues = [ - order.makerTokenAmount, - order.takerTokenAmount, - order.makerFee, - order.takerFee, - order.expirationUnixTimestampSec, - order.salt, - ]; - return [orderAddresses, orderValues]; - } - constructor( - web3Wrapper: Web3Wrapper, - networkId: number, - abiDecoder: AbiDecoder, - tokenWrapper: TokenWrapper, - contractAddressIfExists?: string, - zrxContractAddressIfExists?: string, - ) { - super(web3Wrapper, networkId, abiDecoder); - this._tokenWrapper = tokenWrapper; - this._orderValidationUtils = new OrderValidationUtils(this); - this._contractAddressIfExists = contractAddressIfExists; - this._zrxContractAddressIfExists = zrxContractAddressIfExists; - } - /** - * Returns the unavailable takerAmount of an order. Unavailable amount is defined as the total - * amount that has been filled or cancelled. The remaining takerAmount can be calculated by - * subtracting the unavailable amount from the total order takerAmount. - * @param orderHash The hex encoded orderHash for which you would like to retrieve the - * unavailable takerAmount. - * @param methodOpts Optional arguments this method accepts. - * @return The amount of the order (in taker tokens) that has either been filled or cancelled. - */ - public async getUnavailableTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); + private _exchangeContractIfExists?: ExchangeContract; + private _orderValidationUtils: OrderValidationUtils; + private _tokenWrapper: TokenWrapper; + private _exchangeContractErrCodesToMsg: ExchangeContractErrCodesToMsgs = { + [ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.OrderFillExpired, + [ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.OrderFillExpired, + [ExchangeContractErrCodes.ERROR_FILL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero, + [ExchangeContractErrCodes.ERROR_CANCEL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero, + [ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.OrderFillRoundingError, + [ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FillBalanceAllowanceError, + }; + private _contractAddressIfExists?: string; + private _zrxContractAddressIfExists?: string; + private static _getOrderAddressesAndValues(order: Order): [OrderAddresses, OrderValues] { + const orderAddresses: OrderAddresses = [ + order.maker, + order.taker, + order.makerTokenAddress, + order.takerTokenAddress, + order.feeRecipient, + ]; + const orderValues: OrderValues = [ + order.makerTokenAmount, + order.takerTokenAmount, + order.makerFee, + order.takerFee, + order.expirationUnixTimestampSec, + order.salt, + ]; + return [orderAddresses, orderValues]; + } + constructor( + web3Wrapper: Web3Wrapper, + networkId: number, + abiDecoder: AbiDecoder, + tokenWrapper: TokenWrapper, + contractAddressIfExists?: string, + zrxContractAddressIfExists?: string, + ) { + super(web3Wrapper, networkId, abiDecoder); + this._tokenWrapper = tokenWrapper; + this._orderValidationUtils = new OrderValidationUtils(this); + this._contractAddressIfExists = contractAddressIfExists; + this._zrxContractAddressIfExists = zrxContractAddressIfExists; + } + /** + * Returns the unavailable takerAmount of an order. Unavailable amount is defined as the total + * amount that has been filled or cancelled. The remaining takerAmount can be calculated by + * subtracting the unavailable amount from the total order takerAmount. + * @param orderHash The hex encoded orderHash for which you would like to retrieve the + * unavailable takerAmount. + * @param methodOpts Optional arguments this method accepts. + * @return The amount of the order (in taker tokens) that has either been filled or cancelled. + */ + public async getUnavailableTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { + assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const exchangeContract = await this._getExchangeContractAsync(); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.callAsync( - orderHash, - defaultBlock, - ); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - unavailableTakerTokenAmount = new BigNumber(unavailableTakerTokenAmount); - return unavailableTakerTokenAmount; - } - /** - * Retrieve the takerAmount of an order that has already been filled. - * @param orderHash The hex encoded orderHash for which you would like to retrieve the filled takerAmount. - * @param methodOpts Optional arguments this method accepts. - * @return The amount of the order (in taker tokens) that has already been filled. - */ - public async getFilledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); + const exchangeContract = await this._getExchangeContractAsync(); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.callAsync( + orderHash, + defaultBlock, + ); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + unavailableTakerTokenAmount = new BigNumber(unavailableTakerTokenAmount); + return unavailableTakerTokenAmount; + } + /** + * Retrieve the takerAmount of an order that has already been filled. + * @param orderHash The hex encoded orderHash for which you would like to retrieve the filled takerAmount. + * @param methodOpts Optional arguments this method accepts. + * @return The amount of the order (in taker tokens) that has already been filled. + */ + public async getFilledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { + assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const exchangeContract = await this._getExchangeContractAsync(); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash, defaultBlock); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - fillAmountInBaseUnits = new BigNumber(fillAmountInBaseUnits); - return fillAmountInBaseUnits; - } - /** - * Retrieve the takerAmount of an order that has been cancelled. - * @param orderHash The hex encoded orderHash for which you would like to retrieve the - * cancelled takerAmount. - * @param methodOpts Optional arguments this method accepts. - * @return The amount of the order (in taker tokens) that has been cancelled. - */ - public async getCancelledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); + const exchangeContract = await this._getExchangeContractAsync(); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash, defaultBlock); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + fillAmountInBaseUnits = new BigNumber(fillAmountInBaseUnits); + return fillAmountInBaseUnits; + } + /** + * Retrieve the takerAmount of an order that has been cancelled. + * @param orderHash The hex encoded orderHash for which you would like to retrieve the + * cancelled takerAmount. + * @param methodOpts Optional arguments this method accepts. + * @return The amount of the order (in taker tokens) that has been cancelled. + */ + public async getCancelledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { + assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const exchangeContract = await this._getExchangeContractAsync(); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash, defaultBlock); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - cancelledAmountInBaseUnits = new BigNumber(cancelledAmountInBaseUnits); - return cancelledAmountInBaseUnits; - } - /** - * Fills a signed order with an amount denominated in baseUnits of the taker token. - * Since the order in which transactions are included in the next block is indeterminate, race-conditions - * could arise where a users balance or allowance changes before the fillOrder executes. Because of this, - * we allow you to specify `shouldThrowOnInsufficientBalanceOrAllowance`. - * If false, the smart contract will not throw if the parties - * do not have sufficient balances/allowances, preserving gas costs. Setting it to true forgoes this check - * and causes the smart contract to throw (using all the gas supplied) instead. - * @param signedOrder An object that conforms to the SignedOrder interface. - * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that - * you wish to fill. - * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw - * if upon execution the tokens cannot be transferred. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Web3.Provider - * passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async fillOrderAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const exchangeContract = await this._getExchangeContractAsync(); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash, defaultBlock); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + cancelledAmountInBaseUnits = new BigNumber(cancelledAmountInBaseUnits); + return cancelledAmountInBaseUnits; + } + /** + * Fills a signed order with an amount denominated in baseUnits of the taker token. + * Since the order in which transactions are included in the next block is indeterminate, race-conditions + * could arise where a users balance or allowance changes before the fillOrder executes. Because of this, + * we allow you to specify `shouldThrowOnInsufficientBalanceOrAllowance`. + * If false, the smart contract will not throw if the parties + * do not have sufficient balances/allowances, preserving gas costs. Setting it to true forgoes this check + * and causes the smart contract to throw (using all the gas supplied) instead. + * @param signedOrder An object that conforms to the SignedOrder interface. + * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that + * you wish to fill. + * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw + * if upon execution the tokens cannot be transferred. + * @param takerAddress The user Ethereum address who would like to fill this order. + * Must be available via the supplied Web3.Provider + * passed to 0x.js. + * @param orderTransactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async fillOrderAsync( + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + shouldThrowOnInsufficientBalanceOrAllowance: boolean, + takerAddress: string, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const exchangeInstance = await this._getExchangeContractAsync(); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - } + const exchangeInstance = await this._getExchangeContractAsync(); + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + } - const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder); + const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder); - const txHash: string = await exchangeInstance.fillOrder.sendTransactionAsync( - orderAddresses, - orderValues, - fillTakerTokenAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - signedOrder.ecSignature.v, - signedOrder.ecSignature.r, - signedOrder.ecSignature.s, - { - from: takerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount. - * If the fill amount is reached - it succeeds and does not fill the rest of the orders. - * If fill amount is not reached - it fills as much of the fill amount as possible and succeeds. - * @param signedOrders The array of signedOrders that you would like to fill until - * takerTokenFillAmount is reached. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw if - * upon execution any of the tokens cannot be transferred. - * If set to false, the call will continue to fill subsequent - * signedOrders even when some cannot be filled. - * @param takerAddress The user Ethereum address who would like to fill these - * orders. Must be available via the supplied Web3.Provider - * passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async fillOrdersUpToAsync( - signedOrders: SignedOrder[], - fillTakerTokenAmount: BigNumber, - shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); - const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress); - assert.hasAtMostOneUniqueValue( - takerTokenAddresses, - ExchangeContractErrs.MultipleTakerTokensInFillUpToDisallowed, - ); - const exchangeContractAddresses = _.map(signedOrders, signedOrder => signedOrder.exchangeContractAddress); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const txHash: string = await exchangeInstance.fillOrder.sendTransactionAsync( + orderAddresses, + orderValues, + fillTakerTokenAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + signedOrder.ecSignature.v, + signedOrder.ecSignature.r, + signedOrder.ecSignature.s, + { + from: takerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount. + * If the fill amount is reached - it succeeds and does not fill the rest of the orders. + * If fill amount is not reached - it fills as much of the fill amount as possible and succeeds. + * @param signedOrders The array of signedOrders that you would like to fill until + * takerTokenFillAmount is reached. + * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. + * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw if + * upon execution any of the tokens cannot be transferred. + * If set to false, the call will continue to fill subsequent + * signedOrders even when some cannot be filled. + * @param takerAddress The user Ethereum address who would like to fill these + * orders. Must be available via the supplied Web3.Provider + * passed to 0x.js. + * @param orderTransactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async fillOrdersUpToAsync( + signedOrders: SignedOrder[], + fillTakerTokenAmount: BigNumber, + shouldThrowOnInsufficientBalanceOrAllowance: boolean, + takerAddress: string, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress); + assert.hasAtMostOneUniqueValue( + takerTokenAddresses, + ExchangeContractErrs.MultipleTakerTokensInFillUpToDisallowed, + ); + const exchangeContractAddresses = _.map(signedOrders, signedOrder => signedOrder.exchangeContractAddress); + assert.hasAtMostOneUniqueValue( + exchangeContractAddresses, + ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, + ); + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - let filledTakerTokenAmount = new BigNumber(0); - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - for (const signedOrder of signedOrders) { - const singleFilledTakerTokenAmount = await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount.minus(filledTakerTokenAmount), - takerAddress, - zrxTokenAddress, - ); - filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount); - } - } + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + let filledTakerTokenAmount = new BigNumber(0); + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + for (const signedOrder of signedOrders) { + const singleFilledTakerTokenAmount = await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount.minus(filledTakerTokenAmount), + takerAddress, + zrxTokenAddress, + ); + filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount); + } + } - if (_.isEmpty(signedOrders)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } + if (_.isEmpty(signedOrders)) { + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + } - const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => { - return [ - ...ExchangeWrapper._getOrderAddressesAndValues(signedOrder), - signedOrder.ecSignature.v, - signedOrder.ecSignature.r, - signedOrder.ecSignature.s, - ]; - }); - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddressesArray, orderValuesArray, vArray, rArray, sArray] = _.unzip<any>( - orderAddressesValuesAndSignatureArray, - ); + const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => { + return [ + ...ExchangeWrapper._getOrderAddressesAndValues(signedOrder), + signedOrder.ecSignature.v, + signedOrder.ecSignature.r, + signedOrder.ecSignature.s, + ]; + }); + // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( + const [orderAddressesArray, orderValuesArray, vArray, rArray, sArray] = _.unzip<any>( + orderAddressesValuesAndSignatureArray, + ); - const exchangeInstance = await this._getExchangeContractAsync(); - const txHash = await exchangeInstance.fillOrdersUpTo.sendTransactionAsync( - orderAddressesArray, - orderValuesArray, - fillTakerTokenAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - vArray, - rArray, - sArray, - { - from: takerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Batch version of fillOrderAsync. - * Executes multiple fills atomically in a single transaction. - * If shouldThrowOnInsufficientBalanceOrAllowance is set to false, it will continue filling subsequent orders even - * when earlier ones fail. - * When shouldThrowOnInsufficientBalanceOrAllowance is set to true, if any fill fails, the entire batch fails. - * @param orderFillRequests An array of objects that conform to the - * OrderFillRequest interface. - * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw - * if upon execution any of the tokens cannot be - * transferred. If set to false, the call will continue to - * fill subsequent signedOrders even when some - * cannot be filled. - * @param takerAddress The user Ethereum address who would like to fill - * these orders. Must be available via the supplied - * Web3.Provider passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async batchFillOrdersAsync( - orderFillRequests: OrderFillRequest[], - shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema); - const exchangeContractAddresses = _.map( - orderFillRequests, - orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress, - ); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - for (const orderFillRequest of orderFillRequests) { - await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - orderFillRequest.signedOrder, - orderFillRequest.takerTokenFillAmount, - takerAddress, - zrxTokenAddress, - ); - } - } - if (_.isEmpty(orderFillRequests)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } + const exchangeInstance = await this._getExchangeContractAsync(); + const txHash = await exchangeInstance.fillOrdersUpTo.sendTransactionAsync( + orderAddressesArray, + orderValuesArray, + fillTakerTokenAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + vArray, + rArray, + sArray, + { + from: takerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Batch version of fillOrderAsync. + * Executes multiple fills atomically in a single transaction. + * If shouldThrowOnInsufficientBalanceOrAllowance is set to false, it will continue filling subsequent orders even + * when earlier ones fail. + * When shouldThrowOnInsufficientBalanceOrAllowance is set to true, if any fill fails, the entire batch fails. + * @param orderFillRequests An array of objects that conform to the + * OrderFillRequest interface. + * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw + * if upon execution any of the tokens cannot be + * transferred. If set to false, the call will continue to + * fill subsequent signedOrders even when some + * cannot be filled. + * @param takerAddress The user Ethereum address who would like to fill + * these orders. Must be available via the supplied + * Web3.Provider passed to 0x.js. + * @param orderTransactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async batchFillOrdersAsync( + orderFillRequests: OrderFillRequest[], + shouldThrowOnInsufficientBalanceOrAllowance: boolean, + takerAddress: string, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema); + const exchangeContractAddresses = _.map( + orderFillRequests, + orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress, + ); + assert.hasAtMostOneUniqueValue( + exchangeContractAddresses, + ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, + ); + assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + for (const orderFillRequest of orderFillRequests) { + await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + orderFillRequest.signedOrder, + orderFillRequest.takerTokenFillAmount, + takerAddress, + zrxTokenAddress, + ); + } + } + if (_.isEmpty(orderFillRequests)) { + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + } - const orderAddressesValuesAmountsAndSignatureArray = _.map(orderFillRequests, orderFillRequest => { - return [ - ...ExchangeWrapper._getOrderAddressesAndValues(orderFillRequest.signedOrder), - orderFillRequest.takerTokenFillAmount, - orderFillRequest.signedOrder.ecSignature.v, - orderFillRequest.signedOrder.ecSignature.r, - orderFillRequest.signedOrder.ecSignature.s, - ]; - }); - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddressesArray, orderValuesArray, fillTakerTokenAmounts, vArray, rArray, sArray] = _.unzip<any>( - orderAddressesValuesAmountsAndSignatureArray, - ); + const orderAddressesValuesAmountsAndSignatureArray = _.map(orderFillRequests, orderFillRequest => { + return [ + ...ExchangeWrapper._getOrderAddressesAndValues(orderFillRequest.signedOrder), + orderFillRequest.takerTokenFillAmount, + orderFillRequest.signedOrder.ecSignature.v, + orderFillRequest.signedOrder.ecSignature.r, + orderFillRequest.signedOrder.ecSignature.s, + ]; + }); + // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( + const [orderAddressesArray, orderValuesArray, fillTakerTokenAmounts, vArray, rArray, sArray] = _.unzip<any>( + orderAddressesValuesAmountsAndSignatureArray, + ); - const exchangeInstance = await this._getExchangeContractAsync(); - const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync( - orderAddressesArray, - orderValuesArray, - fillTakerTokenAmounts, - shouldThrowOnInsufficientBalanceOrAllowance, - vArray, - rArray, - sArray, - { - from: takerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled, - * the fill order is abandoned. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to fill. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Web3.Provider passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async fillOrKillOrderAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const exchangeInstance = await this._getExchangeContractAsync(); + const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync( + orderAddressesArray, + orderValuesArray, + fillTakerTokenAmounts, + shouldThrowOnInsufficientBalanceOrAllowance, + vArray, + rArray, + sArray, + { + from: takerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled, + * the fill order is abandoned. + * @param signedOrder An object that conforms to the SignedOrder interface. The + * signedOrder you wish to fill. + * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. + * @param takerAddress The user Ethereum address who would like to fill this order. + * Must be available via the supplied Web3.Provider passed to 0x.js. + * @param orderTransactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async fillOrKillOrderAsync( + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const exchangeInstance = await this._getExchangeContractAsync(); + const exchangeInstance = await this._getExchangeContractAsync(); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - } + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + } - const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder); - const txHash = await exchangeInstance.fillOrKillOrder.sendTransactionAsync( - orderAddresses, - orderValues, - fillTakerTokenAmount, - signedOrder.ecSignature.v, - signedOrder.ecSignature.r, - signedOrder.ecSignature.s, - { - from: takerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Batch version of fillOrKill. Allows a taker to specify a batch of orders that will either be atomically - * filled (each to the specified fillAmount) or aborted. - * @param orderFillRequests An array of objects that conform to the OrderFillRequest interface. - * @param takerAddress The user Ethereum address who would like to fill there orders. - * Must be available via the supplied Web3.Provider passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async batchFillOrKillAsync( - orderFillRequests: OrderFillRequest[], - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema); - const exchangeContractAddresses = _.map( - orderFillRequests, - orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress, - ); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - if (_.isEmpty(orderFillRequests)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } - const exchangeInstance = await this._getExchangeContractAsync(); + const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder); + const txHash = await exchangeInstance.fillOrKillOrder.sendTransactionAsync( + orderAddresses, + orderValues, + fillTakerTokenAmount, + signedOrder.ecSignature.v, + signedOrder.ecSignature.r, + signedOrder.ecSignature.s, + { + from: takerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Batch version of fillOrKill. Allows a taker to specify a batch of orders that will either be atomically + * filled (each to the specified fillAmount) or aborted. + * @param orderFillRequests An array of objects that conform to the OrderFillRequest interface. + * @param takerAddress The user Ethereum address who would like to fill there orders. + * Must be available via the supplied Web3.Provider passed to 0x.js. + * @param orderTransactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async batchFillOrKillAsync( + orderFillRequests: OrderFillRequest[], + takerAddress: string, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema); + const exchangeContractAddresses = _.map( + orderFillRequests, + orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress, + ); + assert.hasAtMostOneUniqueValue( + exchangeContractAddresses, + ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, + ); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + if (_.isEmpty(orderFillRequests)) { + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + } + const exchangeInstance = await this._getExchangeContractAsync(); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - for (const orderFillRequest of orderFillRequests) { - await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - orderFillRequest.signedOrder, - orderFillRequest.takerTokenFillAmount, - takerAddress, - zrxTokenAddress, - ); - } - } + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + for (const orderFillRequest of orderFillRequests) { + await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + orderFillRequest.signedOrder, + orderFillRequest.takerTokenFillAmount, + takerAddress, + zrxTokenAddress, + ); + } + } - const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillRequests, request => { - return [ - ...ExchangeWrapper._getOrderAddressesAndValues(request.signedOrder), - request.takerTokenFillAmount, - request.signedOrder.ecSignature.v, - request.signedOrder.ecSignature.r, - request.signedOrder.ecSignature.s, - ]; - }); + const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillRequests, request => { + return [ + ...ExchangeWrapper._getOrderAddressesAndValues(request.signedOrder), + request.takerTokenFillAmount, + request.signedOrder.ecSignature.v, + request.signedOrder.ecSignature.r, + request.signedOrder.ecSignature.s, + ]; + }); - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddresses, orderValues, fillTakerTokenAmounts, vParams, rParams, sParams] = _.unzip<any>( - orderAddressesValuesAndTakerTokenFillAmounts, - ); - const txHash = await exchangeInstance.batchFillOrKillOrders.sendTransactionAsync( - orderAddresses, - orderValues, - fillTakerTokenAmounts, - vParams, - rParams, - sParams, - { - from: takerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Cancel a given fill amount of an order. Cancellations are cumulative. - * @param order An object that conforms to the Order or SignedOrder interface. - * The order you would like to cancel. - * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. - * @param transactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async cancelOrderAsync( - order: Order | SignedOrder, - cancelTakerTokenAmount: BigNumber, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('order', order, schemas.orderSchema); - assert.isValidBaseUnitAmount('takerTokenCancelAmount', cancelTakerTokenAmount); - await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper); + // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( + const [orderAddresses, orderValues, fillTakerTokenAmounts, vParams, rParams, sParams] = _.unzip<any>( + orderAddressesValuesAndTakerTokenFillAmounts, + ); + const txHash = await exchangeInstance.batchFillOrKillOrders.sendTransactionAsync( + orderAddresses, + orderValues, + fillTakerTokenAmounts, + vParams, + rParams, + sParams, + { + from: takerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Cancel a given fill amount of an order. Cancellations are cumulative. + * @param order An object that conforms to the Order or SignedOrder interface. + * The order you would like to cancel. + * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. + * @param transactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async cancelOrderAsync( + order: Order | SignedOrder, + cancelTakerTokenAmount: BigNumber, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('order', order, schemas.orderSchema); + assert.isValidBaseUnitAmount('takerTokenCancelAmount', cancelTakerTokenAmount); + await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper); - const exchangeInstance = await this._getExchangeContractAsync(); + const exchangeInstance = await this._getExchangeContractAsync(); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const orderHash = utils.getOrderHashHex(order); - const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils.validateCancelOrderThrowIfInvalid( - order, - cancelTakerTokenAmount, - unavailableTakerTokenAmount, - ); - } + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + const orderHash = utils.getOrderHashHex(order); + const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); + OrderValidationUtils.validateCancelOrderThrowIfInvalid( + order, + cancelTakerTokenAmount, + unavailableTakerTokenAmount, + ); + } - const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order); - const txHash = await exchangeInstance.cancelOrder.sendTransactionAsync( - orderAddresses, - orderValues, - cancelTakerTokenAmount, - { - from: order.maker, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Batch version of cancelOrderAsync. Atomically cancels multiple orders in a single transaction. - * All orders must be from the same maker. - * @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest - * interface. - * @param transactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async batchCancelOrdersAsync( - orderCancellationRequests: OrderCancellationRequest[], - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema( - 'orderCancellationRequests', - orderCancellationRequests, - schemas.orderCancellationRequestsSchema, - ); - const exchangeContractAddresses = _.map( - orderCancellationRequests, - orderCancellationRequest => orderCancellationRequest.order.exchangeContractAddress, - ); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - const makers = _.map(orderCancellationRequests, cancellationRequest => cancellationRequest.order.maker); - assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed); - const maker = makers[0]; - await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - for (const orderCancellationRequest of orderCancellationRequests) { - const orderHash = utils.getOrderHashHex(orderCancellationRequest.order); - const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils.validateCancelOrderThrowIfInvalid( - orderCancellationRequest.order, - orderCancellationRequest.takerTokenCancelAmount, - unavailableTakerTokenAmount, - ); - } - } - if (_.isEmpty(orderCancellationRequests)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } - const exchangeInstance = await this._getExchangeContractAsync(); - const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => { - return [ - ...ExchangeWrapper._getOrderAddressesAndValues(cancellationRequest.order), - cancellationRequest.takerTokenCancelAmount, - ]; - }); - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddresses, orderValues, cancelTakerTokenAmounts] = _.unzip<any>( - orderAddressesValuesAndTakerTokenCancelAmounts, - ); - const txHash = await exchangeInstance.batchCancelOrders.sendTransactionAsync( - orderAddresses, - orderValues, - cancelTakerTokenAmounts, - { - from: maker, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Subscribe to an event type emitted by the Exchange contract. - * @param eventName The exchange contract event you would like to subscribe to. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` - * @param callback Callback that gets called when a log is added/removed - * @return Subscription token used later to unsubscribe - */ - public subscribe<ArgsType extends ExchangeContractEventArgs>( - eventName: ExchangeEvents, - indexFilterValues: IndexedFilterValues, - callback: EventCallback<ArgsType>, - ): string { - assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - assert.isFunction('callback', callback); - const exchangeContractAddress = this.getContractAddress(); - const subscriptionToken = this._subscribe<ArgsType>( - exchangeContractAddress, - eventName, - indexFilterValues, - artifacts.ExchangeArtifact.abi, - callback, - ); - return subscriptionToken; - } - /** - * Cancel a subscription - * @param subscriptionToken Subscription token returned by `subscribe()` - */ - public unsubscribe(subscriptionToken: string): void { - this._unsubscribe(subscriptionToken); - } - /** - * Cancels all existing subscriptions - */ - public unsubscribeAll(): void { - super.unsubscribeAll(); - } - /** - * Gets historical logs without creating a subscription - * @param eventName The exchange contract event you would like to subscribe to. - * @param blockRange Block range to get logs from. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` - * @return Array of logs that match the parameters - */ - public async getLogsAsync<ArgsType extends ExchangeContractEventArgs>( - eventName: ExchangeEvents, - blockRange: BlockRange, - indexFilterValues: IndexedFilterValues, - ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { - assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); - assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - const exchangeContractAddress = this.getContractAddress(); - const logs = await this._getLogsAsync<ArgsType>( - exchangeContractAddress, - eventName, - blockRange, - indexFilterValues, - artifacts.ExchangeArtifact.abi, - ); - return logs; - } - /** - * Retrieves the Ethereum address of the Exchange contract deployed on the network - * that the user-passed web3 provider is connected to. - * @returns The Ethereum address of the Exchange contract being used. - */ - public getContractAddress(): string { - const contractAddress = this._getContractAddress(artifacts.ExchangeArtifact, this._contractAddressIfExists); - return contractAddress; - } - /** - * Checks if order is still fillable and throws an error otherwise. Useful for orderbook - * pruning where you want to remove stale orders without knowing who the taker will be. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to validate. - * @param opts An object that conforms to the ValidateOrderFillableOpts - * interface. Allows specifying a specific fillTakerTokenAmount - * to validate for. - */ - public async validateOrderFillableOrThrowAsync( - signedOrder: SignedOrder, - opts?: ValidateOrderFillableOpts, - ): Promise<void> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - const zrxTokenAddress = this.getZRXTokenAddress(); - const expectedFillTakerTokenAmount = !_.isUndefined(opts) ? opts.expectedFillTakerTokenAmount : undefined; - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateOrderFillableOrThrowAsync( - exchangeTradeEmulator, - signedOrder, - zrxTokenAddress, - expectedFillTakerTokenAmount, - ); - } - /** - * Checks if order fill will succeed and throws an error otherwise. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to fill. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Web3.Provider passed to 0x.js. - */ - public async validateFillOrderThrowIfInvalidAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - ): Promise<void> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - } - /** - * Checks if cancelling a given order will succeed and throws an informative error if it won't. - * @param order An object that conforms to the Order or SignedOrder interface. - * The order you would like to cancel. - * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. - */ - public async validateCancelOrderThrowIfInvalidAsync( - order: Order, - cancelTakerTokenAmount: BigNumber, - ): Promise<void> { - assert.doesConformToSchema('order', order, schemas.orderSchema); - assert.isValidBaseUnitAmount('cancelTakerTokenAmount', cancelTakerTokenAmount); - const orderHash = utils.getOrderHashHex(order); - const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils.validateCancelOrderThrowIfInvalid( - order, - cancelTakerTokenAmount, - unavailableTakerTokenAmount, - ); - } - /** - * Checks if calling fillOrKill on a given order will succeed and throws an informative error if it won't. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to fill. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Web3.Provider passed to 0x.js. - */ - public async validateFillOrKillOrderThrowIfInvalidAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - ): Promise<void> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - } - /** - * Checks if rounding error will be > 0.1% when computing makerTokenAmount by doing: - * `(fillTakerTokenAmount * makerTokenAmount) / takerTokenAmount`. - * 0x Protocol does not accept any trades that result in large rounding errors. This means that tokens with few or - * no decimals can only be filled in quantities and ratios that avoid large rounding errors. - * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that you wish to fill. - * @param takerTokenAmount The order size on the taker side - * @param makerTokenAmount The order size on the maker side - */ - public async isRoundingErrorAsync( - fillTakerTokenAmount: BigNumber, - takerTokenAmount: BigNumber, - makerTokenAmount: BigNumber, - ): Promise<boolean> { - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - assert.isValidBaseUnitAmount('takerTokenAmount', takerTokenAmount); - assert.isValidBaseUnitAmount('makerTokenAmount', makerTokenAmount); - const exchangeInstance = await this._getExchangeContractAsync(); - const isRoundingError = await exchangeInstance.isRoundingError.callAsync( - fillTakerTokenAmount, - takerTokenAmount, - makerTokenAmount, - ); - return isRoundingError; - } - /** - * Checks if logs contain LogError, which is emmited by Exchange contract on transaction failure. - * @param logs Transaction logs as returned by `zeroEx.awaitTransactionMinedAsync` - */ - public throwLogErrorsAsErrors(logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>): void { - const errLog = _.find(logs, { - event: ExchangeEvents.LogError, - }) as LogWithDecodedArgs<LogErrorContractEventArgs> | undefined; - if (!_.isUndefined(errLog)) { - const logArgs = errLog.args; - const errCode = logArgs.errorId.toNumber(); - const errMessage = this._exchangeContractErrCodesToMsg[errCode]; - throw new Error(errMessage); - } - } - /** - * Returns the ZRX token address used by the exchange contract. - * @return Address of ZRX token - */ - public getZRXTokenAddress(): string { - const contractAddress = this._getContractAddress(artifacts.ZRXArtifact, this._zrxContractAddressIfExists); - return contractAddress; - } - private _invalidateContractInstances(): void { - this.unsubscribeAll(); - delete this._exchangeContractIfExists; - } - private async _isValidSignatureUsingContractCallAsync( - dataHex: string, - ecSignature: ECSignature, - signerAddressHex: string, - ): Promise<boolean> { - assert.isHexString('dataHex', dataHex); - assert.doesConformToSchema('ecSignature', ecSignature, schemas.ecSignatureSchema); - assert.isETHAddressHex('signerAddressHex', signerAddressHex); + const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order); + const txHash = await exchangeInstance.cancelOrder.sendTransactionAsync( + orderAddresses, + orderValues, + cancelTakerTokenAmount, + { + from: order.maker, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Batch version of cancelOrderAsync. Atomically cancels multiple orders in a single transaction. + * All orders must be from the same maker. + * @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest + * interface. + * @param transactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async batchCancelOrdersAsync( + orderCancellationRequests: OrderCancellationRequest[], + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema( + 'orderCancellationRequests', + orderCancellationRequests, + schemas.orderCancellationRequestsSchema, + ); + const exchangeContractAddresses = _.map( + orderCancellationRequests, + orderCancellationRequest => orderCancellationRequest.order.exchangeContractAddress, + ); + assert.hasAtMostOneUniqueValue( + exchangeContractAddresses, + ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, + ); + const makers = _.map(orderCancellationRequests, cancellationRequest => cancellationRequest.order.maker); + assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed); + const maker = makers[0]; + await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper); + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + for (const orderCancellationRequest of orderCancellationRequests) { + const orderHash = utils.getOrderHashHex(orderCancellationRequest.order); + const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); + OrderValidationUtils.validateCancelOrderThrowIfInvalid( + orderCancellationRequest.order, + orderCancellationRequest.takerTokenCancelAmount, + unavailableTakerTokenAmount, + ); + } + } + if (_.isEmpty(orderCancellationRequests)) { + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + } + const exchangeInstance = await this._getExchangeContractAsync(); + const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => { + return [ + ...ExchangeWrapper._getOrderAddressesAndValues(cancellationRequest.order), + cancellationRequest.takerTokenCancelAmount, + ]; + }); + // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( + const [orderAddresses, orderValues, cancelTakerTokenAmounts] = _.unzip<any>( + orderAddressesValuesAndTakerTokenCancelAmounts, + ); + const txHash = await exchangeInstance.batchCancelOrders.sendTransactionAsync( + orderAddresses, + orderValues, + cancelTakerTokenAmounts, + { + from: maker, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Subscribe to an event type emitted by the Exchange contract. + * @param eventName The exchange contract event you would like to subscribe to. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` + * @param callback Callback that gets called when a log is added/removed + * @return Subscription token used later to unsubscribe + */ + public subscribe<ArgsType extends ExchangeContractEventArgs>( + eventName: ExchangeEvents, + indexFilterValues: IndexedFilterValues, + callback: EventCallback<ArgsType>, + ): string { + assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + assert.isFunction('callback', callback); + const exchangeContractAddress = this.getContractAddress(); + const subscriptionToken = this._subscribe<ArgsType>( + exchangeContractAddress, + eventName, + indexFilterValues, + artifacts.ExchangeArtifact.abi, + callback, + ); + return subscriptionToken; + } + /** + * Cancel a subscription + * @param subscriptionToken Subscription token returned by `subscribe()` + */ + public unsubscribe(subscriptionToken: string): void { + this._unsubscribe(subscriptionToken); + } + /** + * Cancels all existing subscriptions + */ + public unsubscribeAll(): void { + super.unsubscribeAll(); + } + /** + * Gets historical logs without creating a subscription + * @param eventName The exchange contract event you would like to subscribe to. + * @param blockRange Block range to get logs from. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` + * @return Array of logs that match the parameters + */ + public async getLogsAsync<ArgsType extends ExchangeContractEventArgs>( + eventName: ExchangeEvents, + blockRange: BlockRange, + indexFilterValues: IndexedFilterValues, + ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { + assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); + assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + const exchangeContractAddress = this.getContractAddress(); + const logs = await this._getLogsAsync<ArgsType>( + exchangeContractAddress, + eventName, + blockRange, + indexFilterValues, + artifacts.ExchangeArtifact.abi, + ); + return logs; + } + /** + * Retrieves the Ethereum address of the Exchange contract deployed on the network + * that the user-passed web3 provider is connected to. + * @returns The Ethereum address of the Exchange contract being used. + */ + public getContractAddress(): string { + const contractAddress = this._getContractAddress(artifacts.ExchangeArtifact, this._contractAddressIfExists); + return contractAddress; + } + /** + * Checks if order is still fillable and throws an error otherwise. Useful for orderbook + * pruning where you want to remove stale orders without knowing who the taker will be. + * @param signedOrder An object that conforms to the SignedOrder interface. The + * signedOrder you wish to validate. + * @param opts An object that conforms to the ValidateOrderFillableOpts + * interface. Allows specifying a specific fillTakerTokenAmount + * to validate for. + */ + public async validateOrderFillableOrThrowAsync( + signedOrder: SignedOrder, + opts?: ValidateOrderFillableOpts, + ): Promise<void> { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + const zrxTokenAddress = this.getZRXTokenAddress(); + const expectedFillTakerTokenAmount = !_.isUndefined(opts) ? opts.expectedFillTakerTokenAmount : undefined; + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + await this._orderValidationUtils.validateOrderFillableOrThrowAsync( + exchangeTradeEmulator, + signedOrder, + zrxTokenAddress, + expectedFillTakerTokenAmount, + ); + } + /** + * Checks if order fill will succeed and throws an error otherwise. + * @param signedOrder An object that conforms to the SignedOrder interface. The + * signedOrder you wish to fill. + * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. + * @param takerAddress The user Ethereum address who would like to fill this order. + * Must be available via the supplied Web3.Provider passed to 0x.js. + */ + public async validateFillOrderThrowIfInvalidAsync( + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + ): Promise<void> { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + } + /** + * Checks if cancelling a given order will succeed and throws an informative error if it won't. + * @param order An object that conforms to the Order or SignedOrder interface. + * The order you would like to cancel. + * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. + */ + public async validateCancelOrderThrowIfInvalidAsync( + order: Order, + cancelTakerTokenAmount: BigNumber, + ): Promise<void> { + assert.doesConformToSchema('order', order, schemas.orderSchema); + assert.isValidBaseUnitAmount('cancelTakerTokenAmount', cancelTakerTokenAmount); + const orderHash = utils.getOrderHashHex(order); + const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); + OrderValidationUtils.validateCancelOrderThrowIfInvalid( + order, + cancelTakerTokenAmount, + unavailableTakerTokenAmount, + ); + } + /** + * Checks if calling fillOrKill on a given order will succeed and throws an informative error if it won't. + * @param signedOrder An object that conforms to the SignedOrder interface. The + * signedOrder you wish to fill. + * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. + * @param takerAddress The user Ethereum address who would like to fill this order. + * Must be available via the supplied Web3.Provider passed to 0x.js. + */ + public async validateFillOrKillOrderThrowIfInvalidAsync( + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + ): Promise<void> { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + } + /** + * Checks if rounding error will be > 0.1% when computing makerTokenAmount by doing: + * `(fillTakerTokenAmount * makerTokenAmount) / takerTokenAmount`. + * 0x Protocol does not accept any trades that result in large rounding errors. This means that tokens with few or + * no decimals can only be filled in quantities and ratios that avoid large rounding errors. + * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that you wish to fill. + * @param takerTokenAmount The order size on the taker side + * @param makerTokenAmount The order size on the maker side + */ + public async isRoundingErrorAsync( + fillTakerTokenAmount: BigNumber, + takerTokenAmount: BigNumber, + makerTokenAmount: BigNumber, + ): Promise<boolean> { + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + assert.isValidBaseUnitAmount('takerTokenAmount', takerTokenAmount); + assert.isValidBaseUnitAmount('makerTokenAmount', makerTokenAmount); + const exchangeInstance = await this._getExchangeContractAsync(); + const isRoundingError = await exchangeInstance.isRoundingError.callAsync( + fillTakerTokenAmount, + takerTokenAmount, + makerTokenAmount, + ); + return isRoundingError; + } + /** + * Checks if logs contain LogError, which is emmited by Exchange contract on transaction failure. + * @param logs Transaction logs as returned by `zeroEx.awaitTransactionMinedAsync` + */ + public throwLogErrorsAsErrors(logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>): void { + const errLog = _.find(logs, { + event: ExchangeEvents.LogError, + }) as LogWithDecodedArgs<LogErrorContractEventArgs> | undefined; + if (!_.isUndefined(errLog)) { + const logArgs = errLog.args; + const errCode = logArgs.errorId.toNumber(); + const errMessage = this._exchangeContractErrCodesToMsg[errCode]; + throw new Error(errMessage); + } + } + /** + * Returns the ZRX token address used by the exchange contract. + * @return Address of ZRX token + */ + public getZRXTokenAddress(): string { + const contractAddress = this._getContractAddress(artifacts.ZRXArtifact, this._zrxContractAddressIfExists); + return contractAddress; + } + private _invalidateContractInstances(): void { + this.unsubscribeAll(); + delete this._exchangeContractIfExists; + } + private async _isValidSignatureUsingContractCallAsync( + dataHex: string, + ecSignature: ECSignature, + signerAddressHex: string, + ): Promise<boolean> { + assert.isHexString('dataHex', dataHex); + assert.doesConformToSchema('ecSignature', ecSignature, schemas.ecSignatureSchema); + assert.isETHAddressHex('signerAddressHex', signerAddressHex); - const exchangeInstance = await this._getExchangeContractAsync(); + const exchangeInstance = await this._getExchangeContractAsync(); - const isValidSignature = await exchangeInstance.isValidSignature.callAsync( - signerAddressHex, - dataHex, - ecSignature.v, - ecSignature.r, - ecSignature.s, - ); - return isValidSignature; - } - private async _getOrderHashHexUsingContractCallAsync(order: Order | SignedOrder): Promise<string> { - const exchangeInstance = await this._getExchangeContractAsync(); - const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order); - const orderHashHex = await exchangeInstance.getOrderHash.callAsync(orderAddresses, orderValues); - return orderHashHex; - } - private async _getExchangeContractAsync(): Promise<ExchangeContract> { - if (!_.isUndefined(this._exchangeContractIfExists)) { - return this._exchangeContractIfExists; - } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( - artifacts.ExchangeArtifact, - this._contractAddressIfExists, - ); - const contractInstance = new ExchangeContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); - this._exchangeContractIfExists = contractInstance; - return this._exchangeContractIfExists; - } - private async _getTokenTransferProxyAddressAsync(): Promise<string> { - const exchangeInstance = await this._getExchangeContractAsync(); - const tokenTransferProxyAddress = await exchangeInstance.TOKEN_TRANSFER_PROXY_CONTRACT.callAsync(); - const tokenTransferProxyAddressLowerCase = tokenTransferProxyAddress.toLowerCase(); - return tokenTransferProxyAddressLowerCase; - } + const isValidSignature = await exchangeInstance.isValidSignature.callAsync( + signerAddressHex, + dataHex, + ecSignature.v, + ecSignature.r, + ecSignature.s, + ); + return isValidSignature; + } + private async _getOrderHashHexUsingContractCallAsync(order: Order | SignedOrder): Promise<string> { + const exchangeInstance = await this._getExchangeContractAsync(); + const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order); + const orderHashHex = await exchangeInstance.getOrderHash.callAsync(orderAddresses, orderValues); + return orderHashHex; + } + private async _getExchangeContractAsync(): Promise<ExchangeContract> { + if (!_.isUndefined(this._exchangeContractIfExists)) { + return this._exchangeContractIfExists; + } + const web3ContractInstance = await this._instantiateContractIfExistsAsync( + artifacts.ExchangeArtifact, + this._contractAddressIfExists, + ); + const contractInstance = new ExchangeContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); + this._exchangeContractIfExists = contractInstance; + return this._exchangeContractIfExists; + } + private async _getTokenTransferProxyAddressAsync(): Promise<string> { + const exchangeInstance = await this._getExchangeContractAsync(); + const tokenTransferProxyAddress = await exchangeInstance.TOKEN_TRANSFER_PROXY_CONTRACT.callAsync(); + const tokenTransferProxyAddressLowerCase = tokenTransferProxyAddress.toLowerCase(); + return tokenTransferProxyAddressLowerCase; + } } // tslint:disable:max-file-line-count diff --git a/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts index bce48a590..f54aaf0f8 100644 --- a/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts @@ -13,117 +13,117 @@ import { TokenRegistryContract } from './generated/token_registry'; * This class includes all the functionality related to interacting with the 0x Token Registry smart contract. */ export class TokenRegistryWrapper extends ContractWrapper { - private _tokenRegistryContractIfExists?: TokenRegistryContract; - private _contractAddressIfExists?: string; - private static _createTokenFromMetadata(metadata: TokenMetadata): Token | undefined { - if (metadata[0] === constants.NULL_ADDRESS) { - return undefined; - } - const token = { - address: metadata[0], - name: metadata[1], - symbol: metadata[2], - decimals: metadata[3].toNumber(), - }; - return token; - } - constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { - super(web3Wrapper, networkId); - this._contractAddressIfExists = contractAddressIfExists; - } - /** - * Retrieves all the tokens currently listed in the Token Registry smart contract - * @return An array of objects that conform to the Token interface. - */ - public async getTokensAsync(): Promise<Token[]> { - const addresses = await this.getTokenAddressesAsync(); - const tokenPromises: Array<Promise<Token | undefined>> = _.map(addresses, async (address: string) => - this.getTokenIfExistsAsync(address), - ); - const tokens = await Promise.all(tokenPromises); - return tokens as Token[]; - } - /** - * Retrieves all the addresses of the tokens currently listed in the Token Registry smart contract - * @return An array of token addresses. - */ - public async getTokenAddressesAsync(): Promise<string[]> { - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const addresses = await tokenRegistryContract.getTokenAddresses.callAsync(); - return addresses; - } - /** - * Retrieves a token by address currently listed in the Token Registry smart contract - * @return An object that conforms to the Token interface or undefined if token not found. - */ - public async getTokenIfExistsAsync(address: string): Promise<Token | undefined> { - assert.isETHAddressHex('address', address); + private _tokenRegistryContractIfExists?: TokenRegistryContract; + private _contractAddressIfExists?: string; + private static _createTokenFromMetadata(metadata: TokenMetadata): Token | undefined { + if (metadata[0] === constants.NULL_ADDRESS) { + return undefined; + } + const token = { + address: metadata[0], + name: metadata[1], + symbol: metadata[2], + decimals: metadata[3].toNumber(), + }; + return token; + } + constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { + super(web3Wrapper, networkId); + this._contractAddressIfExists = contractAddressIfExists; + } + /** + * Retrieves all the tokens currently listed in the Token Registry smart contract + * @return An array of objects that conform to the Token interface. + */ + public async getTokensAsync(): Promise<Token[]> { + const addresses = await this.getTokenAddressesAsync(); + const tokenPromises: Array<Promise<Token | undefined>> = _.map(addresses, async (address: string) => + this.getTokenIfExistsAsync(address), + ); + const tokens = await Promise.all(tokenPromises); + return tokens as Token[]; + } + /** + * Retrieves all the addresses of the tokens currently listed in the Token Registry smart contract + * @return An array of token addresses. + */ + public async getTokenAddressesAsync(): Promise<string[]> { + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const addresses = await tokenRegistryContract.getTokenAddresses.callAsync(); + return addresses; + } + /** + * Retrieves a token by address currently listed in the Token Registry smart contract + * @return An object that conforms to the Token interface or undefined if token not found. + */ + public async getTokenIfExistsAsync(address: string): Promise<Token | undefined> { + assert.isETHAddressHex('address', address); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(address); - const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); - return token; - } - public async getTokenAddressBySymbolIfExistsAsync(symbol: string): Promise<string | undefined> { - assert.isString('symbol', symbol); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.callAsync(symbol); - if (addressIfExists === constants.NULL_ADDRESS) { - return undefined; - } - return addressIfExists; - } - public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string | undefined> { - assert.isString('name', name); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name); - if (addressIfExists === constants.NULL_ADDRESS) { - return undefined; - } - return addressIfExists; - } - public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token | undefined> { - assert.isString('symbol', symbol); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol); - const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); - return token; - } - public async getTokenByNameIfExistsAsync(name: string): Promise<Token | undefined> { - assert.isString('name', name); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const metadata = await tokenRegistryContract.getTokenByName.callAsync(name); - const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); - return token; - } - /** - * Retrieves the Ethereum address of the TokenRegistry contract deployed on the network - * that the user-passed web3 provider is connected to. - * @returns The Ethereum address of the TokenRegistry contract being used. - */ - public getContractAddress(): string { - const contractAddress = this._getContractAddress( - artifacts.TokenRegistryArtifact, - this._contractAddressIfExists, - ); - return contractAddress; - } - private _invalidateContractInstance(): void { - delete this._tokenRegistryContractIfExists; - } - private async _getTokenRegistryContractAsync(): Promise<TokenRegistryContract> { - if (!_.isUndefined(this._tokenRegistryContractIfExists)) { - return this._tokenRegistryContractIfExists; - } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( - artifacts.TokenRegistryArtifact, - this._contractAddressIfExists, - ); - const contractInstance = new TokenRegistryContract( - web3ContractInstance, - this._web3Wrapper.getContractDefaults(), - ); - this._tokenRegistryContractIfExists = contractInstance; - return this._tokenRegistryContractIfExists; - } + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(address); + const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); + return token; + } + public async getTokenAddressBySymbolIfExistsAsync(symbol: string): Promise<string | undefined> { + assert.isString('symbol', symbol); + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.callAsync(symbol); + if (addressIfExists === constants.NULL_ADDRESS) { + return undefined; + } + return addressIfExists; + } + public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string | undefined> { + assert.isString('name', name); + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name); + if (addressIfExists === constants.NULL_ADDRESS) { + return undefined; + } + return addressIfExists; + } + public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token | undefined> { + assert.isString('symbol', symbol); + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol); + const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); + return token; + } + public async getTokenByNameIfExistsAsync(name: string): Promise<Token | undefined> { + assert.isString('name', name); + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const metadata = await tokenRegistryContract.getTokenByName.callAsync(name); + const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); + return token; + } + /** + * Retrieves the Ethereum address of the TokenRegistry contract deployed on the network + * that the user-passed web3 provider is connected to. + * @returns The Ethereum address of the TokenRegistry contract being used. + */ + public getContractAddress(): string { + const contractAddress = this._getContractAddress( + artifacts.TokenRegistryArtifact, + this._contractAddressIfExists, + ); + return contractAddress; + } + private _invalidateContractInstance(): void { + delete this._tokenRegistryContractIfExists; + } + private async _getTokenRegistryContractAsync(): Promise<TokenRegistryContract> { + if (!_.isUndefined(this._tokenRegistryContractIfExists)) { + return this._tokenRegistryContractIfExists; + } + const web3ContractInstance = await this._instantiateContractIfExistsAsync( + artifacts.TokenRegistryArtifact, + this._contractAddressIfExists, + ); + const contractInstance = new TokenRegistryContract( + web3ContractInstance, + this._web3Wrapper.getContractDefaults(), + ); + this._tokenRegistryContractIfExists = contractInstance; + return this._tokenRegistryContractIfExists; + } } diff --git a/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts index 635588fed..f5d9d108a 100644 --- a/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts @@ -10,59 +10,59 @@ import { TokenTransferProxyContract } from './generated/token_transfer_proxy'; * This class includes the functionality related to interacting with the TokenTransferProxy contract. */ export class TokenTransferProxyWrapper extends ContractWrapper { - private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract; - private _contractAddressIfExists?: string; - constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { - super(web3Wrapper, networkId); - this._contractAddressIfExists = contractAddressIfExists; - } - /** - * Check if the Exchange contract address is authorized by the TokenTransferProxy contract. - * @param exchangeContractAddress The hex encoded address of the Exchange contract to call. - * @return Whether the exchangeContractAddress is authorized. - */ - public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> { - const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); - const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync(exchangeContractAddress); - return isAuthorized; - } - /** - * Get the list of all Exchange contract addresses authorized by the TokenTransferProxy contract. - * @return The list of authorized addresses. - */ - public async getAuthorizedAddressesAsync(): Promise<string[]> { - const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); - const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.callAsync(); - return authorizedAddresses; - } - /** - * Retrieves the Ethereum address of the TokenTransferProxy contract deployed on the network - * that the user-passed web3 provider is connected to. - * @returns The Ethereum address of the TokenTransferProxy contract being used. - */ - public getContractAddress(): string { - const contractAddress = this._getContractAddress( - artifacts.TokenTransferProxyArtifact, - this._contractAddressIfExists, - ); - return contractAddress; - } - private _invalidateContractInstance(): void { - delete this._tokenTransferProxyContractIfExists; - } - private async _getTokenTransferProxyContractAsync(): Promise<TokenTransferProxyContract> { - if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) { - return this._tokenTransferProxyContractIfExists; - } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( - artifacts.TokenTransferProxyArtifact, - this._contractAddressIfExists, - ); - const contractInstance = new TokenTransferProxyContract( - web3ContractInstance, - this._web3Wrapper.getContractDefaults(), - ); - this._tokenTransferProxyContractIfExists = contractInstance; - return this._tokenTransferProxyContractIfExists; - } + private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract; + private _contractAddressIfExists?: string; + constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { + super(web3Wrapper, networkId); + this._contractAddressIfExists = contractAddressIfExists; + } + /** + * Check if the Exchange contract address is authorized by the TokenTransferProxy contract. + * @param exchangeContractAddress The hex encoded address of the Exchange contract to call. + * @return Whether the exchangeContractAddress is authorized. + */ + public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> { + const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); + const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync(exchangeContractAddress); + return isAuthorized; + } + /** + * Get the list of all Exchange contract addresses authorized by the TokenTransferProxy contract. + * @return The list of authorized addresses. + */ + public async getAuthorizedAddressesAsync(): Promise<string[]> { + const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); + const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.callAsync(); + return authorizedAddresses; + } + /** + * Retrieves the Ethereum address of the TokenTransferProxy contract deployed on the network + * that the user-passed web3 provider is connected to. + * @returns The Ethereum address of the TokenTransferProxy contract being used. + */ + public getContractAddress(): string { + const contractAddress = this._getContractAddress( + artifacts.TokenTransferProxyArtifact, + this._contractAddressIfExists, + ); + return contractAddress; + } + private _invalidateContractInstance(): void { + delete this._tokenTransferProxyContractIfExists; + } + private async _getTokenTransferProxyContractAsync(): Promise<TokenTransferProxyContract> { + if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) { + return this._tokenTransferProxyContractIfExists; + } + const web3ContractInstance = await this._instantiateContractIfExistsAsync( + artifacts.TokenTransferProxyArtifact, + this._contractAddressIfExists, + ); + const contractInstance = new TokenTransferProxyContract( + web3ContractInstance, + this._web3Wrapper.getContractDefaults(), + ); + this._tokenTransferProxyContractIfExists = contractInstance; + return this._tokenTransferProxyContractIfExists; + } } diff --git a/packages/0x.js/src/contract_wrappers/token_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_wrapper.ts index 366cabd9e..7943f4a60 100644 --- a/packages/0x.js/src/contract_wrappers/token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_wrapper.ts @@ -5,15 +5,15 @@ import * as _ from 'lodash'; import { artifacts } from '../artifacts'; import { - BlockRange, - EventCallback, - IndexedFilterValues, - LogWithDecodedArgs, - MethodOpts, - TokenContractEventArgs, - TokenEvents, - TransactionOpts, - ZeroExError, + BlockRange, + EventCallback, + IndexedFilterValues, + LogWithDecodedArgs, + MethodOpts, + TokenContractEventArgs, + TokenEvents, + TransactionOpts, + ZeroExError, } from '../types'; import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; @@ -29,367 +29,367 @@ import { TokenTransferProxyWrapper } from './token_transfer_proxy_wrapper'; * to the 0x Proxy smart contract. */ export class TokenWrapper extends ContractWrapper { - public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; - private _tokenContractsByAddress: { [address: string]: TokenContract }; - private _tokenTransferProxyWrapper: TokenTransferProxyWrapper; - constructor( - web3Wrapper: Web3Wrapper, - networkId: number, - abiDecoder: AbiDecoder, - tokenTransferProxyWrapper: TokenTransferProxyWrapper, - ) { - super(web3Wrapper, networkId, abiDecoder); - this._tokenContractsByAddress = {}; - this._tokenTransferProxyWrapper = tokenTransferProxyWrapper; - } - /** - * Retrieves an owner's ERC20 token balance. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check. - * @param methodOpts Optional arguments this method accepts. - * @return The owner's ERC20 token balance in base units. - */ - public async getBalanceAsync( - tokenAddress: string, - ownerAddress: string, - methodOpts?: MethodOpts, - ): Promise<BigNumber> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); + public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; + private _tokenContractsByAddress: { [address: string]: TokenContract }; + private _tokenTransferProxyWrapper: TokenTransferProxyWrapper; + constructor( + web3Wrapper: Web3Wrapper, + networkId: number, + abiDecoder: AbiDecoder, + tokenTransferProxyWrapper: TokenTransferProxyWrapper, + ) { + super(web3Wrapper, networkId, abiDecoder); + this._tokenContractsByAddress = {}; + this._tokenTransferProxyWrapper = tokenTransferProxyWrapper; + } + /** + * Retrieves an owner's ERC20 token balance. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check. + * @param methodOpts Optional arguments this method accepts. + * @return The owner's ERC20 token balance in base units. + */ + public async getBalanceAsync( + tokenAddress: string, + ownerAddress: string, + methodOpts?: MethodOpts, + ): Promise<BigNumber> { + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); - const tokenContract = await this._getTokenContractAsync(tokenAddress); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let balance = await tokenContract.balanceOf.callAsync(ownerAddress, defaultBlock); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - balance = new BigNumber(balance); - return balance; - } - /** - * Sets the spender's allowance to a specified number of baseUnits on behalf of the owner address. - * Equivalent to the ERC20 spec method `approve`. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance - * for spenderAddress. - * @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance. - * @param amountInBaseUnits The allowance amount you would like to set. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async setAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - spenderAddress: string, - amountInBaseUnits: BigNumber, - txOpts: TransactionOpts = {}, - ): Promise<string> { - await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper); - assert.isETHAddressHex('spenderAddress', spenderAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); + const tokenContract = await this._getTokenContractAsync(tokenAddress); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + let balance = await tokenContract.balanceOf.callAsync(ownerAddress, defaultBlock); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + balance = new BigNumber(balance); + return balance; + } + /** + * Sets the spender's allowance to a specified number of baseUnits on behalf of the owner address. + * Equivalent to the ERC20 spec method `approve`. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance + * for spenderAddress. + * @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance. + * @param amountInBaseUnits The allowance amount you would like to set. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + spenderAddress: string, + amountInBaseUnits: BigNumber, + txOpts: TransactionOpts = {}, + ): Promise<string> { + await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper); + assert.isETHAddressHex('spenderAddress', spenderAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - const tokenContract = await this._getTokenContractAsync(tokenAddress); - const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, { - from: ownerAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); - return txHash; - } - /** - * Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address. - * Equivalent to the ERC20 spec method `approve`. - * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating - * allowances set to the max amount (e.g ZRX, WETH) - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance - * for spenderAddress. - * @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async setUnlimitedAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - spenderAddress: string, - txOpts: TransactionOpts = {}, - ): Promise<string> { - const txHash = await this.setAllowanceAsync( - tokenAddress, - ownerAddress, - spenderAddress, - this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, - txOpts, - ); - return txHash; - } - /** - * Retrieves the owners allowance in baseUnits set to the spender's address. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address whose allowance to spenderAddress - * you would like to retrieve. - * @param spenderAddress The hex encoded user Ethereum address who can spend the allowance you are fetching. - * @param methodOpts Optional arguments this method accepts. - */ - public async getAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - spenderAddress: string, - methodOpts?: MethodOpts, - ): Promise<BigNumber> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); + const tokenContract = await this._getTokenContractAsync(tokenAddress); + const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, { + from: ownerAddress, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }); + return txHash; + } + /** + * Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address. + * Equivalent to the ERC20 spec method `approve`. + * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating + * allowances set to the max amount (e.g ZRX, WETH) + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance + * for spenderAddress. + * @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setUnlimitedAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + spenderAddress: string, + txOpts: TransactionOpts = {}, + ): Promise<string> { + const txHash = await this.setAllowanceAsync( + tokenAddress, + ownerAddress, + spenderAddress, + this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + txOpts, + ); + return txHash; + } + /** + * Retrieves the owners allowance in baseUnits set to the spender's address. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address whose allowance to spenderAddress + * you would like to retrieve. + * @param spenderAddress The hex encoded user Ethereum address who can spend the allowance you are fetching. + * @param methodOpts Optional arguments this method accepts. + */ + public async getAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + spenderAddress: string, + methodOpts?: MethodOpts, + ): Promise<BigNumber> { + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); - const tokenContract = await this._getTokenContractAsync(tokenAddress); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let allowanceInBaseUnits = await tokenContract.allowance.callAsync(ownerAddress, spenderAddress, defaultBlock); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits); - return allowanceInBaseUnits; - } - /** - * Retrieves the owner's allowance in baseUnits set to the 0x proxy contract. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address whose proxy contract allowance we are retrieving. - * @param methodOpts Optional arguments this method accepts. - */ - public async getProxyAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - methodOpts?: MethodOpts, - ): Promise<BigNumber> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); + const tokenContract = await this._getTokenContractAsync(tokenAddress); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + let allowanceInBaseUnits = await tokenContract.allowance.callAsync(ownerAddress, spenderAddress, defaultBlock); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits); + return allowanceInBaseUnits; + } + /** + * Retrieves the owner's allowance in baseUnits set to the 0x proxy contract. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address whose proxy contract allowance we are retrieving. + * @param methodOpts Optional arguments this method accepts. + */ + public async getProxyAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + methodOpts?: MethodOpts, + ): Promise<BigNumber> { + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); - const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); - const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts); - return allowanceInBaseUnits; - } - /** - * Sets the 0x proxy contract's allowance to a specified number of a tokens' baseUnits on behalf - * of an owner address. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address who is setting an allowance - * for the Proxy contract. - * @param amountInBaseUnits The allowance amount specified in baseUnits. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async setProxyAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - amountInBaseUnits: BigNumber, - txOpts: TransactionOpts = {}, - ): Promise<string> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); + const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); + const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts); + return allowanceInBaseUnits; + } + /** + * Sets the 0x proxy contract's allowance to a specified number of a tokens' baseUnits on behalf + * of an owner address. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address who is setting an allowance + * for the Proxy contract. + * @param amountInBaseUnits The allowance amount specified in baseUnits. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setProxyAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + amountInBaseUnits: BigNumber, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); - const txHash = await this.setAllowanceAsync( - tokenAddress, - ownerAddress, - proxyAddress, - amountInBaseUnits, - txOpts, - ); - return txHash; - } - /** - * Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf - * of an owner address. - * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating - * allowances set to the max amount (e.g ZRX, WETH) - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address who is setting an allowance - * for the Proxy contract. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async setUnlimitedProxyAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - txOpts: TransactionOpts = {}, - ): Promise<string> { - const txHash = await this.setProxyAllowanceAsync( - tokenAddress, - ownerAddress, - this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, - txOpts, - ); - return txHash; - } - /** - * Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param fromAddress The hex encoded user Ethereum address that will send the funds. - * @param toAddress The hex encoded user Ethereum address that will receive the funds. - * @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async transferAsync( - tokenAddress: string, - fromAddress: string, - toAddress: string, - amountInBaseUnits: BigNumber, - txOpts: TransactionOpts = {}, - ): Promise<string> { - assert.isETHAddressHex('tokenAddress', tokenAddress); - await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper); - assert.isETHAddressHex('toAddress', toAddress); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); + const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); + const txHash = await this.setAllowanceAsync( + tokenAddress, + ownerAddress, + proxyAddress, + amountInBaseUnits, + txOpts, + ); + return txHash; + } + /** + * Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf + * of an owner address. + * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating + * allowances set to the max amount (e.g ZRX, WETH) + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address who is setting an allowance + * for the Proxy contract. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setUnlimitedProxyAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + txOpts: TransactionOpts = {}, + ): Promise<string> { + const txHash = await this.setProxyAllowanceAsync( + tokenAddress, + ownerAddress, + this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + txOpts, + ); + return txHash; + } + /** + * Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param fromAddress The hex encoded user Ethereum address that will send the funds. + * @param toAddress The hex encoded user Ethereum address that will receive the funds. + * @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async transferAsync( + tokenAddress: string, + fromAddress: string, + toAddress: string, + amountInBaseUnits: BigNumber, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper); + assert.isETHAddressHex('toAddress', toAddress); + assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - const tokenContract = await this._getTokenContractAsync(tokenAddress); + const tokenContract = await this._getTokenContractAsync(tokenAddress); - const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress); - if (fromAddressBalance.lessThan(amountInBaseUnits)) { - throw new Error(ZeroExError.InsufficientBalanceForTransfer); - } + const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress); + if (fromAddressBalance.lessThan(amountInBaseUnits)) { + throw new Error(ZeroExError.InsufficientBalanceForTransfer); + } - const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, { - from: fromAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); - return txHash; - } - /** - * Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`. - * Requires the fromAddress to have sufficient funds and to have approved an allowance of - * `amountInBaseUnits` to `senderAddress`. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param fromAddress The hex encoded user Ethereum address whose funds are being sent. - * @param toAddress The hex encoded user Ethereum address that will receive the funds. - * @param senderAddress The hex encoded user Ethereum address whose initiates the fund transfer. The - * `fromAddress` must have set an allowance to the `senderAddress` - * before this call. - * @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async transferFromAsync( - tokenAddress: string, - fromAddress: string, - toAddress: string, - senderAddress: string, - amountInBaseUnits: BigNumber, - txOpts: TransactionOpts = {}, - ): Promise<string> { - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.isETHAddressHex('fromAddress', fromAddress); - assert.isETHAddressHex('toAddress', toAddress); - await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); + const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, { + from: fromAddress, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }); + return txHash; + } + /** + * Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`. + * Requires the fromAddress to have sufficient funds and to have approved an allowance of + * `amountInBaseUnits` to `senderAddress`. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param fromAddress The hex encoded user Ethereum address whose funds are being sent. + * @param toAddress The hex encoded user Ethereum address that will receive the funds. + * @param senderAddress The hex encoded user Ethereum address whose initiates the fund transfer. The + * `fromAddress` must have set an allowance to the `senderAddress` + * before this call. + * @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async transferFromAsync( + tokenAddress: string, + fromAddress: string, + toAddress: string, + senderAddress: string, + amountInBaseUnits: BigNumber, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isETHAddressHex('fromAddress', fromAddress); + assert.isETHAddressHex('toAddress', toAddress); + await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper); + assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - const tokenContract = await this._getTokenContractAsync(tokenAddress); + const tokenContract = await this._getTokenContractAsync(tokenAddress); - const fromAddressAllowance = await this.getAllowanceAsync(tokenAddress, fromAddress, senderAddress); - if (fromAddressAllowance.lessThan(amountInBaseUnits)) { - throw new Error(ZeroExError.InsufficientAllowanceForTransfer); - } + const fromAddressAllowance = await this.getAllowanceAsync(tokenAddress, fromAddress, senderAddress); + if (fromAddressAllowance.lessThan(amountInBaseUnits)) { + throw new Error(ZeroExError.InsufficientAllowanceForTransfer); + } - const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress); - if (fromAddressBalance.lessThan(amountInBaseUnits)) { - throw new Error(ZeroExError.InsufficientBalanceForTransfer); - } + const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress); + if (fromAddressBalance.lessThan(amountInBaseUnits)) { + throw new Error(ZeroExError.InsufficientBalanceForTransfer); + } - const txHash = await tokenContract.transferFrom.sendTransactionAsync( - fromAddress, - toAddress, - amountInBaseUnits, - { - from: senderAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Subscribe to an event type emitted by the Token contract. - * @param tokenAddress The hex encoded address where the ERC20 token is deployed. - * @param eventName The token contract event you would like to subscribe to. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` - * @param callback Callback that gets called when a log is added/removed - * @return Subscription token used later to unsubscribe - */ - public subscribe<ArgsType extends TokenContractEventArgs>( - tokenAddress: string, - eventName: TokenEvents, - indexFilterValues: IndexedFilterValues, - callback: EventCallback<ArgsType>, - ): string { - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - assert.isFunction('callback', callback); - const subscriptionToken = this._subscribe<ArgsType>( - tokenAddress, - eventName, - indexFilterValues, - artifacts.TokenArtifact.abi, - callback, - ); - return subscriptionToken; - } - /** - * Cancel a subscription - * @param subscriptionToken Subscription token returned by `subscribe()` - */ - public unsubscribe(subscriptionToken: string): void { - this._unsubscribe(subscriptionToken); - } - /** - * Cancels all existing subscriptions - */ - public unsubscribeAll(): void { - super.unsubscribeAll(); - } - /** - * Gets historical logs without creating a subscription - * @param tokenAddress An address of the token that emmited the logs. - * @param eventName The token contract event you would like to subscribe to. - * @param blockRange Block range to get logs from. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` - * @return Array of logs that match the parameters - */ - public async getLogsAsync<ArgsType extends TokenContractEventArgs>( - tokenAddress: string, - eventName: TokenEvents, - blockRange: BlockRange, - indexFilterValues: IndexedFilterValues, - ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); - assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - const logs = await this._getLogsAsync<ArgsType>( - tokenAddress, - eventName, - blockRange, - indexFilterValues, - artifacts.TokenArtifact.abi, - ); - return logs; - } - private _invalidateContractInstances(): void { - this.unsubscribeAll(); - this._tokenContractsByAddress = {}; - } - private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> { - let tokenContract = this._tokenContractsByAddress[tokenAddress]; - if (!_.isUndefined(tokenContract)) { - return tokenContract; - } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( - artifacts.TokenArtifact, - tokenAddress, - ); - const contractInstance = new TokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); - tokenContract = contractInstance; - this._tokenContractsByAddress[tokenAddress] = tokenContract; - return tokenContract; - } + const txHash = await tokenContract.transferFrom.sendTransactionAsync( + fromAddress, + toAddress, + amountInBaseUnits, + { + from: senderAddress, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Subscribe to an event type emitted by the Token contract. + * @param tokenAddress The hex encoded address where the ERC20 token is deployed. + * @param eventName The token contract event you would like to subscribe to. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` + * @param callback Callback that gets called when a log is added/removed + * @return Subscription token used later to unsubscribe + */ + public subscribe<ArgsType extends TokenContractEventArgs>( + tokenAddress: string, + eventName: TokenEvents, + indexFilterValues: IndexedFilterValues, + callback: EventCallback<ArgsType>, + ): string { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + assert.isFunction('callback', callback); + const subscriptionToken = this._subscribe<ArgsType>( + tokenAddress, + eventName, + indexFilterValues, + artifacts.TokenArtifact.abi, + callback, + ); + return subscriptionToken; + } + /** + * Cancel a subscription + * @param subscriptionToken Subscription token returned by `subscribe()` + */ + public unsubscribe(subscriptionToken: string): void { + this._unsubscribe(subscriptionToken); + } + /** + * Cancels all existing subscriptions + */ + public unsubscribeAll(): void { + super.unsubscribeAll(); + } + /** + * Gets historical logs without creating a subscription + * @param tokenAddress An address of the token that emmited the logs. + * @param eventName The token contract event you would like to subscribe to. + * @param blockRange Block range to get logs from. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` + * @return Array of logs that match the parameters + */ + public async getLogsAsync<ArgsType extends TokenContractEventArgs>( + tokenAddress: string, + eventName: TokenEvents, + blockRange: BlockRange, + indexFilterValues: IndexedFilterValues, + ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); + assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + const logs = await this._getLogsAsync<ArgsType>( + tokenAddress, + eventName, + blockRange, + indexFilterValues, + artifacts.TokenArtifact.abi, + ); + return logs; + } + private _invalidateContractInstances(): void { + this.unsubscribeAll(); + this._tokenContractsByAddress = {}; + } + private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> { + let tokenContract = this._tokenContractsByAddress[tokenAddress]; + if (!_.isUndefined(tokenContract)) { + return tokenContract; + } + const web3ContractInstance = await this._instantiateContractIfExistsAsync( + artifacts.TokenArtifact, + tokenAddress, + ); + const contractInstance = new TokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); + tokenContract = contractInstance; + this._tokenContractsByAddress[tokenAddress] = tokenContract; + return tokenContract; + } } diff --git a/packages/0x.js/src/globals.d.ts b/packages/0x.js/src/globals.d.ts index 9a53949b4..4f4932b6e 100644 --- a/packages/0x.js/src/globals.d.ts +++ b/packages/0x.js/src/globals.d.ts @@ -10,50 +10,50 @@ declare module 'web3-provider-engine/subproviders/rpc'; // disallow `namespace`, we disable tslint for the following. /* tslint:disable */ declare namespace Chai { - interface Assertion { - bignumber: Assertion; - // HACK: In order to comply with chai-as-promised we make eventually a `PromisedAssertion` not an `Assertion` - eventually: PromisedAssertion; - } + interface Assertion { + bignumber: Assertion; + // HACK: In order to comply with chai-as-promised we make eventually a `PromisedAssertion` not an `Assertion` + eventually: PromisedAssertion; + } } /* tslint:enable */ declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ } declare module 'ethereumjs-abi' { - const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; + const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; } // truffle-hdwallet-provider declarations declare module 'truffle-hdwallet-provider' { - import * as Web3 from 'web3'; - class HDWalletProvider implements Web3.Provider { - constructor(mnemonic: string, rpcUrl: string); - public sendAsync( - payload: Web3.JSONRPCRequestPayload, - callback: (err: Error, result: Web3.JSONRPCResponsePayload) => void, - ): void; - } - export = HDWalletProvider; + import * as Web3 from 'web3'; + class HDWalletProvider implements Web3.Provider { + constructor(mnemonic: string, rpcUrl: string); + public sendAsync( + payload: Web3.JSONRPCRequestPayload, + callback: (err: Error, result: Web3.JSONRPCResponsePayload) => void, + ): void; + } + export = HDWalletProvider; } // abi-decoder declarations interface DecodedLogArg {} interface DecodedLog { - name: string; - events: DecodedLogArg[]; + name: string; + events: DecodedLogArg[]; } declare module 'abi-decoder' { - import * as Web3 from 'web3'; - const addABI: (abi: Web3.AbiDefinition) => void; - const decodeLogs: (logs: Web3.LogEntry[]) => DecodedLog[]; + import * as Web3 from 'web3'; + const addABI: (abi: Web3.AbiDefinition) => void; + const decodeLogs: (logs: Web3.LogEntry[]) => DecodedLog[]; } declare module 'web3/lib/solidity/coder' { - const decodeParams: (types: string[], data: string) => any[]; + const decodeParams: (types: string[], data: string) => any[]; } diff --git a/packages/0x.js/src/globalsAugment.d.ts b/packages/0x.js/src/globalsAugment.d.ts index c2f200ab8..3e2f2216b 100644 --- a/packages/0x.js/src/globalsAugment.d.ts +++ b/packages/0x.js/src/globalsAugment.d.ts @@ -3,21 +3,21 @@ import { BigNumber } from '@0xproject/utils'; // HACK: This module overrides the Chai namespace so that we can use BigNumber types inside. // Source: https://github.com/Microsoft/TypeScript/issues/7352#issuecomment-191547232 declare global { - // HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion - // interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise - // disallow `namespace`, we disable tslint for the following. - /* tslint:disable */ - namespace Chai { - interface NumberComparer { - (value: number | BigNumber, message?: string): Assertion; - } - interface NumericComparison { - greaterThan: NumberComparer; - } - } - /* tslint:enable */ - interface DecodedLogArg { - name: string; - value: string | BigNumber; - } + // HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion + // interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise + // disallow `namespace`, we disable tslint for the following. + /* tslint:disable */ + namespace Chai { + interface NumberComparer { + (value: number | BigNumber, message?: string): Assertion; + } + interface NumericComparison { + greaterThan: NumberComparer; + } + } + /* tslint:enable */ + interface DecodedLogArg { + name: string; + value: string | BigNumber; + } } diff --git a/packages/0x.js/src/index.ts b/packages/0x.js/src/index.ts index ad5ef8534..599c3a2b0 100644 --- a/packages/0x.js/src/index.ts +++ b/packages/0x.js/src/index.ts @@ -1,50 +1,50 @@ export { ZeroEx } from './0x'; export { - Order, - BlockParamLiteral, - SignedOrder, - ECSignature, - ZeroExError, - EventCallback, - ExchangeContractErrs, - ContractEvent, - Token, - ExchangeEvents, - TokenEvents, - IndexedFilterValues, - BlockRange, - BlockParam, - OrderCancellationRequest, - OrderFillRequest, - LogErrorContractEventArgs, - LogCancelContractEventArgs, - LogFillContractEventArgs, - ExchangeContractEventArgs, - TransferContractEventArgs, - ApprovalContractEventArgs, - TokenContractEventArgs, - EtherTokenContractEventArgs, - WithdrawalContractEventArgs, - DepositContractEventArgs, - ContractEventArgs, - ContractEventArg, - Web3Provider, - ZeroExConfig, - EtherTokenEvents, - TransactionReceiptWithDecodedLogs, - LogWithDecodedArgs, - MethodOpts, - OrderTransactionOpts, - TransactionOpts, - FilterObject, - LogEvent, - DecodedLogEvent, - EventWatcherCallback, - OnOrderStateChangeCallback, - OrderStateValid, - OrderStateInvalid, - OrderState, + Order, + BlockParamLiteral, + SignedOrder, + ECSignature, + ZeroExError, + EventCallback, + ExchangeContractErrs, + ContractEvent, + Token, + ExchangeEvents, + TokenEvents, + IndexedFilterValues, + BlockRange, + BlockParam, + OrderCancellationRequest, + OrderFillRequest, + LogErrorContractEventArgs, + LogCancelContractEventArgs, + LogFillContractEventArgs, + ExchangeContractEventArgs, + TransferContractEventArgs, + ApprovalContractEventArgs, + TokenContractEventArgs, + EtherTokenContractEventArgs, + WithdrawalContractEventArgs, + DepositContractEventArgs, + ContractEventArgs, + ContractEventArg, + Web3Provider, + ZeroExConfig, + EtherTokenEvents, + TransactionReceiptWithDecodedLogs, + LogWithDecodedArgs, + MethodOpts, + OrderTransactionOpts, + TransactionOpts, + FilterObject, + LogEvent, + DecodedLogEvent, + EventWatcherCallback, + OnOrderStateChangeCallback, + OrderStateValid, + OrderStateInvalid, + OrderState, } from './types'; export { TransactionReceipt } from '@0xproject/types'; diff --git a/packages/0x.js/src/order_watcher/event_watcher.ts b/packages/0x.js/src/order_watcher/event_watcher.ts index 3e3cd978d..5d05bfb60 100644 --- a/packages/0x.js/src/order_watcher/event_watcher.ts +++ b/packages/0x.js/src/order_watcher/event_watcher.ts @@ -9,8 +9,8 @@ import { assert } from '../utils/assert'; const DEFAULT_EVENT_POLLING_INTERVAL_MS = 200; enum LogEventState { - Removed, - Added, + Removed, + Added, } /* @@ -18,76 +18,76 @@ enum LogEventState { * depth. */ export class EventWatcher { - private _web3Wrapper: Web3Wrapper; - private _pollingIntervalMs: number; - private _intervalIdIfExists?: NodeJS.Timer; - private _lastEvents: Web3.LogEntry[] = []; - constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number) { - this._web3Wrapper = web3Wrapper; - this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs) - ? DEFAULT_EVENT_POLLING_INTERVAL_MS - : pollingIntervalIfExistsMs; - } - public subscribe(callback: EventWatcherCallback): void { - assert.isFunction('callback', callback); - if (!_.isUndefined(this._intervalIdIfExists)) { - throw new Error(ZeroExError.SubscriptionAlreadyPresent); - } - this._intervalIdIfExists = intervalUtils.setAsyncExcludingInterval( - this._pollForBlockchainEventsAsync.bind(this, callback), - this._pollingIntervalMs, - (err: Error) => { - this.unsubscribe(); - callback(err); - }, - ); - } - public unsubscribe(): void { - this._lastEvents = []; - if (!_.isUndefined(this._intervalIdIfExists)) { - intervalUtils.clearAsyncExcludingInterval(this._intervalIdIfExists); - delete this._intervalIdIfExists; - } - } - private async _pollForBlockchainEventsAsync(callback: EventWatcherCallback): Promise<void> { - const pendingEvents = await this._getEventsAsync(); - if (_.isUndefined(pendingEvents)) { - // HACK: This should never happen, but happens frequently on CI due to a ganache bug - return; - } - if (pendingEvents.length === 0) { - // HACK: Sometimes when node rebuilds the pending block we get back the empty result. - // We don't want to emit a lot of removal events and bring them back after a couple of miliseconds, - // that's why we just ignore those cases. - return; - } - const removedEvents = _.differenceBy(this._lastEvents, pendingEvents, JSON.stringify); - const newEvents = _.differenceBy(pendingEvents, this._lastEvents, JSON.stringify); - await this._emitDifferencesAsync(removedEvents, LogEventState.Removed, callback); - await this._emitDifferencesAsync(newEvents, LogEventState.Added, callback); - this._lastEvents = pendingEvents; - } - private async _getEventsAsync(): Promise<Web3.LogEntry[]> { - const eventFilter = { - fromBlock: BlockParamLiteral.Pending, - toBlock: BlockParamLiteral.Pending, - }; - const events = await this._web3Wrapper.getLogsAsync(eventFilter); - return events; - } - private async _emitDifferencesAsync( - logs: Web3.LogEntry[], - logEventState: LogEventState, - callback: EventWatcherCallback, - ): Promise<void> { - for (const log of logs) { - const logEvent = { - removed: logEventState === LogEventState.Removed, - ...log, - }; - if (!_.isUndefined(this._intervalIdIfExists)) { - callback(null, logEvent); - } - } - } + private _web3Wrapper: Web3Wrapper; + private _pollingIntervalMs: number; + private _intervalIdIfExists?: NodeJS.Timer; + private _lastEvents: Web3.LogEntry[] = []; + constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number) { + this._web3Wrapper = web3Wrapper; + this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs) + ? DEFAULT_EVENT_POLLING_INTERVAL_MS + : pollingIntervalIfExistsMs; + } + public subscribe(callback: EventWatcherCallback): void { + assert.isFunction('callback', callback); + if (!_.isUndefined(this._intervalIdIfExists)) { + throw new Error(ZeroExError.SubscriptionAlreadyPresent); + } + this._intervalIdIfExists = intervalUtils.setAsyncExcludingInterval( + this._pollForBlockchainEventsAsync.bind(this, callback), + this._pollingIntervalMs, + (err: Error) => { + this.unsubscribe(); + callback(err); + }, + ); + } + public unsubscribe(): void { + this._lastEvents = []; + if (!_.isUndefined(this._intervalIdIfExists)) { + intervalUtils.clearAsyncExcludingInterval(this._intervalIdIfExists); + delete this._intervalIdIfExists; + } + } + private async _pollForBlockchainEventsAsync(callback: EventWatcherCallback): Promise<void> { + const pendingEvents = await this._getEventsAsync(); + if (_.isUndefined(pendingEvents)) { + // HACK: This should never happen, but happens frequently on CI due to a ganache bug + return; + } + if (pendingEvents.length === 0) { + // HACK: Sometimes when node rebuilds the pending block we get back the empty result. + // We don't want to emit a lot of removal events and bring them back after a couple of miliseconds, + // that's why we just ignore those cases. + return; + } + const removedEvents = _.differenceBy(this._lastEvents, pendingEvents, JSON.stringify); + const newEvents = _.differenceBy(pendingEvents, this._lastEvents, JSON.stringify); + await this._emitDifferencesAsync(removedEvents, LogEventState.Removed, callback); + await this._emitDifferencesAsync(newEvents, LogEventState.Added, callback); + this._lastEvents = pendingEvents; + } + private async _getEventsAsync(): Promise<Web3.LogEntry[]> { + const eventFilter = { + fromBlock: BlockParamLiteral.Pending, + toBlock: BlockParamLiteral.Pending, + }; + const events = await this._web3Wrapper.getLogsAsync(eventFilter); + return events; + } + private async _emitDifferencesAsync( + logs: Web3.LogEntry[], + logEventState: LogEventState, + callback: EventWatcherCallback, + ): Promise<void> { + for (const log of logs) { + const logEvent = { + removed: logEventState === LogEventState.Removed, + ...log, + }; + if (!_.isUndefined(this._intervalIdIfExists)) { + callback(null, logEvent); + } + } + } } diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index a08de94c0..00b62162d 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -13,63 +13,63 @@ const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50; * It stores them in a min heap by expiration time and checks for expired ones every `orderExpirationCheckingIntervalMs` */ export class ExpirationWatcher { - private _orderHashByExpirationRBTree: RBTree<string>; - private _expiration: { [orderHash: string]: BigNumber } = {}; - private _orderExpirationCheckingIntervalMs: number; - private _expirationMarginMs: number; - private _orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer; - constructor(expirationMarginIfExistsMs?: number, orderExpirationCheckingIntervalIfExistsMs?: number) { - this._expirationMarginMs = expirationMarginIfExistsMs || DEFAULT_EXPIRATION_MARGIN_MS; - this._orderExpirationCheckingIntervalMs = - expirationMarginIfExistsMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; - const scoreFunction = (orderHash: string) => this._expiration[orderHash].toNumber(); - const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs); - this._orderHashByExpirationRBTree = new RBTree(comparator); - } - public subscribe(callback: (orderHash: string) => void): void { - if (!_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) { - throw new Error(ZeroExError.SubscriptionAlreadyPresent); - } - this._orderExpirationCheckingIntervalIdIfExists = intervalUtils.setInterval( - this._pruneExpiredOrders.bind(this, callback), - this._orderExpirationCheckingIntervalMs, - _.noop, // _pruneExpiredOrders never throws - ); - } - public unsubscribe(): void { - if (_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) { - throw new Error(ZeroExError.SubscriptionNotFound); - } - intervalUtils.clearInterval(this._orderExpirationCheckingIntervalIdIfExists); - delete this._orderExpirationCheckingIntervalIdIfExists; - } - public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void { - this._expiration[orderHash] = expirationUnixTimestampMs; - this._orderHashByExpirationRBTree.insert(orderHash); - } - public removeOrder(orderHash: string): void { - this._orderHashByExpirationRBTree.remove(orderHash); - delete this._expiration[orderHash]; - } - private _pruneExpiredOrders(callback: (orderHash: string) => void): void { - const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); - while (true) { - const hasTrakedOrders = this._orderHashByExpirationRBTree.size === 0; - if (hasTrakedOrders) { - break; - } - const nextOrderHashToExpire = this._orderHashByExpirationRBTree.min(); - const hasNoExpiredOrders = this._expiration[nextOrderHashToExpire].greaterThan( - currentUnixTimestampMs.plus(this._expirationMarginMs), - ); - const isSubscriptionActive = _.isUndefined(this._orderExpirationCheckingIntervalIdIfExists); - if (hasNoExpiredOrders || isSubscriptionActive) { - break; - } - const orderHash = this._orderHashByExpirationRBTree.min(); - this._orderHashByExpirationRBTree.remove(orderHash); - delete this._expiration[orderHash]; - callback(orderHash); - } - } + private _orderHashByExpirationRBTree: RBTree<string>; + private _expiration: { [orderHash: string]: BigNumber } = {}; + private _orderExpirationCheckingIntervalMs: number; + private _expirationMarginMs: number; + private _orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer; + constructor(expirationMarginIfExistsMs?: number, orderExpirationCheckingIntervalIfExistsMs?: number) { + this._expirationMarginMs = expirationMarginIfExistsMs || DEFAULT_EXPIRATION_MARGIN_MS; + this._orderExpirationCheckingIntervalMs = + expirationMarginIfExistsMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; + const scoreFunction = (orderHash: string) => this._expiration[orderHash].toNumber(); + const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs); + this._orderHashByExpirationRBTree = new RBTree(comparator); + } + public subscribe(callback: (orderHash: string) => void): void { + if (!_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) { + throw new Error(ZeroExError.SubscriptionAlreadyPresent); + } + this._orderExpirationCheckingIntervalIdIfExists = intervalUtils.setInterval( + this._pruneExpiredOrders.bind(this, callback), + this._orderExpirationCheckingIntervalMs, + _.noop, // _pruneExpiredOrders never throws + ); + } + public unsubscribe(): void { + if (_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) { + throw new Error(ZeroExError.SubscriptionNotFound); + } + intervalUtils.clearInterval(this._orderExpirationCheckingIntervalIdIfExists); + delete this._orderExpirationCheckingIntervalIdIfExists; + } + public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void { + this._expiration[orderHash] = expirationUnixTimestampMs; + this._orderHashByExpirationRBTree.insert(orderHash); + } + public removeOrder(orderHash: string): void { + this._orderHashByExpirationRBTree.remove(orderHash); + delete this._expiration[orderHash]; + } + private _pruneExpiredOrders(callback: (orderHash: string) => void): void { + const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); + while (true) { + const hasTrakedOrders = this._orderHashByExpirationRBTree.size === 0; + if (hasTrakedOrders) { + break; + } + const nextOrderHashToExpire = this._orderHashByExpirationRBTree.min(); + const hasNoExpiredOrders = this._expiration[nextOrderHashToExpire].greaterThan( + currentUnixTimestampMs.plus(this._expirationMarginMs), + ); + const isSubscriptionActive = _.isUndefined(this._orderExpirationCheckingIntervalIdIfExists); + if (hasNoExpiredOrders || isSubscriptionActive) { + break; + } + const orderHash = this._orderHashByExpirationRBTree.min(); + this._orderHashByExpirationRBTree.remove(orderHash); + delete this._expiration[orderHash]; + callback(orderHash); + } + } } diff --git a/packages/0x.js/src/order_watcher/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts index 2c5da6b57..12ac60960 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -9,25 +9,25 @@ import { TokenWrapper } from '../contract_wrappers/token_wrapper'; import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store'; import { OrderFilledCancelledLazyStore } from '../stores/order_filled_cancelled_lazy_store'; import { - ApprovalContractEventArgs, - BlockParamLiteral, - ContractEventArgs, - DepositContractEventArgs, - EtherTokenEvents, - ExchangeContractErrs, - ExchangeEvents, - LogCancelContractEventArgs, - LogEvent, - LogFillContractEventArgs, - LogWithDecodedArgs, - OnOrderStateChangeCallback, - OrderState, - OrderStateWatcherConfig, - SignedOrder, - TokenEvents, - TransferContractEventArgs, - WithdrawalContractEventArgs, - ZeroExError, + ApprovalContractEventArgs, + BlockParamLiteral, + ContractEventArgs, + DepositContractEventArgs, + EtherTokenEvents, + ExchangeContractErrs, + ExchangeEvents, + LogCancelContractEventArgs, + LogEvent, + LogFillContractEventArgs, + LogWithDecodedArgs, + OnOrderStateChangeCallback, + OrderState, + OrderStateWatcherConfig, + SignedOrder, + TokenEvents, + TransferContractEventArgs, + WithdrawalContractEventArgs, + ZeroExError, } from '../types'; import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; @@ -38,17 +38,17 @@ import { EventWatcher } from './event_watcher'; import { ExpirationWatcher } from './expiration_watcher'; interface DependentOrderHashes { - [makerAddress: string]: { - [makerToken: string]: Set<string>; - }; + [makerAddress: string]: { + [makerToken: string]: Set<string>; + }; } interface OrderByOrderHash { - [orderHash: string]: SignedOrder; + [orderHash: string]: SignedOrder; } interface OrderStateByOrderHash { - [orderHash: string]: OrderState; + [orderHash: string]: OrderState; } const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h @@ -60,319 +60,319 @@ const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h * the order should be deemed invalid. */ export class OrderStateWatcher { - private _orderStateByOrderHashCache: OrderStateByOrderHash = {}; - private _orderByOrderHash: OrderByOrderHash = {}; - private _dependentOrderHashes: DependentOrderHashes = {}; - private _callbackIfExists?: OnOrderStateChangeCallback; - private _eventWatcher: EventWatcher; - private _web3Wrapper: Web3Wrapper; - private _abiDecoder: AbiDecoder; - private _expirationWatcher: ExpirationWatcher; - private _orderStateUtils: OrderStateUtils; - private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; - private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; - private _cleanupJobInterval: number; - private _cleanupJobIntervalIdIfExists?: NodeJS.Timer; - constructor( - web3Wrapper: Web3Wrapper, - abiDecoder: AbiDecoder, - token: TokenWrapper, - exchange: ExchangeWrapper, - config?: OrderStateWatcherConfig, - ) { - this._abiDecoder = abiDecoder; - this._web3Wrapper = web3Wrapper; - const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs; - this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs); - this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( - token, - BlockParamLiteral.Pending, - ); - this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange); - this._orderStateUtils = new OrderStateUtils( - this._balanceAndProxyAllowanceLazyStore, - this._orderFilledCancelledLazyStore, - ); - const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config) - ? undefined - : config.orderExpirationCheckingIntervalMs; - const expirationMarginIfExistsMs = _.isUndefined(config) ? undefined : config.expirationMarginMs; - this._expirationWatcher = new ExpirationWatcher( - expirationMarginIfExistsMs, - orderExpirationCheckingIntervalMsIfExists, - ); - this._cleanupJobInterval = - _.isUndefined(config) || _.isUndefined(config.cleanupJobIntervalMs) - ? DEFAULT_CLEANUP_JOB_INTERVAL_MS - : config.cleanupJobIntervalMs; - } - /** - * Add an order to the orderStateWatcher. Before the order is added, it's - * signature is verified. - * @param signedOrder The order you wish to start watching. - */ - public addOrder(signedOrder: SignedOrder): void { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker); - this._orderByOrderHash[orderHash] = signedOrder; - this._addToDependentOrderHashes(signedOrder, orderHash); - const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000); - this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs); - } - /** - * Removes an order from the orderStateWatcher - * @param orderHash The orderHash of the order you wish to stop watching. - */ - public removeOrder(orderHash: string): void { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const signedOrder = this._orderByOrderHash[orderHash]; - if (_.isUndefined(signedOrder)) { - return; // noop - } - delete this._orderByOrderHash[orderHash]; - delete this._orderStateByOrderHashCache[orderHash]; - const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; - const zrxTokenAddress = exchange.getZRXTokenAddress(); - this._removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash); - this._removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash); - this._expirationWatcher.removeOrder(orderHash); - } - /** - * Starts an orderStateWatcher subscription. The callback will be called every time a watched order's - * backing blockchain state has changed. This is a call-to-action for the caller to re-validate the order. - * @param callback Receives the orderHash of the order that should be re-validated, together - * with all the order-relevant blockchain state needed to re-validate the order. - */ - public subscribe(callback: OnOrderStateChangeCallback): void { - assert.isFunction('callback', callback); - if (!_.isUndefined(this._callbackIfExists)) { - throw new Error(ZeroExError.SubscriptionAlreadyPresent); - } - this._callbackIfExists = callback; - this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this)); - this._expirationWatcher.subscribe(this._onOrderExpired.bind(this)); - this._cleanupJobIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval( - this._cleanupAsync.bind(this), - this._cleanupJobInterval, - (err: Error) => { - this.unsubscribe(); - callback(err); - }, - ); - } - /** - * Ends an orderStateWatcher subscription. - */ - public unsubscribe(): void { - if (_.isUndefined(this._callbackIfExists) || _.isUndefined(this._cleanupJobIntervalIdIfExists)) { - throw new Error(ZeroExError.SubscriptionNotFound); - } - this._balanceAndProxyAllowanceLazyStore.deleteAll(); - this._orderFilledCancelledLazyStore.deleteAll(); - delete this._callbackIfExists; - this._eventWatcher.unsubscribe(); - this._expirationWatcher.unsubscribe(); - intervalUtils.clearAsyncExcludingInterval(this._cleanupJobIntervalIdIfExists); - } - private async _cleanupAsync(): Promise<void> { - for (const orderHash of _.keys(this._orderByOrderHash)) { - this._cleanupOrderRelatedState(orderHash); - await this._emitRevalidateOrdersAsync([orderHash]); - } - } - private _cleanupOrderRelatedState(orderHash: string): void { - const signedOrder = this._orderByOrderHash[orderHash]; + private _orderStateByOrderHashCache: OrderStateByOrderHash = {}; + private _orderByOrderHash: OrderByOrderHash = {}; + private _dependentOrderHashes: DependentOrderHashes = {}; + private _callbackIfExists?: OnOrderStateChangeCallback; + private _eventWatcher: EventWatcher; + private _web3Wrapper: Web3Wrapper; + private _abiDecoder: AbiDecoder; + private _expirationWatcher: ExpirationWatcher; + private _orderStateUtils: OrderStateUtils; + private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; + private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; + private _cleanupJobInterval: number; + private _cleanupJobIntervalIdIfExists?: NodeJS.Timer; + constructor( + web3Wrapper: Web3Wrapper, + abiDecoder: AbiDecoder, + token: TokenWrapper, + exchange: ExchangeWrapper, + config?: OrderStateWatcherConfig, + ) { + this._abiDecoder = abiDecoder; + this._web3Wrapper = web3Wrapper; + const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs; + this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs); + this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( + token, + BlockParamLiteral.Pending, + ); + this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange); + this._orderStateUtils = new OrderStateUtils( + this._balanceAndProxyAllowanceLazyStore, + this._orderFilledCancelledLazyStore, + ); + const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config) + ? undefined + : config.orderExpirationCheckingIntervalMs; + const expirationMarginIfExistsMs = _.isUndefined(config) ? undefined : config.expirationMarginMs; + this._expirationWatcher = new ExpirationWatcher( + expirationMarginIfExistsMs, + orderExpirationCheckingIntervalMsIfExists, + ); + this._cleanupJobInterval = + _.isUndefined(config) || _.isUndefined(config.cleanupJobIntervalMs) + ? DEFAULT_CLEANUP_JOB_INTERVAL_MS + : config.cleanupJobIntervalMs; + } + /** + * Add an order to the orderStateWatcher. Before the order is added, it's + * signature is verified. + * @param signedOrder The order you wish to start watching. + */ + public addOrder(signedOrder: SignedOrder): void { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker); + this._orderByOrderHash[orderHash] = signedOrder; + this._addToDependentOrderHashes(signedOrder, orderHash); + const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000); + this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs); + } + /** + * Removes an order from the orderStateWatcher + * @param orderHash The orderHash of the order you wish to stop watching. + */ + public removeOrder(orderHash: string): void { + assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); + const signedOrder = this._orderByOrderHash[orderHash]; + if (_.isUndefined(signedOrder)) { + return; // noop + } + delete this._orderByOrderHash[orderHash]; + delete this._orderStateByOrderHashCache[orderHash]; + const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; + const zrxTokenAddress = exchange.getZRXTokenAddress(); + this._removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash); + this._removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash); + this._expirationWatcher.removeOrder(orderHash); + } + /** + * Starts an orderStateWatcher subscription. The callback will be called every time a watched order's + * backing blockchain state has changed. This is a call-to-action for the caller to re-validate the order. + * @param callback Receives the orderHash of the order that should be re-validated, together + * with all the order-relevant blockchain state needed to re-validate the order. + */ + public subscribe(callback: OnOrderStateChangeCallback): void { + assert.isFunction('callback', callback); + if (!_.isUndefined(this._callbackIfExists)) { + throw new Error(ZeroExError.SubscriptionAlreadyPresent); + } + this._callbackIfExists = callback; + this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this)); + this._expirationWatcher.subscribe(this._onOrderExpired.bind(this)); + this._cleanupJobIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval( + this._cleanupAsync.bind(this), + this._cleanupJobInterval, + (err: Error) => { + this.unsubscribe(); + callback(err); + }, + ); + } + /** + * Ends an orderStateWatcher subscription. + */ + public unsubscribe(): void { + if (_.isUndefined(this._callbackIfExists) || _.isUndefined(this._cleanupJobIntervalIdIfExists)) { + throw new Error(ZeroExError.SubscriptionNotFound); + } + this._balanceAndProxyAllowanceLazyStore.deleteAll(); + this._orderFilledCancelledLazyStore.deleteAll(); + delete this._callbackIfExists; + this._eventWatcher.unsubscribe(); + this._expirationWatcher.unsubscribe(); + intervalUtils.clearAsyncExcludingInterval(this._cleanupJobIntervalIdIfExists); + } + private async _cleanupAsync(): Promise<void> { + for (const orderHash of _.keys(this._orderByOrderHash)) { + this._cleanupOrderRelatedState(orderHash); + await this._emitRevalidateOrdersAsync([orderHash]); + } + } + private _cleanupOrderRelatedState(orderHash: string): void { + const signedOrder = this._orderByOrderHash[orderHash]; - this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(orderHash); - this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(orderHash); + this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(orderHash); + this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(orderHash); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.makerTokenAddress, signedOrder.maker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.makerTokenAddress, signedOrder.maker); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.takerTokenAddress, signedOrder.taker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.takerTokenAddress, signedOrder.taker); + this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.makerTokenAddress, signedOrder.maker); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.makerTokenAddress, signedOrder.maker); + this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.takerTokenAddress, signedOrder.taker); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.takerTokenAddress, signedOrder.taker); - const zrxTokenAddress = this._getZRXTokenAddress(); - if (!signedOrder.makerFee.isZero()) { - this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.maker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.maker); - } - if (!signedOrder.takerFee.isZero()) { - this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.taker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.taker); - } - } - private _onOrderExpired(orderHash: string): void { - const orderState: OrderState = { - isValid: false, - orderHash, - error: ExchangeContractErrs.OrderFillExpired, - }; - if (!_.isUndefined(this._orderByOrderHash[orderHash])) { - this.removeOrder(orderHash); - if (!_.isUndefined(this._callbackIfExists)) { - this._callbackIfExists(null, orderState); - } - } - } - private async _onEventWatcherCallbackAsync(err: Error | null, logIfExists?: LogEvent): Promise<void> { - if (!_.isNull(err)) { - if (!_.isUndefined(this._callbackIfExists)) { - this._callbackIfExists(err); - this.unsubscribe(); - } - return; - } - const log = logIfExists as LogEvent; // At this moment we are sure that no error occured and log is defined. - const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log); - const isLogDecoded = !_.isUndefined((maybeDecodedLog as LogWithDecodedArgs<any>).event); - if (!isLogDecoded) { - return; // noop - } - const decodedLog = maybeDecodedLog as LogWithDecodedArgs<ContractEventArgs>; - let makerToken: string; - let makerAddress: string; - switch (decodedLog.event) { - case TokenEvents.Approval: { - // Invalidate cache - const args = decodedLog.args as ApprovalContractEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(decodedLog.address, args._owner); - // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._owner; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); - await this._emitRevalidateOrdersAsync(orderHashes); - } - break; - } - case TokenEvents.Transfer: { - // Invalidate cache - const args = decodedLog.args as TransferContractEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._from); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._to); - // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._from; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); - await this._emitRevalidateOrdersAsync(orderHashes); - } - break; - } - case EtherTokenEvents.Deposit: { - // Invalidate cache - const args = decodedLog.args as DepositContractEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner); - // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._owner; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); - await this._emitRevalidateOrdersAsync(orderHashes); - } - break; - } - case EtherTokenEvents.Withdrawal: { - // Invalidate cache - const args = decodedLog.args as WithdrawalContractEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner); - // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._owner; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); - await this._emitRevalidateOrdersAsync(orderHashes); - } - break; - } - case ExchangeEvents.LogFill: { - // Invalidate cache - const args = decodedLog.args as LogFillContractEventArgs; - this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(args.orderHash); - // Revalidate orders - const orderHash = args.orderHash; - const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]); - if (isOrderWatched) { - await this._emitRevalidateOrdersAsync([orderHash]); - } - break; - } - case ExchangeEvents.LogCancel: { - // Invalidate cache - const args = decodedLog.args as LogCancelContractEventArgs; - this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(args.orderHash); - // Revalidate orders - const orderHash = args.orderHash; - const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]); - if (isOrderWatched) { - await this._emitRevalidateOrdersAsync([orderHash]); - } - break; - } - case ExchangeEvents.LogError: - return; // noop + const zrxTokenAddress = this._getZRXTokenAddress(); + if (!signedOrder.makerFee.isZero()) { + this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.maker); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.maker); + } + if (!signedOrder.takerFee.isZero()) { + this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.taker); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.taker); + } + } + private _onOrderExpired(orderHash: string): void { + const orderState: OrderState = { + isValid: false, + orderHash, + error: ExchangeContractErrs.OrderFillExpired, + }; + if (!_.isUndefined(this._orderByOrderHash[orderHash])) { + this.removeOrder(orderHash); + if (!_.isUndefined(this._callbackIfExists)) { + this._callbackIfExists(null, orderState); + } + } + } + private async _onEventWatcherCallbackAsync(err: Error | null, logIfExists?: LogEvent): Promise<void> { + if (!_.isNull(err)) { + if (!_.isUndefined(this._callbackIfExists)) { + this._callbackIfExists(err); + this.unsubscribe(); + } + return; + } + const log = logIfExists as LogEvent; // At this moment we are sure that no error occured and log is defined. + const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log); + const isLogDecoded = !_.isUndefined((maybeDecodedLog as LogWithDecodedArgs<any>).event); + if (!isLogDecoded) { + return; // noop + } + const decodedLog = maybeDecodedLog as LogWithDecodedArgs<ContractEventArgs>; + let makerToken: string; + let makerAddress: string; + switch (decodedLog.event) { + case TokenEvents.Approval: { + // Invalidate cache + const args = decodedLog.args as ApprovalContractEventArgs; + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(decodedLog.address, args._owner); + // Revalidate orders + makerToken = decodedLog.address; + makerAddress = args._owner; + if ( + !_.isUndefined(this._dependentOrderHashes[makerAddress]) && + !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) + ) { + const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); + await this._emitRevalidateOrdersAsync(orderHashes); + } + break; + } + case TokenEvents.Transfer: { + // Invalidate cache + const args = decodedLog.args as TransferContractEventArgs; + this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._from); + this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._to); + // Revalidate orders + makerToken = decodedLog.address; + makerAddress = args._from; + if ( + !_.isUndefined(this._dependentOrderHashes[makerAddress]) && + !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) + ) { + const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); + await this._emitRevalidateOrdersAsync(orderHashes); + } + break; + } + case EtherTokenEvents.Deposit: { + // Invalidate cache + const args = decodedLog.args as DepositContractEventArgs; + this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner); + // Revalidate orders + makerToken = decodedLog.address; + makerAddress = args._owner; + if ( + !_.isUndefined(this._dependentOrderHashes[makerAddress]) && + !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) + ) { + const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); + await this._emitRevalidateOrdersAsync(orderHashes); + } + break; + } + case EtherTokenEvents.Withdrawal: { + // Invalidate cache + const args = decodedLog.args as WithdrawalContractEventArgs; + this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner); + // Revalidate orders + makerToken = decodedLog.address; + makerAddress = args._owner; + if ( + !_.isUndefined(this._dependentOrderHashes[makerAddress]) && + !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) + ) { + const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); + await this._emitRevalidateOrdersAsync(orderHashes); + } + break; + } + case ExchangeEvents.LogFill: { + // Invalidate cache + const args = decodedLog.args as LogFillContractEventArgs; + this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(args.orderHash); + // Revalidate orders + const orderHash = args.orderHash; + const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]); + if (isOrderWatched) { + await this._emitRevalidateOrdersAsync([orderHash]); + } + break; + } + case ExchangeEvents.LogCancel: { + // Invalidate cache + const args = decodedLog.args as LogCancelContractEventArgs; + this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(args.orderHash); + // Revalidate orders + const orderHash = args.orderHash; + const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]); + if (isOrderWatched) { + await this._emitRevalidateOrdersAsync([orderHash]); + } + break; + } + case ExchangeEvents.LogError: + return; // noop - default: - throw utils.spawnSwitchErr('decodedLog.event', decodedLog.event); - } - } - private async _emitRevalidateOrdersAsync(orderHashes: string[]): Promise<void> { - for (const orderHash of orderHashes) { - const signedOrder = this._orderByOrderHash[orderHash]; - // Most of these calls will never reach the network because the data is fetched from stores - // and only updated when cache is invalidated - const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder); - if (_.isUndefined(this._callbackIfExists)) { - break; // Unsubscribe was called - } - if (_.isEqual(orderState, this._orderStateByOrderHashCache[orderHash])) { - // Actual order state didn't change - continue; - } else { - this._orderStateByOrderHashCache[orderHash] = orderState; - } - this._callbackIfExists(null, orderState); - } - } - private _addToDependentOrderHashes(signedOrder: SignedOrder, orderHash: string): void { - if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker])) { - this._dependentOrderHashes[signedOrder.maker] = {}; - } - if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress])) { - this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set(); - } - this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash); - const zrxTokenAddress = this._getZRXTokenAddress(); - if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress])) { - this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress] = new Set(); - } - this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress].add(orderHash); - } - private _removeFromDependentOrderHashes(makerAddress: string, tokenAddress: string, orderHash: string) { - this._dependentOrderHashes[makerAddress][tokenAddress].delete(orderHash); - if (this._dependentOrderHashes[makerAddress][tokenAddress].size === 0) { - delete this._dependentOrderHashes[makerAddress][tokenAddress]; - } - if (_.isEmpty(this._dependentOrderHashes[makerAddress])) { - delete this._dependentOrderHashes[makerAddress]; - } - } - private _getZRXTokenAddress(): string { - const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; - const zrxTokenAddress = exchange.getZRXTokenAddress(); - return zrxTokenAddress; - } + default: + throw utils.spawnSwitchErr('decodedLog.event', decodedLog.event); + } + } + private async _emitRevalidateOrdersAsync(orderHashes: string[]): Promise<void> { + for (const orderHash of orderHashes) { + const signedOrder = this._orderByOrderHash[orderHash]; + // Most of these calls will never reach the network because the data is fetched from stores + // and only updated when cache is invalidated + const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder); + if (_.isUndefined(this._callbackIfExists)) { + break; // Unsubscribe was called + } + if (_.isEqual(orderState, this._orderStateByOrderHashCache[orderHash])) { + // Actual order state didn't change + continue; + } else { + this._orderStateByOrderHashCache[orderHash] = orderState; + } + this._callbackIfExists(null, orderState); + } + } + private _addToDependentOrderHashes(signedOrder: SignedOrder, orderHash: string): void { + if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker])) { + this._dependentOrderHashes[signedOrder.maker] = {}; + } + if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress])) { + this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set(); + } + this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash); + const zrxTokenAddress = this._getZRXTokenAddress(); + if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress])) { + this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress] = new Set(); + } + this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress].add(orderHash); + } + private _removeFromDependentOrderHashes(makerAddress: string, tokenAddress: string, orderHash: string) { + this._dependentOrderHashes[makerAddress][tokenAddress].delete(orderHash); + if (this._dependentOrderHashes[makerAddress][tokenAddress].size === 0) { + delete this._dependentOrderHashes[makerAddress][tokenAddress]; + } + if (_.isEmpty(this._dependentOrderHashes[makerAddress])) { + delete this._dependentOrderHashes[makerAddress]; + } + } + private _getZRXTokenAddress(): string { + const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; + const zrxTokenAddress = exchange.getZRXTokenAddress(); + return zrxTokenAddress; + } } diff --git a/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts b/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts index e010092af..20b09d606 100644 --- a/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts +++ b/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts @@ -3,94 +3,94 @@ import { BigNumber } from '@0xproject/utils'; import { SignedOrder } from '../types'; export class RemainingFillableCalculator { - private _signedOrder: SignedOrder; - private _isMakerTokenZRX: boolean; - // Transferrable Amount is the minimum of Approval and Balance - private _transferrableMakerTokenAmount: BigNumber; - private _transferrableMakerFeeTokenAmount: BigNumber; - private _remainingMakerTokenAmount: BigNumber; - private _remainingMakerFeeAmount: BigNumber; - constructor( - signedOrder: SignedOrder, - isMakerTokenZRX: boolean, - transferrableMakerTokenAmount: BigNumber, - transferrableMakerFeeTokenAmount: BigNumber, - remainingMakerTokenAmount: BigNumber, - ) { - this._signedOrder = signedOrder; - this._isMakerTokenZRX = isMakerTokenZRX; - this._transferrableMakerTokenAmount = transferrableMakerTokenAmount; - this._transferrableMakerFeeTokenAmount = transferrableMakerFeeTokenAmount; - this._remainingMakerTokenAmount = remainingMakerTokenAmount; - this._remainingMakerFeeAmount = remainingMakerTokenAmount - .times(signedOrder.makerFee) - .dividedToIntegerBy(signedOrder.makerTokenAmount); - } - public computeRemainingMakerFillable(): BigNumber { - if (this._hasSufficientFundsForFeeAndTransferAmount()) { - return this._remainingMakerTokenAmount; - } - if (this._signedOrder.makerFee.isZero()) { - return BigNumber.min(this._remainingMakerTokenAmount, this._transferrableMakerTokenAmount); - } - return this._calculatePartiallyFillableMakerTokenAmount(); - } - public computeRemainingTakerFillable(): BigNumber { - return this.computeRemainingMakerFillable() - .times(this._signedOrder.takerTokenAmount) - .dividedToIntegerBy(this._signedOrder.makerTokenAmount); - } - private _hasSufficientFundsForFeeAndTransferAmount(): boolean { - if (this._isMakerTokenZRX) { - const totalZRXTransferAmountRequired = this._remainingMakerTokenAmount.plus(this._remainingMakerFeeAmount); - const hasSufficientFunds = this._transferrableMakerTokenAmount.greaterThanOrEqualTo( - totalZRXTransferAmountRequired, - ); - return hasSufficientFunds; - } else { - const hasSufficientFundsForTransferAmount = this._transferrableMakerTokenAmount.greaterThanOrEqualTo( - this._remainingMakerTokenAmount, - ); - const hasSufficientFundsForFeeAmount = this._transferrableMakerFeeTokenAmount.greaterThanOrEqualTo( - this._remainingMakerFeeAmount, - ); - const hasSufficientFunds = hasSufficientFundsForTransferAmount && hasSufficientFundsForFeeAmount; - return hasSufficientFunds; - } - } - private _calculatePartiallyFillableMakerTokenAmount(): BigNumber { - // Given an order for 200 wei for 2 ZRXwei fee, find 100 wei for 1 ZRXwei. Order ratio is then 100:1 - const orderToFeeRatio = this._signedOrder.makerTokenAmount.dividedBy(this._signedOrder.makerFee); - // The number of times the maker can fill the order, if each fill only required the transfer of a single - // baseUnit of fee tokens. - // Given 2 ZRXwei, the maximum amount of times Maker can fill this order, in terms of fees, is 2 - const fillableTimesInFeeTokenBaseUnits = BigNumber.min( - this._transferrableMakerFeeTokenAmount, - this._remainingMakerFeeAmount, - ); - // The number of times the Maker can fill the order, given the Maker Token Balance - // Assuming a balance of 150 wei, and an orderToFeeRatio of 100:1, maker can fill this order 1 time. - let fillableTimesInMakerTokenUnits = this._transferrableMakerTokenAmount.dividedBy(orderToFeeRatio); - if (this._isMakerTokenZRX) { - // If ZRX is the maker token, the Fee and the Maker amount need to be removed from the same pool; - // 200 ZRXwei for 2ZRXwei fee can only be filled once (need 202 ZRXwei) - const totalZRXTokenPooled = this._transferrableMakerTokenAmount; - // The purchasing power here is less as the tokens are taken from the same Pool - // For every one number of fills, we have to take an extra ZRX out of the pool - fillableTimesInMakerTokenUnits = totalZRXTokenPooled.dividedBy(orderToFeeRatio.plus(new BigNumber(1))); - } - // When Ratio is not fully divisible there can be remainders which cannot be represented, so they are floored. - // This can result in a RoundingError being thrown by the Exchange Contract. - const partiallyFillableMakerTokenAmount = fillableTimesInMakerTokenUnits - .times(this._signedOrder.makerTokenAmount) - .dividedToIntegerBy(this._signedOrder.makerFee); - const partiallyFillableFeeTokenAmount = fillableTimesInFeeTokenBaseUnits - .times(this._signedOrder.makerTokenAmount) - .dividedToIntegerBy(this._signedOrder.makerFee); - const partiallyFillableAmount = BigNumber.min( - partiallyFillableMakerTokenAmount, - partiallyFillableFeeTokenAmount, - ); - return partiallyFillableAmount; - } + private _signedOrder: SignedOrder; + private _isMakerTokenZRX: boolean; + // Transferrable Amount is the minimum of Approval and Balance + private _transferrableMakerTokenAmount: BigNumber; + private _transferrableMakerFeeTokenAmount: BigNumber; + private _remainingMakerTokenAmount: BigNumber; + private _remainingMakerFeeAmount: BigNumber; + constructor( + signedOrder: SignedOrder, + isMakerTokenZRX: boolean, + transferrableMakerTokenAmount: BigNumber, + transferrableMakerFeeTokenAmount: BigNumber, + remainingMakerTokenAmount: BigNumber, + ) { + this._signedOrder = signedOrder; + this._isMakerTokenZRX = isMakerTokenZRX; + this._transferrableMakerTokenAmount = transferrableMakerTokenAmount; + this._transferrableMakerFeeTokenAmount = transferrableMakerFeeTokenAmount; + this._remainingMakerTokenAmount = remainingMakerTokenAmount; + this._remainingMakerFeeAmount = remainingMakerTokenAmount + .times(signedOrder.makerFee) + .dividedToIntegerBy(signedOrder.makerTokenAmount); + } + public computeRemainingMakerFillable(): BigNumber { + if (this._hasSufficientFundsForFeeAndTransferAmount()) { + return this._remainingMakerTokenAmount; + } + if (this._signedOrder.makerFee.isZero()) { + return BigNumber.min(this._remainingMakerTokenAmount, this._transferrableMakerTokenAmount); + } + return this._calculatePartiallyFillableMakerTokenAmount(); + } + public computeRemainingTakerFillable(): BigNumber { + return this.computeRemainingMakerFillable() + .times(this._signedOrder.takerTokenAmount) + .dividedToIntegerBy(this._signedOrder.makerTokenAmount); + } + private _hasSufficientFundsForFeeAndTransferAmount(): boolean { + if (this._isMakerTokenZRX) { + const totalZRXTransferAmountRequired = this._remainingMakerTokenAmount.plus(this._remainingMakerFeeAmount); + const hasSufficientFunds = this._transferrableMakerTokenAmount.greaterThanOrEqualTo( + totalZRXTransferAmountRequired, + ); + return hasSufficientFunds; + } else { + const hasSufficientFundsForTransferAmount = this._transferrableMakerTokenAmount.greaterThanOrEqualTo( + this._remainingMakerTokenAmount, + ); + const hasSufficientFundsForFeeAmount = this._transferrableMakerFeeTokenAmount.greaterThanOrEqualTo( + this._remainingMakerFeeAmount, + ); + const hasSufficientFunds = hasSufficientFundsForTransferAmount && hasSufficientFundsForFeeAmount; + return hasSufficientFunds; + } + } + private _calculatePartiallyFillableMakerTokenAmount(): BigNumber { + // Given an order for 200 wei for 2 ZRXwei fee, find 100 wei for 1 ZRXwei. Order ratio is then 100:1 + const orderToFeeRatio = this._signedOrder.makerTokenAmount.dividedBy(this._signedOrder.makerFee); + // The number of times the maker can fill the order, if each fill only required the transfer of a single + // baseUnit of fee tokens. + // Given 2 ZRXwei, the maximum amount of times Maker can fill this order, in terms of fees, is 2 + const fillableTimesInFeeTokenBaseUnits = BigNumber.min( + this._transferrableMakerFeeTokenAmount, + this._remainingMakerFeeAmount, + ); + // The number of times the Maker can fill the order, given the Maker Token Balance + // Assuming a balance of 150 wei, and an orderToFeeRatio of 100:1, maker can fill this order 1 time. + let fillableTimesInMakerTokenUnits = this._transferrableMakerTokenAmount.dividedBy(orderToFeeRatio); + if (this._isMakerTokenZRX) { + // If ZRX is the maker token, the Fee and the Maker amount need to be removed from the same pool; + // 200 ZRXwei for 2ZRXwei fee can only be filled once (need 202 ZRXwei) + const totalZRXTokenPooled = this._transferrableMakerTokenAmount; + // The purchasing power here is less as the tokens are taken from the same Pool + // For every one number of fills, we have to take an extra ZRX out of the pool + fillableTimesInMakerTokenUnits = totalZRXTokenPooled.dividedBy(orderToFeeRatio.plus(new BigNumber(1))); + } + // When Ratio is not fully divisible there can be remainders which cannot be represented, so they are floored. + // This can result in a RoundingError being thrown by the Exchange Contract. + const partiallyFillableMakerTokenAmount = fillableTimesInMakerTokenUnits + .times(this._signedOrder.makerTokenAmount) + .dividedToIntegerBy(this._signedOrder.makerFee); + const partiallyFillableFeeTokenAmount = fillableTimesInFeeTokenBaseUnits + .times(this._signedOrder.makerTokenAmount) + .dividedToIntegerBy(this._signedOrder.makerFee); + const partiallyFillableAmount = BigNumber.min( + partiallyFillableMakerTokenAmount, + partiallyFillableFeeTokenAmount, + ); + return partiallyFillableAmount; + } } diff --git a/packages/0x.js/src/schemas/zero_ex_config_schema.ts b/packages/0x.js/src/schemas/zero_ex_config_schema.ts index 657b24c58..546b1c2d0 100644 --- a/packages/0x.js/src/schemas/zero_ex_config_schema.ts +++ b/packages/0x.js/src/schemas/zero_ex_config_schema.ts @@ -1,27 +1,27 @@ export const zeroExConfigSchema = { - id: '/ZeroExConfig', - properties: { - networkId: { - type: 'number', - minimum: 0, - }, - gasPrice: { $ref: '/Number' }, - exchangeContractAddress: { $ref: '/Address' }, - tokenRegistryContractAddress: { $ref: '/Address' }, - orderWatcherConfig: { - type: 'object', - properties: { - pollingIntervalMs: { - type: 'number', - minimum: 0, - }, - numConfirmations: { - type: 'number', - minimum: 0, - }, - }, - }, - }, - type: 'object', - required: ['networkId'], + id: '/ZeroExConfig', + properties: { + networkId: { + type: 'number', + minimum: 0, + }, + gasPrice: { $ref: '/Number' }, + exchangeContractAddress: { $ref: '/Address' }, + tokenRegistryContractAddress: { $ref: '/Address' }, + orderWatcherConfig: { + type: 'object', + properties: { + pollingIntervalMs: { + type: 'number', + minimum: 0, + }, + numConfirmations: { + type: 'number', + minimum: 0, + }, + }, + }, + }, + type: 'object', + required: ['networkId'], }; diff --git a/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts b/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts index 379afa4bc..33feea105 100644 --- a/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts +++ b/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts @@ -8,79 +8,79 @@ import { BlockParamLiteral } from '../types'; * Copy on read store for balances/proxyAllowances of tokens/accounts */ export class BalanceAndProxyAllowanceLazyStore { - private _token: TokenWrapper; - private _defaultBlock: BlockParamLiteral; - private _balance: { - [tokenAddress: string]: { - [userAddress: string]: BigNumber; - }; - }; - private _proxyAllowance: { - [tokenAddress: string]: { - [userAddress: string]: BigNumber; - }; - }; - constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { - this._token = token; - this._defaultBlock = defaultBlock; - this._balance = {}; - this._proxyAllowance = {}; - } - public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { - if (_.isUndefined(this._balance[tokenAddress]) || _.isUndefined(this._balance[tokenAddress][userAddress])) { - const methodOpts = { - defaultBlock: this._defaultBlock, - }; - const balance = await this._token.getBalanceAsync(tokenAddress, userAddress, methodOpts); - this.setBalance(tokenAddress, userAddress, balance); - } - const cachedBalance = this._balance[tokenAddress][userAddress]; - return cachedBalance; - } - public setBalance(tokenAddress: string, userAddress: string, balance: BigNumber): void { - if (_.isUndefined(this._balance[tokenAddress])) { - this._balance[tokenAddress] = {}; - } - this._balance[tokenAddress][userAddress] = balance; - } - public deleteBalance(tokenAddress: string, userAddress: string): void { - if (!_.isUndefined(this._balance[tokenAddress])) { - delete this._balance[tokenAddress][userAddress]; - if (_.isEmpty(this._balance[tokenAddress])) { - delete this._balance[tokenAddress]; - } - } - } - public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { - if ( - _.isUndefined(this._proxyAllowance[tokenAddress]) || - _.isUndefined(this._proxyAllowance[tokenAddress][userAddress]) - ) { - const methodOpts = { - defaultBlock: this._defaultBlock, - }; - const proxyAllowance = await this._token.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts); - this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance); - } - const cachedProxyAllowance = this._proxyAllowance[tokenAddress][userAddress]; - return cachedProxyAllowance; - } - public setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber): void { - if (_.isUndefined(this._proxyAllowance[tokenAddress])) { - this._proxyAllowance[tokenAddress] = {}; - } - this._proxyAllowance[tokenAddress][userAddress] = proxyAllowance; - } - public deleteProxyAllowance(tokenAddress: string, userAddress: string): void { - if (!_.isUndefined(this._proxyAllowance[tokenAddress])) { - delete this._proxyAllowance[tokenAddress][userAddress]; - if (_.isEmpty(this._proxyAllowance[tokenAddress])) { - delete this._proxyAllowance[tokenAddress]; - } - } - } - public deleteAll(): void { - this._balance = {}; - this._proxyAllowance = {}; - } + private _token: TokenWrapper; + private _defaultBlock: BlockParamLiteral; + private _balance: { + [tokenAddress: string]: { + [userAddress: string]: BigNumber; + }; + }; + private _proxyAllowance: { + [tokenAddress: string]: { + [userAddress: string]: BigNumber; + }; + }; + constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { + this._token = token; + this._defaultBlock = defaultBlock; + this._balance = {}; + this._proxyAllowance = {}; + } + public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { + if (_.isUndefined(this._balance[tokenAddress]) || _.isUndefined(this._balance[tokenAddress][userAddress])) { + const methodOpts = { + defaultBlock: this._defaultBlock, + }; + const balance = await this._token.getBalanceAsync(tokenAddress, userAddress, methodOpts); + this.setBalance(tokenAddress, userAddress, balance); + } + const cachedBalance = this._balance[tokenAddress][userAddress]; + return cachedBalance; + } + public setBalance(tokenAddress: string, userAddress: string, balance: BigNumber): void { + if (_.isUndefined(this._balance[tokenAddress])) { + this._balance[tokenAddress] = {}; + } + this._balance[tokenAddress][userAddress] = balance; + } + public deleteBalance(tokenAddress: string, userAddress: string): void { + if (!_.isUndefined(this._balance[tokenAddress])) { + delete this._balance[tokenAddress][userAddress]; + if (_.isEmpty(this._balance[tokenAddress])) { + delete this._balance[tokenAddress]; + } + } + } + public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { + if ( + _.isUndefined(this._proxyAllowance[tokenAddress]) || + _.isUndefined(this._proxyAllowance[tokenAddress][userAddress]) + ) { + const methodOpts = { + defaultBlock: this._defaultBlock, + }; + const proxyAllowance = await this._token.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts); + this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance); + } + const cachedProxyAllowance = this._proxyAllowance[tokenAddress][userAddress]; + return cachedProxyAllowance; + } + public setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber): void { + if (_.isUndefined(this._proxyAllowance[tokenAddress])) { + this._proxyAllowance[tokenAddress] = {}; + } + this._proxyAllowance[tokenAddress][userAddress] = proxyAllowance; + } + public deleteProxyAllowance(tokenAddress: string, userAddress: string): void { + if (!_.isUndefined(this._proxyAllowance[tokenAddress])) { + delete this._proxyAllowance[tokenAddress][userAddress]; + if (_.isEmpty(this._proxyAllowance[tokenAddress])) { + delete this._proxyAllowance[tokenAddress]; + } + } + } + public deleteAll(): void { + this._balance = {}; + this._proxyAllowance = {}; + } } diff --git a/packages/0x.js/src/stores/order_filled_cancelled_lazy_store.ts b/packages/0x.js/src/stores/order_filled_cancelled_lazy_store.ts index a99db0702..e22364c09 100644 --- a/packages/0x.js/src/stores/order_filled_cancelled_lazy_store.ts +++ b/packages/0x.js/src/stores/order_filled_cancelled_lazy_store.ts @@ -8,54 +8,54 @@ import { BlockParamLiteral } from '../types'; * Copy on read store for filled/cancelled taker amounts */ export class OrderFilledCancelledLazyStore { - private _exchange: ExchangeWrapper; - private _filledTakerAmount: { - [orderHash: string]: BigNumber; - }; - private _cancelledTakerAmount: { - [orderHash: string]: BigNumber; - }; - constructor(exchange: ExchangeWrapper) { - this._exchange = exchange; - this._filledTakerAmount = {}; - this._cancelledTakerAmount = {}; - } - public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { - if (_.isUndefined(this._filledTakerAmount[orderHash])) { - const methodOpts = { - defaultBlock: BlockParamLiteral.Pending, - }; - const filledTakerAmount = await this._exchange.getFilledTakerAmountAsync(orderHash, methodOpts); - this.setFilledTakerAmount(orderHash, filledTakerAmount); - } - const cachedFilled = this._filledTakerAmount[orderHash]; - return cachedFilled; - } - public setFilledTakerAmount(orderHash: string, filledTakerAmount: BigNumber): void { - this._filledTakerAmount[orderHash] = filledTakerAmount; - } - public deleteFilledTakerAmount(orderHash: string): void { - delete this._filledTakerAmount[orderHash]; - } - public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> { - if (_.isUndefined(this._cancelledTakerAmount[orderHash])) { - const methodOpts = { - defaultBlock: BlockParamLiteral.Pending, - }; - const cancelledTakerAmount = await this._exchange.getCancelledTakerAmountAsync(orderHash, methodOpts); - this.setCancelledTakerAmount(orderHash, cancelledTakerAmount); - } - const cachedCancelled = this._cancelledTakerAmount[orderHash]; - return cachedCancelled; - } - public setCancelledTakerAmount(orderHash: string, cancelledTakerAmount: BigNumber): void { - this._cancelledTakerAmount[orderHash] = cancelledTakerAmount; - } - public deleteCancelledTakerAmount(orderHash: string): void { - delete this._cancelledTakerAmount[orderHash]; - } - public deleteAll(): void { - this._filledTakerAmount = {}; - this._cancelledTakerAmount = {}; - } + private _exchange: ExchangeWrapper; + private _filledTakerAmount: { + [orderHash: string]: BigNumber; + }; + private _cancelledTakerAmount: { + [orderHash: string]: BigNumber; + }; + constructor(exchange: ExchangeWrapper) { + this._exchange = exchange; + this._filledTakerAmount = {}; + this._cancelledTakerAmount = {}; + } + public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { + if (_.isUndefined(this._filledTakerAmount[orderHash])) { + const methodOpts = { + defaultBlock: BlockParamLiteral.Pending, + }; + const filledTakerAmount = await this._exchange.getFilledTakerAmountAsync(orderHash, methodOpts); + this.setFilledTakerAmount(orderHash, filledTakerAmount); + } + const cachedFilled = this._filledTakerAmount[orderHash]; + return cachedFilled; + } + public setFilledTakerAmount(orderHash: string, filledTakerAmount: BigNumber): void { + this._filledTakerAmount[orderHash] = filledTakerAmount; + } + public deleteFilledTakerAmount(orderHash: string): void { + delete this._filledTakerAmount[orderHash]; + } + public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> { + if (_.isUndefined(this._cancelledTakerAmount[orderHash])) { + const methodOpts = { + defaultBlock: BlockParamLiteral.Pending, + }; + const cancelledTakerAmount = await this._exchange.getCancelledTakerAmountAsync(orderHash, methodOpts); + this.setCancelledTakerAmount(orderHash, cancelledTakerAmount); + } + const cachedCancelled = this._cancelledTakerAmount[orderHash]; + return cachedCancelled; + } + public setCancelledTakerAmount(orderHash: string, cancelledTakerAmount: BigNumber): void { + this._cancelledTakerAmount[orderHash] = cancelledTakerAmount; + } + public deleteCancelledTakerAmount(orderHash: string): void { + delete this._cancelledTakerAmount[orderHash]; + } + public deleteAll(): void { + this._filledTakerAmount = {}; + this._cancelledTakerAmount = {}; + } } diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index 7eda4bea6..3c93910e9 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -3,41 +3,41 @@ import { BigNumber } from '@0xproject/utils'; import * as Web3 from 'web3'; export enum ZeroExError { - ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST', - ZRXContractDoesNotExist = 'ZRX_CONTRACT_DOES_NOT_EXIST', - EtherTokenContractDoesNotExist = 'ETHER_TOKEN_CONTRACT_DOES_NOT_EXIST', - TokenTransferProxyContractDoesNotExist = 'TOKEN_TRANSFER_PROXY_CONTRACT_DOES_NOT_EXIST', - TokenRegistryContractDoesNotExist = 'TOKEN_REGISTRY_CONTRACT_DOES_NOT_EXIST', - TokenContractDoesNotExist = 'TOKEN_CONTRACT_DOES_NOT_EXIST', - UnhandledError = 'UNHANDLED_ERROR', - UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', - InvalidSignature = 'INVALID_SIGNATURE', - ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK', - InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER', - InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER', - InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT', - InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL', - InvalidJump = 'INVALID_JUMP', - OutOfGas = 'OUT_OF_GAS', - NoNetworkId = 'NO_NETWORK_ID', - SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND', - SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT', - TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT', + ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST', + ZRXContractDoesNotExist = 'ZRX_CONTRACT_DOES_NOT_EXIST', + EtherTokenContractDoesNotExist = 'ETHER_TOKEN_CONTRACT_DOES_NOT_EXIST', + TokenTransferProxyContractDoesNotExist = 'TOKEN_TRANSFER_PROXY_CONTRACT_DOES_NOT_EXIST', + TokenRegistryContractDoesNotExist = 'TOKEN_REGISTRY_CONTRACT_DOES_NOT_EXIST', + TokenContractDoesNotExist = 'TOKEN_CONTRACT_DOES_NOT_EXIST', + UnhandledError = 'UNHANDLED_ERROR', + UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', + InvalidSignature = 'INVALID_SIGNATURE', + ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK', + InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER', + InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER', + InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT', + InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL', + InvalidJump = 'INVALID_JUMP', + OutOfGas = 'OUT_OF_GAS', + NoNetworkId = 'NO_NETWORK_ID', + SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND', + SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT', + TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT', } export enum InternalZeroExError { - NoAbiDecoder = 'NO_ABI_DECODER', - ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY', - WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY', + NoAbiDecoder = 'NO_ABI_DECODER', + ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY', + WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY', } /** * Elliptic Curve signature */ export interface ECSignature { - v: number; - r: string; - s: string; + v: number; + r: string; + s: string; } export type OrderAddresses = [string, string, string, string, string]; @@ -46,214 +46,214 @@ export type OrderValues = [BigNumber, BigNumber, BigNumber, BigNumber, BigNumber export type LogEvent = Web3.LogEntryEvent; export interface DecodedLogEvent<ArgsType> { - isRemoved: boolean; - log: LogWithDecodedArgs<ArgsType>; + isRemoved: boolean; + log: LogWithDecodedArgs<ArgsType>; } export type EventCallback<ArgsType> = (err: null | Error, log?: DecodedLogEvent<ArgsType>) => void; export type EventWatcherCallback = (err: null | Error, log?: LogEvent) => void; export enum SolidityTypes { - Address = 'address', - Uint256 = 'uint256', - Uint8 = 'uint8', - Uint = 'uint', + Address = 'address', + Uint256 = 'uint256', + Uint8 = 'uint8', + Uint = 'uint', } export enum ExchangeContractErrCodes { - ERROR_FILL_EXPIRED, // Order has already expired - ERROR_FILL_NO_VALUE, // Order has already been fully filled or cancelled - ERROR_FILL_TRUNCATION, // Rounding error too large - ERROR_FILL_BALANCE_ALLOWANCE, // Insufficient balance or allowance for token transfer - ERROR_CANCEL_EXPIRED, // Order has already expired - ERROR_CANCEL_NO_VALUE, // Order has already been fully filled or cancelled + ERROR_FILL_EXPIRED, // Order has already expired + ERROR_FILL_NO_VALUE, // Order has already been fully filled or cancelled + ERROR_FILL_TRUNCATION, // Rounding error too large + ERROR_FILL_BALANCE_ALLOWANCE, // Insufficient balance or allowance for token transfer + ERROR_CANCEL_EXPIRED, // Order has already expired + ERROR_CANCEL_NO_VALUE, // Order has already been fully filled or cancelled } export enum ExchangeContractErrs { - OrderFillExpired = 'ORDER_FILL_EXPIRED', - OrderCancelExpired = 'ORDER_CANCEL_EXPIRED', - OrderCancelAmountZero = 'ORDER_CANCEL_AMOUNT_ZERO', - OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', - OrderFillAmountZero = 'ORDER_FILL_AMOUNT_ZERO', - OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', - OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', - FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR', - InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE', - InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE', - InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE', - InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE', - InsufficientTakerFeeBalance = 'INSUFFICIENT_TAKER_FEE_BALANCE', - InsufficientTakerFeeAllowance = 'INSUFFICIENT_TAKER_FEE_ALLOWANCE', - InsufficientMakerFeeBalance = 'INSUFFICIENT_MAKER_FEE_BALANCE', - InsufficientMakerFeeAllowance = 'INSUFFICIENT_MAKER_FEE_ALLOWANCE', - TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER', - MultipleMakersInSingleCancelBatchDisallowed = 'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED', - InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT', - MultipleTakerTokensInFillUpToDisallowed = 'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED', - BatchOrdersMustHaveSameExchangeAddress = 'BATCH_ORDERS_MUST_HAVE_SAME_EXCHANGE_ADDRESS', - BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM', + OrderFillExpired = 'ORDER_FILL_EXPIRED', + OrderCancelExpired = 'ORDER_CANCEL_EXPIRED', + OrderCancelAmountZero = 'ORDER_CANCEL_AMOUNT_ZERO', + OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', + OrderFillAmountZero = 'ORDER_FILL_AMOUNT_ZERO', + OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', + OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', + FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR', + InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE', + InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE', + InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE', + InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE', + InsufficientTakerFeeBalance = 'INSUFFICIENT_TAKER_FEE_BALANCE', + InsufficientTakerFeeAllowance = 'INSUFFICIENT_TAKER_FEE_ALLOWANCE', + InsufficientMakerFeeBalance = 'INSUFFICIENT_MAKER_FEE_BALANCE', + InsufficientMakerFeeAllowance = 'INSUFFICIENT_MAKER_FEE_ALLOWANCE', + TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER', + MultipleMakersInSingleCancelBatchDisallowed = 'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED', + InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT', + MultipleTakerTokensInFillUpToDisallowed = 'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED', + BatchOrdersMustHaveSameExchangeAddress = 'BATCH_ORDERS_MUST_HAVE_SAME_EXCHANGE_ADDRESS', + BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM', } export type RawLog = Web3.LogEntry; export interface ContractEvent { - logIndex: number; - transactionIndex: number; - transactionHash: string; - blockHash: string; - blockNumber: number; - address: string; - type: string; - event: string; - args: ContractEventArgs; + logIndex: number; + transactionIndex: number; + transactionHash: string; + blockHash: string; + blockNumber: number; + address: string; + type: string; + event: string; + args: ContractEventArgs; } export interface LogFillContractEventArgs { - maker: string; - taker: string; - feeRecipient: string; - makerToken: string; - takerToken: string; - filledMakerTokenAmount: BigNumber; - filledTakerTokenAmount: BigNumber; - paidMakerFee: BigNumber; - paidTakerFee: BigNumber; - tokens: string; - orderHash: string; + maker: string; + taker: string; + feeRecipient: string; + makerToken: string; + takerToken: string; + filledMakerTokenAmount: BigNumber; + filledTakerTokenAmount: BigNumber; + paidMakerFee: BigNumber; + paidTakerFee: BigNumber; + tokens: string; + orderHash: string; } export interface LogCancelContractEventArgs { - maker: string; - feeRecipient: string; - makerToken: string; - takerToken: string; - cancelledMakerTokenAmount: BigNumber; - cancelledTakerTokenAmount: BigNumber; - tokens: string; - orderHash: string; + maker: string; + feeRecipient: string; + makerToken: string; + takerToken: string; + cancelledMakerTokenAmount: BigNumber; + cancelledTakerTokenAmount: BigNumber; + tokens: string; + orderHash: string; } export interface LogErrorContractEventArgs { - errorId: BigNumber; - orderHash: string; + errorId: BigNumber; + orderHash: string; } export type ExchangeContractEventArgs = - | LogFillContractEventArgs - | LogCancelContractEventArgs - | LogErrorContractEventArgs; + | LogFillContractEventArgs + | LogCancelContractEventArgs + | LogErrorContractEventArgs; export interface TransferContractEventArgs { - _from: string; - _to: string; - _value: BigNumber; + _from: string; + _to: string; + _value: BigNumber; } export interface ApprovalContractEventArgs { - _owner: string; - _spender: string; - _value: BigNumber; + _owner: string; + _spender: string; + _value: BigNumber; } export interface DepositContractEventArgs { - _owner: string; - _value: BigNumber; + _owner: string; + _value: BigNumber; } export interface WithdrawalContractEventArgs { - _owner: string; - _value: BigNumber; + _owner: string; + _value: BigNumber; } export type TokenContractEventArgs = TransferContractEventArgs | ApprovalContractEventArgs; export type EtherTokenContractEventArgs = - | TokenContractEventArgs - | DepositContractEventArgs - | WithdrawalContractEventArgs; + | TokenContractEventArgs + | DepositContractEventArgs + | WithdrawalContractEventArgs; export type ContractEventArgs = ExchangeContractEventArgs | TokenContractEventArgs | EtherTokenContractEventArgs; export type ContractEventArg = string | BigNumber; export interface Order { - maker: string; - taker: string; - makerFee: BigNumber; - takerFee: BigNumber; - makerTokenAmount: BigNumber; - takerTokenAmount: BigNumber; - makerTokenAddress: string; - takerTokenAddress: string; - salt: BigNumber; - exchangeContractAddress: string; - feeRecipient: string; - expirationUnixTimestampSec: BigNumber; + maker: string; + taker: string; + makerFee: BigNumber; + takerFee: BigNumber; + makerTokenAmount: BigNumber; + takerTokenAmount: BigNumber; + makerTokenAddress: string; + takerTokenAddress: string; + salt: BigNumber; + exchangeContractAddress: string; + feeRecipient: string; + expirationUnixTimestampSec: BigNumber; } export interface SignedOrder extends Order { - ecSignature: ECSignature; + ecSignature: ECSignature; } // [address, name, symbol, decimals, ipfsHash, swarmHash] export type TokenMetadata = [string, string, string, BigNumber, string, string]; export interface Token { - name: string; - address: string; - symbol: string; - decimals: number; + name: string; + address: string; + symbol: string; + decimals: number; } export interface TxOpts { - from: string; - gas?: number; - value?: BigNumber; - gasPrice?: BigNumber; + from: string; + gas?: number; + value?: BigNumber; + gasPrice?: BigNumber; } export interface TokenAddressBySymbol { - [symbol: string]: string; + [symbol: string]: string; } export enum ExchangeEvents { - LogFill = 'LogFill', - LogCancel = 'LogCancel', - LogError = 'LogError', + LogFill = 'LogFill', + LogCancel = 'LogCancel', + LogError = 'LogError', } export enum TokenEvents { - Transfer = 'Transfer', - Approval = 'Approval', + Transfer = 'Transfer', + Approval = 'Approval', } export enum EtherTokenEvents { - Transfer = 'Transfer', - Approval = 'Approval', - Deposit = 'Deposit', - Withdrawal = 'Withdrawal', + Transfer = 'Transfer', + Approval = 'Approval', + Deposit = 'Deposit', + Withdrawal = 'Withdrawal', } export type ContractEvents = TokenEvents | ExchangeEvents | EtherTokenEvents; export interface IndexedFilterValues { - [index: string]: ContractEventArg; + [index: string]: ContractEventArg; } // Earliest is omitted by design. It is simply an alias for the `0` constant and // is thus not very helpful. Moreover, this type is used in places that only accept // `latest` or `pending`. export enum BlockParamLiteral { - Latest = 'latest', - Pending = 'pending', + Latest = 'latest', + Pending = 'pending', } export type BlockParam = BlockParamLiteral | number; export interface BlockRange { - fromBlock: BlockParam; - toBlock: BlockParam; + fromBlock: BlockParam; + toBlock: BlockParam; } export type DoneCallback = (err?: Error) => void; export interface OrderCancellationRequest { - order: Order | SignedOrder; - takerTokenCancelAmount: BigNumber; + order: Order | SignedOrder; + takerTokenCancelAmount: BigNumber; } export interface OrderFillRequest { - signedOrder: SignedOrder; - takerTokenFillAmount: BigNumber; + signedOrder: SignedOrder; + takerTokenFillAmount: BigNumber; } export type AsyncMethod = (...args: any[]) => Promise<any>; @@ -268,8 +268,8 @@ export type SyncMethod = (...args: any[]) => any; export type Web3Provider = Web3.Provider; export interface JSONRPCPayload { - params: any[]; - method: string; + params: any[]; + method: string; } /* @@ -280,10 +280,10 @@ export interface JSONRPCPayload { * cleanupJobIntervalMs: How often to run a cleanup job which revalidates all the orders. Defaults: 1h */ export interface OrderStateWatcherConfig { - orderExpirationCheckingIntervalMs?: number; - eventPollingIntervalMs?: number; - expirationMarginMs?: number; - cleanupJobIntervalMs?: number; + orderExpirationCheckingIntervalMs?: number; + eventPollingIntervalMs?: number; + expirationMarginMs?: number; + cleanupJobIntervalMs?: number; } /* @@ -296,42 +296,42 @@ export interface OrderStateWatcherConfig { * orderWatcherConfig: All the configs related to the orderWatcher */ export interface ZeroExConfig { - networkId: number; - gasPrice?: BigNumber; - exchangeContractAddress?: string; - zrxContractAddress?: string; - tokenRegistryContractAddress?: string; - tokenTransferProxyContractAddress?: string; - orderWatcherConfig?: OrderStateWatcherConfig; + networkId: number; + gasPrice?: BigNumber; + exchangeContractAddress?: string; + zrxContractAddress?: string; + tokenRegistryContractAddress?: string; + tokenTransferProxyContractAddress?: string; + orderWatcherConfig?: OrderStateWatcherConfig; } export enum AbiType { - Function = 'function', - Constructor = 'constructor', - Event = 'event', - Fallback = 'fallback', + Function = 'function', + Constructor = 'constructor', + Event = 'event', + Fallback = 'fallback', } export interface DecodedLogArgs { - [argName: string]: ContractEventArg; + [argName: string]: ContractEventArg; } export interface LogWithDecodedArgs<ArgsType> extends Web3.DecodedLogEntry<ArgsType> {} export interface TransactionReceiptWithDecodedLogs extends TransactionReceipt { - logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>; + logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>; } export type ArtifactContractName = 'ZRX' | 'TokenTransferProxy' | 'TokenRegistry' | 'Token' | 'Exchange' | 'EtherToken'; export interface Artifact { - contract_name: ArtifactContractName; - abi: Web3.ContractAbi; - networks: { - [networkId: number]: { - address: string; - }; - }; + contract_name: ArtifactContractName; + abi: Web3.ContractAbi; + networks: { + [networkId: number]: { + address: string; + }; + }; } /* @@ -341,7 +341,7 @@ export interface Artifact { * allowance/balance to fill the entire remaining order amount. */ export interface ValidateOrderFillableOpts { - expectedFillTakerTokenAmount?: BigNumber; + expectedFillTakerTokenAmount?: BigNumber; } /* @@ -351,7 +351,7 @@ export interface ValidateOrderFillableOpts { * flag when running Parity). */ export interface MethodOpts { - defaultBlock?: Web3.BlockParam; + defaultBlock?: Web3.BlockParam; } /* @@ -359,8 +359,8 @@ export interface MethodOpts { * gasLimit: The amount of gas to send with a transaction */ export interface TransactionOpts { - gasPrice?: BigNumber; - gasLimit?: number; + gasPrice?: BigNumber; + gasLimit?: number; } /* @@ -368,42 +368,42 @@ export interface TransactionOpts { * broadcasting it. For example, order has a valid signature, maker has sufficient funds, etc. Default: true */ export interface OrderTransactionOpts extends TransactionOpts { - shouldValidate?: boolean; + shouldValidate?: boolean; } export type FilterObject = Web3.FilterObject; export enum TradeSide { - Maker = 'maker', - Taker = 'taker', + Maker = 'maker', + Taker = 'taker', } export enum TransferType { - Trade = 'trade', - Fee = 'fee', + Trade = 'trade', + Fee = 'fee', } export interface OrderRelevantState { - makerBalance: BigNumber; - makerProxyAllowance: BigNumber; - makerFeeBalance: BigNumber; - makerFeeProxyAllowance: BigNumber; - filledTakerTokenAmount: BigNumber; - cancelledTakerTokenAmount: BigNumber; - remainingFillableMakerTokenAmount: BigNumber; - remainingFillableTakerTokenAmount: BigNumber; + makerBalance: BigNumber; + makerProxyAllowance: BigNumber; + makerFeeBalance: BigNumber; + makerFeeProxyAllowance: BigNumber; + filledTakerTokenAmount: BigNumber; + cancelledTakerTokenAmount: BigNumber; + remainingFillableMakerTokenAmount: BigNumber; + remainingFillableTakerTokenAmount: BigNumber; } export interface OrderStateValid { - isValid: true; - orderHash: string; - orderRelevantState: OrderRelevantState; + isValid: true; + orderHash: string; + orderRelevantState: OrderRelevantState; } export interface OrderStateInvalid { - isValid: false; - orderHash: string; - error: ExchangeContractErrs; + isValid: false; + orderHash: string; + error: ExchangeContractErrs; } export type OrderState = OrderStateValid | OrderStateInvalid; diff --git a/packages/0x.js/src/utils/abi_decoder.ts b/packages/0x.js/src/utils/abi_decoder.ts index ace6dd165..bbd2a0b1d 100644 --- a/packages/0x.js/src/utils/abi_decoder.ts +++ b/packages/0x.js/src/utils/abi_decoder.ts @@ -6,67 +6,67 @@ import * as SolidityCoder from 'web3/lib/solidity/coder'; import { AbiType, ContractEventArgs, DecodedLogArgs, LogWithDecodedArgs, RawLog, SolidityTypes } from '../types'; export class AbiDecoder { - private _savedABIs: Web3.AbiDefinition[] = []; - private _methodIds: { [signatureHash: string]: Web3.EventAbi } = {}; - private static _padZeros(address: string) { - let formatted = address; - if (_.startsWith(formatted, '0x')) { - formatted = formatted.slice(2); - } + private _savedABIs: Web3.AbiDefinition[] = []; + private _methodIds: { [signatureHash: string]: Web3.EventAbi } = {}; + private static _padZeros(address: string) { + let formatted = address; + if (_.startsWith(formatted, '0x')) { + formatted = formatted.slice(2); + } - formatted = _.padStart(formatted, 40, '0'); - return `0x${formatted}`; - } - constructor(abiArrays: Web3.AbiDefinition[][]) { - _.map(abiArrays, this._addABI.bind(this)); - } - // This method can only decode logs from the 0x & ERC20 smart contracts - public tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>( - log: Web3.LogEntry, - ): LogWithDecodedArgs<ArgsType> | RawLog { - const methodId = log.topics[0]; - const event = this._methodIds[methodId]; - if (_.isUndefined(event)) { - return log; - } - const logData = log.data; - const decodedParams: DecodedLogArgs = {}; - let dataIndex = 0; - let topicsIndex = 1; + formatted = _.padStart(formatted, 40, '0'); + return `0x${formatted}`; + } + constructor(abiArrays: Web3.AbiDefinition[][]) { + _.map(abiArrays, this._addABI.bind(this)); + } + // This method can only decode logs from the 0x & ERC20 smart contracts + public tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>( + log: Web3.LogEntry, + ): LogWithDecodedArgs<ArgsType> | RawLog { + const methodId = log.topics[0]; + const event = this._methodIds[methodId]; + if (_.isUndefined(event)) { + return log; + } + const logData = log.data; + const decodedParams: DecodedLogArgs = {}; + let dataIndex = 0; + let topicsIndex = 1; - const nonIndexedInputs = _.filter(event.inputs, input => !input.indexed); - const dataTypes = _.map(nonIndexedInputs, input => input.type); - const decodedData = SolidityCoder.decodeParams(dataTypes, logData.slice('0x'.length)); + const nonIndexedInputs = _.filter(event.inputs, input => !input.indexed); + const dataTypes = _.map(nonIndexedInputs, input => input.type); + const decodedData = SolidityCoder.decodeParams(dataTypes, logData.slice('0x'.length)); - _.map(event.inputs, (param: Web3.EventParameter) => { - // Indexed parameters are stored in topics. Non-indexed ones in decodedData - let value = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++]; - if (param.type === SolidityTypes.Address) { - value = AbiDecoder._padZeros(new BigNumber(value).toString(16)); - } else if ( - param.type === SolidityTypes.Uint256 || - param.type === SolidityTypes.Uint8 || - param.type === SolidityTypes.Uint - ) { - value = new BigNumber(value); - } - decodedParams[param.name] = value; - }); + _.map(event.inputs, (param: Web3.EventParameter) => { + // Indexed parameters are stored in topics. Non-indexed ones in decodedData + let value = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++]; + if (param.type === SolidityTypes.Address) { + value = AbiDecoder._padZeros(new BigNumber(value).toString(16)); + } else if ( + param.type === SolidityTypes.Uint256 || + param.type === SolidityTypes.Uint8 || + param.type === SolidityTypes.Uint + ) { + value = new BigNumber(value); + } + decodedParams[param.name] = value; + }); - return { - ...log, - event: event.name, - args: decodedParams, - }; - } - private _addABI(abiArray: Web3.AbiDefinition[]): void { - _.map(abiArray, (abi: Web3.AbiDefinition) => { - if (abi.type === AbiType.Event) { - const signature = `${abi.name}(${_.map(abi.inputs, input => input.type).join(',')})`; - const signatureHash = new Web3().sha3(signature); - this._methodIds[signatureHash] = abi; - } - }); - this._savedABIs = this._savedABIs.concat(abiArray); - } + return { + ...log, + event: event.name, + args: decodedParams, + }; + } + private _addABI(abiArray: Web3.AbiDefinition[]): void { + _.map(abiArray, (abi: Web3.AbiDefinition) => { + if (abi.type === AbiType.Event) { + const signature = `${abi.name}(${_.map(abi.inputs, input => input.type).join(',')})`; + const signatureHash = new Web3().sha3(signature); + this._methodIds[signatureHash] = abi; + } + }); + this._savedABIs = this._savedABIs.concat(abiArray); + } } diff --git a/packages/0x.js/src/utils/assert.ts b/packages/0x.js/src/utils/assert.ts index 776ee7b79..c21f2dbca 100644 --- a/packages/0x.js/src/utils/assert.ts +++ b/packages/0x.js/src/utils/assert.ts @@ -11,25 +11,25 @@ import { ECSignature } from '../types'; import { signatureUtils } from '../utils/signature_utils'; export const assert = { - ...sharedAssert, - isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) { - const isValidSignature = signatureUtils.isValidSignature(orderHash, ecSignature, signerAddress); - this.assert(isValidSignature, `Expected order with hash '${orderHash}' to have a valid signature`); - }, - async isSenderAddressAsync( - variableName: string, - senderAddressHex: string, - web3Wrapper: Web3Wrapper, - ): Promise<void> { - sharedAssert.isETHAddressHex(variableName, senderAddressHex); - const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex); - sharedAssert.assert( - isSenderAddressAvailable, - `Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`, - ); - }, - async isUserAddressAvailableAsync(web3Wrapper: Web3Wrapper): Promise<void> { - const availableAddresses = await web3Wrapper.getAvailableAddressesAsync(); - this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 provider'); - }, + ...sharedAssert, + isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) { + const isValidSignature = signatureUtils.isValidSignature(orderHash, ecSignature, signerAddress); + this.assert(isValidSignature, `Expected order with hash '${orderHash}' to have a valid signature`); + }, + async isSenderAddressAsync( + variableName: string, + senderAddressHex: string, + web3Wrapper: Web3Wrapper, + ): Promise<void> { + sharedAssert.isETHAddressHex(variableName, senderAddressHex); + const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex); + sharedAssert.assert( + isSenderAddressAvailable, + `Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`, + ); + }, + async isUserAddressAvailableAsync(web3Wrapper: Web3Wrapper): Promise<void> { + const availableAddresses = await web3Wrapper.getAvailableAddressesAsync(); + this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 provider'); + }, }; diff --git a/packages/0x.js/src/utils/constants.ts b/packages/0x.js/src/utils/constants.ts index f32847ec2..06beec8e2 100644 --- a/packages/0x.js/src/utils/constants.ts +++ b/packages/0x.js/src/utils/constants.ts @@ -1,12 +1,12 @@ import { BigNumber } from '@0xproject/utils'; export const constants = { - NULL_ADDRESS: '0x0000000000000000000000000000000000000000', - TESTRPC_NETWORK_ID: 50, - MAX_DIGITS_IN_UNSIGNED_256_INT: 78, - INVALID_JUMP_PATTERN: 'invalid JUMP at', - OUT_OF_GAS_PATTERN: 'out of gas', - INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string', - UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), - DEFAULT_BLOCK_POLLING_INTERVAL: 1000, + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + TESTRPC_NETWORK_ID: 50, + MAX_DIGITS_IN_UNSIGNED_256_INT: 78, + INVALID_JUMP_PATTERN: 'invalid JUMP at', + OUT_OF_GAS_PATTERN: 'out of gas', + INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string', + UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), + DEFAULT_BLOCK_POLLING_INTERVAL: 1000, }; diff --git a/packages/0x.js/src/utils/decorators.ts b/packages/0x.js/src/utils/decorators.ts index c1a022a81..f774d734e 100644 --- a/packages/0x.js/src/utils/decorators.ts +++ b/packages/0x.js/src/utils/decorators.ts @@ -7,85 +7,85 @@ import { constants } from './constants'; type ErrorTransformer = (err: Error) => Error; const contractCallErrorTransformer = (error: Error) => { - if (_.includes(error.message, constants.INVALID_JUMP_PATTERN)) { - return new Error(ZeroExError.InvalidJump); - } - if (_.includes(error.message, constants.OUT_OF_GAS_PATTERN)) { - return new Error(ZeroExError.OutOfGas); - } - return error; + if (_.includes(error.message, constants.INVALID_JUMP_PATTERN)) { + return new Error(ZeroExError.InvalidJump); + } + if (_.includes(error.message, constants.OUT_OF_GAS_PATTERN)) { + return new Error(ZeroExError.OutOfGas); + } + return error; }; const schemaErrorTransformer = (error: Error) => { - if (_.includes(error.message, constants.INVALID_TAKER_FORMAT)) { - const errMsg = - 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; - return new Error(errMsg); - } - return error; + if (_.includes(error.message, constants.INVALID_TAKER_FORMAT)) { + const errMsg = + 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; + return new Error(errMsg); + } + return error; }; /** * Source: https://stackoverflow.com/a/29837695/3546986 */ const asyncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => { - const asyncErrorHandlingDecorator = ( - target: object, - key: string | symbol, - descriptor: TypedPropertyDescriptor<AsyncMethod>, - ) => { - const originalMethod = descriptor.value as AsyncMethod; + const asyncErrorHandlingDecorator = ( + target: object, + key: string | symbol, + descriptor: TypedPropertyDescriptor<AsyncMethod>, + ) => { + const originalMethod = descriptor.value as AsyncMethod; - // Do not use arrow syntax here. Use a function expression in - // order to use the correct value of `this` in this method - // tslint:disable-next-line:only-arrow-functions - descriptor.value = async function(...args: any[]) { - try { - const result = await originalMethod.apply(this, args); - return result; - } catch (error) { - const transformedError = errorTransformer(error); - throw transformedError; - } - }; + // Do not use arrow syntax here. Use a function expression in + // order to use the correct value of `this` in this method + // tslint:disable-next-line:only-arrow-functions + descriptor.value = async function(...args: any[]) { + try { + const result = await originalMethod.apply(this, args); + return result; + } catch (error) { + const transformedError = errorTransformer(error); + throw transformedError; + } + }; - return descriptor; - }; + return descriptor; + }; - return asyncErrorHandlingDecorator; + return asyncErrorHandlingDecorator; }; const syncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => { - const syncErrorHandlingDecorator = ( - target: object, - key: string | symbol, - descriptor: TypedPropertyDescriptor<SyncMethod>, - ) => { - const originalMethod = descriptor.value as SyncMethod; + const syncErrorHandlingDecorator = ( + target: object, + key: string | symbol, + descriptor: TypedPropertyDescriptor<SyncMethod>, + ) => { + const originalMethod = descriptor.value as SyncMethod; - // Do not use arrow syntax here. Use a function expression in - // order to use the correct value of `this` in this method - // tslint:disable-next-line:only-arrow-functions - descriptor.value = function(...args: any[]) { - try { - const result = originalMethod.apply(this, args); - return result; - } catch (error) { - const transformedError = errorTransformer(error); - throw transformedError; - } - }; + // Do not use arrow syntax here. Use a function expression in + // order to use the correct value of `this` in this method + // tslint:disable-next-line:only-arrow-functions + descriptor.value = function(...args: any[]) { + try { + const result = originalMethod.apply(this, args); + return result; + } catch (error) { + const transformedError = errorTransformer(error); + throw transformedError; + } + }; - return descriptor; - }; + return descriptor; + }; - return syncErrorHandlingDecorator; + return syncErrorHandlingDecorator; }; // _.flow(f, g) = f ∘ g const zeroExErrorTransformer = _.flow(schemaErrorTransformer, contractCallErrorTransformer); export const decorators = { - asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer), - syncZeroExErrorHandler: syncErrorHandlerFactory(zeroExErrorTransformer), + asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer), + syncZeroExErrorHandler: syncErrorHandlerFactory(zeroExErrorTransformer), }; diff --git a/packages/0x.js/src/utils/exchange_transfer_simulator.ts b/packages/0x.js/src/utils/exchange_transfer_simulator.ts index c9a2f87ce..662cd210c 100644 --- a/packages/0x.js/src/utils/exchange_transfer_simulator.ts +++ b/packages/0x.js/src/utils/exchange_transfer_simulator.ts @@ -6,101 +6,101 @@ import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allow import { BlockParamLiteral, ExchangeContractErrs, TradeSide, TransferType } from '../types'; enum FailureReason { - Balance = 'balance', - ProxyAllowance = 'proxyAllowance', + Balance = 'balance', + ProxyAllowance = 'proxyAllowance', } const ERR_MSG_MAPPING = { - [FailureReason.Balance]: { - [TradeSide.Maker]: { - [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance, - [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance, - }, - [TradeSide.Taker]: { - [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance, - [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance, - }, - }, - [FailureReason.ProxyAllowance]: { - [TradeSide.Maker]: { - [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance, - [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance, - }, - [TradeSide.Taker]: { - [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance, - [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance, - }, - }, + [FailureReason.Balance]: { + [TradeSide.Maker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance, + }, + [TradeSide.Taker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance, + }, + }, + [FailureReason.ProxyAllowance]: { + [TradeSide.Maker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance, + }, + [TradeSide.Taker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance, + }, + }, }; export class ExchangeTransferSimulator { - private _store: BalanceAndProxyAllowanceLazyStore; - private _UNLIMITED_ALLOWANCE_IN_BASE_UNITS: BigNumber; - private static _throwValidationError( - failureReason: FailureReason, - tradeSide: TradeSide, - transferType: TransferType, - ): never { - const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType]; - throw new Error(errMsg); - } - constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { - this._store = new BalanceAndProxyAllowanceLazyStore(token, defaultBlock); - this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; - } - /** - * Simulates transferFrom call performed by a proxy - * @param tokenAddress Address of the token to be transferred - * @param from Owner of the transferred tokens - * @param to Recipient of the transferred tokens - * @param amountInBaseUnits The amount of tokens being transferred - * @param tradeSide Is Maker/Taker transferring - * @param transferType Is it a fee payment or a value transfer - */ - public async transferFromAsync( - tokenAddress: string, - from: string, - to: string, - amountInBaseUnits: BigNumber, - tradeSide: TradeSide, - transferType: TransferType, - ): Promise<void> { - const balance = await this._store.getBalanceAsync(tokenAddress, from); - const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, from); - if (proxyAllowance.lessThan(amountInBaseUnits)) { - ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType); - } - if (balance.lessThan(amountInBaseUnits)) { - ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType); - } - await this._decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits); - await this._decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits); - await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits); - } - private async _decreaseProxyAllowanceAsync( - tokenAddress: string, - userAddress: string, - amountInBaseUnits: BigNumber, - ): Promise<void> { - const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, userAddress); - if (!proxyAllowance.eq(this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { - this._store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits)); - } - } - private async _increaseBalanceAsync( - tokenAddress: string, - userAddress: string, - amountInBaseUnits: BigNumber, - ): Promise<void> { - const balance = await this._store.getBalanceAsync(tokenAddress, userAddress); - this._store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits)); - } - private async _decreaseBalanceAsync( - tokenAddress: string, - userAddress: string, - amountInBaseUnits: BigNumber, - ): Promise<void> { - const balance = await this._store.getBalanceAsync(tokenAddress, userAddress); - this._store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits)); - } + private _store: BalanceAndProxyAllowanceLazyStore; + private _UNLIMITED_ALLOWANCE_IN_BASE_UNITS: BigNumber; + private static _throwValidationError( + failureReason: FailureReason, + tradeSide: TradeSide, + transferType: TransferType, + ): never { + const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType]; + throw new Error(errMsg); + } + constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { + this._store = new BalanceAndProxyAllowanceLazyStore(token, defaultBlock); + this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; + } + /** + * Simulates transferFrom call performed by a proxy + * @param tokenAddress Address of the token to be transferred + * @param from Owner of the transferred tokens + * @param to Recipient of the transferred tokens + * @param amountInBaseUnits The amount of tokens being transferred + * @param tradeSide Is Maker/Taker transferring + * @param transferType Is it a fee payment or a value transfer + */ + public async transferFromAsync( + tokenAddress: string, + from: string, + to: string, + amountInBaseUnits: BigNumber, + tradeSide: TradeSide, + transferType: TransferType, + ): Promise<void> { + const balance = await this._store.getBalanceAsync(tokenAddress, from); + const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, from); + if (proxyAllowance.lessThan(amountInBaseUnits)) { + ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType); + } + if (balance.lessThan(amountInBaseUnits)) { + ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType); + } + await this._decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits); + await this._decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits); + await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits); + } + private async _decreaseProxyAllowanceAsync( + tokenAddress: string, + userAddress: string, + amountInBaseUnits: BigNumber, + ): Promise<void> { + const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, userAddress); + if (!proxyAllowance.eq(this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { + this._store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits)); + } + } + private async _increaseBalanceAsync( + tokenAddress: string, + userAddress: string, + amountInBaseUnits: BigNumber, + ): Promise<void> { + const balance = await this._store.getBalanceAsync(tokenAddress, userAddress); + this._store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits)); + } + private async _decreaseBalanceAsync( + tokenAddress: string, + userAddress: string, + amountInBaseUnits: BigNumber, + ): Promise<void> { + const balance = await this._store.getBalanceAsync(tokenAddress, userAddress); + this._store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits)); + } } diff --git a/packages/0x.js/src/utils/filter_utils.ts b/packages/0x.js/src/utils/filter_utils.ts index c34ae8893..97205ace3 100644 --- a/packages/0x.js/src/utils/filter_utils.ts +++ b/packages/0x.js/src/utils/filter_utils.ts @@ -9,79 +9,79 @@ import { BlockRange, ContractEvents, IndexedFilterValues } from '../types'; const TOPIC_LENGTH = 32; export const filterUtils = { - generateUUID(): string { - return uuid(); - }, - getFilter( - address: string, - eventName: ContractEvents, - indexFilterValues: IndexedFilterValues, - abi: Web3.ContractAbi, - blockRange?: BlockRange, - ): Web3.FilterObject { - const eventAbi = _.find(abi, { name: eventName }) as Web3.EventAbi; - const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName); - const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature)); - const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues); - const topics = [topicForEventSignature, ...topicsForIndexedArgs]; - let filter: Web3.FilterObject = { - address, - topics, - }; - if (!_.isUndefined(blockRange)) { - filter = { - ...blockRange, - ...filter, - }; - } - return filter; - }, - getEventSignatureFromAbiByName(eventAbi: Web3.EventAbi, eventName: ContractEvents): string { - const types = _.map(eventAbi.inputs, 'type'); - const signature = `${eventAbi.name}(${types.join(',')})`; - return signature; - }, - getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array<string | null> { - const topics: Array<string | null> = []; - for (const eventInput of abi.inputs) { - if (!eventInput.indexed) { - continue; - } - if (_.isUndefined(indexFilterValues[eventInput.name])) { - // Null is a wildcard topic in a JSON-RPC call - topics.push(null); - } else { - const value = indexFilterValues[eventInput.name] as string; - const buffer = ethUtil.toBuffer(value); - const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH); - const topic = ethUtil.bufferToHex(paddedBuffer); - topics.push(topic); - } - } - return topics; - }, - matchesFilter(log: Web3.LogEntry, filter: Web3.FilterObject): boolean { - if (!_.isUndefined(filter.address) && log.address !== filter.address) { - return false; - } - if (!_.isUndefined(filter.topics)) { - return filterUtils.matchesTopics(log.topics, filter.topics); - } - return true; - }, - matchesTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean { - const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils)); - const matchesTopics = _.every(matchesTopic); - return matchesTopics; - }, - matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean { - if (_.isArray(filterTopic)) { - return _.includes(filterTopic, logTopic); - } - if (_.isString(filterTopic)) { - return filterTopic === logTopic; - } - // null topic is a wildcard - return true; - }, + generateUUID(): string { + return uuid(); + }, + getFilter( + address: string, + eventName: ContractEvents, + indexFilterValues: IndexedFilterValues, + abi: Web3.ContractAbi, + blockRange?: BlockRange, + ): Web3.FilterObject { + const eventAbi = _.find(abi, { name: eventName }) as Web3.EventAbi; + const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName); + const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature)); + const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues); + const topics = [topicForEventSignature, ...topicsForIndexedArgs]; + let filter: Web3.FilterObject = { + address, + topics, + }; + if (!_.isUndefined(blockRange)) { + filter = { + ...blockRange, + ...filter, + }; + } + return filter; + }, + getEventSignatureFromAbiByName(eventAbi: Web3.EventAbi, eventName: ContractEvents): string { + const types = _.map(eventAbi.inputs, 'type'); + const signature = `${eventAbi.name}(${types.join(',')})`; + return signature; + }, + getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array<string | null> { + const topics: Array<string | null> = []; + for (const eventInput of abi.inputs) { + if (!eventInput.indexed) { + continue; + } + if (_.isUndefined(indexFilterValues[eventInput.name])) { + // Null is a wildcard topic in a JSON-RPC call + topics.push(null); + } else { + const value = indexFilterValues[eventInput.name] as string; + const buffer = ethUtil.toBuffer(value); + const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH); + const topic = ethUtil.bufferToHex(paddedBuffer); + topics.push(topic); + } + } + return topics; + }, + matchesFilter(log: Web3.LogEntry, filter: Web3.FilterObject): boolean { + if (!_.isUndefined(filter.address) && log.address !== filter.address) { + return false; + } + if (!_.isUndefined(filter.topics)) { + return filterUtils.matchesTopics(log.topics, filter.topics); + } + return true; + }, + matchesTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean { + const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils)); + const matchesTopics = _.every(matchesTopic); + return matchesTopics; + }, + matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean { + if (_.isArray(filterTopic)) { + return _.includes(filterTopic, logTopic); + } + if (_.isString(filterTopic)) { + return filterTopic === logTopic; + } + // null topic is a wildcard + return true; + }, }; diff --git a/packages/0x.js/src/utils/order_state_utils.ts b/packages/0x.js/src/utils/order_state_utils.ts index 04d3b641e..b7a55ff42 100644 --- a/packages/0x.js/src/utils/order_state_utils.ts +++ b/packages/0x.js/src/utils/order_state_utils.ts @@ -7,138 +7,138 @@ import { RemainingFillableCalculator } from '../order_watcher/remaining_fillable import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store'; import { OrderFilledCancelledLazyStore } from '../stores/order_filled_cancelled_lazy_store'; import { - ExchangeContractErrs, - OrderRelevantState, - OrderState, - OrderStateInvalid, - OrderStateValid, - SignedOrder, + ExchangeContractErrs, + OrderRelevantState, + OrderState, + OrderStateInvalid, + OrderStateValid, + SignedOrder, } from '../types'; const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001; export class OrderStateUtils { - private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; - private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; - private static _validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void { - const unavailableTakerTokenAmount = orderRelevantState.cancelledTakerTokenAmount.add( - orderRelevantState.filledTakerTokenAmount, - ); - const availableTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); - if (availableTakerTokenAmount.eq(0)) { - throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); - } + private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; + private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; + private static _validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void { + const unavailableTakerTokenAmount = orderRelevantState.cancelledTakerTokenAmount.add( + orderRelevantState.filledTakerTokenAmount, + ); + const availableTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); + if (availableTakerTokenAmount.eq(0)) { + throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); + } - if (orderRelevantState.makerBalance.eq(0)) { - throw new Error(ExchangeContractErrs.InsufficientMakerBalance); - } - if (orderRelevantState.makerProxyAllowance.eq(0)) { - throw new Error(ExchangeContractErrs.InsufficientMakerAllowance); - } - if (!signedOrder.makerFee.eq(0)) { - if (orderRelevantState.makerFeeBalance.eq(0)) { - throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance); - } - if (orderRelevantState.makerFeeProxyAllowance.eq(0)) { - throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance); - } - } - const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerTokenAmount - .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR) - .dividedBy(signedOrder.makerTokenAmount); - if ( - orderRelevantState.remainingFillableTakerTokenAmount.lessThan( - minFillableTakerTokenAmountWithinNoRoundingErrorRange, - ) - ) { - throw new Error(ExchangeContractErrs.OrderFillRoundingError); - } - } - constructor( - balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore, - orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore, - ) { - this._balanceAndProxyAllowanceLazyStore = balanceAndProxyAllowanceLazyStore; - this._orderFilledCancelledLazyStore = orderFilledCancelledLazyStore; - } - public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> { - const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - try { - OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState); - const orderState: OrderStateValid = { - isValid: true, - orderHash, - orderRelevantState, - }; - return orderState; - } catch (err) { - const orderState: OrderStateInvalid = { - isValid: false, - orderHash, - error: err.message, - }; - return orderState; - } - } - public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> { - // HACK: We access the private property here but otherwise the interface will be less nice. - // If we pass it from the instantiator - there is no opportunity to get it there - // because JS doesn't support async constructors. - // Moreover - it's cached under the hood so it's equivalent to an async constructor. - const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; - const zrxTokenAddress = exchange.getZRXTokenAddress(); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - const makerBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, - ); - const makerProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, - ); - const makerFeeBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync( - zrxTokenAddress, - signedOrder.maker, - ); - const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync( - zrxTokenAddress, - signedOrder.maker, - ); - const filledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getFilledTakerAmountAsync(orderHash); - const cancelledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getCancelledTakerAmountAsync( - orderHash, - ); - const unavailableTakerTokenAmount = await exchange.getUnavailableTakerAmountAsync(orderHash); - const totalMakerTokenAmount = signedOrder.makerTokenAmount; - const totalTakerTokenAmount = signedOrder.takerTokenAmount; - const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount); - const remainingMakerTokenAmount = remainingTakerTokenAmount - .times(totalMakerTokenAmount) - .dividedToIntegerBy(totalTakerTokenAmount); - const transferrableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]); - const transferrableFeeTokenAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]); + if (orderRelevantState.makerBalance.eq(0)) { + throw new Error(ExchangeContractErrs.InsufficientMakerBalance); + } + if (orderRelevantState.makerProxyAllowance.eq(0)) { + throw new Error(ExchangeContractErrs.InsufficientMakerAllowance); + } + if (!signedOrder.makerFee.eq(0)) { + if (orderRelevantState.makerFeeBalance.eq(0)) { + throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance); + } + if (orderRelevantState.makerFeeProxyAllowance.eq(0)) { + throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance); + } + } + const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerTokenAmount + .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR) + .dividedBy(signedOrder.makerTokenAmount); + if ( + orderRelevantState.remainingFillableTakerTokenAmount.lessThan( + minFillableTakerTokenAmountWithinNoRoundingErrorRange, + ) + ) { + throw new Error(ExchangeContractErrs.OrderFillRoundingError); + } + } + constructor( + balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore, + orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore, + ) { + this._balanceAndProxyAllowanceLazyStore = balanceAndProxyAllowanceLazyStore; + this._orderFilledCancelledLazyStore = orderFilledCancelledLazyStore; + } + public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> { + const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + try { + OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState); + const orderState: OrderStateValid = { + isValid: true, + orderHash, + orderRelevantState, + }; + return orderState; + } catch (err) { + const orderState: OrderStateInvalid = { + isValid: false, + orderHash, + error: err.message, + }; + return orderState; + } + } + public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> { + // HACK: We access the private property here but otherwise the interface will be less nice. + // If we pass it from the instantiator - there is no opportunity to get it there + // because JS doesn't support async constructors. + // Moreover - it's cached under the hood so it's equivalent to an async constructor. + const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; + const zrxTokenAddress = exchange.getZRXTokenAddress(); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + const makerBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync( + signedOrder.makerTokenAddress, + signedOrder.maker, + ); + const makerProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync( + signedOrder.makerTokenAddress, + signedOrder.maker, + ); + const makerFeeBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync( + zrxTokenAddress, + signedOrder.maker, + ); + const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync( + zrxTokenAddress, + signedOrder.maker, + ); + const filledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getFilledTakerAmountAsync(orderHash); + const cancelledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getCancelledTakerAmountAsync( + orderHash, + ); + const unavailableTakerTokenAmount = await exchange.getUnavailableTakerAmountAsync(orderHash); + const totalMakerTokenAmount = signedOrder.makerTokenAmount; + const totalTakerTokenAmount = signedOrder.takerTokenAmount; + const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount); + const remainingMakerTokenAmount = remainingTakerTokenAmount + .times(totalMakerTokenAmount) + .dividedToIntegerBy(totalTakerTokenAmount); + const transferrableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]); + const transferrableFeeTokenAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]); - const isMakerTokenZRX = signedOrder.makerTokenAddress === zrxTokenAddress; - const remainingFillableCalculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableFeeTokenAmount, - remainingMakerTokenAmount, - ); - const remainingFillableMakerTokenAmount = remainingFillableCalculator.computeRemainingMakerFillable(); - const remainingFillableTakerTokenAmount = remainingFillableCalculator.computeRemainingTakerFillable(); - const orderRelevantState = { - makerBalance, - makerProxyAllowance, - makerFeeBalance, - makerFeeProxyAllowance, - filledTakerTokenAmount, - cancelledTakerTokenAmount, - remainingFillableMakerTokenAmount, - remainingFillableTakerTokenAmount, - }; - return orderRelevantState; - } + const isMakerTokenZRX = signedOrder.makerTokenAddress === zrxTokenAddress; + const remainingFillableCalculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableFeeTokenAmount, + remainingMakerTokenAmount, + ); + const remainingFillableMakerTokenAmount = remainingFillableCalculator.computeRemainingMakerFillable(); + const remainingFillableTakerTokenAmount = remainingFillableCalculator.computeRemainingTakerFillable(); + const orderRelevantState = { + makerBalance, + makerProxyAllowance, + makerFeeBalance, + makerFeeProxyAllowance, + filledTakerTokenAmount, + cancelledTakerTokenAmount, + remainingFillableMakerTokenAmount, + remainingFillableTakerTokenAmount, + }; + return orderRelevantState; + } } diff --git a/packages/0x.js/src/utils/order_validation_utils.ts b/packages/0x.js/src/utils/order_validation_utils.ts index 8ff22266f..917d414c8 100644 --- a/packages/0x.js/src/utils/order_validation_utils.ts +++ b/packages/0x.js/src/utils/order_validation_utils.ts @@ -10,207 +10,207 @@ import { utils } from '../utils/utils'; import { ExchangeTransferSimulator } from './exchange_transfer_simulator'; export class OrderValidationUtils { - private _exchangeWrapper: ExchangeWrapper; - public static validateCancelOrderThrowIfInvalid( - order: Order, - cancelTakerTokenAmount: BigNumber, - unavailableTakerTokenAmount: BigNumber, - ): void { - if (cancelTakerTokenAmount.eq(0)) { - throw new Error(ExchangeContractErrs.OrderCancelAmountZero); - } - if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) { - throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled); - } - const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); - if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { - throw new Error(ExchangeContractErrs.OrderCancelExpired); - } - } - public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTradeEmulator: ExchangeTransferSimulator, - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - senderAddress: string, - zrxTokenAddress: string, - ): Promise<void> { - const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount( - fillTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.makerTokenAmount, - ); - await exchangeTradeEmulator.transferFromAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, - senderAddress, - fillMakerTokenAmount, - TradeSide.Maker, - TransferType.Trade, - ); - await exchangeTradeEmulator.transferFromAsync( - signedOrder.takerTokenAddress, - senderAddress, - signedOrder.maker, - fillTakerTokenAmount, - TradeSide.Taker, - TransferType.Trade, - ); - const makerFeeAmount = OrderValidationUtils._getPartialAmount( - fillTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.makerFee, - ); - await exchangeTradeEmulator.transferFromAsync( - zrxTokenAddress, - signedOrder.maker, - signedOrder.feeRecipient, - makerFeeAmount, - TradeSide.Maker, - TransferType.Fee, - ); - const takerFeeAmount = OrderValidationUtils._getPartialAmount( - fillTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.takerFee, - ); - await exchangeTradeEmulator.transferFromAsync( - zrxTokenAddress, - senderAddress, - signedOrder.feeRecipient, - takerFeeAmount, - TradeSide.Taker, - TransferType.Fee, - ); - } - private static _validateRemainingFillAmountNotZeroOrThrow( - takerTokenAmount: BigNumber, - unavailableTakerTokenAmount: BigNumber, - ) { - if (takerTokenAmount.eq(unavailableTakerTokenAmount)) { - throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); - } - } - private static _validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) { - const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); - if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { - throw new Error(ExchangeContractErrs.OrderFillExpired); - } - } - private static _getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { - const fillMakerTokenAmount = numerator - .mul(target) - .div(denominator) - .round(0); - return fillMakerTokenAmount; - } - constructor(exchangeWrapper: ExchangeWrapper) { - this._exchangeWrapper = exchangeWrapper; - } - public async validateOrderFillableOrThrowAsync( - exchangeTradeEmulator: ExchangeTransferSimulator, - signedOrder: SignedOrder, - zrxTokenAddress: string, - expectedFillTakerTokenAmount?: BigNumber, - ): Promise<void> { - const orderHash = utils.getOrderHashHex(signedOrder); - const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow( - signedOrder.takerTokenAmount, - unavailableTakerTokenAmount, - ); - OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec); - let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); - if (!_.isUndefined(expectedFillTakerTokenAmount)) { - fillTakerTokenAmount = expectedFillTakerTokenAmount; - } - const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount( - fillTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.makerTokenAmount, - ); - await exchangeTradeEmulator.transferFromAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, - signedOrder.taker, - fillMakerTokenAmount, - TradeSide.Maker, - TransferType.Trade, - ); - const makerFeeAmount = OrderValidationUtils._getPartialAmount( - fillTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.makerFee, - ); - await exchangeTradeEmulator.transferFromAsync( - zrxTokenAddress, - signedOrder.maker, - signedOrder.feeRecipient, - makerFeeAmount, - TradeSide.Maker, - TransferType.Fee, - ); - } - public async validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator: ExchangeTransferSimulator, - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - zrxTokenAddress: string, - ): Promise<BigNumber> { - if (fillTakerTokenAmount.eq(0)) { - throw new Error(ExchangeContractErrs.OrderFillAmountZero); - } - const orderHash = utils.getOrderHashHex(signedOrder); - if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) { - throw new Error(ZeroExError.InvalidSignature); - } - const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow( - signedOrder.takerTokenAmount, - unavailableTakerTokenAmount, - ); - if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) { - throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker); - } - OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec); - const remainingTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); - const filledTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount) - ? remainingTakerTokenAmount - : fillTakerTokenAmount; - await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - filledTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); + private _exchangeWrapper: ExchangeWrapper; + public static validateCancelOrderThrowIfInvalid( + order: Order, + cancelTakerTokenAmount: BigNumber, + unavailableTakerTokenAmount: BigNumber, + ): void { + if (cancelTakerTokenAmount.eq(0)) { + throw new Error(ExchangeContractErrs.OrderCancelAmountZero); + } + if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) { + throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled); + } + const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); + if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { + throw new Error(ExchangeContractErrs.OrderCancelExpired); + } + } + public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTradeEmulator: ExchangeTransferSimulator, + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + senderAddress: string, + zrxTokenAddress: string, + ): Promise<void> { + const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount( + fillTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.makerTokenAmount, + ); + await exchangeTradeEmulator.transferFromAsync( + signedOrder.makerTokenAddress, + signedOrder.maker, + senderAddress, + fillMakerTokenAmount, + TradeSide.Maker, + TransferType.Trade, + ); + await exchangeTradeEmulator.transferFromAsync( + signedOrder.takerTokenAddress, + senderAddress, + signedOrder.maker, + fillTakerTokenAmount, + TradeSide.Taker, + TransferType.Trade, + ); + const makerFeeAmount = OrderValidationUtils._getPartialAmount( + fillTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.makerFee, + ); + await exchangeTradeEmulator.transferFromAsync( + zrxTokenAddress, + signedOrder.maker, + signedOrder.feeRecipient, + makerFeeAmount, + TradeSide.Maker, + TransferType.Fee, + ); + const takerFeeAmount = OrderValidationUtils._getPartialAmount( + fillTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.takerFee, + ); + await exchangeTradeEmulator.transferFromAsync( + zrxTokenAddress, + senderAddress, + signedOrder.feeRecipient, + takerFeeAmount, + TradeSide.Taker, + TransferType.Fee, + ); + } + private static _validateRemainingFillAmountNotZeroOrThrow( + takerTokenAmount: BigNumber, + unavailableTakerTokenAmount: BigNumber, + ) { + if (takerTokenAmount.eq(unavailableTakerTokenAmount)) { + throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); + } + } + private static _validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) { + const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); + if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { + throw new Error(ExchangeContractErrs.OrderFillExpired); + } + } + private static _getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { + const fillMakerTokenAmount = numerator + .mul(target) + .div(denominator) + .round(0); + return fillMakerTokenAmount; + } + constructor(exchangeWrapper: ExchangeWrapper) { + this._exchangeWrapper = exchangeWrapper; + } + public async validateOrderFillableOrThrowAsync( + exchangeTradeEmulator: ExchangeTransferSimulator, + signedOrder: SignedOrder, + zrxTokenAddress: string, + expectedFillTakerTokenAmount?: BigNumber, + ): Promise<void> { + const orderHash = utils.getOrderHashHex(signedOrder); + const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash); + OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow( + signedOrder.takerTokenAmount, + unavailableTakerTokenAmount, + ); + OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec); + let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); + if (!_.isUndefined(expectedFillTakerTokenAmount)) { + fillTakerTokenAmount = expectedFillTakerTokenAmount; + } + const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount( + fillTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.makerTokenAmount, + ); + await exchangeTradeEmulator.transferFromAsync( + signedOrder.makerTokenAddress, + signedOrder.maker, + signedOrder.taker, + fillMakerTokenAmount, + TradeSide.Maker, + TransferType.Trade, + ); + const makerFeeAmount = OrderValidationUtils._getPartialAmount( + fillTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.makerFee, + ); + await exchangeTradeEmulator.transferFromAsync( + zrxTokenAddress, + signedOrder.maker, + signedOrder.feeRecipient, + makerFeeAmount, + TradeSide.Maker, + TransferType.Fee, + ); + } + public async validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator: ExchangeTransferSimulator, + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + zrxTokenAddress: string, + ): Promise<BigNumber> { + if (fillTakerTokenAmount.eq(0)) { + throw new Error(ExchangeContractErrs.OrderFillAmountZero); + } + const orderHash = utils.getOrderHashHex(signedOrder); + if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) { + throw new Error(ZeroExError.InvalidSignature); + } + const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash); + OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow( + signedOrder.takerTokenAmount, + unavailableTakerTokenAmount, + ); + if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) { + throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker); + } + OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec); + const remainingTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); + const filledTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount) + ? remainingTakerTokenAmount + : fillTakerTokenAmount; + await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + filledTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); - const wouldRoundingErrorOccur = await this._exchangeWrapper.isRoundingErrorAsync( - filledTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.makerTokenAmount, - ); - if (wouldRoundingErrorOccur) { - throw new Error(ExchangeContractErrs.OrderFillRoundingError); - } - return filledTakerTokenAmount; - } - public async validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator: ExchangeTransferSimulator, - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - zrxTokenAddress: string, - ): Promise<void> { - const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - if (filledTakerTokenAmount !== fillTakerTokenAmount) { - throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount); - } - } + const wouldRoundingErrorOccur = await this._exchangeWrapper.isRoundingErrorAsync( + filledTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.makerTokenAmount, + ); + if (wouldRoundingErrorOccur) { + throw new Error(ExchangeContractErrs.OrderFillRoundingError); + } + return filledTakerTokenAmount; + } + public async validateFillOrKillOrderThrowIfInvalidAsync( + exchangeTradeEmulator: ExchangeTransferSimulator, + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + zrxTokenAddress: string, + ): Promise<void> { + const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + if (filledTakerTokenAmount !== fillTakerTokenAmount) { + throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount); + } + } } diff --git a/packages/0x.js/src/utils/signature_utils.ts b/packages/0x.js/src/utils/signature_utils.ts index 19b5c76c7..b0f1d61ef 100644 --- a/packages/0x.js/src/utils/signature_utils.ts +++ b/packages/0x.js/src/utils/signature_utils.ts @@ -3,44 +3,44 @@ import * as ethUtil from 'ethereumjs-util'; import { ECSignature } from '../types'; export const signatureUtils = { - isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean { - const dataBuff = ethUtil.toBuffer(data); - const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); - try { - const pubKey = ethUtil.ecrecover( - msgHashBuff, - signature.v, - ethUtil.toBuffer(signature.r), - ethUtil.toBuffer(signature.s), - ); - const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey)); - return retrievedAddress === signerAddress; - } catch (err) { - return false; - } - }, - parseSignatureHexAsVRS(signatureHex: string): ECSignature { - const signatureBuffer = ethUtil.toBuffer(signatureHex); - let v = signatureBuffer[0]; - if (v < 27) { - v += 27; - } - const r = signatureBuffer.slice(1, 33); - const s = signatureBuffer.slice(33, 65); - const ecSignature: ECSignature = { - v, - r: ethUtil.bufferToHex(r), - s: ethUtil.bufferToHex(s), - }; - return ecSignature; - }, - parseSignatureHexAsRSV(signatureHex: string): ECSignature { - const { v, r, s } = ethUtil.fromRpcSig(signatureHex); - const ecSignature: ECSignature = { - v, - r: ethUtil.bufferToHex(r), - s: ethUtil.bufferToHex(s), - }; - return ecSignature; - }, + isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean { + const dataBuff = ethUtil.toBuffer(data); + const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); + try { + const pubKey = ethUtil.ecrecover( + msgHashBuff, + signature.v, + ethUtil.toBuffer(signature.r), + ethUtil.toBuffer(signature.s), + ); + const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey)); + return retrievedAddress === signerAddress; + } catch (err) { + return false; + } + }, + parseSignatureHexAsVRS(signatureHex: string): ECSignature { + const signatureBuffer = ethUtil.toBuffer(signatureHex); + let v = signatureBuffer[0]; + if (v < 27) { + v += 27; + } + const r = signatureBuffer.slice(1, 33); + const s = signatureBuffer.slice(33, 65); + const ecSignature: ECSignature = { + v, + r: ethUtil.bufferToHex(r), + s: ethUtil.bufferToHex(s), + }; + return ecSignature; + }, + parseSignatureHexAsRSV(signatureHex: string): ECSignature { + const { v, r, s } = ethUtil.fromRpcSig(signatureHex); + const ecSignature: ECSignature = { + v, + r: ethUtil.bufferToHex(r), + s: ethUtil.bufferToHex(s), + }; + return ecSignature; + }, }; diff --git a/packages/0x.js/src/utils/utils.ts b/packages/0x.js/src/utils/utils.ts index ae07941ef..42cf5d956 100644 --- a/packages/0x.js/src/utils/utils.ts +++ b/packages/0x.js/src/utils/utils.ts @@ -7,62 +7,62 @@ import * as _ from 'lodash'; import { Order, SignedOrder, SolidityTypes } from '../types'; export const utils = { - /** - * Converts BigNumber instance to BN - * The only reason we convert to BN is to remain compatible with `ethABI. soliditySHA3` that - * expects values of Solidity type `uint` to be passed as type `BN`. - * We do not use BN anywhere else in the codebase. - */ - bigNumberToBN(value: BigNumber) { - return new BN(value.toString(), 10); - }, - consoleLog(message: string): void { - // tslint:disable-next-line: no-console - console.log(message); - }, - spawnSwitchErr(name: string, value: any): Error { - return new Error(`Unexpected switch value: ${value} encountered for ${name}`); - }, - getOrderHashHex(order: Order | SignedOrder): string { - const orderParts = [ - { value: order.exchangeContractAddress, type: SolidityTypes.Address }, - { value: order.maker, type: SolidityTypes.Address }, - { value: order.taker, type: SolidityTypes.Address }, - { value: order.makerTokenAddress, type: SolidityTypes.Address }, - { value: order.takerTokenAddress, type: SolidityTypes.Address }, - { value: order.feeRecipient, type: SolidityTypes.Address }, - { - value: utils.bigNumberToBN(order.makerTokenAmount), - type: SolidityTypes.Uint256, - }, - { - value: utils.bigNumberToBN(order.takerTokenAmount), - type: SolidityTypes.Uint256, - }, - { - value: utils.bigNumberToBN(order.makerFee), - type: SolidityTypes.Uint256, - }, - { - value: utils.bigNumberToBN(order.takerFee), - type: SolidityTypes.Uint256, - }, - { - value: utils.bigNumberToBN(order.expirationUnixTimestampSec), - type: SolidityTypes.Uint256, - }, - { value: utils.bigNumberToBN(order.salt), type: SolidityTypes.Uint256 }, - ]; - const types = _.map(orderParts, o => o.type); - const values = _.map(orderParts, o => o.value); - const hashBuff = ethABI.soliditySHA3(types, values); - const hashHex = ethUtil.bufferToHex(hashBuff); - return hashHex; - }, - getCurrentUnixTimestampSec(): BigNumber { - return new BigNumber(Date.now() / 1000).round(); - }, - getCurrentUnixTimestampMs(): BigNumber { - return new BigNumber(Date.now()); - }, + /** + * Converts BigNumber instance to BN + * The only reason we convert to BN is to remain compatible with `ethABI. soliditySHA3` that + * expects values of Solidity type `uint` to be passed as type `BN`. + * We do not use BN anywhere else in the codebase. + */ + bigNumberToBN(value: BigNumber) { + return new BN(value.toString(), 10); + }, + consoleLog(message: string): void { + // tslint:disable-next-line: no-console + console.log(message); + }, + spawnSwitchErr(name: string, value: any): Error { + return new Error(`Unexpected switch value: ${value} encountered for ${name}`); + }, + getOrderHashHex(order: Order | SignedOrder): string { + const orderParts = [ + { value: order.exchangeContractAddress, type: SolidityTypes.Address }, + { value: order.maker, type: SolidityTypes.Address }, + { value: order.taker, type: SolidityTypes.Address }, + { value: order.makerTokenAddress, type: SolidityTypes.Address }, + { value: order.takerTokenAddress, type: SolidityTypes.Address }, + { value: order.feeRecipient, type: SolidityTypes.Address }, + { + value: utils.bigNumberToBN(order.makerTokenAmount), + type: SolidityTypes.Uint256, + }, + { + value: utils.bigNumberToBN(order.takerTokenAmount), + type: SolidityTypes.Uint256, + }, + { + value: utils.bigNumberToBN(order.makerFee), + type: SolidityTypes.Uint256, + }, + { + value: utils.bigNumberToBN(order.takerFee), + type: SolidityTypes.Uint256, + }, + { + value: utils.bigNumberToBN(order.expirationUnixTimestampSec), + type: SolidityTypes.Uint256, + }, + { value: utils.bigNumberToBN(order.salt), type: SolidityTypes.Uint256 }, + ]; + const types = _.map(orderParts, o => o.type); + const values = _.map(orderParts, o => o.value); + const hashBuff = ethABI.soliditySHA3(types, values); + const hashHex = ethUtil.bufferToHex(hashBuff); + return hashHex; + }, + getCurrentUnixTimestampSec(): BigNumber { + return new BigNumber(Date.now() / 1000).round(); + }, + getCurrentUnixTimestampMs(): BigNumber { + return new BigNumber(Date.now()); + }, }; diff --git a/packages/0x.js/test/0x.js_test.ts b/packages/0x.js/test/0x.js_test.ts index 5ece5192f..927fe20be 100644 --- a/packages/0x.js/test/0x.js_test.ts +++ b/packages/0x.js/test/0x.js_test.ts @@ -19,278 +19,278 @@ const expect = chai.expect; const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false; describe('ZeroEx library', () => { - const web3 = web3Factory.create(); - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3.currentProvider, config); - describe('#setProvider', () => { - it('overrides provider in nested web3s and invalidates contractInstances', async () => { - // Instantiate the contract instances with the current provider - await (zeroEx.exchange as any)._getExchangeContractAsync(); - await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); - expect((zeroEx.exchange as any)._exchangeContractIfExists).to.not.be.undefined(); - expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.not.be.undefined(); + const web3 = web3Factory.create(); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3.currentProvider, config); + describe('#setProvider', () => { + it('overrides provider in nested web3s and invalidates contractInstances', async () => { + // Instantiate the contract instances with the current provider + await (zeroEx.exchange as any)._getExchangeContractAsync(); + await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); + expect((zeroEx.exchange as any)._exchangeContractIfExists).to.not.be.undefined(); + expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.not.be.undefined(); - const newProvider = web3Factory.getRpcProvider(); - // Add property to newProvider so that we can differentiate it from old provider - (newProvider as any).zeroExTestId = 1; - zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); + const newProvider = web3Factory.getRpcProvider(); + // Add property to newProvider so that we can differentiate it from old provider + (newProvider as any).zeroExTestId = 1; + zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); - // Check that contractInstances with old provider are removed after provider update - expect((zeroEx.exchange as any)._exchangeContractIfExists).to.be.undefined(); - expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.be.undefined(); + // Check that contractInstances with old provider are removed after provider update + expect((zeroEx.exchange as any)._exchangeContractIfExists).to.be.undefined(); + expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.be.undefined(); - // Check that all nested web3 wrapper instances return the updated provider - const nestedWeb3WrapperProvider = (zeroEx as any)._web3Wrapper.getCurrentProvider(); - expect(nestedWeb3WrapperProvider.zeroExTestId).to.be.a('number'); - const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getCurrentProvider(); - expect(exchangeWeb3WrapperProvider.zeroExTestId).to.be.a('number'); - const tokenRegistryWeb3WrapperProvider = (zeroEx.tokenRegistry as any)._web3Wrapper.getCurrentProvider(); - expect(tokenRegistryWeb3WrapperProvider.zeroExTestId).to.be.a('number'); - }); - }); - describe('#isValidSignature', () => { - // The Exchange smart contract `isValidSignature` method only validates orderHashes and assumes - // the length of the data is exactly 32 bytes. Thus for these tests, we use data of this size. - const dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; - const signature = { - v: 27, - r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', - s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - }; - const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; - it("should return false if the data doesn't pertain to the signature & address", async () => { - expect(ZeroEx.isValidSignature('0x0', signature, address)).to.be.false(); - return expect( - (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync('0x0', signature, address), - ).to.become(false); - }); - it("should return false if the address doesn't pertain to the signature & data", async () => { - const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42'; - expect(ZeroEx.isValidSignature(dataHex, signature, validUnrelatedAddress)).to.be.false(); - return expect( - (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync( - dataHex, - signature, - validUnrelatedAddress, - ), - ).to.become(false); - }); - it("should return false if the signature doesn't pertain to the dataHex & address", async () => { - const wrongSignature = _.assign({}, signature, { v: 28 }); - expect(ZeroEx.isValidSignature(dataHex, wrongSignature, address)).to.be.false(); - return expect( - (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, wrongSignature, address), - ).to.become(false); - }); - it('should return true if the signature does pertain to the dataHex & address', async () => { - const isValidSignatureLocal = ZeroEx.isValidSignature(dataHex, signature, address); - expect(isValidSignatureLocal).to.be.true(); - return expect( - (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, signature, address), - ).to.become(true); - }); - }); - describe('#generateSalt', () => { - it('generates different salts', () => { - const equal = ZeroEx.generatePseudoRandomSalt().eq(ZeroEx.generatePseudoRandomSalt()); - expect(equal).to.be.false(); - }); - it('generates salt in range [0..2^256)', () => { - const salt = ZeroEx.generatePseudoRandomSalt(); - expect(salt.greaterThanOrEqualTo(0)).to.be.true(); - const twoPow256 = new BigNumber(2).pow(256); - expect(salt.lessThan(twoPow256)).to.be.true(); - }); - }); - describe('#isValidOrderHash', () => { - it('returns false if the value is not a hex string', () => { - const isValid = ZeroEx.isValidOrderHash('not a hex'); - expect(isValid).to.be.false(); - }); - it('returns false if the length is wrong', () => { - const isValid = ZeroEx.isValidOrderHash('0xdeadbeef'); - expect(isValid).to.be.false(); - }); - it('returns true if order hash is correct', () => { - const isValid = ZeroEx.isValidOrderHash('0x' + Array(65).join('0')); - expect(isValid).to.be.true(); - }); - }); - describe('#toUnitAmount', () => { - it('should throw if invalid baseUnit amount supplied as argument', () => { - const invalidBaseUnitAmount = new BigNumber(1000000000.4); - const decimals = 6; - expect(() => ZeroEx.toUnitAmount(invalidBaseUnitAmount, decimals)).to.throw( - 'amount should be in baseUnits (no decimals), found value: 1000000000.4', - ); - }); - it('Should return the expected unit amount for the decimals passed in', () => { - const baseUnitAmount = new BigNumber(1000000000); - const decimals = 6; - const unitAmount = ZeroEx.toUnitAmount(baseUnitAmount, decimals); - const expectedUnitAmount = new BigNumber(1000); - expect(unitAmount).to.be.bignumber.equal(expectedUnitAmount); - }); - }); - describe('#toBaseUnitAmount', () => { - it('Should return the expected base unit amount for the decimals passed in', () => { - const unitAmount = new BigNumber(1000); - const decimals = 6; - const baseUnitAmount = ZeroEx.toBaseUnitAmount(unitAmount, decimals); - const expectedUnitAmount = new BigNumber(1000000000); - expect(baseUnitAmount).to.be.bignumber.equal(expectedUnitAmount); - }); - it('should throw if unitAmount has more decimals then specified as the max decimal precision', () => { - const unitAmount = new BigNumber(0.823091); - const decimals = 5; - expect(() => ZeroEx.toBaseUnitAmount(unitAmount, decimals)).to.throw( - 'Invalid unit amount: 0.823091 - Too many decimal places', - ); - }); - }); - describe('#getOrderHashHex', () => { - const expectedOrderHash = '0x39da987067a3c9e5f1617694f1301326ba8c8b0498ebef5df4863bed394e3c83'; - const fakeExchangeContractAddress = '0xb69e673309512a9d726f87304c6984054f87a93b'; - const order: Order = { - maker: constants.NULL_ADDRESS, - taker: constants.NULL_ADDRESS, - feeRecipient: constants.NULL_ADDRESS, - makerTokenAddress: constants.NULL_ADDRESS, - takerTokenAddress: constants.NULL_ADDRESS, - exchangeContractAddress: fakeExchangeContractAddress, - salt: new BigNumber(0), - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - makerTokenAmount: new BigNumber(0), - takerTokenAmount: new BigNumber(0), - expirationUnixTimestampSec: new BigNumber(0), - }; - it('calculates the order hash', async () => { - const orderHash = ZeroEx.getOrderHashHex(order); - expect(orderHash).to.be.equal(expectedOrderHash); - }); - it('throws a readable error message if taker format is invalid', async () => { - const orderWithInvalidtakerFormat = { - ...order, - taker: (null as any) as string, - }; - const expectedErrorMessage = - 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; - expect(() => ZeroEx.getOrderHashHex(orderWithInvalidtakerFormat)).to.throw(expectedErrorMessage); - }); - }); - describe('#signOrderHashAsync', () => { - let stubs: Sinon.SinonStub[] = []; - let makerAddress: string; - before(async () => { - const availableAddreses = await zeroEx.getAvailableAddressesAsync(); - makerAddress = availableAddreses[0]; - }); - afterEach(() => { - // clean up any stubs after the test has completed - _.each(stubs, s => s.restore()); - stubs = []; - }); - it('Should return the correct ECSignature', async () => { - const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; - const expectedECSignature = { - v: 27, - r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', - s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - }; - const ecSignature = await zeroEx.signOrderHashAsync( - orderHash, - makerAddress, - SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, - ); - expect(ecSignature).to.deep.equal(expectedECSignature); - }); - it('should return the correct ECSignature for signatureHex concatenated as R + S + V', async () => { - const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004'; - const signature = - '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb021b'; - const expectedECSignature = { - v: 27, - r: '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3', - s: '0x050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb02', - }; - stubs = [ - Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)), - Sinon.stub(ZeroEx, 'isValidSignature').returns(true), - ]; + // Check that all nested web3 wrapper instances return the updated provider + const nestedWeb3WrapperProvider = (zeroEx as any)._web3Wrapper.getCurrentProvider(); + expect(nestedWeb3WrapperProvider.zeroExTestId).to.be.a('number'); + const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getCurrentProvider(); + expect(exchangeWeb3WrapperProvider.zeroExTestId).to.be.a('number'); + const tokenRegistryWeb3WrapperProvider = (zeroEx.tokenRegistry as any)._web3Wrapper.getCurrentProvider(); + expect(tokenRegistryWeb3WrapperProvider.zeroExTestId).to.be.a('number'); + }); + }); + describe('#isValidSignature', () => { + // The Exchange smart contract `isValidSignature` method only validates orderHashes and assumes + // the length of the data is exactly 32 bytes. Thus for these tests, we use data of this size. + const dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; + const signature = { + v: 27, + r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', + s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + }; + const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; + it("should return false if the data doesn't pertain to the signature & address", async () => { + expect(ZeroEx.isValidSignature('0x0', signature, address)).to.be.false(); + return expect( + (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync('0x0', signature, address), + ).to.become(false); + }); + it("should return false if the address doesn't pertain to the signature & data", async () => { + const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42'; + expect(ZeroEx.isValidSignature(dataHex, signature, validUnrelatedAddress)).to.be.false(); + return expect( + (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync( + dataHex, + signature, + validUnrelatedAddress, + ), + ).to.become(false); + }); + it("should return false if the signature doesn't pertain to the dataHex & address", async () => { + const wrongSignature = _.assign({}, signature, { v: 28 }); + expect(ZeroEx.isValidSignature(dataHex, wrongSignature, address)).to.be.false(); + return expect( + (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, wrongSignature, address), + ).to.become(false); + }); + it('should return true if the signature does pertain to the dataHex & address', async () => { + const isValidSignatureLocal = ZeroEx.isValidSignature(dataHex, signature, address); + expect(isValidSignatureLocal).to.be.true(); + return expect( + (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, signature, address), + ).to.become(true); + }); + }); + describe('#generateSalt', () => { + it('generates different salts', () => { + const equal = ZeroEx.generatePseudoRandomSalt().eq(ZeroEx.generatePseudoRandomSalt()); + expect(equal).to.be.false(); + }); + it('generates salt in range [0..2^256)', () => { + const salt = ZeroEx.generatePseudoRandomSalt(); + expect(salt.greaterThanOrEqualTo(0)).to.be.true(); + const twoPow256 = new BigNumber(2).pow(256); + expect(salt.lessThan(twoPow256)).to.be.true(); + }); + }); + describe('#isValidOrderHash', () => { + it('returns false if the value is not a hex string', () => { + const isValid = ZeroEx.isValidOrderHash('not a hex'); + expect(isValid).to.be.false(); + }); + it('returns false if the length is wrong', () => { + const isValid = ZeroEx.isValidOrderHash('0xdeadbeef'); + expect(isValid).to.be.false(); + }); + it('returns true if order hash is correct', () => { + const isValid = ZeroEx.isValidOrderHash('0x' + Array(65).join('0')); + expect(isValid).to.be.true(); + }); + }); + describe('#toUnitAmount', () => { + it('should throw if invalid baseUnit amount supplied as argument', () => { + const invalidBaseUnitAmount = new BigNumber(1000000000.4); + const decimals = 6; + expect(() => ZeroEx.toUnitAmount(invalidBaseUnitAmount, decimals)).to.throw( + 'amount should be in baseUnits (no decimals), found value: 1000000000.4', + ); + }); + it('Should return the expected unit amount for the decimals passed in', () => { + const baseUnitAmount = new BigNumber(1000000000); + const decimals = 6; + const unitAmount = ZeroEx.toUnitAmount(baseUnitAmount, decimals); + const expectedUnitAmount = new BigNumber(1000); + expect(unitAmount).to.be.bignumber.equal(expectedUnitAmount); + }); + }); + describe('#toBaseUnitAmount', () => { + it('Should return the expected base unit amount for the decimals passed in', () => { + const unitAmount = new BigNumber(1000); + const decimals = 6; + const baseUnitAmount = ZeroEx.toBaseUnitAmount(unitAmount, decimals); + const expectedUnitAmount = new BigNumber(1000000000); + expect(baseUnitAmount).to.be.bignumber.equal(expectedUnitAmount); + }); + it('should throw if unitAmount has more decimals then specified as the max decimal precision', () => { + const unitAmount = new BigNumber(0.823091); + const decimals = 5; + expect(() => ZeroEx.toBaseUnitAmount(unitAmount, decimals)).to.throw( + 'Invalid unit amount: 0.823091 - Too many decimal places', + ); + }); + }); + describe('#getOrderHashHex', () => { + const expectedOrderHash = '0x39da987067a3c9e5f1617694f1301326ba8c8b0498ebef5df4863bed394e3c83'; + const fakeExchangeContractAddress = '0xb69e673309512a9d726f87304c6984054f87a93b'; + const order: Order = { + maker: constants.NULL_ADDRESS, + taker: constants.NULL_ADDRESS, + feeRecipient: constants.NULL_ADDRESS, + makerTokenAddress: constants.NULL_ADDRESS, + takerTokenAddress: constants.NULL_ADDRESS, + exchangeContractAddress: fakeExchangeContractAddress, + salt: new BigNumber(0), + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + makerTokenAmount: new BigNumber(0), + takerTokenAmount: new BigNumber(0), + expirationUnixTimestampSec: new BigNumber(0), + }; + it('calculates the order hash', async () => { + const orderHash = ZeroEx.getOrderHashHex(order); + expect(orderHash).to.be.equal(expectedOrderHash); + }); + it('throws a readable error message if taker format is invalid', async () => { + const orderWithInvalidtakerFormat = { + ...order, + taker: (null as any) as string, + }; + const expectedErrorMessage = + 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; + expect(() => ZeroEx.getOrderHashHex(orderWithInvalidtakerFormat)).to.throw(expectedErrorMessage); + }); + }); + describe('#signOrderHashAsync', () => { + let stubs: Sinon.SinonStub[] = []; + let makerAddress: string; + before(async () => { + const availableAddreses = await zeroEx.getAvailableAddressesAsync(); + makerAddress = availableAddreses[0]; + }); + afterEach(() => { + // clean up any stubs after the test has completed + _.each(stubs, s => s.restore()); + stubs = []; + }); + it('Should return the correct ECSignature', async () => { + const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; + const expectedECSignature = { + v: 27, + r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', + s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + }; + const ecSignature = await zeroEx.signOrderHashAsync( + orderHash, + makerAddress, + SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, + ); + expect(ecSignature).to.deep.equal(expectedECSignature); + }); + it('should return the correct ECSignature for signatureHex concatenated as R + S + V', async () => { + const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004'; + const signature = + '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb021b'; + const expectedECSignature = { + v: 27, + r: '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3', + s: '0x050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb02', + }; + stubs = [ + Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)), + Sinon.stub(ZeroEx, 'isValidSignature').returns(true), + ]; - const ecSignature = await zeroEx.signOrderHashAsync( - orderHash, - makerAddress, - SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, - ); - expect(ecSignature).to.deep.equal(expectedECSignature); - }); - it('should return the correct ECSignature for signatureHex concatenated as V + R + S', async () => { - const orderHash = '0xc793e33ffded933b76f2f48d9aa3339fc090399d5e7f5dec8d3660f5480793f7'; - const signature = - '0x1bc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee02dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960'; - const expectedECSignature = { - v: 27, - r: '0xc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee0', - s: '0x2dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960', - }; - stubs = [ - Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)), - Sinon.stub(ZeroEx, 'isValidSignature').returns(true), - ]; + const ecSignature = await zeroEx.signOrderHashAsync( + orderHash, + makerAddress, + SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, + ); + expect(ecSignature).to.deep.equal(expectedECSignature); + }); + it('should return the correct ECSignature for signatureHex concatenated as V + R + S', async () => { + const orderHash = '0xc793e33ffded933b76f2f48d9aa3339fc090399d5e7f5dec8d3660f5480793f7'; + const signature = + '0x1bc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee02dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960'; + const expectedECSignature = { + v: 27, + r: '0xc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee0', + s: '0x2dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960', + }; + stubs = [ + Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)), + Sinon.stub(ZeroEx, 'isValidSignature').returns(true), + ]; - const ecSignature = await zeroEx.signOrderHashAsync( - orderHash, - makerAddress, - SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, - ); - expect(ecSignature).to.deep.equal(expectedECSignature); - }); - }); - describe('#awaitTransactionMinedAsync', () => { - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - it('returns transaction receipt with decoded logs', async () => { - const availableAddresses = await zeroEx.getAvailableAddressesAsync(); - const coinbase = availableAddresses[0]; - const tokens = await zeroEx.tokenRegistry.getTokensAsync(); - const tokenUtils = new TokenUtils(tokens); - const zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; - const proxyAddress = zeroEx.proxy.getContractAddress(); - const txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(zrxTokenAddress, coinbase); - const txReceiptWithDecodedLogs = await zeroEx.awaitTransactionMinedAsync(txHash); - const log = txReceiptWithDecodedLogs.logs[0] as LogWithDecodedArgs<ApprovalContractEventArgs>; - expect(log.event).to.be.equal(TokenEvents.Approval); - expect(log.args._owner).to.be.equal(coinbase); - expect(log.args._spender).to.be.equal(proxyAddress); - expect(log.args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - }); - describe('#config', () => { - it('allows to specify exchange contract address', async () => { - const zeroExConfig = { - exchangeContractAddress: ZeroEx.NULL_ADDRESS, - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, zeroExConfig); - expect(zeroExWithWrongExchangeAddress.exchange.getContractAddress()).to.be.equal(ZeroEx.NULL_ADDRESS); - }); - it('allows to specify token registry token contract address', async () => { - const zeroExConfig = { - tokenRegistryContractAddress: ZeroEx.NULL_ADDRESS, - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroExWithWrongTokenRegistryAddress = new ZeroEx(web3.currentProvider, zeroExConfig); - expect(zeroExWithWrongTokenRegistryAddress.tokenRegistry.getContractAddress()).to.be.equal( - ZeroEx.NULL_ADDRESS, - ); - }); - }); + const ecSignature = await zeroEx.signOrderHashAsync( + orderHash, + makerAddress, + SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, + ); + expect(ecSignature).to.deep.equal(expectedECSignature); + }); + }); + describe('#awaitTransactionMinedAsync', () => { + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + it('returns transaction receipt with decoded logs', async () => { + const availableAddresses = await zeroEx.getAvailableAddressesAsync(); + const coinbase = availableAddresses[0]; + const tokens = await zeroEx.tokenRegistry.getTokensAsync(); + const tokenUtils = new TokenUtils(tokens); + const zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + const proxyAddress = zeroEx.proxy.getContractAddress(); + const txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(zrxTokenAddress, coinbase); + const txReceiptWithDecodedLogs = await zeroEx.awaitTransactionMinedAsync(txHash); + const log = txReceiptWithDecodedLogs.logs[0] as LogWithDecodedArgs<ApprovalContractEventArgs>; + expect(log.event).to.be.equal(TokenEvents.Approval); + expect(log.args._owner).to.be.equal(coinbase); + expect(log.args._spender).to.be.equal(proxyAddress); + expect(log.args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + }); + describe('#config', () => { + it('allows to specify exchange contract address', async () => { + const zeroExConfig = { + exchangeContractAddress: ZeroEx.NULL_ADDRESS, + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, zeroExConfig); + expect(zeroExWithWrongExchangeAddress.exchange.getContractAddress()).to.be.equal(ZeroEx.NULL_ADDRESS); + }); + it('allows to specify token registry token contract address', async () => { + const zeroExConfig = { + tokenRegistryContractAddress: ZeroEx.NULL_ADDRESS, + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroExWithWrongTokenRegistryAddress = new ZeroEx(web3.currentProvider, zeroExConfig); + expect(zeroExWithWrongTokenRegistryAddress.tokenRegistry.getContractAddress()).to.be.equal( + ZeroEx.NULL_ADDRESS, + ); + }); + }); }); diff --git a/packages/0x.js/test/artifacts_test.ts b/packages/0x.js/test/artifacts_test.ts index 3a791f436..e8ab9aa97 100644 --- a/packages/0x.js/test/artifacts_test.ts +++ b/packages/0x.js/test/artifacts_test.ts @@ -12,44 +12,44 @@ chaiSetup.configure(); const TIMEOUT = 10000; describe('Artifacts', () => { - describe('contracts are deployed on kovan', () => { - const kovanRpcUrl = constants.KOVAN_RPC_URL; - const packageJSONContent = fs.readFileSync('package.json', 'utf-8'); - const packageJSON = JSON.parse(packageJSONContent); - const mnemonic = packageJSON.config.mnemonic; - const web3Provider = new HDWalletProvider(mnemonic, kovanRpcUrl); - const config = { - networkId: constants.KOVAN_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3Provider, config); - it('token registry contract is deployed', async () => { - await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); - }).timeout(TIMEOUT); - it('proxy contract is deployed', async () => { - await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync(); - }).timeout(TIMEOUT); - it('exchange contract is deployed', async () => { - await (zeroEx.exchange as any)._getExchangeContractAsync(); - }).timeout(TIMEOUT); - }); - describe('contracts are deployed on ropsten', () => { - const ropstenRpcUrl = constants.ROPSTEN_RPC_URL; - const packageJSONContent = fs.readFileSync('package.json', 'utf-8'); - const packageJSON = JSON.parse(packageJSONContent); - const mnemonic = packageJSON.config.mnemonic; - const web3Provider = new HDWalletProvider(mnemonic, ropstenRpcUrl); - const config = { - networkId: constants.ROPSTEN_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3Provider, config); - it('token registry contract is deployed', async () => { - await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); - }).timeout(TIMEOUT); - it('proxy contract is deployed', async () => { - await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync(); - }).timeout(TIMEOUT); - it('exchange contract is deployed', async () => { - await (zeroEx.exchange as any)._getExchangeContractAsync(); - }).timeout(TIMEOUT); - }); + describe('contracts are deployed on kovan', () => { + const kovanRpcUrl = constants.KOVAN_RPC_URL; + const packageJSONContent = fs.readFileSync('package.json', 'utf-8'); + const packageJSON = JSON.parse(packageJSONContent); + const mnemonic = packageJSON.config.mnemonic; + const web3Provider = new HDWalletProvider(mnemonic, kovanRpcUrl); + const config = { + networkId: constants.KOVAN_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3Provider, config); + it('token registry contract is deployed', async () => { + await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); + }).timeout(TIMEOUT); + it('proxy contract is deployed', async () => { + await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync(); + }).timeout(TIMEOUT); + it('exchange contract is deployed', async () => { + await (zeroEx.exchange as any)._getExchangeContractAsync(); + }).timeout(TIMEOUT); + }); + describe('contracts are deployed on ropsten', () => { + const ropstenRpcUrl = constants.ROPSTEN_RPC_URL; + const packageJSONContent = fs.readFileSync('package.json', 'utf-8'); + const packageJSON = JSON.parse(packageJSONContent); + const mnemonic = packageJSON.config.mnemonic; + const web3Provider = new HDWalletProvider(mnemonic, ropstenRpcUrl); + const config = { + networkId: constants.ROPSTEN_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3Provider, config); + it('token registry contract is deployed', async () => { + await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); + }).timeout(TIMEOUT); + it('proxy contract is deployed', async () => { + await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync(); + }).timeout(TIMEOUT); + it('exchange contract is deployed', async () => { + await (zeroEx.exchange as any)._getExchangeContractAsync(); + }).timeout(TIMEOUT); + }); }); diff --git a/packages/0x.js/test/assert_test.ts b/packages/0x.js/test/assert_test.ts index 2c72a5e88..1f2820070 100644 --- a/packages/0x.js/test/assert_test.ts +++ b/packages/0x.js/test/assert_test.ts @@ -10,34 +10,34 @@ import { web3Factory } from './utils/web3_factory'; const expect = chai.expect; describe('Assertion library', () => { - const web3 = web3Factory.create(); - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3.currentProvider, config); - describe('#isSenderAddressHexAsync', () => { - it('throws when address is invalid', async () => { - const address = '0xdeadbeef'; - const varName = 'address'; - return expect( - assert.isSenderAddressAsync(varName, address, (zeroEx as any)._web3Wrapper), - ).to.be.rejectedWith(`Expected ${varName} to be of type ETHAddressHex, encountered: ${address}`); - }); - it('throws when address is unavailable', async () => { - const validUnrelatedAddress = '0x8b0292b11a196601eddce54b665cafeca0347d42'; - const varName = 'address'; - return expect( - assert.isSenderAddressAsync(varName, validUnrelatedAddress, (zeroEx as any)._web3Wrapper), - ).to.be.rejectedWith( - `Specified ${varName} ${validUnrelatedAddress} isn't available through the supplied web3 provider`, - ); - }); - it("doesn't throw if address is available", async () => { - const availableAddress = (await zeroEx.getAvailableAddressesAsync())[0]; - const varName = 'address'; - return expect( - assert.isSenderAddressAsync(varName, availableAddress, (zeroEx as any)._web3Wrapper), - ).to.become(undefined); - }); - }); + const web3 = web3Factory.create(); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3.currentProvider, config); + describe('#isSenderAddressHexAsync', () => { + it('throws when address is invalid', async () => { + const address = '0xdeadbeef'; + const varName = 'address'; + return expect( + assert.isSenderAddressAsync(varName, address, (zeroEx as any)._web3Wrapper), + ).to.be.rejectedWith(`Expected ${varName} to be of type ETHAddressHex, encountered: ${address}`); + }); + it('throws when address is unavailable', async () => { + const validUnrelatedAddress = '0x8b0292b11a196601eddce54b665cafeca0347d42'; + const varName = 'address'; + return expect( + assert.isSenderAddressAsync(varName, validUnrelatedAddress, (zeroEx as any)._web3Wrapper), + ).to.be.rejectedWith( + `Specified ${varName} ${validUnrelatedAddress} isn't available through the supplied web3 provider`, + ); + }); + it("doesn't throw if address is available", async () => { + const availableAddress = (await zeroEx.getAvailableAddressesAsync())[0]; + const varName = 'address'; + return expect( + assert.isSenderAddressAsync(varName, availableAddress, (zeroEx as any)._web3Wrapper), + ).to.become(undefined); + }); + }); }); diff --git a/packages/0x.js/test/ether_token_wrapper_test.ts b/packages/0x.js/test/ether_token_wrapper_test.ts index 67e62dc12..b810fc9f1 100644 --- a/packages/0x.js/test/ether_token_wrapper_test.ts +++ b/packages/0x.js/test/ether_token_wrapper_test.ts @@ -5,17 +5,17 @@ import 'mocha'; import * as Web3 from 'web3'; import { - ApprovalContractEventArgs, - BlockParamLiteral, - BlockRange, - DecodedLogEvent, - DepositContractEventArgs, - EtherTokenEvents, - Token, - TransferContractEventArgs, - WithdrawalContractEventArgs, - ZeroEx, - ZeroExError, + ApprovalContractEventArgs, + BlockParamLiteral, + BlockRange, + DecodedLogEvent, + DepositContractEventArgs, + EtherTokenEvents, + Token, + TransferContractEventArgs, + WithdrawalContractEventArgs, + ZeroEx, + ZeroExError, } from '../src'; import { artifacts } from '../src/artifacts'; import { DoneCallback } from '../src/types'; @@ -37,339 +37,339 @@ const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); const MAX_REASONABLE_GAS_COST_IN_WEI = 62517; describe('EtherTokenWrapper', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let tokens: Token[]; - let userAddresses: string[]; - let addressWithETH: string; - let wethContractAddress: string; - let depositWeiAmount: BigNumber; - let decimalPlaces: number; - let addressWithoutFunds: string; - const gasPrice = new BigNumber(1); - const zeroExConfig = { - gasPrice, - networkId: constants.TESTRPC_NETWORK_ID, - }; - const transferAmount = new BigNumber(42); - const allowanceAmount = new BigNumber(42); - const depositAmount = new BigNumber(42); - const withdrawalAmount = new BigNumber(42); - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - addressWithETH = userAddresses[0]; - wethContractAddress = (zeroEx.etherToken as any)._getContractAddress(artifacts.EtherTokenArtifact); - depositWeiAmount = (zeroEx as any)._web3Wrapper.toWei(new BigNumber(5)); - decimalPlaces = 7; - addressWithoutFunds = userAddresses[1]; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#depositAsync', () => { - it('should successfully deposit ETH and issue Wrapped ETH tokens', async () => { - const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); - expect(preETHBalance).to.be.bignumber.gt(0); - expect(preWETHBalance).to.be.bignumber.equal(0); + let web3: Web3; + let zeroEx: ZeroEx; + let tokens: Token[]; + let userAddresses: string[]; + let addressWithETH: string; + let wethContractAddress: string; + let depositWeiAmount: BigNumber; + let decimalPlaces: number; + let addressWithoutFunds: string; + const gasPrice = new BigNumber(1); + const zeroExConfig = { + gasPrice, + networkId: constants.TESTRPC_NETWORK_ID, + }; + const transferAmount = new BigNumber(42); + const allowanceAmount = new BigNumber(42); + const depositAmount = new BigNumber(42); + const withdrawalAmount = new BigNumber(42); + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + addressWithETH = userAddresses[0]; + wethContractAddress = (zeroEx.etherToken as any)._getContractAddress(artifacts.EtherTokenArtifact); + depositWeiAmount = (zeroEx as any)._web3Wrapper.toWei(new BigNumber(5)); + decimalPlaces = 7; + addressWithoutFunds = userAddresses[1]; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#depositAsync', () => { + it('should successfully deposit ETH and issue Wrapped ETH tokens', async () => { + const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); + expect(preETHBalance).to.be.bignumber.gt(0); + expect(preWETHBalance).to.be.bignumber.equal(0); - const txHash = await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH); - await zeroEx.awaitTransactionMinedAsync(txHash); + const txHash = await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH); + await zeroEx.awaitTransactionMinedAsync(txHash); - const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); + const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); - expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(depositWeiAmount); - const remainingETHInWei = preETHBalance.minus(depositWeiAmount); - const gasCost = remainingETHInWei.minus(postETHBalanceInWei); - expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); - }); - it('should throw if user has insufficient ETH balance for deposit', async () => { - const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(depositWeiAmount); + const remainingETHInWei = preETHBalance.minus(depositWeiAmount); + const gasCost = remainingETHInWei.minus(postETHBalanceInWei); + expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); + }); + it('should throw if user has insufficient ETH balance for deposit', async () => { + const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - const extraETHBalance = (zeroEx as any)._web3Wrapper.toWei(5, 'ether'); - const overETHBalanceinWei = preETHBalance.add(extraETHBalance); + const extraETHBalance = (zeroEx as any)._web3Wrapper.toWei(5, 'ether'); + const overETHBalanceinWei = preETHBalance.add(extraETHBalance); - return expect( - zeroEx.etherToken.depositAsync(wethContractAddress, overETHBalanceinWei, addressWithETH), - ).to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit); - }); - }); - describe('#withdrawAsync', () => { - it('should successfully withdraw ETH in return for Wrapped ETH tokens', async () => { - const ETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + return expect( + zeroEx.etherToken.depositAsync(wethContractAddress, overETHBalanceinWei, addressWithETH), + ).to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit); + }); + }); + describe('#withdrawAsync', () => { + it('should successfully withdraw ETH in return for Wrapped ETH tokens', async () => { + const ETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH); + await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH); - const expectedPreETHBalance = ETHBalanceInWei.minus(depositWeiAmount); - const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); - let gasCost = expectedPreETHBalance.minus(preETHBalance); - expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); - expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount); + const expectedPreETHBalance = ETHBalanceInWei.minus(depositWeiAmount); + const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); + let gasCost = expectedPreETHBalance.minus(preETHBalance); + expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); + expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount); - const txHash = await zeroEx.etherToken.withdrawAsync(wethContractAddress, depositWeiAmount, addressWithETH); - await zeroEx.awaitTransactionMinedAsync(txHash); + const txHash = await zeroEx.etherToken.withdrawAsync(wethContractAddress, depositWeiAmount, addressWithETH); + await zeroEx.awaitTransactionMinedAsync(txHash); - const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); + const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); - expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(0); - const expectedETHBalance = preETHBalance.add(depositWeiAmount).round(decimalPlaces); - gasCost = expectedETHBalance.minus(postETHBalance); - expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); - }); - it('should throw if user has insufficient WETH balance for withdrawl', async () => { - const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); - expect(preWETHBalance).to.be.bignumber.equal(0); + expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(0); + const expectedETHBalance = preETHBalance.add(depositWeiAmount).round(decimalPlaces); + gasCost = expectedETHBalance.minus(postETHBalance); + expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); + }); + it('should throw if user has insufficient WETH balance for withdrawl', async () => { + const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); + expect(preWETHBalance).to.be.bignumber.equal(0); - const overWETHBalance = preWETHBalance.add(999999999); + const overWETHBalance = preWETHBalance.add(999999999); - return expect( - zeroEx.etherToken.withdrawAsync(wethContractAddress, overWETHBalance, addressWithETH), - ).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal); - }); - }); - describe('#subscribe', () => { - const indexFilterValues = {}; - let etherTokenAddress: string; - before(() => { - const tokenUtils = new TokenUtils(tokens); - const etherToken = tokenUtils.getWethTokenOrThrow(); - etherTokenAddress = etherToken.address; - }); - afterEach(() => { - zeroEx.etherToken.unsubscribeAll(); - }); - // Hack: Mocha does not allow a test to be both async and have a `done` callback - // Since we need to await the receipt of the event in the `subscribe` callback, - // we do need both. A hack is to make the top-level async fn w/ a done callback and then - // wrap the rest of the test in an async block - // Source: https://github.com/mochajs/mocha/issues/2407 - it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<TransferContractEventArgs>) => { - expect(logEvent).to.not.be.undefined(); - expect(logEvent.isRemoved).to.be.false(); - expect(logEvent.log.logIndex).to.be.equal(0); - expect(logEvent.log.transactionIndex).to.be.equal(0); - expect(logEvent.log.blockNumber).to.be.a('number'); - const args = logEvent.log.args; - expect(args._from).to.be.equal(addressWithETH); - expect(args._to).to.be.equal(addressWithoutFunds); - expect(args._value).to.be.bignumber.equal(transferAmount); - }, - ); - await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); - zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Transfer, indexFilterValues, callback); - await zeroEx.token.transferAsync( - etherTokenAddress, - addressWithETH, - addressWithoutFunds, - transferAmount, - ); - })().catch(done); - }); - it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - expect(logEvent).to.not.be.undefined(); - expect(logEvent.isRemoved).to.be.false(); - const args = logEvent.log.args; - expect(args._owner).to.be.equal(addressWithETH); - expect(args._spender).to.be.equal(addressWithoutFunds); - expect(args._value).to.be.bignumber.equal(allowanceAmount); - }, - ); - zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Approval, indexFilterValues, callback); - await zeroEx.token.setAllowanceAsync( - etherTokenAddress, - addressWithETH, - addressWithoutFunds, - allowanceAmount, - ); - })().catch(done); - }); - it('Should receive the Deposit event when ether is being deposited', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<DepositContractEventArgs>) => { - expect(logEvent).to.not.be.undefined(); - expect(logEvent.isRemoved).to.be.false(); - const args = logEvent.log.args; - expect(args._owner).to.be.equal(addressWithETH); - expect(args._value).to.be.bignumber.equal(depositAmount); - }, - ); - zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Deposit, indexFilterValues, callback); - await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); - })().catch(done); - }); - it('Should receive the Withdrawal event when ether is being withdrawn', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<WithdrawalContractEventArgs>) => { - expect(logEvent).to.not.be.undefined(); - expect(logEvent.isRemoved).to.be.false(); - const args = logEvent.log.args; - expect(args._owner).to.be.equal(addressWithETH); - expect(args._value).to.be.bignumber.equal(depositAmount); - }, - ); - await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); - zeroEx.etherToken.subscribe( - etherTokenAddress, - EtherTokenEvents.Withdrawal, - indexFilterValues, - callback, - ); - await zeroEx.etherToken.withdrawAsync(etherTokenAddress, withdrawalAmount, addressWithETH); - })().catch(done); - }); - it('should cancel outstanding subscriptions when ZeroEx.setProvider is called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - zeroEx.etherToken.subscribe( - etherTokenAddress, - EtherTokenEvents.Transfer, - indexFilterValues, - callbackNeverToBeCalled, - ); - const callbackToBeCalled = reportNodeCallbackErrors(done)(); - const newProvider = web3Factory.getRpcProvider(); - zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); - await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); - zeroEx.etherToken.subscribe( - etherTokenAddress, - EtherTokenEvents.Transfer, - indexFilterValues, - callbackToBeCalled, - ); - await zeroEx.token.transferAsync( - etherTokenAddress, - addressWithETH, - addressWithoutFunds, - transferAmount, - ); - })().catch(done); - }); - it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); - const subscriptionToken = zeroEx.etherToken.subscribe( - etherTokenAddress, - EtherTokenEvents.Transfer, - indexFilterValues, - callbackNeverToBeCalled, - ); - zeroEx.etherToken.unsubscribe(subscriptionToken); - await zeroEx.token.transferAsync( - etherTokenAddress, - addressWithETH, - addressWithoutFunds, - transferAmount, - ); - done(); - })().catch(done); - }); - }); - describe('#getLogsAsync', () => { - let etherTokenAddress: string; - let tokenTransferProxyAddress: string; - const blockRange: BlockRange = { - fromBlock: 0, - toBlock: BlockParamLiteral.Latest, - }; - let txHash: string; - before(() => { - addressWithETH = userAddresses[0]; - const tokenUtils = new TokenUtils(tokens); - const etherToken = tokenUtils.getWethTokenOrThrow(); - etherTokenAddress = etherToken.address; - tokenTransferProxyAddress = zeroEx.proxy.getContractAddress(); - }); - it('should get logs with decoded args emitted by Approval', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); - await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = EtherTokenEvents.Approval; - const indexFilterValues = {}; - const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>( - etherTokenAddress, - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(logs[0].event).to.be.equal(eventName); - expect(args._owner).to.be.equal(addressWithETH); - expect(args._spender).to.be.equal(tokenTransferProxyAddress); - expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - it('should get logs with decoded args emitted by Deposit', async () => { - await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); - const eventName = EtherTokenEvents.Deposit; - const indexFilterValues = {}; - const logs = await zeroEx.etherToken.getLogsAsync<DepositContractEventArgs>( - etherTokenAddress, - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(logs[0].event).to.be.equal(eventName); - expect(args._owner).to.be.equal(addressWithETH); - expect(args._value).to.be.bignumber.equal(depositAmount); - }); - it('should only get the logs with the correct event name', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); - await zeroEx.awaitTransactionMinedAsync(txHash); - const differentEventName = EtherTokenEvents.Transfer; - const indexFilterValues = {}; - const logs = await zeroEx.etherToken.getLogsAsync( - etherTokenAddress, - differentEventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(0); - }); - it('should only get the logs with the correct indexed fields', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithoutFunds); - await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = EtherTokenEvents.Approval; - const indexFilterValues = { - _owner: addressWithETH, - }; - const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>( - etherTokenAddress, - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(args._owner).to.be.equal(addressWithETH); - }); - }); + return expect( + zeroEx.etherToken.withdrawAsync(wethContractAddress, overWETHBalance, addressWithETH), + ).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal); + }); + }); + describe('#subscribe', () => { + const indexFilterValues = {}; + let etherTokenAddress: string; + before(() => { + const tokenUtils = new TokenUtils(tokens); + const etherToken = tokenUtils.getWethTokenOrThrow(); + etherTokenAddress = etherToken.address; + }); + afterEach(() => { + zeroEx.etherToken.unsubscribeAll(); + }); + // Hack: Mocha does not allow a test to be both async and have a `done` callback + // Since we need to await the receipt of the event in the `subscribe` callback, + // we do need both. A hack is to make the top-level async fn w/ a done callback and then + // wrap the rest of the test in an async block + // Source: https://github.com/mochajs/mocha/issues/2407 + it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<TransferContractEventArgs>) => { + expect(logEvent).to.not.be.undefined(); + expect(logEvent.isRemoved).to.be.false(); + expect(logEvent.log.logIndex).to.be.equal(0); + expect(logEvent.log.transactionIndex).to.be.equal(0); + expect(logEvent.log.blockNumber).to.be.a('number'); + const args = logEvent.log.args; + expect(args._from).to.be.equal(addressWithETH); + expect(args._to).to.be.equal(addressWithoutFunds); + expect(args._value).to.be.bignumber.equal(transferAmount); + }, + ); + await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); + zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Transfer, indexFilterValues, callback); + await zeroEx.token.transferAsync( + etherTokenAddress, + addressWithETH, + addressWithoutFunds, + transferAmount, + ); + })().catch(done); + }); + it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + expect(logEvent).to.not.be.undefined(); + expect(logEvent.isRemoved).to.be.false(); + const args = logEvent.log.args; + expect(args._owner).to.be.equal(addressWithETH); + expect(args._spender).to.be.equal(addressWithoutFunds); + expect(args._value).to.be.bignumber.equal(allowanceAmount); + }, + ); + zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Approval, indexFilterValues, callback); + await zeroEx.token.setAllowanceAsync( + etherTokenAddress, + addressWithETH, + addressWithoutFunds, + allowanceAmount, + ); + })().catch(done); + }); + it('Should receive the Deposit event when ether is being deposited', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<DepositContractEventArgs>) => { + expect(logEvent).to.not.be.undefined(); + expect(logEvent.isRemoved).to.be.false(); + const args = logEvent.log.args; + expect(args._owner).to.be.equal(addressWithETH); + expect(args._value).to.be.bignumber.equal(depositAmount); + }, + ); + zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Deposit, indexFilterValues, callback); + await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); + })().catch(done); + }); + it('Should receive the Withdrawal event when ether is being withdrawn', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<WithdrawalContractEventArgs>) => { + expect(logEvent).to.not.be.undefined(); + expect(logEvent.isRemoved).to.be.false(); + const args = logEvent.log.args; + expect(args._owner).to.be.equal(addressWithETH); + expect(args._value).to.be.bignumber.equal(depositAmount); + }, + ); + await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); + zeroEx.etherToken.subscribe( + etherTokenAddress, + EtherTokenEvents.Withdrawal, + indexFilterValues, + callback, + ); + await zeroEx.etherToken.withdrawAsync(etherTokenAddress, withdrawalAmount, addressWithETH); + })().catch(done); + }); + it('should cancel outstanding subscriptions when ZeroEx.setProvider is called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + zeroEx.etherToken.subscribe( + etherTokenAddress, + EtherTokenEvents.Transfer, + indexFilterValues, + callbackNeverToBeCalled, + ); + const callbackToBeCalled = reportNodeCallbackErrors(done)(); + const newProvider = web3Factory.getRpcProvider(); + zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); + await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); + zeroEx.etherToken.subscribe( + etherTokenAddress, + EtherTokenEvents.Transfer, + indexFilterValues, + callbackToBeCalled, + ); + await zeroEx.token.transferAsync( + etherTokenAddress, + addressWithETH, + addressWithoutFunds, + transferAmount, + ); + })().catch(done); + }); + it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); + const subscriptionToken = zeroEx.etherToken.subscribe( + etherTokenAddress, + EtherTokenEvents.Transfer, + indexFilterValues, + callbackNeverToBeCalled, + ); + zeroEx.etherToken.unsubscribe(subscriptionToken); + await zeroEx.token.transferAsync( + etherTokenAddress, + addressWithETH, + addressWithoutFunds, + transferAmount, + ); + done(); + })().catch(done); + }); + }); + describe('#getLogsAsync', () => { + let etherTokenAddress: string; + let tokenTransferProxyAddress: string; + const blockRange: BlockRange = { + fromBlock: 0, + toBlock: BlockParamLiteral.Latest, + }; + let txHash: string; + before(() => { + addressWithETH = userAddresses[0]; + const tokenUtils = new TokenUtils(tokens); + const etherToken = tokenUtils.getWethTokenOrThrow(); + etherTokenAddress = etherToken.address; + tokenTransferProxyAddress = zeroEx.proxy.getContractAddress(); + }); + it('should get logs with decoded args emitted by Approval', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = EtherTokenEvents.Approval; + const indexFilterValues = {}; + const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>( + etherTokenAddress, + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(logs[0].event).to.be.equal(eventName); + expect(args._owner).to.be.equal(addressWithETH); + expect(args._spender).to.be.equal(tokenTransferProxyAddress); + expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + it('should get logs with decoded args emitted by Deposit', async () => { + await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); + const eventName = EtherTokenEvents.Deposit; + const indexFilterValues = {}; + const logs = await zeroEx.etherToken.getLogsAsync<DepositContractEventArgs>( + etherTokenAddress, + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(logs[0].event).to.be.equal(eventName); + expect(args._owner).to.be.equal(addressWithETH); + expect(args._value).to.be.bignumber.equal(depositAmount); + }); + it('should only get the logs with the correct event name', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); + await zeroEx.awaitTransactionMinedAsync(txHash); + const differentEventName = EtherTokenEvents.Transfer; + const indexFilterValues = {}; + const logs = await zeroEx.etherToken.getLogsAsync( + etherTokenAddress, + differentEventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(0); + }); + it('should only get the logs with the correct indexed fields', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithoutFunds); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = EtherTokenEvents.Approval; + const indexFilterValues = { + _owner: addressWithETH, + }; + const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>( + etherTokenAddress, + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(args._owner).to.be.equal(addressWithETH); + }); + }); }); diff --git a/packages/0x.js/test/event_watcher_test.ts b/packages/0x.js/test/event_watcher_test.ts index f0c97eced..f92fb2b02 100644 --- a/packages/0x.js/test/event_watcher_test.ts +++ b/packages/0x.js/test/event_watcher_test.ts @@ -17,110 +17,110 @@ chaiSetup.configure(); const expect = chai.expect; describe('EventWatcher', () => { - let web3: Web3; - let stubs: Sinon.SinonStub[] = []; - let eventWatcher: EventWatcher; - let web3Wrapper: Web3Wrapper; - const logA: Web3.LogEntry = { - address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5', - blockHash: null, - blockNumber: null, - data: '', - logIndex: null, - topics: [], - transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17', - transactionIndex: 0, - }; - const logB: Web3.LogEntry = { - address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819', - blockHash: null, - blockNumber: null, - data: '', - logIndex: null, - topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], - transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', - transactionIndex: 0, - }; - const logC: Web3.LogEntry = { - address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5', - blockHash: null, - blockNumber: null, - data: '', - logIndex: null, - topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], - transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', - transactionIndex: 0, - }; - before(async () => { - web3 = web3Factory.create(); - const pollingIntervalMs = 10; - web3Wrapper = new Web3Wrapper(web3.currentProvider); - eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs); - }); - afterEach(() => { - // clean up any stubs after the test has completed - _.each(stubs, s => s.restore()); - stubs = []; - eventWatcher.unsubscribe(); - }); - it('correctly emits initial log events', (done: DoneCallback) => { - const logs: Web3.LogEntry[] = [logA, logB]; - const expectedLogEvents = [ - { - removed: false, - ...logA, - }, - { - removed: false, - ...logB, - }, - ]; - const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync'); - getLogsStub.onCall(0).returns(logs); - stubs.push(getLogsStub); - const expectedToBeCalledOnce = false; - const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => { - const expectedLogEvent = expectedLogEvents.shift(); - expect(event).to.be.deep.equal(expectedLogEvent); - if (_.isEmpty(expectedLogEvents)) { - done(); - } - }); - eventWatcher.subscribe(callback); - }); - it('correctly computes the difference and emits only changes', (done: DoneCallback) => { - const initialLogs: Web3.LogEntry[] = [logA, logB]; - const changedLogs: Web3.LogEntry[] = [logA, logC]; - const expectedLogEvents = [ - { - removed: false, - ...logA, - }, - { - removed: false, - ...logB, - }, - { - removed: true, - ...logB, - }, - { - removed: false, - ...logC, - }, - ]; - const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync'); - getLogsStub.onCall(0).returns(initialLogs); - getLogsStub.onCall(1).returns(changedLogs); - stubs.push(getLogsStub); - const expectedToBeCalledOnce = false; - const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => { - const expectedLogEvent = expectedLogEvents.shift(); - expect(event).to.be.deep.equal(expectedLogEvent); - if (_.isEmpty(expectedLogEvents)) { - done(); - } - }); - eventWatcher.subscribe(callback); - }); + let web3: Web3; + let stubs: Sinon.SinonStub[] = []; + let eventWatcher: EventWatcher; + let web3Wrapper: Web3Wrapper; + const logA: Web3.LogEntry = { + address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5', + blockHash: null, + blockNumber: null, + data: '', + logIndex: null, + topics: [], + transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17', + transactionIndex: 0, + }; + const logB: Web3.LogEntry = { + address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819', + blockHash: null, + blockNumber: null, + data: '', + logIndex: null, + topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], + transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', + transactionIndex: 0, + }; + const logC: Web3.LogEntry = { + address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5', + blockHash: null, + blockNumber: null, + data: '', + logIndex: null, + topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], + transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', + transactionIndex: 0, + }; + before(async () => { + web3 = web3Factory.create(); + const pollingIntervalMs = 10; + web3Wrapper = new Web3Wrapper(web3.currentProvider); + eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs); + }); + afterEach(() => { + // clean up any stubs after the test has completed + _.each(stubs, s => s.restore()); + stubs = []; + eventWatcher.unsubscribe(); + }); + it('correctly emits initial log events', (done: DoneCallback) => { + const logs: Web3.LogEntry[] = [logA, logB]; + const expectedLogEvents = [ + { + removed: false, + ...logA, + }, + { + removed: false, + ...logB, + }, + ]; + const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync'); + getLogsStub.onCall(0).returns(logs); + stubs.push(getLogsStub); + const expectedToBeCalledOnce = false; + const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => { + const expectedLogEvent = expectedLogEvents.shift(); + expect(event).to.be.deep.equal(expectedLogEvent); + if (_.isEmpty(expectedLogEvents)) { + done(); + } + }); + eventWatcher.subscribe(callback); + }); + it('correctly computes the difference and emits only changes', (done: DoneCallback) => { + const initialLogs: Web3.LogEntry[] = [logA, logB]; + const changedLogs: Web3.LogEntry[] = [logA, logC]; + const expectedLogEvents = [ + { + removed: false, + ...logA, + }, + { + removed: false, + ...logB, + }, + { + removed: true, + ...logB, + }, + { + removed: false, + ...logC, + }, + ]; + const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync'); + getLogsStub.onCall(0).returns(initialLogs); + getLogsStub.onCall(1).returns(changedLogs); + stubs.push(getLogsStub); + const expectedToBeCalledOnce = false; + const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => { + const expectedLogEvent = expectedLogEvents.shift(); + expect(event).to.be.deep.equal(expectedLogEvent); + if (_.isEmpty(expectedLogEvents)) { + done(); + } + }); + eventWatcher.subscribe(callback); + }); }); diff --git a/packages/0x.js/test/exchange_transfer_simulator_test.ts b/packages/0x.js/test/exchange_transfer_simulator_test.ts index 9f4d76933..20b4a05ca 100644 --- a/packages/0x.js/test/exchange_transfer_simulator_test.ts +++ b/packages/0x.js/test/exchange_transfer_simulator_test.ts @@ -15,103 +15,103 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); describe('ExchangeTransferSimulator', () => { - const web3 = web3Factory.create(); - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3.currentProvider, config); - const transferAmount = new BigNumber(5); - let userAddresses: string[]; - let tokens: Token[]; - let coinbase: string; - let sender: string; - let recipient: string; - let exampleTokenAddress: string; - let exchangeTransferSimulator: ExchangeTransferSimulator; - let txHash: string; - before(async () => { - userAddresses = await zeroEx.getAvailableAddressesAsync(); - [coinbase, sender, recipient] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - exampleTokenAddress = tokens[0].address; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#transferFromAsync', () => { - beforeEach(() => { - exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest); - }); - it("throws if the user doesn't have enough allowance", async () => { - return expect( - exchangeTransferSimulator.transferFromAsync( - exampleTokenAddress, - sender, - recipient, - transferAmount, - TradeSide.Taker, - TransferType.Trade, - ), - ).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance); - }); - it("throws if the user doesn't have enough balance", async () => { - txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount); - await zeroEx.awaitTransactionMinedAsync(txHash); - return expect( - exchangeTransferSimulator.transferFromAsync( - exampleTokenAddress, - sender, - recipient, - transferAmount, - TradeSide.Maker, - TransferType.Trade, - ), - ).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance); - }); - it('updates balances and proxyAllowance after transfer', async () => { - txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount); - await zeroEx.awaitTransactionMinedAsync(txHash); - await exchangeTransferSimulator.transferFromAsync( - exampleTokenAddress, - sender, - recipient, - transferAmount, - TradeSide.Taker, - TransferType.Trade, - ); - const store = (exchangeTransferSimulator as any)._store; - const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender); - const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient); - const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender); - expect(senderBalance).to.be.bignumber.equal(0); - expect(recipientBalance).to.be.bignumber.equal(transferAmount); - expect(senderProxyAllowance).to.be.bignumber.equal(0); - }); - it("doesn't update proxyAllowance after transfer if unlimited", async () => { - txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(exampleTokenAddress, sender); - await zeroEx.awaitTransactionMinedAsync(txHash); - await exchangeTransferSimulator.transferFromAsync( - exampleTokenAddress, - sender, - recipient, - transferAmount, - TradeSide.Taker, - TransferType.Trade, - ); - const store = (exchangeTransferSimulator as any)._store; - const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender); - const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient); - const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender); - expect(senderBalance).to.be.bignumber.equal(0); - expect(recipientBalance).to.be.bignumber.equal(transferAmount); - expect(senderProxyAllowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - }); + const web3 = web3Factory.create(); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3.currentProvider, config); + const transferAmount = new BigNumber(5); + let userAddresses: string[]; + let tokens: Token[]; + let coinbase: string; + let sender: string; + let recipient: string; + let exampleTokenAddress: string; + let exchangeTransferSimulator: ExchangeTransferSimulator; + let txHash: string; + before(async () => { + userAddresses = await zeroEx.getAvailableAddressesAsync(); + [coinbase, sender, recipient] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + exampleTokenAddress = tokens[0].address; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#transferFromAsync', () => { + beforeEach(() => { + exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest); + }); + it("throws if the user doesn't have enough allowance", async () => { + return expect( + exchangeTransferSimulator.transferFromAsync( + exampleTokenAddress, + sender, + recipient, + transferAmount, + TradeSide.Taker, + TransferType.Trade, + ), + ).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance); + }); + it("throws if the user doesn't have enough balance", async () => { + txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount); + await zeroEx.awaitTransactionMinedAsync(txHash); + return expect( + exchangeTransferSimulator.transferFromAsync( + exampleTokenAddress, + sender, + recipient, + transferAmount, + TradeSide.Maker, + TransferType.Trade, + ), + ).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance); + }); + it('updates balances and proxyAllowance after transfer', async () => { + txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount); + await zeroEx.awaitTransactionMinedAsync(txHash); + await exchangeTransferSimulator.transferFromAsync( + exampleTokenAddress, + sender, + recipient, + transferAmount, + TradeSide.Taker, + TransferType.Trade, + ); + const store = (exchangeTransferSimulator as any)._store; + const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender); + const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient); + const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender); + expect(senderBalance).to.be.bignumber.equal(0); + expect(recipientBalance).to.be.bignumber.equal(transferAmount); + expect(senderProxyAllowance).to.be.bignumber.equal(0); + }); + it("doesn't update proxyAllowance after transfer if unlimited", async () => { + txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(exampleTokenAddress, sender); + await zeroEx.awaitTransactionMinedAsync(txHash); + await exchangeTransferSimulator.transferFromAsync( + exampleTokenAddress, + sender, + recipient, + transferAmount, + TradeSide.Taker, + TransferType.Trade, + ); + const store = (exchangeTransferSimulator as any)._store; + const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender); + const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient); + const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender); + expect(senderBalance).to.be.bignumber.equal(0); + expect(recipientBalance).to.be.bignumber.equal(transferAmount); + expect(senderProxyAllowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + }); }); diff --git a/packages/0x.js/test/exchange_wrapper_test.ts b/packages/0x.js/test/exchange_wrapper_test.ts index f67e61a55..7e0ffd818 100644 --- a/packages/0x.js/test/exchange_wrapper_test.ts +++ b/packages/0x.js/test/exchange_wrapper_test.ts @@ -6,17 +6,17 @@ import 'mocha'; import * as Web3 from 'web3'; import { - BlockRange, - DecodedLogEvent, - ExchangeContractErrs, - ExchangeEvents, - LogCancelContractEventArgs, - LogFillContractEventArgs, - OrderCancellationRequest, - OrderFillRequest, - SignedOrder, - Token, - ZeroEx, + BlockRange, + DecodedLogEvent, + ExchangeContractErrs, + ExchangeEvents, + LogCancelContractEventArgs, + LogFillContractEventArgs, + OrderCancellationRequest, + OrderFillRequest, + SignedOrder, + Token, + ZeroEx, } from '../src'; import { BlockParamLiteral, DoneCallback } from '../src/types'; @@ -34,1114 +34,1114 @@ const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); const NON_EXISTENT_ORDER_HASH = '0x79370342234e7acd6bbeac335bd3bb1d368383294b64b8160a00f4060e4d3777'; describe('ExchangeWrapper', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let tokenUtils: TokenUtils; - let tokens: Token[]; - let userAddresses: string[]; - let zrxTokenAddress: string; - let fillScenarios: FillScenarios; - let exchangeContractAddress: string; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - exchangeContractAddress = zeroEx.exchange.getContractAddress(); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - tokenUtils = new TokenUtils(tokens); - zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; - fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); - await fillScenarios.initTokenBalancesAsync(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('fillOrKill order(s)', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipient: string; - const takerTokenFillAmount = new BigNumber(5); - before(async () => { - [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - describe('#batchFillOrKillAsync', () => { - it('successfully batch fillOrKill', async () => { - const fillableAmount = new BigNumber(5); - const partialFillTakerAmount = new BigNumber(2); - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const orderFillRequests = [ - { - signedOrder, - takerTokenFillAmount: partialFillTakerAmount, - }, - { - signedOrder: anotherSignedOrder, - takerTokenFillAmount: partialFillTakerAmount, - }, - ]; - await zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress); - }); - describe('order transaction options', () => { - let signedOrder: SignedOrder; - let orderFillRequests: OrderFillRequest[]; - const fillableAmount = new BigNumber(5); - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - orderFillRequests = [ - { - signedOrder, - takerTokenFillAmount: new BigNumber(0), - }, - ]; - }); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, { - shouldValidate: true, - }), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, { - shouldValidate: false, - }), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - }); - }); - describe('#fillOrKillOrderAsync', () => { - let signedOrder: SignedOrder; - const fillableAmount = new BigNumber(5); - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - }); - describe('successful fills', () => { - it('should fill a valid order', async () => { - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - 0, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - 0, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount, - ); - await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, takerTokenFillAmount, takerAddress); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount.minus(takerTokenFillAmount), - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - takerTokenFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - takerTokenFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount.minus(takerTokenFillAmount), - ); - }); - it('should partially fill a valid order', async () => { - const partialFillAmount = new BigNumber(3); - await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, partialFillAmount, takerAddress); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount.minus(partialFillAmount), - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - partialFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - partialFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount.minus(partialFillAmount), - ); - }); - }); - describe('order transaction options', () => { - const emptyFillableAmount = new BigNumber(0); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, { - shouldValidate: true, - }), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, { - shouldValidate: false, - }), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - }); - }); - }); - describe('fill order(s)', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipient: string; - const fillableAmount = new BigNumber(5); - const takerTokenFillAmount = new BigNumber(5); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - before(async () => { - [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - describe('#fillOrderAsync', () => { - describe('successful fills', () => { - it('should fill a valid order', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - 0, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - 0, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount, - ); - const txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - takerTokenFillAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount.minus(takerTokenFillAmount), - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - takerTokenFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - takerTokenFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount.minus(takerTokenFillAmount), - ); - }); - it('should partially fill the valid order', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const partialFillAmount = new BigNumber(3); - const txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - partialFillAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount.minus(partialFillAmount), - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - partialFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - partialFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount.minus(partialFillAmount), - ); - }); - it('should fill the valid orders with fees', async () => { - const makerFee = new BigNumber(1); - const takerFee = new BigNumber(2); - const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - feeRecipient, - ); - const txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - takerTokenFillAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - expect(await zeroEx.token.getBalanceAsync(zrxTokenAddress, feeRecipient)).to.be.bignumber.equal( - makerFee.plus(takerFee), - ); - }); - }); - describe('order transaction options', () => { - let signedOrder: SignedOrder; - const emptyFillTakerAmount = new BigNumber(0); - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - }); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.fillOrderAsync( - signedOrder, - emptyFillTakerAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.fillOrderAsync( - signedOrder, - emptyFillTakerAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: true, - }, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.fillOrderAsync( - signedOrder, - emptyFillTakerAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: false, - }, - ), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - }); - describe('negative fill amount', async () => { - let signedOrder: SignedOrder; - const negativeFillTakerAmount = new BigNumber(-100); - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - }); - it('should not allow the exchange wrapper to fill if amount is negative', async () => { - return expect( - zeroEx.exchange.fillOrderAsync( - signedOrder, - negativeFillTakerAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejected(); - }); - }); - }); - describe('#batchFillOrdersAsync', () => { - let signedOrder: SignedOrder; - let signedOrderHashHex: string; - let anotherSignedOrder: SignedOrder; - let anotherOrderHashHex: string; - let orderFillBatch: OrderFillRequest[]; - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - signedOrderHashHex = ZeroEx.getOrderHashHex(signedOrder); - anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); - }); - describe('successful batch fills', () => { - beforeEach(() => { - orderFillBatch = [ - { - signedOrder, - takerTokenFillAmount, - }, - { - signedOrder: anotherSignedOrder, - takerTokenFillAmount, - }, - ]; - }); - it('should throw if a batch is empty', async () => { - return expect( - zeroEx.exchange.batchFillOrdersAsync( - [], - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - }); - it('should successfully fill multiple orders', async () => { - const txHash = await zeroEx.exchange.batchFillOrdersAsync( - orderFillBatch, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); - const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); - expect(filledAmount).to.be.bignumber.equal(takerTokenFillAmount); - expect(anotherFilledAmount).to.be.bignumber.equal(takerTokenFillAmount); - }); - }); - describe('order transaction options', () => { - beforeEach(async () => { - const emptyFillTakerAmount = new BigNumber(0); - orderFillBatch = [ - { - signedOrder, - takerTokenFillAmount: emptyFillTakerAmount, - }, - { - signedOrder: anotherSignedOrder, - takerTokenFillAmount: emptyFillTakerAmount, - }, - ]; - }); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.batchFillOrdersAsync( - orderFillBatch, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.batchFillOrdersAsync( - orderFillBatch, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: true, - }, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.batchFillOrdersAsync( - orderFillBatch, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: false, - }, - ), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - }); - describe('negative batch fill amount', async () => { - beforeEach(async () => { - const negativeFillTakerAmount = new BigNumber(-100); - orderFillBatch = [ - { - signedOrder, - takerTokenFillAmount, - }, - { - signedOrder: anotherSignedOrder, - takerTokenFillAmount: negativeFillTakerAmount, - }, - ]; - }); - it('should not allow the exchange wrapper to batch fill if any amount is negative', async () => { - return expect( - zeroEx.exchange.batchFillOrdersAsync( - orderFillBatch, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejected(); - }); - }); - }); - describe('#fillOrdersUpTo', () => { - let signedOrder: SignedOrder; - let signedOrderHashHex: string; - let anotherSignedOrder: SignedOrder; - let anotherOrderHashHex: string; - let signedOrders: SignedOrder[]; - const fillUpToAmount = fillableAmount.plus(fillableAmount).minus(1); - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - signedOrderHashHex = ZeroEx.getOrderHashHex(signedOrder); - anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); - signedOrders = [signedOrder, anotherSignedOrder]; - }); - describe('successful batch fills', () => { - it('should throw if a batch is empty', async () => { - return expect( - zeroEx.exchange.fillOrdersUpToAsync( - [], - fillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - }); - it('should successfully fill up to specified amount when all orders are fully funded', async () => { - const txHash = await zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - fillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); - const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); - expect(filledAmount).to.be.bignumber.equal(fillableAmount); - const remainingFillAmount = fillableAmount.minus(1); - expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount); - }); - it('should successfully fill up to specified amount even if filling all orders would fail', async () => { - const missingBalance = new BigNumber(1); // User will still have enough balance to fill up to 9, - // but won't have 10 to fully fill all orders in a batch. - await zeroEx.token.transferAsync(makerTokenAddress, makerAddress, coinbase, missingBalance); - const txHash = await zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - fillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); - const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); - expect(filledAmount).to.be.bignumber.equal(fillableAmount); - const remainingFillAmount = fillableAmount.minus(1); - expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount); - }); - }); - describe('failed batch fills', () => { - it("should fail validation if user doesn't have enough balance without fill up to", async () => { - const missingBalance = new BigNumber(2); // User will only have enough balance to fill up to 8 - await zeroEx.token.transferAsync(makerTokenAddress, makerAddress, coinbase, missingBalance); - return expect( - zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - fillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance); - }); - }); - describe('order transaction options', () => { - const emptyFillUpToAmount = new BigNumber(0); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - emptyFillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - emptyFillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: true, - }, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - emptyFillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: false, - }, - ), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - }); - }); - }); - describe('cancel order(s)', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let makerAddress: string; - let takerAddress: string; - const fillableAmount = new BigNumber(5); - let signedOrder: SignedOrder; - let orderHashHex: string; - const cancelAmount = new BigNumber(3); - beforeEach(async () => { - [coinbase, makerAddress, takerAddress] = userAddresses; - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - orderHashHex = ZeroEx.getOrderHashHex(signedOrder); - }); - describe('#cancelOrderAsync', () => { - describe('successful cancels', () => { - it('should cancel an order', async () => { - const txHash = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount); - await zeroEx.awaitTransactionMinedAsync(txHash); - const cancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHashHex); - expect(cancelledAmount).to.be.bignumber.equal(cancelAmount); - }); - }); - describe('order transaction options', () => { - const emptyCancelTakerTokenAmount = new BigNumber(0); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount), - ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, { - shouldValidate: true, - }), - ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, { - shouldValidate: false, - }), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - }); - }); - describe('#batchCancelOrdersAsync', () => { - let anotherSignedOrder: SignedOrder; - let anotherOrderHashHex: string; - let cancelBatch: OrderCancellationRequest[]; - beforeEach(async () => { - anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); - cancelBatch = [ - { - order: signedOrder, - takerTokenCancelAmount: cancelAmount, - }, - { - order: anotherSignedOrder, - takerTokenCancelAmount: cancelAmount, - }, - ]; - }); - describe('failed batch cancels', () => { - it('should throw when orders have different makers', async () => { - const signedOrderWithDifferentMaker = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - takerAddress, - takerAddress, - fillableAmount, - ); - return expect( - zeroEx.exchange.batchCancelOrdersAsync([ - cancelBatch[0], - { - order: signedOrderWithDifferentMaker, - takerTokenCancelAmount: cancelAmount, - }, - ]), - ).to.be.rejectedWith(ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed); - }); - }); - describe('successful batch cancels', () => { - it('should cancel a batch of orders', async () => { - await zeroEx.exchange.batchCancelOrdersAsync(cancelBatch); - const cancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHashHex); - const anotherCancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync( - anotherOrderHashHex, - ); - expect(cancelledAmount).to.be.bignumber.equal(cancelAmount); - expect(anotherCancelledAmount).to.be.bignumber.equal(cancelAmount); - }); - }); - describe('order transaction options', () => { - beforeEach(async () => { - const emptyTakerTokenCancelAmount = new BigNumber(0); - cancelBatch = [ - { - order: signedOrder, - takerTokenCancelAmount: emptyTakerTokenCancelAmount, - }, - { - order: anotherSignedOrder, - takerTokenCancelAmount: emptyTakerTokenCancelAmount, - }, - ]; - }); - it('should validate when orderTransactionOptions are not present', async () => { - return expect(zeroEx.exchange.batchCancelOrdersAsync(cancelBatch)).to.be.rejectedWith( - ExchangeContractErrs.OrderCancelAmountZero, - ); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, { - shouldValidate: true, - }), - ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, { - shouldValidate: false, - }), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - }); - }); - }); - describe('tests that require partially filled order', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let takerAddress: string; - let fillableAmount: BigNumber; - let partialFillAmount: BigNumber; - let signedOrder: SignedOrder; - let orderHash: string; - before(() => { - takerAddress = userAddresses[1]; - tokenUtils = new TokenUtils(tokens); - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - beforeEach(async () => { - fillableAmount = new BigNumber(5); - partialFillAmount = new BigNumber(2); - signedOrder = await fillScenarios.createPartiallyFilledSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - takerAddress, - fillableAmount, - partialFillAmount, - ); - orderHash = ZeroEx.getOrderHashHex(signedOrder); - }); - describe('#getUnavailableTakerAmountAsync', () => { - it('should throw if passed an invalid orderHash', async () => { - const invalidOrderHashHex = '0x123'; - return expect(zeroEx.exchange.getUnavailableTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); - }); - it('should return zero if passed a valid but non-existent orderHash', async () => { - const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(NON_EXISTENT_ORDER_HASH); - expect(unavailableValueT).to.be.bignumber.equal(0); - }); - it('should return the unavailableValueT for a valid and partially filled orderHash', async () => { - const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); - expect(unavailableValueT).to.be.bignumber.equal(partialFillAmount); - }); - }); - describe('#getFilledTakerAmountAsync', () => { - it('should throw if passed an invalid orderHash', async () => { - const invalidOrderHashHex = '0x123'; - return expect(zeroEx.exchange.getFilledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); - }); - it('should return zero if passed a valid but non-existent orderHash', async () => { - const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(NON_EXISTENT_ORDER_HASH); - expect(filledValueT).to.be.bignumber.equal(0); - }); - it('should return the filledValueT for a valid and partially filled orderHash', async () => { - const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(orderHash); - expect(filledValueT).to.be.bignumber.equal(partialFillAmount); - }); - }); - describe('#getCancelledTakerAmountAsync', () => { - it('should throw if passed an invalid orderHash', async () => { - const invalidOrderHashHex = '0x123'; - return expect(zeroEx.exchange.getCancelledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); - }); - it('should return zero if passed a valid but non-existent orderHash', async () => { - const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(NON_EXISTENT_ORDER_HASH); - expect(cancelledValueT).to.be.bignumber.equal(0); - }); - it('should return the cancelledValueT for a valid and partially filled orderHash', async () => { - const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHash); - expect(cancelledValueT).to.be.bignumber.equal(0); - }); - it('should return the cancelledValueT for a valid and cancelled orderHash', async () => { - const cancelAmount = fillableAmount.minus(partialFillAmount); - await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount); - const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHash); - expect(cancelledValueT).to.be.bignumber.equal(cancelAmount); - }); - }); - }); - describe('#subscribe', () => { - const indexFilterValues = {}; - const shouldThrowOnInsufficientBalanceOrAllowance = true; - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let takerAddress: string; - let makerAddress: string; - let fillableAmount: BigNumber; - let signedOrder: SignedOrder; - const takerTokenFillAmountInBaseUnits = new BigNumber(1); - const cancelTakerAmountInBaseUnits = new BigNumber(1); - before(() => { - [coinbase, makerAddress, takerAddress] = userAddresses; - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - beforeEach(async () => { - fillableAmount = new BigNumber(5); - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - }); - afterEach(async () => { - zeroEx.exchange.unsubscribeAll(); - }); - // Hack: Mocha does not allow a test to be both async and have a `done` callback - // Since we need to await the receipt of the event in the `subscribe` callback, - // we do need both. A hack is to make the top-level a sync fn w/ a done callback and then - // wrap the rest of the test in an async block - // Source: https://github.com/mochajs/mocha/issues/2407 - it('Should receive the LogFill event when an order is filled', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { - expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogFill); - }, - ); - zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callback); - await zeroEx.exchange.fillOrderAsync( - signedOrder, - takerTokenFillAmountInBaseUnits, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - })().catch(done); - }); - it('Should receive the LogCancel event when an order is cancelled', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<LogCancelContractEventArgs>) => { - expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogCancel); - }, - ); - zeroEx.exchange.subscribe(ExchangeEvents.LogCancel, indexFilterValues, callback); - await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerAmountInBaseUnits); - })().catch(done); - }); - it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callbackNeverToBeCalled); + let web3: Web3; + let zeroEx: ZeroEx; + let tokenUtils: TokenUtils; + let tokens: Token[]; + let userAddresses: string[]; + let zrxTokenAddress: string; + let fillScenarios: FillScenarios; + let exchangeContractAddress: string; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + exchangeContractAddress = zeroEx.exchange.getContractAddress(); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); + await fillScenarios.initTokenBalancesAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('fillOrKill order(s)', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipient: string; + const takerTokenFillAmount = new BigNumber(5); + before(async () => { + [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + describe('#batchFillOrKillAsync', () => { + it('successfully batch fillOrKill', async () => { + const fillableAmount = new BigNumber(5); + const partialFillTakerAmount = new BigNumber(2); + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const orderFillRequests = [ + { + signedOrder, + takerTokenFillAmount: partialFillTakerAmount, + }, + { + signedOrder: anotherSignedOrder, + takerTokenFillAmount: partialFillTakerAmount, + }, + ]; + await zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress); + }); + describe('order transaction options', () => { + let signedOrder: SignedOrder; + let orderFillRequests: OrderFillRequest[]; + const fillableAmount = new BigNumber(5); + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + orderFillRequests = [ + { + signedOrder, + takerTokenFillAmount: new BigNumber(0), + }, + ]; + }); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, { + shouldValidate: true, + }), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, { + shouldValidate: false, + }), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + }); + }); + describe('#fillOrKillOrderAsync', () => { + let signedOrder: SignedOrder; + const fillableAmount = new BigNumber(5); + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + describe('successful fills', () => { + it('should fill a valid order', async () => { + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + 0, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + 0, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount, + ); + await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, takerTokenFillAmount, takerAddress); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount.minus(takerTokenFillAmount), + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + takerTokenFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + takerTokenFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount.minus(takerTokenFillAmount), + ); + }); + it('should partially fill a valid order', async () => { + const partialFillAmount = new BigNumber(3); + await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, partialFillAmount, takerAddress); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount.minus(partialFillAmount), + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + partialFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + partialFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount.minus(partialFillAmount), + ); + }); + }); + describe('order transaction options', () => { + const emptyFillableAmount = new BigNumber(0); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, { + shouldValidate: true, + }), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, { + shouldValidate: false, + }), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + }); + }); + }); + describe('fill order(s)', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipient: string; + const fillableAmount = new BigNumber(5); + const takerTokenFillAmount = new BigNumber(5); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + before(async () => { + [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + describe('#fillOrderAsync', () => { + describe('successful fills', () => { + it('should fill a valid order', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + 0, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + 0, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount, + ); + const txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + takerTokenFillAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount.minus(takerTokenFillAmount), + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + takerTokenFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + takerTokenFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount.minus(takerTokenFillAmount), + ); + }); + it('should partially fill the valid order', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const partialFillAmount = new BigNumber(3); + const txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + partialFillAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount.minus(partialFillAmount), + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + partialFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + partialFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount.minus(partialFillAmount), + ); + }); + it('should fill the valid orders with fees', async () => { + const makerFee = new BigNumber(1); + const takerFee = new BigNumber(2); + const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + feeRecipient, + ); + const txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + takerTokenFillAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + expect(await zeroEx.token.getBalanceAsync(zrxTokenAddress, feeRecipient)).to.be.bignumber.equal( + makerFee.plus(takerFee), + ); + }); + }); + describe('order transaction options', () => { + let signedOrder: SignedOrder; + const emptyFillTakerAmount = new BigNumber(0); + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.fillOrderAsync( + signedOrder, + emptyFillTakerAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.fillOrderAsync( + signedOrder, + emptyFillTakerAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: true, + }, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.fillOrderAsync( + signedOrder, + emptyFillTakerAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: false, + }, + ), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + }); + describe('negative fill amount', async () => { + let signedOrder: SignedOrder; + const negativeFillTakerAmount = new BigNumber(-100); + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + it('should not allow the exchange wrapper to fill if amount is negative', async () => { + return expect( + zeroEx.exchange.fillOrderAsync( + signedOrder, + negativeFillTakerAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejected(); + }); + }); + }); + describe('#batchFillOrdersAsync', () => { + let signedOrder: SignedOrder; + let signedOrderHashHex: string; + let anotherSignedOrder: SignedOrder; + let anotherOrderHashHex: string; + let orderFillBatch: OrderFillRequest[]; + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + signedOrderHashHex = ZeroEx.getOrderHashHex(signedOrder); + anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); + }); + describe('successful batch fills', () => { + beforeEach(() => { + orderFillBatch = [ + { + signedOrder, + takerTokenFillAmount, + }, + { + signedOrder: anotherSignedOrder, + takerTokenFillAmount, + }, + ]; + }); + it('should throw if a batch is empty', async () => { + return expect( + zeroEx.exchange.batchFillOrdersAsync( + [], + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + }); + it('should successfully fill multiple orders', async () => { + const txHash = await zeroEx.exchange.batchFillOrdersAsync( + orderFillBatch, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); + const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); + expect(filledAmount).to.be.bignumber.equal(takerTokenFillAmount); + expect(anotherFilledAmount).to.be.bignumber.equal(takerTokenFillAmount); + }); + }); + describe('order transaction options', () => { + beforeEach(async () => { + const emptyFillTakerAmount = new BigNumber(0); + orderFillBatch = [ + { + signedOrder, + takerTokenFillAmount: emptyFillTakerAmount, + }, + { + signedOrder: anotherSignedOrder, + takerTokenFillAmount: emptyFillTakerAmount, + }, + ]; + }); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.batchFillOrdersAsync( + orderFillBatch, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.batchFillOrdersAsync( + orderFillBatch, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: true, + }, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.batchFillOrdersAsync( + orderFillBatch, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: false, + }, + ), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + }); + describe('negative batch fill amount', async () => { + beforeEach(async () => { + const negativeFillTakerAmount = new BigNumber(-100); + orderFillBatch = [ + { + signedOrder, + takerTokenFillAmount, + }, + { + signedOrder: anotherSignedOrder, + takerTokenFillAmount: negativeFillTakerAmount, + }, + ]; + }); + it('should not allow the exchange wrapper to batch fill if any amount is negative', async () => { + return expect( + zeroEx.exchange.batchFillOrdersAsync( + orderFillBatch, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejected(); + }); + }); + }); + describe('#fillOrdersUpTo', () => { + let signedOrder: SignedOrder; + let signedOrderHashHex: string; + let anotherSignedOrder: SignedOrder; + let anotherOrderHashHex: string; + let signedOrders: SignedOrder[]; + const fillUpToAmount = fillableAmount.plus(fillableAmount).minus(1); + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + signedOrderHashHex = ZeroEx.getOrderHashHex(signedOrder); + anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); + signedOrders = [signedOrder, anotherSignedOrder]; + }); + describe('successful batch fills', () => { + it('should throw if a batch is empty', async () => { + return expect( + zeroEx.exchange.fillOrdersUpToAsync( + [], + fillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + }); + it('should successfully fill up to specified amount when all orders are fully funded', async () => { + const txHash = await zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + fillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); + const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); + expect(filledAmount).to.be.bignumber.equal(fillableAmount); + const remainingFillAmount = fillableAmount.minus(1); + expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount); + }); + it('should successfully fill up to specified amount even if filling all orders would fail', async () => { + const missingBalance = new BigNumber(1); // User will still have enough balance to fill up to 9, + // but won't have 10 to fully fill all orders in a batch. + await zeroEx.token.transferAsync(makerTokenAddress, makerAddress, coinbase, missingBalance); + const txHash = await zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + fillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); + const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); + expect(filledAmount).to.be.bignumber.equal(fillableAmount); + const remainingFillAmount = fillableAmount.minus(1); + expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount); + }); + }); + describe('failed batch fills', () => { + it("should fail validation if user doesn't have enough balance without fill up to", async () => { + const missingBalance = new BigNumber(2); // User will only have enough balance to fill up to 8 + await zeroEx.token.transferAsync(makerTokenAddress, makerAddress, coinbase, missingBalance); + return expect( + zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + fillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance); + }); + }); + describe('order transaction options', () => { + const emptyFillUpToAmount = new BigNumber(0); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + emptyFillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + emptyFillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: true, + }, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + emptyFillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: false, + }, + ), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + }); + }); + }); + describe('cancel order(s)', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + const fillableAmount = new BigNumber(5); + let signedOrder: SignedOrder; + let orderHashHex: string; + const cancelAmount = new BigNumber(3); + beforeEach(async () => { + [coinbase, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + orderHashHex = ZeroEx.getOrderHashHex(signedOrder); + }); + describe('#cancelOrderAsync', () => { + describe('successful cancels', () => { + it('should cancel an order', async () => { + const txHash = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount); + await zeroEx.awaitTransactionMinedAsync(txHash); + const cancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHashHex); + expect(cancelledAmount).to.be.bignumber.equal(cancelAmount); + }); + }); + describe('order transaction options', () => { + const emptyCancelTakerTokenAmount = new BigNumber(0); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount), + ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, { + shouldValidate: true, + }), + ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, { + shouldValidate: false, + }), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + }); + }); + describe('#batchCancelOrdersAsync', () => { + let anotherSignedOrder: SignedOrder; + let anotherOrderHashHex: string; + let cancelBatch: OrderCancellationRequest[]; + beforeEach(async () => { + anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); + cancelBatch = [ + { + order: signedOrder, + takerTokenCancelAmount: cancelAmount, + }, + { + order: anotherSignedOrder, + takerTokenCancelAmount: cancelAmount, + }, + ]; + }); + describe('failed batch cancels', () => { + it('should throw when orders have different makers', async () => { + const signedOrderWithDifferentMaker = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + takerAddress, + takerAddress, + fillableAmount, + ); + return expect( + zeroEx.exchange.batchCancelOrdersAsync([ + cancelBatch[0], + { + order: signedOrderWithDifferentMaker, + takerTokenCancelAmount: cancelAmount, + }, + ]), + ).to.be.rejectedWith(ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed); + }); + }); + describe('successful batch cancels', () => { + it('should cancel a batch of orders', async () => { + await zeroEx.exchange.batchCancelOrdersAsync(cancelBatch); + const cancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHashHex); + const anotherCancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync( + anotherOrderHashHex, + ); + expect(cancelledAmount).to.be.bignumber.equal(cancelAmount); + expect(anotherCancelledAmount).to.be.bignumber.equal(cancelAmount); + }); + }); + describe('order transaction options', () => { + beforeEach(async () => { + const emptyTakerTokenCancelAmount = new BigNumber(0); + cancelBatch = [ + { + order: signedOrder, + takerTokenCancelAmount: emptyTakerTokenCancelAmount, + }, + { + order: anotherSignedOrder, + takerTokenCancelAmount: emptyTakerTokenCancelAmount, + }, + ]; + }); + it('should validate when orderTransactionOptions are not present', async () => { + return expect(zeroEx.exchange.batchCancelOrdersAsync(cancelBatch)).to.be.rejectedWith( + ExchangeContractErrs.OrderCancelAmountZero, + ); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, { + shouldValidate: true, + }), + ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, { + shouldValidate: false, + }), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + }); + }); + }); + describe('tests that require partially filled order', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let takerAddress: string; + let fillableAmount: BigNumber; + let partialFillAmount: BigNumber; + let signedOrder: SignedOrder; + let orderHash: string; + before(() => { + takerAddress = userAddresses[1]; + tokenUtils = new TokenUtils(tokens); + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + beforeEach(async () => { + fillableAmount = new BigNumber(5); + partialFillAmount = new BigNumber(2); + signedOrder = await fillScenarios.createPartiallyFilledSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + takerAddress, + fillableAmount, + partialFillAmount, + ); + orderHash = ZeroEx.getOrderHashHex(signedOrder); + }); + describe('#getUnavailableTakerAmountAsync', () => { + it('should throw if passed an invalid orderHash', async () => { + const invalidOrderHashHex = '0x123'; + return expect(zeroEx.exchange.getUnavailableTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); + }); + it('should return zero if passed a valid but non-existent orderHash', async () => { + const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(NON_EXISTENT_ORDER_HASH); + expect(unavailableValueT).to.be.bignumber.equal(0); + }); + it('should return the unavailableValueT for a valid and partially filled orderHash', async () => { + const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); + expect(unavailableValueT).to.be.bignumber.equal(partialFillAmount); + }); + }); + describe('#getFilledTakerAmountAsync', () => { + it('should throw if passed an invalid orderHash', async () => { + const invalidOrderHashHex = '0x123'; + return expect(zeroEx.exchange.getFilledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); + }); + it('should return zero if passed a valid but non-existent orderHash', async () => { + const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(NON_EXISTENT_ORDER_HASH); + expect(filledValueT).to.be.bignumber.equal(0); + }); + it('should return the filledValueT for a valid and partially filled orderHash', async () => { + const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(orderHash); + expect(filledValueT).to.be.bignumber.equal(partialFillAmount); + }); + }); + describe('#getCancelledTakerAmountAsync', () => { + it('should throw if passed an invalid orderHash', async () => { + const invalidOrderHashHex = '0x123'; + return expect(zeroEx.exchange.getCancelledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); + }); + it('should return zero if passed a valid but non-existent orderHash', async () => { + const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(NON_EXISTENT_ORDER_HASH); + expect(cancelledValueT).to.be.bignumber.equal(0); + }); + it('should return the cancelledValueT for a valid and partially filled orderHash', async () => { + const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHash); + expect(cancelledValueT).to.be.bignumber.equal(0); + }); + it('should return the cancelledValueT for a valid and cancelled orderHash', async () => { + const cancelAmount = fillableAmount.minus(partialFillAmount); + await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount); + const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHash); + expect(cancelledValueT).to.be.bignumber.equal(cancelAmount); + }); + }); + }); + describe('#subscribe', () => { + const indexFilterValues = {}; + const shouldThrowOnInsufficientBalanceOrAllowance = true; + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let takerAddress: string; + let makerAddress: string; + let fillableAmount: BigNumber; + let signedOrder: SignedOrder; + const takerTokenFillAmountInBaseUnits = new BigNumber(1); + const cancelTakerAmountInBaseUnits = new BigNumber(1); + before(() => { + [coinbase, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + beforeEach(async () => { + fillableAmount = new BigNumber(5); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + afterEach(async () => { + zeroEx.exchange.unsubscribeAll(); + }); + // Hack: Mocha does not allow a test to be both async and have a `done` callback + // Since we need to await the receipt of the event in the `subscribe` callback, + // we do need both. A hack is to make the top-level a sync fn w/ a done callback and then + // wrap the rest of the test in an async block + // Source: https://github.com/mochajs/mocha/issues/2407 + it('Should receive the LogFill event when an order is filled', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { + expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogFill); + }, + ); + zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callback); + await zeroEx.exchange.fillOrderAsync( + signedOrder, + takerTokenFillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + })().catch(done); + }); + it('Should receive the LogCancel event when an order is cancelled', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<LogCancelContractEventArgs>) => { + expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogCancel); + }, + ); + zeroEx.exchange.subscribe(ExchangeEvents.LogCancel, indexFilterValues, callback); + await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerAmountInBaseUnits); + })().catch(done); + }); + it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callbackNeverToBeCalled); - const newProvider = web3Factory.getRpcProvider(); - zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); + const newProvider = web3Factory.getRpcProvider(); + zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { - expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogFill); - }, - ); - zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callback); - await zeroEx.exchange.fillOrderAsync( - signedOrder, - takerTokenFillAmountInBaseUnits, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - })().catch(done); - }); - it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - const subscriptionToken = zeroEx.exchange.subscribe( - ExchangeEvents.LogFill, - indexFilterValues, - callbackNeverToBeCalled, - ); - zeroEx.exchange.unsubscribe(subscriptionToken); - await zeroEx.exchange.fillOrderAsync( - signedOrder, - takerTokenFillAmountInBaseUnits, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - done(); - })().catch(done); - }); - }); - describe('#getOrderHashHexUsingContractCallAsync', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let makerAddress: string; - let takerAddress: string; - const fillableAmount = new BigNumber(5); - before(async () => { - [, makerAddress, takerAddress] = userAddresses; - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - it("get's the same hash as the local function", async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - const orderHashFromContract = await (zeroEx.exchange as any)._getOrderHashHexUsingContractCallAsync( - signedOrder, - ); - expect(orderHash).to.equal(orderHashFromContract); - }); - }); - describe('#getZRXTokenAddressAsync', () => { - it('gets the same token as is in token registry', () => { - const zrxAddress = zeroEx.exchange.getZRXTokenAddress(); - const zrxToken = tokenUtils.getProtocolTokenOrThrow(); - expect(zrxAddress).to.equal(zrxToken.address); - }); - }); - describe('#getLogsAsync', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let makerAddress: string; - let takerAddress: string; - const fillableAmount = new BigNumber(5); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - const blockRange: BlockRange = { - fromBlock: 0, - toBlock: BlockParamLiteral.Latest, - }; - let txHash: string; - before(async () => { - [, makerAddress, takerAddress] = userAddresses; - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - it('should get logs with decoded args emitted by LogFill', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = ExchangeEvents.LogFill; - const indexFilterValues = {}; - const logs = await zeroEx.exchange.getLogsAsync(eventName, blockRange, indexFilterValues); - expect(logs).to.have.length(1); - expect(logs[0].event).to.be.equal(eventName); - }); - it('should only get the logs with the correct event name', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - const differentEventName = ExchangeEvents.LogCancel; - const indexFilterValues = {}; - const logs = await zeroEx.exchange.getLogsAsync(differentEventName, blockRange, indexFilterValues); - expect(logs).to.have.length(0); - }); - it('should only get the logs with the correct indexed fields', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { + expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogFill); + }, + ); + zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callback); + await zeroEx.exchange.fillOrderAsync( + signedOrder, + takerTokenFillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + })().catch(done); + }); + it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + const subscriptionToken = zeroEx.exchange.subscribe( + ExchangeEvents.LogFill, + indexFilterValues, + callbackNeverToBeCalled, + ); + zeroEx.exchange.unsubscribe(subscriptionToken); + await zeroEx.exchange.fillOrderAsync( + signedOrder, + takerTokenFillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + done(); + })().catch(done); + }); + }); + describe('#getOrderHashHexUsingContractCallAsync', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + const fillableAmount = new BigNumber(5); + before(async () => { + [, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + it("get's the same hash as the local function", async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + const orderHashFromContract = await (zeroEx.exchange as any)._getOrderHashHexUsingContractCallAsync( + signedOrder, + ); + expect(orderHash).to.equal(orderHashFromContract); + }); + }); + describe('#getZRXTokenAddressAsync', () => { + it('gets the same token as is in token registry', () => { + const zrxAddress = zeroEx.exchange.getZRXTokenAddress(); + const zrxToken = tokenUtils.getProtocolTokenOrThrow(); + expect(zrxAddress).to.equal(zrxToken.address); + }); + }); + describe('#getLogsAsync', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + const fillableAmount = new BigNumber(5); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + const blockRange: BlockRange = { + fromBlock: 0, + toBlock: BlockParamLiteral.Latest, + }; + let txHash: string; + before(async () => { + [, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + it('should get logs with decoded args emitted by LogFill', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = ExchangeEvents.LogFill; + const indexFilterValues = {}; + const logs = await zeroEx.exchange.getLogsAsync(eventName, blockRange, indexFilterValues); + expect(logs).to.have.length(1); + expect(logs[0].event).to.be.equal(eventName); + }); + it('should only get the logs with the correct event name', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const differentEventName = ExchangeEvents.LogCancel; + const indexFilterValues = {}; + const logs = await zeroEx.exchange.getLogsAsync(differentEventName, blockRange, indexFilterValues); + expect(logs).to.have.length(0); + }); + it('should only get the logs with the correct indexed fields', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); - const differentMakerAddress = userAddresses[2]; - const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - differentMakerAddress, - takerAddress, - fillableAmount, - ); - txHash = await zeroEx.exchange.fillOrderAsync( - anotherSignedOrder, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); + const differentMakerAddress = userAddresses[2]; + const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + differentMakerAddress, + takerAddress, + fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + anotherSignedOrder, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = ExchangeEvents.LogFill; - const indexFilterValues = { - maker: differentMakerAddress, - }; - const logs = await zeroEx.exchange.getLogsAsync<LogFillContractEventArgs>( - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(args.maker).to.be.equal(differentMakerAddress); - }); - }); + const eventName = ExchangeEvents.LogFill; + const indexFilterValues = { + maker: differentMakerAddress, + }; + const logs = await zeroEx.exchange.getLogsAsync<LogFillContractEventArgs>( + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(args.maker).to.be.equal(differentMakerAddress); + }); + }); }); // tslint:disable:max-file-line-count diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts index a76d84da1..770615f88 100644 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -24,137 +24,137 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(testConstants.RPC_URL); describe('ExpirationWatcher', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let tokenUtils: TokenUtils; - let tokens: Token[]; - let userAddresses: string[]; - let zrxTokenAddress: string; - let fillScenarios: FillScenarios; - let exchangeContractAddress: string; - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipient: string; - const fillableAmount = new BigNumber(5); - let currentUnixTimestampSec: BigNumber; - let timer: Sinon.SinonFakeTimers; - let expirationWatcher: ExpirationWatcher; - before(async () => { - web3 = web3Factory.create(); - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - zeroEx = new ZeroEx(web3.currentProvider, config); - exchangeContractAddress = zeroEx.exchange.getContractAddress(); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - tokenUtils = new TokenUtils(tokens); - zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; - fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); - [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - const sinonTimerConfig = { shouldAdvanceTime: true } as any; - // This constructor has incorrect types - timer = Sinon.useFakeTimers(sinonTimerConfig); - currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); - expirationWatcher = new ExpirationWatcher(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - timer.restore(); - expirationWatcher.unsubscribe(); - }); - it('correctly emits events when order expires', (done: DoneCallback) => { - (async () => { - const orderLifetimeSec = 60; - const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - expirationUnixTimestampSec, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); - const callbackAsync = reportNoErrorCallbackErrors(done)((hash: string) => { - expect(hash).to.be.equal(orderHash); - expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec); - }); - expirationWatcher.subscribe(callbackAsync); - timer.tick(orderLifetimeSec * 1000); - })().catch(done); - }); - it("doesn't emit events before order expires", (done: DoneCallback) => { - (async () => { - const orderLifetimeSec = 60; - const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - expirationUnixTimestampSec, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); - const callbackAsync = reportNoErrorCallbackErrors(done)(async (hash: string) => { - done(new Error('Emitted expiration went before the order actually expired')); - }); - expirationWatcher.subscribe(callbackAsync); - const notEnoughTime = orderLifetimeSec - 1; - timer.tick(notEnoughTime * 1000); - done(); - })().catch(done); - }); - it('emits events in correct order', (done: DoneCallback) => { - (async () => { - const order1Lifetime = 60; - const order2Lifetime = 120; - const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime); - const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime); - const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - order1ExpirationUnixTimestampSec, - ); - const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - order2ExpirationUnixTimestampSec, - ); - const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1); - const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2); - expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000)); - expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000)); - const expirationOrder = [orderHash1, orderHash2]; - const expectToBeCalledOnce = false; - const callbackAsync = reportNoErrorCallbackErrors(done, expectToBeCalledOnce)((hash: string) => { - const orderHash = expirationOrder.shift(); - expect(hash).to.be.equal(orderHash); - if (_.isEmpty(expirationOrder)) { - done(); - } - }); - expirationWatcher.subscribe(callbackAsync); - timer.tick(order2Lifetime * 1000); - })().catch(done); - }); + let web3: Web3; + let zeroEx: ZeroEx; + let tokenUtils: TokenUtils; + let tokens: Token[]; + let userAddresses: string[]; + let zrxTokenAddress: string; + let fillScenarios: FillScenarios; + let exchangeContractAddress: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipient: string; + const fillableAmount = new BigNumber(5); + let currentUnixTimestampSec: BigNumber; + let timer: Sinon.SinonFakeTimers; + let expirationWatcher: ExpirationWatcher; + before(async () => { + web3 = web3Factory.create(); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + zeroEx = new ZeroEx(web3.currentProvider, config); + exchangeContractAddress = zeroEx.exchange.getContractAddress(); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); + [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + const sinonTimerConfig = { shouldAdvanceTime: true } as any; + // This constructor has incorrect types + timer = Sinon.useFakeTimers(sinonTimerConfig); + currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); + expirationWatcher = new ExpirationWatcher(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + timer.restore(); + expirationWatcher.unsubscribe(); + }); + it('correctly emits events when order expires', (done: DoneCallback) => { + (async () => { + const orderLifetimeSec = 60; + const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationUnixTimestampSec, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); + const callbackAsync = reportNoErrorCallbackErrors(done)((hash: string) => { + expect(hash).to.be.equal(orderHash); + expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec); + }); + expirationWatcher.subscribe(callbackAsync); + timer.tick(orderLifetimeSec * 1000); + })().catch(done); + }); + it("doesn't emit events before order expires", (done: DoneCallback) => { + (async () => { + const orderLifetimeSec = 60; + const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationUnixTimestampSec, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); + const callbackAsync = reportNoErrorCallbackErrors(done)(async (hash: string) => { + done(new Error('Emitted expiration went before the order actually expired')); + }); + expirationWatcher.subscribe(callbackAsync); + const notEnoughTime = orderLifetimeSec - 1; + timer.tick(notEnoughTime * 1000); + done(); + })().catch(done); + }); + it('emits events in correct order', (done: DoneCallback) => { + (async () => { + const order1Lifetime = 60; + const order2Lifetime = 120; + const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime); + const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime); + const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + order1ExpirationUnixTimestampSec, + ); + const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + order2ExpirationUnixTimestampSec, + ); + const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1); + const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2); + expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000)); + expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000)); + const expirationOrder = [orderHash1, orderHash2]; + const expectToBeCalledOnce = false; + const callbackAsync = reportNoErrorCallbackErrors(done, expectToBeCalledOnce)((hash: string) => { + const orderHash = expirationOrder.shift(); + expect(hash).to.be.equal(orderHash); + if (_.isEmpty(expirationOrder)) { + done(); + } + }); + expirationWatcher.subscribe(callbackAsync); + timer.tick(order2Lifetime * 1000); + })().catch(done); + }); }); diff --git a/packages/0x.js/test/order_state_watcher_test.ts b/packages/0x.js/test/order_state_watcher_test.ts index 22ed80446..2e9202fe2 100644 --- a/packages/0x.js/test/order_state_watcher_test.ts +++ b/packages/0x.js/test/order_state_watcher_test.ts @@ -6,14 +6,14 @@ import 'mocha'; import * as Web3 from 'web3'; import { - ExchangeContractErrs, - OrderState, - OrderStateInvalid, - OrderStateValid, - SignedOrder, - Token, - ZeroEx, - ZeroExError, + ExchangeContractErrs, + OrderState, + OrderStateInvalid, + OrderStateValid, + SignedOrder, + Token, + ZeroEx, + ZeroExError, } from '../src'; import { DoneCallback } from '../src/types'; @@ -31,528 +31,528 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); describe('OrderStateWatcher', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let tokens: Token[]; - let tokenUtils: TokenUtils; - let fillScenarios: FillScenarios; - let userAddresses: string[]; - let zrxTokenAddress: string; - let exchangeContractAddress: string; - let makerToken: Token; - let takerToken: Token; - let maker: string; - let taker: string; - let signedOrder: SignedOrder; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const decimals = constants.ZRX_DECIMALS; - const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - exchangeContractAddress = zeroEx.exchange.getContractAddress(); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - [, maker, taker] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - tokenUtils = new TokenUtils(tokens); - zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; - fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); - await fillScenarios.initTokenBalancesAsync(); - [makerToken, takerToken] = tokenUtils.getDummyTokens(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#removeOrder', async () => { - it('should successfully remove existing order', async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); - expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({ - [orderHash]: signedOrder, - }); - let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes; - expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash); - zeroEx.orderStateWatcher.removeOrder(orderHash); - expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.not.include({ - [orderHash]: signedOrder, - }); - dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes; - expect(dependentOrderHashes[signedOrder.maker]).to.be.undefined(); - }); - it('should no-op when removing a non-existing order', async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - const nonExistentOrderHash = `0x${orderHash - .substr(2) - .split('') - .reverse() - .join('')}`; - zeroEx.orderStateWatcher.removeOrder(nonExistentOrderHash); - }); - }); - describe('#subscribe', async () => { - afterEach(async () => { - zeroEx.orderStateWatcher.unsubscribe(); - }); - it('should fail when trying to subscribe twice', async () => { - zeroEx.orderStateWatcher.subscribe(_.noop); - expect(() => zeroEx.orderStateWatcher.subscribe(_.noop)).to.throw(ZeroExError.SubscriptionAlreadyPresent); - }); - }); - describe('tests with cleanup', async () => { - afterEach(async () => { - zeroEx.orderStateWatcher.unsubscribe(); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.removeOrder(orderHash); - }); - it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.false(); - const invalidOrderState = orderState as OrderStateInvalid; - expect(invalidOrderState.orderHash).to.be.equal(orderHash); - expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0)); - })().catch(done); - }); - it('should not emit an orderState event when irrelevant Transfer event received', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - throw new Error('OrderState callback fired for irrelevant order'); - }); - zeroEx.orderStateWatcher.subscribe(callback); - const notTheMaker = userAddresses[0]; - const anyRecipient = taker; - const transferAmount = new BigNumber(2); - await zeroEx.token.transferAsync(makerToken.address, notTheMaker, anyRecipient, transferAmount); - setTimeout(() => { - done(); - }, TIMEOUT_MS); - })().catch(done); - }); - it('should emit orderStateInvalid when maker moves balance backing watched order', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.false(); - const invalidOrderState = orderState as OrderStateInvalid; - expect(invalidOrderState.orderHash).to.be.equal(orderHash); - expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance); - }); - zeroEx.orderStateWatcher.subscribe(callback); - const anyRecipient = taker; - const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); - await zeroEx.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance); - })().catch(done); - }); - it('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + let web3: Web3; + let zeroEx: ZeroEx; + let tokens: Token[]; + let tokenUtils: TokenUtils; + let fillScenarios: FillScenarios; + let userAddresses: string[]; + let zrxTokenAddress: string; + let exchangeContractAddress: string; + let makerToken: Token; + let takerToken: Token; + let maker: string; + let taker: string; + let signedOrder: SignedOrder; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const decimals = constants.ZRX_DECIMALS; + const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + exchangeContractAddress = zeroEx.exchange.getContractAddress(); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + [, maker, taker] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); + await fillScenarios.initTokenBalancesAsync(); + [makerToken, takerToken] = tokenUtils.getDummyTokens(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#removeOrder', async () => { + it('should successfully remove existing order', async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); + expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({ + [orderHash]: signedOrder, + }); + let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes; + expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash); + zeroEx.orderStateWatcher.removeOrder(orderHash); + expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.not.include({ + [orderHash]: signedOrder, + }); + dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes; + expect(dependentOrderHashes[signedOrder.maker]).to.be.undefined(); + }); + it('should no-op when removing a non-existing order', async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + const nonExistentOrderHash = `0x${orderHash + .substr(2) + .split('') + .reverse() + .join('')}`; + zeroEx.orderStateWatcher.removeOrder(nonExistentOrderHash); + }); + }); + describe('#subscribe', async () => { + afterEach(async () => { + zeroEx.orderStateWatcher.unsubscribe(); + }); + it('should fail when trying to subscribe twice', async () => { + zeroEx.orderStateWatcher.subscribe(_.noop); + expect(() => zeroEx.orderStateWatcher.subscribe(_.noop)).to.throw(ZeroExError.SubscriptionAlreadyPresent); + }); + }); + describe('tests with cleanup', async () => { + afterEach(async () => { + zeroEx.orderStateWatcher.unsubscribe(); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.removeOrder(orderHash); + }); + it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0)); + })().catch(done); + }); + it('should not emit an orderState event when irrelevant Transfer event received', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + zeroEx.orderStateWatcher.addOrder(signedOrder); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + throw new Error('OrderState callback fired for irrelevant order'); + }); + zeroEx.orderStateWatcher.subscribe(callback); + const notTheMaker = userAddresses[0]; + const anyRecipient = taker; + const transferAmount = new BigNumber(2); + await zeroEx.token.transferAsync(makerToken.address, notTheMaker, anyRecipient, transferAmount); + setTimeout(() => { + done(); + }, TIMEOUT_MS); + })().catch(done); + }); + it('should emit orderStateInvalid when maker moves balance backing watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance); + }); + zeroEx.orderStateWatcher.subscribe(callback); + const anyRecipient = taker; + const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); + await zeroEx.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance); + })().catch(done); + }); + it('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.false(); - const invalidOrderState = orderState as OrderStateInvalid; - expect(invalidOrderState.orderHash).to.be.equal(orderHash); - expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); - }); - zeroEx.orderStateWatcher.subscribe(callback); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); + }); + zeroEx.orderStateWatcher.subscribe(callback); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - taker, - ); - })().catch(done); - }); - it('should emit orderStateValid when watched order partially filled', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + taker, + ); + })().catch(done); + }); + it('should emit orderStateValid when watched order partially filled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); - const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); - const fillAmountInBaseUnits = new BigNumber(2); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); + const fillAmountInBaseUnits = new BigNumber(2); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.true(); - const validOrderState = orderState as OrderStateValid; - expect(validOrderState.orderHash).to.be.equal(orderHash); - const orderRelevantState = validOrderState.orderRelevantState; - const remainingMakerBalance = makerBalance.sub(fillAmountInBaseUnits); - const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits); - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - remainingFillable, - ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( - remainingFillable, - ); - expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance); - }); - zeroEx.orderStateWatcher.subscribe(callback); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillAmountInBaseUnits, - shouldThrowOnInsufficientBalanceOrAllowance, - taker, - ); - })().catch(done); - }); - it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => { - (async () => { - const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18); - const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), 18); - signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerToken.address, - takerToken.address, - makerFee, - takerFee, - maker, - taker, - fillableAmount, - taker, - ); - const callback = reportNodeCallbackErrors(done)(); - zeroEx.orderStateWatcher.addOrder(signedOrder); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0)); - })().catch(done); - }); - describe('remainingFillable(M|T)akerTokenAmount', () => { - it('should calculate correct remaining fillable', (done: DoneCallback) => { - (async () => { - const takerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(10), decimals); - const makerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(20), decimals); - signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - makerFillableAmount, - takerFillableAmount, - ); - const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.true(); - const validOrderState = orderState as OrderStateValid; - expect(validOrderState.orderHash).to.be.equal(orderHash); - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - ZeroEx.toBaseUnitAmount(new BigNumber(16), decimals), - ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( - ZeroEx.toBaseUnitAmount(new BigNumber(8), decimals), - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillAmountInBaseUnits, - shouldThrowOnInsufficientBalanceOrAllowance, - taker, - ); - })().catch(done); - }); - it('should equal approved amount when approved amount is lowest', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + expect(validOrderState.orderHash).to.be.equal(orderHash); + const orderRelevantState = validOrderState.orderRelevantState; + const remainingMakerBalance = makerBalance.sub(fillAmountInBaseUnits); + const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits); + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingFillable, + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + remainingFillable, + ); + expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance); + }); + zeroEx.orderStateWatcher.subscribe(callback); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + taker, + ); + })().catch(done); + }); + it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => { + (async () => { + const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18); + const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), 18); + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + taker, + ); + const callback = reportNodeCallbackErrors(done)(); + zeroEx.orderStateWatcher.addOrder(signedOrder); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0)); + })().catch(done); + }); + describe('remainingFillable(M|T)akerTokenAmount', () => { + it('should calculate correct remaining fillable', (done: DoneCallback) => { + (async () => { + const takerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(10), decimals); + const makerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(20), decimals); + signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + makerFillableAmount, + takerFillableAmount, + ); + const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + expect(validOrderState.orderHash).to.be.equal(orderHash); + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + ZeroEx.toBaseUnitAmount(new BigNumber(16), decimals), + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + ZeroEx.toBaseUnitAmount(new BigNumber(8), decimals), + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + taker, + ); + })().catch(done); + }); + it('should equal approved amount when approved amount is lowest', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); - const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - const validOrderState = orderState as OrderStateValid; - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - changedMakerApprovalAmount, - ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( - changedMakerApprovalAmount, - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, changedMakerApprovalAmount); - })().catch(done); - }); - it('should equal balance amount when balance amount is lowest', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + changedMakerApprovalAmount, + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + changedMakerApprovalAmount, + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, changedMakerApprovalAmount); + })().catch(done); + }); + it('should equal balance amount when balance amount is lowest', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); - const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); + const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); - const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); - const transferAmount = makerBalance.sub(remainingAmount); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); + const transferAmount = makerBalance.sub(remainingAmount); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.true(); - const validOrderState = orderState as OrderStateValid; - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - remainingAmount, - ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( - remainingAmount, - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.transferAsync(makerToken.address, maker, ZeroEx.NULL_ADDRESS, transferAmount); - })().catch(done); - }); - it('should equal remaining amount when partially cancelled and order has fees', (done: DoneCallback) => { - (async () => { - const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); - const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); - const feeRecipient = taker; - signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerToken.address, - takerToken.address, - makerFee, - takerFee, - maker, - taker, - fillableAmount, - feeRecipient, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingAmount, + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + remainingAmount, + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.transferAsync(makerToken.address, maker, ZeroEx.NULL_ADDRESS, transferAmount); + })().catch(done); + }); + it('should equal remaining amount when partially cancelled and order has fees', (done: DoneCallback) => { + (async () => { + const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); + const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); + const feeRecipient = taker; + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + feeRecipient, + ); - const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals); - const transferTokenAmount = makerFee.sub(remainingTokenAmount); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals); + const transferTokenAmount = makerFee.sub(remainingTokenAmount); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.true(); - const validOrderState = orderState as OrderStateValid; - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - remainingTokenAmount, - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.exchange.cancelOrderAsync(signedOrder, transferTokenAmount); - })().catch(done); - }); - it('should equal ratio amount when fee balance is lowered', (done: DoneCallback) => { - (async () => { - const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); - const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); - const feeRecipient = taker; - signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerToken.address, - takerToken.address, - makerFee, - takerFee, - maker, - taker, - fillableAmount, - feeRecipient, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingTokenAmount, + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.exchange.cancelOrderAsync(signedOrder, transferTokenAmount); + })().catch(done); + }); + it('should equal ratio amount when fee balance is lowered', (done: DoneCallback) => { + (async () => { + const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); + const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); + const feeRecipient = taker; + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + feeRecipient, + ); - const remainingFeeAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals); + const remainingFeeAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals); - const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals); - const transferTokenAmount = makerFee.sub(remainingTokenAmount); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals); + const transferTokenAmount = makerFee.sub(remainingTokenAmount); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - const validOrderState = orderState as OrderStateValid; - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - remainingFeeAmount, - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, remainingFeeAmount); - await zeroEx.token.transferAsync( - makerToken.address, - maker, - ZeroEx.NULL_ADDRESS, - transferTokenAmount, - ); - })().catch(done); - }); - it('should calculate full amount when all available and non-divisible', (done: DoneCallback) => { - (async () => { - const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); - const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - const feeRecipient = taker; - signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerToken.address, - takerToken.address, - makerFee, - takerFee, - maker, - taker, - fillableAmount, - feeRecipient, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingFeeAmount, + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, remainingFeeAmount); + await zeroEx.token.transferAsync( + makerToken.address, + maker, + ZeroEx.NULL_ADDRESS, + transferTokenAmount, + ); + })().catch(done); + }); + it('should calculate full amount when all available and non-divisible', (done: DoneCallback) => { + (async () => { + const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); + const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + const feeRecipient = taker; + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + feeRecipient, + ); - zeroEx.orderStateWatcher.addOrder(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - const validOrderState = orderState as OrderStateValid; - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - fillableAmount, - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.setProxyAllowanceAsync( - makerToken.address, - maker, - ZeroEx.toBaseUnitAmount(new BigNumber(100), decimals), - ); - })().catch(done); - }); - }); - it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + fillableAmount, + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.setProxyAllowanceAsync( + makerToken.address, + maker, + ZeroEx.toBaseUnitAmount(new BigNumber(100), decimals), + ); + })().catch(done); + }); + }); + it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.false(); - const invalidOrderState = orderState as OrderStateInvalid; - expect(invalidOrderState.orderHash).to.be.equal(orderHash); - expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); - }); - zeroEx.orderStateWatcher.subscribe(callback); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); + }); + zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); - })().catch(done); - }); - it('should emit orderStateInvalid when within rounding error range', (done: DoneCallback) => { - (async () => { - const remainingFillableAmountInBaseUnits = new BigNumber(100); - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); + })().catch(done); + }); + it('should emit orderStateInvalid when within rounding error range', (done: DoneCallback) => { + (async () => { + const remainingFillableAmountInBaseUnits = new BigNumber(100); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.false(); - const invalidOrderState = orderState as OrderStateInvalid; - expect(invalidOrderState.orderHash).to.be.equal(orderHash); - expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.exchange.cancelOrderAsync( - signedOrder, - fillableAmount.minus(remainingFillableAmountInBaseUnits), - ); - })().catch(done); - }); - it('should emit orderStateValid when watched order partially cancelled', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.exchange.cancelOrderAsync( + signedOrder, + fillableAmount.minus(remainingFillableAmountInBaseUnits), + ); + })().catch(done); + }); + it('should emit orderStateValid when watched order partially cancelled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); - const cancelAmountInBaseUnits = new BigNumber(2); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const cancelAmountInBaseUnits = new BigNumber(2); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.true(); - const validOrderState = orderState as OrderStateValid; - expect(validOrderState.orderHash).to.be.equal(orderHash); - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.cancelledTakerTokenAmount).to.be.bignumber.equal(cancelAmountInBaseUnits); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmountInBaseUnits); - })().catch(done); - }); - }); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + expect(validOrderState.orderHash).to.be.equal(orderHash); + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.cancelledTakerTokenAmount).to.be.bignumber.equal(cancelAmountInBaseUnits); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmountInBaseUnits); + })().catch(done); + }); + }); }); // tslint:disable:max-file-line-count diff --git a/packages/0x.js/test/order_validation_test.ts b/packages/0x.js/test/order_validation_test.ts index 4e023a9cc..be3e0590c 100644 --- a/packages/0x.js/test/order_validation_test.ts +++ b/packages/0x.js/test/order_validation_test.ts @@ -20,455 +20,455 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); describe('OrderValidation', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let userAddresses: string[]; - let tokens: Token[]; - let tokenUtils: TokenUtils; - let exchangeContractAddress: string; - let zrxTokenAddress: string; - let fillScenarios: FillScenarios; - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipient: string; - const fillableAmount = new BigNumber(5); - const fillTakerAmount = new BigNumber(5); - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - exchangeContractAddress = zeroEx.exchange.getContractAddress(); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - tokenUtils = new TokenUtils(tokens); - zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; - fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('validateOrderFillableOrThrowAsync', () => { - it('should succeed if the order is fillable', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder); - }); - it('should succeed if the order is asymmetric and fillable', async () => { - const makerFillableAmount = fillableAmount; - const takerFillableAmount = fillableAmount.minus(4); - const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - makerFillableAmount, - takerFillableAmount, - ); - await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder); - }); - it('should throw when the order is fully filled or cancelled', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); - return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith( - ExchangeContractErrs.OrderRemainingFillAmountZero, - ); - }); - it('should throw when order is expired', async () => { - const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - expirationInPast, - ); - return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith( - ExchangeContractErrs.OrderFillExpired, - ); - }); - }); - describe('validateFillOrderAndThrowIfInvalidAsync', () => { - it('should throw when the fill amount is zero', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const zeroFillAmount = new BigNumber(0); - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, zeroFillAmount, takerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should throw when the signature is invalid', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - // 27 <--> 28 - signedOrder.ecSignature.v = 28 - signedOrder.ecSignature.v + 27; - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress), - ).to.be.rejectedWith(ZeroExError.InvalidSignature); - }); - it('should throw when the order is fully filled or cancelled', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero); - }); - it('should throw when sender is not a taker', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const nonTakerAddress = userAddresses[6]; - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, nonTakerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker); - }); - it('should throw when order is expired', async () => { - const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - expirationInPast, - ); - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, takerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired); - }); - it('should throw when there a rounding error would have occurred', async () => { - const makerAmount = new BigNumber(3); - const takerAmount = new BigNumber(5); - const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - makerAmount, - takerAmount, - ); - const fillTakerAmountThatCausesRoundingError = new BigNumber(3); - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync( - signedOrder, - fillTakerAmountThatCausesRoundingError, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillRoundingError); - }); - }); - describe('#validateFillOrKillOrderAndThrowIfInvalidAsync', () => { - it('should throw if remaining fillAmount is less then the desired fillAmount', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const tooLargeFillAmount = new BigNumber(7); - const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount); - await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference); - await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount); - await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference); - await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount); + let web3: Web3; + let zeroEx: ZeroEx; + let userAddresses: string[]; + let tokens: Token[]; + let tokenUtils: TokenUtils; + let exchangeContractAddress: string; + let zrxTokenAddress: string; + let fillScenarios: FillScenarios; + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipient: string; + const fillableAmount = new BigNumber(5); + const fillTakerAmount = new BigNumber(5); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + exchangeContractAddress = zeroEx.exchange.getContractAddress(); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('validateOrderFillableOrThrowAsync', () => { + it('should succeed if the order is fillable', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder); + }); + it('should succeed if the order is asymmetric and fillable', async () => { + const makerFillableAmount = fillableAmount; + const takerFillableAmount = fillableAmount.minus(4); + const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + makerFillableAmount, + takerFillableAmount, + ); + await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder); + }); + it('should throw when the order is fully filled or cancelled', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); + return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith( + ExchangeContractErrs.OrderRemainingFillAmountZero, + ); + }); + it('should throw when order is expired', async () => { + const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationInPast, + ); + return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith( + ExchangeContractErrs.OrderFillExpired, + ); + }); + }); + describe('validateFillOrderAndThrowIfInvalidAsync', () => { + it('should throw when the fill amount is zero', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const zeroFillAmount = new BigNumber(0); + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, zeroFillAmount, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should throw when the signature is invalid', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + // 27 <--> 28 + signedOrder.ecSignature.v = 28 - signedOrder.ecSignature.v + 27; + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress), + ).to.be.rejectedWith(ZeroExError.InvalidSignature); + }); + it('should throw when the order is fully filled or cancelled', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero); + }); + it('should throw when sender is not a taker', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const nonTakerAddress = userAddresses[6]; + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, nonTakerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker); + }); + it('should throw when order is expired', async () => { + const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationInPast, + ); + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired); + }); + it('should throw when there a rounding error would have occurred', async () => { + const makerAmount = new BigNumber(3); + const takerAmount = new BigNumber(5); + const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + makerAmount, + takerAmount, + ); + const fillTakerAmountThatCausesRoundingError = new BigNumber(3); + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync( + signedOrder, + fillTakerAmountThatCausesRoundingError, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillRoundingError); + }); + }); + describe('#validateFillOrKillOrderAndThrowIfInvalidAsync', () => { + it('should throw if remaining fillAmount is less then the desired fillAmount', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const tooLargeFillAmount = new BigNumber(7); + const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount); + await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference); + await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount); + await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference); + await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount); - return expect( - zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync( - signedOrder, - tooLargeFillAmount, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.InsufficientRemainingFillAmount); - }); - }); - describe('validateCancelOrderAndThrowIfInvalidAsync', () => { - let signedOrder: SignedOrder; - const cancelAmount = new BigNumber(3); - beforeEach(async () => { - [coinbase, makerAddress, takerAddress] = userAddresses; - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - }); - it('should throw when cancel amount is zero', async () => { - const zeroCancelAmount = new BigNumber(0); - return expect( - zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, zeroCancelAmount), - ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - it('should throw when order is expired', async () => { - const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 - const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - expirationInPast, - ); - return expect( - zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(expiredSignedOrder, cancelAmount), - ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelExpired); - }); - it('should throw when order is already cancelled or filled', async () => { - await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); - return expect( - zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, fillableAmount), - ).to.be.rejectedWith(ExchangeContractErrs.OrderAlreadyCancelledOrFilled); - }); - }); - describe('#validateFillOrderBalancesAllowancesThrowIfInvalidAsync', () => { - let exchangeTransferSimulator: ExchangeTransferSimulator; - let transferFromAsync: Sinon.SinonSpy; - const bigNumberMatch = (expected: BigNumber) => { - return Sinon.match((value: BigNumber) => value.eq(expected)); - }; - beforeEach('create exchangeTransferSimulator', async () => { - exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest); - transferFromAsync = Sinon.spy(); - exchangeTransferSimulator.transferFromAsync = transferFromAsync as any; - }); - it('should call exchangeTransferSimulator.transferFrom in a correct order', async () => { - const makerFee = new BigNumber(2); - const takerFee = new BigNumber(2); - const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - feeRecipient, - ); - await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTransferSimulator, - signedOrder, - fillableAmount, - takerAddress, - zrxTokenAddress, - ); - expect(transferFromAsync.callCount).to.be.equal(4); - expect( - transferFromAsync - .getCall(0) - .calledWith( - makerTokenAddress, - makerAddress, - takerAddress, - bigNumberMatch(fillableAmount), - TradeSide.Maker, - TransferType.Trade, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(1) - .calledWith( - takerTokenAddress, - takerAddress, - makerAddress, - bigNumberMatch(fillableAmount), - TradeSide.Taker, - TransferType.Trade, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(2) - .calledWith( - zrxTokenAddress, - makerAddress, - feeRecipient, - bigNumberMatch(makerFee), - TradeSide.Maker, - TransferType.Fee, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(3) - .calledWith( - zrxTokenAddress, - takerAddress, - feeRecipient, - bigNumberMatch(takerFee), - TradeSide.Taker, - TransferType.Fee, - ), - ).to.be.true(); - }); - it('should call exchangeTransferSimulator.transferFrom with correct values for an open order', async () => { - const makerFee = new BigNumber(2); - const takerFee = new BigNumber(2); - const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - ZeroEx.NULL_ADDRESS, - fillableAmount, - feeRecipient, - ); - await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTransferSimulator, - signedOrder, - fillableAmount, - takerAddress, - zrxTokenAddress, - ); - expect(transferFromAsync.callCount).to.be.equal(4); - expect( - transferFromAsync - .getCall(0) - .calledWith( - makerTokenAddress, - makerAddress, - takerAddress, - bigNumberMatch(fillableAmount), - TradeSide.Maker, - TransferType.Trade, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(1) - .calledWith( - takerTokenAddress, - takerAddress, - makerAddress, - bigNumberMatch(fillableAmount), - TradeSide.Taker, - TransferType.Trade, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(2) - .calledWith( - zrxTokenAddress, - makerAddress, - feeRecipient, - bigNumberMatch(makerFee), - TradeSide.Maker, - TransferType.Fee, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(3) - .calledWith( - zrxTokenAddress, - takerAddress, - feeRecipient, - bigNumberMatch(takerFee), - TradeSide.Taker, - TransferType.Fee, - ), - ).to.be.true(); - }); - it('should correctly round the fillMakerTokenAmount', async () => { - const makerTokenAmount = new BigNumber(3); - const takerTokenAmount = new BigNumber(1); - const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - makerTokenAmount, - takerTokenAmount, - ); - await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTransferSimulator, - signedOrder, - takerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - expect(transferFromAsync.callCount).to.be.equal(4); - const makerFillAmount = transferFromAsync.getCall(0).args[3]; - expect(makerFillAmount).to.be.bignumber.equal(makerTokenAmount); - }); - it('should correctly round the makerFeeAmount', async () => { - const makerFee = new BigNumber(2); - const takerFee = new BigNumber(4); - const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - ZeroEx.NULL_ADDRESS, - ); - const fillTakerTokenAmount = fillableAmount.div(2).round(0); - await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTransferSimulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - const makerPartialFee = makerFee.div(2); - const takerPartialFee = takerFee.div(2); - expect(transferFromAsync.callCount).to.be.equal(4); - const partialMakerFee = transferFromAsync.getCall(2).args[3]; - expect(partialMakerFee).to.be.bignumber.equal(makerPartialFee); - const partialTakerFee = transferFromAsync.getCall(3).args[3]; - expect(partialTakerFee).to.be.bignumber.equal(takerPartialFee); - }); - }); + return expect( + zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync( + signedOrder, + tooLargeFillAmount, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.InsufficientRemainingFillAmount); + }); + }); + describe('validateCancelOrderAndThrowIfInvalidAsync', () => { + let signedOrder: SignedOrder; + const cancelAmount = new BigNumber(3); + beforeEach(async () => { + [coinbase, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + it('should throw when cancel amount is zero', async () => { + const zeroCancelAmount = new BigNumber(0); + return expect( + zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, zeroCancelAmount), + ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + it('should throw when order is expired', async () => { + const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 + const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationInPast, + ); + return expect( + zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(expiredSignedOrder, cancelAmount), + ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelExpired); + }); + it('should throw when order is already cancelled or filled', async () => { + await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); + return expect( + zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, fillableAmount), + ).to.be.rejectedWith(ExchangeContractErrs.OrderAlreadyCancelledOrFilled); + }); + }); + describe('#validateFillOrderBalancesAllowancesThrowIfInvalidAsync', () => { + let exchangeTransferSimulator: ExchangeTransferSimulator; + let transferFromAsync: Sinon.SinonSpy; + const bigNumberMatch = (expected: BigNumber) => { + return Sinon.match((value: BigNumber) => value.eq(expected)); + }; + beforeEach('create exchangeTransferSimulator', async () => { + exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest); + transferFromAsync = Sinon.spy(); + exchangeTransferSimulator.transferFromAsync = transferFromAsync as any; + }); + it('should call exchangeTransferSimulator.transferFrom in a correct order', async () => { + const makerFee = new BigNumber(2); + const takerFee = new BigNumber(2); + const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + feeRecipient, + ); + await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTransferSimulator, + signedOrder, + fillableAmount, + takerAddress, + zrxTokenAddress, + ); + expect(transferFromAsync.callCount).to.be.equal(4); + expect( + transferFromAsync + .getCall(0) + .calledWith( + makerTokenAddress, + makerAddress, + takerAddress, + bigNumberMatch(fillableAmount), + TradeSide.Maker, + TransferType.Trade, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(1) + .calledWith( + takerTokenAddress, + takerAddress, + makerAddress, + bigNumberMatch(fillableAmount), + TradeSide.Taker, + TransferType.Trade, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(2) + .calledWith( + zrxTokenAddress, + makerAddress, + feeRecipient, + bigNumberMatch(makerFee), + TradeSide.Maker, + TransferType.Fee, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(3) + .calledWith( + zrxTokenAddress, + takerAddress, + feeRecipient, + bigNumberMatch(takerFee), + TradeSide.Taker, + TransferType.Fee, + ), + ).to.be.true(); + }); + it('should call exchangeTransferSimulator.transferFrom with correct values for an open order', async () => { + const makerFee = new BigNumber(2); + const takerFee = new BigNumber(2); + const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + ZeroEx.NULL_ADDRESS, + fillableAmount, + feeRecipient, + ); + await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTransferSimulator, + signedOrder, + fillableAmount, + takerAddress, + zrxTokenAddress, + ); + expect(transferFromAsync.callCount).to.be.equal(4); + expect( + transferFromAsync + .getCall(0) + .calledWith( + makerTokenAddress, + makerAddress, + takerAddress, + bigNumberMatch(fillableAmount), + TradeSide.Maker, + TransferType.Trade, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(1) + .calledWith( + takerTokenAddress, + takerAddress, + makerAddress, + bigNumberMatch(fillableAmount), + TradeSide.Taker, + TransferType.Trade, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(2) + .calledWith( + zrxTokenAddress, + makerAddress, + feeRecipient, + bigNumberMatch(makerFee), + TradeSide.Maker, + TransferType.Fee, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(3) + .calledWith( + zrxTokenAddress, + takerAddress, + feeRecipient, + bigNumberMatch(takerFee), + TradeSide.Taker, + TransferType.Fee, + ), + ).to.be.true(); + }); + it('should correctly round the fillMakerTokenAmount', async () => { + const makerTokenAmount = new BigNumber(3); + const takerTokenAmount = new BigNumber(1); + const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + makerTokenAmount, + takerTokenAmount, + ); + await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTransferSimulator, + signedOrder, + takerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + expect(transferFromAsync.callCount).to.be.equal(4); + const makerFillAmount = transferFromAsync.getCall(0).args[3]; + expect(makerFillAmount).to.be.bignumber.equal(makerTokenAmount); + }); + it('should correctly round the makerFeeAmount', async () => { + const makerFee = new BigNumber(2); + const takerFee = new BigNumber(4); + const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + ZeroEx.NULL_ADDRESS, + ); + const fillTakerTokenAmount = fillableAmount.div(2).round(0); + await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTransferSimulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + const makerPartialFee = makerFee.div(2); + const takerPartialFee = takerFee.div(2); + expect(transferFromAsync.callCount).to.be.equal(4); + const partialMakerFee = transferFromAsync.getCall(2).args[3]; + expect(partialMakerFee).to.be.bignumber.equal(makerPartialFee); + const partialTakerFee = transferFromAsync.getCall(3).args[3]; + expect(partialTakerFee).to.be.bignumber.equal(takerPartialFee); + }); + }); }); diff --git a/packages/0x.js/test/remaining_fillable_calculator_test.ts b/packages/0x.js/test/remaining_fillable_calculator_test.ts index 58e9dd93b..4c6b8f3ac 100644 --- a/packages/0x.js/test/remaining_fillable_calculator_test.ts +++ b/packages/0x.js/test/remaining_fillable_calculator_test.ts @@ -12,223 +12,223 @@ chaiSetup.configure(); const expect = chai.expect; describe('RemainingFillableCalculator', () => { - let calculator: RemainingFillableCalculator; - let signedOrder: SignedOrder; - let transferrableMakerTokenAmount: BigNumber; - let transferrableMakerFeeTokenAmount: BigNumber; - let remainingMakerTokenAmount: BigNumber; - let makerAmount: BigNumber; - let takerAmount: BigNumber; - let makerFeeAmount: BigNumber; - let isMakerTokenZRX: boolean; - const makerToken: string = '0x1'; - const takerToken: string = '0x2'; - const decimals: number = 4; - const zero: BigNumber = new BigNumber(0); - const zeroAddress = '0x0'; - const signature: ECSignature = { v: 27, r: '', s: '' }; - beforeEach(async () => { - [makerAmount, takerAmount, makerFeeAmount] = [ - ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals), - ]; - [transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount] = [ - ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals), - ]; - }); - function buildSignedOrder(): SignedOrder { - return { - ecSignature: signature, - exchangeContractAddress: zeroAddress, - feeRecipient: zeroAddress, - maker: zeroAddress, - taker: zeroAddress, - makerFee: makerFeeAmount, - takerFee: zero, - makerTokenAmount: makerAmount, - takerTokenAmount: takerAmount, - makerTokenAddress: makerToken, - takerTokenAddress: takerToken, - salt: zero, - expirationUnixTimestampSec: zero, - }; - } - describe('Maker token is NOT ZRX', () => { - before(async () => { - isMakerTokenZRX = false; - }); - it('calculates the correct amount when unfilled and funds available', () => { - signedOrder = buildSignedOrder(); - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); - }); - it('calculates the correct amount when partially filled and funds available', () => { - signedOrder = buildSignedOrder(); - remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); - }); - it('calculates the amount to be 0 when all fee funds are transferred', () => { - signedOrder = buildSignedOrder(); - transferrableMakerFeeTokenAmount = zero; - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero); - }); - it('calculates the correct amount when balance is less than remaining fillable', () => { - signedOrder = buildSignedOrder(); - const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount); - transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount); - }); - describe('Order to Fee Ratio is < 1', () => { - beforeEach(async () => { - [makerAmount, takerAmount, makerFeeAmount] = [ - ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals), - ]; - }); - it('calculates the correct amount when funds unavailable', () => { - signedOrder = buildSignedOrder(); - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount); - }); - }); - describe('Ratio is not evenly divisble', () => { - beforeEach(async () => { - [makerAmount, takerAmount, makerFeeAmount] = [ - ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals), - ]; - }); - it('calculates the correct amount when funds unavailable', () => { - signedOrder = buildSignedOrder(); - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - const calculatedFillableAmount = calculator.computeRemainingMakerFillable(); - expect(calculatedFillableAmount.lessThanOrEqualTo(transferrableMakerTokenAmount)).to.be.true(); - expect(calculatedFillableAmount).to.be.bignumber.greaterThan(new BigNumber(0)); - const orderToFeeRatio = signedOrder.makerTokenAmount.dividedBy(signedOrder.makerFee); - const calculatedFeeAmount = calculatedFillableAmount.dividedBy(orderToFeeRatio); - expect(calculatedFeeAmount).to.be.bignumber.lessThan(transferrableMakerFeeTokenAmount); - }); - }); - }); - describe('Maker Token is ZRX', () => { - before(async () => { - isMakerTokenZRX = true; - }); - it('calculates the correct amount when unfilled and funds available', () => { - signedOrder = buildSignedOrder(); - transferrableMakerTokenAmount = makerAmount.plus(makerFeeAmount); - transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount; - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); - }); - it('calculates the correct amount when partially filled and funds available', () => { - signedOrder = buildSignedOrder(); - remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); - }); - it('calculates the amount to be 0 when all fee funds are transferred', () => { - signedOrder = buildSignedOrder(); - transferrableMakerTokenAmount = zero; - transferrableMakerFeeTokenAmount = zero; - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero); - }); - it('calculates the correct amount when balance is less than remaining fillable', () => { - signedOrder = buildSignedOrder(); - const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount); - transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount); - transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount; + let calculator: RemainingFillableCalculator; + let signedOrder: SignedOrder; + let transferrableMakerTokenAmount: BigNumber; + let transferrableMakerFeeTokenAmount: BigNumber; + let remainingMakerTokenAmount: BigNumber; + let makerAmount: BigNumber; + let takerAmount: BigNumber; + let makerFeeAmount: BigNumber; + let isMakerTokenZRX: boolean; + const makerToken: string = '0x1'; + const takerToken: string = '0x2'; + const decimals: number = 4; + const zero: BigNumber = new BigNumber(0); + const zeroAddress = '0x0'; + const signature: ECSignature = { v: 27, r: '', s: '' }; + beforeEach(async () => { + [makerAmount, takerAmount, makerFeeAmount] = [ + ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals), + ]; + [transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount] = [ + ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals), + ]; + }); + function buildSignedOrder(): SignedOrder { + return { + ecSignature: signature, + exchangeContractAddress: zeroAddress, + feeRecipient: zeroAddress, + maker: zeroAddress, + taker: zeroAddress, + makerFee: makerFeeAmount, + takerFee: zero, + makerTokenAmount: makerAmount, + takerTokenAmount: takerAmount, + makerTokenAddress: makerToken, + takerTokenAddress: takerToken, + salt: zero, + expirationUnixTimestampSec: zero, + }; + } + describe('Maker token is NOT ZRX', () => { + before(async () => { + isMakerTokenZRX = false; + }); + it('calculates the correct amount when unfilled and funds available', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the correct amount when partially filled and funds available', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the amount to be 0 when all fee funds are transferred', () => { + signedOrder = buildSignedOrder(); + transferrableMakerFeeTokenAmount = zero; + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero); + }); + it('calculates the correct amount when balance is less than remaining fillable', () => { + signedOrder = buildSignedOrder(); + const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount); + }); + describe('Order to Fee Ratio is < 1', () => { + beforeEach(async () => { + [makerAmount, takerAmount, makerFeeAmount] = [ + ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals), + ]; + }); + it('calculates the correct amount when funds unavailable', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount); + }); + }); + describe('Ratio is not evenly divisble', () => { + beforeEach(async () => { + [makerAmount, takerAmount, makerFeeAmount] = [ + ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals), + ]; + }); + it('calculates the correct amount when funds unavailable', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + const calculatedFillableAmount = calculator.computeRemainingMakerFillable(); + expect(calculatedFillableAmount.lessThanOrEqualTo(transferrableMakerTokenAmount)).to.be.true(); + expect(calculatedFillableAmount).to.be.bignumber.greaterThan(new BigNumber(0)); + const orderToFeeRatio = signedOrder.makerTokenAmount.dividedBy(signedOrder.makerFee); + const calculatedFeeAmount = calculatedFillableAmount.dividedBy(orderToFeeRatio); + expect(calculatedFeeAmount).to.be.bignumber.lessThan(transferrableMakerFeeTokenAmount); + }); + }); + }); + describe('Maker Token is ZRX', () => { + before(async () => { + isMakerTokenZRX = true; + }); + it('calculates the correct amount when unfilled and funds available', () => { + signedOrder = buildSignedOrder(); + transferrableMakerTokenAmount = makerAmount.plus(makerFeeAmount); + transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount; + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the correct amount when partially filled and funds available', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the amount to be 0 when all fee funds are transferred', () => { + signedOrder = buildSignedOrder(); + transferrableMakerTokenAmount = zero; + transferrableMakerFeeTokenAmount = zero; + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero); + }); + it('calculates the correct amount when balance is less than remaining fillable', () => { + signedOrder = buildSignedOrder(); + const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount); + transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount; - const orderToFeeRatio = signedOrder.makerTokenAmount.dividedToIntegerBy(signedOrder.makerFee); - const expectedFillableAmount = new BigNumber(450980); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - const calculatedFillableAmount = calculator.computeRemainingMakerFillable(); - const numberOfFillsInRatio = calculatedFillableAmount.dividedToIntegerBy(orderToFeeRatio); - const calculatedFillableAmountPlusFees = calculatedFillableAmount.plus(numberOfFillsInRatio); - expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(transferrableMakerTokenAmount); - expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(remainingMakerTokenAmount); - expect(calculatedFillableAmount).to.be.bignumber.equal(expectedFillableAmount); - expect(numberOfFillsInRatio.decimalPlaces()).to.be.equal(0); - }); - }); + const orderToFeeRatio = signedOrder.makerTokenAmount.dividedToIntegerBy(signedOrder.makerFee); + const expectedFillableAmount = new BigNumber(450980); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + const calculatedFillableAmount = calculator.computeRemainingMakerFillable(); + const numberOfFillsInRatio = calculatedFillableAmount.dividedToIntegerBy(orderToFeeRatio); + const calculatedFillableAmountPlusFees = calculatedFillableAmount.plus(numberOfFillsInRatio); + expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(transferrableMakerTokenAmount); + expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(remainingMakerTokenAmount); + expect(calculatedFillableAmount).to.be.bignumber.equal(expectedFillableAmount); + expect(numberOfFillsInRatio.decimalPlaces()).to.be.equal(0); + }); + }); }); diff --git a/packages/0x.js/test/subscription_test.ts b/packages/0x.js/test/subscription_test.ts index f70ecd6cd..f4c6f748f 100644 --- a/packages/0x.js/test/subscription_test.ts +++ b/packages/0x.js/test/subscription_test.ts @@ -17,69 +17,69 @@ chaiSetup.configure(); const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); describe('SubscriptionTest', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let userAddresses: string[]; - let tokens: Token[]; - let coinbase: string; - let addressWithoutFunds: string; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - coinbase = userAddresses[0]; - addressWithoutFunds = userAddresses[1]; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#subscribe', () => { - const indexFilterValues = {}; - let tokenAddress: string; - const allowanceAmount = new BigNumber(42); - let stubs: Sinon.SinonStub[] = []; - before(() => { - const token = tokens[0]; - tokenAddress = token.address; - }); - afterEach(() => { - zeroEx.token.unsubscribeAll(); - _.each(stubs, s => s.restore()); - stubs = []; - }); - it('Should receive the Error when an error occurs while fetching the block', (done: DoneCallback) => { - (async () => { - const errMsg = 'Error fetching block'; - const callback = assertNodeCallbackError(done, errMsg); - stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error(errMsg))]; - zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); - await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); - })().catch(done); - }); - it('Should receive the Error when an error occurs while reconciling the new block', (done: DoneCallback) => { - (async () => { - const errMsg = 'Error fetching logs'; - const callback = assertNodeCallbackError(done, errMsg); - stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getLogsAsync').throws(new Error(errMsg))]; - zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); - await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); - })().catch(done); - }); - it('Should allow unsubscribeAll to be called successfully after an error', (done: DoneCallback) => { - (async () => { - const callback = (err: Error | null, logEvent?: DecodedLogEvent<ApprovalContractEventArgs>) => _.noop; - zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); - stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error('JSON RPC error'))]; - zeroEx.token.unsubscribeAll(); - done(); - })().catch(done); - }); - }); + let web3: Web3; + let zeroEx: ZeroEx; + let userAddresses: string[]; + let tokens: Token[]; + let coinbase: string; + let addressWithoutFunds: string; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + coinbase = userAddresses[0]; + addressWithoutFunds = userAddresses[1]; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#subscribe', () => { + const indexFilterValues = {}; + let tokenAddress: string; + const allowanceAmount = new BigNumber(42); + let stubs: Sinon.SinonStub[] = []; + before(() => { + const token = tokens[0]; + tokenAddress = token.address; + }); + afterEach(() => { + zeroEx.token.unsubscribeAll(); + _.each(stubs, s => s.restore()); + stubs = []; + }); + it('Should receive the Error when an error occurs while fetching the block', (done: DoneCallback) => { + (async () => { + const errMsg = 'Error fetching block'; + const callback = assertNodeCallbackError(done, errMsg); + stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error(errMsg))]; + zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); + await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); + })().catch(done); + }); + it('Should receive the Error when an error occurs while reconciling the new block', (done: DoneCallback) => { + (async () => { + const errMsg = 'Error fetching logs'; + const callback = assertNodeCallbackError(done, errMsg); + stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getLogsAsync').throws(new Error(errMsg))]; + zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); + await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); + })().catch(done); + }); + it('Should allow unsubscribeAll to be called successfully after an error', (done: DoneCallback) => { + (async () => { + const callback = (err: Error | null, logEvent?: DecodedLogEvent<ApprovalContractEventArgs>) => _.noop; + zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); + stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error('JSON RPC error'))]; + zeroEx.token.unsubscribeAll(); + done(); + })().catch(done); + }); + }); }); diff --git a/packages/0x.js/test/token_registry_wrapper_test.ts b/packages/0x.js/test/token_registry_wrapper_test.ts index c798c00d7..0a170db8f 100644 --- a/packages/0x.js/test/token_registry_wrapper_test.ts +++ b/packages/0x.js/test/token_registry_wrapper_test.ts @@ -17,113 +17,113 @@ const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); const TOKEN_REGISTRY_SIZE_AFTER_MIGRATION = 7; describe('TokenRegistryWrapper', () => { - let zeroEx: ZeroEx; - let tokens: Token[]; - const tokenAddressBySymbol: { [symbol: string]: string } = {}; - const tokenAddressByName: { [symbol: string]: string } = {}; - const tokenBySymbol: { [symbol: string]: Token } = {}; - const tokenByName: { [symbol: string]: Token } = {}; - const registeredSymbol = 'ZRX'; - const registeredName = '0x Protocol Token'; - const unregisteredSymbol = 'MAL'; - const unregisteredName = 'Malicious Token'; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - const web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - _.map(tokens, token => { - tokenAddressBySymbol[token.symbol] = token.address; - tokenAddressByName[token.name] = token.address; - tokenBySymbol[token.symbol] = token; - tokenByName[token.name] = token; - }); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#getTokensAsync', () => { - it('should return all the tokens added to the tokenRegistry during the migration', async () => { - expect(tokens).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION); + let zeroEx: ZeroEx; + let tokens: Token[]; + const tokenAddressBySymbol: { [symbol: string]: string } = {}; + const tokenAddressByName: { [symbol: string]: string } = {}; + const tokenBySymbol: { [symbol: string]: Token } = {}; + const tokenByName: { [symbol: string]: Token } = {}; + const registeredSymbol = 'ZRX'; + const registeredName = '0x Protocol Token'; + const unregisteredSymbol = 'MAL'; + const unregisteredName = 'Malicious Token'; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + const web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + _.map(tokens, token => { + tokenAddressBySymbol[token.symbol] = token.address; + tokenAddressByName[token.name] = token.address; + tokenBySymbol[token.symbol] = token; + tokenByName[token.name] = token; + }); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#getTokensAsync', () => { + it('should return all the tokens added to the tokenRegistry during the migration', async () => { + expect(tokens).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION); - const schemaValidator = new SchemaValidator(); - _.each(tokens, token => { - const validationResult = schemaValidator.validate(token, schemas.tokenSchema); - expect(validationResult.errors).to.have.lengthOf(0); - }); - }); - }); - describe('#getTokenAddressesAsync', () => { - it('should return all the token addresses added to the tokenRegistry during the migration', async () => { - const tokenAddresses = await zeroEx.tokenRegistry.getTokenAddressesAsync(); - expect(tokenAddresses).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION); + const schemaValidator = new SchemaValidator(); + _.each(tokens, token => { + const validationResult = schemaValidator.validate(token, schemas.tokenSchema); + expect(validationResult.errors).to.have.lengthOf(0); + }); + }); + }); + describe('#getTokenAddressesAsync', () => { + it('should return all the token addresses added to the tokenRegistry during the migration', async () => { + const tokenAddresses = await zeroEx.tokenRegistry.getTokenAddressesAsync(); + expect(tokenAddresses).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION); - const schemaValidator = new SchemaValidator(); - _.each(tokenAddresses, tokenAddress => { - const validationResult = schemaValidator.validate(tokenAddress, schemas.addressSchema); - expect(validationResult.errors).to.have.lengthOf(0); - expect(tokenAddress).to.not.be.equal(ZeroEx.NULL_ADDRESS); - }); - }); - }); - describe('#getTokenAddressBySymbol', () => { - it('should return correct address for a token in the registry', async () => { - const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(registeredSymbol); - expect(tokenAddress).to.be.equal(tokenAddressBySymbol[registeredSymbol]); - }); - it('should return undefined for a token out of registry', async () => { - const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(unregisteredSymbol); - expect(tokenAddress).to.be.undefined(); - }); - }); - describe('#getTokenAddressByName', () => { - it('should return correct address for a token in the registry', async () => { - const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(registeredName); - expect(tokenAddress).to.be.equal(tokenAddressByName[registeredName]); - }); - it('should return undefined for a token out of registry', async () => { - const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(unregisteredName); - expect(tokenAddress).to.be.undefined(); - }); - }); - describe('#getTokenBySymbol', () => { - it('should return correct token for a token in the registry', async () => { - const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(registeredSymbol); - expect(token).to.be.deep.equal(tokenBySymbol[registeredSymbol]); - }); - it('should return undefined for a token out of registry', async () => { - const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(unregisteredSymbol); - expect(token).to.be.undefined(); - }); - }); - describe('#getTokenByName', () => { - it('should return correct token for a token in the registry', async () => { - const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(registeredName); - expect(token).to.be.deep.equal(tokenByName[registeredName]); - }); - it('should return undefined for a token out of registry', async () => { - const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(unregisteredName); - expect(token).to.be.undefined(); - }); - }); - describe('#getTokenIfExistsAsync', () => { - it('should return the token added to the tokenRegistry during the migration', async () => { - const aToken = tokens[0]; + const schemaValidator = new SchemaValidator(); + _.each(tokenAddresses, tokenAddress => { + const validationResult = schemaValidator.validate(tokenAddress, schemas.addressSchema); + expect(validationResult.errors).to.have.lengthOf(0); + expect(tokenAddress).to.not.be.equal(ZeroEx.NULL_ADDRESS); + }); + }); + }); + describe('#getTokenAddressBySymbol', () => { + it('should return correct address for a token in the registry', async () => { + const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(registeredSymbol); + expect(tokenAddress).to.be.equal(tokenAddressBySymbol[registeredSymbol]); + }); + it('should return undefined for a token out of registry', async () => { + const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(unregisteredSymbol); + expect(tokenAddress).to.be.undefined(); + }); + }); + describe('#getTokenAddressByName', () => { + it('should return correct address for a token in the registry', async () => { + const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(registeredName); + expect(tokenAddress).to.be.equal(tokenAddressByName[registeredName]); + }); + it('should return undefined for a token out of registry', async () => { + const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(unregisteredName); + expect(tokenAddress).to.be.undefined(); + }); + }); + describe('#getTokenBySymbol', () => { + it('should return correct token for a token in the registry', async () => { + const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(registeredSymbol); + expect(token).to.be.deep.equal(tokenBySymbol[registeredSymbol]); + }); + it('should return undefined for a token out of registry', async () => { + const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(unregisteredSymbol); + expect(token).to.be.undefined(); + }); + }); + describe('#getTokenByName', () => { + it('should return correct token for a token in the registry', async () => { + const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(registeredName); + expect(token).to.be.deep.equal(tokenByName[registeredName]); + }); + it('should return undefined for a token out of registry', async () => { + const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(unregisteredName); + expect(token).to.be.undefined(); + }); + }); + describe('#getTokenIfExistsAsync', () => { + it('should return the token added to the tokenRegistry during the migration', async () => { + const aToken = tokens[0]; - const token = await zeroEx.tokenRegistry.getTokenIfExistsAsync(aToken.address); - const schemaValidator = new SchemaValidator(); - const validationResult = schemaValidator.validate(token, schemas.tokenSchema); - expect(validationResult.errors).to.have.lengthOf(0); - }); - it('should return return undefined when passed a token address not in the tokenRegistry', async () => { - const unregisteredTokenAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; - const tokenIfExists = await zeroEx.tokenRegistry.getTokenIfExistsAsync(unregisteredTokenAddress); - expect(tokenIfExists).to.be.undefined(); - }); - }); + const token = await zeroEx.tokenRegistry.getTokenIfExistsAsync(aToken.address); + const schemaValidator = new SchemaValidator(); + const validationResult = schemaValidator.validate(token, schemas.tokenSchema); + expect(validationResult.errors).to.have.lengthOf(0); + }); + it('should return return undefined when passed a token address not in the tokenRegistry', async () => { + const unregisteredTokenAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; + const tokenIfExists = await zeroEx.tokenRegistry.getTokenIfExistsAsync(unregisteredTokenAddress); + expect(tokenIfExists).to.be.undefined(); + }); + }); }); diff --git a/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts b/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts index 622919be2..15bd7a8ba 100644 --- a/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts +++ b/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts @@ -10,27 +10,27 @@ chaiSetup.configure(); const expect = chai.expect; describe('TokenTransferProxyWrapper', () => { - let zeroEx: ZeroEx; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - const web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - }); - describe('#isAuthorizedAsync', () => { - it('should return false if the address is not authorized', async () => { - const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(ZeroEx.NULL_ADDRESS); - expect(isAuthorized).to.be.false(); - }); - }); - describe('#getAuthorizedAddressesAsync', () => { - it('should return the list of authorized addresses', async () => { - const authorizedAddresses = await zeroEx.proxy.getAuthorizedAddressesAsync(); - for (const authorizedAddress of authorizedAddresses) { - const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(authorizedAddress); - expect(isAuthorized).to.be.true(); - } - }); - }); + let zeroEx: ZeroEx; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + const web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + }); + describe('#isAuthorizedAsync', () => { + it('should return false if the address is not authorized', async () => { + const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(ZeroEx.NULL_ADDRESS); + expect(isAuthorized).to.be.false(); + }); + }); + describe('#getAuthorizedAddressesAsync', () => { + it('should return the list of authorized addresses', async () => { + const authorizedAddresses = await zeroEx.proxy.getAuthorizedAddressesAsync(); + for (const authorizedAddress of authorizedAddresses) { + const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(authorizedAddress); + expect(isAuthorized).to.be.true(); + } + }); + }); }); diff --git a/packages/0x.js/test/token_wrapper_test.ts b/packages/0x.js/test/token_wrapper_test.ts index 6919183cb..4ba1f07c5 100644 --- a/packages/0x.js/test/token_wrapper_test.ts +++ b/packages/0x.js/test/token_wrapper_test.ts @@ -6,15 +6,15 @@ import 'mocha'; import * as Web3 from 'web3'; import { - ApprovalContractEventArgs, - BlockParamLiteral, - BlockRange, - DecodedLogEvent, - Token, - TokenEvents, - TransferContractEventArgs, - ZeroEx, - ZeroExError, + ApprovalContractEventArgs, + BlockParamLiteral, + BlockRange, + DecodedLogEvent, + Token, + TokenEvents, + TransferContractEventArgs, + ZeroEx, + ZeroExError, } from '../src'; import { DoneCallback } from '../src/types'; @@ -29,493 +29,493 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); describe('TokenWrapper', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let userAddresses: string[]; - let tokens: Token[]; - let tokenUtils: TokenUtils; - let coinbase: string; - let addressWithoutFunds: string; - let web3Wrapper: Web3Wrapper; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - web3Wrapper = new Web3Wrapper(web3.currentProvider); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - tokenUtils = new TokenUtils(tokens); - coinbase = userAddresses[0]; - addressWithoutFunds = userAddresses[1]; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#transferAsync', () => { - let token: Token; - let transferAmount: BigNumber; - before(() => { - token = tokens[0]; - transferAmount = new BigNumber(42); - }); - it('should successfully transfer tokens', async () => { - const fromAddress = coinbase; - const toAddress = addressWithoutFunds; - const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); - expect(preBalance).to.be.bignumber.equal(0); - await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount); - const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); - return expect(postBalance).to.be.bignumber.equal(transferAmount); - }); - it('should fail to transfer tokens if fromAddress has an insufficient balance', async () => { - const fromAddress = addressWithoutFunds; - const toAddress = coinbase; - return expect( - zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount), - ).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer); - }); - it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { - const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; - const fromAddress = coinbase; - const toAddress = coinbase; - return expect( - zeroEx.token.transferAsync(nonExistentTokenAddress, fromAddress, toAddress, transferAmount), - ).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist); - }); - }); - describe('#transferFromAsync', () => { - let token: Token; - let toAddress: string; - let senderAddress: string; - before(async () => { - token = tokens[0]; - toAddress = addressWithoutFunds; - senderAddress = userAddresses[2]; - }); - it('should fail to transfer tokens if fromAddress has insufficient allowance set', async () => { - const fromAddress = coinbase; - const transferAmount = new BigNumber(42); - - const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress); - expect(fromAddressBalance).to.be.bignumber.greaterThan(transferAmount); - - const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(token.address, fromAddress, toAddress); - expect(fromAddressAllowance).to.be.bignumber.equal(0); - - return expect( - zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), - ).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer); - }); - it('[regression] should fail to transfer tokens if set allowance for toAddress instead of senderAddress', async () => { - const fromAddress = coinbase; - const transferAmount = new BigNumber(42); - - await zeroEx.token.setAllowanceAsync(token.address, fromAddress, toAddress, transferAmount); - - return expect( - zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), - ).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer); - }); - it('should fail to transfer tokens if fromAddress has insufficient balance', async () => { - const fromAddress = addressWithoutFunds; - const transferAmount = new BigNumber(42); - - const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress); - expect(fromAddressBalance).to.be.bignumber.equal(0); - - await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount); - const fromAddressAllowance = await zeroEx.token.getAllowanceAsync( - token.address, - fromAddress, - senderAddress, - ); - expect(fromAddressAllowance).to.be.bignumber.equal(transferAmount); - - return expect( - zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), - ).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer); - }); - it('should successfully transfer tokens', async () => { - const fromAddress = coinbase; - - const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); - expect(preBalance).to.be.bignumber.equal(0); - - const transferAmount = new BigNumber(42); - await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount); - - await zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount); - const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); - return expect(postBalance).to.be.bignumber.equal(transferAmount); - }); - it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { - const fromAddress = coinbase; - const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; - return expect( - zeroEx.token.transferFromAsync( - nonExistentTokenAddress, - fromAddress, - toAddress, - senderAddress, - new BigNumber(42), - ), - ).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist); - }); - }); - describe('#getBalanceAsync', () => { - describe('With web3 provider with accounts', () => { - it('should return the balance for an existing ERC20 token', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress); - const expectedBalance = new BigNumber('1000000000000000000000000000'); - return expect(balance).to.be.bignumber.equal(expectedBalance); - }); - it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { - const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; - const ownerAddress = coinbase; - return expect(zeroEx.token.getBalanceAsync(nonExistentTokenAddress, ownerAddress)).to.be.rejectedWith( - ZeroExError.TokenContractDoesNotExist, - ); - }); - it('should return a balance of 0 for a non-existent owner address', async () => { - const token = tokens[0]; - const nonExistentOwner = '0x198c6ad858f213fb31b6fe809e25040e6b964593'; - const balance = await zeroEx.token.getBalanceAsync(token.address, nonExistentOwner); - const expectedBalance = new BigNumber(0); - return expect(balance).to.be.bignumber.equal(expectedBalance); - }); - }); - describe('With web3 provider without accounts', () => { - let zeroExWithoutAccounts: ZeroEx; - before(async () => { - const hasAddresses = false; - const web3WithoutAccounts = web3Factory.create(hasAddresses); - zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config); - }); - it('should return balance even when called with Web3 provider instance without addresses', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const balance = await zeroExWithoutAccounts.token.getBalanceAsync(token.address, ownerAddress); - const expectedBalance = new BigNumber('1000000000000000000000000000'); - return expect(balance).to.be.bignumber.equal(expectedBalance); - }); - }); - }); - describe('#setAllowanceAsync', () => { - it("should set the spender's allowance", async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const spenderAddress = addressWithoutFunds; - - const allowanceBeforeSet = await zeroEx.token.getAllowanceAsync( - token.address, - ownerAddress, - spenderAddress, - ); - const expectedAllowanceBeforeAllowanceSet = new BigNumber(0); - expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet); - - const amountInBaseUnits = new BigNumber(50); - await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); - - const allowanceAfterSet = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); - const expectedAllowanceAfterAllowanceSet = amountInBaseUnits; - return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet); - }); - }); - describe('#setUnlimitedAllowanceAsync', () => { - it("should set the unlimited spender's allowance", async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const spenderAddress = addressWithoutFunds; - - await zeroEx.token.setUnlimitedAllowanceAsync(token.address, ownerAddress, spenderAddress); - const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); - return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - it('should reduce the gas cost for transfers including tokens with unlimited allowance support', async () => { - const transferAmount = new BigNumber(5); - const zrx = tokenUtils.getProtocolTokenOrThrow(); - const [, userWithNormalAllowance, userWithUnlimitedAllowance] = userAddresses; - await zeroEx.token.setAllowanceAsync(zrx.address, coinbase, userWithNormalAllowance, transferAmount); - await zeroEx.token.setUnlimitedAllowanceAsync(zrx.address, coinbase, userWithUnlimitedAllowance); - - const initBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance); - const initBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync( - userWithUnlimitedAllowance, - ); - - await zeroEx.token.transferFromAsync( - zrx.address, - coinbase, - userWithNormalAllowance, - userWithNormalAllowance, - transferAmount, - ); - await zeroEx.token.transferFromAsync( - zrx.address, - coinbase, - userWithUnlimitedAllowance, - userWithUnlimitedAllowance, - transferAmount, - ); - - const finalBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance); - const finalBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync( - userWithUnlimitedAllowance, - ); - - const normalGasCost = initBalanceWithNormalAllowance.minus(finalBalanceWithNormalAllowance); - const unlimitedGasCost = initBalanceWithUnlimitedAllowance.minus(finalBalanceWithUnlimitedAllowance); - - // In theory the gas cost with unlimited allowance should be smaller, but with testrpc it's actually bigger. - // This needs to be investigated in ethereumjs-vm. This test is essentially a repro. - // TODO: Make this test pass with inverted assertion. - expect(unlimitedGasCost.toNumber()).to.be.gt(normalGasCost.toNumber()); - }); - }); - describe('#getAllowanceAsync', () => { - describe('With web3 provider with accounts', () => { - it('should get the proxy allowance', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const spenderAddress = addressWithoutFunds; - - const amountInBaseUnits = new BigNumber(50); - await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); - - const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); - const expectedAllowance = amountInBaseUnits; - return expect(allowance).to.be.bignumber.equal(expectedAllowance); - }); - it('should return 0 if no allowance set yet', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const spenderAddress = addressWithoutFunds; - const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); - const expectedAllowance = new BigNumber(0); - return expect(allowance).to.be.bignumber.equal(expectedAllowance); - }); - }); - describe('With web3 provider without accounts', () => { - let zeroExWithoutAccounts: ZeroEx; - before(async () => { - const hasAddresses = false; - const web3WithoutAccounts = web3Factory.create(hasAddresses); - zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config); - }); - it('should get the proxy allowance', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const spenderAddress = addressWithoutFunds; - - const amountInBaseUnits = new BigNumber(50); - await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); - - const allowance = await zeroExWithoutAccounts.token.getAllowanceAsync( - token.address, - ownerAddress, - spenderAddress, - ); - const expectedAllowance = amountInBaseUnits; - return expect(allowance).to.be.bignumber.equal(expectedAllowance); - }); - }); - }); - describe('#getProxyAllowanceAsync', () => { - it('should get the proxy allowance', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - - const amountInBaseUnits = new BigNumber(50); - await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits); - - const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); - const expectedAllowance = amountInBaseUnits; - return expect(allowance).to.be.bignumber.equal(expectedAllowance); - }); - }); - describe('#setProxyAllowanceAsync', () => { - it('should set the proxy allowance', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - - const allowanceBeforeSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); - const expectedAllowanceBeforeAllowanceSet = new BigNumber(0); - expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet); - - const amountInBaseUnits = new BigNumber(50); - await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits); - - const allowanceAfterSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); - const expectedAllowanceAfterAllowanceSet = amountInBaseUnits; - return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet); - }); - }); - describe('#setUnlimitedProxyAllowanceAsync', () => { - it('should set the unlimited proxy allowance', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - - await zeroEx.token.setUnlimitedProxyAllowanceAsync(token.address, ownerAddress); - const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); - return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - }); - describe('#subscribe', () => { - const indexFilterValues = {}; - let tokenAddress: string; - const transferAmount = new BigNumber(42); - const allowanceAmount = new BigNumber(42); - before(() => { - const token = tokens[0]; - tokenAddress = token.address; - }); - afterEach(() => { - zeroEx.token.unsubscribeAll(); - }); - // Hack: Mocha does not allow a test to be both async and have a `done` callback - // Since we need to await the receipt of the event in the `subscribe` callback, - // we do need both. A hack is to make the top-level a sync fn w/ a done callback and then - // wrap the rest of the test in an async block - // Source: https://github.com/mochajs/mocha/issues/2407 - it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<TransferContractEventArgs>) => { - expect(logEvent.isRemoved).to.be.false(); - expect(logEvent.log.logIndex).to.be.equal(0); - expect(logEvent.log.transactionIndex).to.be.equal(0); - expect(logEvent.log.blockNumber).to.be.a('number'); - const args = logEvent.log.args; - expect(args._from).to.be.equal(coinbase); - expect(args._to).to.be.equal(addressWithoutFunds); - expect(args._value).to.be.bignumber.equal(transferAmount); - }, - ); - zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callback); - await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); - })().catch(done); - }); - it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - expect(logEvent).to.not.be.undefined(); - expect(logEvent.isRemoved).to.be.false(); - const args = logEvent.log.args; - expect(args._owner).to.be.equal(coinbase); - expect(args._spender).to.be.equal(addressWithoutFunds); - expect(args._value).to.be.bignumber.equal(allowanceAmount); - }, - ); - zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); - await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); - })().catch(done); - }); - it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled); - const callbackToBeCalled = reportNodeCallbackErrors(done)(); - const newProvider = web3Factory.getRpcProvider(); - zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); - zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled); - await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); - })().catch(done); - }); - it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - const subscriptionToken = zeroEx.token.subscribe( - tokenAddress, - TokenEvents.Transfer, - indexFilterValues, - callbackNeverToBeCalled, - ); - zeroEx.token.unsubscribe(subscriptionToken); - await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); - done(); - })().catch(done); - }); - }); - describe('#getLogsAsync', () => { - let tokenAddress: string; - let tokenTransferProxyAddress: string; - const blockRange: BlockRange = { - fromBlock: 0, - toBlock: BlockParamLiteral.Latest, - }; - let txHash: string; - before(() => { - const token = tokens[0]; - tokenAddress = token.address; - tokenTransferProxyAddress = zeroEx.proxy.getContractAddress(); - }); - it('should get logs with decoded args emitted by Approval', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); - await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = TokenEvents.Approval; - const indexFilterValues = {}; - const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>( - tokenAddress, - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(logs[0].event).to.be.equal(eventName); - expect(args._owner).to.be.equal(coinbase); - expect(args._spender).to.be.equal(tokenTransferProxyAddress); - expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - it('should only get the logs with the correct event name', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); - await zeroEx.awaitTransactionMinedAsync(txHash); - const differentEventName = TokenEvents.Transfer; - const indexFilterValues = {}; - const logs = await zeroEx.token.getLogsAsync( - tokenAddress, - differentEventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(0); - }); - it('should only get the logs with the correct indexed fields', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, addressWithoutFunds); - await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = TokenEvents.Approval; - const indexFilterValues = { - _owner: coinbase, - }; - const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>( - tokenAddress, - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(args._owner).to.be.equal(coinbase); - }); - }); + let web3: Web3; + let zeroEx: ZeroEx; + let userAddresses: string[]; + let tokens: Token[]; + let tokenUtils: TokenUtils; + let coinbase: string; + let addressWithoutFunds: string; + let web3Wrapper: Web3Wrapper; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + web3Wrapper = new Web3Wrapper(web3.currentProvider); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + coinbase = userAddresses[0]; + addressWithoutFunds = userAddresses[1]; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#transferAsync', () => { + let token: Token; + let transferAmount: BigNumber; + before(() => { + token = tokens[0]; + transferAmount = new BigNumber(42); + }); + it('should successfully transfer tokens', async () => { + const fromAddress = coinbase; + const toAddress = addressWithoutFunds; + const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); + expect(preBalance).to.be.bignumber.equal(0); + await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount); + const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); + return expect(postBalance).to.be.bignumber.equal(transferAmount); + }); + it('should fail to transfer tokens if fromAddress has an insufficient balance', async () => { + const fromAddress = addressWithoutFunds; + const toAddress = coinbase; + return expect( + zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount), + ).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer); + }); + it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { + const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; + const fromAddress = coinbase; + const toAddress = coinbase; + return expect( + zeroEx.token.transferAsync(nonExistentTokenAddress, fromAddress, toAddress, transferAmount), + ).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist); + }); + }); + describe('#transferFromAsync', () => { + let token: Token; + let toAddress: string; + let senderAddress: string; + before(async () => { + token = tokens[0]; + toAddress = addressWithoutFunds; + senderAddress = userAddresses[2]; + }); + it('should fail to transfer tokens if fromAddress has insufficient allowance set', async () => { + const fromAddress = coinbase; + const transferAmount = new BigNumber(42); + + const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress); + expect(fromAddressBalance).to.be.bignumber.greaterThan(transferAmount); + + const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(token.address, fromAddress, toAddress); + expect(fromAddressAllowance).to.be.bignumber.equal(0); + + return expect( + zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), + ).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer); + }); + it('[regression] should fail to transfer tokens if set allowance for toAddress instead of senderAddress', async () => { + const fromAddress = coinbase; + const transferAmount = new BigNumber(42); + + await zeroEx.token.setAllowanceAsync(token.address, fromAddress, toAddress, transferAmount); + + return expect( + zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), + ).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer); + }); + it('should fail to transfer tokens if fromAddress has insufficient balance', async () => { + const fromAddress = addressWithoutFunds; + const transferAmount = new BigNumber(42); + + const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress); + expect(fromAddressBalance).to.be.bignumber.equal(0); + + await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount); + const fromAddressAllowance = await zeroEx.token.getAllowanceAsync( + token.address, + fromAddress, + senderAddress, + ); + expect(fromAddressAllowance).to.be.bignumber.equal(transferAmount); + + return expect( + zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), + ).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer); + }); + it('should successfully transfer tokens', async () => { + const fromAddress = coinbase; + + const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); + expect(preBalance).to.be.bignumber.equal(0); + + const transferAmount = new BigNumber(42); + await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount); + + await zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount); + const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); + return expect(postBalance).to.be.bignumber.equal(transferAmount); + }); + it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { + const fromAddress = coinbase; + const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; + return expect( + zeroEx.token.transferFromAsync( + nonExistentTokenAddress, + fromAddress, + toAddress, + senderAddress, + new BigNumber(42), + ), + ).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist); + }); + }); + describe('#getBalanceAsync', () => { + describe('With web3 provider with accounts', () => { + it('should return the balance for an existing ERC20 token', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress); + const expectedBalance = new BigNumber('1000000000000000000000000000'); + return expect(balance).to.be.bignumber.equal(expectedBalance); + }); + it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { + const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; + const ownerAddress = coinbase; + return expect(zeroEx.token.getBalanceAsync(nonExistentTokenAddress, ownerAddress)).to.be.rejectedWith( + ZeroExError.TokenContractDoesNotExist, + ); + }); + it('should return a balance of 0 for a non-existent owner address', async () => { + const token = tokens[0]; + const nonExistentOwner = '0x198c6ad858f213fb31b6fe809e25040e6b964593'; + const balance = await zeroEx.token.getBalanceAsync(token.address, nonExistentOwner); + const expectedBalance = new BigNumber(0); + return expect(balance).to.be.bignumber.equal(expectedBalance); + }); + }); + describe('With web3 provider without accounts', () => { + let zeroExWithoutAccounts: ZeroEx; + before(async () => { + const hasAddresses = false; + const web3WithoutAccounts = web3Factory.create(hasAddresses); + zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config); + }); + it('should return balance even when called with Web3 provider instance without addresses', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const balance = await zeroExWithoutAccounts.token.getBalanceAsync(token.address, ownerAddress); + const expectedBalance = new BigNumber('1000000000000000000000000000'); + return expect(balance).to.be.bignumber.equal(expectedBalance); + }); + }); + }); + describe('#setAllowanceAsync', () => { + it("should set the spender's allowance", async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const spenderAddress = addressWithoutFunds; + + const allowanceBeforeSet = await zeroEx.token.getAllowanceAsync( + token.address, + ownerAddress, + spenderAddress, + ); + const expectedAllowanceBeforeAllowanceSet = new BigNumber(0); + expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet); + + const amountInBaseUnits = new BigNumber(50); + await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); + + const allowanceAfterSet = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); + const expectedAllowanceAfterAllowanceSet = amountInBaseUnits; + return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet); + }); + }); + describe('#setUnlimitedAllowanceAsync', () => { + it("should set the unlimited spender's allowance", async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const spenderAddress = addressWithoutFunds; + + await zeroEx.token.setUnlimitedAllowanceAsync(token.address, ownerAddress, spenderAddress); + const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); + return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + it('should reduce the gas cost for transfers including tokens with unlimited allowance support', async () => { + const transferAmount = new BigNumber(5); + const zrx = tokenUtils.getProtocolTokenOrThrow(); + const [, userWithNormalAllowance, userWithUnlimitedAllowance] = userAddresses; + await zeroEx.token.setAllowanceAsync(zrx.address, coinbase, userWithNormalAllowance, transferAmount); + await zeroEx.token.setUnlimitedAllowanceAsync(zrx.address, coinbase, userWithUnlimitedAllowance); + + const initBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance); + const initBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync( + userWithUnlimitedAllowance, + ); + + await zeroEx.token.transferFromAsync( + zrx.address, + coinbase, + userWithNormalAllowance, + userWithNormalAllowance, + transferAmount, + ); + await zeroEx.token.transferFromAsync( + zrx.address, + coinbase, + userWithUnlimitedAllowance, + userWithUnlimitedAllowance, + transferAmount, + ); + + const finalBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance); + const finalBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync( + userWithUnlimitedAllowance, + ); + + const normalGasCost = initBalanceWithNormalAllowance.minus(finalBalanceWithNormalAllowance); + const unlimitedGasCost = initBalanceWithUnlimitedAllowance.minus(finalBalanceWithUnlimitedAllowance); + + // In theory the gas cost with unlimited allowance should be smaller, but with testrpc it's actually bigger. + // This needs to be investigated in ethereumjs-vm. This test is essentially a repro. + // TODO: Make this test pass with inverted assertion. + expect(unlimitedGasCost.toNumber()).to.be.gt(normalGasCost.toNumber()); + }); + }); + describe('#getAllowanceAsync', () => { + describe('With web3 provider with accounts', () => { + it('should get the proxy allowance', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const spenderAddress = addressWithoutFunds; + + const amountInBaseUnits = new BigNumber(50); + await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); + + const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); + const expectedAllowance = amountInBaseUnits; + return expect(allowance).to.be.bignumber.equal(expectedAllowance); + }); + it('should return 0 if no allowance set yet', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const spenderAddress = addressWithoutFunds; + const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); + const expectedAllowance = new BigNumber(0); + return expect(allowance).to.be.bignumber.equal(expectedAllowance); + }); + }); + describe('With web3 provider without accounts', () => { + let zeroExWithoutAccounts: ZeroEx; + before(async () => { + const hasAddresses = false; + const web3WithoutAccounts = web3Factory.create(hasAddresses); + zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config); + }); + it('should get the proxy allowance', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const spenderAddress = addressWithoutFunds; + + const amountInBaseUnits = new BigNumber(50); + await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); + + const allowance = await zeroExWithoutAccounts.token.getAllowanceAsync( + token.address, + ownerAddress, + spenderAddress, + ); + const expectedAllowance = amountInBaseUnits; + return expect(allowance).to.be.bignumber.equal(expectedAllowance); + }); + }); + }); + describe('#getProxyAllowanceAsync', () => { + it('should get the proxy allowance', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + + const amountInBaseUnits = new BigNumber(50); + await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits); + + const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); + const expectedAllowance = amountInBaseUnits; + return expect(allowance).to.be.bignumber.equal(expectedAllowance); + }); + }); + describe('#setProxyAllowanceAsync', () => { + it('should set the proxy allowance', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + + const allowanceBeforeSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); + const expectedAllowanceBeforeAllowanceSet = new BigNumber(0); + expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet); + + const amountInBaseUnits = new BigNumber(50); + await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits); + + const allowanceAfterSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); + const expectedAllowanceAfterAllowanceSet = amountInBaseUnits; + return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet); + }); + }); + describe('#setUnlimitedProxyAllowanceAsync', () => { + it('should set the unlimited proxy allowance', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + + await zeroEx.token.setUnlimitedProxyAllowanceAsync(token.address, ownerAddress); + const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); + return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + }); + describe('#subscribe', () => { + const indexFilterValues = {}; + let tokenAddress: string; + const transferAmount = new BigNumber(42); + const allowanceAmount = new BigNumber(42); + before(() => { + const token = tokens[0]; + tokenAddress = token.address; + }); + afterEach(() => { + zeroEx.token.unsubscribeAll(); + }); + // Hack: Mocha does not allow a test to be both async and have a `done` callback + // Since we need to await the receipt of the event in the `subscribe` callback, + // we do need both. A hack is to make the top-level a sync fn w/ a done callback and then + // wrap the rest of the test in an async block + // Source: https://github.com/mochajs/mocha/issues/2407 + it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<TransferContractEventArgs>) => { + expect(logEvent.isRemoved).to.be.false(); + expect(logEvent.log.logIndex).to.be.equal(0); + expect(logEvent.log.transactionIndex).to.be.equal(0); + expect(logEvent.log.blockNumber).to.be.a('number'); + const args = logEvent.log.args; + expect(args._from).to.be.equal(coinbase); + expect(args._to).to.be.equal(addressWithoutFunds); + expect(args._value).to.be.bignumber.equal(transferAmount); + }, + ); + zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callback); + await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); + })().catch(done); + }); + it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + expect(logEvent).to.not.be.undefined(); + expect(logEvent.isRemoved).to.be.false(); + const args = logEvent.log.args; + expect(args._owner).to.be.equal(coinbase); + expect(args._spender).to.be.equal(addressWithoutFunds); + expect(args._value).to.be.bignumber.equal(allowanceAmount); + }, + ); + zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); + await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); + })().catch(done); + }); + it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled); + const callbackToBeCalled = reportNodeCallbackErrors(done)(); + const newProvider = web3Factory.getRpcProvider(); + zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); + zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled); + await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); + })().catch(done); + }); + it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + const subscriptionToken = zeroEx.token.subscribe( + tokenAddress, + TokenEvents.Transfer, + indexFilterValues, + callbackNeverToBeCalled, + ); + zeroEx.token.unsubscribe(subscriptionToken); + await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); + done(); + })().catch(done); + }); + }); + describe('#getLogsAsync', () => { + let tokenAddress: string; + let tokenTransferProxyAddress: string; + const blockRange: BlockRange = { + fromBlock: 0, + toBlock: BlockParamLiteral.Latest, + }; + let txHash: string; + before(() => { + const token = tokens[0]; + tokenAddress = token.address; + tokenTransferProxyAddress = zeroEx.proxy.getContractAddress(); + }); + it('should get logs with decoded args emitted by Approval', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = TokenEvents.Approval; + const indexFilterValues = {}; + const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>( + tokenAddress, + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(logs[0].event).to.be.equal(eventName); + expect(args._owner).to.be.equal(coinbase); + expect(args._spender).to.be.equal(tokenTransferProxyAddress); + expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + it('should only get the logs with the correct event name', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); + await zeroEx.awaitTransactionMinedAsync(txHash); + const differentEventName = TokenEvents.Transfer; + const indexFilterValues = {}; + const logs = await zeroEx.token.getLogsAsync( + tokenAddress, + differentEventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(0); + }); + it('should only get the logs with the correct indexed fields', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, addressWithoutFunds); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = TokenEvents.Approval; + const indexFilterValues = { + _owner: coinbase, + }; + const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>( + tokenAddress, + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(args._owner).to.be.equal(coinbase); + }); + }); }); // tslint:disable:max-file-line-count diff --git a/packages/0x.js/test/utils/chai_setup.ts b/packages/0x.js/test/utils/chai_setup.ts index e156b5f7c..078edd309 100644 --- a/packages/0x.js/test/utils/chai_setup.ts +++ b/packages/0x.js/test/utils/chai_setup.ts @@ -4,10 +4,10 @@ import ChaiBigNumber = require('chai-bignumber'); import * as dirtyChai from 'dirty-chai'; export const chaiSetup = { - configure() { - chai.config.includeStack = true; - chai.use(ChaiBigNumber()); - chai.use(dirtyChai); - chai.use(chaiAsPromised); - }, + configure() { + chai.config.includeStack = true; + chai.use(ChaiBigNumber()); + chai.use(dirtyChai); + chai.use(chaiAsPromised); + }, }; diff --git a/packages/0x.js/test/utils/constants.ts b/packages/0x.js/test/utils/constants.ts index b7161e9ab..a9e665c25 100644 --- a/packages/0x.js/test/utils/constants.ts +++ b/packages/0x.js/test/utils/constants.ts @@ -1,11 +1,11 @@ export const constants = { - NULL_ADDRESS: '0x0000000000000000000000000000000000000000', - RPC_URL: 'http://localhost:8545', - ROPSTEN_NETWORK_ID: 3, - KOVAN_NETWORK_ID: 42, - TESTRPC_NETWORK_ID: 50, - KOVAN_RPC_URL: 'https://kovan.infura.io/', - ROPSTEN_RPC_URL: 'https://ropsten.infura.io/', - ZRX_DECIMALS: 18, - GAS_ESTIMATE: 500000, + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + RPC_URL: 'http://localhost:8545', + ROPSTEN_NETWORK_ID: 3, + KOVAN_NETWORK_ID: 42, + TESTRPC_NETWORK_ID: 50, + KOVAN_RPC_URL: 'https://kovan.infura.io/', + ROPSTEN_RPC_URL: 'https://ropsten.infura.io/', + ZRX_DECIMALS: 18, + GAS_ESTIMATE: 500000, }; diff --git a/packages/0x.js/test/utils/fill_scenarios.ts b/packages/0x.js/test/utils/fill_scenarios.ts index 1d4b8b6f4..1a61487f4 100644 --- a/packages/0x.js/test/utils/fill_scenarios.ts +++ b/packages/0x.js/test/utils/fill_scenarios.ts @@ -11,192 +11,192 @@ import { constants } from './constants'; const INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS = new BigNumber(100); export class FillScenarios { - private _zeroEx: ZeroEx; - private _userAddresses: string[]; - private _tokens: Token[]; - private _coinbase: string; - private _zrxTokenAddress: string; - private _exchangeContractAddress: string; - constructor( - zeroEx: ZeroEx, - userAddresses: string[], - tokens: Token[], - zrxTokenAddress: string, - exchangeContractAddress: string, - ) { - this._zeroEx = zeroEx; - this._userAddresses = userAddresses; - this._tokens = tokens; - this._coinbase = userAddresses[0]; - this._zrxTokenAddress = zrxTokenAddress; - this._exchangeContractAddress = exchangeContractAddress; - } - public async initTokenBalancesAsync() { - const web3Wrapper = (this._zeroEx as any)._web3Wrapper as Web3Wrapper; - for (const token of this._tokens) { - if (token.symbol !== 'ZRX' && token.symbol !== 'WETH') { - const contractInstance = web3Wrapper.getContractInstance( - artifacts.DummyTokenArtifact.abi, - token.address, - ); - const defaults = {}; - const dummyToken = new DummyTokenContract(contractInstance, defaults); - const tokenSupply = ZeroEx.toBaseUnitAmount(INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS, token.decimals); - const txHash = await dummyToken.setBalance.sendTransactionAsync(this._coinbase, tokenSupply, { - from: this._coinbase, - }); - await this._zeroEx.awaitTransactionMinedAsync(txHash); - } - } - } - public async createFillableSignedOrderAsync( - makerTokenAddress: string, - takerTokenAddress: string, - makerAddress: string, - takerAddress: string, - fillableAmount: BigNumber, - expirationUnixTimestampSec?: BigNumber, - ): Promise<SignedOrder> { - return this.createAsymmetricFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - fillableAmount, - expirationUnixTimestampSec, - ); - } - public async createFillableSignedOrderWithFeesAsync( - makerTokenAddress: string, - takerTokenAddress: string, - makerFee: BigNumber, - takerFee: BigNumber, - makerAddress: string, - takerAddress: string, - fillableAmount: BigNumber, - feeRecepient: string, - expirationUnixTimestampSec?: BigNumber, - ): Promise<SignedOrder> { - return this._createAsymmetricFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - fillableAmount, - feeRecepient, - expirationUnixTimestampSec, - ); - } - public async createAsymmetricFillableSignedOrderAsync( - makerTokenAddress: string, - takerTokenAddress: string, - makerAddress: string, - takerAddress: string, - makerFillableAmount: BigNumber, - takerFillableAmount: BigNumber, - expirationUnixTimestampSec?: BigNumber, - ): Promise<SignedOrder> { - const makerFee = new BigNumber(0); - const takerFee = new BigNumber(0); - const feeRecepient = constants.NULL_ADDRESS; - return this._createAsymmetricFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - takerAddress, - makerFillableAmount, - takerFillableAmount, - feeRecepient, - expirationUnixTimestampSec, - ); - } - public async createPartiallyFilledSignedOrderAsync( - makerTokenAddress: string, - takerTokenAddress: string, - takerAddress: string, - fillableAmount: BigNumber, - partialFillAmount: BigNumber, - ) { - const [makerAddress] = this._userAddresses; - const signedOrder = await this.createAsymmetricFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - fillableAmount, - ); - const shouldThrowOnInsufficientBalanceOrAllowance = false; - await this._zeroEx.exchange.fillOrderAsync( - signedOrder, - partialFillAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - return signedOrder; - } - private async _createAsymmetricFillableSignedOrderWithFeesAsync( - makerTokenAddress: string, - takerTokenAddress: string, - makerFee: BigNumber, - takerFee: BigNumber, - makerAddress: string, - takerAddress: string, - makerFillableAmount: BigNumber, - takerFillableAmount: BigNumber, - feeRecepient: string, - expirationUnixTimestampSec?: BigNumber, - ): Promise<SignedOrder> { - await Promise.all([ - this._increaseBalanceAndAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount), - this._increaseBalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount), - ]); - await Promise.all([ - this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, makerAddress, makerFee), - this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, takerAddress, takerFee), - ]); + private _zeroEx: ZeroEx; + private _userAddresses: string[]; + private _tokens: Token[]; + private _coinbase: string; + private _zrxTokenAddress: string; + private _exchangeContractAddress: string; + constructor( + zeroEx: ZeroEx, + userAddresses: string[], + tokens: Token[], + zrxTokenAddress: string, + exchangeContractAddress: string, + ) { + this._zeroEx = zeroEx; + this._userAddresses = userAddresses; + this._tokens = tokens; + this._coinbase = userAddresses[0]; + this._zrxTokenAddress = zrxTokenAddress; + this._exchangeContractAddress = exchangeContractAddress; + } + public async initTokenBalancesAsync() { + const web3Wrapper = (this._zeroEx as any)._web3Wrapper as Web3Wrapper; + for (const token of this._tokens) { + if (token.symbol !== 'ZRX' && token.symbol !== 'WETH') { + const contractInstance = web3Wrapper.getContractInstance( + artifacts.DummyTokenArtifact.abi, + token.address, + ); + const defaults = {}; + const dummyToken = new DummyTokenContract(contractInstance, defaults); + const tokenSupply = ZeroEx.toBaseUnitAmount(INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS, token.decimals); + const txHash = await dummyToken.setBalance.sendTransactionAsync(this._coinbase, tokenSupply, { + from: this._coinbase, + }); + await this._zeroEx.awaitTransactionMinedAsync(txHash); + } + } + } + public async createFillableSignedOrderAsync( + makerTokenAddress: string, + takerTokenAddress: string, + makerAddress: string, + takerAddress: string, + fillableAmount: BigNumber, + expirationUnixTimestampSec?: BigNumber, + ): Promise<SignedOrder> { + return this.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + fillableAmount, + expirationUnixTimestampSec, + ); + } + public async createFillableSignedOrderWithFeesAsync( + makerTokenAddress: string, + takerTokenAddress: string, + makerFee: BigNumber, + takerFee: BigNumber, + makerAddress: string, + takerAddress: string, + fillableAmount: BigNumber, + feeRecepient: string, + expirationUnixTimestampSec?: BigNumber, + ): Promise<SignedOrder> { + return this._createAsymmetricFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + fillableAmount, + feeRecepient, + expirationUnixTimestampSec, + ); + } + public async createAsymmetricFillableSignedOrderAsync( + makerTokenAddress: string, + takerTokenAddress: string, + makerAddress: string, + takerAddress: string, + makerFillableAmount: BigNumber, + takerFillableAmount: BigNumber, + expirationUnixTimestampSec?: BigNumber, + ): Promise<SignedOrder> { + const makerFee = new BigNumber(0); + const takerFee = new BigNumber(0); + const feeRecepient = constants.NULL_ADDRESS; + return this._createAsymmetricFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + takerAddress, + makerFillableAmount, + takerFillableAmount, + feeRecepient, + expirationUnixTimestampSec, + ); + } + public async createPartiallyFilledSignedOrderAsync( + makerTokenAddress: string, + takerTokenAddress: string, + takerAddress: string, + fillableAmount: BigNumber, + partialFillAmount: BigNumber, + ) { + const [makerAddress] = this._userAddresses; + const signedOrder = await this.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + fillableAmount, + ); + const shouldThrowOnInsufficientBalanceOrAllowance = false; + await this._zeroEx.exchange.fillOrderAsync( + signedOrder, + partialFillAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + return signedOrder; + } + private async _createAsymmetricFillableSignedOrderWithFeesAsync( + makerTokenAddress: string, + takerTokenAddress: string, + makerFee: BigNumber, + takerFee: BigNumber, + makerAddress: string, + takerAddress: string, + makerFillableAmount: BigNumber, + takerFillableAmount: BigNumber, + feeRecepient: string, + expirationUnixTimestampSec?: BigNumber, + ): Promise<SignedOrder> { + await Promise.all([ + this._increaseBalanceAndAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount), + this._increaseBalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount), + ]); + await Promise.all([ + this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, makerAddress, makerFee), + this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, takerAddress, takerFee), + ]); - const signedOrder = await orderFactory.createSignedOrderAsync( - this._zeroEx, - makerAddress, - takerAddress, - makerFee, - takerFee, - makerFillableAmount, - makerTokenAddress, - takerFillableAmount, - takerTokenAddress, - this._exchangeContractAddress, - feeRecepient, - expirationUnixTimestampSec, - ); - return signedOrder; - } - private async _increaseBalanceAndAllowanceAsync( - tokenAddress: string, - address: string, - amount: BigNumber, - ): Promise<void> { - if (amount.isZero() || address === ZeroEx.NULL_ADDRESS) { - return; // noop - } - await Promise.all([ - this._increaseBalanceAsync(tokenAddress, address, amount), - this._increaseAllowanceAsync(tokenAddress, address, amount), - ]); - } - private async _increaseBalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> { - await this._zeroEx.token.transferAsync(tokenAddress, this._coinbase, address, amount); - } - private async _increaseAllowanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> { - const oldMakerAllowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, address); - const newMakerAllowance = oldMakerAllowance.plus(amount); - await this._zeroEx.token.setProxyAllowanceAsync(tokenAddress, address, newMakerAllowance); - } + const signedOrder = await orderFactory.createSignedOrderAsync( + this._zeroEx, + makerAddress, + takerAddress, + makerFee, + takerFee, + makerFillableAmount, + makerTokenAddress, + takerFillableAmount, + takerTokenAddress, + this._exchangeContractAddress, + feeRecepient, + expirationUnixTimestampSec, + ); + return signedOrder; + } + private async _increaseBalanceAndAllowanceAsync( + tokenAddress: string, + address: string, + amount: BigNumber, + ): Promise<void> { + if (amount.isZero() || address === ZeroEx.NULL_ADDRESS) { + return; // noop + } + await Promise.all([ + this._increaseBalanceAsync(tokenAddress, address, amount), + this._increaseAllowanceAsync(tokenAddress, address, amount), + ]); + } + private async _increaseBalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> { + await this._zeroEx.token.transferAsync(tokenAddress, this._coinbase, address, amount); + } + private async _increaseAllowanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> { + const oldMakerAllowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, address); + const newMakerAllowance = oldMakerAllowance.plus(amount); + await this._zeroEx.token.setProxyAllowanceAsync(tokenAddress, address, newMakerAllowance); + } } diff --git a/packages/0x.js/test/utils/order_factory.ts b/packages/0x.js/test/utils/order_factory.ts index b65920e64..08f2081a4 100644 --- a/packages/0x.js/test/utils/order_factory.ts +++ b/packages/0x.js/test/utils/order_factory.ts @@ -6,41 +6,41 @@ import { SignedOrder, ZeroEx } from '../../src'; const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false; export const orderFactory = { - async createSignedOrderAsync( - zeroEx: ZeroEx, - maker: string, - taker: string, - makerFee: BigNumber, - takerFee: BigNumber, - makerTokenAmount: BigNumber, - makerTokenAddress: string, - takerTokenAmount: BigNumber, - takerTokenAddress: string, - exchangeContractAddress: string, - feeRecipient: string, - expirationUnixTimestampSecIfExists?: BigNumber, - ): Promise<SignedOrder> { - const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite - const expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSecIfExists) - ? defaultExpirationUnixTimestampSec - : expirationUnixTimestampSecIfExists; - const order = { - maker, - taker, - makerFee, - takerFee, - makerTokenAmount, - takerTokenAmount, - makerTokenAddress, - takerTokenAddress, - salt: ZeroEx.generatePseudoRandomSalt(), - exchangeContractAddress, - feeRecipient, - expirationUnixTimestampSec, - }; - const orderHash = ZeroEx.getOrderHashHex(order); - const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX); - const signedOrder: SignedOrder = _.assign(order, { ecSignature }); - return signedOrder; - }, + async createSignedOrderAsync( + zeroEx: ZeroEx, + maker: string, + taker: string, + makerFee: BigNumber, + takerFee: BigNumber, + makerTokenAmount: BigNumber, + makerTokenAddress: string, + takerTokenAmount: BigNumber, + takerTokenAddress: string, + exchangeContractAddress: string, + feeRecipient: string, + expirationUnixTimestampSecIfExists?: BigNumber, + ): Promise<SignedOrder> { + const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite + const expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSecIfExists) + ? defaultExpirationUnixTimestampSec + : expirationUnixTimestampSecIfExists; + const order = { + maker, + taker, + makerFee, + takerFee, + makerTokenAmount, + takerTokenAmount, + makerTokenAddress, + takerTokenAddress, + salt: ZeroEx.generatePseudoRandomSalt(), + exchangeContractAddress, + feeRecipient, + expirationUnixTimestampSec, + }; + const orderHash = ZeroEx.getOrderHashHex(order); + const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX); + const signedOrder: SignedOrder = _.assign(order, { ecSignature }); + return signedOrder; + }, }; diff --git a/packages/0x.js/test/utils/report_callback_errors.ts b/packages/0x.js/test/utils/report_callback_errors.ts index 0b619f73e..27c9745c9 100644 --- a/packages/0x.js/test/utils/report_callback_errors.ts +++ b/packages/0x.js/test/utils/report_callback_errors.ts @@ -6,61 +6,61 @@ import { DoneCallback } from '../../src/types'; const expect = chai.expect; export const reportNoErrorCallbackErrors = (done: DoneCallback, expectToBeCalledOnce = true) => { - return <T>(f?: (value: T) => void) => { - const wrapped = (value: T) => { - if (_.isUndefined(f)) { - done(); - return; - } - try { - f(value); - if (expectToBeCalledOnce) { - done(); - } - } catch (err) { - done(err); - } - }; - return wrapped; - }; + return <T>(f?: (value: T) => void) => { + const wrapped = (value: T) => { + if (_.isUndefined(f)) { + done(); + return; + } + try { + f(value); + if (expectToBeCalledOnce) { + done(); + } + } catch (err) { + done(err); + } + }; + return wrapped; + }; }; export const reportNodeCallbackErrors = (done: DoneCallback, expectToBeCalledOnce = true) => { - return <T>(f?: (value: T) => void) => { - const wrapped = (error: Error | null, value: T | undefined) => { - if (!_.isNull(error)) { - done(error); - } else { - if (_.isUndefined(f)) { - done(); - return; - } - try { - f(value as T); - if (expectToBeCalledOnce) { - done(); - } - } catch (err) { - done(err); - } - } - }; - return wrapped; - }; + return <T>(f?: (value: T) => void) => { + const wrapped = (error: Error | null, value: T | undefined) => { + if (!_.isNull(error)) { + done(error); + } else { + if (_.isUndefined(f)) { + done(); + return; + } + try { + f(value as T); + if (expectToBeCalledOnce) { + done(); + } + } catch (err) { + done(err); + } + } + }; + return wrapped; + }; }; export const assertNodeCallbackError = (done: DoneCallback, errMsg: string) => { - const wrapped = <T>(error: Error | null, value: T | undefined) => { - if (_.isNull(error)) { - done(new Error('Expected callback to receive an error')); - } else { - try { - expect(error.message).to.be.equal(errMsg); - done(); - } catch (err) { - done(err); - } - } - }; - return wrapped; + const wrapped = <T>(error: Error | null, value: T | undefined) => { + if (_.isNull(error)) { + done(new Error('Expected callback to receive an error')); + } else { + try { + expect(error.message).to.be.equal(errMsg); + done(); + } catch (err) { + done(err); + } + } + }; + return wrapped; }; diff --git a/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts b/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts index 466b0f350..53f2be83d 100644 --- a/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts +++ b/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts @@ -6,22 +6,22 @@ import { JSONRPCPayload } from '../../../src/types'; * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js */ export class EmptyWalletSubprovider { - // This method needs to be here to satisfy the interface but linter wants it to be static. - // tslint:disable-next-line:prefer-function-over-method - public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) { - switch (payload.method) { - case 'eth_accounts': - end(null, []); - return; + // This method needs to be here to satisfy the interface but linter wants it to be static. + // tslint:disable-next-line:prefer-function-over-method + public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) { + switch (payload.method) { + case 'eth_accounts': + end(null, []); + return; - default: - next(); - return; - } - } - // Required to implement this method despite not needing it for this subprovider - // tslint:disable-next-line:prefer-function-over-method - public setEngine(engine: any) { - // noop - } + default: + next(); + return; + } + } + // Required to implement this method despite not needing it for this subprovider + // tslint:disable-next-line:prefer-function-over-method + public setEngine(engine: any) { + // noop + } } diff --git a/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts b/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts index 307bfb188..e1113a851 100644 --- a/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts +++ b/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts @@ -9,26 +9,26 @@ import { JSONRPCPayload } from '../../../src/types'; * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js */ export class FakeGasEstimateSubprovider { - private _constantGasAmount: number; - constructor(constantGasAmount: number) { - this._constantGasAmount = constantGasAmount; - } - // This method needs to be here to satisfy the interface but linter wants it to be static. - // tslint:disable-next-line:prefer-function-over-method - public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) { - switch (payload.method) { - case 'eth_estimateGas': - end(null, this._constantGasAmount); - return; + private _constantGasAmount: number; + constructor(constantGasAmount: number) { + this._constantGasAmount = constantGasAmount; + } + // This method needs to be here to satisfy the interface but linter wants it to be static. + // tslint:disable-next-line:prefer-function-over-method + public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) { + switch (payload.method) { + case 'eth_estimateGas': + end(null, this._constantGasAmount); + return; - default: - next(); - return; - } - } - // Required to implement this method despite not needing it for this subprovider - // tslint:disable-next-line:prefer-function-over-method - public setEngine(engine: any) { - // noop - } + default: + next(); + return; + } + } + // Required to implement this method despite not needing it for this subprovider + // tslint:disable-next-line:prefer-function-over-method + public setEngine(engine: any) { + // noop + } } diff --git a/packages/0x.js/test/utils/token_utils.ts b/packages/0x.js/test/utils/token_utils.ts index 9cbefcf8e..d3fc22ff4 100644 --- a/packages/0x.js/test/utils/token_utils.ts +++ b/packages/0x.js/test/utils/token_utils.ts @@ -6,28 +6,28 @@ const PROTOCOL_TOKEN_SYMBOL = 'ZRX'; const WETH_TOKEN_SYMBOL = 'WETH'; export class TokenUtils { - private _tokens: Token[]; - constructor(tokens: Token[]) { - this._tokens = tokens; - } - public getProtocolTokenOrThrow(): Token { - const zrxToken = _.find(this._tokens, { symbol: PROTOCOL_TOKEN_SYMBOL }); - if (_.isUndefined(zrxToken)) { - throw new Error(InternalZeroExError.ZrxNotInTokenRegistry); - } - return zrxToken; - } - public getWethTokenOrThrow(): Token { - const wethToken = _.find(this._tokens, { symbol: WETH_TOKEN_SYMBOL }); - if (_.isUndefined(wethToken)) { - throw new Error(InternalZeroExError.WethNotInTokenRegistry); - } - return wethToken; - } - public getDummyTokens(): Token[] { - const dummyTokens = _.filter(this._tokens, token => { - return !_.includes([PROTOCOL_TOKEN_SYMBOL, WETH_TOKEN_SYMBOL], token.symbol); - }); - return dummyTokens; - } + private _tokens: Token[]; + constructor(tokens: Token[]) { + this._tokens = tokens; + } + public getProtocolTokenOrThrow(): Token { + const zrxToken = _.find(this._tokens, { symbol: PROTOCOL_TOKEN_SYMBOL }); + if (_.isUndefined(zrxToken)) { + throw new Error(InternalZeroExError.ZrxNotInTokenRegistry); + } + return zrxToken; + } + public getWethTokenOrThrow(): Token { + const wethToken = _.find(this._tokens, { symbol: WETH_TOKEN_SYMBOL }); + if (_.isUndefined(wethToken)) { + throw new Error(InternalZeroExError.WethNotInTokenRegistry); + } + return wethToken; + } + public getDummyTokens(): Token[] { + const dummyTokens = _.filter(this._tokens, token => { + return !_.includes([PROTOCOL_TOKEN_SYMBOL, WETH_TOKEN_SYMBOL], token.symbol); + }); + return dummyTokens; + } } diff --git a/packages/0x.js/test/utils/web3_factory.ts b/packages/0x.js/test/utils/web3_factory.ts index 640b96256..26c26e03d 100644 --- a/packages/0x.js/test/utils/web3_factory.ts +++ b/packages/0x.js/test/utils/web3_factory.ts @@ -19,24 +19,24 @@ import { constants } from './constants'; import * as Web3 from 'web3'; export const web3Factory = { - create(hasAddresses: boolean = true): Web3 { - const provider = this.getRpcProvider(hasAddresses); - const web3 = new Web3(); - web3.setProvider(provider); - return web3; - }, - getRpcProvider(hasAddresses: boolean = true): Web3.Provider { - const provider = new ProviderEngine(); - if (!hasAddresses) { - provider.addProvider(new EmptyWalletSubprovider()); - } - provider.addProvider(new FakeGasEstimateSubprovider(constants.GAS_ESTIMATE)); - provider.addProvider( - new RpcSubprovider({ - rpcUrl: constants.RPC_URL, - }), - ); - provider.start(); - return provider; - }, + create(hasAddresses: boolean = true): Web3 { + const provider = this.getRpcProvider(hasAddresses); + const web3 = new Web3(); + web3.setProvider(provider); + return web3; + }, + getRpcProvider(hasAddresses: boolean = true): Web3.Provider { + const provider = new ProviderEngine(); + if (!hasAddresses) { + provider.addProvider(new EmptyWalletSubprovider()); + } + provider.addProvider(new FakeGasEstimateSubprovider(constants.GAS_ESTIMATE)); + provider.addProvider( + new RpcSubprovider({ + rpcUrl: constants.RPC_URL, + }), + ); + provider.start(); + return provider; + }, }; diff --git a/packages/0x.js/tsconfig.json b/packages/0x.js/tsconfig.json index 0666fbd82..117f51e83 100644 --- a/packages/0x.js/tsconfig.json +++ b/packages/0x.js/tsconfig.json @@ -1,16 +1,16 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib", - "noImplicitThis": false - }, - "include": [ - "./src/**/*", - "./test/**/*", - "../../node_modules/types-bn/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts", - "../../node_modules/web3-typescript-typings/index.d.ts", - "../../node_modules/chai-typescript-typings/index.d.ts", - "../../node_modules/chai-as-promised-typescript-typings/index.d.ts" - ] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "noImplicitThis": false + }, + "include": [ + "./src/**/*", + "./test/**/*", + "../../node_modules/types-bn/index.d.ts", + "../../node_modules/types-ethereumjs-util/index.d.ts", + "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/chai-typescript-typings/index.d.ts", + "../../node_modules/chai-as-promised-typescript-typings/index.d.ts" + ] } diff --git a/packages/0x.js/tslint.json b/packages/0x.js/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/0x.js/tslint.json +++ b/packages/0x.js/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/abi-gen/package.json b/packages/abi-gen/package.json index 58ee12220..31a0f7f55 100644 --- a/packages/abi-gen/package.json +++ b/packages/abi-gen/package.json @@ -1,48 +1,48 @@ { - "name": "@0xproject/abi-gen", - "version": "0.1.4", - "description": "Generate contract wrappers from ABI and handlebars templates", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "scripts": { - "lint": "tslint --project . 'src/**/*.ts'", - "clean": "shx rm -rf lib", - "build": "tsc" - }, - "bin": { - "abi-gen": "lib/index.js" - }, - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/abi-gen/README.md", - "dependencies": { - "@0xproject/utils": "^0.2.2", - "chalk": "^2.3.0", - "glob": "^7.1.2", - "handlebars": "^4.0.11", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "to-snake-case": "^1.0.0", - "web3": "^0.20.0", - "yargs": "^10.0.3" - }, - "devDependencies": { - "@0xproject/tslint-config": "^0.4.4", - "@types/glob": "^5.0.33", - "@types/handlebars": "^4.0.36", - "@types/mkdirp": "^0.5.1", - "@types/node": "^8.0.53", - "@types/yargs": "^10.0.0", - "npm-run-all": "^4.1.2", - "shx": "^0.2.2", - "tslint": "5.8.0", - "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.6" - } + "name": "@0xproject/abi-gen", + "version": "0.1.4", + "description": "Generate contract wrappers from ABI and handlebars templates", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "lint": "tslint --project . 'src/**/*.ts'", + "clean": "shx rm -rf lib", + "build": "tsc" + }, + "bin": { + "abi-gen": "lib/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/abi-gen/README.md", + "dependencies": { + "@0xproject/utils": "^0.2.2", + "chalk": "^2.3.0", + "glob": "^7.1.2", + "handlebars": "^4.0.11", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "to-snake-case": "^1.0.0", + "web3": "^0.20.0", + "yargs": "^10.0.3" + }, + "devDependencies": { + "@0xproject/tslint-config": "^0.4.4", + "@types/glob": "^5.0.33", + "@types/handlebars": "^4.0.36", + "@types/mkdirp": "^0.5.1", + "@types/node": "^8.0.53", + "@types/yargs": "^10.0.0", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.8.0", + "typescript": "~2.6.1", + "web3-typescript-typings": "^0.9.6" + } } diff --git a/packages/abi-gen/src/globals.d.ts b/packages/abi-gen/src/globals.d.ts index 0b46f2c40..39df3f852 100644 --- a/packages/abi-gen/src/globals.d.ts +++ b/packages/abi-gen/src/globals.d.ts @@ -1,4 +1,4 @@ declare function toSnakeCase(str: string): string; declare module 'to-snake-case' { - export = toSnakeCase; + export = toSnakeCase; } diff --git a/packages/abi-gen/src/index.ts b/packages/abi-gen/src/index.ts index 15d218711..527af32b1 100644 --- a/packages/abi-gen/src/index.ts +++ b/packages/abi-gen/src/index.ts @@ -20,91 +20,91 @@ const ABI_TYPE_EVENT = 'event'; const MAIN_TEMPLATE_NAME = 'contract.mustache'; const args = yargs - .option('abiGlob', { - describe: 'Glob pattern to search for ABI JSON files', - type: 'string', - demand: true, - }) - .option('templates', { - describe: 'Folder where to search for templates', - type: 'string', - demand: true, - }) - .option('output', { - describe: 'Folder where to put the output files', - type: 'string', - demand: true, - }).argv; + .option('abiGlob', { + describe: 'Glob pattern to search for ABI JSON files', + type: 'string', + demand: true, + }) + .option('templates', { + describe: 'Folder where to search for templates', + type: 'string', + demand: true, + }) + .option('output', { + describe: 'Folder where to put the output files', + type: 'string', + demand: true, + }).argv; function writeOutputFile(name: string, renderedTsCode: string): void { - const fileName = toSnakeCase(name); - const filePath = `${args.output}/${fileName}.ts`; - fs.writeFileSync(filePath, renderedTsCode); - utils.log(`Created: ${chalk.bold(filePath)}`); + const fileName = toSnakeCase(name); + const filePath = `${args.output}/${fileName}.ts`; + fs.writeFileSync(filePath, renderedTsCode); + utils.log(`Created: ${chalk.bold(filePath)}`); } Handlebars.registerHelper('parameterType', utils.solTypeToTsType.bind(utils, ParamKind.Input)); Handlebars.registerHelper('returnType', utils.solTypeToTsType.bind(utils, ParamKind.Output)); const partialTemplateFileNames = globSync(`${args.templates}/partials/**/*.mustache`); for (const partialTemplateFileName of partialTemplateFileNames) { - const namedContent = utils.getNamedContent(partialTemplateFileName); - Handlebars.registerPartial(namedContent.name, namedContent.content); + const namedContent = utils.getNamedContent(partialTemplateFileName); + Handlebars.registerPartial(namedContent.name, namedContent.content); } const mainTemplate = utils.getNamedContent(`${args.templates}/${MAIN_TEMPLATE_NAME}`); const template = Handlebars.compile<ContextData>(mainTemplate.content); const abiFileNames = globSync(args.abiGlob); if (_.isEmpty(abiFileNames)) { - utils.log(`${chalk.red(`No ABI files found.`)}`); - utils.log(`Please make sure you've passed the correct folder name and that the files have + utils.log(`${chalk.red(`No ABI files found.`)}`); + utils.log(`Please make sure you've passed the correct folder name and that the files have ${chalk.bold('*.json')} extensions`); - process.exit(1); + process.exit(1); } else { - utils.log(`Found ${chalk.green(`${abiFileNames.length}`)} ${chalk.bold('ABI')} files`); - mkdirp.sync(args.output); + utils.log(`Found ${chalk.green(`${abiFileNames.length}`)} ${chalk.bold('ABI')} files`); + mkdirp.sync(args.output); } for (const abiFileName of abiFileNames) { - const namedContent = utils.getNamedContent(abiFileName); - utils.log(`Processing: ${chalk.bold(namedContent.name)}...`); - const parsedContent = JSON.parse(namedContent.content); - const ABI = _.isArray(parsedContent) - ? parsedContent // ABI file - : parsedContent.abi; // Truffle contracts file - if (_.isUndefined(ABI)) { - utils.log(`${chalk.red(`ABI not found in ${abiFileName}.`)}`); - utils.log(`Please make sure your ABI file is either an array with ABI entries or an object with the abi key`); - process.exit(1); - } + const namedContent = utils.getNamedContent(abiFileName); + utils.log(`Processing: ${chalk.bold(namedContent.name)}...`); + const parsedContent = JSON.parse(namedContent.content); + const ABI = _.isArray(parsedContent) + ? parsedContent // ABI file + : parsedContent.abi; // Truffle contracts file + if (_.isUndefined(ABI)) { + utils.log(`${chalk.red(`ABI not found in ${abiFileName}.`)}`); + utils.log(`Please make sure your ABI file is either an array with ABI entries or an object with the abi key`); + process.exit(1); + } - let ctor = ABI.find((abi: Web3.AbiDefinition) => abi.type === ABI_TYPE_CONSTRUCTOR) as Web3.ConstructorAbi; - if (_.isUndefined(ctor)) { - ctor = utils.getEmptyConstructor(); // The constructor exists, but it's implicit in JSON's ABI definition - } + let ctor = ABI.find((abi: Web3.AbiDefinition) => abi.type === ABI_TYPE_CONSTRUCTOR) as Web3.ConstructorAbi; + if (_.isUndefined(ctor)) { + ctor = utils.getEmptyConstructor(); // The constructor exists, but it's implicit in JSON's ABI definition + } - const methodAbis = ABI.filter((abi: Web3.AbiDefinition) => abi.type === ABI_TYPE_METHOD) as Web3.MethodAbi[]; - const methodsData = _.map(methodAbis, methodAbi => { - _.map(methodAbi.inputs, input => { - if (_.isEmpty(input.name)) { - // Auto-generated getters don't have parameter names - input.name = 'index'; - } - }); - // This will make templates simpler - const methodData = { - ...methodAbi, - singleReturnValue: methodAbi.outputs.length === 1, - }; - return methodData; - }); + const methodAbis = ABI.filter((abi: Web3.AbiDefinition) => abi.type === ABI_TYPE_METHOD) as Web3.MethodAbi[]; + const methodsData = _.map(methodAbis, methodAbi => { + _.map(methodAbi.inputs, input => { + if (_.isEmpty(input.name)) { + // Auto-generated getters don't have parameter names + input.name = 'index'; + } + }); + // This will make templates simpler + const methodData = { + ...methodAbi, + singleReturnValue: methodAbi.outputs.length === 1, + }; + return methodData; + }); - const eventAbis = ABI.filter((abi: Web3.AbiDefinition) => abi.type === ABI_TYPE_EVENT) as Web3.EventAbi[]; + const eventAbis = ABI.filter((abi: Web3.AbiDefinition) => abi.type === ABI_TYPE_EVENT) as Web3.EventAbi[]; - const contextData = { - contractName: namedContent.name, - ctor, - methods: methodsData, - events: eventAbis, - }; - const renderedTsCode = template(contextData); - writeOutputFile(namedContent.name, renderedTsCode); + const contextData = { + contractName: namedContent.name, + ctor, + methods: methodsData, + events: eventAbis, + }; + const renderedTsCode = template(contextData); + writeOutputFile(namedContent.name, renderedTsCode); } diff --git a/packages/abi-gen/src/types.ts b/packages/abi-gen/src/types.ts index 413f94467..e82ab824b 100644 --- a/packages/abi-gen/src/types.ts +++ b/packages/abi-gen/src/types.ts @@ -1,23 +1,23 @@ import * as Web3 from 'web3'; export enum ParamKind { - Input = 'input', - Output = 'output', + Input = 'input', + Output = 'output', } export enum AbiType { - Function = 'function', - Constructor = 'constructor', - Event = 'event', - Fallback = 'fallback', + Function = 'function', + Constructor = 'constructor', + Event = 'event', + Fallback = 'fallback', } export interface Method extends Web3.MethodAbi { - singleReturnValue: boolean; + singleReturnValue: boolean; } export interface ContextData { - contractName: string; - methods: Method[]; - events: Web3.EventAbi[]; + contractName: string; + methods: Method[]; + events: Web3.EventAbi[]; } diff --git a/packages/abi-gen/src/utils.ts b/packages/abi-gen/src/utils.ts index 4e1216a77..14255643a 100644 --- a/packages/abi-gen/src/utils.ts +++ b/packages/abi-gen/src/utils.ts @@ -6,68 +6,68 @@ import * as Web3 from 'web3'; import { AbiType, ParamKind } from './types'; export const utils = { - solTypeToTsType(paramKind: ParamKind, solType: string): string { - const trailingArrayRegex = /\[\d*\]$/; - if (solType.match(trailingArrayRegex)) { - const arrayItemSolType = solType.replace(trailingArrayRegex, ''); - const arrayItemTsType = utils.solTypeToTsType(paramKind, arrayItemSolType); - const arrayTsType = utils.isUnionType(arrayItemTsType) - ? `Array<${arrayItemTsType}>` - : `${arrayItemTsType}[]`; - return arrayTsType; - } else { - const solTypeRegexToTsType = [ - { regex: '^string$', tsType: 'string' }, - { regex: '^address$', tsType: 'string' }, - { regex: '^bool$', tsType: 'boolean' }, - { regex: '^u?int\\d*$', tsType: 'BigNumber' }, - { regex: '^bytes\\d*$', tsType: 'string' }, - ]; - if (paramKind === ParamKind.Input) { - // web3 allows to pass those an non-bignumbers and that's nice - // but it always returns stuff as BigNumbers - solTypeRegexToTsType.unshift({ - regex: '^u?int(8|16|32)?$', - tsType: 'number|BigNumber', - }); - } - for (const regexAndTxType of solTypeRegexToTsType) { - const { regex, tsType } = regexAndTxType; - if (solType.match(regex)) { - return tsType; - } - } - throw new Error(`Unknown Solidity type found: ${solType}`); - } - }, - isUnionType(tsType: string): boolean { - return tsType === 'number|BigNumber'; - }, - log(...args: any[]): void { - console.log(...args); // tslint:disable-line:no-console - }, - getPartialNameFromFileName(filename: string): string { - const name = path.parse(filename).name; - return name; - }, - getNamedContent(filename: string): { name: string; content: string } { - const name = utils.getPartialNameFromFileName(filename); - try { - const content = fs.readFileSync(filename).toString(); - return { - name, - content, - }; - } catch (err) { - throw new Error(`Failed to read ${filename}: ${err}`); - } - }, - getEmptyConstructor(): Web3.ConstructorAbi { - return { - type: AbiType.Constructor, - stateMutability: 'nonpayable', - payable: false, - inputs: [], - }; - }, + solTypeToTsType(paramKind: ParamKind, solType: string): string { + const trailingArrayRegex = /\[\d*\]$/; + if (solType.match(trailingArrayRegex)) { + const arrayItemSolType = solType.replace(trailingArrayRegex, ''); + const arrayItemTsType = utils.solTypeToTsType(paramKind, arrayItemSolType); + const arrayTsType = utils.isUnionType(arrayItemTsType) + ? `Array<${arrayItemTsType}>` + : `${arrayItemTsType}[]`; + return arrayTsType; + } else { + const solTypeRegexToTsType = [ + { regex: '^string$', tsType: 'string' }, + { regex: '^address$', tsType: 'string' }, + { regex: '^bool$', tsType: 'boolean' }, + { regex: '^u?int\\d*$', tsType: 'BigNumber' }, + { regex: '^bytes\\d*$', tsType: 'string' }, + ]; + if (paramKind === ParamKind.Input) { + // web3 allows to pass those an non-bignumbers and that's nice + // but it always returns stuff as BigNumbers + solTypeRegexToTsType.unshift({ + regex: '^u?int(8|16|32)?$', + tsType: 'number|BigNumber', + }); + } + for (const regexAndTxType of solTypeRegexToTsType) { + const { regex, tsType } = regexAndTxType; + if (solType.match(regex)) { + return tsType; + } + } + throw new Error(`Unknown Solidity type found: ${solType}`); + } + }, + isUnionType(tsType: string): boolean { + return tsType === 'number|BigNumber'; + }, + log(...args: any[]): void { + console.log(...args); // tslint:disable-line:no-console + }, + getPartialNameFromFileName(filename: string): string { + const name = path.parse(filename).name; + return name; + }, + getNamedContent(filename: string): { name: string; content: string } { + const name = utils.getPartialNameFromFileName(filename); + try { + const content = fs.readFileSync(filename).toString(); + return { + name, + content, + }; + } catch (err) { + throw new Error(`Failed to read ${filename}: ${err}`); + } + }, + getEmptyConstructor(): Web3.ConstructorAbi { + return { + type: AbiType.Constructor, + stateMutability: 'nonpayable', + payable: false, + inputs: [], + }; + }, }; diff --git a/packages/abi-gen/tsconfig.json b/packages/abi-gen/tsconfig.json index 1f89fb820..5e0c7c6d3 100644 --- a/packages/abi-gen/tsconfig.json +++ b/packages/abi-gen/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./src/**/*", "./test/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*", "./test/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] } diff --git a/packages/abi-gen/tslint.json b/packages/abi-gen/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/abi-gen/tslint.json +++ b/packages/abi-gen/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/assert/package.json b/packages/assert/package.json index 0e10b3384..a2d195728 100644 --- a/packages/assert/package.json +++ b/packages/assert/package.json @@ -1,45 +1,45 @@ { - "name": "@0xproject/assert", - "version": "0.0.13", - "description": "Provides a standard way of performing type and schema validation across 0x projects", - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", - "scripts": { - "build": "tsc", - "clean": "shx rm -rf _bundles lib test_temp", - "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", - "run_mocha": "mocha lib/test/**/*_test.js", - "prepublishOnly": "run-p build", - "test": "run-s clean build run_mocha", - "test:circleci": "yarn test" - }, - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/assert/README.md", - "devDependencies": { - "@0xproject/tslint-config": "^0.4.4", - "@types/lodash": "^4.14.86", - "@types/mocha": "^2.2.42", - "@types/valid-url": "^1.0.2", - "chai": "^4.0.1", - "chai-typescript-typings": "^0.0.2", - "dirty-chai": "^2.0.1", - "mocha": "^4.0.1", - "npm-run-all": "^4.1.2", - "shx": "^0.2.2", - "tslint": "5.8.0", - "typescript": "~2.6.1" - }, - "dependencies": { - "@0xproject/json-schemas": "^0.7.5", - "@0xproject/utils": "^0.2.2", - "lodash": "^4.17.4", - "valid-url": "^1.0.9" - } + "name": "@0xproject/assert", + "version": "0.0.13", + "description": "Provides a standard way of performing type and schema validation across 0x projects", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "build": "tsc", + "clean": "shx rm -rf _bundles lib test_temp", + "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", + "run_mocha": "mocha lib/test/**/*_test.js", + "prepublishOnly": "run-p build", + "test": "run-s clean build run_mocha", + "test:circleci": "yarn test" + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/assert/README.md", + "devDependencies": { + "@0xproject/tslint-config": "^0.4.4", + "@types/lodash": "^4.14.86", + "@types/mocha": "^2.2.42", + "@types/valid-url": "^1.0.2", + "chai": "^4.0.1", + "chai-typescript-typings": "^0.0.2", + "dirty-chai": "^2.0.1", + "mocha": "^4.0.1", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.8.0", + "typescript": "~2.6.1" + }, + "dependencies": { + "@0xproject/json-schemas": "^0.7.5", + "@0xproject/utils": "^0.2.2", + "lodash": "^4.17.4", + "valid-url": "^1.0.9" + } } diff --git a/packages/assert/src/index.ts b/packages/assert/src/index.ts index 0578bd303..7ad574ec7 100644 --- a/packages/assert/src/index.ts +++ b/packages/assert/src/index.ts @@ -6,89 +6,89 @@ import * as validUrl from 'valid-url'; const HEX_REGEX = /^0x[0-9A-F]*$/i; export const assert = { - isBigNumber(variableName: string, value: BigNumber): void { - const isBigNumber = _.isObject(value) && (value as any).isBigNumber; - this.assert(isBigNumber, this.typeAssertionMessage(variableName, 'BigNumber', value)); - }, - isValidBaseUnitAmount(variableName: string, value: BigNumber) { - assert.isBigNumber(variableName, value); - const isNegative = value.lessThan(0); - this.assert(!isNegative, `${variableName} cannot be a negative number, found value: ${value.toNumber()}`); - const hasDecimals = value.decimalPlaces() !== 0; - this.assert( - !hasDecimals, - `${variableName} should be in baseUnits (no decimals), found value: ${value.toNumber()}`, - ); - }, - isString(variableName: string, value: string): void { - this.assert(_.isString(value), this.typeAssertionMessage(variableName, 'string', value)); - }, - isFunction(variableName: string, value: any): void { - this.assert(_.isFunction(value), this.typeAssertionMessage(variableName, 'function', value)); - }, - isHexString(variableName: string, value: string): void { - this.assert( - _.isString(value) && HEX_REGEX.test(value), - this.typeAssertionMessage(variableName, 'HexString', value), - ); - }, - isETHAddressHex(variableName: string, value: string): void { - this.assert(addressUtils.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value)); - this.assert( - addressUtils.isAddress(value) && value.toLowerCase() === value, - `Checksummed addresses are not supported. Convert ${variableName} to lower case before passing`, - ); - }, - doesBelongToStringEnum( - variableName: string, - value: string, - stringEnum: any /* There is no base type for every string enum */, - ): void { - const doesBelongToStringEnum = !_.isUndefined(stringEnum[value]); - const enumValues = _.keys(stringEnum); - const enumValuesAsStrings = _.map(enumValues, enumValue => `'${enumValue}'`); - const enumValuesAsString = enumValuesAsStrings.join(', '); - assert.assert( - doesBelongToStringEnum, - `Expected ${variableName} to be one of: ${enumValuesAsString}, encountered: ${value}`, - ); - }, - hasAtMostOneUniqueValue(value: any[], errMsg: string): void { - this.assert(_.uniq(value).length <= 1, errMsg); - }, - isNumber(variableName: string, value: number): void { - this.assert(_.isFinite(value), this.typeAssertionMessage(variableName, 'number', value)); - }, - isBoolean(variableName: string, value: boolean): void { - this.assert(_.isBoolean(value), this.typeAssertionMessage(variableName, 'boolean', value)); - }, - isWeb3Provider(variableName: string, value: any): void { - const isWeb3Provider = _.isFunction(value.send) || _.isFunction(value.sendAsync); - this.assert(isWeb3Provider, this.typeAssertionMessage(variableName, 'Web3.Provider', value)); - }, - doesConformToSchema(variableName: string, value: any, schema: Schema): void { - const schemaValidator = new SchemaValidator(); - const validationResult = schemaValidator.validate(value, schema); - const hasValidationErrors = validationResult.errors.length > 0; - const msg = `Expected ${variableName} to conform to schema ${schema.id} + isBigNumber(variableName: string, value: BigNumber): void { + const isBigNumber = _.isObject(value) && (value as any).isBigNumber; + this.assert(isBigNumber, this.typeAssertionMessage(variableName, 'BigNumber', value)); + }, + isValidBaseUnitAmount(variableName: string, value: BigNumber) { + assert.isBigNumber(variableName, value); + const isNegative = value.lessThan(0); + this.assert(!isNegative, `${variableName} cannot be a negative number, found value: ${value.toNumber()}`); + const hasDecimals = value.decimalPlaces() !== 0; + this.assert( + !hasDecimals, + `${variableName} should be in baseUnits (no decimals), found value: ${value.toNumber()}`, + ); + }, + isString(variableName: string, value: string): void { + this.assert(_.isString(value), this.typeAssertionMessage(variableName, 'string', value)); + }, + isFunction(variableName: string, value: any): void { + this.assert(_.isFunction(value), this.typeAssertionMessage(variableName, 'function', value)); + }, + isHexString(variableName: string, value: string): void { + this.assert( + _.isString(value) && HEX_REGEX.test(value), + this.typeAssertionMessage(variableName, 'HexString', value), + ); + }, + isETHAddressHex(variableName: string, value: string): void { + this.assert(addressUtils.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value)); + this.assert( + addressUtils.isAddress(value) && value.toLowerCase() === value, + `Checksummed addresses are not supported. Convert ${variableName} to lower case before passing`, + ); + }, + doesBelongToStringEnum( + variableName: string, + value: string, + stringEnum: any /* There is no base type for every string enum */, + ): void { + const doesBelongToStringEnum = !_.isUndefined(stringEnum[value]); + const enumValues = _.keys(stringEnum); + const enumValuesAsStrings = _.map(enumValues, enumValue => `'${enumValue}'`); + const enumValuesAsString = enumValuesAsStrings.join(', '); + assert.assert( + doesBelongToStringEnum, + `Expected ${variableName} to be one of: ${enumValuesAsString}, encountered: ${value}`, + ); + }, + hasAtMostOneUniqueValue(value: any[], errMsg: string): void { + this.assert(_.uniq(value).length <= 1, errMsg); + }, + isNumber(variableName: string, value: number): void { + this.assert(_.isFinite(value), this.typeAssertionMessage(variableName, 'number', value)); + }, + isBoolean(variableName: string, value: boolean): void { + this.assert(_.isBoolean(value), this.typeAssertionMessage(variableName, 'boolean', value)); + }, + isWeb3Provider(variableName: string, value: any): void { + const isWeb3Provider = _.isFunction(value.send) || _.isFunction(value.sendAsync); + this.assert(isWeb3Provider, this.typeAssertionMessage(variableName, 'Web3.Provider', value)); + }, + doesConformToSchema(variableName: string, value: any, schema: Schema): void { + const schemaValidator = new SchemaValidator(); + const validationResult = schemaValidator.validate(value, schema); + const hasValidationErrors = validationResult.errors.length > 0; + const msg = `Expected ${variableName} to conform to schema ${schema.id} Encountered: ${JSON.stringify(value, null, '\t')} Validation errors: ${validationResult.errors.join(', ')}`; - this.assert(!hasValidationErrors, msg); - }, - isHttpUrl(variableName: string, value: any): void { - const isValidUrl = !_.isUndefined(validUrl.isWebUri(value)); - this.assert(isValidUrl, this.typeAssertionMessage(variableName, 'http url', value)); - }, - isUri(variableName: string, value: any): void { - const isValidUri = !_.isUndefined(validUrl.isUri(value)); - this.assert(isValidUri, this.typeAssertionMessage(variableName, 'uri', value)); - }, - assert(condition: boolean, message: string): void { - if (!condition) { - throw new Error(message); - } - }, - typeAssertionMessage(variableName: string, type: string, value: any): string { - return `Expected ${variableName} to be of type ${type}, encountered: ${value}`; - }, + this.assert(!hasValidationErrors, msg); + }, + isHttpUrl(variableName: string, value: any): void { + const isValidUrl = !_.isUndefined(validUrl.isWebUri(value)); + this.assert(isValidUrl, this.typeAssertionMessage(variableName, 'http url', value)); + }, + isUri(variableName: string, value: any): void { + const isValidUri = !_.isUndefined(validUrl.isUri(value)); + this.assert(isValidUri, this.typeAssertionMessage(variableName, 'uri', value)); + }, + assert(condition: boolean, message: string): void { + if (!condition) { + throw new Error(message); + } + }, + typeAssertionMessage(variableName: string, type: string, value: any): string { + return `Expected ${variableName} to be of type ${type}, encountered: ${value}`; + }, }; diff --git a/packages/assert/test/assert_test.ts b/packages/assert/test/assert_test.ts index 0af493bda..b0fa398d6 100644 --- a/packages/assert/test/assert_test.ts +++ b/packages/assert/test/assert_test.ts @@ -11,243 +11,243 @@ chai.use(dirtyChai); const expect = chai.expect; describe('Assertions', () => { - const variableName = 'variable'; - describe('#isBigNumber', () => { - it('should not throw for valid input', () => { - const validInputs = [new BigNumber(23), new BigNumber('45')]; - validInputs.forEach(input => expect(assert.isBigNumber.bind(assert, variableName, input)).to.not.throw()); - }); - it('should throw for invalid input', () => { - const invalidInputs = ['test', 42, false, { random: 'test' }, undefined]; - invalidInputs.forEach(input => expect(assert.isBigNumber.bind(assert, variableName, input)).to.throw()); - }); - }); - describe('#isValidBaseUnitAmount', () => { - it('should not throw for valid input', () => { - const validInputs = [new BigNumber(23), new BigNumber('45000000')]; - validInputs.forEach(input => - expect(assert.isValidBaseUnitAmount.bind(assert, variableName, input)).to.not.throw(), - ); - }); - it('should throw for invalid input', () => { - const invalidInputs = [0, undefined, new BigNumber(3.145), 3.145, new BigNumber(-400)]; - invalidInputs.forEach(input => - expect(assert.isValidBaseUnitAmount.bind(assert, variableName, input)).to.throw(), - ); - }); - }); - describe('#isString', () => { - it('should not throw for valid input', () => { - const validInputs = ['hello', 'goodbye']; - validInputs.forEach(input => expect(assert.isString.bind(assert, variableName, input)).to.not.throw()); - }); - it('should throw for invalid input', () => { - const invalidInputs = [42, false, { random: 'test' }, undefined, new BigNumber(45)]; - invalidInputs.forEach(input => expect(assert.isString.bind(assert, variableName, input)).to.throw()); - }); - }); - describe('#isFunction', () => { - it('should not throw for valid input', () => { - const validInputs = [BigNumber, assert.isString]; - validInputs.forEach(input => expect(assert.isFunction.bind(assert, variableName, input)).to.not.throw()); - }); - it('should throw for invalid input', () => { - const invalidInputs = [42, false, { random: 'test' }, undefined, new BigNumber(45)]; - invalidInputs.forEach(input => expect(assert.isFunction.bind(assert, variableName, input)).to.throw()); - }); - }); - describe('#isHexString', () => { - it('should not throw for valid input', () => { - const validInputs = [ - '0x61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33', - '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - ]; - validInputs.forEach(input => expect(assert.isHexString.bind(assert, variableName, input)).to.not.throw()); - }); - it('should throw for invalid input', () => { - const invalidInputs = [ - 42, - false, - { random: 'test' }, - undefined, - new BigNumber(45), - '0x61a3ed31B43c8780e905a260a35faYfEc527be7516aa11c0256729b5b351bc33', - ]; - invalidInputs.forEach(input => expect(assert.isHexString.bind(assert, variableName, input)).to.throw()); - }); - }); - describe('#isETHAddressHex', () => { - it('should not throw for valid input', () => { - const validInputs = [ - '0x0000000000000000000000000000000000000000', - '0x6fffd0ae3f7d88c9b4925323f54c6e4b2918c5fd', - '0x12459c951127e0c374ff9105dda097662a027093', - ]; - validInputs.forEach(input => - expect(assert.isETHAddressHex.bind(assert, variableName, input)).to.not.throw(), - ); - }); - it('should throw for invalid input', () => { - const invalidInputs = [ - 42, - false, - { random: 'test' }, - undefined, - new BigNumber(45), - '0x6FFFd0ae3f7d88c9b4925323f54c6e4b2918c5fd', - '0x6FFFd0ae3f7d88c9b4925323f54c6e4', - ]; - invalidInputs.forEach(input => expect(assert.isETHAddressHex.bind(assert, variableName, input)).to.throw()); - }); - }); - describe('#doesBelongToStringEnum', () => { - enum TestEnums { - Test1 = 'Test1', - Test2 = 'Test2', - } - it('should not throw for valid input', () => { - const validInputs = [TestEnums.Test1, TestEnums.Test2]; - validInputs.forEach(input => - expect(assert.doesBelongToStringEnum.bind(assert, variableName, input, TestEnums)).to.not.throw(), - ); - }); - it('should throw for invalid input', () => { - const invalidInputs = [42, false, { random: 'test' }, undefined, new BigNumber(45)]; - invalidInputs.forEach(input => - expect(assert.doesBelongToStringEnum.bind(assert, variableName, input, TestEnums)).to.throw(), - ); - }); - }); - describe('#hasAtMostOneUniqueValue', () => { - const errorMsg = 'more than one unique value'; - it('should not throw for valid input', () => { - const validInputs = [['hello'], ['goodbye', 'goodbye', 'goodbye']]; - validInputs.forEach(input => - expect(assert.hasAtMostOneUniqueValue.bind(assert, input, errorMsg)).to.not.throw(), - ); - }); - it('should throw for invalid input', () => { - const invalidInputs = [['hello', 'goodbye'], ['goodbye', 42, false, false]]; - invalidInputs.forEach(input => - expect(assert.hasAtMostOneUniqueValue.bind(assert, input, errorMsg)).to.throw(), - ); - }); - }); - describe('#isNumber', () => { - it('should not throw for valid input', () => { - const validInputs = [42, 0, 21e42]; - validInputs.forEach(input => expect(assert.isNumber.bind(assert, variableName, input)).to.not.throw()); - }); - it('should throw for invalid input', () => { - const invalidInputs = [false, { random: 'test' }, undefined, new BigNumber(45)]; - invalidInputs.forEach(input => expect(assert.isNumber.bind(assert, variableName, input)).to.throw()); - }); - }); - describe('#isBoolean', () => { - it('should not throw for valid input', () => { - const validInputs = [true, false]; - validInputs.forEach(input => expect(assert.isBoolean.bind(assert, variableName, input)).to.not.throw()); - }); - it('should throw for invalid input', () => { - const invalidInputs = [42, { random: 'test' }, undefined, new BigNumber(45)]; - invalidInputs.forEach(input => expect(assert.isBoolean.bind(assert, variableName, input)).to.throw()); - }); - }); - describe('#isWeb3Provider', () => { - it('should not throw for valid input', () => { - const validInputs = [{ send: () => 45 }, { sendAsync: () => 45 }]; - validInputs.forEach(input => - expect(assert.isWeb3Provider.bind(assert, variableName, input)).to.not.throw(), - ); - }); - it('should throw for invalid input', () => { - const invalidInputs = [42, { random: 'test' }, undefined, new BigNumber(45)]; - invalidInputs.forEach(input => expect(assert.isWeb3Provider.bind(assert, variableName, input)).to.throw()); - }); - }); - describe('#doesConformToSchema', () => { - const schema = schemas.addressSchema; - it('should not throw for valid input', () => { - const validInputs = [ - '0x6fffd0ae3f7d88c9b4925323f54c6e4b2918c5fd', - '0x12459c951127e0c374ff9105dda097662a027093', - ]; - validInputs.forEach(input => - expect(assert.doesConformToSchema.bind(assert, variableName, input, schema)).to.not.throw(), - ); - }); - it('should throw for invalid input', () => { - const invalidInputs = [42, { random: 'test' }, undefined, new BigNumber(45)]; - invalidInputs.forEach(input => - expect(assert.doesConformToSchema.bind(assert, variableName, input, schema)).to.throw(), - ); - }); - }); - describe('#isHttpUrl', () => { - it('should not throw for valid input', () => { - const validInputs = [ - 'http://www.google.com', - 'https://api.example-relayer.net', - 'https://api.radarrelay.com/0x/v0/', - 'https://zeroex.beta.radarrelay.com:8000/0x/v0/', - ]; - validInputs.forEach(input => expect(assert.isHttpUrl.bind(assert, variableName, input)).to.not.throw()); - }); - it('should throw for invalid input', () => { - const invalidInputs = [ - 42, - { random: 'test' }, - undefined, - new BigNumber(45), - 'ws://www.api.example-relayer.net', - 'www.google.com', - 'api.example-relayer.net', - 'user:password@api.example-relayer.net', - '//api.example-relayer.net', - ]; - invalidInputs.forEach(input => expect(assert.isHttpUrl.bind(assert, variableName, input)).to.throw()); - }); - }); - describe('#isUri', () => { - it('should not throw for valid input', () => { - const validInputs = [ - 'http://www.google.com', - 'https://api.example-relayer.net', - 'https://api.radarrelay.com/0x/v0/', - 'https://zeroex.beta.radarrelay.com:8000/0x/v0/', - 'ws://www.api.example-relayer.net', - 'wss://www.api.example-relayer.net', - 'user:password@api.example-relayer.net', - ]; - validInputs.forEach(input => expect(assert.isUri.bind(assert, variableName, input)).to.not.throw()); - }); - it('should throw for invalid input', () => { - const invalidInputs = [ - 42, - { random: 'test' }, - undefined, - new BigNumber(45), - 'www.google.com', - 'api.example-relayer.net', - '//api.example-relayer.net', - ]; - invalidInputs.forEach(input => expect(assert.isUri.bind(assert, variableName, input)).to.throw()); - }); - }); - describe('#assert', () => { - const assertMessage = 'assert not satisfied'; - it('should not throw for valid input', () => { - expect(assert.assert.bind(assert, true, assertMessage)).to.not.throw(); - }); - it('should throw for invalid input', () => { - expect(assert.assert.bind(assert, false, assertMessage)).to.throw(); - }); - }); - describe('#typeAssertionMessage', () => { - it('should render correct message', () => { - expect(assert.typeAssertionMessage('variable', 'string', 'number')).to.equal( - `Expected variable to be of type string, encountered: number`, - ); - }); - }); + const variableName = 'variable'; + describe('#isBigNumber', () => { + it('should not throw for valid input', () => { + const validInputs = [new BigNumber(23), new BigNumber('45')]; + validInputs.forEach(input => expect(assert.isBigNumber.bind(assert, variableName, input)).to.not.throw()); + }); + it('should throw for invalid input', () => { + const invalidInputs = ['test', 42, false, { random: 'test' }, undefined]; + invalidInputs.forEach(input => expect(assert.isBigNumber.bind(assert, variableName, input)).to.throw()); + }); + }); + describe('#isValidBaseUnitAmount', () => { + it('should not throw for valid input', () => { + const validInputs = [new BigNumber(23), new BigNumber('45000000')]; + validInputs.forEach(input => + expect(assert.isValidBaseUnitAmount.bind(assert, variableName, input)).to.not.throw(), + ); + }); + it('should throw for invalid input', () => { + const invalidInputs = [0, undefined, new BigNumber(3.145), 3.145, new BigNumber(-400)]; + invalidInputs.forEach(input => + expect(assert.isValidBaseUnitAmount.bind(assert, variableName, input)).to.throw(), + ); + }); + }); + describe('#isString', () => { + it('should not throw for valid input', () => { + const validInputs = ['hello', 'goodbye']; + validInputs.forEach(input => expect(assert.isString.bind(assert, variableName, input)).to.not.throw()); + }); + it('should throw for invalid input', () => { + const invalidInputs = [42, false, { random: 'test' }, undefined, new BigNumber(45)]; + invalidInputs.forEach(input => expect(assert.isString.bind(assert, variableName, input)).to.throw()); + }); + }); + describe('#isFunction', () => { + it('should not throw for valid input', () => { + const validInputs = [BigNumber, assert.isString]; + validInputs.forEach(input => expect(assert.isFunction.bind(assert, variableName, input)).to.not.throw()); + }); + it('should throw for invalid input', () => { + const invalidInputs = [42, false, { random: 'test' }, undefined, new BigNumber(45)]; + invalidInputs.forEach(input => expect(assert.isFunction.bind(assert, variableName, input)).to.throw()); + }); + }); + describe('#isHexString', () => { + it('should not throw for valid input', () => { + const validInputs = [ + '0x61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33', + '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + ]; + validInputs.forEach(input => expect(assert.isHexString.bind(assert, variableName, input)).to.not.throw()); + }); + it('should throw for invalid input', () => { + const invalidInputs = [ + 42, + false, + { random: 'test' }, + undefined, + new BigNumber(45), + '0x61a3ed31B43c8780e905a260a35faYfEc527be7516aa11c0256729b5b351bc33', + ]; + invalidInputs.forEach(input => expect(assert.isHexString.bind(assert, variableName, input)).to.throw()); + }); + }); + describe('#isETHAddressHex', () => { + it('should not throw for valid input', () => { + const validInputs = [ + '0x0000000000000000000000000000000000000000', + '0x6fffd0ae3f7d88c9b4925323f54c6e4b2918c5fd', + '0x12459c951127e0c374ff9105dda097662a027093', + ]; + validInputs.forEach(input => + expect(assert.isETHAddressHex.bind(assert, variableName, input)).to.not.throw(), + ); + }); + it('should throw for invalid input', () => { + const invalidInputs = [ + 42, + false, + { random: 'test' }, + undefined, + new BigNumber(45), + '0x6FFFd0ae3f7d88c9b4925323f54c6e4b2918c5fd', + '0x6FFFd0ae3f7d88c9b4925323f54c6e4', + ]; + invalidInputs.forEach(input => expect(assert.isETHAddressHex.bind(assert, variableName, input)).to.throw()); + }); + }); + describe('#doesBelongToStringEnum', () => { + enum TestEnums { + Test1 = 'Test1', + Test2 = 'Test2', + } + it('should not throw for valid input', () => { + const validInputs = [TestEnums.Test1, TestEnums.Test2]; + validInputs.forEach(input => + expect(assert.doesBelongToStringEnum.bind(assert, variableName, input, TestEnums)).to.not.throw(), + ); + }); + it('should throw for invalid input', () => { + const invalidInputs = [42, false, { random: 'test' }, undefined, new BigNumber(45)]; + invalidInputs.forEach(input => + expect(assert.doesBelongToStringEnum.bind(assert, variableName, input, TestEnums)).to.throw(), + ); + }); + }); + describe('#hasAtMostOneUniqueValue', () => { + const errorMsg = 'more than one unique value'; + it('should not throw for valid input', () => { + const validInputs = [['hello'], ['goodbye', 'goodbye', 'goodbye']]; + validInputs.forEach(input => + expect(assert.hasAtMostOneUniqueValue.bind(assert, input, errorMsg)).to.not.throw(), + ); + }); + it('should throw for invalid input', () => { + const invalidInputs = [['hello', 'goodbye'], ['goodbye', 42, false, false]]; + invalidInputs.forEach(input => + expect(assert.hasAtMostOneUniqueValue.bind(assert, input, errorMsg)).to.throw(), + ); + }); + }); + describe('#isNumber', () => { + it('should not throw for valid input', () => { + const validInputs = [42, 0, 21e42]; + validInputs.forEach(input => expect(assert.isNumber.bind(assert, variableName, input)).to.not.throw()); + }); + it('should throw for invalid input', () => { + const invalidInputs = [false, { random: 'test' }, undefined, new BigNumber(45)]; + invalidInputs.forEach(input => expect(assert.isNumber.bind(assert, variableName, input)).to.throw()); + }); + }); + describe('#isBoolean', () => { + it('should not throw for valid input', () => { + const validInputs = [true, false]; + validInputs.forEach(input => expect(assert.isBoolean.bind(assert, variableName, input)).to.not.throw()); + }); + it('should throw for invalid input', () => { + const invalidInputs = [42, { random: 'test' }, undefined, new BigNumber(45)]; + invalidInputs.forEach(input => expect(assert.isBoolean.bind(assert, variableName, input)).to.throw()); + }); + }); + describe('#isWeb3Provider', () => { + it('should not throw for valid input', () => { + const validInputs = [{ send: () => 45 }, { sendAsync: () => 45 }]; + validInputs.forEach(input => + expect(assert.isWeb3Provider.bind(assert, variableName, input)).to.not.throw(), + ); + }); + it('should throw for invalid input', () => { + const invalidInputs = [42, { random: 'test' }, undefined, new BigNumber(45)]; + invalidInputs.forEach(input => expect(assert.isWeb3Provider.bind(assert, variableName, input)).to.throw()); + }); + }); + describe('#doesConformToSchema', () => { + const schema = schemas.addressSchema; + it('should not throw for valid input', () => { + const validInputs = [ + '0x6fffd0ae3f7d88c9b4925323f54c6e4b2918c5fd', + '0x12459c951127e0c374ff9105dda097662a027093', + ]; + validInputs.forEach(input => + expect(assert.doesConformToSchema.bind(assert, variableName, input, schema)).to.not.throw(), + ); + }); + it('should throw for invalid input', () => { + const invalidInputs = [42, { random: 'test' }, undefined, new BigNumber(45)]; + invalidInputs.forEach(input => + expect(assert.doesConformToSchema.bind(assert, variableName, input, schema)).to.throw(), + ); + }); + }); + describe('#isHttpUrl', () => { + it('should not throw for valid input', () => { + const validInputs = [ + 'http://www.google.com', + 'https://api.example-relayer.net', + 'https://api.radarrelay.com/0x/v0/', + 'https://zeroex.beta.radarrelay.com:8000/0x/v0/', + ]; + validInputs.forEach(input => expect(assert.isHttpUrl.bind(assert, variableName, input)).to.not.throw()); + }); + it('should throw for invalid input', () => { + const invalidInputs = [ + 42, + { random: 'test' }, + undefined, + new BigNumber(45), + 'ws://www.api.example-relayer.net', + 'www.google.com', + 'api.example-relayer.net', + 'user:password@api.example-relayer.net', + '//api.example-relayer.net', + ]; + invalidInputs.forEach(input => expect(assert.isHttpUrl.bind(assert, variableName, input)).to.throw()); + }); + }); + describe('#isUri', () => { + it('should not throw for valid input', () => { + const validInputs = [ + 'http://www.google.com', + 'https://api.example-relayer.net', + 'https://api.radarrelay.com/0x/v0/', + 'https://zeroex.beta.radarrelay.com:8000/0x/v0/', + 'ws://www.api.example-relayer.net', + 'wss://www.api.example-relayer.net', + 'user:password@api.example-relayer.net', + ]; + validInputs.forEach(input => expect(assert.isUri.bind(assert, variableName, input)).to.not.throw()); + }); + it('should throw for invalid input', () => { + const invalidInputs = [ + 42, + { random: 'test' }, + undefined, + new BigNumber(45), + 'www.google.com', + 'api.example-relayer.net', + '//api.example-relayer.net', + ]; + invalidInputs.forEach(input => expect(assert.isUri.bind(assert, variableName, input)).to.throw()); + }); + }); + describe('#assert', () => { + const assertMessage = 'assert not satisfied'; + it('should not throw for valid input', () => { + expect(assert.assert.bind(assert, true, assertMessage)).to.not.throw(); + }); + it('should throw for invalid input', () => { + expect(assert.assert.bind(assert, false, assertMessage)).to.throw(); + }); + }); + describe('#typeAssertionMessage', () => { + it('should render correct message', () => { + expect(assert.typeAssertionMessage('variable', 'string', 'number')).to.equal( + `Expected variable to be of type string, encountered: number`, + ); + }); + }); }); diff --git a/packages/assert/tsconfig.json b/packages/assert/tsconfig.json index 8314a9459..88a467ccb 100644 --- a/packages/assert/tsconfig.json +++ b/packages/assert/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./src/**/*", "./test/**/*", "../../node_modules/chai-typescript-typings/index.d.ts"] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*", "./test/**/*", "../../node_modules/chai-typescript-typings/index.d.ts"] } diff --git a/packages/assert/tslint.json b/packages/assert/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/assert/tslint.json +++ b/packages/assert/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/chai-as-promised-typescript-typings/index.d.ts b/packages/chai-as-promised-typescript-typings/index.d.ts index 389a076c6..ba6dabdcc 100644 --- a/packages/chai-as-promised-typescript-typings/index.d.ts +++ b/packages/chai-as-promised-typescript-typings/index.d.ts @@ -4,265 +4,265 @@ // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped declare module 'chai-as-promised' { - function chaiAsPromised(chai: any, utils: any): void; - namespace chaiAsPromised { + function chaiAsPromised(chai: any, utils: any): void; + namespace chaiAsPromised { - } - export = chaiAsPromised; + } + export = chaiAsPromised; } // tslint:disable:no-namespace ban-types member-ordering declare namespace Chai { - // For BDD API - interface Assertion extends LanguageChains, NumericComparison, TypeComparison { - eventually: PromisedAssertion; - fulfilled: PromisedAssertion; - become(expected: any): PromisedAssertion; - rejected(): PromisedAssertion; - rejectedWith(expected: any, message?: string | RegExp): PromisedAssertion; - notify(fn: Function): PromisedAssertion; - } - - // Eventually does not have .then(), but PromisedAssertion have. - interface Eventually extends PromisedLanguageChains, PromisedNumericComparison, PromisedTypeComparison { - // From chai-as-promised - become(expected: PromiseLike<any>): PromisedAssertion; - fulfilled: PromisedAssertion; - rejected: () => PromisedAssertion; - rejectedWith(expected: any, message?: string | RegExp): PromisedAssertion; - notify(fn: Function): PromisedAssertion; - - // From chai - not: PromisedAssertion; - deep: PromisedDeep; - all: PromisedKeyFilter; - a: PromisedTypeComparison; - an: PromisedTypeComparison; - include: PromisedInclude; - contain: PromisedInclude; - ok: PromisedAssertion; - true: () => PromisedAssertion; - false: () => PromisedAssertion; - null: PromisedAssertion; - undefined: PromisedAssertion; - exist: PromisedAssertion; - empty: PromisedAssertion; - arguments: PromisedAssertion; - Arguments: PromisedAssertion; - equal: PromisedEqual; - equals: PromisedEqual; - eq: PromisedEqual; - eql: PromisedEqual; - eqls: PromisedEqual; - property: PromisedProperty; - ownProperty: PromisedOwnProperty; - haveOwnProperty: PromisedOwnProperty; - length: PromisedLength; - lengthOf: PromisedLength; - match(regexp: RegExp | string, message?: string): PromisedAssertion; - string(string: string, message?: string): PromisedAssertion; - keys: PromisedKeys; - key(string: string): PromisedAssertion; - throw: PromisedThrow; - throws: PromisedThrow; - Throw: PromisedThrow; - respondTo(method: string, message?: string): PromisedAssertion; - itself: PromisedAssertion; - satisfy(matcher: Function, message?: string): PromisedAssertion; - closeTo(expected: number, delta: number, message?: string): PromisedAssertion; - members: PromisedMembers; - } - - interface PromisedAssertion extends Eventually, PromiseLike<any> {} - - interface PromisedLanguageChains { - eventually: Eventually; - - // From chai - to: PromisedAssertion; - be: PromisedAssertion; - been: PromisedAssertion; - is: PromisedAssertion; - that: PromisedAssertion; - which: PromisedAssertion; - and: PromisedAssertion; - has: PromisedAssertion; - have: PromisedAssertion; - with: PromisedAssertion; - at: PromisedAssertion; - of: PromisedAssertion; - same: PromisedAssertion; - } - - interface PromisedNumericComparison { - above: PromisedNumberComparer; - gt: PromisedNumberComparer; - greaterThan: PromisedNumberComparer; - least: PromisedNumberComparer; - gte: PromisedNumberComparer; - below: PromisedNumberComparer; - lt: PromisedNumberComparer; - lessThan: PromisedNumberComparer; - most: PromisedNumberComparer; - lte: PromisedNumberComparer; - within(start: number, finish: number, message?: string): PromisedAssertion; - } - - type PromisedNumberComparer = (value: number, message?: string) => PromisedAssertion; - - interface PromisedTypeComparison { - (type: string, message?: string): PromisedAssertion; - instanceof: PromisedInstanceOf; - instanceOf: PromisedInstanceOf; - } - - type PromisedInstanceOf = (constructor: Object, message?: string) => PromisedAssertion; - - interface PromisedDeep { - equal: PromisedEqual; - include: PromisedInclude; - property: PromisedProperty; - } - - interface PromisedKeyFilter { - keys: PromisedKeys; - } - - type PromisedEqual = (value: any, message?: string) => PromisedAssertion; - - type PromisedProperty = (name: string, value?: any, message?: string) => PromisedAssertion; - - type PromisedOwnProperty = (name: string, message?: string) => PromisedAssertion; - - interface PromisedLength extends PromisedLanguageChains, PromisedNumericComparison { - (length: number, message?: string): PromisedAssertion; - } - - interface PromisedInclude { - (value: Object | string | number, message?: string): PromisedAssertion; - keys: PromisedKeys; - members: PromisedMembers; - all: PromisedKeyFilter; - } - - interface PromisedKeys { - (...keys: string[]): PromisedAssertion; - (keys: any[]): PromisedAssertion; - } - - interface PromisedThrow { - (): PromisedAssertion; - (expected: string | RegExp, message?: string): PromisedAssertion; - (constructor: Error | Function, expected?: string | RegExp, message?: string): PromisedAssertion; - } - - type PromisedMembers = (set: any[], message?: string) => PromisedAssertion; - - // For Assert API - interface Assert { - eventually: PromisedAssert; - isFulfilled(promise: PromiseLike<any>, message?: string): PromiseLike<void>; - becomes(promise: PromiseLike<any>, expected: any, message?: string): PromiseLike<void>; - doesNotBecome(promise: PromiseLike<any>, expected: any, message?: string): PromiseLike<void>; - isRejected(promise: PromiseLike<any>, message?: string): PromiseLike<void>; - isRejected(promise: PromiseLike<any>, expected: any | RegExp, message?: string): PromiseLike<void>; - notify(fn: Function): PromiseLike<void>; - } - - export interface PromisedAssert { - fail(actual?: any, expected?: any, msg?: string, operator?: string): PromiseLike<void>; - - ok(val: any, msg?: string): PromiseLike<void>; - notOk(val: any, msg?: string): PromiseLike<void>; - - equal(act: any, exp: any, msg?: string): PromiseLike<void>; - notEqual(act: any, exp: any, msg?: string): PromiseLike<void>; - - strictEqual(act: any, exp: any, msg?: string): PromiseLike<void>; - notStrictEqual(act: any, exp: any, msg?: string): PromiseLike<void>; - - deepEqual(act: any, exp: any, msg?: string): PromiseLike<void>; - notDeepEqual(act: any, exp: any, msg?: string): PromiseLike<void>; - - isTrue(val: any, msg?: string): PromiseLike<void>; - isFalse(val: any, msg?: string): PromiseLike<void>; + // For BDD API + interface Assertion extends LanguageChains, NumericComparison, TypeComparison { + eventually: PromisedAssertion; + fulfilled: PromisedAssertion; + become(expected: any): PromisedAssertion; + rejected(): PromisedAssertion; + rejectedWith(expected: any, message?: string | RegExp): PromisedAssertion; + notify(fn: Function): PromisedAssertion; + } + + // Eventually does not have .then(), but PromisedAssertion have. + interface Eventually extends PromisedLanguageChains, PromisedNumericComparison, PromisedTypeComparison { + // From chai-as-promised + become(expected: PromiseLike<any>): PromisedAssertion; + fulfilled: PromisedAssertion; + rejected: () => PromisedAssertion; + rejectedWith(expected: any, message?: string | RegExp): PromisedAssertion; + notify(fn: Function): PromisedAssertion; + + // From chai + not: PromisedAssertion; + deep: PromisedDeep; + all: PromisedKeyFilter; + a: PromisedTypeComparison; + an: PromisedTypeComparison; + include: PromisedInclude; + contain: PromisedInclude; + ok: PromisedAssertion; + true: () => PromisedAssertion; + false: () => PromisedAssertion; + null: PromisedAssertion; + undefined: PromisedAssertion; + exist: PromisedAssertion; + empty: PromisedAssertion; + arguments: PromisedAssertion; + Arguments: PromisedAssertion; + equal: PromisedEqual; + equals: PromisedEqual; + eq: PromisedEqual; + eql: PromisedEqual; + eqls: PromisedEqual; + property: PromisedProperty; + ownProperty: PromisedOwnProperty; + haveOwnProperty: PromisedOwnProperty; + length: PromisedLength; + lengthOf: PromisedLength; + match(regexp: RegExp | string, message?: string): PromisedAssertion; + string(string: string, message?: string): PromisedAssertion; + keys: PromisedKeys; + key(string: string): PromisedAssertion; + throw: PromisedThrow; + throws: PromisedThrow; + Throw: PromisedThrow; + respondTo(method: string, message?: string): PromisedAssertion; + itself: PromisedAssertion; + satisfy(matcher: Function, message?: string): PromisedAssertion; + closeTo(expected: number, delta: number, message?: string): PromisedAssertion; + members: PromisedMembers; + } + + interface PromisedAssertion extends Eventually, PromiseLike<any> {} + + interface PromisedLanguageChains { + eventually: Eventually; + + // From chai + to: PromisedAssertion; + be: PromisedAssertion; + been: PromisedAssertion; + is: PromisedAssertion; + that: PromisedAssertion; + which: PromisedAssertion; + and: PromisedAssertion; + has: PromisedAssertion; + have: PromisedAssertion; + with: PromisedAssertion; + at: PromisedAssertion; + of: PromisedAssertion; + same: PromisedAssertion; + } + + interface PromisedNumericComparison { + above: PromisedNumberComparer; + gt: PromisedNumberComparer; + greaterThan: PromisedNumberComparer; + least: PromisedNumberComparer; + gte: PromisedNumberComparer; + below: PromisedNumberComparer; + lt: PromisedNumberComparer; + lessThan: PromisedNumberComparer; + most: PromisedNumberComparer; + lte: PromisedNumberComparer; + within(start: number, finish: number, message?: string): PromisedAssertion; + } + + type PromisedNumberComparer = (value: number, message?: string) => PromisedAssertion; + + interface PromisedTypeComparison { + (type: string, message?: string): PromisedAssertion; + instanceof: PromisedInstanceOf; + instanceOf: PromisedInstanceOf; + } + + type PromisedInstanceOf = (constructor: Object, message?: string) => PromisedAssertion; + + interface PromisedDeep { + equal: PromisedEqual; + include: PromisedInclude; + property: PromisedProperty; + } + + interface PromisedKeyFilter { + keys: PromisedKeys; + } + + type PromisedEqual = (value: any, message?: string) => PromisedAssertion; + + type PromisedProperty = (name: string, value?: any, message?: string) => PromisedAssertion; + + type PromisedOwnProperty = (name: string, message?: string) => PromisedAssertion; + + interface PromisedLength extends PromisedLanguageChains, PromisedNumericComparison { + (length: number, message?: string): PromisedAssertion; + } + + interface PromisedInclude { + (value: Object | string | number, message?: string): PromisedAssertion; + keys: PromisedKeys; + members: PromisedMembers; + all: PromisedKeyFilter; + } + + interface PromisedKeys { + (...keys: string[]): PromisedAssertion; + (keys: any[]): PromisedAssertion; + } + + interface PromisedThrow { + (): PromisedAssertion; + (expected: string | RegExp, message?: string): PromisedAssertion; + (constructor: Error | Function, expected?: string | RegExp, message?: string): PromisedAssertion; + } + + type PromisedMembers = (set: any[], message?: string) => PromisedAssertion; + + // For Assert API + interface Assert { + eventually: PromisedAssert; + isFulfilled(promise: PromiseLike<any>, message?: string): PromiseLike<void>; + becomes(promise: PromiseLike<any>, expected: any, message?: string): PromiseLike<void>; + doesNotBecome(promise: PromiseLike<any>, expected: any, message?: string): PromiseLike<void>; + isRejected(promise: PromiseLike<any>, message?: string): PromiseLike<void>; + isRejected(promise: PromiseLike<any>, expected: any | RegExp, message?: string): PromiseLike<void>; + notify(fn: Function): PromiseLike<void>; + } + + export interface PromisedAssert { + fail(actual?: any, expected?: any, msg?: string, operator?: string): PromiseLike<void>; + + ok(val: any, msg?: string): PromiseLike<void>; + notOk(val: any, msg?: string): PromiseLike<void>; + + equal(act: any, exp: any, msg?: string): PromiseLike<void>; + notEqual(act: any, exp: any, msg?: string): PromiseLike<void>; + + strictEqual(act: any, exp: any, msg?: string): PromiseLike<void>; + notStrictEqual(act: any, exp: any, msg?: string): PromiseLike<void>; + + deepEqual(act: any, exp: any, msg?: string): PromiseLike<void>; + notDeepEqual(act: any, exp: any, msg?: string): PromiseLike<void>; + + isTrue(val: any, msg?: string): PromiseLike<void>; + isFalse(val: any, msg?: string): PromiseLike<void>; - isNull(val: any, msg?: string): PromiseLike<void>; - isNotNull(val: any, msg?: string): PromiseLike<void>; + isNull(val: any, msg?: string): PromiseLike<void>; + isNotNull(val: any, msg?: string): PromiseLike<void>; - isUndefined(val: any, msg?: string): PromiseLike<void>; - isDefined(val: any, msg?: string): PromiseLike<void>; + isUndefined(val: any, msg?: string): PromiseLike<void>; + isDefined(val: any, msg?: string): PromiseLike<void>; - isFunction(val: any, msg?: string): PromiseLike<void>; - isNotFunction(val: any, msg?: string): PromiseLike<void>; + isFunction(val: any, msg?: string): PromiseLike<void>; + isNotFunction(val: any, msg?: string): PromiseLike<void>; - isObject(val: any, msg?: string): PromiseLike<void>; - isNotObject(val: any, msg?: string): PromiseLike<void>; + isObject(val: any, msg?: string): PromiseLike<void>; + isNotObject(val: any, msg?: string): PromiseLike<void>; - isArray(val: any, msg?: string): PromiseLike<void>; - isNotArray(val: any, msg?: string): PromiseLike<void>; + isArray(val: any, msg?: string): PromiseLike<void>; + isNotArray(val: any, msg?: string): PromiseLike<void>; - isString(val: any, msg?: string): PromiseLike<void>; - isNotString(val: any, msg?: string): PromiseLike<void>; + isString(val: any, msg?: string): PromiseLike<void>; + isNotString(val: any, msg?: string): PromiseLike<void>; - isNumber(val: any, msg?: string): PromiseLike<void>; - isNotNumber(val: any, msg?: string): PromiseLike<void>; + isNumber(val: any, msg?: string): PromiseLike<void>; + isNotNumber(val: any, msg?: string): PromiseLike<void>; - isBoolean(val: any, msg?: string): PromiseLike<void>; - isNotBoolean(val: any, msg?: string): PromiseLike<void>; + isBoolean(val: any, msg?: string): PromiseLike<void>; + isNotBoolean(val: any, msg?: string): PromiseLike<void>; - typeOf(val: any, type: string, msg?: string): PromiseLike<void>; - notTypeOf(val: any, type: string, msg?: string): PromiseLike<void>; + typeOf(val: any, type: string, msg?: string): PromiseLike<void>; + notTypeOf(val: any, type: string, msg?: string): PromiseLike<void>; - instanceOf(val: any, type: Function, msg?: string): PromiseLike<void>; - notInstanceOf(val: any, type: Function, msg?: string): PromiseLike<void>; + instanceOf(val: any, type: Function, msg?: string): PromiseLike<void>; + notInstanceOf(val: any, type: Function, msg?: string): PromiseLike<void>; - include(exp: string | any[], inc: any, msg?: string): PromiseLike<void>; + include(exp: string | any[], inc: any, msg?: string): PromiseLike<void>; - notInclude(exp: string | any[], inc: any, msg?: string): PromiseLike<void>; + notInclude(exp: string | any[], inc: any, msg?: string): PromiseLike<void>; - match(exp: any, re: RegExp, msg?: string): PromiseLike<void>; - notMatch(exp: any, re: RegExp, msg?: string): PromiseLike<void>; + match(exp: any, re: RegExp, msg?: string): PromiseLike<void>; + notMatch(exp: any, re: RegExp, msg?: string): PromiseLike<void>; - property(obj: Object, prop: string, msg?: string): PromiseLike<void>; - notProperty(obj: Object, prop: string, msg?: string): PromiseLike<void>; - deepProperty(obj: Object, prop: string, msg?: string): PromiseLike<void>; - notDeepProperty(obj: Object, prop: string, msg?: string): PromiseLike<void>; + property(obj: Object, prop: string, msg?: string): PromiseLike<void>; + notProperty(obj: Object, prop: string, msg?: string): PromiseLike<void>; + deepProperty(obj: Object, prop: string, msg?: string): PromiseLike<void>; + notDeepProperty(obj: Object, prop: string, msg?: string): PromiseLike<void>; - propertyVal(obj: Object, prop: string, val: any, msg?: string): PromiseLike<void>; - propertyNotVal(obj: Object, prop: string, val: any, msg?: string): PromiseLike<void>; + propertyVal(obj: Object, prop: string, val: any, msg?: string): PromiseLike<void>; + propertyNotVal(obj: Object, prop: string, val: any, msg?: string): PromiseLike<void>; - deepPropertyVal(obj: Object, prop: string, val: any, msg?: string): PromiseLike<void>; - deepPropertyNotVal(obj: Object, prop: string, val: any, msg?: string): PromiseLike<void>; + deepPropertyVal(obj: Object, prop: string, val: any, msg?: string): PromiseLike<void>; + deepPropertyNotVal(obj: Object, prop: string, val: any, msg?: string): PromiseLike<void>; - lengthOf(exp: any, len: number, msg?: string): PromiseLike<void>; - // alias frenzy - throw(fn: Function, msg?: string): PromiseLike<void>; - throw(fn: Function, regExp: RegExp): PromiseLike<void>; - throw(fn: Function, errType: Function, msg?: string): PromiseLike<void>; - throw(fn: Function, errType: Function, regExp: RegExp): PromiseLike<void>; + lengthOf(exp: any, len: number, msg?: string): PromiseLike<void>; + // alias frenzy + throw(fn: Function, msg?: string): PromiseLike<void>; + throw(fn: Function, regExp: RegExp): PromiseLike<void>; + throw(fn: Function, errType: Function, msg?: string): PromiseLike<void>; + throw(fn: Function, errType: Function, regExp: RegExp): PromiseLike<void>; - throws(fn: Function, msg?: string): PromiseLike<void>; - throws(fn: Function, regExp: RegExp): PromiseLike<void>; - throws(fn: Function, errType: Function, msg?: string): PromiseLike<void>; - throws(fn: Function, errType: Function, regExp: RegExp): PromiseLike<void>; + throws(fn: Function, msg?: string): PromiseLike<void>; + throws(fn: Function, regExp: RegExp): PromiseLike<void>; + throws(fn: Function, errType: Function, msg?: string): PromiseLike<void>; + throws(fn: Function, errType: Function, regExp: RegExp): PromiseLike<void>; - Throw(fn: Function, msg?: string): PromiseLike<void>; - Throw(fn: Function, regExp: RegExp): PromiseLike<void>; - Throw(fn: Function, errType: Function, msg?: string): PromiseLike<void>; - Throw(fn: Function, errType: Function, regExp: RegExp): PromiseLike<void>; + Throw(fn: Function, msg?: string): PromiseLike<void>; + Throw(fn: Function, regExp: RegExp): PromiseLike<void>; + Throw(fn: Function, errType: Function, msg?: string): PromiseLike<void>; + Throw(fn: Function, errType: Function, regExp: RegExp): PromiseLike<void>; - doesNotThrow(fn: Function, msg?: string): PromiseLike<void>; - doesNotThrow(fn: Function, regExp: RegExp): PromiseLike<void>; - doesNotThrow(fn: Function, errType: Function, msg?: string): PromiseLike<void>; - doesNotThrow(fn: Function, errType: Function, regExp: RegExp): PromiseLike<void>; + doesNotThrow(fn: Function, msg?: string): PromiseLike<void>; + doesNotThrow(fn: Function, regExp: RegExp): PromiseLike<void>; + doesNotThrow(fn: Function, errType: Function, msg?: string): PromiseLike<void>; + doesNotThrow(fn: Function, errType: Function, regExp: RegExp): PromiseLike<void>; - operator(val: any, operator: string, val2: any, msg?: string): PromiseLike<void>; - closeTo(act: number, exp: number, delta: number, msg?: string): PromiseLike<void>; + operator(val: any, operator: string, val2: any, msg?: string): PromiseLike<void>; + closeTo(act: number, exp: number, delta: number, msg?: string): PromiseLike<void>; - sameMembers(set1: any[], set2: any[], msg?: string): PromiseLike<void>; - includeMembers(set1: any[], set2: any[], msg?: string): PromiseLike<void>; + sameMembers(set1: any[], set2: any[], msg?: string): PromiseLike<void>; + includeMembers(set1: any[], set2: any[], msg?: string): PromiseLike<void>; - ifError(val: any, msg?: string): PromiseLike<void>; - } + ifError(val: any, msg?: string): PromiseLike<void>; + } } diff --git a/packages/chai-as-promised-typescript-typings/package.json b/packages/chai-as-promised-typescript-typings/package.json index 77985de05..bea3d273d 100644 --- a/packages/chai-as-promised-typescript-typings/package.json +++ b/packages/chai-as-promised-typescript-typings/package.json @@ -1,21 +1,21 @@ { - "name": "chai-as-promised-typescript-typings", - "version": "0.0.6", - "description": "Typescript type definitions for chai-as-promised", - "main": "index.d.ts", - "types": "index.d.ts", - "repository": { - "type": "git", - "url": "git+https://github.com/0xProject/0x.js.git" - }, - "author": "Fabio Berger", - "contributors": ["Leonid Logvinov <logvinov.leon@gmail.com>"], - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/chai-as-promised-typescript-typings#readme", - "dependencies": { - "chai-typescript-typings": "^0.0.0" - } + "name": "chai-as-promised-typescript-typings", + "version": "0.0.6", + "description": "Typescript type definitions for chai-as-promised", + "main": "index.d.ts", + "types": "index.d.ts", + "repository": { + "type": "git", + "url": "git+https://github.com/0xProject/0x.js.git" + }, + "author": "Fabio Berger", + "contributors": ["Leonid Logvinov <logvinov.leon@gmail.com>"], + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/chai-as-promised-typescript-typings#readme", + "dependencies": { + "chai-typescript-typings": "^0.0.0" + } } diff --git a/packages/chai-as-promised-typescript-typings/tslint.json b/packages/chai-as-promised-typescript-typings/tslint.json index ef528b22e..9a93a1f74 100644 --- a/packages/chai-as-promised-typescript-typings/tslint.json +++ b/packages/chai-as-promised-typescript-typings/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["tslint-config-0xproject"] + "extends": ["tslint-config-0xproject"] } diff --git a/packages/chai-typescript-typings/index.d.ts b/packages/chai-typescript-typings/index.d.ts index 73d87a887..8b3e4c079 100644 --- a/packages/chai-typescript-typings/index.d.ts +++ b/packages/chai-typescript-typings/index.d.ts @@ -12,1243 +12,1243 @@ // tslint:disable:no-namespace member-ordering ban-types unified-signatures variable-name callable-types declare namespace Chai { - interface ChaiStatic { - expect: ExpectStatic; - should(): Should; - /** - * Provides a way to extend the internals of Chai - */ - use(fn: (chai: any, utils: any) => void): ChaiStatic; - assert: AssertStatic; - config: Config; - AssertionError: typeof AssertionError; - version: string; - } - - export interface ExpectStatic extends AssertionStatic { - fail(actual?: any, expected?: any, message?: string, operator?: Operator): void; - } - - export interface AssertStatic extends Assert {} - - type AssertionStatic = (target: any, message?: string) => Assertion; - - export type Operator = string; // "==" | "===" | ">" | ">=" | "<" | "<=" | "!=" | "!=="; - - export type OperatorComparable = boolean | null | number | string | undefined | Date; - - interface ShouldAssertion { - equal(value1: any, value2: any, message?: string): void; - Throw: ShouldThrow; - throw: ShouldThrow; - exist(value: any, message?: string): void; - } - - interface Should extends ShouldAssertion { - not: ShouldAssertion; - fail(actual: any, expected: any, message?: string, operator?: Operator): void; - } - - interface ShouldThrow { - (actual: Function): void; - (actual: Function, expected: string | RegExp, message?: string): void; - (actual: Function, constructor: Error | Function, expected?: string | RegExp, message?: string): void; - } - - interface Assertion extends LanguageChains, NumericComparison, TypeComparison { - not: Assertion; - deep: Deep; - nested: Nested; - any: KeyFilter; - all: KeyFilter; - a: TypeComparison; - an: TypeComparison; - include: Include; - includes: Include; - contain: Include; - contains: Include; - ok: Assertion; - true: () => Assertion; - false: () => Assertion; - null: () => Assertion; - undefined: () => Assertion; - NaN: Assertion; - exist: Assertion; - empty: Assertion; - arguments: Assertion; - Arguments: Assertion; - equal: Equal; - equals: Equal; - eq: Equal; - eql: Equal; - eqls: Equal; - property: Property; - ownProperty: OwnProperty; - haveOwnProperty: OwnProperty; - ownPropertyDescriptor: OwnPropertyDescriptor; - haveOwnPropertyDescriptor: OwnPropertyDescriptor; - length: Length; - lengthOf: Length; - match: Match; - matches: Match; - string(string: string, message?: string): Assertion; - keys: Keys; - key(string: string): Assertion; - throw: (message?: string) => Assertion; - throws: Throw; - Throw: Throw; - respondTo: RespondTo; - respondsTo: RespondTo; - itself: Assertion; - satisfy: Satisfy; - satisfies: Satisfy; - closeTo: CloseTo; - approximately: CloseTo; - members: Members; - increase: PropertyChange; - increases: PropertyChange; - decrease: PropertyChange; - decreases: PropertyChange; - change: PropertyChange; - changes: PropertyChange; - extensible: Assertion; - sealed: Assertion; - frozen: Assertion; - oneOf(list: any[], message?: string): Assertion; - } - - interface LanguageChains { - to: Assertion; - be: Assertion; - been: Assertion; - is: Assertion; - that: Assertion; - which: Assertion; - and: Assertion; - has: Assertion; - have: Assertion; - with: Assertion; - at: Assertion; - of: Assertion; - same: Assertion; - } - - interface NumericComparison { - above: NumberComparer; - gt: NumberComparer; - greaterThan: NumberComparer; - least: NumberComparer; - gte: NumberComparer; - below: NumberComparer; - lt: NumberComparer; - lessThan: NumberComparer; - most: NumberComparer; - lte: NumberComparer; - within(start: number, finish: number, message?: string): Assertion; - } - - interface NumberComparer { - (value: number, message?: string): Assertion; - } - - interface TypeComparison { - (type: string, message?: string): Assertion; - instanceof: InstanceOf; - instanceOf: InstanceOf; - } - - interface InstanceOf { - (constructor: Object, message?: string): Assertion; - } - - interface CloseTo { - (expected: number, delta: number, message?: string): Assertion; - } - - interface Nested { - include: Include; - property: Property; - members: Members; - } - - interface Deep { - equal: Equal; - equals: Equal; - eq: Equal; - include: Include; - property: Property; - members: Members; - } - - interface KeyFilter { - keys: Keys; - } - - interface Equal { - (value: any, message?: string): Assertion; - } - - interface Property { - (name: string, value?: any, message?: string): Assertion; - } - - interface OwnProperty { - (name: string, message?: string): Assertion; - } - - interface OwnPropertyDescriptor { - (name: string, descriptor: PropertyDescriptor, message?: string): Assertion; - (name: string, message?: string): Assertion; - } - - interface Length extends LanguageChains, NumericComparison { - (length: number, message?: string): Assertion; - } - - interface Include { - (value: Object | string | number, message?: string): Assertion; - keys: Keys; - members: Members; - any: KeyFilter; - all: KeyFilter; - } - - interface Match { - (regexp: RegExp | string, message?: string): Assertion; - } - - interface Keys { - (...keys: string[]): Assertion; - (keys: any[]): Assertion; - (keys: Object): Assertion; - } - - interface Throw { - (): Assertion; - (expected: string, message?: string): Assertion; - (expected: RegExp, message?: string): Assertion; - (constructor: Error, expected?: string, message?: string): Assertion; - (constructor: Error, expected?: RegExp, message?: string): Assertion; - (constructor: Function, expected?: string, message?: string): Assertion; - (constructor: Function, expected?: RegExp, message?: string): Assertion; - } - - interface RespondTo { - (method: string, message?: string): Assertion; - } - - interface Satisfy { - (matcher: Function, message?: string): Assertion; - } - - interface Members { - (set: any[], message?: string): Assertion; - } - - interface PropertyChange { - (object: Object, property: string, message?: string): Assertion; - } - - export interface Assert { - /** - * @param expression Expression to test for truthiness. - * @param message Message to display on error. - */ - (expression: any, message?: string): void; - - /** - * Throws a failure. - * - * @type T Type of the objects. - * @param actual Actual value. - * @param expected Potential expected value. - * @param message Message to display on error. - * @param operator Comparison operator, if not strict equality. - * @remarks Node.js assert module-compatible. - */ - fail<T>(actual?: T, expected?: T, message?: string, operator?: Operator): void; - - /** - * Asserts that object is truthy. - * - * @type T Type of object. - * @param object Object to test. - * @param message Message to display on error. - */ - isOk<T>(value: T, message?: string): void; - - /** - * Asserts that object is truthy. - * - * @type T Type of object. - * @param object Object to test. - * @param message Message to display on error. - */ - ok<T>(value: T, message?: string): void; - - /** - * Asserts that object is falsy. - * - * @type T Type of object. - * @param object Object to test. - * @param message Message to display on error. - */ - isNotOk<T>(value: T, message?: string): void; - - /** - * Asserts that object is falsy. - * - * @type T Type of object. - * @param object Object to test. - * @param message Message to display on error. - */ - notOk<T>(value: T, message?: string): void; - - /** - * Asserts non-strict equality (==) of actual and expected. - * - * @type T Type of the objects. - * @param actual Actual value. - * @param expected Potential expected value. - * @param message Message to display on error. - */ - equal<T>(actual: T, expected: T, message?: string): void; - - /** - * Asserts non-strict inequality (==) of actual and expected. - * - * @type T Type of the objects. - * @param actual Actual value. - * @param expected Potential expected value. - * @param message Message to display on error. - */ - notEqual<T>(actual: T, expected: T, message?: string): void; - - /** - * Asserts strict equality (===) of actual and expected. - * - * @type T Type of the objects. - * @param actual Actual value. - * @param expected Potential expected value. - * @param message Message to display on error. - */ - strictEqual<T>(actual: T, expected: T, message?: string): void; - - /** - * Asserts strict inequality (==) of actual and expected. - * - * @type T Type of the objects. - * @param actual Actual value. - * @param expected Potential expected value. - * @param message Message to display on error. - */ - notStrictEqual<T>(actual: T, expected: T, message?: string): void; - - /** - * Asserts that actual is deeply equal to expected. - * - * @type T Type of the objects. - * @param actual Actual value. - * @param expected Potential expected value. - * @param message Message to display on error. - */ - deepEqual<T>(actual: T, expected: T, message?: string): void; - - /** - * Asserts that actual is not deeply equal to expected. - * - * @type T Type of the objects. - * @param actual Actual value. - * @param expected Potential expected value. - * @param message Message to display on error. - */ - notDeepEqual<T>(actual: T, expected: T, message?: string): void; - - /** - * Asserts valueToCheck is strictly greater than (>) valueToBeAbove. - * - * @param valueToCheck Actual value. - * @param valueToBeAbove Minimum Potential expected value. - * @param message Message to display on error. - */ - isAbove(valueToCheck: number, valueToBeAbove: number, message?: string): void; - - /** - * Asserts valueToCheck is greater than or equal to (>=) valueToBeAtLeast. - * - * @param valueToCheck Actual value. - * @param valueToBeAtLeast Minimum Potential expected value. - * @param message Message to display on error. - */ - isAtLeast(valueToCheck: number, valueToBeAtLeast: number, message?: string): void; - - /** - * Asserts valueToCheck is strictly less than (<) valueToBeBelow. - * - * @param valueToCheck Actual value. - * @param valueToBeBelow Minimum Potential expected value. - * @param message Message to display on error. - */ - isBelow(valueToCheck: number, valueToBeBelow: number, message?: string): void; - - /** - * Asserts valueToCheck is greater than or equal to (>=) valueToBeAtMost. - * - * @param valueToCheck Actual value. - * @param valueToBeAtMost Minimum Potential expected value. - * @param message Message to display on error. - */ - isAtMost(valueToCheck: number, valueToBeAtMost: number, message?: string): void; - - /** - * Asserts that value is true. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isTrue<T>(value: T, message?: string): void; - - /** - * Asserts that value is false. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isFalse<T>(value: T, message?: string): void; - - /** - * Asserts that value is not true. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNotTrue<T>(value: T, message?: string): void; - - /** - * Asserts that value is not false. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNotFalse<T>(value: T, message?: string): void; - - /** - * Asserts that value is null. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNull<T>(value: T, message?: string): void; - - /** - * Asserts that value is not null. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNotNull<T>(value: T, message?: string): void; - - /** - * Asserts that value is not null. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNaN<T>(value: T, message?: string): void; - - /** - * Asserts that value is not null. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNotNaN<T>(value: T, message?: string): void; - - /** - * Asserts that value is undefined. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isUndefined<T>(value: T, message?: string): void; - - /** - * Asserts that value is not undefined. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isDefined<T>(value: T, message?: string): void; - - /** - * Asserts that value is a function. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isFunction<T>(value: T, message?: string): void; - - /** - * Asserts that value is not a function. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNotFunction<T>(value: T, message?: string): void; - - /** - * Asserts that value is an object of type 'Object' - * (as revealed by Object.prototype.toString). - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - * @remarks The assertion does not match subclassed objects. - */ - isObject<T>(value: T, message?: string): void; - - /** - * Asserts that value is not an object of type 'Object' - * (as revealed by Object.prototype.toString). - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNotObject<T>(value: T, message?: string): void; - - /** - * Asserts that value is an array. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isArray<T>(value: T, message?: string): void; - - /** - * Asserts that value is not an array. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNotArray<T>(value: T, message?: string): void; - - /** - * Asserts that value is a string. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isString<T>(value: T, message?: string): void; - - /** - * Asserts that value is not a string. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNotString<T>(value: T, message?: string): void; - - /** - * Asserts that value is a number. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNumber<T>(value: T, message?: string): void; - - /** - * Asserts that value is not a number. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNotNumber<T>(value: T, message?: string): void; - - /** - * Asserts that value is a boolean. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isBoolean<T>(value: T, message?: string): void; - - /** - * Asserts that value is not a boolean. - * - * @type T Type of value. - * @param value Actual value. - * @param message Message to display on error. - */ - isNotBoolean<T>(value: T, message?: string): void; - - /** - * Asserts that value's type is name, as determined by Object.prototype.toString. - * - * @type T Type of value. - * @param value Actual value. - * @param name Potential expected type name of value. - * @param message Message to display on error. - */ - typeOf<T>(value: T, name: string, message?: string): void; - - /** - * Asserts that value's type is not name, as determined by Object.prototype.toString. - * - * @type T Type of value. - * @param value Actual value. - * @param name Potential expected type name of value. - * @param message Message to display on error. - */ - notTypeOf<T>(value: T, name: string, message?: string): void; - - /** - * Asserts that value is an instance of constructor. - * - * @type T Type of value. - * @param value Actual value. - * @param constructor Potential expected contructor of value. - * @param message Message to display on error. - */ - instanceOf<T>(value: T, constructor: Function, message?: string): void; - - /** - * Asserts that value is not an instance of constructor. - * - * @type T Type of value. - * @param value Actual value. - * @param constructor Potential expected contructor of value. - * @param message Message to display on error. - */ - notInstanceOf<T>(value: T, type: Function, message?: string): void; - - /** - * Asserts that haystack includes needle. - * - * @param haystack Container string. - * @param needle Potential expected substring of haystack. - * @param message Message to display on error. - */ - include(haystack: string, needle: string, message?: string): void; - - /** - * Asserts that haystack includes needle. - * - * @type T Type of values in haystack. - * @param haystack Container array. - * @param needle Potential value contained in haystack. - * @param message Message to display on error. - */ - include<T>(haystack: T[], needle: T, message?: string): void; - - /** - * Asserts that haystack does not include needle. - * - * @param haystack Container string. - * @param needle Potential expected substring of haystack. - * @param message Message to display on error. - */ - notInclude(haystack: string, needle: any, message?: string): void; - - /** - * Asserts that haystack does not include needle. - * - * @type T Type of values in haystack. - * @param haystack Container array. - * @param needle Potential value contained in haystack. - * @param message Message to display on error. - */ - notInclude(haystack: any[], needle: any, message?: string): void; - - /** - * Asserts that value matches the regular expression regexp. - * - * @param value Actual value. - * @param regexp Potential match of value. - * @param message Message to display on error. - */ - match(value: string, regexp: RegExp, message?: string): void; - - /** - * Asserts that value does not match the regular expression regexp. - * - * @param value Actual value. - * @param regexp Potential match of value. - * @param message Message to display on error. - */ - notMatch(expected: any, regexp: RegExp, message?: string): void; - - /** - * Asserts that object has a property named by property. - * - * @type T Type of object. - * @param object Container object. - * @param property Potential contained property of object. - * @param message Message to display on error. - */ - property<T>(object: T, property: string /* keyof T */, message?: string): void; - - /** - * Asserts that object has a property named by property. - * - * @type T Type of object. - * @param object Container object. - * @param property Potential contained property of object. - * @param message Message to display on error. - */ - notProperty<T>(object: T, property: string /* keyof T */, message?: string): void; - - /** - * Asserts that object has a property named by property, which can be a string - * using dot- and bracket-notation for deep reference. - * - * @type T Type of object. - * @param object Container object. - * @param property Potential contained property of object. - * @param message Message to display on error. - */ - deepProperty<T>(object: T, property: string, message?: string): void; - - /** - * Asserts that object does not have a property named by property, which can be a - * string using dot- and bracket-notation for deep reference. - * - * @type T Type of object. - * @param object Container object. - * @param property Potential contained property of object. - * @param message Message to display on error. - */ - notDeepProperty<T>(object: T, property: string, message?: string): void; - - /** - * Asserts that object has a property named by property with value given by value. - * - * @type T Type of object. - * @type V Type of value. - * @param object Container object. - * @param property Potential contained property of object. - * @param value Potential expected property value. - * @param message Message to display on error. - */ - propertyVal<T, V>(object: T, property: string /* keyof T */, value: V, message?: string): void; - - /** - * Asserts that object has a property named by property with value given by value. - * - * @type T Type of object. - * @type V Type of value. - * @param object Container object. - * @param property Potential contained property of object. - * @param value Potential expected property value. - * @param message Message to display on error. - */ - propertyNotVal<T, V>(object: T, property: string /* keyof T */, value: V, message?: string): void; - - /** - * Asserts that object has a property named by property, which can be a string - * using dot- and bracket-notation for deep reference. - * - * @type T Type of object. - * @type V Type of value. - * @param object Container object. - * @param property Potential contained property of object. - * @param value Potential expected property value. - * @param message Message to display on error. - */ - deepPropertyVal<T, V>(object: T, property: string, value: V, message?: string): void; - - /** - * Asserts that object does not have a property named by property, which can be a - * string using dot- and bracket-notation for deep reference. - * - * @type T Type of object. - * @type V Type of value. - * @param object Container object. - * @param property Potential contained property of object. - * @param value Potential expected property value. - * @param message Message to display on error. - */ - deepPropertyNotVal<T, V>(object: T, property: string, value: V, message?: string): void; - - /** - * Asserts that object has a length property with the expected value. - * - * @type T Type of object. - * @param object Container object. - * @param length Potential expected length of object. - * @param message Message to display on error. - */ - lengthOf<T extends { readonly length?: number }>(object: T, length: number, message?: string): void; - - /** - * Asserts that fn will throw an error. - * - * @param fn Function that may throw. - * @param message Message to display on error. - */ - throw(fn: Function, message?: string): void; - - /** - * Asserts that function will throw an error with message matching regexp. - * - * @param fn Function that may throw. - * @param regExp Potential expected message match. - * @param message Message to display on error. - */ - throw(fn: Function, regExp: RegExp): void; - - /** - * Asserts that function will throw an error that is an instance of constructor. - * - * @param fn Function that may throw. - * @param constructor Potential expected error constructor. - * @param message Message to display on error. - */ - throw(fn: Function, constructor: Function, message?: string): void; - - /** - * Asserts that function will throw an error that is an instance of constructor - * and an error with message matching regexp. - * - * @param fn Function that may throw. - * @param constructor Potential expected error constructor. - * @param message Message to display on error. - */ - throw(fn: Function, constructor: Function, regExp: RegExp): void; - - /** - * Asserts that fn will throw an error. - * - * @param fn Function that may throw. - * @param message Message to display on error. - */ - throws(fn: Function, message?: string): void; - - /** - * Asserts that function will throw an error with message matching regexp. - * - * @param fn Function that may throw. - * @param regExp Potential expected message match. - * @param message Message to display on error. - */ - throws(fn: Function, regExp: RegExp, message?: string): void; - - /** - * Asserts that function will throw an error that is an instance of constructor. - * - * @param fn Function that may throw. - * @param constructor Potential expected error constructor. - * @param message Message to display on error. - */ - throws(fn: Function, errType: Function, message?: string): void; - - /** - * Asserts that function will throw an error that is an instance of constructor - * and an error with message matching regexp. - * - * @param fn Function that may throw. - * @param constructor Potential expected error constructor. - * @param message Message to display on error. - */ - throws(fn: Function, errType: Function, regExp: RegExp): void; - - /** - * Asserts that fn will throw an error. - * - * @param fn Function that may throw. - * @param message Message to display on error. - */ - Throw(fn: Function, message?: string): void; - - /** - * Asserts that function will throw an error with message matching regexp. - * - * @param fn Function that may throw. - * @param regExp Potential expected message match. - * @param message Message to display on error. - */ - Throw(fn: Function, regExp: RegExp): void; - - /** - * Asserts that function will throw an error that is an instance of constructor. - * - * @param fn Function that may throw. - * @param constructor Potential expected error constructor. - * @param message Message to display on error. - */ - Throw(fn: Function, errType: Function, message?: string): void; - - /** - * Asserts that function will throw an error that is an instance of constructor - * and an error with message matching regexp. - * - * @param fn Function that may throw. - * @param constructor Potential expected error constructor. - * @param message Message to display on error. - */ - Throw(fn: Function, errType: Function, regExp: RegExp): void; - - /** - * Asserts that fn will not throw an error. - * - * @param fn Function that may throw. - * @param message Message to display on error. - */ - doesNotThrow(fn: Function, message?: string): void; - - /** - * Asserts that function will throw an error with message matching regexp. - * - * @param fn Function that may throw. - * @param regExp Potential expected message match. - * @param message Message to display on error. - */ - doesNotThrow(fn: Function, regExp: RegExp): void; - - /** - * Asserts that function will throw an error that is an instance of constructor. - * - * @param fn Function that may throw. - * @param constructor Potential expected error constructor. - * @param message Message to display on error. - */ - doesNotThrow(fn: Function, errType: Function, message?: string): void; - - /** - * Asserts that function will throw an error that is an instance of constructor - * and an error with message matching regexp. - * - * @param fn Function that may throw. - * @param constructor Potential expected error constructor. - * @param message Message to display on error. - */ - doesNotThrow(fn: Function, errType: Function, regExp: RegExp): void; - - /** - * Compares two values using operator. - * - * @param val1 Left value during comparison. - * @param operator Comparison operator. - * @param val2 Right value during comparison. - * @param message Message to display on error. - */ - operator(val1: OperatorComparable, operator: Operator, val2: OperatorComparable, message?: string): void; - - /** - * Asserts that the target is equal to expected, to within a +/- delta range. - * - * @param actual Actual value - * @param expected Potential expected value. - * @param delta Maximum differenced between values. - * @param message Message to display on error. - */ - closeTo(actual: number, expected: number, delta: number, message?: string): void; - - /** - * Asserts that the target is equal to expected, to within a +/- delta range. - * - * @param actual Actual value - * @param expected Potential expected value. - * @param delta Maximum differenced between values. - * @param message Message to display on error. - */ - approximately(act: number, exp: number, delta: number, message?: string): void; - - /** - * Asserts that set1 and set2 have the same members. Order is not take into account. - * - * @type T Type of set values. - * @param set1 Actual set of values. - * @param set2 Potential expected set of values. - * @param message Message to display on error. - */ - sameMembers<T>(set1: T[], set2: T[], message?: string): void; - - /** - * Asserts that set1 and set2 have the same members using deep equality checking. - * Order is not take into account. - * - * @type T Type of set values. - * @param set1 Actual set of values. - * @param set2 Potential expected set of values. - * @param message Message to display on error. - */ - sameDeepMembers<T>(set1: T[], set2: T[], message?: string): void; - - /** - * Asserts that subset is included in superset. Order is not take into account. - * - * @type T Type of set values. - * @param superset Actual set of values. - * @param subset Potential contained set of values. - * @param message Message to display on error. - */ - includeMembers<T>(superset: T[], subset: T[], message?: string): void; - - /** - * Asserts that subset is included in superset using deep equality checking. - * Order is not take into account. - * - * @type T Type of set values. - * @param superset Actual set of values. - * @param subset Potential contained set of values. - * @param message Message to display on error. - */ - includeDeepMembers<T>(superset: T[], subset: T[], message?: string): void; - - /** - * Asserts that non-object, non-array value inList appears in the flat array list. - * - * @type T Type of list values. - * @param inList Value expected to be in the list. - * @param list List of values. - * @param message Message to display on error. - */ - oneOf<T>(inList: T, list: T[], message?: string): void; - - /** - * Asserts that a function changes the value of a property. - * - * @type T Type of object. - * @param modifier Function to run. - * @param object Container object. - * @param property Property of object expected to be modified. - * @param message Message to display on error. - */ - changes<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; - - /** - * Asserts that a function does not change the value of a property. - * - * @type T Type of object. - * @param modifier Function to run. - * @param object Container object. - * @param property Property of object expected not to be modified. - * @param message Message to display on error. - */ - doesNotChange<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; - - /** - * Asserts that a function increases an object property. - * - * @type T Type of object. - * @param modifier Function to run. - * @param object Container object. - * @param property Property of object expected to be increased. - * @param message Message to display on error. - */ - increases<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; - - /** - * Asserts that a function does not increase an object property. - * - * @type T Type of object. - * @param modifier Function to run. - * @param object Container object. - * @param property Property of object expected not to be increased. - * @param message Message to display on error. - */ - doesNotIncrease<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; - - /** - * Asserts that a function decreases an object property. - * - * @type T Type of object. - * @param modifier Function to run. - * @param object Container object. - * @param property Property of object expected to be decreased. - * @param message Message to display on error. - */ - decreases<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; - - /** - * Asserts that a function does not decrease an object property. - * - * @type T Type of object. - * @param modifier Function to run. - * @param object Container object. - * @param property Property of object expected not to be decreased. - * @param message Message to display on error. - */ - doesNotDecrease<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; - - /** - * Asserts if value is not a false value, and throws if it is a true value. - * - * @type T Type of object. - * @param object Actual value. - * @param message Message to display on error. - * @remarks This is added to allow for chai to be a drop-in replacement for - * Node’s assert class. - */ - ifError<T>(object: T, message?: string): void; - - /** - * Asserts that object is extensible (can have new properties added to it). - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - isExtensible<T>(object: T, message?: string): void; - - /** - * Asserts that object is extensible (can have new properties added to it). - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - extensible<T>(object: T, message?: string): void; - - /** - * Asserts that object is not extensible. - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - isNotExtensible<T>(object: T, message?: string): void; - - /** - * Asserts that object is not extensible. - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - notExtensible<T>(object: T, message?: string): void; - - /** - * Asserts that object is sealed (can have new properties added to it - * and its existing properties cannot be removed). - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - isSealed<T>(object: T, message?: string): void; - - /** - * Asserts that object is sealed (can have new properties added to it - * and its existing properties cannot be removed). - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - sealed<T>(object: T, message?: string): void; - - /** - * Asserts that object is not sealed. - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - isNotSealed<T>(object: T, message?: string): void; - - /** - * Asserts that object is not sealed. - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - notSealed<T>(object: T, message?: string): void; - - /** - * Asserts that object is frozen (cannot have new properties added to it - * and its existing properties cannot be removed). - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - isFrozen<T>(object: T, message?: string): void; - - /** - * Asserts that object is frozen (cannot have new properties added to it - * and its existing properties cannot be removed). - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - frozen<T>(object: T, message?: string): void; - - /** - * Asserts that object is not frozen (cannot have new properties added to it - * and its existing properties cannot be removed). - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - isNotFrozen<T>(object: T, message?: string): void; - - /** - * Asserts that object is not frozen (cannot have new properties added to it - * and its existing properties cannot be removed). - * - * @type T Type of object - * @param object Actual value. - * @param message Message to display on error. - */ - notFrozen<T>(object: T, message?: string): void; - } - - export interface Config { - /** - * Default: false - */ - includeStack: boolean; - - /** - * Default: true - */ - showDiff: boolean; - - /** - * Default: 40 - */ - truncateThreshold: number; - } - - export class AssertionError { - constructor(message: string, _props?: any, ssf?: Function); - public name: string; - public message: string; - public showDiff: boolean; - public stack: string; - } + interface ChaiStatic { + expect: ExpectStatic; + should(): Should; + /** + * Provides a way to extend the internals of Chai + */ + use(fn: (chai: any, utils: any) => void): ChaiStatic; + assert: AssertStatic; + config: Config; + AssertionError: typeof AssertionError; + version: string; + } + + export interface ExpectStatic extends AssertionStatic { + fail(actual?: any, expected?: any, message?: string, operator?: Operator): void; + } + + export interface AssertStatic extends Assert {} + + type AssertionStatic = (target: any, message?: string) => Assertion; + + export type Operator = string; // "==" | "===" | ">" | ">=" | "<" | "<=" | "!=" | "!=="; + + export type OperatorComparable = boolean | null | number | string | undefined | Date; + + interface ShouldAssertion { + equal(value1: any, value2: any, message?: string): void; + Throw: ShouldThrow; + throw: ShouldThrow; + exist(value: any, message?: string): void; + } + + interface Should extends ShouldAssertion { + not: ShouldAssertion; + fail(actual: any, expected: any, message?: string, operator?: Operator): void; + } + + interface ShouldThrow { + (actual: Function): void; + (actual: Function, expected: string | RegExp, message?: string): void; + (actual: Function, constructor: Error | Function, expected?: string | RegExp, message?: string): void; + } + + interface Assertion extends LanguageChains, NumericComparison, TypeComparison { + not: Assertion; + deep: Deep; + nested: Nested; + any: KeyFilter; + all: KeyFilter; + a: TypeComparison; + an: TypeComparison; + include: Include; + includes: Include; + contain: Include; + contains: Include; + ok: Assertion; + true: () => Assertion; + false: () => Assertion; + null: () => Assertion; + undefined: () => Assertion; + NaN: Assertion; + exist: Assertion; + empty: Assertion; + arguments: Assertion; + Arguments: Assertion; + equal: Equal; + equals: Equal; + eq: Equal; + eql: Equal; + eqls: Equal; + property: Property; + ownProperty: OwnProperty; + haveOwnProperty: OwnProperty; + ownPropertyDescriptor: OwnPropertyDescriptor; + haveOwnPropertyDescriptor: OwnPropertyDescriptor; + length: Length; + lengthOf: Length; + match: Match; + matches: Match; + string(string: string, message?: string): Assertion; + keys: Keys; + key(string: string): Assertion; + throw: (message?: string) => Assertion; + throws: Throw; + Throw: Throw; + respondTo: RespondTo; + respondsTo: RespondTo; + itself: Assertion; + satisfy: Satisfy; + satisfies: Satisfy; + closeTo: CloseTo; + approximately: CloseTo; + members: Members; + increase: PropertyChange; + increases: PropertyChange; + decrease: PropertyChange; + decreases: PropertyChange; + change: PropertyChange; + changes: PropertyChange; + extensible: Assertion; + sealed: Assertion; + frozen: Assertion; + oneOf(list: any[], message?: string): Assertion; + } + + interface LanguageChains { + to: Assertion; + be: Assertion; + been: Assertion; + is: Assertion; + that: Assertion; + which: Assertion; + and: Assertion; + has: Assertion; + have: Assertion; + with: Assertion; + at: Assertion; + of: Assertion; + same: Assertion; + } + + interface NumericComparison { + above: NumberComparer; + gt: NumberComparer; + greaterThan: NumberComparer; + least: NumberComparer; + gte: NumberComparer; + below: NumberComparer; + lt: NumberComparer; + lessThan: NumberComparer; + most: NumberComparer; + lte: NumberComparer; + within(start: number, finish: number, message?: string): Assertion; + } + + interface NumberComparer { + (value: number, message?: string): Assertion; + } + + interface TypeComparison { + (type: string, message?: string): Assertion; + instanceof: InstanceOf; + instanceOf: InstanceOf; + } + + interface InstanceOf { + (constructor: Object, message?: string): Assertion; + } + + interface CloseTo { + (expected: number, delta: number, message?: string): Assertion; + } + + interface Nested { + include: Include; + property: Property; + members: Members; + } + + interface Deep { + equal: Equal; + equals: Equal; + eq: Equal; + include: Include; + property: Property; + members: Members; + } + + interface KeyFilter { + keys: Keys; + } + + interface Equal { + (value: any, message?: string): Assertion; + } + + interface Property { + (name: string, value?: any, message?: string): Assertion; + } + + interface OwnProperty { + (name: string, message?: string): Assertion; + } + + interface OwnPropertyDescriptor { + (name: string, descriptor: PropertyDescriptor, message?: string): Assertion; + (name: string, message?: string): Assertion; + } + + interface Length extends LanguageChains, NumericComparison { + (length: number, message?: string): Assertion; + } + + interface Include { + (value: Object | string | number, message?: string): Assertion; + keys: Keys; + members: Members; + any: KeyFilter; + all: KeyFilter; + } + + interface Match { + (regexp: RegExp | string, message?: string): Assertion; + } + + interface Keys { + (...keys: string[]): Assertion; + (keys: any[]): Assertion; + (keys: Object): Assertion; + } + + interface Throw { + (): Assertion; + (expected: string, message?: string): Assertion; + (expected: RegExp, message?: string): Assertion; + (constructor: Error, expected?: string, message?: string): Assertion; + (constructor: Error, expected?: RegExp, message?: string): Assertion; + (constructor: Function, expected?: string, message?: string): Assertion; + (constructor: Function, expected?: RegExp, message?: string): Assertion; + } + + interface RespondTo { + (method: string, message?: string): Assertion; + } + + interface Satisfy { + (matcher: Function, message?: string): Assertion; + } + + interface Members { + (set: any[], message?: string): Assertion; + } + + interface PropertyChange { + (object: Object, property: string, message?: string): Assertion; + } + + export interface Assert { + /** + * @param expression Expression to test for truthiness. + * @param message Message to display on error. + */ + (expression: any, message?: string): void; + + /** + * Throws a failure. + * + * @type T Type of the objects. + * @param actual Actual value. + * @param expected Potential expected value. + * @param message Message to display on error. + * @param operator Comparison operator, if not strict equality. + * @remarks Node.js assert module-compatible. + */ + fail<T>(actual?: T, expected?: T, message?: string, operator?: Operator): void; + + /** + * Asserts that object is truthy. + * + * @type T Type of object. + * @param object Object to test. + * @param message Message to display on error. + */ + isOk<T>(value: T, message?: string): void; + + /** + * Asserts that object is truthy. + * + * @type T Type of object. + * @param object Object to test. + * @param message Message to display on error. + */ + ok<T>(value: T, message?: string): void; + + /** + * Asserts that object is falsy. + * + * @type T Type of object. + * @param object Object to test. + * @param message Message to display on error. + */ + isNotOk<T>(value: T, message?: string): void; + + /** + * Asserts that object is falsy. + * + * @type T Type of object. + * @param object Object to test. + * @param message Message to display on error. + */ + notOk<T>(value: T, message?: string): void; + + /** + * Asserts non-strict equality (==) of actual and expected. + * + * @type T Type of the objects. + * @param actual Actual value. + * @param expected Potential expected value. + * @param message Message to display on error. + */ + equal<T>(actual: T, expected: T, message?: string): void; + + /** + * Asserts non-strict inequality (==) of actual and expected. + * + * @type T Type of the objects. + * @param actual Actual value. + * @param expected Potential expected value. + * @param message Message to display on error. + */ + notEqual<T>(actual: T, expected: T, message?: string): void; + + /** + * Asserts strict equality (===) of actual and expected. + * + * @type T Type of the objects. + * @param actual Actual value. + * @param expected Potential expected value. + * @param message Message to display on error. + */ + strictEqual<T>(actual: T, expected: T, message?: string): void; + + /** + * Asserts strict inequality (==) of actual and expected. + * + * @type T Type of the objects. + * @param actual Actual value. + * @param expected Potential expected value. + * @param message Message to display on error. + */ + notStrictEqual<T>(actual: T, expected: T, message?: string): void; + + /** + * Asserts that actual is deeply equal to expected. + * + * @type T Type of the objects. + * @param actual Actual value. + * @param expected Potential expected value. + * @param message Message to display on error. + */ + deepEqual<T>(actual: T, expected: T, message?: string): void; + + /** + * Asserts that actual is not deeply equal to expected. + * + * @type T Type of the objects. + * @param actual Actual value. + * @param expected Potential expected value. + * @param message Message to display on error. + */ + notDeepEqual<T>(actual: T, expected: T, message?: string): void; + + /** + * Asserts valueToCheck is strictly greater than (>) valueToBeAbove. + * + * @param valueToCheck Actual value. + * @param valueToBeAbove Minimum Potential expected value. + * @param message Message to display on error. + */ + isAbove(valueToCheck: number, valueToBeAbove: number, message?: string): void; + + /** + * Asserts valueToCheck is greater than or equal to (>=) valueToBeAtLeast. + * + * @param valueToCheck Actual value. + * @param valueToBeAtLeast Minimum Potential expected value. + * @param message Message to display on error. + */ + isAtLeast(valueToCheck: number, valueToBeAtLeast: number, message?: string): void; + + /** + * Asserts valueToCheck is strictly less than (<) valueToBeBelow. + * + * @param valueToCheck Actual value. + * @param valueToBeBelow Minimum Potential expected value. + * @param message Message to display on error. + */ + isBelow(valueToCheck: number, valueToBeBelow: number, message?: string): void; + + /** + * Asserts valueToCheck is greater than or equal to (>=) valueToBeAtMost. + * + * @param valueToCheck Actual value. + * @param valueToBeAtMost Minimum Potential expected value. + * @param message Message to display on error. + */ + isAtMost(valueToCheck: number, valueToBeAtMost: number, message?: string): void; + + /** + * Asserts that value is true. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isTrue<T>(value: T, message?: string): void; + + /** + * Asserts that value is false. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isFalse<T>(value: T, message?: string): void; + + /** + * Asserts that value is not true. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNotTrue<T>(value: T, message?: string): void; + + /** + * Asserts that value is not false. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNotFalse<T>(value: T, message?: string): void; + + /** + * Asserts that value is null. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNull<T>(value: T, message?: string): void; + + /** + * Asserts that value is not null. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNotNull<T>(value: T, message?: string): void; + + /** + * Asserts that value is not null. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNaN<T>(value: T, message?: string): void; + + /** + * Asserts that value is not null. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNotNaN<T>(value: T, message?: string): void; + + /** + * Asserts that value is undefined. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isUndefined<T>(value: T, message?: string): void; + + /** + * Asserts that value is not undefined. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isDefined<T>(value: T, message?: string): void; + + /** + * Asserts that value is a function. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isFunction<T>(value: T, message?: string): void; + + /** + * Asserts that value is not a function. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNotFunction<T>(value: T, message?: string): void; + + /** + * Asserts that value is an object of type 'Object' + * (as revealed by Object.prototype.toString). + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + * @remarks The assertion does not match subclassed objects. + */ + isObject<T>(value: T, message?: string): void; + + /** + * Asserts that value is not an object of type 'Object' + * (as revealed by Object.prototype.toString). + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNotObject<T>(value: T, message?: string): void; + + /** + * Asserts that value is an array. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isArray<T>(value: T, message?: string): void; + + /** + * Asserts that value is not an array. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNotArray<T>(value: T, message?: string): void; + + /** + * Asserts that value is a string. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isString<T>(value: T, message?: string): void; + + /** + * Asserts that value is not a string. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNotString<T>(value: T, message?: string): void; + + /** + * Asserts that value is a number. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNumber<T>(value: T, message?: string): void; + + /** + * Asserts that value is not a number. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNotNumber<T>(value: T, message?: string): void; + + /** + * Asserts that value is a boolean. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isBoolean<T>(value: T, message?: string): void; + + /** + * Asserts that value is not a boolean. + * + * @type T Type of value. + * @param value Actual value. + * @param message Message to display on error. + */ + isNotBoolean<T>(value: T, message?: string): void; + + /** + * Asserts that value's type is name, as determined by Object.prototype.toString. + * + * @type T Type of value. + * @param value Actual value. + * @param name Potential expected type name of value. + * @param message Message to display on error. + */ + typeOf<T>(value: T, name: string, message?: string): void; + + /** + * Asserts that value's type is not name, as determined by Object.prototype.toString. + * + * @type T Type of value. + * @param value Actual value. + * @param name Potential expected type name of value. + * @param message Message to display on error. + */ + notTypeOf<T>(value: T, name: string, message?: string): void; + + /** + * Asserts that value is an instance of constructor. + * + * @type T Type of value. + * @param value Actual value. + * @param constructor Potential expected contructor of value. + * @param message Message to display on error. + */ + instanceOf<T>(value: T, constructor: Function, message?: string): void; + + /** + * Asserts that value is not an instance of constructor. + * + * @type T Type of value. + * @param value Actual value. + * @param constructor Potential expected contructor of value. + * @param message Message to display on error. + */ + notInstanceOf<T>(value: T, type: Function, message?: string): void; + + /** + * Asserts that haystack includes needle. + * + * @param haystack Container string. + * @param needle Potential expected substring of haystack. + * @param message Message to display on error. + */ + include(haystack: string, needle: string, message?: string): void; + + /** + * Asserts that haystack includes needle. + * + * @type T Type of values in haystack. + * @param haystack Container array. + * @param needle Potential value contained in haystack. + * @param message Message to display on error. + */ + include<T>(haystack: T[], needle: T, message?: string): void; + + /** + * Asserts that haystack does not include needle. + * + * @param haystack Container string. + * @param needle Potential expected substring of haystack. + * @param message Message to display on error. + */ + notInclude(haystack: string, needle: any, message?: string): void; + + /** + * Asserts that haystack does not include needle. + * + * @type T Type of values in haystack. + * @param haystack Container array. + * @param needle Potential value contained in haystack. + * @param message Message to display on error. + */ + notInclude(haystack: any[], needle: any, message?: string): void; + + /** + * Asserts that value matches the regular expression regexp. + * + * @param value Actual value. + * @param regexp Potential match of value. + * @param message Message to display on error. + */ + match(value: string, regexp: RegExp, message?: string): void; + + /** + * Asserts that value does not match the regular expression regexp. + * + * @param value Actual value. + * @param regexp Potential match of value. + * @param message Message to display on error. + */ + notMatch(expected: any, regexp: RegExp, message?: string): void; + + /** + * Asserts that object has a property named by property. + * + * @type T Type of object. + * @param object Container object. + * @param property Potential contained property of object. + * @param message Message to display on error. + */ + property<T>(object: T, property: string /* keyof T */, message?: string): void; + + /** + * Asserts that object has a property named by property. + * + * @type T Type of object. + * @param object Container object. + * @param property Potential contained property of object. + * @param message Message to display on error. + */ + notProperty<T>(object: T, property: string /* keyof T */, message?: string): void; + + /** + * Asserts that object has a property named by property, which can be a string + * using dot- and bracket-notation for deep reference. + * + * @type T Type of object. + * @param object Container object. + * @param property Potential contained property of object. + * @param message Message to display on error. + */ + deepProperty<T>(object: T, property: string, message?: string): void; + + /** + * Asserts that object does not have a property named by property, which can be a + * string using dot- and bracket-notation for deep reference. + * + * @type T Type of object. + * @param object Container object. + * @param property Potential contained property of object. + * @param message Message to display on error. + */ + notDeepProperty<T>(object: T, property: string, message?: string): void; + + /** + * Asserts that object has a property named by property with value given by value. + * + * @type T Type of object. + * @type V Type of value. + * @param object Container object. + * @param property Potential contained property of object. + * @param value Potential expected property value. + * @param message Message to display on error. + */ + propertyVal<T, V>(object: T, property: string /* keyof T */, value: V, message?: string): void; + + /** + * Asserts that object has a property named by property with value given by value. + * + * @type T Type of object. + * @type V Type of value. + * @param object Container object. + * @param property Potential contained property of object. + * @param value Potential expected property value. + * @param message Message to display on error. + */ + propertyNotVal<T, V>(object: T, property: string /* keyof T */, value: V, message?: string): void; + + /** + * Asserts that object has a property named by property, which can be a string + * using dot- and bracket-notation for deep reference. + * + * @type T Type of object. + * @type V Type of value. + * @param object Container object. + * @param property Potential contained property of object. + * @param value Potential expected property value. + * @param message Message to display on error. + */ + deepPropertyVal<T, V>(object: T, property: string, value: V, message?: string): void; + + /** + * Asserts that object does not have a property named by property, which can be a + * string using dot- and bracket-notation for deep reference. + * + * @type T Type of object. + * @type V Type of value. + * @param object Container object. + * @param property Potential contained property of object. + * @param value Potential expected property value. + * @param message Message to display on error. + */ + deepPropertyNotVal<T, V>(object: T, property: string, value: V, message?: string): void; + + /** + * Asserts that object has a length property with the expected value. + * + * @type T Type of object. + * @param object Container object. + * @param length Potential expected length of object. + * @param message Message to display on error. + */ + lengthOf<T extends { readonly length?: number }>(object: T, length: number, message?: string): void; + + /** + * Asserts that fn will throw an error. + * + * @param fn Function that may throw. + * @param message Message to display on error. + */ + throw(fn: Function, message?: string): void; + + /** + * Asserts that function will throw an error with message matching regexp. + * + * @param fn Function that may throw. + * @param regExp Potential expected message match. + * @param message Message to display on error. + */ + throw(fn: Function, regExp: RegExp): void; + + /** + * Asserts that function will throw an error that is an instance of constructor. + * + * @param fn Function that may throw. + * @param constructor Potential expected error constructor. + * @param message Message to display on error. + */ + throw(fn: Function, constructor: Function, message?: string): void; + + /** + * Asserts that function will throw an error that is an instance of constructor + * and an error with message matching regexp. + * + * @param fn Function that may throw. + * @param constructor Potential expected error constructor. + * @param message Message to display on error. + */ + throw(fn: Function, constructor: Function, regExp: RegExp): void; + + /** + * Asserts that fn will throw an error. + * + * @param fn Function that may throw. + * @param message Message to display on error. + */ + throws(fn: Function, message?: string): void; + + /** + * Asserts that function will throw an error with message matching regexp. + * + * @param fn Function that may throw. + * @param regExp Potential expected message match. + * @param message Message to display on error. + */ + throws(fn: Function, regExp: RegExp, message?: string): void; + + /** + * Asserts that function will throw an error that is an instance of constructor. + * + * @param fn Function that may throw. + * @param constructor Potential expected error constructor. + * @param message Message to display on error. + */ + throws(fn: Function, errType: Function, message?: string): void; + + /** + * Asserts that function will throw an error that is an instance of constructor + * and an error with message matching regexp. + * + * @param fn Function that may throw. + * @param constructor Potential expected error constructor. + * @param message Message to display on error. + */ + throws(fn: Function, errType: Function, regExp: RegExp): void; + + /** + * Asserts that fn will throw an error. + * + * @param fn Function that may throw. + * @param message Message to display on error. + */ + Throw(fn: Function, message?: string): void; + + /** + * Asserts that function will throw an error with message matching regexp. + * + * @param fn Function that may throw. + * @param regExp Potential expected message match. + * @param message Message to display on error. + */ + Throw(fn: Function, regExp: RegExp): void; + + /** + * Asserts that function will throw an error that is an instance of constructor. + * + * @param fn Function that may throw. + * @param constructor Potential expected error constructor. + * @param message Message to display on error. + */ + Throw(fn: Function, errType: Function, message?: string): void; + + /** + * Asserts that function will throw an error that is an instance of constructor + * and an error with message matching regexp. + * + * @param fn Function that may throw. + * @param constructor Potential expected error constructor. + * @param message Message to display on error. + */ + Throw(fn: Function, errType: Function, regExp: RegExp): void; + + /** + * Asserts that fn will not throw an error. + * + * @param fn Function that may throw. + * @param message Message to display on error. + */ + doesNotThrow(fn: Function, message?: string): void; + + /** + * Asserts that function will throw an error with message matching regexp. + * + * @param fn Function that may throw. + * @param regExp Potential expected message match. + * @param message Message to display on error. + */ + doesNotThrow(fn: Function, regExp: RegExp): void; + + /** + * Asserts that function will throw an error that is an instance of constructor. + * + * @param fn Function that may throw. + * @param constructor Potential expected error constructor. + * @param message Message to display on error. + */ + doesNotThrow(fn: Function, errType: Function, message?: string): void; + + /** + * Asserts that function will throw an error that is an instance of constructor + * and an error with message matching regexp. + * + * @param fn Function that may throw. + * @param constructor Potential expected error constructor. + * @param message Message to display on error. + */ + doesNotThrow(fn: Function, errType: Function, regExp: RegExp): void; + + /** + * Compares two values using operator. + * + * @param val1 Left value during comparison. + * @param operator Comparison operator. + * @param val2 Right value during comparison. + * @param message Message to display on error. + */ + operator(val1: OperatorComparable, operator: Operator, val2: OperatorComparable, message?: string): void; + + /** + * Asserts that the target is equal to expected, to within a +/- delta range. + * + * @param actual Actual value + * @param expected Potential expected value. + * @param delta Maximum differenced between values. + * @param message Message to display on error. + */ + closeTo(actual: number, expected: number, delta: number, message?: string): void; + + /** + * Asserts that the target is equal to expected, to within a +/- delta range. + * + * @param actual Actual value + * @param expected Potential expected value. + * @param delta Maximum differenced between values. + * @param message Message to display on error. + */ + approximately(act: number, exp: number, delta: number, message?: string): void; + + /** + * Asserts that set1 and set2 have the same members. Order is not take into account. + * + * @type T Type of set values. + * @param set1 Actual set of values. + * @param set2 Potential expected set of values. + * @param message Message to display on error. + */ + sameMembers<T>(set1: T[], set2: T[], message?: string): void; + + /** + * Asserts that set1 and set2 have the same members using deep equality checking. + * Order is not take into account. + * + * @type T Type of set values. + * @param set1 Actual set of values. + * @param set2 Potential expected set of values. + * @param message Message to display on error. + */ + sameDeepMembers<T>(set1: T[], set2: T[], message?: string): void; + + /** + * Asserts that subset is included in superset. Order is not take into account. + * + * @type T Type of set values. + * @param superset Actual set of values. + * @param subset Potential contained set of values. + * @param message Message to display on error. + */ + includeMembers<T>(superset: T[], subset: T[], message?: string): void; + + /** + * Asserts that subset is included in superset using deep equality checking. + * Order is not take into account. + * + * @type T Type of set values. + * @param superset Actual set of values. + * @param subset Potential contained set of values. + * @param message Message to display on error. + */ + includeDeepMembers<T>(superset: T[], subset: T[], message?: string): void; + + /** + * Asserts that non-object, non-array value inList appears in the flat array list. + * + * @type T Type of list values. + * @param inList Value expected to be in the list. + * @param list List of values. + * @param message Message to display on error. + */ + oneOf<T>(inList: T, list: T[], message?: string): void; + + /** + * Asserts that a function changes the value of a property. + * + * @type T Type of object. + * @param modifier Function to run. + * @param object Container object. + * @param property Property of object expected to be modified. + * @param message Message to display on error. + */ + changes<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; + + /** + * Asserts that a function does not change the value of a property. + * + * @type T Type of object. + * @param modifier Function to run. + * @param object Container object. + * @param property Property of object expected not to be modified. + * @param message Message to display on error. + */ + doesNotChange<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; + + /** + * Asserts that a function increases an object property. + * + * @type T Type of object. + * @param modifier Function to run. + * @param object Container object. + * @param property Property of object expected to be increased. + * @param message Message to display on error. + */ + increases<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; + + /** + * Asserts that a function does not increase an object property. + * + * @type T Type of object. + * @param modifier Function to run. + * @param object Container object. + * @param property Property of object expected not to be increased. + * @param message Message to display on error. + */ + doesNotIncrease<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; + + /** + * Asserts that a function decreases an object property. + * + * @type T Type of object. + * @param modifier Function to run. + * @param object Container object. + * @param property Property of object expected to be decreased. + * @param message Message to display on error. + */ + decreases<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; + + /** + * Asserts that a function does not decrease an object property. + * + * @type T Type of object. + * @param modifier Function to run. + * @param object Container object. + * @param property Property of object expected not to be decreased. + * @param message Message to display on error. + */ + doesNotDecrease<T>(modifier: Function, object: T, property: string /* keyof T */, message?: string): void; + + /** + * Asserts if value is not a false value, and throws if it is a true value. + * + * @type T Type of object. + * @param object Actual value. + * @param message Message to display on error. + * @remarks This is added to allow for chai to be a drop-in replacement for + * Node’s assert class. + */ + ifError<T>(object: T, message?: string): void; + + /** + * Asserts that object is extensible (can have new properties added to it). + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + isExtensible<T>(object: T, message?: string): void; + + /** + * Asserts that object is extensible (can have new properties added to it). + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + extensible<T>(object: T, message?: string): void; + + /** + * Asserts that object is not extensible. + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + isNotExtensible<T>(object: T, message?: string): void; + + /** + * Asserts that object is not extensible. + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + notExtensible<T>(object: T, message?: string): void; + + /** + * Asserts that object is sealed (can have new properties added to it + * and its existing properties cannot be removed). + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + isSealed<T>(object: T, message?: string): void; + + /** + * Asserts that object is sealed (can have new properties added to it + * and its existing properties cannot be removed). + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + sealed<T>(object: T, message?: string): void; + + /** + * Asserts that object is not sealed. + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + isNotSealed<T>(object: T, message?: string): void; + + /** + * Asserts that object is not sealed. + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + notSealed<T>(object: T, message?: string): void; + + /** + * Asserts that object is frozen (cannot have new properties added to it + * and its existing properties cannot be removed). + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + isFrozen<T>(object: T, message?: string): void; + + /** + * Asserts that object is frozen (cannot have new properties added to it + * and its existing properties cannot be removed). + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + frozen<T>(object: T, message?: string): void; + + /** + * Asserts that object is not frozen (cannot have new properties added to it + * and its existing properties cannot be removed). + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + isNotFrozen<T>(object: T, message?: string): void; + + /** + * Asserts that object is not frozen (cannot have new properties added to it + * and its existing properties cannot be removed). + * + * @type T Type of object + * @param object Actual value. + * @param message Message to display on error. + */ + notFrozen<T>(object: T, message?: string): void; + } + + export interface Config { + /** + * Default: false + */ + includeStack: boolean; + + /** + * Default: true + */ + showDiff: boolean; + + /** + * Default: 40 + */ + truncateThreshold: number; + } + + export class AssertionError { + constructor(message: string, _props?: any, ssf?: Function); + public name: string; + public message: string; + public showDiff: boolean; + public stack: string; + } } declare const chai: Chai.ChaiStatic; declare module 'chai' { - export = chai; + export = chai; } interface Object { - should: Chai.Assertion; + should: Chai.Assertion; } diff --git a/packages/chai-typescript-typings/package.json b/packages/chai-typescript-typings/package.json index f2136ffa4..db7cba863 100644 --- a/packages/chai-typescript-typings/package.json +++ b/packages/chai-typescript-typings/package.json @@ -1,16 +1,16 @@ { - "name": "chai-typescript-typings", - "version": "0.0.2", - "description": "Typescript type definitions for chai", - "main": "index.d.ts", - "types": "index.d.ts", - "repository": { - "type": "git", - "url": "git+https://github.com/0xProject/0x.js.git" - }, - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/chai-typescript-typings#readme" + "name": "chai-typescript-typings", + "version": "0.0.2", + "description": "Typescript type definitions for chai", + "main": "index.d.ts", + "types": "index.d.ts", + "repository": { + "type": "git", + "url": "git+https://github.com/0xProject/0x.js.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/chai-typescript-typings#readme" } diff --git a/packages/chai-typescript-typings/tslint.json b/packages/chai-typescript-typings/tslint.json index ef528b22e..9a93a1f74 100644 --- a/packages/chai-typescript-typings/tslint.json +++ b/packages/chai-typescript-typings/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["tslint-config-0xproject"] + "extends": ["tslint-config-0xproject"] } diff --git a/packages/connect/package.json b/packages/connect/package.json index 2eff3f681..104e28803 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -1,64 +1,64 @@ { - "name": "@0xproject/connect", - "version": "0.5.2", - "description": "A javascript library for interacting with the standard relayer api", - "keywords": ["connect", "0xproject", "ethereum", "tokens", "exchange"], - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", - "scripts": { - "build": "tsc", - "clean": "shx rm -rf _bundles lib test_temp", - "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR", - "upload_docs_json": - "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json", - "copy_test_fixtures": "copyfiles -u 2 './test/fixtures/**/*.json' ./lib/test/fixtures", - "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", - "run_mocha": "mocha lib/test/**/*_test.js", - "test": "run-s clean build copy_test_fixtures run_mocha", - "test:circleci": "yarn test" - }, - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "author": "Brandon Millman", - "license": "Apache-2.0", - "engines": { - "node": ">=6.0.0" - }, - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/connect/README.md", - "dependencies": { - "@0xproject/assert": "^0.0.13", - "@0xproject/json-schemas": "^0.7.5", - "@0xproject/utils": "^0.2.2", - "isomorphic-fetch": "^2.2.1", - "lodash": "^4.17.4", - "query-string": "^5.0.1", - "websocket": "^1.0.25" - }, - "devDependencies": { - "@0xproject/tslint-config": "^0.4.4", - "@types/fetch-mock": "^5.12.1", - "@types/lodash": "^4.14.86", - "@types/mocha": "^2.2.42", - "@types/query-string": "^5.0.1", - "@types/websocket": "^0.0.34", - "chai": "^4.0.1", - "chai-as-promised": "^7.1.0", - "chai-as-promised-typescript-typings": "^0.0.6", - "chai-typescript-typings": "^0.0.2", - "copyfiles": "^1.2.0", - "dirty-chai": "^2.0.1", - "fetch-mock": "^5.13.1", - "mocha": "^4.0.1", - "npm-run-all": "^4.1.2", - "shx": "^0.2.2", - "tslint": "5.8.0", - "typedoc": "~0.8.0", - "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.6" - } + "name": "@0xproject/connect", + "version": "0.5.2", + "description": "A javascript library for interacting with the standard relayer api", + "keywords": ["connect", "0xproject", "ethereum", "tokens", "exchange"], + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "build": "tsc", + "clean": "shx rm -rf _bundles lib test_temp", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR", + "upload_docs_json": + "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json", + "copy_test_fixtures": "copyfiles -u 2 './test/fixtures/**/*.json' ./lib/test/fixtures", + "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", + "run_mocha": "mocha lib/test/**/*_test.js", + "test": "run-s clean build copy_test_fixtures run_mocha", + "test:circleci": "yarn test" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "author": "Brandon Millman", + "license": "Apache-2.0", + "engines": { + "node": ">=6.0.0" + }, + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/connect/README.md", + "dependencies": { + "@0xproject/assert": "^0.0.13", + "@0xproject/json-schemas": "^0.7.5", + "@0xproject/utils": "^0.2.2", + "isomorphic-fetch": "^2.2.1", + "lodash": "^4.17.4", + "query-string": "^5.0.1", + "websocket": "^1.0.25" + }, + "devDependencies": { + "@0xproject/tslint-config": "^0.4.4", + "@types/fetch-mock": "^5.12.1", + "@types/lodash": "^4.14.86", + "@types/mocha": "^2.2.42", + "@types/query-string": "^5.0.1", + "@types/websocket": "^0.0.34", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-as-promised-typescript-typings": "^0.0.6", + "chai-typescript-typings": "^0.0.2", + "copyfiles": "^1.2.0", + "dirty-chai": "^2.0.1", + "fetch-mock": "^5.13.1", + "mocha": "^4.0.1", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.8.0", + "typedoc": "~0.8.0", + "typescript": "~2.6.1", + "web3-typescript-typings": "^0.9.6" + } } diff --git a/packages/connect/src/globals.d.ts b/packages/connect/src/globals.d.ts index d2011026b..078e189cd 100644 --- a/packages/connect/src/globals.d.ts +++ b/packages/connect/src/globals.d.ts @@ -1,6 +1,6 @@ declare module 'dirty-chai'; declare module '*.json' { - const value: any; - export default value; + const value: any; + export default value; } diff --git a/packages/connect/src/http_client.ts b/packages/connect/src/http_client.ts index 03c0b9702..3df77b0f0 100644 --- a/packages/connect/src/http_client.ts +++ b/packages/connect/src/http_client.ts @@ -6,17 +6,17 @@ import * as queryString from 'query-string'; import { schemas as clientSchemas } from './schemas/schemas'; import { - Client, - FeesRequest, - FeesResponse, - HttpRequestOptions, - HttpRequestType, - OrderbookRequest, - OrderbookResponse, - OrdersRequest, - SignedOrder, - TokenPairsItem, - TokenPairsRequest, + Client, + FeesRequest, + FeesResponse, + HttpRequestOptions, + HttpRequestType, + OrderbookRequest, + OrderbookResponse, + OrdersRequest, + SignedOrder, + TokenPairsItem, + TokenPairsRequest, } from './types'; import { relayerResponseJsonParsers } from './utils/relayer_response_json_parsers'; @@ -26,127 +26,127 @@ const TRAILING_SLASHES_REGEX = /\/+$/; * that implement the standard relayer API v0 */ export class HttpClient implements Client { - private _apiEndpointUrl: string; - /** - * Instantiates a new HttpClient instance - * @param url The relayer API base HTTP url you would like to interact with - * @return An instance of HttpClient - */ - constructor(url: string) { - assert.isHttpUrl('url', url); - this._apiEndpointUrl = url.replace(TRAILING_SLASHES_REGEX, ''); // remove trailing slashes - } - /** - * Retrieve token pair info from the API - * @param request A TokenPairsRequest instance describing specific token information - * to retrieve - * @return The resulting TokenPairsItems that match the request - */ - public async getTokenPairsAsync(request?: TokenPairsRequest): Promise<TokenPairsItem[]> { - if (!_.isUndefined(request)) { - assert.doesConformToSchema('request', request, clientSchemas.relayerTokenPairsRequestSchema); - } - const requestOpts = { - params: request, - }; - const responseJson = await this._requestAsync('/token_pairs', HttpRequestType.Get, requestOpts); - const tokenPairs = relayerResponseJsonParsers.parseTokenPairsJson(responseJson); - return tokenPairs; - } - /** - * Retrieve orders from the API - * @param request An OrdersRequest instance describing specific orders to retrieve - * @return The resulting SignedOrders that match the request - */ - public async getOrdersAsync(request?: OrdersRequest): Promise<SignedOrder[]> { - if (!_.isUndefined(request)) { - assert.doesConformToSchema('request', request, clientSchemas.relayerOrdersRequestSchema); - } - const requestOpts = { - params: request, - }; - const responseJson = await this._requestAsync(`/orders`, HttpRequestType.Get, requestOpts); - const orders = relayerResponseJsonParsers.parseOrdersJson(responseJson); - return orders; - } - /** - * Retrieve a specific order from the API - * @param orderHash An orderHash generated from the desired order - * @return The SignedOrder that matches the supplied orderHash - */ - public async getOrderAsync(orderHash: string): Promise<SignedOrder> { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const responseJson = await this._requestAsync(`/order/${orderHash}`, HttpRequestType.Get); - const order = relayerResponseJsonParsers.parseOrderJson(responseJson); - return order; - } - /** - * Retrieve an orderbook from the API - * @param request An OrderbookRequest instance describing the specific orderbook to retrieve - * @return The resulting OrderbookResponse that matches the request - */ - public async getOrderbookAsync(request: OrderbookRequest): Promise<OrderbookResponse> { - assert.doesConformToSchema('request', request, clientSchemas.relayerOrderBookRequestSchema); - const requestOpts = { - params: request, - }; - const responseJson = await this._requestAsync('/orderbook', HttpRequestType.Get, requestOpts); - const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(responseJson); - return orderbook; - } - /** - * Retrieve fee information from the API - * @param request A FeesRequest instance describing the specific fees to retrieve - * @return The resulting FeesResponse that matches the request - */ - public async getFeesAsync(request: FeesRequest): Promise<FeesResponse> { - assert.doesConformToSchema('request', request, schemas.relayerApiFeesPayloadSchema); - const requestOpts = { - payload: request, - }; - const responseJson = await this._requestAsync('/fees', HttpRequestType.Post, requestOpts); - const fees = relayerResponseJsonParsers.parseFeesResponseJson(responseJson); - return fees; - } - /** - * Submit a signed order to the API - * @param signedOrder A SignedOrder instance to submit - */ - public async submitOrderAsync(signedOrder: SignedOrder): Promise<void> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - const requestOpts = { - payload: signedOrder, - }; - await this._requestAsync('/order', HttpRequestType.Post, requestOpts); - } - private async _requestAsync( - path: string, - requestType: HttpRequestType, - requestOptions?: HttpRequestOptions, - ): Promise<any> { - const params = _.get(requestOptions, 'params'); - const payload = _.get(requestOptions, 'payload'); - let query = ''; - if (!_.isUndefined(params) && !_.isEmpty(params)) { - const stringifiedParams = queryString.stringify(params); - query = `?${stringifiedParams}`; - } - const url = `${this._apiEndpointUrl}${path}${query}`; - const headers = new Headers({ - 'content-type': 'application/json', - }); - const response = await fetch(url, { - method: requestType, - body: JSON.stringify(payload), - headers, - }); - const json = await response.json(); - if (!response.ok) { - const errorString = `${response.status} - ${response.statusText}\n${requestType} ${url}\n${JSON.stringify( - json, - )}`; - throw Error(errorString); - } - return json; - } + private _apiEndpointUrl: string; + /** + * Instantiates a new HttpClient instance + * @param url The relayer API base HTTP url you would like to interact with + * @return An instance of HttpClient + */ + constructor(url: string) { + assert.isHttpUrl('url', url); + this._apiEndpointUrl = url.replace(TRAILING_SLASHES_REGEX, ''); // remove trailing slashes + } + /** + * Retrieve token pair info from the API + * @param request A TokenPairsRequest instance describing specific token information + * to retrieve + * @return The resulting TokenPairsItems that match the request + */ + public async getTokenPairsAsync(request?: TokenPairsRequest): Promise<TokenPairsItem[]> { + if (!_.isUndefined(request)) { + assert.doesConformToSchema('request', request, clientSchemas.relayerTokenPairsRequestSchema); + } + const requestOpts = { + params: request, + }; + const responseJson = await this._requestAsync('/token_pairs', HttpRequestType.Get, requestOpts); + const tokenPairs = relayerResponseJsonParsers.parseTokenPairsJson(responseJson); + return tokenPairs; + } + /** + * Retrieve orders from the API + * @param request An OrdersRequest instance describing specific orders to retrieve + * @return The resulting SignedOrders that match the request + */ + public async getOrdersAsync(request?: OrdersRequest): Promise<SignedOrder[]> { + if (!_.isUndefined(request)) { + assert.doesConformToSchema('request', request, clientSchemas.relayerOrdersRequestSchema); + } + const requestOpts = { + params: request, + }; + const responseJson = await this._requestAsync(`/orders`, HttpRequestType.Get, requestOpts); + const orders = relayerResponseJsonParsers.parseOrdersJson(responseJson); + return orders; + } + /** + * Retrieve a specific order from the API + * @param orderHash An orderHash generated from the desired order + * @return The SignedOrder that matches the supplied orderHash + */ + public async getOrderAsync(orderHash: string): Promise<SignedOrder> { + assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); + const responseJson = await this._requestAsync(`/order/${orderHash}`, HttpRequestType.Get); + const order = relayerResponseJsonParsers.parseOrderJson(responseJson); + return order; + } + /** + * Retrieve an orderbook from the API + * @param request An OrderbookRequest instance describing the specific orderbook to retrieve + * @return The resulting OrderbookResponse that matches the request + */ + public async getOrderbookAsync(request: OrderbookRequest): Promise<OrderbookResponse> { + assert.doesConformToSchema('request', request, clientSchemas.relayerOrderBookRequestSchema); + const requestOpts = { + params: request, + }; + const responseJson = await this._requestAsync('/orderbook', HttpRequestType.Get, requestOpts); + const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(responseJson); + return orderbook; + } + /** + * Retrieve fee information from the API + * @param request A FeesRequest instance describing the specific fees to retrieve + * @return The resulting FeesResponse that matches the request + */ + public async getFeesAsync(request: FeesRequest): Promise<FeesResponse> { + assert.doesConformToSchema('request', request, schemas.relayerApiFeesPayloadSchema); + const requestOpts = { + payload: request, + }; + const responseJson = await this._requestAsync('/fees', HttpRequestType.Post, requestOpts); + const fees = relayerResponseJsonParsers.parseFeesResponseJson(responseJson); + return fees; + } + /** + * Submit a signed order to the API + * @param signedOrder A SignedOrder instance to submit + */ + public async submitOrderAsync(signedOrder: SignedOrder): Promise<void> { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + const requestOpts = { + payload: signedOrder, + }; + await this._requestAsync('/order', HttpRequestType.Post, requestOpts); + } + private async _requestAsync( + path: string, + requestType: HttpRequestType, + requestOptions?: HttpRequestOptions, + ): Promise<any> { + const params = _.get(requestOptions, 'params'); + const payload = _.get(requestOptions, 'payload'); + let query = ''; + if (!_.isUndefined(params) && !_.isEmpty(params)) { + const stringifiedParams = queryString.stringify(params); + query = `?${stringifiedParams}`; + } + const url = `${this._apiEndpointUrl}${path}${query}`; + const headers = new Headers({ + 'content-type': 'application/json', + }); + const response = await fetch(url, { + method: requestType, + body: JSON.stringify(payload), + headers, + }); + const json = await response.json(); + if (!response.ok) { + const errorString = `${response.status} - ${response.statusText}\n${requestType} ${url}\n${JSON.stringify( + json, + )}`; + throw Error(errorString); + } + return json; + } } diff --git a/packages/connect/src/index.ts b/packages/connect/src/index.ts index d0cce9935..a492f5ae9 100644 --- a/packages/connect/src/index.ts +++ b/packages/connect/src/index.ts @@ -1,19 +1,19 @@ export { HttpClient } from './http_client'; export { WebSocketOrderbookChannel } from './ws_orderbook_channel'; export { - Client, - ECSignature, - FeesRequest, - FeesResponse, - Order, - OrderbookChannel, - OrderbookChannelHandler, - OrderbookChannelSubscriptionOpts, - OrderbookRequest, - OrderbookResponse, - OrdersRequest, - SignedOrder, - TokenPairsItem, - TokenPairsRequest, - TokenTradeInfo, + Client, + ECSignature, + FeesRequest, + FeesResponse, + Order, + OrderbookChannel, + OrderbookChannelHandler, + OrderbookChannelSubscriptionOpts, + OrderbookRequest, + OrderbookResponse, + OrdersRequest, + SignedOrder, + TokenPairsItem, + TokenPairsRequest, + TokenTradeInfo, } from './types'; diff --git a/packages/connect/src/schemas/relayer_fees_request_schema.ts b/packages/connect/src/schemas/relayer_fees_request_schema.ts index bd0b91fa3..f20e077ba 100644 --- a/packages/connect/src/schemas/relayer_fees_request_schema.ts +++ b/packages/connect/src/schemas/relayer_fees_request_schema.ts @@ -1,8 +1,8 @@ export const relayerOrderBookRequestSchema = { - id: '/RelayerOrderBookRequest', - type: 'object', - properties: { - baseTokenAddress: { $ref: '/Address' }, - quoteTokenAddress: { $ref: '/Address' }, - }, + id: '/RelayerOrderBookRequest', + type: 'object', + properties: { + baseTokenAddress: { $ref: '/Address' }, + quoteTokenAddress: { $ref: '/Address' }, + }, }; diff --git a/packages/connect/src/schemas/relayer_orderbook_request_schema.ts b/packages/connect/src/schemas/relayer_orderbook_request_schema.ts index bd0b91fa3..f20e077ba 100644 --- a/packages/connect/src/schemas/relayer_orderbook_request_schema.ts +++ b/packages/connect/src/schemas/relayer_orderbook_request_schema.ts @@ -1,8 +1,8 @@ export const relayerOrderBookRequestSchema = { - id: '/RelayerOrderBookRequest', - type: 'object', - properties: { - baseTokenAddress: { $ref: '/Address' }, - quoteTokenAddress: { $ref: '/Address' }, - }, + id: '/RelayerOrderBookRequest', + type: 'object', + properties: { + baseTokenAddress: { $ref: '/Address' }, + quoteTokenAddress: { $ref: '/Address' }, + }, }; diff --git a/packages/connect/src/schemas/relayer_orders_request_schema.ts b/packages/connect/src/schemas/relayer_orders_request_schema.ts index 0471cb104..570238dae 100644 --- a/packages/connect/src/schemas/relayer_orders_request_schema.ts +++ b/packages/connect/src/schemas/relayer_orders_request_schema.ts @@ -1,16 +1,16 @@ export const relayerOrdersRequestSchema = { - id: '/RelayerOrdersRequest', - type: 'object', - properties: { - exchangeContractAddress: { $ref: '/Address' }, - tokenAddress: { $ref: '/Address' }, - makerTokenAddress: { $ref: '/Address' }, - takerTokenAddress: { $ref: '/Address' }, - tokenA: { $ref: '/Address' }, - tokenB: { $ref: '/Address' }, - maker: { $ref: '/Address' }, - taker: { $ref: '/Address' }, - trader: { $ref: '/Address' }, - feeRecipient: { $ref: '/Address' }, - }, + id: '/RelayerOrdersRequest', + type: 'object', + properties: { + exchangeContractAddress: { $ref: '/Address' }, + tokenAddress: { $ref: '/Address' }, + makerTokenAddress: { $ref: '/Address' }, + takerTokenAddress: { $ref: '/Address' }, + tokenA: { $ref: '/Address' }, + tokenB: { $ref: '/Address' }, + maker: { $ref: '/Address' }, + taker: { $ref: '/Address' }, + trader: { $ref: '/Address' }, + feeRecipient: { $ref: '/Address' }, + }, }; diff --git a/packages/connect/src/schemas/relayer_token_pairs_request_schema.ts b/packages/connect/src/schemas/relayer_token_pairs_request_schema.ts index cb79efdb3..379232204 100644 --- a/packages/connect/src/schemas/relayer_token_pairs_request_schema.ts +++ b/packages/connect/src/schemas/relayer_token_pairs_request_schema.ts @@ -1,8 +1,8 @@ export const relayerTokenPairsRequestSchema = { - id: '/RelayerTokenPairsRequest', - type: 'object', - properties: { - tokenA: { $ref: '/Address' }, - tokenB: { $ref: '/Address' }, - }, + id: '/RelayerTokenPairsRequest', + type: 'object', + properties: { + tokenA: { $ref: '/Address' }, + tokenB: { $ref: '/Address' }, + }, }; diff --git a/packages/connect/src/schemas/schemas.ts b/packages/connect/src/schemas/schemas.ts index 2982d81ab..288d6969d 100644 --- a/packages/connect/src/schemas/schemas.ts +++ b/packages/connect/src/schemas/schemas.ts @@ -3,7 +3,7 @@ import { relayerOrdersRequestSchema } from './relayer_orders_request_schema'; import { relayerTokenPairsRequestSchema } from './relayer_token_pairs_request_schema'; export const schemas = { - relayerOrderBookRequestSchema, - relayerOrdersRequestSchema, - relayerTokenPairsRequestSchema, + relayerOrderBookRequestSchema, + relayerOrdersRequestSchema, + relayerTokenPairsRequestSchema, }; diff --git a/packages/connect/src/types.ts b/packages/connect/src/types.ts index 5f72f6e85..edb6c77a6 100644 --- a/packages/connect/src/types.ts +++ b/packages/connect/src/types.ts @@ -2,45 +2,45 @@ import { BigNumber } from '@0xproject/utils'; // TODO: Consolidate Order, SignedOrder and ECSignature into a shared package instead of duplicating them from 0x.js export interface Order { - maker: string; - taker: string; - makerFee: BigNumber; - takerFee: BigNumber; - makerTokenAmount: BigNumber; - takerTokenAmount: BigNumber; - makerTokenAddress: string; - takerTokenAddress: string; - salt: BigNumber; - exchangeContractAddress: string; - feeRecipient: string; - expirationUnixTimestampSec: BigNumber; + maker: string; + taker: string; + makerFee: BigNumber; + takerFee: BigNumber; + makerTokenAmount: BigNumber; + takerTokenAmount: BigNumber; + makerTokenAddress: string; + takerTokenAddress: string; + salt: BigNumber; + exchangeContractAddress: string; + feeRecipient: string; + expirationUnixTimestampSec: BigNumber; } export interface SignedOrder extends Order { - ecSignature: ECSignature; + ecSignature: ECSignature; } /** * Elliptic Curve signature */ export interface ECSignature { - v: number; - r: string; - s: string; + v: number; + r: string; + s: string; } export interface Client { - getTokenPairsAsync: (request?: TokenPairsRequest) => Promise<TokenPairsItem[]>; - getOrdersAsync: (request?: OrdersRequest) => Promise<SignedOrder[]>; - getOrderAsync: (orderHash: string) => Promise<SignedOrder>; - getOrderbookAsync: (request: OrderbookRequest) => Promise<OrderbookResponse>; - getFeesAsync: (request: FeesRequest) => Promise<FeesResponse>; - submitOrderAsync: (signedOrder: SignedOrder) => Promise<void>; + getTokenPairsAsync: (request?: TokenPairsRequest) => Promise<TokenPairsItem[]>; + getOrdersAsync: (request?: OrdersRequest) => Promise<SignedOrder[]>; + getOrderAsync: (orderHash: string) => Promise<SignedOrder>; + getOrderbookAsync: (request: OrderbookRequest) => Promise<OrderbookResponse>; + getFeesAsync: (request: FeesRequest) => Promise<FeesResponse>; + submitOrderAsync: (signedOrder: SignedOrder) => Promise<void>; } export interface OrderbookChannel { - subscribe: (subscriptionOpts: OrderbookChannelSubscriptionOpts, handler: OrderbookChannelHandler) => void; - close: () => void; + subscribe: (subscriptionOpts: OrderbookChannelSubscriptionOpts, handler: OrderbookChannelHandler) => void; + close: () => void; } /* @@ -50,129 +50,129 @@ export interface OrderbookChannel { * limit: Maximum number of bids and asks in orderbook snapshot */ export interface OrderbookChannelSubscriptionOpts { - baseTokenAddress: string; - quoteTokenAddress: string; - snapshot: boolean; - limit: number; + baseTokenAddress: string; + quoteTokenAddress: string; + snapshot: boolean; + limit: number; } export interface OrderbookChannelHandler { - onSnapshot: ( - channel: OrderbookChannel, - subscriptionOpts: OrderbookChannelSubscriptionOpts, - snapshot: OrderbookResponse, - ) => void; - onUpdate: ( - channel: OrderbookChannel, - subscriptionOpts: OrderbookChannelSubscriptionOpts, - order: SignedOrder, - ) => void; - onError: (channel: OrderbookChannel, subscriptionOpts: OrderbookChannelSubscriptionOpts, err: Error) => void; - onClose: (channel: OrderbookChannel, subscriptionOpts: OrderbookChannelSubscriptionOpts) => void; + onSnapshot: ( + channel: OrderbookChannel, + subscriptionOpts: OrderbookChannelSubscriptionOpts, + snapshot: OrderbookResponse, + ) => void; + onUpdate: ( + channel: OrderbookChannel, + subscriptionOpts: OrderbookChannelSubscriptionOpts, + order: SignedOrder, + ) => void; + onError: (channel: OrderbookChannel, subscriptionOpts: OrderbookChannelSubscriptionOpts, err: Error) => void; + onClose: (channel: OrderbookChannel, subscriptionOpts: OrderbookChannelSubscriptionOpts) => void; } export type OrderbookChannelMessage = - | SnapshotOrderbookChannelMessage - | UpdateOrderbookChannelMessage - | UnknownOrderbookChannelMessage; + | SnapshotOrderbookChannelMessage + | UpdateOrderbookChannelMessage + | UnknownOrderbookChannelMessage; export enum OrderbookChannelMessageTypes { - Snapshot = 'snapshot', - Update = 'update', - Unknown = 'unknown', + Snapshot = 'snapshot', + Update = 'update', + Unknown = 'unknown', } export interface SnapshotOrderbookChannelMessage { - type: OrderbookChannelMessageTypes.Snapshot; - requestId: number; - payload: OrderbookResponse; + type: OrderbookChannelMessageTypes.Snapshot; + requestId: number; + payload: OrderbookResponse; } export interface UpdateOrderbookChannelMessage { - type: OrderbookChannelMessageTypes.Update; - requestId: number; - payload: SignedOrder; + type: OrderbookChannelMessageTypes.Update; + requestId: number; + payload: SignedOrder; } export interface UnknownOrderbookChannelMessage { - type: OrderbookChannelMessageTypes.Unknown; - requestId: number; - payload: undefined; + type: OrderbookChannelMessageTypes.Unknown; + requestId: number; + payload: undefined; } export enum WebsocketConnectionEventType { - Close = 'close', - Error = 'error', - Message = 'message', + Close = 'close', + Error = 'error', + Message = 'message', } export enum WebsocketClientEventType { - Connect = 'connect', - ConnectFailed = 'connectFailed', + Connect = 'connect', + ConnectFailed = 'connectFailed', } export interface TokenPairsRequest { - tokenA?: string; - tokenB?: string; + tokenA?: string; + tokenB?: string; } export interface TokenPairsItem { - tokenA: TokenTradeInfo; - tokenB: TokenTradeInfo; + tokenA: TokenTradeInfo; + tokenB: TokenTradeInfo; } export interface TokenTradeInfo { - address: string; - minAmount: BigNumber; - maxAmount: BigNumber; - precision: number; + address: string; + minAmount: BigNumber; + maxAmount: BigNumber; + precision: number; } export interface OrdersRequest { - exchangeContractAddress?: string; - tokenAddress?: string; - makerTokenAddress?: string; - takerTokenAddress?: string; - maker?: string; - taker?: string; - trader?: string; - feeRecipient?: string; + exchangeContractAddress?: string; + tokenAddress?: string; + makerTokenAddress?: string; + takerTokenAddress?: string; + maker?: string; + taker?: string; + trader?: string; + feeRecipient?: string; } export interface OrderbookRequest { - baseTokenAddress: string; - quoteTokenAddress: string; + baseTokenAddress: string; + quoteTokenAddress: string; } export interface OrderbookResponse { - bids: SignedOrder[]; - asks: SignedOrder[]; + bids: SignedOrder[]; + asks: SignedOrder[]; } export interface FeesRequest { - exchangeContractAddress: string; - maker: string; - taker: string; - makerTokenAddress: string; - takerTokenAddress: string; - makerTokenAmount: BigNumber; - takerTokenAmount: BigNumber; - expirationUnixTimestampSec: BigNumber; - salt: BigNumber; + exchangeContractAddress: string; + maker: string; + taker: string; + makerTokenAddress: string; + takerTokenAddress: string; + makerTokenAmount: BigNumber; + takerTokenAmount: BigNumber; + expirationUnixTimestampSec: BigNumber; + salt: BigNumber; } export interface FeesResponse { - feeRecipient: string; - makerFee: BigNumber; - takerFee: BigNumber; + feeRecipient: string; + makerFee: BigNumber; + takerFee: BigNumber; } export interface HttpRequestOptions { - params?: object; - payload?: object; + params?: object; + payload?: object; } export enum HttpRequestType { - Get = 'GET', - Post = 'POST', + Get = 'GET', + Post = 'POST', } diff --git a/packages/connect/src/utils/orderbook_channel_message_parser.ts b/packages/connect/src/utils/orderbook_channel_message_parser.ts index c5dd2ee2e..9a9ca8901 100644 --- a/packages/connect/src/utils/orderbook_channel_message_parser.ts +++ b/packages/connect/src/utils/orderbook_channel_message_parser.ts @@ -7,31 +7,31 @@ import { OrderbookChannelMessage, OrderbookChannelMessageTypes } from '../types' import { relayerResponseJsonParsers } from './relayer_response_json_parsers'; export const orderbookChannelMessageParser = { - parse(utf8Data: string): OrderbookChannelMessage { - const messageObj = JSON.parse(utf8Data); - const type: string = _.get(messageObj, 'type'); - assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`); - assert.isString('type', type); - switch (type) { - case OrderbookChannelMessageTypes.Snapshot: { - assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelSnapshotSchema); - const orderbookJson = messageObj.payload; - const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(orderbookJson); - return _.assign(messageObj, { payload: orderbook }); - } - case OrderbookChannelMessageTypes.Update: { - assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelUpdateSchema); - const orderJson = messageObj.payload; - const order = relayerResponseJsonParsers.parseOrderJson(orderJson); - return _.assign(messageObj, { payload: order }); - } - default: { - return { - type: OrderbookChannelMessageTypes.Unknown, - requestId: 0, - payload: undefined, - }; - } - } - }, + parse(utf8Data: string): OrderbookChannelMessage { + const messageObj = JSON.parse(utf8Data); + const type: string = _.get(messageObj, 'type'); + assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`); + assert.isString('type', type); + switch (type) { + case OrderbookChannelMessageTypes.Snapshot: { + assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelSnapshotSchema); + const orderbookJson = messageObj.payload; + const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(orderbookJson); + return _.assign(messageObj, { payload: orderbook }); + } + case OrderbookChannelMessageTypes.Update: { + assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelUpdateSchema); + const orderJson = messageObj.payload; + const order = relayerResponseJsonParsers.parseOrderJson(orderJson); + return _.assign(messageObj, { payload: order }); + } + default: { + return { + type: OrderbookChannelMessageTypes.Unknown, + requestId: 0, + payload: undefined, + }; + } + } + }, }; diff --git a/packages/connect/src/utils/relayer_response_json_parsers.ts b/packages/connect/src/utils/relayer_response_json_parsers.ts index b6c5decaf..668461bf4 100644 --- a/packages/connect/src/utils/relayer_response_json_parsers.ts +++ b/packages/connect/src/utils/relayer_response_json_parsers.ts @@ -7,31 +7,31 @@ import { FeesResponse, OrderbookResponse, SignedOrder, TokenPairsItem } from '.. import { typeConverters } from './type_converters'; export const relayerResponseJsonParsers = { - parseTokenPairsJson(json: any): TokenPairsItem[] { - assert.doesConformToSchema('tokenPairs', json, schemas.relayerApiTokenPairsResponseSchema); - return json.map((tokenPair: any) => { - return typeConverters.convertStringsFieldsToBigNumbers(tokenPair, [ - 'tokenA.minAmount', - 'tokenA.maxAmount', - 'tokenB.minAmount', - 'tokenB.maxAmount', - ]); - }); - }, - parseOrdersJson(json: any): SignedOrder[] { - assert.doesConformToSchema('orders', json, schemas.signedOrdersSchema); - return json.map((order: object) => typeConverters.convertOrderStringFieldsToBigNumber(order)); - }, - parseOrderJson(json: any): SignedOrder { - assert.doesConformToSchema('order', json, schemas.signedOrderSchema); - return typeConverters.convertOrderStringFieldsToBigNumber(json); - }, - parseOrderbookResponseJson(json: any): OrderbookResponse { - assert.doesConformToSchema('orderBook', json, schemas.relayerApiOrderBookResponseSchema); - return typeConverters.convertOrderbookStringFieldsToBigNumber(json); - }, - parseFeesResponseJson(json: any): FeesResponse { - assert.doesConformToSchema('fees', json, schemas.relayerApiFeesResponseSchema); - return typeConverters.convertStringsFieldsToBigNumbers(json, ['makerFee', 'takerFee']); - }, + parseTokenPairsJson(json: any): TokenPairsItem[] { + assert.doesConformToSchema('tokenPairs', json, schemas.relayerApiTokenPairsResponseSchema); + return json.map((tokenPair: any) => { + return typeConverters.convertStringsFieldsToBigNumbers(tokenPair, [ + 'tokenA.minAmount', + 'tokenA.maxAmount', + 'tokenB.minAmount', + 'tokenB.maxAmount', + ]); + }); + }, + parseOrdersJson(json: any): SignedOrder[] { + assert.doesConformToSchema('orders', json, schemas.signedOrdersSchema); + return json.map((order: object) => typeConverters.convertOrderStringFieldsToBigNumber(order)); + }, + parseOrderJson(json: any): SignedOrder { + assert.doesConformToSchema('order', json, schemas.signedOrderSchema); + return typeConverters.convertOrderStringFieldsToBigNumber(json); + }, + parseOrderbookResponseJson(json: any): OrderbookResponse { + assert.doesConformToSchema('orderBook', json, schemas.relayerApiOrderBookResponseSchema); + return typeConverters.convertOrderbookStringFieldsToBigNumber(json); + }, + parseFeesResponseJson(json: any): FeesResponse { + assert.doesConformToSchema('fees', json, schemas.relayerApiFeesResponseSchema); + return typeConverters.convertStringsFieldsToBigNumbers(json, ['makerFee', 'takerFee']); + }, }; diff --git a/packages/connect/src/utils/type_converters.ts b/packages/connect/src/utils/type_converters.ts index beea557c2..c1808ce8a 100644 --- a/packages/connect/src/utils/type_converters.ts +++ b/packages/connect/src/utils/type_converters.ts @@ -2,29 +2,29 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; export const typeConverters = { - convertOrderbookStringFieldsToBigNumber(orderbook: any): any { - const bids = _.get(orderbook, 'bids', []); - const asks = _.get(orderbook, 'asks', []); - return { - bids: bids.map((order: any) => this.convertOrderStringFieldsToBigNumber(order)), - asks: asks.map((order: any) => this.convertOrderStringFieldsToBigNumber(order)), - }; - }, - convertOrderStringFieldsToBigNumber(order: any): any { - return this.convertStringsFieldsToBigNumbers(order, [ - 'makerTokenAmount', - 'takerTokenAmount', - 'makerFee', - 'takerFee', - 'expirationUnixTimestampSec', - 'salt', - ]); - }, - convertStringsFieldsToBigNumbers(obj: any, fields: string[]): any { - const result = _.assign({}, obj); - _.each(fields, field => { - _.update(result, field, (value: string) => new BigNumber(value)); - }); - return result; - }, + convertOrderbookStringFieldsToBigNumber(orderbook: any): any { + const bids = _.get(orderbook, 'bids', []); + const asks = _.get(orderbook, 'asks', []); + return { + bids: bids.map((order: any) => this.convertOrderStringFieldsToBigNumber(order)), + asks: asks.map((order: any) => this.convertOrderStringFieldsToBigNumber(order)), + }; + }, + convertOrderStringFieldsToBigNumber(order: any): any { + return this.convertStringsFieldsToBigNumbers(order, [ + 'makerTokenAmount', + 'takerTokenAmount', + 'makerFee', + 'takerFee', + 'expirationUnixTimestampSec', + 'salt', + ]); + }, + convertStringsFieldsToBigNumbers(obj: any, fields: string[]): any { + const result = _.assign({}, obj); + _.each(fields, field => { + _.update(result, field, (value: string) => new BigNumber(value)); + }); + return result; + }, }; diff --git a/packages/connect/src/ws_orderbook_channel.ts b/packages/connect/src/ws_orderbook_channel.ts index bbb47c649..822a022f4 100644 --- a/packages/connect/src/ws_orderbook_channel.ts +++ b/packages/connect/src/ws_orderbook_channel.ts @@ -4,12 +4,12 @@ import * as _ from 'lodash'; import * as WebSocket from 'websocket'; import { - OrderbookChannel, - OrderbookChannelHandler, - OrderbookChannelMessageTypes, - OrderbookChannelSubscriptionOpts, - WebsocketClientEventType, - WebsocketConnectionEventType, + OrderbookChannel, + OrderbookChannelHandler, + OrderbookChannelMessageTypes, + OrderbookChannelSubscriptionOpts, + WebsocketClientEventType, + WebsocketConnectionEventType, } from './types'; import { orderbookChannelMessageParser } from './utils/orderbook_channel_message_parser'; @@ -18,117 +18,117 @@ import { orderbookChannelMessageParser } from './utils/orderbook_channel_message * that implements the standard relayer API v0 */ export class WebSocketOrderbookChannel implements OrderbookChannel { - private _apiEndpointUrl: string; - private _client: WebSocket.client; - private _connectionIfExists?: WebSocket.connection; - private _subscriptionCounter = 0; - /** - * Instantiates a new WebSocketOrderbookChannel instance - * @param url The relayer API base WS url you would like to interact with - * @return An instance of WebSocketOrderbookChannel - */ - constructor(url: string) { - assert.isUri('url', url); - this._apiEndpointUrl = url; - this._client = new WebSocket.client(); - } - /** - * Subscribe to orderbook snapshots and updates from the websocket - * @param subscriptionOpts An OrderbookChannelSubscriptionOpts instance describing which - * token pair to subscribe to - * @param handler An OrderbookChannelHandler instance that responds to various - * channel updates - */ - public subscribe(subscriptionOpts: OrderbookChannelSubscriptionOpts, handler: OrderbookChannelHandler): void { - assert.doesConformToSchema( - 'subscriptionOpts', - subscriptionOpts, - schemas.relayerApiOrderbookChannelSubscribePayload, - ); - assert.isFunction('handler.onSnapshot', _.get(handler, 'onSnapshot')); - assert.isFunction('handler.onUpdate', _.get(handler, 'onUpdate')); - assert.isFunction('handler.onError', _.get(handler, 'onError')); - assert.isFunction('handler.onClose', _.get(handler, 'onClose')); - this._subscriptionCounter += 1; - const subscribeMessage = { - type: 'subscribe', - channel: 'orderbook', - requestId: this._subscriptionCounter, - payload: subscriptionOpts, - }; - this._getConnection((error, connection) => { - if (!_.isUndefined(error)) { - handler.onError(this, subscriptionOpts, error); - } else if (!_.isUndefined(connection) && connection.connected) { - connection.on(WebsocketConnectionEventType.Error, wsError => { - handler.onError(this, subscriptionOpts, wsError); - }); - connection.on(WebsocketConnectionEventType.Close, () => { - handler.onClose(this, subscriptionOpts); - }); - connection.on(WebsocketConnectionEventType.Message, message => { - this._handleWebSocketMessage(subscribeMessage.requestId, subscriptionOpts, message, handler); - }); - connection.sendUTF(JSON.stringify(subscribeMessage)); - } - }); - } - /** - * Close the websocket and stop receiving updates - */ - public close() { - if (!_.isUndefined(this._connectionIfExists)) { - this._connectionIfExists.close(); - } - } - private _getConnection(callback: (error?: Error, connection?: WebSocket.connection) => void) { - if (!_.isUndefined(this._connectionIfExists) && this._connectionIfExists.connected) { - callback(undefined, this._connectionIfExists); - } else { - this._client.on(WebsocketClientEventType.Connect, connection => { - this._connectionIfExists = connection; - callback(undefined, this._connectionIfExists); - }); - this._client.on(WebsocketClientEventType.ConnectFailed, error => { - callback(error, undefined); - }); - this._client.connect(this._apiEndpointUrl); - } - } - private _handleWebSocketMessage( - requestId: number, - subscriptionOpts: OrderbookChannelSubscriptionOpts, - message: WebSocket.IMessage, - handler: OrderbookChannelHandler, - ): void { - if (!_.isUndefined(message.utf8Data)) { - try { - const utf8Data = message.utf8Data; - const parserResult = orderbookChannelMessageParser.parse(utf8Data); - if (parserResult.requestId === requestId) { - switch (parserResult.type) { - case OrderbookChannelMessageTypes.Snapshot: { - handler.onSnapshot(this, subscriptionOpts, parserResult.payload); - break; - } - case OrderbookChannelMessageTypes.Update: { - handler.onUpdate(this, subscriptionOpts, parserResult.payload); - break; - } - default: { - handler.onError( - this, - subscriptionOpts, - new Error(`Message has missing a type parameter: ${utf8Data}`), - ); - } - } - } - } catch (error) { - handler.onError(this, subscriptionOpts, error); - } - } else { - handler.onError(this, subscriptionOpts, new Error(`Message does not contain utf8Data`)); - } - } + private _apiEndpointUrl: string; + private _client: WebSocket.client; + private _connectionIfExists?: WebSocket.connection; + private _subscriptionCounter = 0; + /** + * Instantiates a new WebSocketOrderbookChannel instance + * @param url The relayer API base WS url you would like to interact with + * @return An instance of WebSocketOrderbookChannel + */ + constructor(url: string) { + assert.isUri('url', url); + this._apiEndpointUrl = url; + this._client = new WebSocket.client(); + } + /** + * Subscribe to orderbook snapshots and updates from the websocket + * @param subscriptionOpts An OrderbookChannelSubscriptionOpts instance describing which + * token pair to subscribe to + * @param handler An OrderbookChannelHandler instance that responds to various + * channel updates + */ + public subscribe(subscriptionOpts: OrderbookChannelSubscriptionOpts, handler: OrderbookChannelHandler): void { + assert.doesConformToSchema( + 'subscriptionOpts', + subscriptionOpts, + schemas.relayerApiOrderbookChannelSubscribePayload, + ); + assert.isFunction('handler.onSnapshot', _.get(handler, 'onSnapshot')); + assert.isFunction('handler.onUpdate', _.get(handler, 'onUpdate')); + assert.isFunction('handler.onError', _.get(handler, 'onError')); + assert.isFunction('handler.onClose', _.get(handler, 'onClose')); + this._subscriptionCounter += 1; + const subscribeMessage = { + type: 'subscribe', + channel: 'orderbook', + requestId: this._subscriptionCounter, + payload: subscriptionOpts, + }; + this._getConnection((error, connection) => { + if (!_.isUndefined(error)) { + handler.onError(this, subscriptionOpts, error); + } else if (!_.isUndefined(connection) && connection.connected) { + connection.on(WebsocketConnectionEventType.Error, wsError => { + handler.onError(this, subscriptionOpts, wsError); + }); + connection.on(WebsocketConnectionEventType.Close, () => { + handler.onClose(this, subscriptionOpts); + }); + connection.on(WebsocketConnectionEventType.Message, message => { + this._handleWebSocketMessage(subscribeMessage.requestId, subscriptionOpts, message, handler); + }); + connection.sendUTF(JSON.stringify(subscribeMessage)); + } + }); + } + /** + * Close the websocket and stop receiving updates + */ + public close() { + if (!_.isUndefined(this._connectionIfExists)) { + this._connectionIfExists.close(); + } + } + private _getConnection(callback: (error?: Error, connection?: WebSocket.connection) => void) { + if (!_.isUndefined(this._connectionIfExists) && this._connectionIfExists.connected) { + callback(undefined, this._connectionIfExists); + } else { + this._client.on(WebsocketClientEventType.Connect, connection => { + this._connectionIfExists = connection; + callback(undefined, this._connectionIfExists); + }); + this._client.on(WebsocketClientEventType.ConnectFailed, error => { + callback(error, undefined); + }); + this._client.connect(this._apiEndpointUrl); + } + } + private _handleWebSocketMessage( + requestId: number, + subscriptionOpts: OrderbookChannelSubscriptionOpts, + message: WebSocket.IMessage, + handler: OrderbookChannelHandler, + ): void { + if (!_.isUndefined(message.utf8Data)) { + try { + const utf8Data = message.utf8Data; + const parserResult = orderbookChannelMessageParser.parse(utf8Data); + if (parserResult.requestId === requestId) { + switch (parserResult.type) { + case OrderbookChannelMessageTypes.Snapshot: { + handler.onSnapshot(this, subscriptionOpts, parserResult.payload); + break; + } + case OrderbookChannelMessageTypes.Update: { + handler.onUpdate(this, subscriptionOpts, parserResult.payload); + break; + } + default: { + handler.onError( + this, + subscriptionOpts, + new Error(`Message has missing a type parameter: ${utf8Data}`), + ); + } + } + } + } catch (error) { + handler.onError(this, subscriptionOpts, error); + } + } else { + handler.onError(this, subscriptionOpts, new Error(`Message does not contain utf8Data`)); + } + } } diff --git a/packages/connect/test/fixtures/standard_relayer_api/fees.json b/packages/connect/test/fixtures/standard_relayer_api/fees.json index 9f24e93d2..483a74254 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/fees.json +++ b/packages/connect/test/fixtures/standard_relayer_api/fees.json @@ -1,5 +1,5 @@ { - "feeRecipient": "0x323b5d4c32345ced77393b3530b1eed0f346429d", - "makerFee": "10000000000000000", - "takerFee": "30000000000000000" + "feeRecipient": "0x323b5d4c32345ced77393b3530b1eed0f346429d", + "makerFee": "10000000000000000", + "takerFee": "30000000000000000" } diff --git a/packages/connect/test/fixtures/standard_relayer_api/fees.ts b/packages/connect/test/fixtures/standard_relayer_api/fees.ts index 03981031a..fecbaacff 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/fees.ts +++ b/packages/connect/test/fixtures/standard_relayer_api/fees.ts @@ -3,7 +3,7 @@ import { BigNumber } from '@0xproject/utils'; import { FeesResponse } from '../../../src/types'; export const feesResponse: FeesResponse = { - feeRecipient: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - makerFee: new BigNumber('10000000000000000'), - takerFee: new BigNumber('30000000000000000'), + feeRecipient: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + makerFee: new BigNumber('10000000000000000'), + takerFee: new BigNumber('30000000000000000'), }; diff --git a/packages/connect/test/fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.json b/packages/connect/test/fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.json index c0423bf49..e84954b0d 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.json +++ b/packages/connect/test/fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.json @@ -1,19 +1,19 @@ { - "maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b", - "taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32", - "makerFee": "100000000000000", - "takerFee": "200000000000000", - "makerTokenAmount": "10000000000000000", - "takerTokenAmount": "20000000000000000", - "makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d", - "takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990", - "salt": "256", - "feeRecipient": "0xb046140686d052fff581f63f8136cce132e857da", - "exchangeContractAddress": "0x12459c951127e0c374ff9105dda097662a027093", - "expirationUnixTimestampSec": "42", - "ecSignature": { - "v": 27, - "r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33", - "s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254" - } + "maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b", + "taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32", + "makerFee": "100000000000000", + "takerFee": "200000000000000", + "makerTokenAmount": "10000000000000000", + "takerTokenAmount": "20000000000000000", + "makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d", + "takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990", + "salt": "256", + "feeRecipient": "0xb046140686d052fff581f63f8136cce132e857da", + "exchangeContractAddress": "0x12459c951127e0c374ff9105dda097662a027093", + "expirationUnixTimestampSec": "42", + "ecSignature": { + "v": 27, + "r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33", + "s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254" + } } diff --git a/packages/connect/test/fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.ts b/packages/connect/test/fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.ts index 459a87919..5a03a2ff6 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.ts +++ b/packages/connect/test/fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f.ts @@ -1,21 +1,21 @@ import { BigNumber } from '@0xproject/utils'; export const orderResponse = { - maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b', - taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', - makerFee: new BigNumber('100000000000000'), - takerFee: new BigNumber('200000000000000'), - makerTokenAmount: new BigNumber('10000000000000000'), - takerTokenAmount: new BigNumber('20000000000000000'), - makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', - salt: new BigNumber('256'), - feeRecipient: '0xb046140686d052fff581f63f8136cce132e857da', - exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093', - expirationUnixTimestampSec: new BigNumber('42'), - ecSignature: { - v: 27, - r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', - s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - }, + maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b', + taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', + makerFee: new BigNumber('100000000000000'), + takerFee: new BigNumber('200000000000000'), + makerTokenAmount: new BigNumber('10000000000000000'), + takerTokenAmount: new BigNumber('20000000000000000'), + makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', + salt: new BigNumber('256'), + feeRecipient: '0xb046140686d052fff581f63f8136cce132e857da', + exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093', + expirationUnixTimestampSec: new BigNumber('42'), + ecSignature: { + v: 27, + r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', + s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + }, }; diff --git a/packages/connect/test/fixtures/standard_relayer_api/orderbook.json b/packages/connect/test/fixtures/standard_relayer_api/orderbook.json index 569498312..825be34c2 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/orderbook.json +++ b/packages/connect/test/fixtures/standard_relayer_api/orderbook.json @@ -1,44 +1,44 @@ { - "bids": [ - { - "maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b", - "taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32", - "makerFee": "100000000000000", - "takerFee": "200000000000000", - "makerTokenAmount": "10000000000000000", - "takerTokenAmount": "20000000000000000", - "makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d", - "takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990", - "salt": "256", - "feeRecipient": "0xb046140686d052fff581f63f8136cce132e857da", - "exchangeContractAddress": "0x12459c951127e0c374ff9105dda097662a027093", - "expirationUnixTimestampSec": "42", - "ecSignature": { - "v": 27, - "r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33", - "s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254" - } - } - ], - "asks": [ - { - "maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b", - "taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32", - "makerFee": "100000000000000", - "takerFee": "200000000000000", - "makerTokenAmount": "10000000000000000", - "takerTokenAmount": "20000000000000000", - "makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d", - "takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990", - "salt": "256", - "feeRecipient": "0xb046140686d052fff581f63f8136cce132e857da", - "exchangeContractAddress": "0x12459c951127e0c374ff9105dda097662a027093", - "expirationUnixTimestampSec": "42", - "ecSignature": { - "v": 27, - "r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33", - "s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254" - } - } - ] + "bids": [ + { + "maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b", + "taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32", + "makerFee": "100000000000000", + "takerFee": "200000000000000", + "makerTokenAmount": "10000000000000000", + "takerTokenAmount": "20000000000000000", + "makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d", + "takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990", + "salt": "256", + "feeRecipient": "0xb046140686d052fff581f63f8136cce132e857da", + "exchangeContractAddress": "0x12459c951127e0c374ff9105dda097662a027093", + "expirationUnixTimestampSec": "42", + "ecSignature": { + "v": 27, + "r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33", + "s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254" + } + } + ], + "asks": [ + { + "maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b", + "taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32", + "makerFee": "100000000000000", + "takerFee": "200000000000000", + "makerTokenAmount": "10000000000000000", + "takerTokenAmount": "20000000000000000", + "makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d", + "takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990", + "salt": "256", + "feeRecipient": "0xb046140686d052fff581f63f8136cce132e857da", + "exchangeContractAddress": "0x12459c951127e0c374ff9105dda097662a027093", + "expirationUnixTimestampSec": "42", + "ecSignature": { + "v": 27, + "r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33", + "s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254" + } + } + ] } diff --git a/packages/connect/test/fixtures/standard_relayer_api/orderbook.ts b/packages/connect/test/fixtures/standard_relayer_api/orderbook.ts index 90d3d9220..6684ac2e5 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/orderbook.ts +++ b/packages/connect/test/fixtures/standard_relayer_api/orderbook.ts @@ -1,46 +1,46 @@ import { BigNumber } from '@0xproject/utils'; export const orderbookResponse = { - bids: [ - { - maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b', - taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', - makerFee: new BigNumber('100000000000000'), - takerFee: new BigNumber('200000000000000'), - makerTokenAmount: new BigNumber('10000000000000000'), - takerTokenAmount: new BigNumber('20000000000000000'), - makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', - salt: new BigNumber('256'), - feeRecipient: '0xb046140686d052fff581f63f8136cce132e857da', - exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093', - expirationUnixTimestampSec: new BigNumber('42'), - ecSignature: { - v: 27, - r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', - s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - }, - }, - ], - asks: [ - { - maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b', - taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', - makerFee: new BigNumber('100000000000000'), - takerFee: new BigNumber('200000000000000'), - makerTokenAmount: new BigNumber('10000000000000000'), - takerTokenAmount: new BigNumber('20000000000000000'), - makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', - salt: new BigNumber('256'), - feeRecipient: '0xb046140686d052fff581f63f8136cce132e857da', - exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093', - expirationUnixTimestampSec: new BigNumber('42'), - ecSignature: { - v: 27, - r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', - s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - }, - }, - ], + bids: [ + { + maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b', + taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', + makerFee: new BigNumber('100000000000000'), + takerFee: new BigNumber('200000000000000'), + makerTokenAmount: new BigNumber('10000000000000000'), + takerTokenAmount: new BigNumber('20000000000000000'), + makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', + salt: new BigNumber('256'), + feeRecipient: '0xb046140686d052fff581f63f8136cce132e857da', + exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093', + expirationUnixTimestampSec: new BigNumber('42'), + ecSignature: { + v: 27, + r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', + s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + }, + }, + ], + asks: [ + { + maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b', + taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', + makerFee: new BigNumber('100000000000000'), + takerFee: new BigNumber('200000000000000'), + makerTokenAmount: new BigNumber('10000000000000000'), + takerTokenAmount: new BigNumber('20000000000000000'), + makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', + salt: new BigNumber('256'), + feeRecipient: '0xb046140686d052fff581f63f8136cce132e857da', + exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093', + expirationUnixTimestampSec: new BigNumber('42'), + ecSignature: { + v: 27, + r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', + s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + }, + }, + ], }; diff --git a/packages/connect/test/fixtures/standard_relayer_api/orders.json b/packages/connect/test/fixtures/standard_relayer_api/orders.json index 97d21abd8..cfa780dc4 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/orders.json +++ b/packages/connect/test/fixtures/standard_relayer_api/orders.json @@ -1,21 +1,21 @@ [ - { - "maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b", - "taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32", - "makerFee": "100000000000000", - "takerFee": "200000000000000", - "makerTokenAmount": "10000000000000000", - "takerTokenAmount": "20000000000000000", - "makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d", - "takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990", - "salt": "256", - "feeRecipient": "0x9e56625509c2f60af937f23b7b532600390e8c8b", - "exchangeContractAddress": "0x9e56625509c2f60af937f23b7b532600390e8c8b", - "expirationUnixTimestampSec": "42", - "ecSignature": { - "v": 27, - "r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33", - "s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254" - } - } + { + "maker": "0x9e56625509c2f60af937f23b7b532600390e8c8b", + "taker": "0xa2b31dacf30a9c50ca473337c01d8a201ae33e32", + "makerFee": "100000000000000", + "takerFee": "200000000000000", + "makerTokenAmount": "10000000000000000", + "takerTokenAmount": "20000000000000000", + "makerTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d", + "takerTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990", + "salt": "256", + "feeRecipient": "0x9e56625509c2f60af937f23b7b532600390e8c8b", + "exchangeContractAddress": "0x9e56625509c2f60af937f23b7b532600390e8c8b", + "expirationUnixTimestampSec": "42", + "ecSignature": { + "v": 27, + "r": "0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33", + "s": "0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254" + } + } ] diff --git a/packages/connect/test/fixtures/standard_relayer_api/orders.ts b/packages/connect/test/fixtures/standard_relayer_api/orders.ts index 701346971..5044777bd 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/orders.ts +++ b/packages/connect/test/fixtures/standard_relayer_api/orders.ts @@ -1,23 +1,23 @@ import { BigNumber } from '@0xproject/utils'; export const ordersResponse = [ - { - maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b', - taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', - makerFee: new BigNumber('100000000000000'), - takerFee: new BigNumber('200000000000000'), - makerTokenAmount: new BigNumber('10000000000000000'), - takerTokenAmount: new BigNumber('20000000000000000'), - makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', - salt: new BigNumber('256'), - feeRecipient: '0x9e56625509c2f60af937f23b7b532600390e8c8b', - exchangeContractAddress: '0x9e56625509c2f60af937f23b7b532600390e8c8b', - expirationUnixTimestampSec: new BigNumber('42'), - ecSignature: { - v: 27, - r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', - s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - }, - }, + { + maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b', + taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', + makerFee: new BigNumber('100000000000000'), + takerFee: new BigNumber('200000000000000'), + makerTokenAmount: new BigNumber('10000000000000000'), + takerTokenAmount: new BigNumber('20000000000000000'), + makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', + salt: new BigNumber('256'), + feeRecipient: '0x9e56625509c2f60af937f23b7b532600390e8c8b', + exchangeContractAddress: '0x9e56625509c2f60af937f23b7b532600390e8c8b', + expirationUnixTimestampSec: new BigNumber('42'), + ecSignature: { + v: 27, + r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', + s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + }, + }, ]; diff --git a/packages/connect/test/fixtures/standard_relayer_api/token_pairs.json b/packages/connect/test/fixtures/standard_relayer_api/token_pairs.json index 8d2ea328a..90f57a974 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/token_pairs.json +++ b/packages/connect/test/fixtures/standard_relayer_api/token_pairs.json @@ -1,16 +1,16 @@ [ - { - "tokenA": { - "address": "0x323b5d4c32345ced77393b3530b1eed0f346429d", - "minAmount": "0", - "maxAmount": "10000000000000000000", - "precision": 5 - }, - "tokenB": { - "address": "0xef7fff64389b814a946f3e92105513705ca6b990", - "minAmount": "0", - "maxAmount": "50000000000000000000", - "precision": 5 - } - } + { + "tokenA": { + "address": "0x323b5d4c32345ced77393b3530b1eed0f346429d", + "minAmount": "0", + "maxAmount": "10000000000000000000", + "precision": 5 + }, + "tokenB": { + "address": "0xef7fff64389b814a946f3e92105513705ca6b990", + "minAmount": "0", + "maxAmount": "50000000000000000000", + "precision": 5 + } + } ] diff --git a/packages/connect/test/fixtures/standard_relayer_api/token_pairs.ts b/packages/connect/test/fixtures/standard_relayer_api/token_pairs.ts index 80ecb8207..f48b1e877 100644 --- a/packages/connect/test/fixtures/standard_relayer_api/token_pairs.ts +++ b/packages/connect/test/fixtures/standard_relayer_api/token_pairs.ts @@ -3,18 +3,18 @@ import { BigNumber } from '@0xproject/utils'; import { TokenPairsItem } from '../../../src/types'; export const tokenPairsResponse: TokenPairsItem[] = [ - { - tokenA: { - address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - minAmount: new BigNumber(0), - maxAmount: new BigNumber('10000000000000000000'), - precision: 5, - }, - tokenB: { - address: '0xef7fff64389b814a946f3e92105513705ca6b990', - minAmount: new BigNumber(0), - maxAmount: new BigNumber('50000000000000000000'), - precision: 5, - }, - }, + { + tokenA: { + address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + minAmount: new BigNumber(0), + maxAmount: new BigNumber('10000000000000000000'), + precision: 5, + }, + tokenB: { + address: '0xef7fff64389b814a946f3e92105513705ca6b990', + minAmount: new BigNumber(0), + maxAmount: new BigNumber('50000000000000000000'), + precision: 5, + }, + }, ]; diff --git a/packages/connect/test/http_client_test.ts b/packages/connect/test/http_client_test.ts index 00bde2fc6..15759d911 100644 --- a/packages/connect/test/http_client_test.ts +++ b/packages/connect/test/http_client_test.ts @@ -24,128 +24,128 @@ chai.use(chaiAsPromised); const expect = chai.expect; describe('HttpClient', () => { - const relayUrl = 'https://example.com'; - const relayerClient = new HttpClient(relayUrl); - afterEach(() => { - fetchMock.restore(); - }); - describe('#constructor', () => { - it('should remove trailing slashes from api url', async () => { - const urlWithTrailingSlash = 'https://slash.com/'; - const urlWithoutTrailingSlash = 'https://slash.com'; - const client = new HttpClient(urlWithTrailingSlash); - const sanitizedUrl = (client as any)._apiEndpointUrl; - expect(sanitizedUrl).to.be.deep.equal(urlWithoutTrailingSlash); - }); - }); - describe('#getTokenPairsAsync', () => { - const url = `${relayUrl}/token_pairs`; - it('gets token pairs', async () => { - fetchMock.get(url, tokenPairsResponseJSON); - const tokenPairs = await relayerClient.getTokenPairsAsync(); - expect(tokenPairs).to.be.deep.equal(tokenPairsResponse); - }); - it('gets specific token pairs for request', async () => { - const tokenAddress = '0x323b5d4c32345ced77393b3530b1eed0f346429d'; - const tokenPairsRequest = { - tokenA: tokenAddress, - }; - const urlWithQuery = `${url}?tokenA=${tokenAddress}`; - fetchMock.get(urlWithQuery, tokenPairsResponseJSON); - const tokenPairs = await relayerClient.getTokenPairsAsync(tokenPairsRequest); - expect(tokenPairs).to.be.deep.equal(tokenPairsResponse); - }); - it('throws an error for invalid JSON response', async () => { - fetchMock.get(url, { test: 'dummy' }); - expect(relayerClient.getTokenPairsAsync()).to.be.rejected(); - }); - }); - describe('#getOrdersAsync', () => { - const url = `${relayUrl}/orders`; - it('gets orders', async () => { - fetchMock.get(url, ordersResponseJSON); - const orders = await relayerClient.getOrdersAsync(); - expect(orders).to.be.deep.equal(ordersResponse); - }); - it('gets specific orders for request', async () => { - const tokenAddress = '0x323b5d4c32345ced77393b3530b1eed0f346429d'; - const ordersRequest = { - tokenAddress, - }; - const urlWithQuery = `${url}?tokenAddress=${tokenAddress}`; - fetchMock.get(urlWithQuery, ordersResponseJSON); - const orders = await relayerClient.getOrdersAsync(ordersRequest); - expect(orders).to.be.deep.equal(ordersResponse); - }); - it('throws an error for invalid JSON response', async () => { - fetchMock.get(url, { test: 'dummy' }); - expect(relayerClient.getOrdersAsync()).to.be.rejected(); - }); - }); - describe('#getOrderAsync', () => { - const orderHash = '0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f'; - const url = `${relayUrl}/order/${orderHash}`; - it('gets order', async () => { - fetchMock.get(url, orderResponseJSON); - const order = await relayerClient.getOrderAsync(orderHash); - expect(order).to.be.deep.equal(orderResponse); - }); - it('throws an error for invalid JSON response', async () => { - fetchMock.get(url, { test: 'dummy' }); - expect(relayerClient.getOrderAsync(orderHash)).to.be.rejected(); - }); - }); - describe('#getOrderBookAsync', () => { - const request = { - baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - quoteTokenAddress: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', - }; - const url = `${relayUrl}/orderbook?baseTokenAddress=${request.baseTokenAddress}"eTokenAddress=${ - request.quoteTokenAddress - }`; - it('gets order book', async () => { - fetchMock.get(url, orderbookJSON); - const orderbook = await relayerClient.getOrderbookAsync(request); - expect(orderbook).to.be.deep.equal(orderbookResponse); - }); - it('throws an error for invalid JSON response', async () => { - fetchMock.get(url, { test: 'dummy' }); - expect(relayerClient.getOrderbookAsync(request)).to.be.rejected(); - }); - }); - describe('#getFeesAsync', () => { - const request = { - exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093', - maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b', - taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', - makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', - makerTokenAmount: new BigNumber('10000000000000000000'), - takerTokenAmount: new BigNumber('30000000000000000000'), - salt: new BigNumber('256'), - expirationUnixTimestampSec: new BigNumber('42'), - }; - const url = `${relayUrl}/fees`; - it('gets fees', async () => { - fetchMock.post(url, feesResponseJSON); - const fees = await relayerClient.getFeesAsync(request); - expect(fees).to.be.deep.equal(feesResponse); - }); - it('does not mutate input', async () => { - fetchMock.post(url, feesResponseJSON); - const makerTokenAmountBefore = new BigNumber(request.makerTokenAmount); - const takerTokenAmountBefore = new BigNumber(request.takerTokenAmount); - const saltBefore = new BigNumber(request.salt); - const expirationUnixTimestampSecBefore = new BigNumber(request.expirationUnixTimestampSec); - await relayerClient.getFeesAsync(request); - expect(makerTokenAmountBefore).to.be.deep.equal(request.makerTokenAmount); - expect(takerTokenAmountBefore).to.be.deep.equal(request.takerTokenAmount); - expect(saltBefore).to.be.deep.equal(request.salt); - expect(expirationUnixTimestampSecBefore).to.be.deep.equal(request.expirationUnixTimestampSec); - }); - it('throws an error for invalid JSON response', async () => { - fetchMock.post(url, { test: 'dummy' }); - expect(relayerClient.getFeesAsync(request)).to.be.rejected(); - }); - }); + const relayUrl = 'https://example.com'; + const relayerClient = new HttpClient(relayUrl); + afterEach(() => { + fetchMock.restore(); + }); + describe('#constructor', () => { + it('should remove trailing slashes from api url', async () => { + const urlWithTrailingSlash = 'https://slash.com/'; + const urlWithoutTrailingSlash = 'https://slash.com'; + const client = new HttpClient(urlWithTrailingSlash); + const sanitizedUrl = (client as any)._apiEndpointUrl; + expect(sanitizedUrl).to.be.deep.equal(urlWithoutTrailingSlash); + }); + }); + describe('#getTokenPairsAsync', () => { + const url = `${relayUrl}/token_pairs`; + it('gets token pairs', async () => { + fetchMock.get(url, tokenPairsResponseJSON); + const tokenPairs = await relayerClient.getTokenPairsAsync(); + expect(tokenPairs).to.be.deep.equal(tokenPairsResponse); + }); + it('gets specific token pairs for request', async () => { + const tokenAddress = '0x323b5d4c32345ced77393b3530b1eed0f346429d'; + const tokenPairsRequest = { + tokenA: tokenAddress, + }; + const urlWithQuery = `${url}?tokenA=${tokenAddress}`; + fetchMock.get(urlWithQuery, tokenPairsResponseJSON); + const tokenPairs = await relayerClient.getTokenPairsAsync(tokenPairsRequest); + expect(tokenPairs).to.be.deep.equal(tokenPairsResponse); + }); + it('throws an error for invalid JSON response', async () => { + fetchMock.get(url, { test: 'dummy' }); + expect(relayerClient.getTokenPairsAsync()).to.be.rejected(); + }); + }); + describe('#getOrdersAsync', () => { + const url = `${relayUrl}/orders`; + it('gets orders', async () => { + fetchMock.get(url, ordersResponseJSON); + const orders = await relayerClient.getOrdersAsync(); + expect(orders).to.be.deep.equal(ordersResponse); + }); + it('gets specific orders for request', async () => { + const tokenAddress = '0x323b5d4c32345ced77393b3530b1eed0f346429d'; + const ordersRequest = { + tokenAddress, + }; + const urlWithQuery = `${url}?tokenAddress=${tokenAddress}`; + fetchMock.get(urlWithQuery, ordersResponseJSON); + const orders = await relayerClient.getOrdersAsync(ordersRequest); + expect(orders).to.be.deep.equal(ordersResponse); + }); + it('throws an error for invalid JSON response', async () => { + fetchMock.get(url, { test: 'dummy' }); + expect(relayerClient.getOrdersAsync()).to.be.rejected(); + }); + }); + describe('#getOrderAsync', () => { + const orderHash = '0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f'; + const url = `${relayUrl}/order/${orderHash}`; + it('gets order', async () => { + fetchMock.get(url, orderResponseJSON); + const order = await relayerClient.getOrderAsync(orderHash); + expect(order).to.be.deep.equal(orderResponse); + }); + it('throws an error for invalid JSON response', async () => { + fetchMock.get(url, { test: 'dummy' }); + expect(relayerClient.getOrderAsync(orderHash)).to.be.rejected(); + }); + }); + describe('#getOrderBookAsync', () => { + const request = { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + quoteTokenAddress: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', + }; + const url = `${relayUrl}/orderbook?baseTokenAddress=${request.baseTokenAddress}"eTokenAddress=${ + request.quoteTokenAddress + }`; + it('gets order book', async () => { + fetchMock.get(url, orderbookJSON); + const orderbook = await relayerClient.getOrderbookAsync(request); + expect(orderbook).to.be.deep.equal(orderbookResponse); + }); + it('throws an error for invalid JSON response', async () => { + fetchMock.get(url, { test: 'dummy' }); + expect(relayerClient.getOrderbookAsync(request)).to.be.rejected(); + }); + }); + describe('#getFeesAsync', () => { + const request = { + exchangeContractAddress: '0x12459c951127e0c374ff9105dda097662a027093', + maker: '0x9e56625509c2f60af937f23b7b532600390e8c8b', + taker: '0xa2b31dacf30a9c50ca473337c01d8a201ae33e32', + makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', + makerTokenAmount: new BigNumber('10000000000000000000'), + takerTokenAmount: new BigNumber('30000000000000000000'), + salt: new BigNumber('256'), + expirationUnixTimestampSec: new BigNumber('42'), + }; + const url = `${relayUrl}/fees`; + it('gets fees', async () => { + fetchMock.post(url, feesResponseJSON); + const fees = await relayerClient.getFeesAsync(request); + expect(fees).to.be.deep.equal(feesResponse); + }); + it('does not mutate input', async () => { + fetchMock.post(url, feesResponseJSON); + const makerTokenAmountBefore = new BigNumber(request.makerTokenAmount); + const takerTokenAmountBefore = new BigNumber(request.takerTokenAmount); + const saltBefore = new BigNumber(request.salt); + const expirationUnixTimestampSecBefore = new BigNumber(request.expirationUnixTimestampSec); + await relayerClient.getFeesAsync(request); + expect(makerTokenAmountBefore).to.be.deep.equal(request.makerTokenAmount); + expect(takerTokenAmountBefore).to.be.deep.equal(request.takerTokenAmount); + expect(saltBefore).to.be.deep.equal(request.salt); + expect(expirationUnixTimestampSecBefore).to.be.deep.equal(request.expirationUnixTimestampSec); + }); + it('throws an error for invalid JSON response', async () => { + fetchMock.post(url, { test: 'dummy' }); + expect(relayerClient.getFeesAsync(request)).to.be.rejected(); + }); + }); }); diff --git a/packages/connect/test/orderbook_channel_message_parsers_test.ts b/packages/connect/test/orderbook_channel_message_parsers_test.ts index 04ca157a8..3e1f44384 100644 --- a/packages/connect/test/orderbook_channel_message_parsers_test.ts +++ b/packages/connect/test/orderbook_channel_message_parsers_test.ts @@ -7,13 +7,13 @@ import { orderbookChannelMessageParser } from '../src/utils/orderbook_channel_me import { orderResponse } from './fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f'; import { orderbookResponse } from './fixtures/standard_relayer_api/orderbook'; import { - malformedSnapshotOrderbookChannelMessage, - snapshotOrderbookChannelMessage, + malformedSnapshotOrderbookChannelMessage, + snapshotOrderbookChannelMessage, } from './fixtures/standard_relayer_api/snapshot_orderbook_channel_message'; import { unknownOrderbookChannelMessage } from './fixtures/standard_relayer_api/unknown_orderbook_channel_message'; import { - malformedUpdateOrderbookChannelMessage, - updateOrderbookChannelMessage, + malformedUpdateOrderbookChannelMessage, + updateOrderbookChannelMessage, } from './fixtures/standard_relayer_api/update_orderbook_channel_message'; chai.config.includeStack = true; @@ -21,56 +21,56 @@ chai.use(dirtyChai); const expect = chai.expect; describe('orderbookChannelMessageParser', () => { - describe('#parser', () => { - it('parses snapshot messages', () => { - const snapshotMessage = orderbookChannelMessageParser.parse(snapshotOrderbookChannelMessage); - expect(snapshotMessage.type).to.be.equal('snapshot'); - expect(snapshotMessage.payload).to.be.deep.equal(orderbookResponse); - }); - it('parses update messages', () => { - const updateMessage = orderbookChannelMessageParser.parse(updateOrderbookChannelMessage); - expect(updateMessage.type).to.be.equal('update'); - expect(updateMessage.payload).to.be.deep.equal(orderResponse); - }); - it('returns unknown message for messages with unsupported types', () => { - const unknownMessage = orderbookChannelMessageParser.parse(unknownOrderbookChannelMessage); - expect(unknownMessage.type).to.be.equal('unknown'); - expect(unknownMessage.payload).to.be.undefined(); - }); - it('throws when message does not include a type', () => { - const typelessMessage = `{ + describe('#parser', () => { + it('parses snapshot messages', () => { + const snapshotMessage = orderbookChannelMessageParser.parse(snapshotOrderbookChannelMessage); + expect(snapshotMessage.type).to.be.equal('snapshot'); + expect(snapshotMessage.payload).to.be.deep.equal(orderbookResponse); + }); + it('parses update messages', () => { + const updateMessage = orderbookChannelMessageParser.parse(updateOrderbookChannelMessage); + expect(updateMessage.type).to.be.equal('update'); + expect(updateMessage.payload).to.be.deep.equal(orderResponse); + }); + it('returns unknown message for messages with unsupported types', () => { + const unknownMessage = orderbookChannelMessageParser.parse(unknownOrderbookChannelMessage); + expect(unknownMessage.type).to.be.equal('unknown'); + expect(unknownMessage.payload).to.be.undefined(); + }); + it('throws when message does not include a type', () => { + const typelessMessage = `{ "channel": "orderbook", "requestId": 1, "payload": {} }`; - const badCall = () => orderbookChannelMessageParser.parse(typelessMessage); - expect(badCall).throws(`Message is missing a type parameter: ${typelessMessage}`); - }); - it('throws when type is not a string', () => { - const messageWithBadType = `{ + const badCall = () => orderbookChannelMessageParser.parse(typelessMessage); + expect(badCall).throws(`Message is missing a type parameter: ${typelessMessage}`); + }); + it('throws when type is not a string', () => { + const messageWithBadType = `{ "type": 1, "channel": "orderbook", "requestId": 1, "payload": {} }`; - const badCall = () => orderbookChannelMessageParser.parse(messageWithBadType); - expect(badCall).throws('Expected type to be of type string, encountered: 1'); - }); - it('throws when snapshot message has malformed payload', () => { - const badCall = () => orderbookChannelMessageParser.parse(malformedSnapshotOrderbookChannelMessage); - // tslint:disable-next-line:max-line-length - const errMsg = - 'Validation errors: instance.payload requires property "bids", instance.payload requires property "asks"'; - expect(badCall).throws(errMsg); - }); - it('throws when update message has malformed payload', () => { - const badCall = () => orderbookChannelMessageParser.parse(malformedUpdateOrderbookChannelMessage); - expect(badCall).throws(/^Expected message to conform to schema/); - }); - it('throws when input message is not valid JSON', () => { - const nonJsonString = 'h93b{sdfs9fsd f'; - const badCall = () => orderbookChannelMessageParser.parse(nonJsonString); - expect(badCall).throws('Unexpected token h in JSON at position 0'); - }); - }); + const badCall = () => orderbookChannelMessageParser.parse(messageWithBadType); + expect(badCall).throws('Expected type to be of type string, encountered: 1'); + }); + it('throws when snapshot message has malformed payload', () => { + const badCall = () => orderbookChannelMessageParser.parse(malformedSnapshotOrderbookChannelMessage); + // tslint:disable-next-line:max-line-length + const errMsg = + 'Validation errors: instance.payload requires property "bids", instance.payload requires property "asks"'; + expect(badCall).throws(errMsg); + }); + it('throws when update message has malformed payload', () => { + const badCall = () => orderbookChannelMessageParser.parse(malformedUpdateOrderbookChannelMessage); + expect(badCall).throws(/^Expected message to conform to schema/); + }); + it('throws when input message is not valid JSON', () => { + const nonJsonString = 'h93b{sdfs9fsd f'; + const badCall = () => orderbookChannelMessageParser.parse(nonJsonString); + expect(badCall).throws('Unexpected token h in JSON at position 0'); + }); + }); }); diff --git a/packages/connect/test/ws_orderbook_channel_test.ts b/packages/connect/test/ws_orderbook_channel_test.ts index 0cd8cd12f..ce404d934 100644 --- a/packages/connect/test/ws_orderbook_channel_test.ts +++ b/packages/connect/test/ws_orderbook_channel_test.ts @@ -10,52 +10,52 @@ chai.use(dirtyChai); const expect = chai.expect; describe('WebSocketOrderbookChannel', () => { - const websocketUrl = 'ws://localhost:8080'; - const orderbookChannel = new WebSocketOrderbookChannel(websocketUrl); - const subscriptionOpts = { - baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - quoteTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', - snapshot: true, - limit: 100, - }; - const emptyOrderbookChannelHandler = { - onSnapshot: () => { - _.noop(); - }, - onUpdate: () => { - _.noop(); - }, - onError: () => { - _.noop(); - }, - onClose: () => { - _.noop(); - }, - }; - describe('#subscribe', () => { - it('throws when subscriptionOpts does not conform to schema', () => { - const badSubscribeCall = orderbookChannel.subscribe.bind( - orderbookChannel, - {}, - emptyOrderbookChannelHandler, - ); - expect(badSubscribeCall).throws( - 'Expected subscriptionOpts to conform to schema /RelayerApiOrderbookChannelSubscribePayload\nEncountered: {}\nValidation errors: instance requires property "baseTokenAddress", instance requires property "quoteTokenAddress"', - ); - }); - it('throws when handler has the incorrect members', () => { - const badSubscribeCall = orderbookChannel.subscribe.bind(orderbookChannel, subscriptionOpts, {}); - expect(badSubscribeCall).throws( - 'Expected handler.onSnapshot to be of type function, encountered: undefined', - ); - }); - it('does not throw when inputs are of correct types', () => { - const goodSubscribeCall = orderbookChannel.subscribe.bind( - orderbookChannel, - subscriptionOpts, - emptyOrderbookChannelHandler, - ); - expect(goodSubscribeCall).to.not.throw(); - }); - }); + const websocketUrl = 'ws://localhost:8080'; + const orderbookChannel = new WebSocketOrderbookChannel(websocketUrl); + const subscriptionOpts = { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + quoteTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', + snapshot: true, + limit: 100, + }; + const emptyOrderbookChannelHandler = { + onSnapshot: () => { + _.noop(); + }, + onUpdate: () => { + _.noop(); + }, + onError: () => { + _.noop(); + }, + onClose: () => { + _.noop(); + }, + }; + describe('#subscribe', () => { + it('throws when subscriptionOpts does not conform to schema', () => { + const badSubscribeCall = orderbookChannel.subscribe.bind( + orderbookChannel, + {}, + emptyOrderbookChannelHandler, + ); + expect(badSubscribeCall).throws( + 'Expected subscriptionOpts to conform to schema /RelayerApiOrderbookChannelSubscribePayload\nEncountered: {}\nValidation errors: instance requires property "baseTokenAddress", instance requires property "quoteTokenAddress"', + ); + }); + it('throws when handler has the incorrect members', () => { + const badSubscribeCall = orderbookChannel.subscribe.bind(orderbookChannel, subscriptionOpts, {}); + expect(badSubscribeCall).throws( + 'Expected handler.onSnapshot to be of type function, encountered: undefined', + ); + }); + it('does not throw when inputs are of correct types', () => { + const goodSubscribeCall = orderbookChannel.subscribe.bind( + orderbookChannel, + subscriptionOpts, + emptyOrderbookChannelHandler, + ); + expect(goodSubscribeCall).to.not.throw(); + }); + }); }); diff --git a/packages/connect/tsconfig.json b/packages/connect/tsconfig.json index ed8210b04..3c150236e 100644 --- a/packages/connect/tsconfig.json +++ b/packages/connect/tsconfig.json @@ -1,12 +1,12 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": [ - "./src/**/*", - "./test/**/*", - "../../node_modules/chai-as-promised-typescript-typings/index.d.ts", - "../../node_modules/chai-typescript-typings/index.d.ts" - ] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": [ + "./src/**/*", + "./test/**/*", + "../../node_modules/chai-as-promised-typescript-typings/index.d.ts", + "../../node_modules/chai-typescript-typings/index.d.ts" + ] } diff --git a/packages/connect/tslint.json b/packages/connect/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/connect/tslint.json +++ b/packages/connect/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/contracts/globals.d.ts b/packages/contracts/globals.d.ts index 1f451cf5b..2e5827324 100644 --- a/packages/contracts/globals.d.ts +++ b/packages/contracts/globals.d.ts @@ -6,31 +6,31 @@ declare module 'dirty-chai'; // disallow `namespace`, we disable tslint for the following. /* tslint:disable */ declare namespace Chai { - interface Assertion { - bignumber: Assertion; - } + interface Assertion { + bignumber: Assertion; + } } /* tslint:enable */ declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ } declare module 'solc' { - export function compile(sources: any, optimizerEnabled: number, findImports: (importPath: string) => any): any; - export function setupMethods(solcBin: any): any; + export function compile(sources: any, optimizerEnabled: number, findImports: (importPath: string) => any): any; + export function setupMethods(solcBin: any): any; } declare module 'web3-eth-abi' { - export function encodeParameters(typesArray: string[], parameters: any[]): string; + export function encodeParameters(typesArray: string[], parameters: any[]): string; } declare module 'ethereumjs-abi' { - const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; - const methodID: (name: string, types: string[]) => Buffer; + const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; + const methodID: (name: string, types: string[]) => Buffer; } // Truffle injects the following into the global scope diff --git a/packages/contracts/globalsAugment.d.ts b/packages/contracts/globalsAugment.d.ts index c81a74844..9b16ce2ad 100644 --- a/packages/contracts/globalsAugment.d.ts +++ b/packages/contracts/globalsAugment.d.ts @@ -3,17 +3,17 @@ import { BigNumber } from '@0xproject/utils'; // HACK: This module overrides the Chai namespace so that we can use BigNumber types inside. // Source: https://github.com/Microsoft/TypeScript/issues/7352#issuecomment-191547232 declare global { - // HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion - // interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise - // disallow `namespace`, we disable tslint for the following. - /* tslint:disable */ - namespace Chai { - interface NumberComparer { - (value: number | BigNumber, message?: string): Assertion; - } - interface NumericComparison { - greaterThan: NumberComparer; - } - } - /* tslint:enable */ + // HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion + // interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise + // disallow `namespace`, we disable tslint for the following. + /* tslint:disable */ + namespace Chai { + interface NumberComparer { + (value: number | BigNumber, message?: string): Assertion; + } + interface NumericComparison { + greaterThan: NumberComparer; + } + } + /* tslint:enable */ } diff --git a/packages/contracts/migrations/1_initial_migration.ts b/packages/contracts/migrations/1_initial_migration.ts index 73999dac5..8661ee218 100644 --- a/packages/contracts/migrations/1_initial_migration.ts +++ b/packages/contracts/migrations/1_initial_migration.ts @@ -2,5 +2,5 @@ import { Artifacts } from '../util/artifacts'; const { Migrations } = new Artifacts(artifacts); module.exports = (deployer: any) => { - deployer.deploy(Migrations); + deployer.deploy(Migrations); }; diff --git a/packages/contracts/migrations/2_deploy_independent_contracts.ts b/packages/contracts/migrations/2_deploy_independent_contracts.ts index 07e2f600f..ac1752347 100644 --- a/packages/contracts/migrations/2_deploy_independent_contracts.ts +++ b/packages/contracts/migrations/2_deploy_independent_contracts.ts @@ -4,38 +4,38 @@ const { MultiSigWalletWithTimeLock, TokenTransferProxy, EtherToken, TokenRegistr let multiSigConfigByNetwork: MultiSigConfigByNetwork; try { - /* tslint:disable */ - const multiSigConfig = require('./config/multisig'); - multiSigConfigByNetwork = multiSigConfig.multiSig; - /* tslint:enable */ + /* tslint:disable */ + const multiSigConfig = require('./config/multisig'); + multiSigConfigByNetwork = multiSigConfig.multiSig; + /* tslint:enable */ } catch (e) { - multiSigConfigByNetwork = {}; + multiSigConfigByNetwork = {}; } module.exports = (deployer: any, network: string, accounts: string[]) => { - const defaultConfig = { - owners: [accounts[0], accounts[1]], - confirmationsRequired: 2, - secondsRequired: 0, - }; - const config = multiSigConfigByNetwork[network] || defaultConfig; - if (network !== 'live') { - deployer - .deploy(MultiSigWalletWithTimeLock, config.owners, config.confirmationsRequired, config.secondsRequired) - .then(() => { - return deployer.deploy(TokenTransferProxy); - }) - .then(() => { - return deployer.deploy(TokenRegistry); - }) - .then(() => { - return deployer.deploy(EtherToken); - }); - } else { - deployer.deploy([ - [MultiSigWalletWithTimeLock, config.owners, config.confirmationsRequired, config.secondsRequired], - TokenTransferProxy, - TokenRegistry, - ]); - } + const defaultConfig = { + owners: [accounts[0], accounts[1]], + confirmationsRequired: 2, + secondsRequired: 0, + }; + const config = multiSigConfigByNetwork[network] || defaultConfig; + if (network !== 'live') { + deployer + .deploy(MultiSigWalletWithTimeLock, config.owners, config.confirmationsRequired, config.secondsRequired) + .then(() => { + return deployer.deploy(TokenTransferProxy); + }) + .then(() => { + return deployer.deploy(TokenRegistry); + }) + .then(() => { + return deployer.deploy(EtherToken); + }); + } else { + deployer.deploy([ + [MultiSigWalletWithTimeLock, config.owners, config.confirmationsRequired, config.secondsRequired], + TokenTransferProxy, + TokenRegistry, + ]); + } }; diff --git a/packages/contracts/migrations/3_register_tokens.ts b/packages/contracts/migrations/3_register_tokens.ts index b36cea296..d5cf63f94 100644 --- a/packages/contracts/migrations/3_register_tokens.ts +++ b/packages/contracts/migrations/3_register_tokens.ts @@ -9,87 +9,87 @@ import { tokenInfo } from './config/token_info'; const { DummyToken, EtherToken, ZRXToken, TokenRegistry } = new Artifacts(artifacts); module.exports = (deployer: any, network: string) => { - const tokens = network === 'live' ? tokenInfo.live : tokenInfo.development; - deployer - .then(() => { - return TokenRegistry.deployed(); - }) - .then((tokenRegistry: ContractInstance) => { - if (network !== 'live') { - const totalSupply = Math.pow(10, 18) * 1000000000; - return Bluebird.each( - tokens.map((token: Token) => DummyToken.new(token.name, token.symbol, token.decimals, totalSupply)), - _.noop, - ).then((dummyTokens: ContractInstance[]) => { - const weth = { - address: EtherToken.address, - name: 'Ether Token', - symbol: 'WETH', - url: '', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }; - return Bluebird.each( - dummyTokens - .map((tokenContract: ContractInstance, i: number) => { - const token = tokens[i]; - return tokenRegistry.addToken( - tokenContract.address, - token.name, - token.symbol, - token.decimals, - token.ipfsHash, - token.swarmHash, - ); - }) - .concat( - tokenRegistry.addToken( - weth.address, - weth.name, - weth.symbol, - weth.decimals, - weth.ipfsHash, - weth.swarmHash, - ), - ), - _.noop, - ); - }); - } else { - const zrx = { - address: ZRXToken.address, - name: '0x Protocol Token', - symbol: 'ZRX', - url: 'https://www.0xproject.com/', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }; - return Bluebird.each( - tokens - .map((token: Token) => { - return tokenRegistry.addToken( - token.address, - token.name, - token.symbol, - token.decimals, - token.ipfsHash, - token.swarmHash, - ); - }) - .concat( - tokenRegistry.addToken( - zrx.address, - zrx.name, - zrx.symbol, - zrx.decimals, - zrx.ipfsHash, - zrx.swarmHash, - ), - ), - _.noop, - ); - } - }); + const tokens = network === 'live' ? tokenInfo.live : tokenInfo.development; + deployer + .then(() => { + return TokenRegistry.deployed(); + }) + .then((tokenRegistry: ContractInstance) => { + if (network !== 'live') { + const totalSupply = Math.pow(10, 18) * 1000000000; + return Bluebird.each( + tokens.map((token: Token) => DummyToken.new(token.name, token.symbol, token.decimals, totalSupply)), + _.noop, + ).then((dummyTokens: ContractInstance[]) => { + const weth = { + address: EtherToken.address, + name: 'Ether Token', + symbol: 'WETH', + url: '', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }; + return Bluebird.each( + dummyTokens + .map((tokenContract: ContractInstance, i: number) => { + const token = tokens[i]; + return tokenRegistry.addToken( + tokenContract.address, + token.name, + token.symbol, + token.decimals, + token.ipfsHash, + token.swarmHash, + ); + }) + .concat( + tokenRegistry.addToken( + weth.address, + weth.name, + weth.symbol, + weth.decimals, + weth.ipfsHash, + weth.swarmHash, + ), + ), + _.noop, + ); + }); + } else { + const zrx = { + address: ZRXToken.address, + name: '0x Protocol Token', + symbol: 'ZRX', + url: 'https://www.0xproject.com/', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }; + return Bluebird.each( + tokens + .map((token: Token) => { + return tokenRegistry.addToken( + token.address, + token.name, + token.symbol, + token.decimals, + token.ipfsHash, + token.swarmHash, + ); + }) + .concat( + tokenRegistry.addToken( + zrx.address, + zrx.name, + zrx.symbol, + zrx.decimals, + zrx.ipfsHash, + zrx.swarmHash, + ), + ), + _.noop, + ); + } + }); }; diff --git a/packages/contracts/migrations/4_configure_proxy.ts b/packages/contracts/migrations/4_configure_proxy.ts index ce34ddd32..ff3b844d6 100644 --- a/packages/contracts/migrations/4_configure_proxy.ts +++ b/packages/contracts/migrations/4_configure_proxy.ts @@ -4,19 +4,19 @@ const { TokenTransferProxy, Exchange, TokenRegistry } = new Artifacts(artifacts) let tokenTransferProxy: ContractInstance; module.exports = (deployer: any) => { - deployer - .then(async () => { - return Promise.all([TokenTransferProxy.deployed(), TokenRegistry.deployed()]); - }) - .then((instances: ContractInstance[]) => { - let tokenRegistry: ContractInstance; - [tokenTransferProxy, tokenRegistry] = instances; - return tokenRegistry.getTokenAddressBySymbol('ZRX'); - }) - .then((ptAddress: string) => { - return deployer.deploy(Exchange, ptAddress, tokenTransferProxy.address); - }) - .then(() => { - return tokenTransferProxy.addAuthorizedAddress(Exchange.address); - }); + deployer + .then(async () => { + return Promise.all([TokenTransferProxy.deployed(), TokenRegistry.deployed()]); + }) + .then((instances: ContractInstance[]) => { + let tokenRegistry: ContractInstance; + [tokenTransferProxy, tokenRegistry] = instances; + return tokenRegistry.getTokenAddressBySymbol('ZRX'); + }) + .then((ptAddress: string) => { + return deployer.deploy(Exchange, ptAddress, tokenTransferProxy.address); + }) + .then(() => { + return tokenTransferProxy.addAuthorizedAddress(Exchange.address); + }); }; diff --git a/packages/contracts/migrations/5_transfer_ownership.ts b/packages/contracts/migrations/5_transfer_ownership.ts index 08b068e18..a27801de3 100644 --- a/packages/contracts/migrations/5_transfer_ownership.ts +++ b/packages/contracts/migrations/5_transfer_ownership.ts @@ -4,17 +4,17 @@ const { TokenTransferProxy, MultiSigWalletWithTimeLock, TokenRegistry } = new Ar let tokenRegistry: ContractInstance; module.exports = (deployer: any, network: string) => { - if (network !== 'development') { - deployer.then(async () => { - return Promise.all([TokenTransferProxy.deployed(), TokenRegistry.deployed()]) - .then((instances: ContractInstance[]) => { - let tokenTransferProxy: ContractInstance; - [tokenTransferProxy, tokenRegistry] = instances; - return tokenTransferProxy.transferOwnership(MultiSigWalletWithTimeLock.address); - }) - .then(() => { - return tokenRegistry.transferOwnership(MultiSigWalletWithTimeLock.address); - }); - }); - } + if (network !== 'development') { + deployer.then(async () => { + return Promise.all([TokenTransferProxy.deployed(), TokenRegistry.deployed()]) + .then((instances: ContractInstance[]) => { + let tokenTransferProxy: ContractInstance; + [tokenTransferProxy, tokenRegistry] = instances; + return tokenTransferProxy.transferOwnership(MultiSigWalletWithTimeLock.address); + }) + .then(() => { + return tokenRegistry.transferOwnership(MultiSigWalletWithTimeLock.address); + }); + }); + } }; diff --git a/packages/contracts/migrations/config/multisig_sample.ts b/packages/contracts/migrations/config/multisig_sample.ts index 6ff5efc1c..97cdc2eae 100644 --- a/packages/contracts/migrations/config/multisig_sample.ts +++ b/packages/contracts/migrations/config/multisig_sample.ts @@ -2,9 +2,9 @@ import { MultiSigConfigByNetwork } from '../../util/types'; // Make a copy of this file named `multisig.js` and input custom params as needed export const multiSig: MultiSigConfigByNetwork = { - kovan: { - owners: [], - confirmationsRequired: 0, - secondsRequired: 0, - }, + kovan: { + owners: [], + confirmationsRequired: 0, + secondsRequired: 0, + }, }; diff --git a/packages/contracts/migrations/config/token_info.ts b/packages/contracts/migrations/config/token_info.ts index 9e211c489..6ae67175e 100644 --- a/packages/contracts/migrations/config/token_info.ts +++ b/packages/contracts/migrations/config/token_info.ts @@ -2,98 +2,98 @@ import { constants } from '../../util/constants'; import { TokenInfoByNetwork } from '../../util/types'; export const tokenInfo: TokenInfoByNetwork = { - development: [ - { - name: '0x Protocol Token', - symbol: 'ZRX', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'Augur Reputation Token', - symbol: 'REP', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'Digix DAO Token', - symbol: 'DGD', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'Golem Network Token', - symbol: 'GNT', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'MakerDAO', - symbol: 'MKR', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'Melon Token', - symbol: 'MLN', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - ], - live: [ - { - address: '0xecf8f87f810ecf450940c9f60066b4a7a501d6a7', - name: 'ETH Wrapper Token', - symbol: 'WETH', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - address: '0x48c80f1f4d53d5951e5d5438b54cba84f29f32a5', - name: 'Augur Reputation Token', - symbol: 'REP', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - address: '0xe0b7927c4af23765cb51314a0e0521a9645f0e2a', - name: 'Digix DAO Token', - symbol: 'DGD', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - address: '0xa74476443119a942de498590fe1f2454d7d4ac0d', - name: 'Golem Network Token', - symbol: 'GNT', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - address: '0xc66ea802717bfb9833400264dd12c2bceaa34a6d', - name: 'MakerDAO', - symbol: 'MKR', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - address: '0xbeb9ef514a379b997e0798fdcc901ee474b6d9a1', - name: 'Melon Token', - symbol: 'MLN', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - ], + development: [ + { + name: '0x Protocol Token', + symbol: 'ZRX', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + name: 'Augur Reputation Token', + symbol: 'REP', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + name: 'Digix DAO Token', + symbol: 'DGD', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + name: 'Golem Network Token', + symbol: 'GNT', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + name: 'MakerDAO', + symbol: 'MKR', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + name: 'Melon Token', + symbol: 'MLN', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + ], + live: [ + { + address: '0xecf8f87f810ecf450940c9f60066b4a7a501d6a7', + name: 'ETH Wrapper Token', + symbol: 'WETH', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + address: '0x48c80f1f4d53d5951e5d5438b54cba84f29f32a5', + name: 'Augur Reputation Token', + symbol: 'REP', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + address: '0xe0b7927c4af23765cb51314a0e0521a9645f0e2a', + name: 'Digix DAO Token', + symbol: 'DGD', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + address: '0xa74476443119a942de498590fe1f2454d7d4ac0d', + name: 'Golem Network Token', + symbol: 'GNT', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + address: '0xc66ea802717bfb9833400264dd12c2bceaa34a6d', + name: 'MakerDAO', + symbol: 'MKR', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + address: '0xbeb9ef514a379b997e0798fdcc901ee474b6d9a1', + name: 'Melon Token', + symbol: 'MLN', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + ], }; diff --git a/packages/contracts/package.json b/packages/contracts/package.json index b361c6622..468607c16 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,74 +1,74 @@ { - "private": true, - "name": "contracts", - "version": "2.1.6", - "description": "Smart contract components of 0x protocol", - "main": "index.js", - "directories": { - "test": "test" - }, - "scripts": { - "build": - "rm -rf ./lib; copyfiles ./build/**/* ./deploy/solc/solc_bin/* ./deploy/test/fixtures/contracts/**/* ./deploy/test/fixtures/contracts/* ./lib; tsc;", - "test": "npm run build; truffle test", - "compile:comment": - "Yarn workspaces do not link binaries correctly so we need to reference them directly https://github.com/yarnpkg/yarn/issues/3846", - "compile": "node ../deployer/lib/src/cli.js compile", - "clean": "rm -rf ./lib", - "migrate": "node ../deployer/lib/src/cli.js migrate", - "lint": "tslint --project . 'migrations/**/*.ts' 'test/**/*.ts' 'util/**/*.ts' 'deploy/**/*.ts'", - "test:circleci": "yarn test" - }, - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "author": "Amir Bandeali", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/contracts/README.md", - "devDependencies": { - "@0xproject/dev-utils": "^0.0.7", - "@0xproject/tslint-config": "^0.4.4", - "@0xproject/types": "^0.1.6", - "@types/bluebird": "^3.5.3", - "@types/lodash": "^4.14.86", - "@types/node": "^8.0.53", - "@types/request-promise-native": "^1.0.2", - "@types/yargs": "^10.0.0", - "chai": "^4.0.1", - "chai-as-promised": "^7.1.0", - "chai-as-promised-typescript-typings": "^0.0.6", - "chai-bignumber": "^2.0.1", - "chai-typescript-typings": "^0.0.2", - "copyfiles": "^1.2.0", - "dirty-chai": "^2.0.1", - "mocha": "^4.0.1", - "solc": "^0.4.18", - "truffle": "^4.0.1", - "tslint": "5.8.0", - "types-bn": "^0.0.1", - "types-ethereumjs-util": "0xProject/types-ethereumjs-util", - "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.6", - "yargs": "^10.0.3" - }, - "dependencies": { - "0x.js": "^0.30.2", - "@0xproject/deployer": "^0.0.3", - "@0xproject/json-schemas": "^0.7.5", - "@0xproject/utils": "^0.2.2", - "@0xproject/web3-wrapper": "^0.1.7", - "bluebird": "^3.5.0", - "bn.js": "^4.11.8", - "ethereumjs-abi": "^0.6.4", - "ethereumjs-util": "^5.1.1", - "isomorphic-fetch": "^2.2.1", - "lodash": "^4.17.4", - "request": "^2.81.0", - "web3": "^0.20.0", - "web3-eth-abi": "^1.0.0-beta.24" - } + "private": true, + "name": "contracts", + "version": "2.1.6", + "description": "Smart contract components of 0x protocol", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": { + "build": + "rm -rf ./lib; copyfiles ./build/**/* ./deploy/solc/solc_bin/* ./deploy/test/fixtures/contracts/**/* ./deploy/test/fixtures/contracts/* ./lib; tsc;", + "test": "npm run build; truffle test", + "compile:comment": + "Yarn workspaces do not link binaries correctly so we need to reference them directly https://github.com/yarnpkg/yarn/issues/3846", + "compile": "node ../deployer/lib/src/cli.js compile", + "clean": "rm -rf ./lib", + "migrate": "node ../deployer/lib/src/cli.js migrate", + "lint": "tslint --project . 'migrations/**/*.ts' 'test/**/*.ts' 'util/**/*.ts' 'deploy/**/*.ts'", + "test:circleci": "yarn test" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "author": "Amir Bandeali", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/contracts/README.md", + "devDependencies": { + "@0xproject/dev-utils": "^0.0.7", + "@0xproject/tslint-config": "^0.4.4", + "@0xproject/types": "^0.1.6", + "@types/bluebird": "^3.5.3", + "@types/lodash": "^4.14.86", + "@types/node": "^8.0.53", + "@types/request-promise-native": "^1.0.2", + "@types/yargs": "^10.0.0", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-as-promised-typescript-typings": "^0.0.6", + "chai-bignumber": "^2.0.1", + "chai-typescript-typings": "^0.0.2", + "copyfiles": "^1.2.0", + "dirty-chai": "^2.0.1", + "mocha": "^4.0.1", + "solc": "^0.4.18", + "truffle": "^4.0.1", + "tslint": "5.8.0", + "types-bn": "^0.0.1", + "types-ethereumjs-util": "0xProject/types-ethereumjs-util", + "typescript": "~2.6.1", + "web3-typescript-typings": "^0.9.6", + "yargs": "^10.0.3" + }, + "dependencies": { + "0x.js": "^0.30.2", + "@0xproject/deployer": "^0.0.3", + "@0xproject/json-schemas": "^0.7.5", + "@0xproject/utils": "^0.2.2", + "@0xproject/web3-wrapper": "^0.1.7", + "bluebird": "^3.5.0", + "bn.js": "^4.11.8", + "ethereumjs-abi": "^0.6.4", + "ethereumjs-util": "^5.1.1", + "isomorphic-fetch": "^2.2.1", + "lodash": "^4.17.4", + "request": "^2.81.0", + "web3": "^0.20.0", + "web3-eth-abi": "^1.0.0-beta.24" + } } diff --git a/packages/contracts/test/ts/ether_token.ts b/packages/contracts/test/ts/ether_token.ts index c770b4001..f807cdaa3 100644 --- a/packages/contracts/test/ts/ether_token.ts +++ b/packages/contracts/test/ts/ether_token.ts @@ -18,107 +18,107 @@ const expect = chai.expect; const web3: Web3 = (global as any).web3; contract('EtherToken', (accounts: string[]) => { - const account = accounts[0]; - const gasPrice = ZeroEx.toBaseUnitAmount(new BigNumber(20), 9); - let zeroEx: ZeroEx; - let etherTokenAddress: string; - - before(async () => { - etherTokenAddress = EtherToken.address; - zeroEx = new ZeroEx(web3.currentProvider, { - gasPrice, - networkId: constants.TESTRPC_NETWORK_ID, - }); - }); - - const sendTransactionAsync = promisify<string>(web3.eth.sendTransaction); - const getEthBalanceAsync = async (owner: string) => { - const balanceStr = await promisify<string>(web3.eth.getBalance)(owner); - const balance = new BigNumber(balanceStr); - return balance; - }; - - describe('deposit', () => { - it('should throw if caller attempts to deposit more Ether than caller balance', async () => { - const initEthBalance = await getEthBalanceAsync(account); - const ethToDeposit = initEthBalance.plus(1); - - return expect(zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account)).to.be.rejectedWith( - ZeroExError.InsufficientEthBalanceForDeposit, - ); - }); - - it('should convert deposited Ether to wrapped Ether tokens', async () => { - const initEthBalance = await getEthBalanceAsync(account); - const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); - - const ethToDeposit = new BigNumber(web3.toWei(1, 'ether')); - - const txHash = await zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account); - const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); - - const ethSpentOnGas = gasPrice.times(receipt.gasUsed); - const finalEthBalance = await getEthBalanceAsync(account); - const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); - - expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas))); - expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.plus(ethToDeposit)); - }); - }); - - describe('withdraw', () => { - it('should throw if caller attempts to withdraw greater than caller balance', async () => { - const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); - const ethTokensToWithdraw = initEthTokenBalance.plus(1); - - return expect( - zeroEx.etherToken.withdrawAsync(etherTokenAddress, ethTokensToWithdraw, account), - ).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal); - }); - - it('should convert ether tokens to ether with sufficient balance', async () => { - const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); - const initEthBalance = await getEthBalanceAsync(account); - const ethTokensToWithdraw = initEthTokenBalance; - expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0); - const txHash = await zeroEx.etherToken.withdrawAsync(etherTokenAddress, ethTokensToWithdraw, account, { - gasLimit: constants.MAX_ETHERTOKEN_WITHDRAW_GAS, - }); - const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); - - const ethSpentOnGas = gasPrice.times(receipt.gasUsed); - const finalEthBalance = await getEthBalanceAsync(account); - const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); - - expect(finalEthBalance).to.be.bignumber.equal( - initEthBalance.plus(ethTokensToWithdraw.minus(ethSpentOnGas)), - ); - expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.minus(ethTokensToWithdraw)); - }); - }); - - describe('fallback', () => { - it('should convert sent ether to ether tokens', async () => { - const initEthBalance = await getEthBalanceAsync(account); - const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); - - const ethToDeposit = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18); - - const txHash = await sendTransactionAsync({ - from: account, - to: etherTokenAddress, - value: ethToDeposit, - gasPrice, - }); - - const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); - - const ethSpentOnGas = gasPrice.times(receipt.gasUsed); - const finalEthBalance = await getEthBalanceAsync(account); - const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); - - expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas))); - expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.plus(ethToDeposit)); - }); - }); + const account = accounts[0]; + const gasPrice = ZeroEx.toBaseUnitAmount(new BigNumber(20), 9); + let zeroEx: ZeroEx; + let etherTokenAddress: string; + + before(async () => { + etherTokenAddress = EtherToken.address; + zeroEx = new ZeroEx(web3.currentProvider, { + gasPrice, + networkId: constants.TESTRPC_NETWORK_ID, + }); + }); + + const sendTransactionAsync = promisify<string>(web3.eth.sendTransaction); + const getEthBalanceAsync = async (owner: string) => { + const balanceStr = await promisify<string>(web3.eth.getBalance)(owner); + const balance = new BigNumber(balanceStr); + return balance; + }; + + describe('deposit', () => { + it('should throw if caller attempts to deposit more Ether than caller balance', async () => { + const initEthBalance = await getEthBalanceAsync(account); + const ethToDeposit = initEthBalance.plus(1); + + return expect(zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account)).to.be.rejectedWith( + ZeroExError.InsufficientEthBalanceForDeposit, + ); + }); + + it('should convert deposited Ether to wrapped Ether tokens', async () => { + const initEthBalance = await getEthBalanceAsync(account); + const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); + + const ethToDeposit = new BigNumber(web3.toWei(1, 'ether')); + + const txHash = await zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account); + const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); + + const ethSpentOnGas = gasPrice.times(receipt.gasUsed); + const finalEthBalance = await getEthBalanceAsync(account); + const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); + + expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas))); + expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.plus(ethToDeposit)); + }); + }); + + describe('withdraw', () => { + it('should throw if caller attempts to withdraw greater than caller balance', async () => { + const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); + const ethTokensToWithdraw = initEthTokenBalance.plus(1); + + return expect( + zeroEx.etherToken.withdrawAsync(etherTokenAddress, ethTokensToWithdraw, account), + ).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal); + }); + + it('should convert ether tokens to ether with sufficient balance', async () => { + const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); + const initEthBalance = await getEthBalanceAsync(account); + const ethTokensToWithdraw = initEthTokenBalance; + expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0); + const txHash = await zeroEx.etherToken.withdrawAsync(etherTokenAddress, ethTokensToWithdraw, account, { + gasLimit: constants.MAX_ETHERTOKEN_WITHDRAW_GAS, + }); + const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); + + const ethSpentOnGas = gasPrice.times(receipt.gasUsed); + const finalEthBalance = await getEthBalanceAsync(account); + const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); + + expect(finalEthBalance).to.be.bignumber.equal( + initEthBalance.plus(ethTokensToWithdraw.minus(ethSpentOnGas)), + ); + expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.minus(ethTokensToWithdraw)); + }); + }); + + describe('fallback', () => { + it('should convert sent ether to ether tokens', async () => { + const initEthBalance = await getEthBalanceAsync(account); + const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); + + const ethToDeposit = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18); + + const txHash = await sendTransactionAsync({ + from: account, + to: etherTokenAddress, + value: ethToDeposit, + gasPrice, + }); + + const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); + + const ethSpentOnGas = gasPrice.times(receipt.gasUsed); + const finalEthBalance = await getEthBalanceAsync(account); + const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); + + expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas))); + expect(finalEthTokenBalance).to.be.bignumber.equal(initEthTokenBalance.plus(ethToDeposit)); + }); + }); }); diff --git a/packages/contracts/test/ts/exchange/core.ts b/packages/contracts/test/ts/exchange/core.ts index 36078aaa9..770ef0c43 100644 --- a/packages/contracts/test/ts/exchange/core.ts +++ b/packages/contracts/test/ts/exchange/core.ts @@ -23,830 +23,830 @@ const { Exchange, TokenTransferProxy, DummyToken, TokenRegistry, MaliciousToken const web3: Web3 = (global as any).web3; contract('Exchange', (accounts: string[]) => { - const maker = accounts[0]; - const tokenOwner = accounts[0]; - const taker = accounts[1] || accounts[accounts.length - 1]; - const feeRecipient = accounts[2] || accounts[accounts.length - 1]; - - const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); - const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); - - let rep: ContractInstance; - let dgd: ContractInstance; - let zrx: ContractInstance; - let exchange: ContractInstance; - let tokenRegistry: ContractInstance; - - let order: Order; - let balances: BalancesByOwner; - let exWrapper: ExchangeWrapper; - let dmyBalances: Balances; - let orderFactory: OrderFactory; - - let zeroEx: ZeroEx; - - before(async () => { - [tokenRegistry, exchange] = await Promise.all([TokenRegistry.deployed(), Exchange.deployed()]); - exWrapper = new ExchangeWrapper(exchange); - zeroEx = new ZeroEx(web3.currentProvider, { - exchangeContractAddress: exchange.address, - networkId: constants.TESTRPC_NETWORK_ID, - }); - - const [repAddress, dgdAddress, zrxAddress] = await Promise.all([ - tokenRegistry.getTokenAddressBySymbol('REP'), - tokenRegistry.getTokenAddressBySymbol('DGD'), - tokenRegistry.getTokenAddressBySymbol('ZRX'), - ]); - - const defaultOrderParams = { - exchangeContractAddress: Exchange.address, - maker, - feeRecipient, - makerToken: repAddress, - takerToken: dgdAddress, - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), - takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), - makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), - takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), - }; - orderFactory = new OrderFactory(defaultOrderParams); - - [rep, dgd, zrx] = await Promise.all([ - DummyToken.at(repAddress), - DummyToken.at(dgdAddress), - DummyToken.at(zrxAddress), - ]); - dmyBalances = new Balances([rep, dgd, zrx], [maker, taker, feeRecipient]); - await Promise.all([ - rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { - from: maker, - }), - rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { - from: taker, - }), - rep.setBalance(maker, INITIAL_BALANCE, { from: tokenOwner }), - rep.setBalance(taker, INITIAL_BALANCE, { from: tokenOwner }), - dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { - from: maker, - }), - dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { - from: taker, - }), - dgd.setBalance(maker, INITIAL_BALANCE, { from: tokenOwner }), - dgd.setBalance(taker, INITIAL_BALANCE, { from: tokenOwner }), - zrx.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { - from: maker, - }), - zrx.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { - from: taker, - }), - zrx.setBalance(maker, INITIAL_BALANCE, { from: tokenOwner }), - zrx.setBalance(taker, INITIAL_BALANCE, { from: tokenOwner }), - ]); - }); - - describe('internal functions', () => { - it('should include transferViaTokenTransferProxy', () => { - expect(exchange.transferViaTokenTransferProxy).to.be.undefined(); - }); - - it('should include isTransferable', () => { - expect(exchange.isTransferable).to.be.undefined(); - }); - - it('should include getBalance', () => { - expect(exchange.getBalance).to.be.undefined(); - }); - - it('should include getAllowance', () => { - expect(exchange.getAllowance).to.be.undefined(); - }); - }); - - describe('fillOrder', () => { - beforeEach(async () => { - balances = await dmyBalances.getAsync(); - order = await orderFactory.newSignedOrderAsync(); - }); - - it('should create an unfillable order', async () => { - order = await orderFactory.newSignedOrderAsync({ - makerTokenAmount: new BigNumber(1001), - takerTokenAmount: new BigNumber(3), - }); - - const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); - expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); - - const fillTakerTokenAmount1 = new BigNumber(2); - await exWrapper.fillOrderAsync(order, taker, { - fillTakerTokenAmount: fillTakerTokenAmount1, - }); - - const filledTakerTokenAmountAfter1 = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); - expect(filledTakerTokenAmountAfter1).to.be.bignumber.equal(fillTakerTokenAmount1); - - const fillTakerTokenAmount2 = new BigNumber(1); - await exWrapper.fillOrderAsync(order, taker, { - fillTakerTokenAmount: fillTakerTokenAmount2, - }); - - const filledTakerTokenAmountAfter2 = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); - expect(filledTakerTokenAmountAfter2).to.be.bignumber.equal(filledTakerTokenAmountAfter1); - }); - - it('should transfer the correct amounts when makerTokenAmount === takerTokenAmount', async () => { - order = await orderFactory.newSignedOrderAsync({ - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), - takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), - }); - - const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); - expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); - - const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); - await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); - - const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); - expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount); - - const newBalances = await dmyBalances.getAsync(); - - const fillMakerTokenAmount = fillTakerTokenAmount - .times(order.params.makerTokenAmount) - .dividedToIntegerBy(order.params.takerTokenAmount); - const paidMakerFee = order.params.makerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - const paidTakerFee = order.params.takerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( - balances[maker][order.params.makerToken].minus(fillMakerTokenAmount), - ); - expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( - balances[maker][order.params.takerToken].add(fillTakerTokenAmount), - ); - expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( - balances[maker][zrx.address].minus(paidMakerFee), - ); - expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( - balances[taker][order.params.takerToken].minus(fillTakerTokenAmount), - ); - expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( - balances[taker][order.params.makerToken].add(fillMakerTokenAmount), - ); - expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( - balances[taker][zrx.address].minus(paidTakerFee), - ); - expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( - balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)), - ); - }); - - it('should transfer the correct amounts when makerTokenAmount > takerTokenAmount', async () => { - order = await orderFactory.newSignedOrderAsync({ - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), - takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), - }); - - const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); - expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); - - const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); - await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); - - const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); - expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount); - - const newBalances = await dmyBalances.getAsync(); - - const fillMakerTokenAmount = fillTakerTokenAmount - .times(order.params.makerTokenAmount) - .dividedToIntegerBy(order.params.takerTokenAmount); - const paidMakerFee = order.params.makerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - const paidTakerFee = order.params.takerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( - balances[maker][order.params.makerToken].minus(fillMakerTokenAmount), - ); - expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( - balances[maker][order.params.takerToken].add(fillTakerTokenAmount), - ); - expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( - balances[maker][zrx.address].minus(paidMakerFee), - ); - expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( - balances[taker][order.params.takerToken].minus(fillTakerTokenAmount), - ); - expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( - balances[taker][order.params.makerToken].add(fillMakerTokenAmount), - ); - expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( - balances[taker][zrx.address].minus(paidTakerFee), - ); - expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( - balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)), - ); - }); - - it('should transfer the correct amounts when makerTokenAmount < takerTokenAmount', async () => { - order = await orderFactory.newSignedOrderAsync({ - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), - takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), - }); - - const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); - expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); - - const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); - await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); - - const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); - expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount); - - const newBalances = await dmyBalances.getAsync(); - - const fillMakerTokenAmount = fillTakerTokenAmount - .times(order.params.makerTokenAmount) - .dividedToIntegerBy(order.params.takerTokenAmount); - const paidMakerFee = order.params.makerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - const paidTakerFee = order.params.takerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( - balances[maker][order.params.makerToken].minus(fillMakerTokenAmount), - ); - expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( - balances[maker][order.params.takerToken].add(fillTakerTokenAmount), - ); - expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( - balances[maker][zrx.address].minus(paidMakerFee), - ); - expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( - balances[taker][order.params.takerToken].minus(fillTakerTokenAmount), - ); - expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( - balances[taker][order.params.makerToken].add(fillMakerTokenAmount), - ); - expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( - balances[taker][zrx.address].minus(paidTakerFee), - ); - expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( - balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)), - ); - }); - - it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => { - order = await orderFactory.newSignedOrderAsync({ - taker, - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), - takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), - }); - - const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); - expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); - - const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); - await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); - - const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); - const expectedFillAmountTAfter = fillTakerTokenAmount.add(filledTakerTokenAmountBefore); - expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(expectedFillAmountTAfter); - - const newBalances = await dmyBalances.getAsync(); - - const fillMakerTokenAmount = fillTakerTokenAmount - .times(order.params.makerTokenAmount) - .dividedToIntegerBy(order.params.takerTokenAmount); - const paidMakerFee = order.params.makerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - const paidTakerFee = order.params.takerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( - balances[maker][order.params.makerToken].minus(fillMakerTokenAmount), - ); - expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( - balances[maker][order.params.takerToken].add(fillTakerTokenAmount), - ); - expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( - balances[maker][zrx.address].minus(paidMakerFee), - ); - expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( - balances[taker][order.params.takerToken].minus(fillTakerTokenAmount), - ); - expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( - balances[taker][order.params.makerToken].add(fillMakerTokenAmount), - ); - expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( - balances[taker][zrx.address].minus(paidTakerFee), - ); - expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( - balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)), - ); - }); - - it('should fill remaining value if fillTakerTokenAmount > remaining takerTokenAmount', async () => { - const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); - await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); - - const res = await exWrapper.fillOrderAsync(order, taker, { - fillTakerTokenAmount: order.params.takerTokenAmount, - }); - - expect(res.logs[0].args.filledTakerTokenAmount).to.be.bignumber.equal( - order.params.takerTokenAmount.minus(fillTakerTokenAmount), - ); - const newBalances = await dmyBalances.getAsync(); - - expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( - balances[maker][order.params.makerToken].minus(order.params.makerTokenAmount), - ); - expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( - balances[maker][order.params.takerToken].add(order.params.takerTokenAmount), - ); - expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( - balances[maker][zrx.address].minus(order.params.makerFee), - ); - expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( - balances[taker][order.params.takerToken].minus(order.params.takerTokenAmount), - ); - expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( - balances[taker][order.params.makerToken].add(order.params.makerTokenAmount), - ); - expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( - balances[taker][zrx.address].minus(order.params.takerFee), - ); - expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( - balances[feeRecipient][zrx.address].add(order.params.makerFee.add(order.params.takerFee)), - ); - }); - - it('should log 1 event with the correct arguments when order has a feeRecipient', async () => { - const divisor = 2; - const res = await exWrapper.fillOrderAsync(order, taker, { - fillTakerTokenAmount: order.params.takerTokenAmount.div(divisor), - }); - expect(res.logs).to.have.length(1); - - const logArgs = res.logs[0].args; - const expectedFilledMakerTokenAmount = order.params.makerTokenAmount.div(divisor); - const expectedFilledTakerTokenAmount = order.params.takerTokenAmount.div(divisor); - const expectedFeeMPaid = order.params.makerFee.div(divisor); - const expectedFeeTPaid = order.params.takerFee.div(divisor); - const tokensHashBuff = crypto.solSHA3([order.params.makerToken, order.params.takerToken]); - const expectedTokens = ethUtil.bufferToHex(tokensHashBuff); - - expect(order.params.maker).to.be.equal(logArgs.maker); - expect(taker).to.be.equal(logArgs.taker); - expect(order.params.feeRecipient).to.be.equal(logArgs.feeRecipient); - expect(order.params.makerToken).to.be.equal(logArgs.makerToken); - expect(order.params.takerToken).to.be.equal(logArgs.takerToken); - expect(expectedFilledMakerTokenAmount).to.be.bignumber.equal(logArgs.filledMakerTokenAmount); - expect(expectedFilledTakerTokenAmount).to.be.bignumber.equal(logArgs.filledTakerTokenAmount); - expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.paidMakerFee); - expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.paidTakerFee); - expect(expectedTokens).to.be.equal(logArgs.tokens); - expect(order.params.orderHashHex).to.be.equal(logArgs.orderHash); - }); - - it('should log 1 event with the correct arguments when order has no feeRecipient', async () => { - order = await orderFactory.newSignedOrderAsync({ - feeRecipient: ZeroEx.NULL_ADDRESS, - }); - const divisor = 2; - const res = await exWrapper.fillOrderAsync(order, taker, { - fillTakerTokenAmount: order.params.takerTokenAmount.div(divisor), - }); - expect(res.logs).to.have.length(1); - - const logArgs = res.logs[0].args; - const expectedFilledMakerTokenAmount = order.params.makerTokenAmount.div(divisor); - const expectedFilledTakerTokenAmount = order.params.takerTokenAmount.div(divisor); - const expectedFeeMPaid = new BigNumber(0); - const expectedFeeTPaid = new BigNumber(0); - const tokensHashBuff = crypto.solSHA3([order.params.makerToken, order.params.takerToken]); - const expectedTokens = ethUtil.bufferToHex(tokensHashBuff); - - expect(order.params.maker).to.be.equal(logArgs.maker); - expect(taker).to.be.equal(logArgs.taker); - expect(order.params.feeRecipient).to.be.equal(logArgs.feeRecipient); - expect(order.params.makerToken).to.be.equal(logArgs.makerToken); - expect(order.params.takerToken).to.be.equal(logArgs.takerToken); - expect(expectedFilledMakerTokenAmount).to.be.bignumber.equal(logArgs.filledMakerTokenAmount); - expect(expectedFilledTakerTokenAmount).to.be.bignumber.equal(logArgs.filledTakerTokenAmount); - expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.paidMakerFee); - expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.paidTakerFee); - expect(expectedTokens).to.be.equal(logArgs.tokens); - expect(order.params.orderHashHex).to.be.equal(logArgs.orderHash); - }); - - it('should throw when taker is specified and order is claimed by other', async () => { - order = await orderFactory.newSignedOrderAsync({ - taker: feeRecipient, - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), - takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), - }); - - return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if signature is invalid', async () => { - order = await orderFactory.newSignedOrderAsync({ - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), - }); - - order.params.r = ethUtil.bufferToHex(ethUtil.sha3('invalidR')); - order.params.s = ethUtil.bufferToHex(ethUtil.sha3('invalidS')); - return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if makerTokenAmount is 0', async () => { - order = await orderFactory.newSignedOrderAsync({ - makerTokenAmount: new BigNumber(0), - }); - - return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if takerTokenAmount is 0', async () => { - order = await orderFactory.newSignedOrderAsync({ - takerTokenAmount: new BigNumber(0), - }); - - return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if fillTakerTokenAmount is 0', async () => { - order = await orderFactory.newSignedOrderAsync(); - - return expect( - exWrapper.fillOrderAsync(order, taker, { - fillTakerTokenAmount: new BigNumber(0), - }), - ).to.be.rejectedWith(constants.REVERT); - }); - - it('should not change balances if maker balances are too low to fill order and \ + const maker = accounts[0]; + const tokenOwner = accounts[0]; + const taker = accounts[1] || accounts[accounts.length - 1]; + const feeRecipient = accounts[2] || accounts[accounts.length - 1]; + + const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); + const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); + + let rep: ContractInstance; + let dgd: ContractInstance; + let zrx: ContractInstance; + let exchange: ContractInstance; + let tokenRegistry: ContractInstance; + + let order: Order; + let balances: BalancesByOwner; + let exWrapper: ExchangeWrapper; + let dmyBalances: Balances; + let orderFactory: OrderFactory; + + let zeroEx: ZeroEx; + + before(async () => { + [tokenRegistry, exchange] = await Promise.all([TokenRegistry.deployed(), Exchange.deployed()]); + exWrapper = new ExchangeWrapper(exchange); + zeroEx = new ZeroEx(web3.currentProvider, { + exchangeContractAddress: exchange.address, + networkId: constants.TESTRPC_NETWORK_ID, + }); + + const [repAddress, dgdAddress, zrxAddress] = await Promise.all([ + tokenRegistry.getTokenAddressBySymbol('REP'), + tokenRegistry.getTokenAddressBySymbol('DGD'), + tokenRegistry.getTokenAddressBySymbol('ZRX'), + ]); + + const defaultOrderParams = { + exchangeContractAddress: Exchange.address, + maker, + feeRecipient, + makerToken: repAddress, + takerToken: dgdAddress, + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), + makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), + takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), + }; + orderFactory = new OrderFactory(defaultOrderParams); + + [rep, dgd, zrx] = await Promise.all([ + DummyToken.at(repAddress), + DummyToken.at(dgdAddress), + DummyToken.at(zrxAddress), + ]); + dmyBalances = new Balances([rep, dgd, zrx], [maker, taker, feeRecipient]); + await Promise.all([ + rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + from: maker, + }), + rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + from: taker, + }), + rep.setBalance(maker, INITIAL_BALANCE, { from: tokenOwner }), + rep.setBalance(taker, INITIAL_BALANCE, { from: tokenOwner }), + dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + from: maker, + }), + dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + from: taker, + }), + dgd.setBalance(maker, INITIAL_BALANCE, { from: tokenOwner }), + dgd.setBalance(taker, INITIAL_BALANCE, { from: tokenOwner }), + zrx.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + from: maker, + }), + zrx.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + from: taker, + }), + zrx.setBalance(maker, INITIAL_BALANCE, { from: tokenOwner }), + zrx.setBalance(taker, INITIAL_BALANCE, { from: tokenOwner }), + ]); + }); + + describe('internal functions', () => { + it('should include transferViaTokenTransferProxy', () => { + expect(exchange.transferViaTokenTransferProxy).to.be.undefined(); + }); + + it('should include isTransferable', () => { + expect(exchange.isTransferable).to.be.undefined(); + }); + + it('should include getBalance', () => { + expect(exchange.getBalance).to.be.undefined(); + }); + + it('should include getAllowance', () => { + expect(exchange.getAllowance).to.be.undefined(); + }); + }); + + describe('fillOrder', () => { + beforeEach(async () => { + balances = await dmyBalances.getAsync(); + order = await orderFactory.newSignedOrderAsync(); + }); + + it('should create an unfillable order', async () => { + order = await orderFactory.newSignedOrderAsync({ + makerTokenAmount: new BigNumber(1001), + takerTokenAmount: new BigNumber(3), + }); + + const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( + order.params.orderHashHex, + ); + expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); + + const fillTakerTokenAmount1 = new BigNumber(2); + await exWrapper.fillOrderAsync(order, taker, { + fillTakerTokenAmount: fillTakerTokenAmount1, + }); + + const filledTakerTokenAmountAfter1 = await zeroEx.exchange.getFilledTakerAmountAsync( + order.params.orderHashHex, + ); + expect(filledTakerTokenAmountAfter1).to.be.bignumber.equal(fillTakerTokenAmount1); + + const fillTakerTokenAmount2 = new BigNumber(1); + await exWrapper.fillOrderAsync(order, taker, { + fillTakerTokenAmount: fillTakerTokenAmount2, + }); + + const filledTakerTokenAmountAfter2 = await zeroEx.exchange.getFilledTakerAmountAsync( + order.params.orderHashHex, + ); + expect(filledTakerTokenAmountAfter2).to.be.bignumber.equal(filledTakerTokenAmountAfter1); + }); + + it('should transfer the correct amounts when makerTokenAmount === takerTokenAmount', async () => { + order = await orderFactory.newSignedOrderAsync({ + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + }); + + const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( + order.params.orderHashHex, + ); + expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); + + const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); + await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); + + const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( + order.params.orderHashHex, + ); + expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount); + + const newBalances = await dmyBalances.getAsync(); + + const fillMakerTokenAmount = fillTakerTokenAmount + .times(order.params.makerTokenAmount) + .dividedToIntegerBy(order.params.takerTokenAmount); + const paidMakerFee = order.params.makerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + const paidTakerFee = order.params.takerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( + balances[maker][order.params.makerToken].minus(fillMakerTokenAmount), + ); + expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( + balances[maker][order.params.takerToken].add(fillTakerTokenAmount), + ); + expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( + balances[maker][zrx.address].minus(paidMakerFee), + ); + expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( + balances[taker][order.params.takerToken].minus(fillTakerTokenAmount), + ); + expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( + balances[taker][order.params.makerToken].add(fillMakerTokenAmount), + ); + expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( + balances[taker][zrx.address].minus(paidTakerFee), + ); + expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( + balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)), + ); + }); + + it('should transfer the correct amounts when makerTokenAmount > takerTokenAmount', async () => { + order = await orderFactory.newSignedOrderAsync({ + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + }); + + const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( + order.params.orderHashHex, + ); + expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); + + const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); + await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); + + const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( + order.params.orderHashHex, + ); + expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount); + + const newBalances = await dmyBalances.getAsync(); + + const fillMakerTokenAmount = fillTakerTokenAmount + .times(order.params.makerTokenAmount) + .dividedToIntegerBy(order.params.takerTokenAmount); + const paidMakerFee = order.params.makerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + const paidTakerFee = order.params.takerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( + balances[maker][order.params.makerToken].minus(fillMakerTokenAmount), + ); + expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( + balances[maker][order.params.takerToken].add(fillTakerTokenAmount), + ); + expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( + balances[maker][zrx.address].minus(paidMakerFee), + ); + expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( + balances[taker][order.params.takerToken].minus(fillTakerTokenAmount), + ); + expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( + balances[taker][order.params.makerToken].add(fillMakerTokenAmount), + ); + expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( + balances[taker][zrx.address].minus(paidTakerFee), + ); + expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( + balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)), + ); + }); + + it('should transfer the correct amounts when makerTokenAmount < takerTokenAmount', async () => { + order = await orderFactory.newSignedOrderAsync({ + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), + }); + + const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( + order.params.orderHashHex, + ); + expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); + + const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); + await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); + + const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( + order.params.orderHashHex, + ); + expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount); + + const newBalances = await dmyBalances.getAsync(); + + const fillMakerTokenAmount = fillTakerTokenAmount + .times(order.params.makerTokenAmount) + .dividedToIntegerBy(order.params.takerTokenAmount); + const paidMakerFee = order.params.makerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + const paidTakerFee = order.params.takerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( + balances[maker][order.params.makerToken].minus(fillMakerTokenAmount), + ); + expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( + balances[maker][order.params.takerToken].add(fillTakerTokenAmount), + ); + expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( + balances[maker][zrx.address].minus(paidMakerFee), + ); + expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( + balances[taker][order.params.takerToken].minus(fillTakerTokenAmount), + ); + expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( + balances[taker][order.params.makerToken].add(fillMakerTokenAmount), + ); + expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( + balances[taker][zrx.address].minus(paidTakerFee), + ); + expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( + balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)), + ); + }); + + it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => { + order = await orderFactory.newSignedOrderAsync({ + taker, + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), + }); + + const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( + order.params.orderHashHex, + ); + expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); + + const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); + await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); + + const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( + order.params.orderHashHex, + ); + const expectedFillAmountTAfter = fillTakerTokenAmount.add(filledTakerTokenAmountBefore); + expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(expectedFillAmountTAfter); + + const newBalances = await dmyBalances.getAsync(); + + const fillMakerTokenAmount = fillTakerTokenAmount + .times(order.params.makerTokenAmount) + .dividedToIntegerBy(order.params.takerTokenAmount); + const paidMakerFee = order.params.makerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + const paidTakerFee = order.params.takerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( + balances[maker][order.params.makerToken].minus(fillMakerTokenAmount), + ); + expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( + balances[maker][order.params.takerToken].add(fillTakerTokenAmount), + ); + expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( + balances[maker][zrx.address].minus(paidMakerFee), + ); + expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( + balances[taker][order.params.takerToken].minus(fillTakerTokenAmount), + ); + expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( + balances[taker][order.params.makerToken].add(fillMakerTokenAmount), + ); + expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( + balances[taker][zrx.address].minus(paidTakerFee), + ); + expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( + balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)), + ); + }); + + it('should fill remaining value if fillTakerTokenAmount > remaining takerTokenAmount', async () => { + const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); + await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); + + const res = await exWrapper.fillOrderAsync(order, taker, { + fillTakerTokenAmount: order.params.takerTokenAmount, + }); + + expect(res.logs[0].args.filledTakerTokenAmount).to.be.bignumber.equal( + order.params.takerTokenAmount.minus(fillTakerTokenAmount), + ); + const newBalances = await dmyBalances.getAsync(); + + expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( + balances[maker][order.params.makerToken].minus(order.params.makerTokenAmount), + ); + expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( + balances[maker][order.params.takerToken].add(order.params.takerTokenAmount), + ); + expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( + balances[maker][zrx.address].minus(order.params.makerFee), + ); + expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( + balances[taker][order.params.takerToken].minus(order.params.takerTokenAmount), + ); + expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( + balances[taker][order.params.makerToken].add(order.params.makerTokenAmount), + ); + expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( + balances[taker][zrx.address].minus(order.params.takerFee), + ); + expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( + balances[feeRecipient][zrx.address].add(order.params.makerFee.add(order.params.takerFee)), + ); + }); + + it('should log 1 event with the correct arguments when order has a feeRecipient', async () => { + const divisor = 2; + const res = await exWrapper.fillOrderAsync(order, taker, { + fillTakerTokenAmount: order.params.takerTokenAmount.div(divisor), + }); + expect(res.logs).to.have.length(1); + + const logArgs = res.logs[0].args; + const expectedFilledMakerTokenAmount = order.params.makerTokenAmount.div(divisor); + const expectedFilledTakerTokenAmount = order.params.takerTokenAmount.div(divisor); + const expectedFeeMPaid = order.params.makerFee.div(divisor); + const expectedFeeTPaid = order.params.takerFee.div(divisor); + const tokensHashBuff = crypto.solSHA3([order.params.makerToken, order.params.takerToken]); + const expectedTokens = ethUtil.bufferToHex(tokensHashBuff); + + expect(order.params.maker).to.be.equal(logArgs.maker); + expect(taker).to.be.equal(logArgs.taker); + expect(order.params.feeRecipient).to.be.equal(logArgs.feeRecipient); + expect(order.params.makerToken).to.be.equal(logArgs.makerToken); + expect(order.params.takerToken).to.be.equal(logArgs.takerToken); + expect(expectedFilledMakerTokenAmount).to.be.bignumber.equal(logArgs.filledMakerTokenAmount); + expect(expectedFilledTakerTokenAmount).to.be.bignumber.equal(logArgs.filledTakerTokenAmount); + expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.paidMakerFee); + expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.paidTakerFee); + expect(expectedTokens).to.be.equal(logArgs.tokens); + expect(order.params.orderHashHex).to.be.equal(logArgs.orderHash); + }); + + it('should log 1 event with the correct arguments when order has no feeRecipient', async () => { + order = await orderFactory.newSignedOrderAsync({ + feeRecipient: ZeroEx.NULL_ADDRESS, + }); + const divisor = 2; + const res = await exWrapper.fillOrderAsync(order, taker, { + fillTakerTokenAmount: order.params.takerTokenAmount.div(divisor), + }); + expect(res.logs).to.have.length(1); + + const logArgs = res.logs[0].args; + const expectedFilledMakerTokenAmount = order.params.makerTokenAmount.div(divisor); + const expectedFilledTakerTokenAmount = order.params.takerTokenAmount.div(divisor); + const expectedFeeMPaid = new BigNumber(0); + const expectedFeeTPaid = new BigNumber(0); + const tokensHashBuff = crypto.solSHA3([order.params.makerToken, order.params.takerToken]); + const expectedTokens = ethUtil.bufferToHex(tokensHashBuff); + + expect(order.params.maker).to.be.equal(logArgs.maker); + expect(taker).to.be.equal(logArgs.taker); + expect(order.params.feeRecipient).to.be.equal(logArgs.feeRecipient); + expect(order.params.makerToken).to.be.equal(logArgs.makerToken); + expect(order.params.takerToken).to.be.equal(logArgs.takerToken); + expect(expectedFilledMakerTokenAmount).to.be.bignumber.equal(logArgs.filledMakerTokenAmount); + expect(expectedFilledTakerTokenAmount).to.be.bignumber.equal(logArgs.filledTakerTokenAmount); + expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.paidMakerFee); + expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.paidTakerFee); + expect(expectedTokens).to.be.equal(logArgs.tokens); + expect(order.params.orderHashHex).to.be.equal(logArgs.orderHash); + }); + + it('should throw when taker is specified and order is claimed by other', async () => { + order = await orderFactory.newSignedOrderAsync({ + taker: feeRecipient, + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), + }); + + return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if signature is invalid', async () => { + order = await orderFactory.newSignedOrderAsync({ + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18), + }); + + order.params.r = ethUtil.bufferToHex(ethUtil.sha3('invalidR')); + order.params.s = ethUtil.bufferToHex(ethUtil.sha3('invalidS')); + return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if makerTokenAmount is 0', async () => { + order = await orderFactory.newSignedOrderAsync({ + makerTokenAmount: new BigNumber(0), + }); + + return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if takerTokenAmount is 0', async () => { + order = await orderFactory.newSignedOrderAsync({ + takerTokenAmount: new BigNumber(0), + }); + + return expect(exWrapper.fillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if fillTakerTokenAmount is 0', async () => { + order = await orderFactory.newSignedOrderAsync(); + + return expect( + exWrapper.fillOrderAsync(order, taker, { + fillTakerTokenAmount: new BigNumber(0), + }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should not change balances if maker balances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - order = await orderFactory.newSignedOrderAsync({ - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18), - }); + order = await orderFactory.newSignedOrderAsync({ + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18), + }); - await exWrapper.fillOrderAsync(order, taker); - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); + await exWrapper.fillOrderAsync(order, taker); + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); - it('should throw if maker balances are too low to fill order and \ + it('should throw if maker balances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = true', async () => { - order = await orderFactory.newSignedOrderAsync({ - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18), - }); - - return expect( - exWrapper.fillOrderAsync(order, taker, { - shouldThrowOnInsufficientBalanceOrAllowance: true, - }), - ).to.be.rejectedWith(constants.REVERT); - }); - - it('should not change balances if taker balances are too low to fill order and \ + order = await orderFactory.newSignedOrderAsync({ + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18), + }); + + return expect( + exWrapper.fillOrderAsync(order, taker, { + shouldThrowOnInsufficientBalanceOrAllowance: true, + }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should not change balances if taker balances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - order = await orderFactory.newSignedOrderAsync({ - takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18), - }); + order = await orderFactory.newSignedOrderAsync({ + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18), + }); - await exWrapper.fillOrderAsync(order, taker); - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); + await exWrapper.fillOrderAsync(order, taker); + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); - it('should throw if taker balances are too low to fill order and \ + it('should throw if taker balances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = true', async () => { - order = await orderFactory.newSignedOrderAsync({ - takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18), - }); - - return expect( - exWrapper.fillOrderAsync(order, taker, { - shouldThrowOnInsufficientBalanceOrAllowance: true, - }), - ).to.be.rejectedWith(constants.REVERT); - }); - - it('should not change balances if maker allowances are too low to fill order and \ + order = await orderFactory.newSignedOrderAsync({ + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18), + }); + + return expect( + exWrapper.fillOrderAsync(order, taker, { + shouldThrowOnInsufficientBalanceOrAllowance: true, + }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should not change balances if maker allowances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - await rep.approve(TokenTransferProxy.address, 0, { from: maker }); - await exWrapper.fillOrderAsync(order, taker); - await rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { - from: maker, - }); + await rep.approve(TokenTransferProxy.address, 0, { from: maker }); + await exWrapper.fillOrderAsync(order, taker); + await rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + from: maker, + }); - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); - it('should throw if maker allowances are too low to fill order and \ + it('should throw if maker allowances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = true', async () => { - await rep.approve(TokenTransferProxy.address, 0, { from: maker }); - expect( - exWrapper.fillOrderAsync(order, taker, { - shouldThrowOnInsufficientBalanceOrAllowance: true, - }), - ).to.be.rejectedWith(constants.REVERT); - await rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { - from: maker, - }); - }); - - it('should not change balances if taker allowances are too low to fill order and \ + await rep.approve(TokenTransferProxy.address, 0, { from: maker }); + expect( + exWrapper.fillOrderAsync(order, taker, { + shouldThrowOnInsufficientBalanceOrAllowance: true, + }), + ).to.be.rejectedWith(constants.REVERT); + await rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + from: maker, + }); + }); + + it('should not change balances if taker allowances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - await dgd.approve(TokenTransferProxy.address, 0, { from: taker }); - await exWrapper.fillOrderAsync(order, taker); - await dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { - from: taker, - }); + await dgd.approve(TokenTransferProxy.address, 0, { from: taker }); + await exWrapper.fillOrderAsync(order, taker); + await dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + from: taker, + }); - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); - it('should throw if taker allowances are too low to fill order and \ + it('should throw if taker allowances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = true', async () => { - await dgd.approve(TokenTransferProxy.address, 0, { from: taker }); - expect( - exWrapper.fillOrderAsync(order, taker, { - shouldThrowOnInsufficientBalanceOrAllowance: true, - }), - ).to.be.rejectedWith(constants.REVERT); - await dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { - from: taker, - }); - }); - - it('should not change balances if makerToken is ZRX, makerTokenAmount + makerFee > maker balance, \ + await dgd.approve(TokenTransferProxy.address, 0, { from: taker }); + expect( + exWrapper.fillOrderAsync(order, taker, { + shouldThrowOnInsufficientBalanceOrAllowance: true, + }), + ).to.be.rejectedWith(constants.REVERT); + await dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + from: taker, + }); + }); + + it('should not change balances if makerToken is ZRX, makerTokenAmount + makerFee > maker balance, \ and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - const makerZRXBalance = new BigNumber(balances[maker][zrx.address]); - order = await orderFactory.newSignedOrderAsync({ - makerToken: zrx.address, - makerTokenAmount: makerZRXBalance, - makerFee: new BigNumber(1), - }); - await exWrapper.fillOrderAsync(order, taker); - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); - - it('should not change balances if makerToken is ZRX, makerTokenAmount + makerFee > maker allowance, \ + const makerZRXBalance = new BigNumber(balances[maker][zrx.address]); + order = await orderFactory.newSignedOrderAsync({ + makerToken: zrx.address, + makerTokenAmount: makerZRXBalance, + makerFee: new BigNumber(1), + }); + await exWrapper.fillOrderAsync(order, taker); + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); + + it('should not change balances if makerToken is ZRX, makerTokenAmount + makerFee > maker allowance, \ and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - const makerZRXAllowance = await zrx.allowance(maker, TokenTransferProxy.address); - order = await orderFactory.newSignedOrderAsync({ - makerToken: zrx.address, - makerTokenAmount: new BigNumber(makerZRXAllowance), - makerFee: new BigNumber(1), - }); - await exWrapper.fillOrderAsync(order, taker); - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); - - it('should not change balances if takerToken is ZRX, takerTokenAmount + takerFee > taker balance, \ + const makerZRXAllowance = await zrx.allowance(maker, TokenTransferProxy.address); + order = await orderFactory.newSignedOrderAsync({ + makerToken: zrx.address, + makerTokenAmount: new BigNumber(makerZRXAllowance), + makerFee: new BigNumber(1), + }); + await exWrapper.fillOrderAsync(order, taker); + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); + + it('should not change balances if takerToken is ZRX, takerTokenAmount + takerFee > taker balance, \ and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - const takerZRXBalance = new BigNumber(balances[taker][zrx.address]); - order = await orderFactory.newSignedOrderAsync({ - takerToken: zrx.address, - takerTokenAmount: takerZRXBalance, - takerFee: new BigNumber(1), - }); - await exWrapper.fillOrderAsync(order, taker); - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); - - it('should not change balances if takerToken is ZRX, takerTokenAmount + takerFee > taker allowance, \ + const takerZRXBalance = new BigNumber(balances[taker][zrx.address]); + order = await orderFactory.newSignedOrderAsync({ + takerToken: zrx.address, + takerTokenAmount: takerZRXBalance, + takerFee: new BigNumber(1), + }); + await exWrapper.fillOrderAsync(order, taker); + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); + + it('should not change balances if takerToken is ZRX, takerTokenAmount + takerFee > taker allowance, \ and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - const takerZRXAllowance = await zrx.allowance(taker, TokenTransferProxy.address); - order = await orderFactory.newSignedOrderAsync({ - takerToken: zrx.address, - takerTokenAmount: new BigNumber(takerZRXAllowance), - takerFee: new BigNumber(1), - }); - await exWrapper.fillOrderAsync(order, taker); - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); - - it('should throw if getBalance or getAllowance attempts to change state and \ + const takerZRXAllowance = await zrx.allowance(taker, TokenTransferProxy.address); + order = await orderFactory.newSignedOrderAsync({ + takerToken: zrx.address, + takerTokenAmount: new BigNumber(takerZRXAllowance), + takerFee: new BigNumber(1), + }); + await exWrapper.fillOrderAsync(order, taker); + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); + + it('should throw if getBalance or getAllowance attempts to change state and \ shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - const maliciousToken = await MaliciousToken.new(); - await maliciousToken.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { from: taker }); - - order = await orderFactory.newSignedOrderAsync({ - takerToken: maliciousToken.address, - }); - - return expect( - exWrapper.fillOrderAsync(order, taker, { - shouldThrowOnInsufficientBalanceOrAllowance: false, - }), - ).to.be.rejectedWith(constants.REVERT); - }); - - it('should not change balances if an order is expired', async () => { - order = await orderFactory.newSignedOrderAsync({ - expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), - }); - await exWrapper.fillOrderAsync(order, taker); - - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); - - it('should log an error event if an order is expired', async () => { - order = await orderFactory.newSignedOrderAsync({ - expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), - }); - - const res = await exWrapper.fillOrderAsync(order, taker); - expect(res.logs).to.have.length(1); - const errCode = res.logs[0].args.errorId.toNumber(); - expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED); - }); - - it('should log an error event if no value is filled', async () => { - await exWrapper.fillOrderAsync(order, taker); - - const res = await exWrapper.fillOrderAsync(order, taker); - expect(res.logs).to.have.length(1); - const errCode = res.logs[0].args.errorId.toNumber(); - expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED); - }); - }); - - describe('cancelOrder', () => { - beforeEach(async () => { - balances = await dmyBalances.getAsync(); - order = await orderFactory.newSignedOrderAsync(); - }); - - it('should throw if not sent by maker', async () => { - return expect(exWrapper.cancelOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if makerTokenAmount is 0', async () => { - order = await orderFactory.newSignedOrderAsync({ - makerTokenAmount: new BigNumber(0), - }); - - return expect(exWrapper.cancelOrderAsync(order, maker)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if takerTokenAmount is 0', async () => { - order = await orderFactory.newSignedOrderAsync({ - takerTokenAmount: new BigNumber(0), - }); - - return expect(exWrapper.cancelOrderAsync(order, maker)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if cancelTakerTokenAmount is 0', async () => { - order = await orderFactory.newSignedOrderAsync(); - - return expect( - exWrapper.cancelOrderAsync(order, maker, { - cancelTakerTokenAmount: new BigNumber(0), - }), - ).to.be.rejectedWith(constants.REVERT); - }); - - it('should be able to cancel a full order', async () => { - await exWrapper.cancelOrderAsync(order, maker); - await exWrapper.fillOrderAsync(order, taker, { - fillTakerTokenAmount: order.params.takerTokenAmount.div(2), - }); - - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); - - it('should be able to cancel part of an order', async () => { - const cancelTakerTokenAmount = order.params.takerTokenAmount.div(2); - await exWrapper.cancelOrderAsync(order, maker, { - cancelTakerTokenAmount, - }); - - const res = await exWrapper.fillOrderAsync(order, taker, { - fillTakerTokenAmount: order.params.takerTokenAmount, - }); - expect(res.logs[0].args.filledTakerTokenAmount).to.be.bignumber.equal( - order.params.takerTokenAmount.minus(cancelTakerTokenAmount), - ); - - const newBalances = await dmyBalances.getAsync(); - const cancelMakerTokenAmount = cancelTakerTokenAmount - .times(order.params.makerTokenAmount) - .dividedToIntegerBy(order.params.takerTokenAmount); - const paidMakerFee = order.params.makerFee - .times(cancelMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - const paidTakerFee = order.params.takerFee - .times(cancelMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( - balances[maker][order.params.makerToken].minus(cancelMakerTokenAmount), - ); - expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( - balances[maker][order.params.takerToken].add(cancelTakerTokenAmount), - ); - expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( - balances[maker][zrx.address].minus(paidMakerFee), - ); - expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( - balances[taker][order.params.takerToken].minus(cancelTakerTokenAmount), - ); - expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( - balances[taker][order.params.makerToken].add(cancelMakerTokenAmount), - ); - expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( - balances[taker][zrx.address].minus(paidTakerFee), - ); - expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( - balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)), - ); - }); - - it('should log 1 event with correct arguments', async () => { - const divisor = 2; - const res = await exWrapper.cancelOrderAsync(order, maker, { - cancelTakerTokenAmount: order.params.takerTokenAmount.div(divisor), - }); - expect(res.logs).to.have.length(1); - - const logArgs = res.logs[0].args; - const expectedCancelledMakerTokenAmount = order.params.makerTokenAmount.div(divisor); - const expectedCancelledTakerTokenAmount = order.params.takerTokenAmount.div(divisor); - const tokensHashBuff = crypto.solSHA3([order.params.makerToken, order.params.takerToken]); - const expectedTokens = ethUtil.bufferToHex(tokensHashBuff); - - expect(order.params.maker).to.be.equal(logArgs.maker); - expect(order.params.feeRecipient).to.be.equal(logArgs.feeRecipient); - expect(order.params.makerToken).to.be.equal(logArgs.makerToken); - expect(order.params.takerToken).to.be.equal(logArgs.takerToken); - expect(expectedCancelledMakerTokenAmount).to.be.bignumber.equal(logArgs.cancelledMakerTokenAmount); - expect(expectedCancelledTakerTokenAmount).to.be.bignumber.equal(logArgs.cancelledTakerTokenAmount); - expect(expectedTokens).to.be.equal(logArgs.tokens); - expect(order.params.orderHashHex).to.be.equal(logArgs.orderHash); - }); - - it('should not log events if no value is cancelled', async () => { - await exWrapper.cancelOrderAsync(order, maker); - - const res = await exWrapper.cancelOrderAsync(order, maker); - expect(res.logs).to.have.length(1); - const errCode = res.logs[0].args.errorId.toNumber(); - expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED); - }); - - it('should not log events if order is expired', async () => { - order = await orderFactory.newSignedOrderAsync({ - expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), - }); - - const res = await exWrapper.cancelOrderAsync(order, maker); - expect(res.logs).to.have.length(1); - const errCode = res.logs[0].args.errorId.toNumber(); - expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED); - }); - }); + const maliciousToken = await MaliciousToken.new(); + await maliciousToken.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { from: taker }); + + order = await orderFactory.newSignedOrderAsync({ + takerToken: maliciousToken.address, + }); + + return expect( + exWrapper.fillOrderAsync(order, taker, { + shouldThrowOnInsufficientBalanceOrAllowance: false, + }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should not change balances if an order is expired', async () => { + order = await orderFactory.newSignedOrderAsync({ + expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), + }); + await exWrapper.fillOrderAsync(order, taker); + + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); + + it('should log an error event if an order is expired', async () => { + order = await orderFactory.newSignedOrderAsync({ + expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), + }); + + const res = await exWrapper.fillOrderAsync(order, taker); + expect(res.logs).to.have.length(1); + const errCode = res.logs[0].args.errorId.toNumber(); + expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED); + }); + + it('should log an error event if no value is filled', async () => { + await exWrapper.fillOrderAsync(order, taker); + + const res = await exWrapper.fillOrderAsync(order, taker); + expect(res.logs).to.have.length(1); + const errCode = res.logs[0].args.errorId.toNumber(); + expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED); + }); + }); + + describe('cancelOrder', () => { + beforeEach(async () => { + balances = await dmyBalances.getAsync(); + order = await orderFactory.newSignedOrderAsync(); + }); + + it('should throw if not sent by maker', async () => { + return expect(exWrapper.cancelOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if makerTokenAmount is 0', async () => { + order = await orderFactory.newSignedOrderAsync({ + makerTokenAmount: new BigNumber(0), + }); + + return expect(exWrapper.cancelOrderAsync(order, maker)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if takerTokenAmount is 0', async () => { + order = await orderFactory.newSignedOrderAsync({ + takerTokenAmount: new BigNumber(0), + }); + + return expect(exWrapper.cancelOrderAsync(order, maker)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if cancelTakerTokenAmount is 0', async () => { + order = await orderFactory.newSignedOrderAsync(); + + return expect( + exWrapper.cancelOrderAsync(order, maker, { + cancelTakerTokenAmount: new BigNumber(0), + }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should be able to cancel a full order', async () => { + await exWrapper.cancelOrderAsync(order, maker); + await exWrapper.fillOrderAsync(order, taker, { + fillTakerTokenAmount: order.params.takerTokenAmount.div(2), + }); + + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); + + it('should be able to cancel part of an order', async () => { + const cancelTakerTokenAmount = order.params.takerTokenAmount.div(2); + await exWrapper.cancelOrderAsync(order, maker, { + cancelTakerTokenAmount, + }); + + const res = await exWrapper.fillOrderAsync(order, taker, { + fillTakerTokenAmount: order.params.takerTokenAmount, + }); + expect(res.logs[0].args.filledTakerTokenAmount).to.be.bignumber.equal( + order.params.takerTokenAmount.minus(cancelTakerTokenAmount), + ); + + const newBalances = await dmyBalances.getAsync(); + const cancelMakerTokenAmount = cancelTakerTokenAmount + .times(order.params.makerTokenAmount) + .dividedToIntegerBy(order.params.takerTokenAmount); + const paidMakerFee = order.params.makerFee + .times(cancelMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + const paidTakerFee = order.params.takerFee + .times(cancelMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( + balances[maker][order.params.makerToken].minus(cancelMakerTokenAmount), + ); + expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( + balances[maker][order.params.takerToken].add(cancelTakerTokenAmount), + ); + expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( + balances[maker][zrx.address].minus(paidMakerFee), + ); + expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( + balances[taker][order.params.takerToken].minus(cancelTakerTokenAmount), + ); + expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( + balances[taker][order.params.makerToken].add(cancelMakerTokenAmount), + ); + expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( + balances[taker][zrx.address].minus(paidTakerFee), + ); + expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( + balances[feeRecipient][zrx.address].add(paidMakerFee.add(paidTakerFee)), + ); + }); + + it('should log 1 event with correct arguments', async () => { + const divisor = 2; + const res = await exWrapper.cancelOrderAsync(order, maker, { + cancelTakerTokenAmount: order.params.takerTokenAmount.div(divisor), + }); + expect(res.logs).to.have.length(1); + + const logArgs = res.logs[0].args; + const expectedCancelledMakerTokenAmount = order.params.makerTokenAmount.div(divisor); + const expectedCancelledTakerTokenAmount = order.params.takerTokenAmount.div(divisor); + const tokensHashBuff = crypto.solSHA3([order.params.makerToken, order.params.takerToken]); + const expectedTokens = ethUtil.bufferToHex(tokensHashBuff); + + expect(order.params.maker).to.be.equal(logArgs.maker); + expect(order.params.feeRecipient).to.be.equal(logArgs.feeRecipient); + expect(order.params.makerToken).to.be.equal(logArgs.makerToken); + expect(order.params.takerToken).to.be.equal(logArgs.takerToken); + expect(expectedCancelledMakerTokenAmount).to.be.bignumber.equal(logArgs.cancelledMakerTokenAmount); + expect(expectedCancelledTakerTokenAmount).to.be.bignumber.equal(logArgs.cancelledTakerTokenAmount); + expect(expectedTokens).to.be.equal(logArgs.tokens); + expect(order.params.orderHashHex).to.be.equal(logArgs.orderHash); + }); + + it('should not log events if no value is cancelled', async () => { + await exWrapper.cancelOrderAsync(order, maker); + + const res = await exWrapper.cancelOrderAsync(order, maker); + expect(res.logs).to.have.length(1); + const errCode = res.logs[0].args.errorId.toNumber(); + expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED); + }); + + it('should not log events if order is expired', async () => { + order = await orderFactory.newSignedOrderAsync({ + expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), + }); + + const res = await exWrapper.cancelOrderAsync(order, maker); + expect(res.logs).to.have.length(1); + const errCode = res.logs[0].args.errorId.toNumber(); + expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED); + }); + }); }); // tslint:disable-line:max-file-line-count diff --git a/packages/contracts/test/ts/exchange/helpers.ts b/packages/contracts/test/ts/exchange/helpers.ts index 993b4c7d5..95f68e419 100644 --- a/packages/contracts/test/ts/exchange/helpers.ts +++ b/packages/contracts/test/ts/exchange/helpers.ts @@ -15,153 +15,153 @@ const expect = chai.expect; const { Exchange, TokenRegistry } = new Artifacts(artifacts); contract('Exchange', (accounts: string[]) => { - const maker = accounts[0]; - const feeRecipient = accounts[1] || accounts[accounts.length - 1]; - - let order: Order; - let exchangeWrapper: ExchangeWrapper; - let orderFactory: OrderFactory; - - before(async () => { - const [tokenRegistry, exchange] = await Promise.all([TokenRegistry.deployed(), Exchange.deployed()]); - exchangeWrapper = new ExchangeWrapper(exchange); - const [repAddress, dgdAddress] = await Promise.all([ - tokenRegistry.getTokenAddressBySymbol('REP'), - tokenRegistry.getTokenAddressBySymbol('DGD'), - ]); - const defaultOrderParams = { - exchangeContractAddress: Exchange.address, - maker, - feeRecipient, - makerToken: repAddress, - takerToken: dgdAddress, - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), - takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), - makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), - takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), - }; - orderFactory = new OrderFactory(defaultOrderParams); - }); - - beforeEach(async () => { - order = await orderFactory.newSignedOrderAsync(); - }); - - describe('getOrderHash', () => { - it('should output the correct orderHash', async () => { - const orderHashHex = await exchangeWrapper.getOrderHashAsync(order); - expect(order.params.orderHashHex).to.be.equal(orderHashHex); - }); - }); - - describe('isValidSignature', () => { - beforeEach(async () => { - order = await orderFactory.newSignedOrderAsync(); - }); - - it('should return true with a valid signature', async () => { - const success = await exchangeWrapper.isValidSignatureAsync(order); - const isValidSignature = order.isValidSignature(); - expect(isValidSignature).to.be.true(); - expect(success).to.be.true(); - }); - - it('should return false with an invalid signature', async () => { - order.params.r = ethUtil.bufferToHex(ethUtil.sha3('invalidR')); - order.params.s = ethUtil.bufferToHex(ethUtil.sha3('invalidS')); - const success = await exchangeWrapper.isValidSignatureAsync(order); - expect(order.isValidSignature()).to.be.false(); - expect(success).to.be.false(); - }); - }); - - describe('isRoundingError', () => { - it('should return false if there is a rounding error of 0.1%', async () => { - const numerator = new BigNumber(20); - const denominator = new BigNumber(999); - const target = new BigNumber(50); - // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1% - const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); - expect(isRoundingError).to.be.false(); - }); - - it('should return false if there is a rounding of 0.09%', async () => { - const numerator = new BigNumber(20); - const denominator = new BigNumber(9991); - const target = new BigNumber(500); - // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09% - const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); - expect(isRoundingError).to.be.false(); - }); - - it('should return true if there is a rounding error of 0.11%', async () => { - const numerator = new BigNumber(20); - const denominator = new BigNumber(9989); - const target = new BigNumber(500); - // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011% - const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); - expect(isRoundingError).to.be.true(); - }); - - it('should return true if there is a rounding error > 0.1%', async () => { - const numerator = new BigNumber(3); - const denominator = new BigNumber(7); - const target = new BigNumber(10); - // rounding error = ((3*10/7) - floor(3*10/7)) / (3*10/7) = 6.67% - const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); - expect(isRoundingError).to.be.true(); - }); - - it('should return false when there is no rounding error', async () => { - const numerator = new BigNumber(1); - const denominator = new BigNumber(2); - const target = new BigNumber(10); - - const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); - expect(isRoundingError).to.be.false(); - }); - - it('should return false when there is rounding error <= 0.1%', async () => { - // randomly generated numbers - const numerator = new BigNumber(76564); - const denominator = new BigNumber(676373677); - const target = new BigNumber(105762562); - // rounding error = ((76564*105762562/676373677) - floor(76564*105762562/676373677)) / - // (76564*105762562/676373677) = 0.0007% - const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); - expect(isRoundingError).to.be.false(); - }); - }); - - describe('getPartialAmount', () => { - it('should return the numerator/denominator*target', async () => { - const numerator = new BigNumber(1); - const denominator = new BigNumber(2); - const target = new BigNumber(10); - - const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target); - const expectedPartialAmount = 5; - expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount); - }); - - it('should round down', async () => { - const numerator = new BigNumber(2); - const denominator = new BigNumber(3); - const target = new BigNumber(10); - - const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target); - const expectedPartialAmount = 6; - expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount); - }); - - it('should round .5 down', async () => { - const numerator = new BigNumber(1); - const denominator = new BigNumber(20); - const target = new BigNumber(10); - - const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target); - const expectedPartialAmount = 0; - expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount); - }); - }); + const maker = accounts[0]; + const feeRecipient = accounts[1] || accounts[accounts.length - 1]; + + let order: Order; + let exchangeWrapper: ExchangeWrapper; + let orderFactory: OrderFactory; + + before(async () => { + const [tokenRegistry, exchange] = await Promise.all([TokenRegistry.deployed(), Exchange.deployed()]); + exchangeWrapper = new ExchangeWrapper(exchange); + const [repAddress, dgdAddress] = await Promise.all([ + tokenRegistry.getTokenAddressBySymbol('REP'), + tokenRegistry.getTokenAddressBySymbol('DGD'), + ]); + const defaultOrderParams = { + exchangeContractAddress: Exchange.address, + maker, + feeRecipient, + makerToken: repAddress, + takerToken: dgdAddress, + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), + makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), + takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), + }; + orderFactory = new OrderFactory(defaultOrderParams); + }); + + beforeEach(async () => { + order = await orderFactory.newSignedOrderAsync(); + }); + + describe('getOrderHash', () => { + it('should output the correct orderHash', async () => { + const orderHashHex = await exchangeWrapper.getOrderHashAsync(order); + expect(order.params.orderHashHex).to.be.equal(orderHashHex); + }); + }); + + describe('isValidSignature', () => { + beforeEach(async () => { + order = await orderFactory.newSignedOrderAsync(); + }); + + it('should return true with a valid signature', async () => { + const success = await exchangeWrapper.isValidSignatureAsync(order); + const isValidSignature = order.isValidSignature(); + expect(isValidSignature).to.be.true(); + expect(success).to.be.true(); + }); + + it('should return false with an invalid signature', async () => { + order.params.r = ethUtil.bufferToHex(ethUtil.sha3('invalidR')); + order.params.s = ethUtil.bufferToHex(ethUtil.sha3('invalidS')); + const success = await exchangeWrapper.isValidSignatureAsync(order); + expect(order.isValidSignature()).to.be.false(); + expect(success).to.be.false(); + }); + }); + + describe('isRoundingError', () => { + it('should return false if there is a rounding error of 0.1%', async () => { + const numerator = new BigNumber(20); + const denominator = new BigNumber(999); + const target = new BigNumber(50); + // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1% + const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); + expect(isRoundingError).to.be.false(); + }); + + it('should return false if there is a rounding of 0.09%', async () => { + const numerator = new BigNumber(20); + const denominator = new BigNumber(9991); + const target = new BigNumber(500); + // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09% + const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); + expect(isRoundingError).to.be.false(); + }); + + it('should return true if there is a rounding error of 0.11%', async () => { + const numerator = new BigNumber(20); + const denominator = new BigNumber(9989); + const target = new BigNumber(500); + // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011% + const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); + expect(isRoundingError).to.be.true(); + }); + + it('should return true if there is a rounding error > 0.1%', async () => { + const numerator = new BigNumber(3); + const denominator = new BigNumber(7); + const target = new BigNumber(10); + // rounding error = ((3*10/7) - floor(3*10/7)) / (3*10/7) = 6.67% + const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); + expect(isRoundingError).to.be.true(); + }); + + it('should return false when there is no rounding error', async () => { + const numerator = new BigNumber(1); + const denominator = new BigNumber(2); + const target = new BigNumber(10); + + const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); + expect(isRoundingError).to.be.false(); + }); + + it('should return false when there is rounding error <= 0.1%', async () => { + // randomly generated numbers + const numerator = new BigNumber(76564); + const denominator = new BigNumber(676373677); + const target = new BigNumber(105762562); + // rounding error = ((76564*105762562/676373677) - floor(76564*105762562/676373677)) / + // (76564*105762562/676373677) = 0.0007% + const isRoundingError = await exchangeWrapper.isRoundingErrorAsync(numerator, denominator, target); + expect(isRoundingError).to.be.false(); + }); + }); + + describe('getPartialAmount', () => { + it('should return the numerator/denominator*target', async () => { + const numerator = new BigNumber(1); + const denominator = new BigNumber(2); + const target = new BigNumber(10); + + const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target); + const expectedPartialAmount = 5; + expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount); + }); + + it('should round down', async () => { + const numerator = new BigNumber(2); + const denominator = new BigNumber(3); + const target = new BigNumber(10); + + const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target); + const expectedPartialAmount = 6; + expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount); + }); + + it('should round .5 down', async () => { + const numerator = new BigNumber(1); + const denominator = new BigNumber(20); + const target = new BigNumber(10); + + const partialAmount = await exchangeWrapper.getPartialAmountAsync(numerator, denominator, target); + const expectedPartialAmount = 0; + expect(partialAmount).to.be.bignumber.equal(expectedPartialAmount); + }); + }); }); diff --git a/packages/contracts/test/ts/exchange/wrapper.ts b/packages/contracts/test/ts/exchange/wrapper.ts index e0243fc1b..e69e08bcf 100644 --- a/packages/contracts/test/ts/exchange/wrapper.ts +++ b/packages/contracts/test/ts/exchange/wrapper.ts @@ -17,332 +17,332 @@ const expect = chai.expect; const { Exchange, TokenTransferProxy, DummyToken, TokenRegistry } = new Artifacts(artifacts); contract('Exchange', (accounts: string[]) => { - const maker = accounts[0]; - const tokenOwner = accounts[0]; - const taker = accounts[1] || accounts[accounts.length - 1]; - const feeRecipient = accounts[2] || accounts[accounts.length - 1]; - - const INIT_BAL = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); - const INIT_ALLOW = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); - - let rep: ContractInstance; - let dgd: ContractInstance; - let zrx: ContractInstance; - let exchange: ContractInstance; - let tokenRegistry: ContractInstance; - - let balances: BalancesByOwner; - - let exWrapper: ExchangeWrapper; - let dmyBalances: Balances; - let orderFactory: OrderFactory; - - before(async () => { - [tokenRegistry, exchange] = await Promise.all([TokenRegistry.deployed(), Exchange.deployed()]); - exWrapper = new ExchangeWrapper(exchange); - const [repAddress, dgdAddress, zrxAddress] = await Promise.all([ - tokenRegistry.getTokenAddressBySymbol('REP'), - tokenRegistry.getTokenAddressBySymbol('DGD'), - tokenRegistry.getTokenAddressBySymbol('ZRX'), - ]); - - const defaultOrderParams = { - exchangeContractAddress: Exchange.address, - maker, - feeRecipient, - makerToken: repAddress, - takerToken: dgdAddress, - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), - takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), - makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), - takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), - }; - orderFactory = new OrderFactory(defaultOrderParams); - - [rep, dgd, zrx] = await Promise.all([ - DummyToken.at(repAddress), - DummyToken.at(dgdAddress), - DummyToken.at(zrxAddress), - ]); - dmyBalances = new Balances([rep, dgd, zrx], [maker, taker, feeRecipient]); - await Promise.all([ - rep.approve(TokenTransferProxy.address, INIT_ALLOW, { from: maker }), - rep.approve(TokenTransferProxy.address, INIT_ALLOW, { from: taker }), - rep.setBalance(maker, INIT_BAL, { from: tokenOwner }), - rep.setBalance(taker, INIT_BAL, { from: tokenOwner }), - dgd.approve(TokenTransferProxy.address, INIT_ALLOW, { from: maker }), - dgd.approve(TokenTransferProxy.address, INIT_ALLOW, { from: taker }), - dgd.setBalance(maker, INIT_BAL, { from: tokenOwner }), - dgd.setBalance(taker, INIT_BAL, { from: tokenOwner }), - zrx.approve(TokenTransferProxy.address, INIT_ALLOW, { from: maker }), - zrx.approve(TokenTransferProxy.address, INIT_ALLOW, { from: taker }), - zrx.setBalance(maker, INIT_BAL, { from: tokenOwner }), - zrx.setBalance(taker, INIT_BAL, { from: tokenOwner }), - ]); - }); - - describe('fillOrKillOrder', () => { - beforeEach(async () => { - balances = await dmyBalances.getAsync(); - }); - - it('should transfer the correct amounts', async () => { - const order = await orderFactory.newSignedOrderAsync({ - makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), - takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), - }); - const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); - await exWrapper.fillOrKillOrderAsync(order, taker, { - fillTakerTokenAmount, - }); - - const newBalances = await dmyBalances.getAsync(); - - const fillMakerTokenAmount = fillTakerTokenAmount - .times(order.params.makerTokenAmount) - .dividedToIntegerBy(order.params.takerTokenAmount); - const makerFee = order.params.makerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - const takerFee = order.params.takerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( - balances[maker][order.params.makerToken].minus(fillMakerTokenAmount), - ); - expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( - balances[maker][order.params.takerToken].add(fillTakerTokenAmount), - ); - expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(balances[maker][zrx.address].minus(makerFee)); - expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( - balances[taker][order.params.takerToken].minus(fillTakerTokenAmount), - ); - expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( - balances[taker][order.params.makerToken].add(fillMakerTokenAmount), - ); - expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(balances[taker][zrx.address].minus(takerFee)); - expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( - balances[feeRecipient][zrx.address].add(makerFee.add(takerFee)), - ); - }); - - it('should throw if an order is expired', async () => { - const order = await orderFactory.newSignedOrderAsync({ - expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), - }); - - return expect(exWrapper.fillOrKillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if entire fillTakerTokenAmount not filled', async () => { - const order = await orderFactory.newSignedOrderAsync(); - - const from = taker; - await exWrapper.fillOrderAsync(order, from, { - fillTakerTokenAmount: order.params.takerTokenAmount.div(2), - }); - - return expect(exWrapper.fillOrKillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); - }); - }); - - describe('batch functions', () => { - let orders: Order[]; - beforeEach(async () => { - orders = await Promise.all([ - orderFactory.newSignedOrderAsync(), - orderFactory.newSignedOrderAsync(), - orderFactory.newSignedOrderAsync(), - ]); - balances = await dmyBalances.getAsync(); - }); - - describe('batchFillOrders', () => { - it('should transfer the correct amounts', async () => { - const fillTakerTokenAmounts: BigNumber[] = []; - const makerToken = rep.address; - const takerToken = dgd.address; - orders.forEach(order => { - const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); - const fillMakerTokenAmount = fillTakerTokenAmount - .times(order.params.makerTokenAmount) - .dividedToIntegerBy(order.params.takerTokenAmount); - const makerFee = order.params.makerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - const takerFee = order.params.takerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - fillTakerTokenAmounts.push(fillTakerTokenAmount); - balances[maker][makerToken] = balances[maker][makerToken].minus(fillMakerTokenAmount); - balances[maker][takerToken] = balances[maker][takerToken].add(fillTakerTokenAmount); - balances[maker][zrx.address] = balances[maker][zrx.address].minus(makerFee); - balances[taker][makerToken] = balances[taker][makerToken].add(fillMakerTokenAmount); - balances[taker][takerToken] = balances[taker][takerToken].minus(fillTakerTokenAmount); - balances[taker][zrx.address] = balances[taker][zrx.address].minus(takerFee); - balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address].add( - makerFee.add(takerFee), - ); - }); - - await exWrapper.batchFillOrdersAsync(orders, taker, { - fillTakerTokenAmounts, - }); - - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); - }); - - describe('batchFillOrKillOrders', () => { - it('should transfer the correct amounts', async () => { - const fillTakerTokenAmounts: BigNumber[] = []; - const makerToken = rep.address; - const takerToken = dgd.address; - orders.forEach(order => { - const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); - const fillMakerTokenAmount = fillTakerTokenAmount - .times(order.params.makerTokenAmount) - .dividedToIntegerBy(order.params.takerTokenAmount); - const makerFee = order.params.makerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - const takerFee = order.params.takerFee - .times(fillMakerTokenAmount) - .dividedToIntegerBy(order.params.makerTokenAmount); - fillTakerTokenAmounts.push(fillTakerTokenAmount); - balances[maker][makerToken] = balances[maker][makerToken].minus(fillMakerTokenAmount); - balances[maker][takerToken] = balances[maker][takerToken].add(fillTakerTokenAmount); - balances[maker][zrx.address] = balances[maker][zrx.address].minus(makerFee); - balances[taker][makerToken] = balances[taker][makerToken].add(fillMakerTokenAmount); - balances[taker][takerToken] = balances[taker][takerToken].minus(fillTakerTokenAmount); - balances[taker][zrx.address] = balances[taker][zrx.address].minus(takerFee); - balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address].add( - makerFee.add(takerFee), - ); - }); - - await exWrapper.batchFillOrKillOrdersAsync(orders, taker, { - fillTakerTokenAmounts, - }); - - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); - - it('should throw if a single order does not fill the expected amount', async () => { - const fillTakerTokenAmounts: BigNumber[] = []; - orders.forEach(order => { - const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); - fillTakerTokenAmounts.push(fillTakerTokenAmount); - }); - - await exWrapper.fillOrKillOrderAsync(orders[0], taker); - - return expect( - exWrapper.batchFillOrKillOrdersAsync(orders, taker, { - fillTakerTokenAmounts, - }), - ).to.be.rejectedWith(constants.REVERT); - }); - }); - - describe('fillOrdersUpTo', () => { - it('should stop when the entire fillTakerTokenAmount is filled', async () => { - const fillTakerTokenAmount = orders[0].params.takerTokenAmount.plus( - orders[1].params.takerTokenAmount.div(2), - ); - await exWrapper.fillOrdersUpToAsync(orders, taker, { - fillTakerTokenAmount, - }); - - const newBalances = await dmyBalances.getAsync(); - - const fillMakerTokenAmount = orders[0].params.makerTokenAmount.add( - orders[1].params.makerTokenAmount.dividedToIntegerBy(2), - ); - const makerFee = orders[0].params.makerFee.add(orders[1].params.makerFee.dividedToIntegerBy(2)); - const takerFee = orders[0].params.takerFee.add(orders[1].params.takerFee.dividedToIntegerBy(2)); - expect(newBalances[maker][orders[0].params.makerToken]).to.be.bignumber.equal( - balances[maker][orders[0].params.makerToken].minus(fillMakerTokenAmount), - ); - expect(newBalances[maker][orders[0].params.takerToken]).to.be.bignumber.equal( - balances[maker][orders[0].params.takerToken].add(fillTakerTokenAmount), - ); - expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( - balances[maker][zrx.address].minus(makerFee), - ); - expect(newBalances[taker][orders[0].params.takerToken]).to.be.bignumber.equal( - balances[taker][orders[0].params.takerToken].minus(fillTakerTokenAmount), - ); - expect(newBalances[taker][orders[0].params.makerToken]).to.be.bignumber.equal( - balances[taker][orders[0].params.makerToken].add(fillMakerTokenAmount), - ); - expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( - balances[taker][zrx.address].minus(takerFee), - ); - expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( - balances[feeRecipient][zrx.address].add(makerFee.add(takerFee)), - ); - }); - - it('should fill all orders if cannot fill entire fillTakerTokenAmount', async () => { - const fillTakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18); - orders.forEach(order => { - balances[maker][order.params.makerToken] = balances[maker][order.params.makerToken].minus( - order.params.makerTokenAmount, - ); - balances[maker][order.params.takerToken] = balances[maker][order.params.takerToken].add( - order.params.takerTokenAmount, - ); - balances[maker][zrx.address] = balances[maker][zrx.address].minus(order.params.makerFee); - balances[taker][order.params.makerToken] = balances[taker][order.params.makerToken].add( - order.params.makerTokenAmount, - ); - balances[taker][order.params.takerToken] = balances[taker][order.params.takerToken].minus( - order.params.takerTokenAmount, - ); - balances[taker][zrx.address] = balances[taker][zrx.address].minus(order.params.takerFee); - balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address].add( - order.params.makerFee.add(order.params.takerFee), - ); - }); - await exWrapper.fillOrdersUpToAsync(orders, taker, { - fillTakerTokenAmount, - }); - - const newBalances = await dmyBalances.getAsync(); - expect(newBalances).to.be.deep.equal(balances); - }); - - it('should throw when an order does not use the same takerToken', async () => { - orders = await Promise.all([ - orderFactory.newSignedOrderAsync(), - orderFactory.newSignedOrderAsync({ takerToken: zrx.address }), - orderFactory.newSignedOrderAsync(), - ]); - - return expect( - exWrapper.fillOrdersUpToAsync(orders, taker, { - fillTakerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1000), 18), - }), - ).to.be.rejectedWith(constants.REVERT); - }); - }); - - describe('batchCancelOrders', () => { - it('should be able to cancel multiple orders', async () => { - const cancelTakerTokenAmounts = _.map(orders, order => order.params.takerTokenAmount); - await exWrapper.batchCancelOrdersAsync(orders, maker, { - cancelTakerTokenAmounts, - }); - - await exWrapper.batchFillOrdersAsync(orders, taker, { - fillTakerTokenAmounts: cancelTakerTokenAmounts, - }); - const newBalances = await dmyBalances.getAsync(); - expect(balances).to.be.deep.equal(newBalances); - }); - }); - }); + const maker = accounts[0]; + const tokenOwner = accounts[0]; + const taker = accounts[1] || accounts[accounts.length - 1]; + const feeRecipient = accounts[2] || accounts[accounts.length - 1]; + + const INIT_BAL = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); + const INIT_ALLOW = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); + + let rep: ContractInstance; + let dgd: ContractInstance; + let zrx: ContractInstance; + let exchange: ContractInstance; + let tokenRegistry: ContractInstance; + + let balances: BalancesByOwner; + + let exWrapper: ExchangeWrapper; + let dmyBalances: Balances; + let orderFactory: OrderFactory; + + before(async () => { + [tokenRegistry, exchange] = await Promise.all([TokenRegistry.deployed(), Exchange.deployed()]); + exWrapper = new ExchangeWrapper(exchange); + const [repAddress, dgdAddress, zrxAddress] = await Promise.all([ + tokenRegistry.getTokenAddressBySymbol('REP'), + tokenRegistry.getTokenAddressBySymbol('DGD'), + tokenRegistry.getTokenAddressBySymbol('ZRX'), + ]); + + const defaultOrderParams = { + exchangeContractAddress: Exchange.address, + maker, + feeRecipient, + makerToken: repAddress, + takerToken: dgdAddress, + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), + makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), + takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), + }; + orderFactory = new OrderFactory(defaultOrderParams); + + [rep, dgd, zrx] = await Promise.all([ + DummyToken.at(repAddress), + DummyToken.at(dgdAddress), + DummyToken.at(zrxAddress), + ]); + dmyBalances = new Balances([rep, dgd, zrx], [maker, taker, feeRecipient]); + await Promise.all([ + rep.approve(TokenTransferProxy.address, INIT_ALLOW, { from: maker }), + rep.approve(TokenTransferProxy.address, INIT_ALLOW, { from: taker }), + rep.setBalance(maker, INIT_BAL, { from: tokenOwner }), + rep.setBalance(taker, INIT_BAL, { from: tokenOwner }), + dgd.approve(TokenTransferProxy.address, INIT_ALLOW, { from: maker }), + dgd.approve(TokenTransferProxy.address, INIT_ALLOW, { from: taker }), + dgd.setBalance(maker, INIT_BAL, { from: tokenOwner }), + dgd.setBalance(taker, INIT_BAL, { from: tokenOwner }), + zrx.approve(TokenTransferProxy.address, INIT_ALLOW, { from: maker }), + zrx.approve(TokenTransferProxy.address, INIT_ALLOW, { from: taker }), + zrx.setBalance(maker, INIT_BAL, { from: tokenOwner }), + zrx.setBalance(taker, INIT_BAL, { from: tokenOwner }), + ]); + }); + + describe('fillOrKillOrder', () => { + beforeEach(async () => { + balances = await dmyBalances.getAsync(); + }); + + it('should transfer the correct amounts', async () => { + const order = await orderFactory.newSignedOrderAsync({ + makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), + takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), + }); + const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); + await exWrapper.fillOrKillOrderAsync(order, taker, { + fillTakerTokenAmount, + }); + + const newBalances = await dmyBalances.getAsync(); + + const fillMakerTokenAmount = fillTakerTokenAmount + .times(order.params.makerTokenAmount) + .dividedToIntegerBy(order.params.takerTokenAmount); + const makerFee = order.params.makerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + const takerFee = order.params.takerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + expect(newBalances[maker][order.params.makerToken]).to.be.bignumber.equal( + balances[maker][order.params.makerToken].minus(fillMakerTokenAmount), + ); + expect(newBalances[maker][order.params.takerToken]).to.be.bignumber.equal( + balances[maker][order.params.takerToken].add(fillTakerTokenAmount), + ); + expect(newBalances[maker][zrx.address]).to.be.bignumber.equal(balances[maker][zrx.address].minus(makerFee)); + expect(newBalances[taker][order.params.takerToken]).to.be.bignumber.equal( + balances[taker][order.params.takerToken].minus(fillTakerTokenAmount), + ); + expect(newBalances[taker][order.params.makerToken]).to.be.bignumber.equal( + balances[taker][order.params.makerToken].add(fillMakerTokenAmount), + ); + expect(newBalances[taker][zrx.address]).to.be.bignumber.equal(balances[taker][zrx.address].minus(takerFee)); + expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( + balances[feeRecipient][zrx.address].add(makerFee.add(takerFee)), + ); + }); + + it('should throw if an order is expired', async () => { + const order = await orderFactory.newSignedOrderAsync({ + expirationTimestampInSec: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), + }); + + return expect(exWrapper.fillOrKillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if entire fillTakerTokenAmount not filled', async () => { + const order = await orderFactory.newSignedOrderAsync(); + + const from = taker; + await exWrapper.fillOrderAsync(order, from, { + fillTakerTokenAmount: order.params.takerTokenAmount.div(2), + }); + + return expect(exWrapper.fillOrKillOrderAsync(order, taker)).to.be.rejectedWith(constants.REVERT); + }); + }); + + describe('batch functions', () => { + let orders: Order[]; + beforeEach(async () => { + orders = await Promise.all([ + orderFactory.newSignedOrderAsync(), + orderFactory.newSignedOrderAsync(), + orderFactory.newSignedOrderAsync(), + ]); + balances = await dmyBalances.getAsync(); + }); + + describe('batchFillOrders', () => { + it('should transfer the correct amounts', async () => { + const fillTakerTokenAmounts: BigNumber[] = []; + const makerToken = rep.address; + const takerToken = dgd.address; + orders.forEach(order => { + const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); + const fillMakerTokenAmount = fillTakerTokenAmount + .times(order.params.makerTokenAmount) + .dividedToIntegerBy(order.params.takerTokenAmount); + const makerFee = order.params.makerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + const takerFee = order.params.takerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + fillTakerTokenAmounts.push(fillTakerTokenAmount); + balances[maker][makerToken] = balances[maker][makerToken].minus(fillMakerTokenAmount); + balances[maker][takerToken] = balances[maker][takerToken].add(fillTakerTokenAmount); + balances[maker][zrx.address] = balances[maker][zrx.address].minus(makerFee); + balances[taker][makerToken] = balances[taker][makerToken].add(fillMakerTokenAmount); + balances[taker][takerToken] = balances[taker][takerToken].minus(fillTakerTokenAmount); + balances[taker][zrx.address] = balances[taker][zrx.address].minus(takerFee); + balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address].add( + makerFee.add(takerFee), + ); + }); + + await exWrapper.batchFillOrdersAsync(orders, taker, { + fillTakerTokenAmounts, + }); + + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); + }); + + describe('batchFillOrKillOrders', () => { + it('should transfer the correct amounts', async () => { + const fillTakerTokenAmounts: BigNumber[] = []; + const makerToken = rep.address; + const takerToken = dgd.address; + orders.forEach(order => { + const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); + const fillMakerTokenAmount = fillTakerTokenAmount + .times(order.params.makerTokenAmount) + .dividedToIntegerBy(order.params.takerTokenAmount); + const makerFee = order.params.makerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + const takerFee = order.params.takerFee + .times(fillMakerTokenAmount) + .dividedToIntegerBy(order.params.makerTokenAmount); + fillTakerTokenAmounts.push(fillTakerTokenAmount); + balances[maker][makerToken] = balances[maker][makerToken].minus(fillMakerTokenAmount); + balances[maker][takerToken] = balances[maker][takerToken].add(fillTakerTokenAmount); + balances[maker][zrx.address] = balances[maker][zrx.address].minus(makerFee); + balances[taker][makerToken] = balances[taker][makerToken].add(fillMakerTokenAmount); + balances[taker][takerToken] = balances[taker][takerToken].minus(fillTakerTokenAmount); + balances[taker][zrx.address] = balances[taker][zrx.address].minus(takerFee); + balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address].add( + makerFee.add(takerFee), + ); + }); + + await exWrapper.batchFillOrKillOrdersAsync(orders, taker, { + fillTakerTokenAmounts, + }); + + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); + + it('should throw if a single order does not fill the expected amount', async () => { + const fillTakerTokenAmounts: BigNumber[] = []; + orders.forEach(order => { + const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); + fillTakerTokenAmounts.push(fillTakerTokenAmount); + }); + + await exWrapper.fillOrKillOrderAsync(orders[0], taker); + + return expect( + exWrapper.batchFillOrKillOrdersAsync(orders, taker, { + fillTakerTokenAmounts, + }), + ).to.be.rejectedWith(constants.REVERT); + }); + }); + + describe('fillOrdersUpTo', () => { + it('should stop when the entire fillTakerTokenAmount is filled', async () => { + const fillTakerTokenAmount = orders[0].params.takerTokenAmount.plus( + orders[1].params.takerTokenAmount.div(2), + ); + await exWrapper.fillOrdersUpToAsync(orders, taker, { + fillTakerTokenAmount, + }); + + const newBalances = await dmyBalances.getAsync(); + + const fillMakerTokenAmount = orders[0].params.makerTokenAmount.add( + orders[1].params.makerTokenAmount.dividedToIntegerBy(2), + ); + const makerFee = orders[0].params.makerFee.add(orders[1].params.makerFee.dividedToIntegerBy(2)); + const takerFee = orders[0].params.takerFee.add(orders[1].params.takerFee.dividedToIntegerBy(2)); + expect(newBalances[maker][orders[0].params.makerToken]).to.be.bignumber.equal( + balances[maker][orders[0].params.makerToken].minus(fillMakerTokenAmount), + ); + expect(newBalances[maker][orders[0].params.takerToken]).to.be.bignumber.equal( + balances[maker][orders[0].params.takerToken].add(fillTakerTokenAmount), + ); + expect(newBalances[maker][zrx.address]).to.be.bignumber.equal( + balances[maker][zrx.address].minus(makerFee), + ); + expect(newBalances[taker][orders[0].params.takerToken]).to.be.bignumber.equal( + balances[taker][orders[0].params.takerToken].minus(fillTakerTokenAmount), + ); + expect(newBalances[taker][orders[0].params.makerToken]).to.be.bignumber.equal( + balances[taker][orders[0].params.makerToken].add(fillMakerTokenAmount), + ); + expect(newBalances[taker][zrx.address]).to.be.bignumber.equal( + balances[taker][zrx.address].minus(takerFee), + ); + expect(newBalances[feeRecipient][zrx.address]).to.be.bignumber.equal( + balances[feeRecipient][zrx.address].add(makerFee.add(takerFee)), + ); + }); + + it('should fill all orders if cannot fill entire fillTakerTokenAmount', async () => { + const fillTakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(100000), 18); + orders.forEach(order => { + balances[maker][order.params.makerToken] = balances[maker][order.params.makerToken].minus( + order.params.makerTokenAmount, + ); + balances[maker][order.params.takerToken] = balances[maker][order.params.takerToken].add( + order.params.takerTokenAmount, + ); + balances[maker][zrx.address] = balances[maker][zrx.address].minus(order.params.makerFee); + balances[taker][order.params.makerToken] = balances[taker][order.params.makerToken].add( + order.params.makerTokenAmount, + ); + balances[taker][order.params.takerToken] = balances[taker][order.params.takerToken].minus( + order.params.takerTokenAmount, + ); + balances[taker][zrx.address] = balances[taker][zrx.address].minus(order.params.takerFee); + balances[feeRecipient][zrx.address] = balances[feeRecipient][zrx.address].add( + order.params.makerFee.add(order.params.takerFee), + ); + }); + await exWrapper.fillOrdersUpToAsync(orders, taker, { + fillTakerTokenAmount, + }); + + const newBalances = await dmyBalances.getAsync(); + expect(newBalances).to.be.deep.equal(balances); + }); + + it('should throw when an order does not use the same takerToken', async () => { + orders = await Promise.all([ + orderFactory.newSignedOrderAsync(), + orderFactory.newSignedOrderAsync({ takerToken: zrx.address }), + orderFactory.newSignedOrderAsync(), + ]); + + return expect( + exWrapper.fillOrdersUpToAsync(orders, taker, { + fillTakerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1000), 18), + }), + ).to.be.rejectedWith(constants.REVERT); + }); + }); + + describe('batchCancelOrders', () => { + it('should be able to cancel multiple orders', async () => { + const cancelTakerTokenAmounts = _.map(orders, order => order.params.takerTokenAmount); + await exWrapper.batchCancelOrdersAsync(orders, maker, { + cancelTakerTokenAmounts, + }); + + await exWrapper.batchFillOrdersAsync(orders, taker, { + fillTakerTokenAmounts: cancelTakerTokenAmounts, + }); + const newBalances = await dmyBalances.getAsync(); + expect(balances).to.be.deep.equal(newBalances); + }); + }); + }); }); diff --git a/packages/contracts/test/ts/multi_sig_with_time_lock.ts b/packages/contracts/test/ts/multi_sig_with_time_lock.ts index fa2028437..ea939a758 100644 --- a/packages/contracts/test/ts/multi_sig_with_time_lock.ts +++ b/packages/contracts/test/ts/multi_sig_with_time_lock.ts @@ -23,93 +23,93 @@ const expect = chai.expect; const web3: Web3 = (global as any).web3; contract('MultiSigWalletWithTimeLock', (accounts: string[]) => { - const owners = [accounts[0], accounts[1]]; - const SECONDS_TIME_LOCKED = 10000; - - let multiSig: ContractInstance; - let multiSigWrapper: MultiSigWrapper; - let txId: number; - let initialSecondsTimeLocked: number; - let rpc: RPC; - - before(async () => { - multiSig = await MultiSigWalletWithTimeLock.deployed(); - multiSigWrapper = new MultiSigWrapper(multiSig); - - const secondsTimeLocked = await multiSig.secondsTimeLocked.call(); - initialSecondsTimeLocked = secondsTimeLocked.toNumber(); - const rpcUrl = `http://${truffleConf.networks.development.host}:${truffleConf.networks.development.port}`; - rpc = new RPC(rpcUrl); - }); - - describe('changeTimeLock', () => { - it('should throw when not called by wallet', async () => { - return expect(multiSig.changeTimeLock(SECONDS_TIME_LOCKED, { from: owners[0] })).to.be.rejectedWith( - constants.REVERT, - ); - }); - - it('should throw without enough confirmations', async () => { - const destination = multiSig.address; - const from = owners[0]; - const dataParams = { - name: 'changeTimeLock', - abi: MULTI_SIG_ABI, - args: [SECONDS_TIME_LOCKED], - }; - const subRes = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams); - - txId = subRes.logs[0].args.transactionId.toNumber(); - return expect(multiSig.executeTransaction(txId)).to.be.rejectedWith(constants.REVERT); - }); - - it('should set confirmation time with enough confirmations', async () => { - const res = await multiSig.confirmTransaction(txId, { from: owners[1] }); - expect(res.logs).to.have.length(2); - const blockNum = await promisify<number>(web3.eth.getBlockNumber)(); - const blockInfo = await promisify<Web3.BlockWithoutTransactionData>(web3.eth.getBlock)(blockNum); - const timestamp = new BigNumber(blockInfo.timestamp); - const confirmationTimeBigNum = new BigNumber(await multiSig.confirmationTimes.call(txId)); - - expect(timestamp).to.be.bignumber.equal(confirmationTimeBigNum); - }); - - it('should be executable with enough confirmations and secondsTimeLocked of 0', async () => { - expect(initialSecondsTimeLocked).to.be.equal(0); - - const res = await multiSig.executeTransaction(txId); - expect(res.logs).to.have.length(2); - - const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.call()); - expect(secondsTimeLocked).to.be.bignumber.equal(SECONDS_TIME_LOCKED); - }); - - const newSecondsTimeLocked = 0; - it('should throw if it has enough confirmations but is not past the time lock', async () => { - const destination = multiSig.address; - const from = owners[0]; - const dataParams = { - name: 'changeTimeLock', - abi: MULTI_SIG_ABI, - args: [newSecondsTimeLocked], - }; - const subRes = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams); - - txId = subRes.logs[0].args.transactionId.toNumber(); - const confRes = await multiSig.confirmTransaction(txId, { - from: owners[1], - }); - expect(confRes.logs).to.have.length(2); - - return expect(multiSig.executeTransaction(txId)).to.be.rejectedWith(constants.REVERT); - }); - - it('should execute if it has enough confirmations and is past the time lock', async () => { - await rpc.increaseTimeAsync(SECONDS_TIME_LOCKED); - await multiSig.executeTransaction(txId); - - const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.call()); - expect(secondsTimeLocked).to.be.bignumber.equal(newSecondsTimeLocked); - }); - }); + const owners = [accounts[0], accounts[1]]; + const SECONDS_TIME_LOCKED = 10000; + + let multiSig: ContractInstance; + let multiSigWrapper: MultiSigWrapper; + let txId: number; + let initialSecondsTimeLocked: number; + let rpc: RPC; + + before(async () => { + multiSig = await MultiSigWalletWithTimeLock.deployed(); + multiSigWrapper = new MultiSigWrapper(multiSig); + + const secondsTimeLocked = await multiSig.secondsTimeLocked.call(); + initialSecondsTimeLocked = secondsTimeLocked.toNumber(); + const rpcUrl = `http://${truffleConf.networks.development.host}:${truffleConf.networks.development.port}`; + rpc = new RPC(rpcUrl); + }); + + describe('changeTimeLock', () => { + it('should throw when not called by wallet', async () => { + return expect(multiSig.changeTimeLock(SECONDS_TIME_LOCKED, { from: owners[0] })).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should throw without enough confirmations', async () => { + const destination = multiSig.address; + const from = owners[0]; + const dataParams = { + name: 'changeTimeLock', + abi: MULTI_SIG_ABI, + args: [SECONDS_TIME_LOCKED], + }; + const subRes = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams); + + txId = subRes.logs[0].args.transactionId.toNumber(); + return expect(multiSig.executeTransaction(txId)).to.be.rejectedWith(constants.REVERT); + }); + + it('should set confirmation time with enough confirmations', async () => { + const res = await multiSig.confirmTransaction(txId, { from: owners[1] }); + expect(res.logs).to.have.length(2); + const blockNum = await promisify<number>(web3.eth.getBlockNumber)(); + const blockInfo = await promisify<Web3.BlockWithoutTransactionData>(web3.eth.getBlock)(blockNum); + const timestamp = new BigNumber(blockInfo.timestamp); + const confirmationTimeBigNum = new BigNumber(await multiSig.confirmationTimes.call(txId)); + + expect(timestamp).to.be.bignumber.equal(confirmationTimeBigNum); + }); + + it('should be executable with enough confirmations and secondsTimeLocked of 0', async () => { + expect(initialSecondsTimeLocked).to.be.equal(0); + + const res = await multiSig.executeTransaction(txId); + expect(res.logs).to.have.length(2); + + const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.call()); + expect(secondsTimeLocked).to.be.bignumber.equal(SECONDS_TIME_LOCKED); + }); + + const newSecondsTimeLocked = 0; + it('should throw if it has enough confirmations but is not past the time lock', async () => { + const destination = multiSig.address; + const from = owners[0]; + const dataParams = { + name: 'changeTimeLock', + abi: MULTI_SIG_ABI, + args: [newSecondsTimeLocked], + }; + const subRes = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams); + + txId = subRes.logs[0].args.transactionId.toNumber(); + const confRes = await multiSig.confirmTransaction(txId, { + from: owners[1], + }); + expect(confRes.logs).to.have.length(2); + + return expect(multiSig.executeTransaction(txId)).to.be.rejectedWith(constants.REVERT); + }); + + it('should execute if it has enough confirmations and is past the time lock', async () => { + await rpc.increaseTimeAsync(SECONDS_TIME_LOCKED); + await multiSig.executeTransaction(txId); + + const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.call()); + expect(secondsTimeLocked).to.be.bignumber.equal(newSecondsTimeLocked); + }); + }); }); diff --git a/packages/contracts/test/ts/multi_sig_with_time_lock_except_remove_auth_addr.ts b/packages/contracts/test/ts/multi_sig_with_time_lock_except_remove_auth_addr.ts index 9211cb1b6..62aa625fe 100644 --- a/packages/contracts/test/ts/multi_sig_with_time_lock_except_remove_auth_addr.ts +++ b/packages/contracts/test/ts/multi_sig_with_time_lock_except_remove_auth_addr.ts @@ -15,136 +15,136 @@ chaiSetup.configure(); const expect = chai.expect; contract('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', (accounts: string[]) => { - const owners = [accounts[0], accounts[1]]; - const requiredApprovals = 2; - const SECONDS_TIME_LOCKED = 1000000; - - // initialize fake addresses - const authorizedAddress = `0x${crypto - .solSHA3([accounts[0]]) - .slice(0, 20) - .toString('hex')}`; - const unauthorizedAddress = `0x${crypto - .solSHA3([accounts[1]]) - .slice(0, 20) - .toString('hex')}`; - - let tokenTransferProxy: ContractInstance; - let multiSig: ContractInstance; - let multiSigWrapper: MultiSigWrapper; - - let validDestination: string; - - beforeEach(async () => { - const initialOwner = accounts[0]; - tokenTransferProxy = await TokenTransferProxy.new({ from: initialOwner }); - await tokenTransferProxy.addAuthorizedAddress(authorizedAddress, { - from: initialOwner, - }); - multiSig = await MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.new( - owners, - requiredApprovals, - SECONDS_TIME_LOCKED, - tokenTransferProxy.address, - ); - await tokenTransferProxy.transferOwnership(multiSig.address, { - from: initialOwner, - }); - multiSigWrapper = new MultiSigWrapper(multiSig); - validDestination = tokenTransferProxy.address; - }); - - describe('isFunctionRemoveAuthorizedAddress', () => { - it('should throw if data is not for removeAuthorizedAddress', async () => { - const data = MultiSigWrapper.encodeFnArgs('addAuthorizedAddress', PROXY_ABI, [owners[0]]); - return expect(multiSig.isFunctionRemoveAuthorizedAddress.call(data)).to.be.rejectedWith(constants.REVERT); - }); - - it('should return true if data is for removeAuthorizedAddress', async () => { - const data = MultiSigWrapper.encodeFnArgs('removeAuthorizedAddress', PROXY_ABI, [owners[0]]); - const isFunctionRemoveAuthorizedAddress = await multiSig.isFunctionRemoveAuthorizedAddress.call(data); - expect(isFunctionRemoveAuthorizedAddress).to.be.true(); - }); - }); - - describe('executeRemoveAuthorizedAddress', () => { - it('should throw without the required confirmations', async () => { - const dataParams: TransactionDataParams = { - name: 'removeAuthorizedAddress', - abi: PROXY_ABI, - args: [authorizedAddress], - }; - const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); - const txId = res.logs[0].args.transactionId.toString(); - - return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if tx destination is not the tokenTransferProxy', async () => { - const invalidTokenTransferProxy = await TokenTransferProxy.new(); - const invalidDestination = invalidTokenTransferProxy.address; - const dataParams: TransactionDataParams = { - name: 'removeAuthorizedAddress', - abi: PROXY_ABI, - args: [authorizedAddress], - }; - const res = await multiSigWrapper.submitTransactionAsync(invalidDestination, owners[0], dataParams); - const txId = res.logs[0].args.transactionId.toString(); - await multiSig.confirmTransaction(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed.call(txId); - expect(isConfirmed).to.be.true(); - - return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if tx data is not for removeAuthorizedAddress', async () => { - const dataParams: TransactionDataParams = { - name: 'addAuthorizedAddress', - abi: PROXY_ABI, - args: [unauthorizedAddress], - }; - const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); - const txId = res.logs[0].args.transactionId.toString(); - await multiSig.confirmTransaction(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed.call(txId); - expect(isConfirmed).to.be.true(); - - return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); - }); - - it('should execute removeAuthorizedAddress for valid tokenTransferProxy if fully confirmed', async () => { - const dataParams: TransactionDataParams = { - name: 'removeAuthorizedAddress', - abi: PROXY_ABI, - args: [authorizedAddress], - }; - const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); - const txId = res.logs[0].args.transactionId.toString(); - await multiSig.confirmTransaction(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed.call(txId); - expect(isConfirmed).to.be.true(); - await multiSig.executeRemoveAuthorizedAddress(txId); - - const isAuthorized = await tokenTransferProxy.authorized.call(authorizedAddress); - expect(isAuthorized).to.be.false(); - }); - - it('should throw if already executed', async () => { - const dataParams: TransactionDataParams = { - name: 'removeAuthorizedAddress', - abi: PROXY_ABI, - args: [authorizedAddress], - }; - const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); - const txId = res.logs[0].args.transactionId.toString(); - await multiSig.confirmTransaction(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed.call(txId); - expect(isConfirmed).to.be.true(); - await multiSig.executeRemoveAuthorizedAddress(txId); - const tx = await multiSig.transactions.call(txId); - const isExecuted = tx[3]; - expect(isExecuted).to.be.true(); - return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); - }); - }); + const owners = [accounts[0], accounts[1]]; + const requiredApprovals = 2; + const SECONDS_TIME_LOCKED = 1000000; + + // initialize fake addresses + const authorizedAddress = `0x${crypto + .solSHA3([accounts[0]]) + .slice(0, 20) + .toString('hex')}`; + const unauthorizedAddress = `0x${crypto + .solSHA3([accounts[1]]) + .slice(0, 20) + .toString('hex')}`; + + let tokenTransferProxy: ContractInstance; + let multiSig: ContractInstance; + let multiSigWrapper: MultiSigWrapper; + + let validDestination: string; + + beforeEach(async () => { + const initialOwner = accounts[0]; + tokenTransferProxy = await TokenTransferProxy.new({ from: initialOwner }); + await tokenTransferProxy.addAuthorizedAddress(authorizedAddress, { + from: initialOwner, + }); + multiSig = await MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.new( + owners, + requiredApprovals, + SECONDS_TIME_LOCKED, + tokenTransferProxy.address, + ); + await tokenTransferProxy.transferOwnership(multiSig.address, { + from: initialOwner, + }); + multiSigWrapper = new MultiSigWrapper(multiSig); + validDestination = tokenTransferProxy.address; + }); + + describe('isFunctionRemoveAuthorizedAddress', () => { + it('should throw if data is not for removeAuthorizedAddress', async () => { + const data = MultiSigWrapper.encodeFnArgs('addAuthorizedAddress', PROXY_ABI, [owners[0]]); + return expect(multiSig.isFunctionRemoveAuthorizedAddress.call(data)).to.be.rejectedWith(constants.REVERT); + }); + + it('should return true if data is for removeAuthorizedAddress', async () => { + const data = MultiSigWrapper.encodeFnArgs('removeAuthorizedAddress', PROXY_ABI, [owners[0]]); + const isFunctionRemoveAuthorizedAddress = await multiSig.isFunctionRemoveAuthorizedAddress.call(data); + expect(isFunctionRemoveAuthorizedAddress).to.be.true(); + }); + }); + + describe('executeRemoveAuthorizedAddress', () => { + it('should throw without the required confirmations', async () => { + const dataParams: TransactionDataParams = { + name: 'removeAuthorizedAddress', + abi: PROXY_ABI, + args: [authorizedAddress], + }; + const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); + const txId = res.logs[0].args.transactionId.toString(); + + return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if tx destination is not the tokenTransferProxy', async () => { + const invalidTokenTransferProxy = await TokenTransferProxy.new(); + const invalidDestination = invalidTokenTransferProxy.address; + const dataParams: TransactionDataParams = { + name: 'removeAuthorizedAddress', + abi: PROXY_ABI, + args: [authorizedAddress], + }; + const res = await multiSigWrapper.submitTransactionAsync(invalidDestination, owners[0], dataParams); + const txId = res.logs[0].args.transactionId.toString(); + await multiSig.confirmTransaction(txId, { from: owners[1] }); + const isConfirmed = await multiSig.isConfirmed.call(txId); + expect(isConfirmed).to.be.true(); + + return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if tx data is not for removeAuthorizedAddress', async () => { + const dataParams: TransactionDataParams = { + name: 'addAuthorizedAddress', + abi: PROXY_ABI, + args: [unauthorizedAddress], + }; + const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); + const txId = res.logs[0].args.transactionId.toString(); + await multiSig.confirmTransaction(txId, { from: owners[1] }); + const isConfirmed = await multiSig.isConfirmed.call(txId); + expect(isConfirmed).to.be.true(); + + return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); + }); + + it('should execute removeAuthorizedAddress for valid tokenTransferProxy if fully confirmed', async () => { + const dataParams: TransactionDataParams = { + name: 'removeAuthorizedAddress', + abi: PROXY_ABI, + args: [authorizedAddress], + }; + const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); + const txId = res.logs[0].args.transactionId.toString(); + await multiSig.confirmTransaction(txId, { from: owners[1] }); + const isConfirmed = await multiSig.isConfirmed.call(txId); + expect(isConfirmed).to.be.true(); + await multiSig.executeRemoveAuthorizedAddress(txId); + + const isAuthorized = await tokenTransferProxy.authorized.call(authorizedAddress); + expect(isAuthorized).to.be.false(); + }); + + it('should throw if already executed', async () => { + const dataParams: TransactionDataParams = { + name: 'removeAuthorizedAddress', + abi: PROXY_ABI, + args: [authorizedAddress], + }; + const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); + const txId = res.logs[0].args.transactionId.toString(); + await multiSig.confirmTransaction(txId, { from: owners[1] }); + const isConfirmed = await multiSig.isConfirmed.call(txId); + expect(isConfirmed).to.be.true(); + await multiSig.executeRemoveAuthorizedAddress(txId); + const tx = await multiSig.transactions.call(txId); + const isExecuted = tx[3]; + expect(isExecuted).to.be.true(); + return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); + }); + }); }); diff --git a/packages/contracts/test/ts/token_registry.ts b/packages/contracts/test/ts/token_registry.ts index fadb276da..d1c551565 100644 --- a/packages/contracts/test/ts/token_registry.ts +++ b/packages/contracts/test/ts/token_registry.ts @@ -15,220 +15,220 @@ chaiSetup.configure(); const expect = chai.expect; contract('TokenRegistry', (accounts: string[]) => { - const owner = accounts[0]; - const notOwner = accounts[1]; - - const tokenAddress1 = `0x${ethUtil.setLength(ethUtil.toBuffer('0x1'), 20, false).toString('hex')}`; - const tokenAddress2 = `0x${ethUtil.setLength(ethUtil.toBuffer('0x2'), 20, false).toString('hex')}`; - - const token1 = { - address: tokenAddress1, - name: 'testToken1', - symbol: 'TT1', - decimals: 18, - ipfsHash: `0x${ethUtil.sha3('ipfs1').toString('hex')}`, - swarmHash: `0x${ethUtil.sha3('swarm1').toString('hex')}`, - }; - - const token2 = { - address: tokenAddress2, - name: 'testToken2', - symbol: 'TT2', - decimals: 18, - ipfsHash: `0x${ethUtil.sha3('ipfs2').toString('hex')}`, - swarmHash: `0x${ethUtil.sha3('swarm2').toString('hex')}`, - }; - - const nullToken = { - address: ZeroEx.NULL_ADDRESS, - name: '', - symbol: '', - decimals: 0, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }; - - let tokenReg: ContractInstance; - let tokenRegWrapper: TokenRegWrapper; - - beforeEach(async () => { - tokenReg = await TokenRegistry.new(); - tokenRegWrapper = new TokenRegWrapper(tokenReg); - }); - - describe('addToken', () => { - it('should throw when not called by owner', async () => { - return expect(tokenRegWrapper.addTokenAsync(token1, notOwner)).to.be.rejectedWith(constants.REVERT); - }); - - it('should add token metadata when called by owner', async () => { - await tokenRegWrapper.addTokenAsync(token1, owner); - const tokenData = await tokenRegWrapper.getTokenMetaDataAsync(token1.address); - expect(tokenData).to.be.deep.equal(token1); - }); - - it('should throw if token already exists', async () => { - await tokenRegWrapper.addTokenAsync(token1, owner); - - return expect(tokenRegWrapper.addTokenAsync(token1, owner)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if token address is null', async () => { - return expect(tokenRegWrapper.addTokenAsync(nullToken, owner)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if name already exists', async () => { - await tokenRegWrapper.addTokenAsync(token1, owner); - const duplicateNameToken = _.assign({}, token2, { name: token1.name }); - - return expect(tokenRegWrapper.addTokenAsync(duplicateNameToken, owner)).to.be.rejectedWith( - constants.REVERT, - ); - }); - - it('should throw if symbol already exists', async () => { - await tokenRegWrapper.addTokenAsync(token1, owner); - const duplicateSymbolToken = _.assign({}, token2, { - symbol: token1.symbol, - }); - - return expect(tokenRegWrapper.addTokenAsync(duplicateSymbolToken, owner)).to.be.rejectedWith( - constants.REVERT, - ); - }); - }); - - describe('after addToken', () => { - beforeEach(async () => { - await tokenRegWrapper.addTokenAsync(token1, owner); - }); - - describe('getTokenByName', () => { - it('should return token metadata when given the token name', async () => { - const tokenData = await tokenRegWrapper.getTokenByNameAsync(token1.name); - expect(tokenData).to.be.deep.equal(token1); - }); - }); - - describe('getTokenBySymbol', () => { - it('should return token metadata when given the token symbol', async () => { - const tokenData = await tokenRegWrapper.getTokenBySymbolAsync(token1.symbol); - expect(tokenData).to.be.deep.equal(token1); - }); - }); - - describe('setTokenName', () => { - it('should throw when not called by owner', async () => { - return expect( - tokenReg.setTokenName(token1.address, token2.name, { from: notOwner }), - ).to.be.rejectedWith(constants.REVERT); - }); - - it('should change the token name when called by owner', async () => { - const res = await tokenReg.setTokenName(token1.address, token2.name, { - from: owner, - }); - expect(res.logs).to.have.length(1); - const [newData, oldData] = await Promise.all([ - tokenRegWrapper.getTokenByNameAsync(token2.name), - tokenRegWrapper.getTokenByNameAsync(token1.name), - ]); - - const expectedNewData = _.assign({}, token1, { name: token2.name }); - const expectedOldData = nullToken; - expect(newData).to.be.deep.equal(expectedNewData); - expect(oldData).to.be.deep.equal(expectedOldData); - }); - - it('should throw if the name already exists', async () => { - await tokenRegWrapper.addTokenAsync(token2, owner); - - return expect(tokenReg.setTokenName(token1.address, token2.name, { from: owner })).to.be.rejectedWith( - constants.REVERT, - ); - }); - - it('should throw if token does not exist', async () => { - return expect( - tokenReg.setTokenName(nullToken.address, token2.name, { from: owner }), - ).to.be.rejectedWith(constants.REVERT); - }); - }); - - describe('setTokenSymbol', () => { - it('should throw when not called by owner', async () => { - return expect( - tokenReg.setTokenSymbol(token1.address, token2.symbol, { - from: notOwner, - }), - ).to.be.rejectedWith(constants.REVERT); - }); - - it('should change the token symbol when called by owner', async () => { - const res = await tokenReg.setTokenSymbol(token1.address, token2.symbol, { from: owner }); - expect(res.logs).to.have.length(1); - const [newData, oldData] = await Promise.all([ - tokenRegWrapper.getTokenBySymbolAsync(token2.symbol), - tokenRegWrapper.getTokenBySymbolAsync(token1.symbol), - ]); - - const expectedNewData = _.assign({}, token1, { symbol: token2.symbol }); - const expectedOldData = nullToken; - expect(newData).to.be.deep.equal(expectedNewData); - expect(oldData).to.be.deep.equal(expectedOldData); - }); - - it('should throw if the symbol already exists', async () => { - await tokenRegWrapper.addTokenAsync(token2, owner); - - return expect( - tokenReg.setTokenSymbol(token1.address, token2.symbol, { - from: owner, - }), - ).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if token does not exist', async () => { - return expect( - tokenReg.setTokenSymbol(nullToken.address, token2.symbol, { - from: owner, - }), - ).to.be.rejectedWith(constants.REVERT); - }); - }); - - describe('removeToken', () => { - it('should throw if not called by owner', async () => { - const index = 0; - return expect(tokenReg.removeToken(token1.address, index, { from: notOwner })).to.be.rejectedWith( - constants.REVERT, - ); - }); - - it('should remove token metadata when called by owner', async () => { - const index = 0; - const res = await tokenReg.removeToken(token1.address, index, { - from: owner, - }); - expect(res.logs).to.have.length(1); - const tokenData = await tokenRegWrapper.getTokenMetaDataAsync(token1.address); - expect(tokenData).to.be.deep.equal(nullToken); - }); - - it('should throw if token does not exist', async () => { - const index = 0; - return expect(tokenReg.removeToken(nullToken.address, index, { from: owner })).to.be.rejectedWith( - constants.REVERT, - ); - }); - - it('should throw if token at given index does not match address', async () => { - await tokenRegWrapper.addTokenAsync(token2, owner); - const incorrectIndex = 0; - return expect(tokenReg.removeToken(token2.address, incorrectIndex, { from: owner })).to.be.rejectedWith( - constants.REVERT, - ); - }); - }); - }); + const owner = accounts[0]; + const notOwner = accounts[1]; + + const tokenAddress1 = `0x${ethUtil.setLength(ethUtil.toBuffer('0x1'), 20, false).toString('hex')}`; + const tokenAddress2 = `0x${ethUtil.setLength(ethUtil.toBuffer('0x2'), 20, false).toString('hex')}`; + + const token1 = { + address: tokenAddress1, + name: 'testToken1', + symbol: 'TT1', + decimals: 18, + ipfsHash: `0x${ethUtil.sha3('ipfs1').toString('hex')}`, + swarmHash: `0x${ethUtil.sha3('swarm1').toString('hex')}`, + }; + + const token2 = { + address: tokenAddress2, + name: 'testToken2', + symbol: 'TT2', + decimals: 18, + ipfsHash: `0x${ethUtil.sha3('ipfs2').toString('hex')}`, + swarmHash: `0x${ethUtil.sha3('swarm2').toString('hex')}`, + }; + + const nullToken = { + address: ZeroEx.NULL_ADDRESS, + name: '', + symbol: '', + decimals: 0, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }; + + let tokenReg: ContractInstance; + let tokenRegWrapper: TokenRegWrapper; + + beforeEach(async () => { + tokenReg = await TokenRegistry.new(); + tokenRegWrapper = new TokenRegWrapper(tokenReg); + }); + + describe('addToken', () => { + it('should throw when not called by owner', async () => { + return expect(tokenRegWrapper.addTokenAsync(token1, notOwner)).to.be.rejectedWith(constants.REVERT); + }); + + it('should add token metadata when called by owner', async () => { + await tokenRegWrapper.addTokenAsync(token1, owner); + const tokenData = await tokenRegWrapper.getTokenMetaDataAsync(token1.address); + expect(tokenData).to.be.deep.equal(token1); + }); + + it('should throw if token already exists', async () => { + await tokenRegWrapper.addTokenAsync(token1, owner); + + return expect(tokenRegWrapper.addTokenAsync(token1, owner)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if token address is null', async () => { + return expect(tokenRegWrapper.addTokenAsync(nullToken, owner)).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if name already exists', async () => { + await tokenRegWrapper.addTokenAsync(token1, owner); + const duplicateNameToken = _.assign({}, token2, { name: token1.name }); + + return expect(tokenRegWrapper.addTokenAsync(duplicateNameToken, owner)).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should throw if symbol already exists', async () => { + await tokenRegWrapper.addTokenAsync(token1, owner); + const duplicateSymbolToken = _.assign({}, token2, { + symbol: token1.symbol, + }); + + return expect(tokenRegWrapper.addTokenAsync(duplicateSymbolToken, owner)).to.be.rejectedWith( + constants.REVERT, + ); + }); + }); + + describe('after addToken', () => { + beforeEach(async () => { + await tokenRegWrapper.addTokenAsync(token1, owner); + }); + + describe('getTokenByName', () => { + it('should return token metadata when given the token name', async () => { + const tokenData = await tokenRegWrapper.getTokenByNameAsync(token1.name); + expect(tokenData).to.be.deep.equal(token1); + }); + }); + + describe('getTokenBySymbol', () => { + it('should return token metadata when given the token symbol', async () => { + const tokenData = await tokenRegWrapper.getTokenBySymbolAsync(token1.symbol); + expect(tokenData).to.be.deep.equal(token1); + }); + }); + + describe('setTokenName', () => { + it('should throw when not called by owner', async () => { + return expect( + tokenReg.setTokenName(token1.address, token2.name, { from: notOwner }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should change the token name when called by owner', async () => { + const res = await tokenReg.setTokenName(token1.address, token2.name, { + from: owner, + }); + expect(res.logs).to.have.length(1); + const [newData, oldData] = await Promise.all([ + tokenRegWrapper.getTokenByNameAsync(token2.name), + tokenRegWrapper.getTokenByNameAsync(token1.name), + ]); + + const expectedNewData = _.assign({}, token1, { name: token2.name }); + const expectedOldData = nullToken; + expect(newData).to.be.deep.equal(expectedNewData); + expect(oldData).to.be.deep.equal(expectedOldData); + }); + + it('should throw if the name already exists', async () => { + await tokenRegWrapper.addTokenAsync(token2, owner); + + return expect(tokenReg.setTokenName(token1.address, token2.name, { from: owner })).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should throw if token does not exist', async () => { + return expect( + tokenReg.setTokenName(nullToken.address, token2.name, { from: owner }), + ).to.be.rejectedWith(constants.REVERT); + }); + }); + + describe('setTokenSymbol', () => { + it('should throw when not called by owner', async () => { + return expect( + tokenReg.setTokenSymbol(token1.address, token2.symbol, { + from: notOwner, + }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should change the token symbol when called by owner', async () => { + const res = await tokenReg.setTokenSymbol(token1.address, token2.symbol, { from: owner }); + expect(res.logs).to.have.length(1); + const [newData, oldData] = await Promise.all([ + tokenRegWrapper.getTokenBySymbolAsync(token2.symbol), + tokenRegWrapper.getTokenBySymbolAsync(token1.symbol), + ]); + + const expectedNewData = _.assign({}, token1, { symbol: token2.symbol }); + const expectedOldData = nullToken; + expect(newData).to.be.deep.equal(expectedNewData); + expect(oldData).to.be.deep.equal(expectedOldData); + }); + + it('should throw if the symbol already exists', async () => { + await tokenRegWrapper.addTokenAsync(token2, owner); + + return expect( + tokenReg.setTokenSymbol(token1.address, token2.symbol, { + from: owner, + }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if token does not exist', async () => { + return expect( + tokenReg.setTokenSymbol(nullToken.address, token2.symbol, { + from: owner, + }), + ).to.be.rejectedWith(constants.REVERT); + }); + }); + + describe('removeToken', () => { + it('should throw if not called by owner', async () => { + const index = 0; + return expect(tokenReg.removeToken(token1.address, index, { from: notOwner })).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should remove token metadata when called by owner', async () => { + const index = 0; + const res = await tokenReg.removeToken(token1.address, index, { + from: owner, + }); + expect(res.logs).to.have.length(1); + const tokenData = await tokenRegWrapper.getTokenMetaDataAsync(token1.address); + expect(tokenData).to.be.deep.equal(nullToken); + }); + + it('should throw if token does not exist', async () => { + const index = 0; + return expect(tokenReg.removeToken(nullToken.address, index, { from: owner })).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should throw if token at given index does not match address', async () => { + await tokenRegWrapper.addTokenAsync(token2, owner); + const incorrectIndex = 0; + return expect(tokenReg.removeToken(token2.address, incorrectIndex, { from: owner })).to.be.rejectedWith( + constants.REVERT, + ); + }); + }); + }); }); diff --git a/packages/contracts/test/ts/token_transfer_proxy/auth.ts b/packages/contracts/test/ts/token_transfer_proxy/auth.ts index 69a6c0ada..9ae0a8fc3 100644 --- a/packages/contracts/test/ts/token_transfer_proxy/auth.ts +++ b/packages/contracts/test/ts/token_transfer_proxy/auth.ts @@ -9,91 +9,91 @@ const expect = chai.expect; const TokenTransferProxy = artifacts.require('./db/TokenTransferProxy.sol'); contract('TokenTransferProxy', (accounts: string[]) => { - const owner = accounts[0]; - const notOwner = accounts[1]; + const owner = accounts[0]; + const notOwner = accounts[1]; - let tokenTransferProxy: ContractInstance; - let authorized: string; - let notAuthorized = owner; + let tokenTransferProxy: ContractInstance; + let authorized: string; + let notAuthorized = owner; - before(async () => { - tokenTransferProxy = await TokenTransferProxy.deployed(); - }); + before(async () => { + tokenTransferProxy = await TokenTransferProxy.deployed(); + }); - describe('addAuthorizedAddress', () => { - it('should throw if not called by owner', async () => { - return expect(tokenTransferProxy.addAuthorizedAddress(notOwner, { from: notOwner })).to.be.rejectedWith( - constants.REVERT, - ); - }); + describe('addAuthorizedAddress', () => { + it('should throw if not called by owner', async () => { + return expect(tokenTransferProxy.addAuthorizedAddress(notOwner, { from: notOwner })).to.be.rejectedWith( + constants.REVERT, + ); + }); - it('should allow owner to add an authorized address', async () => { - await tokenTransferProxy.addAuthorizedAddress(notAuthorized, { - from: owner, - }); - authorized = notAuthorized; - notAuthorized = null; - const isAuthorized = await tokenTransferProxy.authorized.call(authorized); - expect(isAuthorized).to.be.true(); - }); + it('should allow owner to add an authorized address', async () => { + await tokenTransferProxy.addAuthorizedAddress(notAuthorized, { + from: owner, + }); + authorized = notAuthorized; + notAuthorized = null; + const isAuthorized = await tokenTransferProxy.authorized.call(authorized); + expect(isAuthorized).to.be.true(); + }); - it('should throw if owner attempts to authorize a duplicate address', async () => { - return expect(tokenTransferProxy.addAuthorizedAddress(authorized, { from: owner })).to.be.rejectedWith( - constants.REVERT, - ); - }); - }); + it('should throw if owner attempts to authorize a duplicate address', async () => { + return expect(tokenTransferProxy.addAuthorizedAddress(authorized, { from: owner })).to.be.rejectedWith( + constants.REVERT, + ); + }); + }); - describe('removeAuthorizedAddress', () => { - it('should throw if not called by owner', async () => { - return expect( - tokenTransferProxy.removeAuthorizedAddress(authorized, { - from: notOwner, - }), - ).to.be.rejectedWith(constants.REVERT); - }); + describe('removeAuthorizedAddress', () => { + it('should throw if not called by owner', async () => { + return expect( + tokenTransferProxy.removeAuthorizedAddress(authorized, { + from: notOwner, + }), + ).to.be.rejectedWith(constants.REVERT); + }); - it('should allow owner to remove an authorized address', async () => { - await tokenTransferProxy.removeAuthorizedAddress(authorized, { - from: owner, - }); - notAuthorized = authorized; - authorized = null; + it('should allow owner to remove an authorized address', async () => { + await tokenTransferProxy.removeAuthorizedAddress(authorized, { + from: owner, + }); + notAuthorized = authorized; + authorized = null; - const isAuthorized = await tokenTransferProxy.authorized.call(notAuthorized); - expect(isAuthorized).to.be.false(); - }); + const isAuthorized = await tokenTransferProxy.authorized.call(notAuthorized); + expect(isAuthorized).to.be.false(); + }); - it('should throw if owner attempts to remove an address that is not authorized', async () => { - return expect( - tokenTransferProxy.removeAuthorizedAddress(notAuthorized, { - from: owner, - }), - ).to.be.rejectedWith(constants.REVERT); - }); - }); + it('should throw if owner attempts to remove an address that is not authorized', async () => { + return expect( + tokenTransferProxy.removeAuthorizedAddress(notAuthorized, { + from: owner, + }), + ).to.be.rejectedWith(constants.REVERT); + }); + }); - describe('getAuthorizedAddresses', () => { - it('should return all authorized addresses', async () => { - const initial = await tokenTransferProxy.getAuthorizedAddresses(); - expect(initial).to.have.length(1); - await tokenTransferProxy.addAuthorizedAddress(notAuthorized, { - from: owner, - }); + describe('getAuthorizedAddresses', () => { + it('should return all authorized addresses', async () => { + const initial = await tokenTransferProxy.getAuthorizedAddresses(); + expect(initial).to.have.length(1); + await tokenTransferProxy.addAuthorizedAddress(notAuthorized, { + from: owner, + }); - authorized = notAuthorized; - notAuthorized = null; - const afterAdd = await tokenTransferProxy.getAuthorizedAddresses(); - expect(afterAdd).to.have.length(2); - expect(afterAdd).to.include(authorized); + authorized = notAuthorized; + notAuthorized = null; + const afterAdd = await tokenTransferProxy.getAuthorizedAddresses(); + expect(afterAdd).to.have.length(2); + expect(afterAdd).to.include(authorized); - await tokenTransferProxy.removeAuthorizedAddress(authorized, { - from: owner, - }); - notAuthorized = authorized; - authorized = null; - const afterRemove = await tokenTransferProxy.getAuthorizedAddresses(); - expect(afterRemove).to.have.length(1); - }); - }); + await tokenTransferProxy.removeAuthorizedAddress(authorized, { + from: owner, + }); + notAuthorized = authorized; + authorized = null; + const afterRemove = await tokenTransferProxy.getAuthorizedAddresses(); + expect(afterRemove).to.have.length(1); + }); + }); }); diff --git a/packages/contracts/test/ts/token_transfer_proxy/transfer_from.ts b/packages/contracts/test/ts/token_transfer_proxy/transfer_from.ts index 847256598..e1aff6dae 100644 --- a/packages/contracts/test/ts/token_transfer_proxy/transfer_from.ts +++ b/packages/contracts/test/ts/token_transfer_proxy/transfer_from.ts @@ -11,63 +11,63 @@ const expect = chai.expect; const { TokenTransferProxy, DummyToken, TokenRegistry } = new Artifacts(artifacts); contract('TokenTransferProxy', (accounts: string[]) => { - const INIT_BAL = 100000000; - const INIT_ALLOW = 100000000; + const INIT_BAL = 100000000; + const INIT_ALLOW = 100000000; - const owner = accounts[0]; - const notAuthorized = owner; + const owner = accounts[0]; + const notAuthorized = owner; - let tokenTransferProxy: ContractInstance; - let tokenRegistry: ContractInstance; - let rep: ContractInstance; - let dmyBalances: Balances; + let tokenTransferProxy: ContractInstance; + let tokenRegistry: ContractInstance; + let rep: ContractInstance; + let dmyBalances: Balances; - before(async () => { - [tokenTransferProxy, tokenRegistry] = await Promise.all([ - TokenTransferProxy.deployed(), - TokenRegistry.deployed(), - ]); - const repAddress = await tokenRegistry.getTokenAddressBySymbol('REP'); - rep = DummyToken.at(repAddress); + before(async () => { + [tokenTransferProxy, tokenRegistry] = await Promise.all([ + TokenTransferProxy.deployed(), + TokenRegistry.deployed(), + ]); + const repAddress = await tokenRegistry.getTokenAddressBySymbol('REP'); + rep = DummyToken.at(repAddress); - dmyBalances = new Balances([rep], [accounts[0], accounts[1]]); - await Promise.all([ - rep.approve(TokenTransferProxy.address, INIT_ALLOW, { - from: accounts[0], - }), - rep.setBalance(accounts[0], INIT_BAL, { from: owner }), - rep.approve(TokenTransferProxy.address, INIT_ALLOW, { - from: accounts[1], - }), - rep.setBalance(accounts[1], INIT_BAL, { from: owner }), - ]); - }); + dmyBalances = new Balances([rep], [accounts[0], accounts[1]]); + await Promise.all([ + rep.approve(TokenTransferProxy.address, INIT_ALLOW, { + from: accounts[0], + }), + rep.setBalance(accounts[0], INIT_BAL, { from: owner }), + rep.approve(TokenTransferProxy.address, INIT_ALLOW, { + from: accounts[1], + }), + rep.setBalance(accounts[1], INIT_BAL, { from: owner }), + ]); + }); - describe('transferFrom', () => { - it('should throw when called by an unauthorized address', async () => { - expect( - tokenTransferProxy.transferFrom(rep.address, accounts[0], accounts[1], 1000, { from: notAuthorized }), - ).to.be.rejectedWith(constants.REVERT); - }); + describe('transferFrom', () => { + it('should throw when called by an unauthorized address', async () => { + expect( + tokenTransferProxy.transferFrom(rep.address, accounts[0], accounts[1], 1000, { from: notAuthorized }), + ).to.be.rejectedWith(constants.REVERT); + }); - it('should allow an authorized address to transfer', async () => { - const balances = await dmyBalances.getAsync(); + it('should allow an authorized address to transfer', async () => { + const balances = await dmyBalances.getAsync(); - await tokenTransferProxy.addAuthorizedAddress(notAuthorized, { - from: owner, - }); - const transferAmt = 10000; - await tokenTransferProxy.transferFrom(rep.address, accounts[0], accounts[1], transferAmt, { - from: notAuthorized, - }); + await tokenTransferProxy.addAuthorizedAddress(notAuthorized, { + from: owner, + }); + const transferAmt = 10000; + await tokenTransferProxy.transferFrom(rep.address, accounts[0], accounts[1], transferAmt, { + from: notAuthorized, + }); - const newBalances = await dmyBalances.getAsync(); - expect(newBalances[accounts[0]][rep.address]).to.be.bignumber.equal( - balances[accounts[0]][rep.address].minus(transferAmt), - ); - expect(newBalances[accounts[1]][rep.address]).to.be.bignumber.equal( - balances[accounts[1]][rep.address].add(transferAmt), - ); - }); - }); + const newBalances = await dmyBalances.getAsync(); + expect(newBalances[accounts[0]][rep.address]).to.be.bignumber.equal( + balances[accounts[0]][rep.address].minus(transferAmt), + ); + expect(newBalances[accounts[1]][rep.address]).to.be.bignumber.equal( + balances[accounts[1]][rep.address].add(transferAmt), + ); + }); + }); }); diff --git a/packages/contracts/test/ts/unlimited_allowance_token.ts b/packages/contracts/test/ts/unlimited_allowance_token.ts index f2e070fbb..c90a52095 100644 --- a/packages/contracts/test/ts/unlimited_allowance_token.ts +++ b/packages/contracts/test/ts/unlimited_allowance_token.ts @@ -15,113 +15,113 @@ chaiSetup.configure(); const expect = chai.expect; contract('UnlimitedAllowanceToken', (accounts: string[]) => { - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3.currentProvider, config); - const owner = accounts[0]; - const spender = accounts[1]; - - const MAX_MINT_VALUE = new BigNumber(100000000000000000000); - let tokenAddress: string; - let token: ContractInstance; - - beforeEach(async () => { - token = await DummyToken.new({ from: owner }); - await token.mint(MAX_MINT_VALUE, { from: owner }); - tokenAddress = token.address; - }); - - describe('transfer', () => { - it('should transfer balance from sender to receiver', async () => { - const receiver = spender; - const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = new BigNumber(1); - await zeroEx.token.transferAsync(tokenAddress, owner, receiver, amountToTransfer); - const finalOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const finalReceiverBalance = await zeroEx.token.getBalanceAsync(tokenAddress, receiver); - - const expectedFinalOwnerBalance = initOwnerBalance.minus(amountToTransfer); - const expectedFinalReceiverBalance = amountToTransfer; - expect(finalOwnerBalance).to.be.bignumber.equal(expectedFinalOwnerBalance); - expect(finalReceiverBalance).to.be.bignumber.equal(expectedFinalReceiverBalance); - }); - - it('should return true on a 0 value transfer', async () => { - const didReturnTrue = await token.transfer.call(spender, 0, { - from: owner, - }); - expect(didReturnTrue).to.be.true(); - }); - }); - - describe('transferFrom', () => { - it('should return false if owner has insufficient balance', async () => { - const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = ownerBalance.plus(1); - await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, amountToTransfer); - const didReturnTrue = await token.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); - expect(didReturnTrue).to.be.false(); - }); - - it('should return false if spender has insufficient allowance', async () => { - const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = ownerBalance; - - const spenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); - const spenderAllowanceIsInsufficient = spenderAllowance.cmp(amountToTransfer) < 0; - expect(spenderAllowanceIsInsufficient).to.be.true(); - - const didReturnTrue = await token.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); - expect(didReturnTrue).to.be.false(); - }); - - it('should return true on a 0 value transfer', async () => { - const amountToTransfer = 0; - const didReturnTrue = await token.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); - expect(didReturnTrue).to.be.true(); - }); - - it('should not modify spender allowance if spender allowance is 2^256 - 1', async () => { - const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = initOwnerBalance; - const initSpenderAllowance = zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; - await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); - await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { - gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); - - const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); - expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance); - }); - - it('should transfer the correct balances if spender has sufficient allowance', async () => { - const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = initOwnerBalance; - const initSpenderAllowance = initOwnerBalance; - await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); - await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { - gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); - - const newOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const newSpenderBalance = await zeroEx.token.getBalanceAsync(tokenAddress, spender); - - expect(newOwnerBalance).to.be.bignumber.equal(0); - expect(newSpenderBalance).to.be.bignumber.equal(initOwnerBalance); - }); - - it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => { - const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = initOwnerBalance; - const initSpenderAllowance = initOwnerBalance; - await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); - await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { - gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); - - const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); - expect(newSpenderAllowance).to.be.bignumber.equal(0); - }); - }); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3.currentProvider, config); + const owner = accounts[0]; + const spender = accounts[1]; + + const MAX_MINT_VALUE = new BigNumber(100000000000000000000); + let tokenAddress: string; + let token: ContractInstance; + + beforeEach(async () => { + token = await DummyToken.new({ from: owner }); + await token.mint(MAX_MINT_VALUE, { from: owner }); + tokenAddress = token.address; + }); + + describe('transfer', () => { + it('should transfer balance from sender to receiver', async () => { + const receiver = spender; + const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = new BigNumber(1); + await zeroEx.token.transferAsync(tokenAddress, owner, receiver, amountToTransfer); + const finalOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const finalReceiverBalance = await zeroEx.token.getBalanceAsync(tokenAddress, receiver); + + const expectedFinalOwnerBalance = initOwnerBalance.minus(amountToTransfer); + const expectedFinalReceiverBalance = amountToTransfer; + expect(finalOwnerBalance).to.be.bignumber.equal(expectedFinalOwnerBalance); + expect(finalReceiverBalance).to.be.bignumber.equal(expectedFinalReceiverBalance); + }); + + it('should return true on a 0 value transfer', async () => { + const didReturnTrue = await token.transfer.call(spender, 0, { + from: owner, + }); + expect(didReturnTrue).to.be.true(); + }); + }); + + describe('transferFrom', () => { + it('should return false if owner has insufficient balance', async () => { + const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = ownerBalance.plus(1); + await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, amountToTransfer); + const didReturnTrue = await token.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); + expect(didReturnTrue).to.be.false(); + }); + + it('should return false if spender has insufficient allowance', async () => { + const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = ownerBalance; + + const spenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); + const spenderAllowanceIsInsufficient = spenderAllowance.cmp(amountToTransfer) < 0; + expect(spenderAllowanceIsInsufficient).to.be.true(); + + const didReturnTrue = await token.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); + expect(didReturnTrue).to.be.false(); + }); + + it('should return true on a 0 value transfer', async () => { + const amountToTransfer = 0; + const didReturnTrue = await token.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); + expect(didReturnTrue).to.be.true(); + }); + + it('should not modify spender allowance if spender allowance is 2^256 - 1', async () => { + const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = initOwnerBalance; + const initSpenderAllowance = zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; + await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); + await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { + gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }); + + const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); + expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance); + }); + + it('should transfer the correct balances if spender has sufficient allowance', async () => { + const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = initOwnerBalance; + const initSpenderAllowance = initOwnerBalance; + await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); + await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { + gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }); + + const newOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const newSpenderBalance = await zeroEx.token.getBalanceAsync(tokenAddress, spender); + + expect(newOwnerBalance).to.be.bignumber.equal(0); + expect(newSpenderBalance).to.be.bignumber.equal(initOwnerBalance); + }); + + it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => { + const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = initOwnerBalance; + const initSpenderAllowance = initOwnerBalance; + await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); + await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { + gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }); + + const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); + expect(newSpenderAllowance).to.be.bignumber.equal(0); + }); + }); }); diff --git a/packages/contracts/test/ts/unlimited_allowance_token_v2.ts b/packages/contracts/test/ts/unlimited_allowance_token_v2.ts index 4f2ab2e1e..1b29a02ba 100644 --- a/packages/contracts/test/ts/unlimited_allowance_token_v2.ts +++ b/packages/contracts/test/ts/unlimited_allowance_token_v2.ts @@ -15,127 +15,127 @@ chaiSetup.configure(); const expect = chai.expect; contract('UnlimitedAllowanceTokenV2', (accounts: string[]) => { - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3.currentProvider, config); - const owner = accounts[0]; - const spender = accounts[1]; - - const MAX_MINT_VALUE = new BigNumber(100000000000000000000); - let tokenAddress: string; - let token: ContractInstance; - - beforeEach(async () => { - token = await DummyTokenV2.new({ from: owner }); - await token.mint(MAX_MINT_VALUE, { from: owner }); - tokenAddress = token.address; - }); - - describe('transfer', () => { - it('should throw if owner has insufficient balance', async () => { - const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = ownerBalance.plus(1); - return expect(token.transfer.call(spender, amountToTransfer, { from: owner })).to.be.rejectedWith( - constants.REVERT, - ); - }); - - it('should transfer balance from sender to receiver', async () => { - const receiver = spender; - const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = new BigNumber(1); - await zeroEx.token.transferAsync(tokenAddress, owner, receiver, amountToTransfer); - const finalOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const finalReceiverBalance = await zeroEx.token.getBalanceAsync(tokenAddress, receiver); - - const expectedFinalOwnerBalance = initOwnerBalance.minus(amountToTransfer); - const expectedFinalReceiverBalance = amountToTransfer; - expect(finalOwnerBalance).to.be.bignumber.equal(expectedFinalOwnerBalance); - expect(finalReceiverBalance).to.be.bignumber.equal(expectedFinalReceiverBalance); - }); - - it('should return true on a 0 value transfer', async () => { - const didReturnTrue = await token.transfer.call(spender, 0, { - from: owner, - }); - expect(didReturnTrue).to.be.true(); - }); - }); - - describe('transferFrom', () => { - it('should throw if owner has insufficient balance', async () => { - const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = ownerBalance.plus(1); - await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, amountToTransfer); - return expect( - token.transferFrom.call(owner, spender, amountToTransfer, { - from: spender, - }), - ).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if spender has insufficient allowance', async () => { - const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = ownerBalance; - - const spenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); - const spenderAllowanceIsInsufficient = spenderAllowance.cmp(amountToTransfer) < 0; - expect(spenderAllowanceIsInsufficient).to.be.true(); - - return expect( - token.transferFrom.call(owner, spender, amountToTransfer, { - from: spender, - }), - ).to.be.rejectedWith(constants.REVERT); - }); - - it('should return true on a 0 value transfer', async () => { - const amountToTransfer = 0; - const didReturnTrue = await token.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); - expect(didReturnTrue).to.be.true(); - }); - - it('should not modify spender allowance if spender allowance is 2^256 - 1', async () => { - const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = initOwnerBalance; - const initSpenderAllowance = zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; - await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); - await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { - gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); - - const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); - expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance); - }); - - it('should transfer the correct balances if spender has sufficient allowance', async () => { - const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = initOwnerBalance; - const initSpenderAllowance = initOwnerBalance; - await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); - await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { - gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); - - const newOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const newSpenderBalance = await zeroEx.token.getBalanceAsync(tokenAddress, spender); - - expect(newOwnerBalance).to.be.bignumber.equal(0); - expect(newSpenderBalance).to.be.bignumber.equal(initOwnerBalance); - }); - - it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => { - const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); - const amountToTransfer = initOwnerBalance; - const initSpenderAllowance = initOwnerBalance; - await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); - await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { - gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); - - const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); - expect(newSpenderAllowance).to.be.bignumber.equal(0); - }); - }); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3.currentProvider, config); + const owner = accounts[0]; + const spender = accounts[1]; + + const MAX_MINT_VALUE = new BigNumber(100000000000000000000); + let tokenAddress: string; + let token: ContractInstance; + + beforeEach(async () => { + token = await DummyTokenV2.new({ from: owner }); + await token.mint(MAX_MINT_VALUE, { from: owner }); + tokenAddress = token.address; + }); + + describe('transfer', () => { + it('should throw if owner has insufficient balance', async () => { + const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = ownerBalance.plus(1); + return expect(token.transfer.call(spender, amountToTransfer, { from: owner })).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should transfer balance from sender to receiver', async () => { + const receiver = spender; + const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = new BigNumber(1); + await zeroEx.token.transferAsync(tokenAddress, owner, receiver, amountToTransfer); + const finalOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const finalReceiverBalance = await zeroEx.token.getBalanceAsync(tokenAddress, receiver); + + const expectedFinalOwnerBalance = initOwnerBalance.minus(amountToTransfer); + const expectedFinalReceiverBalance = amountToTransfer; + expect(finalOwnerBalance).to.be.bignumber.equal(expectedFinalOwnerBalance); + expect(finalReceiverBalance).to.be.bignumber.equal(expectedFinalReceiverBalance); + }); + + it('should return true on a 0 value transfer', async () => { + const didReturnTrue = await token.transfer.call(spender, 0, { + from: owner, + }); + expect(didReturnTrue).to.be.true(); + }); + }); + + describe('transferFrom', () => { + it('should throw if owner has insufficient balance', async () => { + const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = ownerBalance.plus(1); + await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, amountToTransfer); + return expect( + token.transferFrom.call(owner, spender, amountToTransfer, { + from: spender, + }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should throw if spender has insufficient allowance', async () => { + const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = ownerBalance; + + const spenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); + const spenderAllowanceIsInsufficient = spenderAllowance.cmp(amountToTransfer) < 0; + expect(spenderAllowanceIsInsufficient).to.be.true(); + + return expect( + token.transferFrom.call(owner, spender, amountToTransfer, { + from: spender, + }), + ).to.be.rejectedWith(constants.REVERT); + }); + + it('should return true on a 0 value transfer', async () => { + const amountToTransfer = 0; + const didReturnTrue = await token.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); + expect(didReturnTrue).to.be.true(); + }); + + it('should not modify spender allowance if spender allowance is 2^256 - 1', async () => { + const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = initOwnerBalance; + const initSpenderAllowance = zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; + await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); + await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { + gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }); + + const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); + expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance); + }); + + it('should transfer the correct balances if spender has sufficient allowance', async () => { + const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = initOwnerBalance; + const initSpenderAllowance = initOwnerBalance; + await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); + await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { + gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }); + + const newOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const newSpenderBalance = await zeroEx.token.getBalanceAsync(tokenAddress, spender); + + expect(newOwnerBalance).to.be.bignumber.equal(0); + expect(newSpenderBalance).to.be.bignumber.equal(initOwnerBalance); + }); + + it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => { + const initOwnerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); + const amountToTransfer = initOwnerBalance; + const initSpenderAllowance = initOwnerBalance; + await zeroEx.token.setAllowanceAsync(tokenAddress, owner, spender, initSpenderAllowance); + await zeroEx.token.transferFromAsync(tokenAddress, owner, spender, spender, amountToTransfer, { + gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }); + + const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); + expect(newSpenderAllowance).to.be.bignumber.equal(0); + }); + }); }); diff --git a/packages/contracts/test/ts/utils/chai_setup.ts b/packages/contracts/test/ts/utils/chai_setup.ts index e156b5f7c..078edd309 100644 --- a/packages/contracts/test/ts/utils/chai_setup.ts +++ b/packages/contracts/test/ts/utils/chai_setup.ts @@ -4,10 +4,10 @@ import ChaiBigNumber = require('chai-bignumber'); import * as dirtyChai from 'dirty-chai'; export const chaiSetup = { - configure() { - chai.config.includeStack = true; - chai.use(ChaiBigNumber()); - chai.use(dirtyChai); - chai.use(chaiAsPromised); - }, + configure() { + chai.config.includeStack = true; + chai.use(ChaiBigNumber()); + chai.use(dirtyChai); + chai.use(chaiAsPromised); + }, }; diff --git a/packages/contracts/test/ts/zrx_token.ts b/packages/contracts/test/ts/zrx_token.ts index a0b11cdb6..766c94c2a 100644 --- a/packages/contracts/test/ts/zrx_token.ts +++ b/packages/contracts/test/ts/zrx_token.ts @@ -15,161 +15,161 @@ const { Exchange, ZRXToken } = new Artifacts(artifacts); const web3: Web3 = (global as any).web3; contract('ZRXToken', (accounts: string[]) => { - const owner = accounts[0]; - const spender = accounts[1]; - let zeroEx: ZeroEx; - - let MAX_UINT: BigNumber; - - let zrx: ContractInstance; - let zrxAddress: string; - - beforeEach(async () => { - zeroEx = new ZeroEx(web3.currentProvider, { - exchangeContractAddress: Exchange.address, - networkId: constants.TESTRPC_NETWORK_ID, - }); - zrx = await ZRXToken.new(); - zrxAddress = zrx.address; - MAX_UINT = zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; - }); - - describe('constants', () => { - it('should have 18 decimals', async () => { - const decimals = new BigNumber(await zrx.decimals.call()); - const expectedDecimals = 18; - expect(decimals).to.be.bignumber.equal(expectedDecimals); - }); - - it('should have a total supply of 1 billion tokens', async () => { - const totalSupply = new BigNumber(await zrx.totalSupply.call()); - const expectedTotalSupply = 1000000000; - expect(ZeroEx.toUnitAmount(totalSupply, 18)).to.be.bignumber.equal(expectedTotalSupply); - }); - - it('should be named 0x Protocol Token', async () => { - const name = await zrx.name.call(); - const expectedName = '0x Protocol Token'; - expect(name).to.be.equal(expectedName); - }); - - it('should have the symbol ZRX', async () => { - const symbol = await zrx.symbol.call(); - const expectedSymbol = 'ZRX'; - expect(symbol).to.be.equal(expectedSymbol); - }); - }); - - describe('constructor', () => { - it('should initialize owner balance to totalSupply', async () => { - const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); - const totalSupply = new BigNumber(await zrx.totalSupply.call()); - expect(totalSupply).to.be.bignumber.equal(ownerBalance); - }); - }); - - describe('transfer', () => { - it('should transfer balance from sender to receiver', async () => { - const receiver = spender; - const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); - const amountToTransfer = new BigNumber(1); - const txHash = await zeroEx.token.transferAsync(zrxAddress, owner, receiver, amountToTransfer); - await zeroEx.awaitTransactionMinedAsync(txHash); - const finalOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); - const finalReceiverBalance = await zeroEx.token.getBalanceAsync(zrxAddress, receiver); - - const expectedFinalOwnerBalance = initOwnerBalance.minus(amountToTransfer); - const expectedFinalReceiverBalance = amountToTransfer; - expect(finalOwnerBalance).to.be.bignumber.equal(expectedFinalOwnerBalance); - expect(finalReceiverBalance).to.be.bignumber.equal(expectedFinalReceiverBalance); - }); - - it('should return true on a 0 value transfer', async () => { - const didReturnTrue = await zrx.transfer.call(spender, 0, { - from: owner, - }); - expect(didReturnTrue).to.be.true(); - }); - }); - - describe('transferFrom', () => { - it('should return false if owner has insufficient balance', async () => { - const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); - const amountToTransfer = ownerBalance.plus(1); - const txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, amountToTransfer, { - gasLimit: constants.MAX_TOKEN_APPROVE_GAS, - }); - await zeroEx.awaitTransactionMinedAsync(txHash); - const didReturnTrue = await zrx.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); - expect(didReturnTrue).to.be.false(); - }); - - it('should return false if spender has insufficient allowance', async () => { - const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); - const amountToTransfer = ownerBalance; - - const spenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender); - const spenderAllowanceIsInsufficient = spenderAllowance.cmp(amountToTransfer) < 0; - expect(spenderAllowanceIsInsufficient).to.be.true(); - - const didReturnTrue = await zrx.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); - expect(didReturnTrue).to.be.false(); - }); - - it('should return true on a 0 value transfer', async () => { - const amountToTransfer = 0; - const didReturnTrue = await zrx.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); - expect(didReturnTrue).to.be.true(); - }); - - it('should not modify spender allowance if spender allowance is 2^256 - 1', async () => { - const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); - const amountToTransfer = initOwnerBalance; - const initSpenderAllowance = MAX_UINT; - let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, initSpenderAllowance, { - gasLimit: constants.MAX_TOKEN_APPROVE_GAS, - }); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { - gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); - await zeroEx.awaitTransactionMinedAsync(txHash); - - const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender); - expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance); - }); - - it('should transfer the correct balances if spender has sufficient allowance', async () => { - const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); - const initSpenderBalance = await zeroEx.token.getBalanceAsync(zrxAddress, spender); - const amountToTransfer = initOwnerBalance; - const initSpenderAllowance = initOwnerBalance; - let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, initSpenderAllowance); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { - gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); - await zeroEx.awaitTransactionMinedAsync(txHash); - - const newOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); - const newSpenderBalance = await zeroEx.token.getBalanceAsync(zrxAddress, spender); - - expect(newOwnerBalance).to.be.bignumber.equal(0); - expect(newSpenderBalance).to.be.bignumber.equal(initSpenderBalance.plus(initOwnerBalance)); - }); - - it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => { - const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); - const amountToTransfer = initOwnerBalance; - let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, amountToTransfer); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { - gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); - await zeroEx.awaitTransactionMinedAsync(txHash); - - const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender); - expect(newSpenderAllowance).to.be.bignumber.equal(0); - }); - }); + const owner = accounts[0]; + const spender = accounts[1]; + let zeroEx: ZeroEx; + + let MAX_UINT: BigNumber; + + let zrx: ContractInstance; + let zrxAddress: string; + + beforeEach(async () => { + zeroEx = new ZeroEx(web3.currentProvider, { + exchangeContractAddress: Exchange.address, + networkId: constants.TESTRPC_NETWORK_ID, + }); + zrx = await ZRXToken.new(); + zrxAddress = zrx.address; + MAX_UINT = zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; + }); + + describe('constants', () => { + it('should have 18 decimals', async () => { + const decimals = new BigNumber(await zrx.decimals.call()); + const expectedDecimals = 18; + expect(decimals).to.be.bignumber.equal(expectedDecimals); + }); + + it('should have a total supply of 1 billion tokens', async () => { + const totalSupply = new BigNumber(await zrx.totalSupply.call()); + const expectedTotalSupply = 1000000000; + expect(ZeroEx.toUnitAmount(totalSupply, 18)).to.be.bignumber.equal(expectedTotalSupply); + }); + + it('should be named 0x Protocol Token', async () => { + const name = await zrx.name.call(); + const expectedName = '0x Protocol Token'; + expect(name).to.be.equal(expectedName); + }); + + it('should have the symbol ZRX', async () => { + const symbol = await zrx.symbol.call(); + const expectedSymbol = 'ZRX'; + expect(symbol).to.be.equal(expectedSymbol); + }); + }); + + describe('constructor', () => { + it('should initialize owner balance to totalSupply', async () => { + const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); + const totalSupply = new BigNumber(await zrx.totalSupply.call()); + expect(totalSupply).to.be.bignumber.equal(ownerBalance); + }); + }); + + describe('transfer', () => { + it('should transfer balance from sender to receiver', async () => { + const receiver = spender; + const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); + const amountToTransfer = new BigNumber(1); + const txHash = await zeroEx.token.transferAsync(zrxAddress, owner, receiver, amountToTransfer); + await zeroEx.awaitTransactionMinedAsync(txHash); + const finalOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); + const finalReceiverBalance = await zeroEx.token.getBalanceAsync(zrxAddress, receiver); + + const expectedFinalOwnerBalance = initOwnerBalance.minus(amountToTransfer); + const expectedFinalReceiverBalance = amountToTransfer; + expect(finalOwnerBalance).to.be.bignumber.equal(expectedFinalOwnerBalance); + expect(finalReceiverBalance).to.be.bignumber.equal(expectedFinalReceiverBalance); + }); + + it('should return true on a 0 value transfer', async () => { + const didReturnTrue = await zrx.transfer.call(spender, 0, { + from: owner, + }); + expect(didReturnTrue).to.be.true(); + }); + }); + + describe('transferFrom', () => { + it('should return false if owner has insufficient balance', async () => { + const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); + const amountToTransfer = ownerBalance.plus(1); + const txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, amountToTransfer, { + gasLimit: constants.MAX_TOKEN_APPROVE_GAS, + }); + await zeroEx.awaitTransactionMinedAsync(txHash); + const didReturnTrue = await zrx.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); + expect(didReturnTrue).to.be.false(); + }); + + it('should return false if spender has insufficient allowance', async () => { + const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); + const amountToTransfer = ownerBalance; + + const spenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender); + const spenderAllowanceIsInsufficient = spenderAllowance.cmp(amountToTransfer) < 0; + expect(spenderAllowanceIsInsufficient).to.be.true(); + + const didReturnTrue = await zrx.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); + expect(didReturnTrue).to.be.false(); + }); + + it('should return true on a 0 value transfer', async () => { + const amountToTransfer = 0; + const didReturnTrue = await zrx.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); + expect(didReturnTrue).to.be.true(); + }); + + it('should not modify spender allowance if spender allowance is 2^256 - 1', async () => { + const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); + const amountToTransfer = initOwnerBalance; + const initSpenderAllowance = MAX_UINT; + let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, initSpenderAllowance, { + gasLimit: constants.MAX_TOKEN_APPROVE_GAS, + }); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { + gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }); + await zeroEx.awaitTransactionMinedAsync(txHash); + + const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender); + expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance); + }); + + it('should transfer the correct balances if spender has sufficient allowance', async () => { + const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); + const initSpenderBalance = await zeroEx.token.getBalanceAsync(zrxAddress, spender); + const amountToTransfer = initOwnerBalance; + const initSpenderAllowance = initOwnerBalance; + let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, initSpenderAllowance); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { + gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }); + await zeroEx.awaitTransactionMinedAsync(txHash); + + const newOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); + const newSpenderBalance = await zeroEx.token.getBalanceAsync(zrxAddress, spender); + + expect(newOwnerBalance).to.be.bignumber.equal(0); + expect(newSpenderBalance).to.be.bignumber.equal(initSpenderBalance.plus(initOwnerBalance)); + }); + + it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => { + const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); + const amountToTransfer = initOwnerBalance; + let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, amountToTransfer); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { + gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }); + await zeroEx.awaitTransactionMinedAsync(txHash); + + const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender); + expect(newSpenderAllowance).to.be.bignumber.equal(0); + }); + }); }); diff --git a/packages/contracts/tsconfig.json b/packages/contracts/tsconfig.json index e88b3c3f0..38008a542 100644 --- a/packages/contracts/tsconfig.json +++ b/packages/contracts/tsconfig.json @@ -1,25 +1,25 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib", - "baseUrl": ".", - "declaration": false, - "strictNullChecks": false, - "strictFunctionTypes": false, - "allowJs": true - }, - "include": [ - "../../node_modules/types-ethereumjs-util/index.d.ts", - "../../node_modules/chai-typescript-typings/index.d.ts", - "../../node_modules/web3-typescript-typings/index.d.ts", - "../../node_modules/chai-as-promised-typescript-typings/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts", - "../../node_modules/types-bn/index.d.ts", - "./globals.d.ts", - "./util/**/*", - "./test/**/*", - "./migrations/**/*", - "./deploy/**/*" - ], - "exclude": ["./deploy/solc/solc_bin"] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "baseUrl": ".", + "declaration": false, + "strictNullChecks": false, + "strictFunctionTypes": false, + "allowJs": true + }, + "include": [ + "../../node_modules/types-ethereumjs-util/index.d.ts", + "../../node_modules/chai-typescript-typings/index.d.ts", + "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/chai-as-promised-typescript-typings/index.d.ts", + "../../node_modules/types-ethereumjs-util/index.d.ts", + "../../node_modules/types-bn/index.d.ts", + "./globals.d.ts", + "./util/**/*", + "./test/**/*", + "./migrations/**/*", + "./deploy/**/*" + ], + "exclude": ["./deploy/solc/solc_bin"] } diff --git a/packages/contracts/tslint.json b/packages/contracts/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/contracts/tslint.json +++ b/packages/contracts/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/contracts/util/artifacts.ts b/packages/contracts/util/artifacts.ts index ab9518d41..ecb18cbce 100644 --- a/packages/contracts/util/artifacts.ts +++ b/packages/contracts/util/artifacts.ts @@ -1,28 +1,28 @@ export class Artifacts { - public Migrations: any; - public TokenTransferProxy: any; - public TokenRegistry: any; - public MultiSigWalletWithTimeLock: any; - public Exchange: any; - public ZRXToken: any; - public DummyToken: any; - public DummyTokenV2: any; - public EtherToken: any; - public MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress: any; - public MaliciousToken: any; - constructor(artifacts: any) { - this.Migrations = artifacts.require('Migrations'); - this.TokenTransferProxy = artifacts.require('TokenTransferProxy'); - this.TokenRegistry = artifacts.require('TokenRegistry'); - this.MultiSigWalletWithTimeLock = artifacts.require('MultiSigWalletWithTimeLock'); - this.Exchange = artifacts.require('Exchange'); - this.ZRXToken = artifacts.require('ZRXToken'); - this.DummyToken = artifacts.require('DummyToken'); - this.DummyTokenV2 = artifacts.require('DummyToken_v2'); - this.EtherToken = artifacts.require('WETH9'); - this.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress = artifacts.require( - 'MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', - ); - this.MaliciousToken = artifacts.require('MaliciousToken'); - } + public Migrations: any; + public TokenTransferProxy: any; + public TokenRegistry: any; + public MultiSigWalletWithTimeLock: any; + public Exchange: any; + public ZRXToken: any; + public DummyToken: any; + public DummyTokenV2: any; + public EtherToken: any; + public MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress: any; + public MaliciousToken: any; + constructor(artifacts: any) { + this.Migrations = artifacts.require('Migrations'); + this.TokenTransferProxy = artifacts.require('TokenTransferProxy'); + this.TokenRegistry = artifacts.require('TokenRegistry'); + this.MultiSigWalletWithTimeLock = artifacts.require('MultiSigWalletWithTimeLock'); + this.Exchange = artifacts.require('Exchange'); + this.ZRXToken = artifacts.require('ZRXToken'); + this.DummyToken = artifacts.require('DummyToken'); + this.DummyTokenV2 = artifacts.require('DummyToken_v2'); + this.EtherToken = artifacts.require('WETH9'); + this.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress = artifacts.require( + 'MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', + ); + this.MaliciousToken = artifacts.require('MaliciousToken'); + } } diff --git a/packages/contracts/util/balances.ts b/packages/contracts/util/balances.ts index f8c103670..6a1659ab1 100644 --- a/packages/contracts/util/balances.ts +++ b/packages/contracts/util/balances.ts @@ -4,24 +4,24 @@ import * as _ from 'lodash'; import { BalancesByOwner, ContractInstance } from './types'; export class Balances { - private _tokenContractInstances: ContractInstance[]; - private _ownerAddresses: string[]; - constructor(tokenContractInstances: ContractInstance[], ownerAddresses: string[]) { - this._tokenContractInstances = tokenContractInstances; - this._ownerAddresses = ownerAddresses; - } - public async getAsync(): Promise<BalancesByOwner> { - const balancesByOwner: BalancesByOwner = {}; - for (const tokenContractInstance of this._tokenContractInstances) { - for (const ownerAddress of this._ownerAddresses) { - let balance = await tokenContractInstance.balanceOf(ownerAddress); - balance = new BigNumber(balance); - if (_.isUndefined(balancesByOwner[ownerAddress])) { - balancesByOwner[ownerAddress] = {}; - } - balancesByOwner[ownerAddress][tokenContractInstance.address] = balance; - } - } - return balancesByOwner; - } + private _tokenContractInstances: ContractInstance[]; + private _ownerAddresses: string[]; + constructor(tokenContractInstances: ContractInstance[], ownerAddresses: string[]) { + this._tokenContractInstances = tokenContractInstances; + this._ownerAddresses = ownerAddresses; + } + public async getAsync(): Promise<BalancesByOwner> { + const balancesByOwner: BalancesByOwner = {}; + for (const tokenContractInstance of this._tokenContractInstances) { + for (const ownerAddress of this._ownerAddresses) { + let balance = await tokenContractInstance.balanceOf(ownerAddress); + balance = new BigNumber(balance); + if (_.isUndefined(balancesByOwner[ownerAddress])) { + balancesByOwner[ownerAddress] = {}; + } + balancesByOwner[ownerAddress][tokenContractInstance.address] = balance; + } + } + return balancesByOwner; + } } diff --git a/packages/contracts/util/constants.ts b/packages/contracts/util/constants.ts index 85fd31be3..e61b2f802 100644 --- a/packages/contracts/util/constants.ts +++ b/packages/contracts/util/constants.ts @@ -1,9 +1,9 @@ export const constants = { - NULL_BYTES: '0x', - INVALID_OPCODE: 'invalid opcode', - REVERT: 'revert', - TESTRPC_NETWORK_ID: 50, - MAX_ETHERTOKEN_WITHDRAW_GAS: 43000, - MAX_TOKEN_TRANSFERFROM_GAS: 80000, - MAX_TOKEN_APPROVE_GAS: 60000, + NULL_BYTES: '0x', + INVALID_OPCODE: 'invalid opcode', + REVERT: 'revert', + TESTRPC_NETWORK_ID: 50, + MAX_ETHERTOKEN_WITHDRAW_GAS: 43000, + MAX_TOKEN_TRANSFERFROM_GAS: 80000, + MAX_TOKEN_APPROVE_GAS: 60000, }; diff --git a/packages/contracts/util/crypto.ts b/packages/contracts/util/crypto.ts index 29bbf206a..9173df643 100644 --- a/packages/contracts/util/crypto.ts +++ b/packages/contracts/util/crypto.ts @@ -4,7 +4,7 @@ import ethUtil = require('ethereumjs-util'); import * as _ from 'lodash'; export const crypto = { - /* + /* * We convert types from JS to Solidity as follows: * BigNumber -> uint256 * number -> uint8 @@ -12,26 +12,26 @@ export const crypto = { * boolean -> bool * valid Ethereum address -> address */ - solSHA3(args: any[]): Buffer { - const argTypes: string[] = []; - _.each(args, (arg, i) => { - const isNumber = _.isFinite(arg); - if (isNumber) { - argTypes.push('uint8'); - } else if (arg.isBigNumber) { - argTypes.push('uint256'); - args[i] = new BN(arg.toString(10), 10); - } else if (ethUtil.isValidAddress(arg)) { - argTypes.push('address'); - } else if (_.isString(arg)) { - argTypes.push('string'); - } else if (_.isBoolean(arg)) { - argTypes.push('bool'); - } else { - throw new Error(`Unable to guess arg type: ${arg}`); - } - }); - const hash = ABI.soliditySHA3(argTypes, args); - return hash; - }, + solSHA3(args: any[]): Buffer { + const argTypes: string[] = []; + _.each(args, (arg, i) => { + const isNumber = _.isFinite(arg); + if (isNumber) { + argTypes.push('uint8'); + } else if (arg.isBigNumber) { + argTypes.push('uint256'); + args[i] = new BN(arg.toString(10), 10); + } else if (ethUtil.isValidAddress(arg)) { + argTypes.push('address'); + } else if (_.isString(arg)) { + argTypes.push('string'); + } else if (_.isBoolean(arg)) { + argTypes.push('bool'); + } else { + throw new Error(`Unable to guess arg type: ${arg}`); + } + }); + const hash = ABI.soliditySHA3(argTypes, args); + return hash; + }, }; diff --git a/packages/contracts/util/exchange_wrapper.ts b/packages/contracts/util/exchange_wrapper.ts index 3a3c44aae..ca79f92c4 100644 --- a/packages/contracts/util/exchange_wrapper.ts +++ b/packages/contracts/util/exchange_wrapper.ts @@ -6,186 +6,186 @@ import { Order } from './order'; import { ContractInstance } from './types'; export class ExchangeWrapper { - private _exchange: ContractInstance; - constructor(exchangeContractInstance: ContractInstance) { - this._exchange = exchangeContractInstance; - } - public async fillOrderAsync( - order: Order, - from: string, - opts: { - fillTakerTokenAmount?: BigNumber; - shouldThrowOnInsufficientBalanceOrAllowance?: boolean; - } = {}, - ) { - const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance; - const params = order.createFill(shouldThrowOnInsufficientBalanceOrAllowance, opts.fillTakerTokenAmount); - const tx = await this._exchange.fillOrder( - params.orderAddresses, - params.orderValues, - params.fillTakerTokenAmount, - params.shouldThrowOnInsufficientBalanceOrAllowance, - params.v, - params.r, - params.s, - { from }, - ); - _.each(tx.logs, log => wrapLogBigNumbers(log)); - return tx; - } - public async cancelOrderAsync(order: Order, from: string, opts: { cancelTakerTokenAmount?: BigNumber } = {}) { - const params = order.createCancel(opts.cancelTakerTokenAmount); - const tx = await this._exchange.cancelOrder( - params.orderAddresses, - params.orderValues, - params.cancelTakerTokenAmount, - { from }, - ); - _.each(tx.logs, log => wrapLogBigNumbers(log)); - return tx; - } - public async fillOrKillOrderAsync(order: Order, from: string, opts: { fillTakerTokenAmount?: BigNumber } = {}) { - const shouldThrowOnInsufficientBalanceOrAllowance = true; - const params = order.createFill(shouldThrowOnInsufficientBalanceOrAllowance, opts.fillTakerTokenAmount); - const tx = await this._exchange.fillOrKillOrder( - params.orderAddresses, - params.orderValues, - params.fillTakerTokenAmount, - params.v, - params.r, - params.s, - { from }, - ); - _.each(tx.logs, log => wrapLogBigNumbers(log)); - return tx; - } - public async batchFillOrdersAsync( - orders: Order[], - from: string, - opts: { - fillTakerTokenAmounts?: BigNumber[]; - shouldThrowOnInsufficientBalanceOrAllowance?: boolean; - } = {}, - ) { - const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance; - const params = formatters.createBatchFill( - orders, - shouldThrowOnInsufficientBalanceOrAllowance, - opts.fillTakerTokenAmounts, - ); - const tx = await this._exchange.batchFillOrders( - params.orderAddresses, - params.orderValues, - params.fillTakerTokenAmounts, - params.shouldThrowOnInsufficientBalanceOrAllowance, - params.v, - params.r, - params.s, - { from }, - ); - _.each(tx.logs, log => wrapLogBigNumbers(log)); - return tx; - } - public async batchFillOrKillOrdersAsync( - orders: Order[], - from: string, - opts: { fillTakerTokenAmounts?: BigNumber[] } = {}, - ) { - const params = formatters.createBatchFill(orders, undefined, opts.fillTakerTokenAmounts); - const tx = await this._exchange.batchFillOrKillOrders( - params.orderAddresses, - params.orderValues, - params.fillTakerTokenAmounts, - params.v, - params.r, - params.s, - { from }, - ); - _.each(tx.logs, log => wrapLogBigNumbers(log)); - return tx; - } - public async fillOrdersUpToAsync( - orders: Order[], - from: string, - opts: { - fillTakerTokenAmount?: BigNumber; - shouldThrowOnInsufficientBalanceOrAllowance?: boolean; - } = {}, - ) { - const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance; - const params = formatters.createFillUpTo( - orders, - shouldThrowOnInsufficientBalanceOrAllowance, - opts.fillTakerTokenAmount, - ); - const tx = await this._exchange.fillOrdersUpTo( - params.orderAddresses, - params.orderValues, - params.fillTakerTokenAmount, - params.shouldThrowOnInsufficientBalanceOrAllowance, - params.v, - params.r, - params.s, - { from }, - ); - _.each(tx.logs, log => wrapLogBigNumbers(log)); - return tx; - } - public async batchCancelOrdersAsync( - orders: Order[], - from: string, - opts: { cancelTakerTokenAmounts?: BigNumber[] } = {}, - ) { - const params = formatters.createBatchCancel(orders, opts.cancelTakerTokenAmounts); - const tx = await this._exchange.batchCancelOrders( - params.orderAddresses, - params.orderValues, - params.cancelTakerTokenAmounts, - { from }, - ); - _.each(tx.logs, log => wrapLogBigNumbers(log)); - return tx; - } - public async getOrderHashAsync(order: Order): Promise<string> { - const shouldThrowOnInsufficientBalanceOrAllowance = false; - const params = order.createFill(shouldThrowOnInsufficientBalanceOrAllowance); - const orderHash = await this._exchange.getOrderHash(params.orderAddresses, params.orderValues); - return orderHash; - } - public async isValidSignatureAsync(order: Order): Promise<boolean> { - const isValidSignature = await this._exchange.isValidSignature( - order.params.maker, - order.params.orderHashHex, - order.params.v, - order.params.r, - order.params.s, - ); - return isValidSignature; - } - public async isRoundingErrorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<boolean> { - const isRoundingError = await this._exchange.isRoundingError(numerator, denominator, target); - return isRoundingError; - } - public async getPartialAmountAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<BigNumber> { - const partialAmount = new BigNumber(await this._exchange.getPartialAmount(numerator, denominator, target)); - return partialAmount; - } + private _exchange: ContractInstance; + constructor(exchangeContractInstance: ContractInstance) { + this._exchange = exchangeContractInstance; + } + public async fillOrderAsync( + order: Order, + from: string, + opts: { + fillTakerTokenAmount?: BigNumber; + shouldThrowOnInsufficientBalanceOrAllowance?: boolean; + } = {}, + ) { + const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance; + const params = order.createFill(shouldThrowOnInsufficientBalanceOrAllowance, opts.fillTakerTokenAmount); + const tx = await this._exchange.fillOrder( + params.orderAddresses, + params.orderValues, + params.fillTakerTokenAmount, + params.shouldThrowOnInsufficientBalanceOrAllowance, + params.v, + params.r, + params.s, + { from }, + ); + _.each(tx.logs, log => wrapLogBigNumbers(log)); + return tx; + } + public async cancelOrderAsync(order: Order, from: string, opts: { cancelTakerTokenAmount?: BigNumber } = {}) { + const params = order.createCancel(opts.cancelTakerTokenAmount); + const tx = await this._exchange.cancelOrder( + params.orderAddresses, + params.orderValues, + params.cancelTakerTokenAmount, + { from }, + ); + _.each(tx.logs, log => wrapLogBigNumbers(log)); + return tx; + } + public async fillOrKillOrderAsync(order: Order, from: string, opts: { fillTakerTokenAmount?: BigNumber } = {}) { + const shouldThrowOnInsufficientBalanceOrAllowance = true; + const params = order.createFill(shouldThrowOnInsufficientBalanceOrAllowance, opts.fillTakerTokenAmount); + const tx = await this._exchange.fillOrKillOrder( + params.orderAddresses, + params.orderValues, + params.fillTakerTokenAmount, + params.v, + params.r, + params.s, + { from }, + ); + _.each(tx.logs, log => wrapLogBigNumbers(log)); + return tx; + } + public async batchFillOrdersAsync( + orders: Order[], + from: string, + opts: { + fillTakerTokenAmounts?: BigNumber[]; + shouldThrowOnInsufficientBalanceOrAllowance?: boolean; + } = {}, + ) { + const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance; + const params = formatters.createBatchFill( + orders, + shouldThrowOnInsufficientBalanceOrAllowance, + opts.fillTakerTokenAmounts, + ); + const tx = await this._exchange.batchFillOrders( + params.orderAddresses, + params.orderValues, + params.fillTakerTokenAmounts, + params.shouldThrowOnInsufficientBalanceOrAllowance, + params.v, + params.r, + params.s, + { from }, + ); + _.each(tx.logs, log => wrapLogBigNumbers(log)); + return tx; + } + public async batchFillOrKillOrdersAsync( + orders: Order[], + from: string, + opts: { fillTakerTokenAmounts?: BigNumber[] } = {}, + ) { + const params = formatters.createBatchFill(orders, undefined, opts.fillTakerTokenAmounts); + const tx = await this._exchange.batchFillOrKillOrders( + params.orderAddresses, + params.orderValues, + params.fillTakerTokenAmounts, + params.v, + params.r, + params.s, + { from }, + ); + _.each(tx.logs, log => wrapLogBigNumbers(log)); + return tx; + } + public async fillOrdersUpToAsync( + orders: Order[], + from: string, + opts: { + fillTakerTokenAmount?: BigNumber; + shouldThrowOnInsufficientBalanceOrAllowance?: boolean; + } = {}, + ) { + const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance; + const params = formatters.createFillUpTo( + orders, + shouldThrowOnInsufficientBalanceOrAllowance, + opts.fillTakerTokenAmount, + ); + const tx = await this._exchange.fillOrdersUpTo( + params.orderAddresses, + params.orderValues, + params.fillTakerTokenAmount, + params.shouldThrowOnInsufficientBalanceOrAllowance, + params.v, + params.r, + params.s, + { from }, + ); + _.each(tx.logs, log => wrapLogBigNumbers(log)); + return tx; + } + public async batchCancelOrdersAsync( + orders: Order[], + from: string, + opts: { cancelTakerTokenAmounts?: BigNumber[] } = {}, + ) { + const params = formatters.createBatchCancel(orders, opts.cancelTakerTokenAmounts); + const tx = await this._exchange.batchCancelOrders( + params.orderAddresses, + params.orderValues, + params.cancelTakerTokenAmounts, + { from }, + ); + _.each(tx.logs, log => wrapLogBigNumbers(log)); + return tx; + } + public async getOrderHashAsync(order: Order): Promise<string> { + const shouldThrowOnInsufficientBalanceOrAllowance = false; + const params = order.createFill(shouldThrowOnInsufficientBalanceOrAllowance); + const orderHash = await this._exchange.getOrderHash(params.orderAddresses, params.orderValues); + return orderHash; + } + public async isValidSignatureAsync(order: Order): Promise<boolean> { + const isValidSignature = await this._exchange.isValidSignature( + order.params.maker, + order.params.orderHashHex, + order.params.v, + order.params.r, + order.params.s, + ); + return isValidSignature; + } + public async isRoundingErrorAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise<boolean> { + const isRoundingError = await this._exchange.isRoundingError(numerator, denominator, target); + return isRoundingError; + } + public async getPartialAmountAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise<BigNumber> { + const partialAmount = new BigNumber(await this._exchange.getPartialAmount(numerator, denominator, target)); + return partialAmount; + } } function wrapLogBigNumbers(log: any): any { - const argNames = _.keys(log.args); - for (const argName of argNames) { - const isWeb3BigNumber = _.startsWith(log.args[argName].constructor.toString(), 'function BigNumber('); - if (isWeb3BigNumber) { - log.args[argName] = new BigNumber(log.args[argName]); - } - } + const argNames = _.keys(log.args); + for (const argName of argNames) { + const isWeb3BigNumber = _.startsWith(log.args[argName].constructor.toString(), 'function BigNumber('); + if (isWeb3BigNumber) { + log.args[argName] = new BigNumber(log.args[argName]); + } + } } diff --git a/packages/contracts/util/formatters.ts b/packages/contracts/util/formatters.ts index c452b8e79..0d0ef6df4 100644 --- a/packages/contracts/util/formatters.ts +++ b/packages/contracts/util/formatters.ts @@ -5,107 +5,107 @@ import { Order } from './order'; import { BatchCancelOrders, BatchFillOrders, FillOrdersUpTo } from './types'; export const formatters = { - createBatchFill( - orders: Order[], - shouldThrowOnInsufficientBalanceOrAllowance: boolean, - fillTakerTokenAmounts: BigNumber[] = [], - ) { - const batchFill: BatchFillOrders = { - orderAddresses: [], - orderValues: [], - fillTakerTokenAmounts, - shouldThrowOnInsufficientBalanceOrAllowance, - v: [], - r: [], - s: [], - }; - _.forEach(orders, order => { - batchFill.orderAddresses.push([ - order.params.maker, - order.params.taker, - order.params.makerToken, - order.params.takerToken, - order.params.feeRecipient, - ]); - batchFill.orderValues.push([ - order.params.makerTokenAmount, - order.params.takerTokenAmount, - order.params.makerFee, - order.params.takerFee, - order.params.expirationTimestampInSec, - order.params.salt, - ]); - batchFill.v.push(order.params.v); - batchFill.r.push(order.params.r); - batchFill.s.push(order.params.s); - if (fillTakerTokenAmounts.length < orders.length) { - batchFill.fillTakerTokenAmounts.push(order.params.takerTokenAmount); - } - }); - return batchFill; - }, - createFillUpTo( - orders: Order[], - shouldThrowOnInsufficientBalanceOrAllowance: boolean, - fillTakerTokenAmount: BigNumber, - ) { - const fillUpTo: FillOrdersUpTo = { - orderAddresses: [], - orderValues: [], - fillTakerTokenAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - v: [], - r: [], - s: [], - }; - orders.forEach(order => { - fillUpTo.orderAddresses.push([ - order.params.maker, - order.params.taker, - order.params.makerToken, - order.params.takerToken, - order.params.feeRecipient, - ]); - fillUpTo.orderValues.push([ - order.params.makerTokenAmount, - order.params.takerTokenAmount, - order.params.makerFee, - order.params.takerFee, - order.params.expirationTimestampInSec, - order.params.salt, - ]); - fillUpTo.v.push(order.params.v); - fillUpTo.r.push(order.params.r); - fillUpTo.s.push(order.params.s); - }); - return fillUpTo; - }, - createBatchCancel(orders: Order[], cancelTakerTokenAmounts: BigNumber[] = []) { - const batchCancel: BatchCancelOrders = { - orderAddresses: [], - orderValues: [], - cancelTakerTokenAmounts, - }; - orders.forEach(order => { - batchCancel.orderAddresses.push([ - order.params.maker, - order.params.taker, - order.params.makerToken, - order.params.takerToken, - order.params.feeRecipient, - ]); - batchCancel.orderValues.push([ - order.params.makerTokenAmount, - order.params.takerTokenAmount, - order.params.makerFee, - order.params.takerFee, - order.params.expirationTimestampInSec, - order.params.salt, - ]); - if (cancelTakerTokenAmounts.length < orders.length) { - batchCancel.cancelTakerTokenAmounts.push(order.params.takerTokenAmount); - } - }); - return batchCancel; - }, + createBatchFill( + orders: Order[], + shouldThrowOnInsufficientBalanceOrAllowance: boolean, + fillTakerTokenAmounts: BigNumber[] = [], + ) { + const batchFill: BatchFillOrders = { + orderAddresses: [], + orderValues: [], + fillTakerTokenAmounts, + shouldThrowOnInsufficientBalanceOrAllowance, + v: [], + r: [], + s: [], + }; + _.forEach(orders, order => { + batchFill.orderAddresses.push([ + order.params.maker, + order.params.taker, + order.params.makerToken, + order.params.takerToken, + order.params.feeRecipient, + ]); + batchFill.orderValues.push([ + order.params.makerTokenAmount, + order.params.takerTokenAmount, + order.params.makerFee, + order.params.takerFee, + order.params.expirationTimestampInSec, + order.params.salt, + ]); + batchFill.v.push(order.params.v); + batchFill.r.push(order.params.r); + batchFill.s.push(order.params.s); + if (fillTakerTokenAmounts.length < orders.length) { + batchFill.fillTakerTokenAmounts.push(order.params.takerTokenAmount); + } + }); + return batchFill; + }, + createFillUpTo( + orders: Order[], + shouldThrowOnInsufficientBalanceOrAllowance: boolean, + fillTakerTokenAmount: BigNumber, + ) { + const fillUpTo: FillOrdersUpTo = { + orderAddresses: [], + orderValues: [], + fillTakerTokenAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + v: [], + r: [], + s: [], + }; + orders.forEach(order => { + fillUpTo.orderAddresses.push([ + order.params.maker, + order.params.taker, + order.params.makerToken, + order.params.takerToken, + order.params.feeRecipient, + ]); + fillUpTo.orderValues.push([ + order.params.makerTokenAmount, + order.params.takerTokenAmount, + order.params.makerFee, + order.params.takerFee, + order.params.expirationTimestampInSec, + order.params.salt, + ]); + fillUpTo.v.push(order.params.v); + fillUpTo.r.push(order.params.r); + fillUpTo.s.push(order.params.s); + }); + return fillUpTo; + }, + createBatchCancel(orders: Order[], cancelTakerTokenAmounts: BigNumber[] = []) { + const batchCancel: BatchCancelOrders = { + orderAddresses: [], + orderValues: [], + cancelTakerTokenAmounts, + }; + orders.forEach(order => { + batchCancel.orderAddresses.push([ + order.params.maker, + order.params.taker, + order.params.makerToken, + order.params.takerToken, + order.params.feeRecipient, + ]); + batchCancel.orderValues.push([ + order.params.makerTokenAmount, + order.params.takerTokenAmount, + order.params.makerFee, + order.params.takerFee, + order.params.expirationTimestampInSec, + order.params.salt, + ]); + if (cancelTakerTokenAmounts.length < orders.length) { + batchCancel.cancelTakerTokenAmounts.push(order.params.takerTokenAmount); + } + }); + return batchCancel; + }, }; diff --git a/packages/contracts/util/multi_sig_wrapper.ts b/packages/contracts/util/multi_sig_wrapper.ts index ca9f07ce5..0e2e671ec 100644 --- a/packages/contracts/util/multi_sig_wrapper.ts +++ b/packages/contracts/util/multi_sig_wrapper.ts @@ -6,34 +6,34 @@ import * as Web3 from 'web3'; import { ContractInstance, TransactionDataParams } from './types'; export class MultiSigWrapper { - private _multiSig: ContractInstance; - public static encodeFnArgs(name: string, abi: Web3.AbiDefinition[], args: any[]) { - const abiEntity = _.find(abi, { name }) as Web3.MethodAbi; - if (_.isUndefined(abiEntity)) { - throw new Error(`Did not find abi entry for name: ${name}`); - } - const types = _.map(abiEntity.inputs, input => input.type); - const funcSig = ethUtil.bufferToHex(ABI.methodID(name, types)); - const argsData = _.map(args, arg => { - const target = _.isBoolean(arg) ? +arg : arg; - const targetBuff = ethUtil.toBuffer(target); - return ethUtil.setLengthLeft(targetBuff, 32).toString('hex'); - }); - return funcSig + argsData.join(''); - } - constructor(multiSigContractInstance: ContractInstance) { - this._multiSig = multiSigContractInstance; - } - public async submitTransactionAsync( - destination: string, - from: string, - dataParams: TransactionDataParams, - value: number = 0, - ) { - const { name, abi, args = [] } = dataParams; - const encoded = MultiSigWrapper.encodeFnArgs(name, abi, args); - return this._multiSig.submitTransaction(destination, value, encoded, { - from, - }); - } + private _multiSig: ContractInstance; + public static encodeFnArgs(name: string, abi: Web3.AbiDefinition[], args: any[]) { + const abiEntity = _.find(abi, { name }) as Web3.MethodAbi; + if (_.isUndefined(abiEntity)) { + throw new Error(`Did not find abi entry for name: ${name}`); + } + const types = _.map(abiEntity.inputs, input => input.type); + const funcSig = ethUtil.bufferToHex(ABI.methodID(name, types)); + const argsData = _.map(args, arg => { + const target = _.isBoolean(arg) ? +arg : arg; + const targetBuff = ethUtil.toBuffer(target); + return ethUtil.setLengthLeft(targetBuff, 32).toString('hex'); + }); + return funcSig + argsData.join(''); + } + constructor(multiSigContractInstance: ContractInstance) { + this._multiSig = multiSigContractInstance; + } + public async submitTransactionAsync( + destination: string, + from: string, + dataParams: TransactionDataParams, + value: number = 0, + ) { + const { name, abi, args = [] } = dataParams; + const encoded = MultiSigWrapper.encodeFnArgs(name, abi, args); + return this._multiSig.submitTransaction(destination, value, encoded, { + from, + }); + } } diff --git a/packages/contracts/util/order.ts b/packages/contracts/util/order.ts index 31194c03e..e202d485b 100644 --- a/packages/contracts/util/order.ts +++ b/packages/contracts/util/order.ts @@ -11,98 +11,98 @@ import { OrderParams } from './types'; const web3: Web3 = (global as any).web3; export class Order { - public params: OrderParams; - constructor(params: OrderParams) { - this.params = params; - } - public isValidSignature() { - const { v, r, s } = this.params; - if (_.isUndefined(v) || _.isUndefined(r) || _.isUndefined(s)) { - throw new Error('Cannot call isValidSignature on unsigned order'); - } - const orderHash = this._getOrderHash(); - const msgHash = ethUtil.hashPersonalMessage(ethUtil.toBuffer(orderHash)); - try { - const pubKey = ethUtil.ecrecover(msgHash, v, ethUtil.toBuffer(r), ethUtil.toBuffer(s)); - const recoveredAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey)); - return recoveredAddress === this.params.maker; - } catch (err) { - return false; - } - } - public async signAsync() { - const orderHash = this._getOrderHash(); - const signature = await promisify<string>(web3.eth.sign)(this.params.maker, orderHash); - const { v, r, s } = ethUtil.fromRpcSig(signature); - this.params = _.assign(this.params, { - orderHashHex: orderHash, - v, - r: ethUtil.bufferToHex(r), - s: ethUtil.bufferToHex(s), - }); - } - public createFill(shouldThrowOnInsufficientBalanceOrAllowance?: boolean, fillTakerTokenAmount?: BigNumber) { - const fill = { - orderAddresses: [ - this.params.maker, - this.params.taker, - this.params.makerToken, - this.params.takerToken, - this.params.feeRecipient, - ], - orderValues: [ - this.params.makerTokenAmount, - this.params.takerTokenAmount, - this.params.makerFee, - this.params.takerFee, - this.params.expirationTimestampInSec, - this.params.salt, - ], - fillTakerTokenAmount: fillTakerTokenAmount || this.params.takerTokenAmount, - shouldThrowOnInsufficientBalanceOrAllowance: !!shouldThrowOnInsufficientBalanceOrAllowance, - v: this.params.v, - r: this.params.r, - s: this.params.s, - }; - return fill; - } - public createCancel(cancelTakerTokenAmount?: BigNumber) { - const cancel = { - orderAddresses: [ - this.params.maker, - this.params.taker, - this.params.makerToken, - this.params.takerToken, - this.params.feeRecipient, - ], - orderValues: [ - this.params.makerTokenAmount, - this.params.takerTokenAmount, - this.params.makerFee, - this.params.takerFee, - this.params.expirationTimestampInSec, - this.params.salt, - ], - cancelTakerTokenAmount: cancelTakerTokenAmount || this.params.takerTokenAmount, - }; - return cancel; - } - private _getOrderHash(): string { - const orderHash = crypto.solSHA3([ - this.params.exchangeContractAddress, - this.params.maker, - this.params.taker, - this.params.makerToken, - this.params.takerToken, - this.params.feeRecipient, - this.params.makerTokenAmount, - this.params.takerTokenAmount, - this.params.makerFee, - this.params.takerFee, - this.params.expirationTimestampInSec, - this.params.salt, - ]); - const orderHashHex = ethUtil.bufferToHex(orderHash); - return orderHashHex; - } + public params: OrderParams; + constructor(params: OrderParams) { + this.params = params; + } + public isValidSignature() { + const { v, r, s } = this.params; + if (_.isUndefined(v) || _.isUndefined(r) || _.isUndefined(s)) { + throw new Error('Cannot call isValidSignature on unsigned order'); + } + const orderHash = this._getOrderHash(); + const msgHash = ethUtil.hashPersonalMessage(ethUtil.toBuffer(orderHash)); + try { + const pubKey = ethUtil.ecrecover(msgHash, v, ethUtil.toBuffer(r), ethUtil.toBuffer(s)); + const recoveredAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey)); + return recoveredAddress === this.params.maker; + } catch (err) { + return false; + } + } + public async signAsync() { + const orderHash = this._getOrderHash(); + const signature = await promisify<string>(web3.eth.sign)(this.params.maker, orderHash); + const { v, r, s } = ethUtil.fromRpcSig(signature); + this.params = _.assign(this.params, { + orderHashHex: orderHash, + v, + r: ethUtil.bufferToHex(r), + s: ethUtil.bufferToHex(s), + }); + } + public createFill(shouldThrowOnInsufficientBalanceOrAllowance?: boolean, fillTakerTokenAmount?: BigNumber) { + const fill = { + orderAddresses: [ + this.params.maker, + this.params.taker, + this.params.makerToken, + this.params.takerToken, + this.params.feeRecipient, + ], + orderValues: [ + this.params.makerTokenAmount, + this.params.takerTokenAmount, + this.params.makerFee, + this.params.takerFee, + this.params.expirationTimestampInSec, + this.params.salt, + ], + fillTakerTokenAmount: fillTakerTokenAmount || this.params.takerTokenAmount, + shouldThrowOnInsufficientBalanceOrAllowance: !!shouldThrowOnInsufficientBalanceOrAllowance, + v: this.params.v, + r: this.params.r, + s: this.params.s, + }; + return fill; + } + public createCancel(cancelTakerTokenAmount?: BigNumber) { + const cancel = { + orderAddresses: [ + this.params.maker, + this.params.taker, + this.params.makerToken, + this.params.takerToken, + this.params.feeRecipient, + ], + orderValues: [ + this.params.makerTokenAmount, + this.params.takerTokenAmount, + this.params.makerFee, + this.params.takerFee, + this.params.expirationTimestampInSec, + this.params.salt, + ], + cancelTakerTokenAmount: cancelTakerTokenAmount || this.params.takerTokenAmount, + }; + return cancel; + } + private _getOrderHash(): string { + const orderHash = crypto.solSHA3([ + this.params.exchangeContractAddress, + this.params.maker, + this.params.taker, + this.params.makerToken, + this.params.takerToken, + this.params.feeRecipient, + this.params.makerTokenAmount, + this.params.takerTokenAmount, + this.params.makerFee, + this.params.takerFee, + this.params.expirationTimestampInSec, + this.params.salt, + ]); + const orderHashHex = ethUtil.bufferToHex(orderHash); + return orderHashHex; + } } diff --git a/packages/contracts/util/order_factory.ts b/packages/contracts/util/order_factory.ts index 55034655b..a45877de0 100644 --- a/packages/contracts/util/order_factory.ts +++ b/packages/contracts/util/order_factory.ts @@ -6,24 +6,24 @@ import { Order } from './order'; import { DefaultOrderParams, OptionalOrderParams, OrderParams } from './types'; export class OrderFactory { - private _defaultOrderParams: DefaultOrderParams; - constructor(defaultOrderParams: DefaultOrderParams) { - this._defaultOrderParams = defaultOrderParams; - } - public async newSignedOrderAsync(customOrderParams: OptionalOrderParams = {}) { - const randomExpiration = new BigNumber(Math.floor((Date.now() + Math.random() * 100000000000) / 1000)); - const orderParams: OrderParams = _.assign( - {}, - { - expirationTimestampInSec: randomExpiration, - salt: ZeroEx.generatePseudoRandomSalt(), - taker: ZeroEx.NULL_ADDRESS, - }, - this._defaultOrderParams, - customOrderParams, - ); - const order = new Order(orderParams); - await order.signAsync(); - return order; - } + private _defaultOrderParams: DefaultOrderParams; + constructor(defaultOrderParams: DefaultOrderParams) { + this._defaultOrderParams = defaultOrderParams; + } + public async newSignedOrderAsync(customOrderParams: OptionalOrderParams = {}) { + const randomExpiration = new BigNumber(Math.floor((Date.now() + Math.random() * 100000000000) / 1000)); + const orderParams: OrderParams = _.assign( + {}, + { + expirationTimestampInSec: randomExpiration, + salt: ZeroEx.generatePseudoRandomSalt(), + taker: ZeroEx.NULL_ADDRESS, + }, + this._defaultOrderParams, + customOrderParams, + ); + const order = new Order(orderParams); + await order.signAsync(); + return order; + } } diff --git a/packages/contracts/util/token_registry_wrapper.ts b/packages/contracts/util/token_registry_wrapper.ts index d1983db7c..07a577dea 100644 --- a/packages/contracts/util/token_registry_wrapper.ts +++ b/packages/contracts/util/token_registry_wrapper.ts @@ -1,56 +1,56 @@ import { ContractInstance, Token } from './types'; export class TokenRegWrapper { - private _tokenReg: ContractInstance; - constructor(tokenRegContractInstance: ContractInstance) { - this._tokenReg = tokenRegContractInstance; - } - public addTokenAsync(token: Token, from: string) { - const tx = this._tokenReg.addToken( - token.address, - token.name, - token.symbol, - token.decimals, - token.ipfsHash, - token.swarmHash, - { from }, - ); - return tx; - } - public async getTokenMetaDataAsync(tokenAddress: string) { - const data = await this._tokenReg.getTokenMetaData(tokenAddress); - const token: Token = { - address: data[0], - name: data[1], - symbol: data[2], - decimals: data[3].toNumber(), - ipfsHash: data[4], - swarmHash: data[5], - }; - return token; - } - public async getTokenByNameAsync(tokenName: string) { - const data = await this._tokenReg.getTokenByName(tokenName); - const token: Token = { - address: data[0], - name: data[1], - symbol: data[2], - decimals: data[3].toNumber(), - ipfsHash: data[4], - swarmHash: data[5], - }; - return token; - } - public async getTokenBySymbolAsync(tokenSymbol: string) { - const data = await this._tokenReg.getTokenBySymbol(tokenSymbol); - const token: Token = { - address: data[0], - name: data[1], - symbol: data[2], - decimals: data[3].toNumber(), - ipfsHash: data[4], - swarmHash: data[5], - }; - return token; - } + private _tokenReg: ContractInstance; + constructor(tokenRegContractInstance: ContractInstance) { + this._tokenReg = tokenRegContractInstance; + } + public addTokenAsync(token: Token, from: string) { + const tx = this._tokenReg.addToken( + token.address, + token.name, + token.symbol, + token.decimals, + token.ipfsHash, + token.swarmHash, + { from }, + ); + return tx; + } + public async getTokenMetaDataAsync(tokenAddress: string) { + const data = await this._tokenReg.getTokenMetaData(tokenAddress); + const token: Token = { + address: data[0], + name: data[1], + symbol: data[2], + decimals: data[3].toNumber(), + ipfsHash: data[4], + swarmHash: data[5], + }; + return token; + } + public async getTokenByNameAsync(tokenName: string) { + const data = await this._tokenReg.getTokenByName(tokenName); + const token: Token = { + address: data[0], + name: data[1], + symbol: data[2], + decimals: data[3].toNumber(), + ipfsHash: data[4], + swarmHash: data[5], + }; + return token; + } + public async getTokenBySymbolAsync(tokenSymbol: string) { + const data = await this._tokenReg.getTokenBySymbol(tokenSymbol); + const token: Token = { + address: data[0], + name: data[1], + symbol: data[2], + decimals: data[3].toNumber(), + ipfsHash: data[4], + swarmHash: data[5], + }; + return token; + } } diff --git a/packages/contracts/util/types.ts b/packages/contracts/util/types.ts index 675f3f17d..e511ca9f4 100644 --- a/packages/contracts/util/types.ts +++ b/packages/contracts/util/types.ts @@ -2,118 +2,118 @@ import { BigNumber } from '@0xproject/utils'; import * as Web3 from 'web3'; export interface BalancesByOwner { - [ownerAddress: string]: { - [tokenAddress: string]: BigNumber; - }; + [ownerAddress: string]: { + [tokenAddress: string]: BigNumber; + }; } export interface BatchFillOrders { - orderAddresses: string[][]; - orderValues: BigNumber[][]; - fillTakerTokenAmounts: BigNumber[]; - shouldThrowOnInsufficientBalanceOrAllowance: boolean; - v: number[]; - r: string[]; - s: string[]; + orderAddresses: string[][]; + orderValues: BigNumber[][]; + fillTakerTokenAmounts: BigNumber[]; + shouldThrowOnInsufficientBalanceOrAllowance: boolean; + v: number[]; + r: string[]; + s: string[]; } export interface FillOrdersUpTo { - orderAddresses: string[][]; - orderValues: BigNumber[][]; - fillTakerTokenAmount: BigNumber; - shouldThrowOnInsufficientBalanceOrAllowance: boolean; - v: number[]; - r: string[]; - s: string[]; + orderAddresses: string[][]; + orderValues: BigNumber[][]; + fillTakerTokenAmount: BigNumber; + shouldThrowOnInsufficientBalanceOrAllowance: boolean; + v: number[]; + r: string[]; + s: string[]; } export interface BatchCancelOrders { - orderAddresses: string[][]; - orderValues: BigNumber[][]; - cancelTakerTokenAmounts: BigNumber[]; + orderAddresses: string[][]; + orderValues: BigNumber[][]; + cancelTakerTokenAmounts: BigNumber[]; } export interface DefaultOrderParams { - exchangeContractAddress: string; - maker: string; - feeRecipient: string; - makerToken: string; - takerToken: string; - makerTokenAmount: BigNumber; - takerTokenAmount: BigNumber; - makerFee: BigNumber; - takerFee: BigNumber; + exchangeContractAddress: string; + maker: string; + feeRecipient: string; + makerToken: string; + takerToken: string; + makerTokenAmount: BigNumber; + takerTokenAmount: BigNumber; + makerFee: BigNumber; + takerFee: BigNumber; } export interface OptionalOrderParams { - exchangeContractAddress?: string; - maker?: string; - taker?: string; - feeRecipient?: string; - makerToken?: string; - takerToken?: string; - makerTokenAmount?: BigNumber; - takerTokenAmount?: BigNumber; - makerFee?: BigNumber; - takerFee?: BigNumber; - expirationTimestampInSec?: BigNumber; + exchangeContractAddress?: string; + maker?: string; + taker?: string; + feeRecipient?: string; + makerToken?: string; + takerToken?: string; + makerTokenAmount?: BigNumber; + takerTokenAmount?: BigNumber; + makerFee?: BigNumber; + takerFee?: BigNumber; + expirationTimestampInSec?: BigNumber; } export interface OrderParams { - exchangeContractAddress: string; - maker: string; - taker: string; - feeRecipient: string; - makerToken: string; - takerToken: string; - makerTokenAmount: BigNumber; - takerTokenAmount: BigNumber; - makerFee: BigNumber; - takerFee: BigNumber; - expirationTimestampInSec: BigNumber; - salt: BigNumber; - orderHashHex?: string; - v?: number; - r?: string; - s?: string; + exchangeContractAddress: string; + maker: string; + taker: string; + feeRecipient: string; + makerToken: string; + takerToken: string; + makerTokenAmount: BigNumber; + takerTokenAmount: BigNumber; + makerFee: BigNumber; + takerFee: BigNumber; + expirationTimestampInSec: BigNumber; + salt: BigNumber; + orderHashHex?: string; + v?: number; + r?: string; + s?: string; } export interface TransactionDataParams { - name: string; - abi: Web3.AbiDefinition[]; - args: any[]; + name: string; + abi: Web3.AbiDefinition[]; + args: any[]; } export interface MultiSigConfig { - owners: string[]; - confirmationsRequired: number; - secondsRequired: number; + owners: string[]; + confirmationsRequired: number; + secondsRequired: number; } export interface MultiSigConfigByNetwork { - [networkName: string]: MultiSigConfig; + [networkName: string]: MultiSigConfig; } export interface Token { - address?: string; - name: string; - symbol: string; - decimals: number; - ipfsHash: string; - swarmHash: string; + address?: string; + name: string; + symbol: string; + decimals: number; + ipfsHash: string; + swarmHash: string; } export interface TokenInfoByNetwork { - development: Token[]; - live: Token[]; + development: Token[]; + live: Token[]; } // Named type aliases to improve readability export type ContractInstance = any; export enum ExchangeContractErrs { - ERROR_ORDER_EXPIRED, - ERROR_ORDER_FULLY_FILLED_OR_CANCELLED, - ERROR_ROUNDING_ERROR_TOO_LARGE, - ERROR_INSUFFICIENT_BALANCE_OR_ALLOWANCE, + ERROR_ORDER_EXPIRED, + ERROR_ORDER_FULLY_FILLED_OR_CANCELLED, + ERROR_ROUNDING_ERROR_TOO_LARGE, + ERROR_INSUFFICIENT_BALANCE_OR_ALLOWANCE, } diff --git a/packages/deployer/package.json b/packages/deployer/package.json index f78f49497..423d3bad8 100644 --- a/packages/deployer/package.json +++ b/packages/deployer/package.json @@ -1,43 +1,43 @@ { - "name": "@0xproject/deployer", - "version": "0.0.3", - "description": "Smart contract deployer of 0x protocol", - "main": "lib/src/cli.js", - "scripts": { - "build": "yarn clean && copyfiles 'test/fixtures/contracts/**/*' src/solc/solc_bin/* ./lib && tsc", - "test": "npm run build; mocha lib/test/*_test.js", - "compile": "npm run build; node lib/src/cli.js compile", - "clean": "rm -rf ./lib", - "migrate": "npm run build; node lib/src/cli.js migrate", - "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", - "test:circleci": "yarn test" - }, - "bin": { - "0x-deployer": "lib/src/cli.js" - }, - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "author": "Amir Bandeali", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/deployer/README.md", - "devDependencies": { - "copyfiles": "^1.2.0", - "types-bn": "^0.0.1", - "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.6" - }, - "dependencies": { - "@0xproject/utils": "^0.1.3", - "@0xproject/web3-wrapper": "^0.1.7", - "lodash": "^4.17.4", - "solc": "^0.4.18", - "web3": "^0.20.0", - "web3-eth-abi": "^1.0.0-beta.24", - "yargs": "^10.0.3" - } + "name": "@0xproject/deployer", + "version": "0.0.3", + "description": "Smart contract deployer of 0x protocol", + "main": "lib/src/cli.js", + "scripts": { + "build": "yarn clean && copyfiles 'test/fixtures/contracts/**/*' src/solc/solc_bin/* ./lib && tsc", + "test": "npm run build; mocha lib/test/*_test.js", + "compile": "npm run build; node lib/src/cli.js compile", + "clean": "rm -rf ./lib", + "migrate": "npm run build; node lib/src/cli.js migrate", + "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", + "test:circleci": "yarn test" + }, + "bin": { + "0x-deployer": "lib/src/cli.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "author": "Amir Bandeali", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/deployer/README.md", + "devDependencies": { + "copyfiles": "^1.2.0", + "types-bn": "^0.0.1", + "typescript": "~2.6.1", + "web3-typescript-typings": "^0.9.6" + }, + "dependencies": { + "@0xproject/utils": "^0.1.3", + "@0xproject/web3-wrapper": "^0.1.7", + "lodash": "^4.17.4", + "solc": "^0.4.18", + "web3": "^0.20.0", + "web3-eth-abi": "^1.0.0-beta.24", + "yargs": "^10.0.3" + } } diff --git a/packages/deployer/src/cli.ts b/packages/deployer/src/cli.ts index f4241b1cc..decb37fdc 100644 --- a/packages/deployer/src/cli.ts +++ b/packages/deployer/src/cli.ts @@ -20,13 +20,13 @@ const DEFAULT_GAS_PRICE = (10 ** 9 * 2).toString(); * @param argv Instance of process.argv provided by yargs. */ async function onCompileCommand(argv: CliOptions): Promise<void> { - const opts: CompilerOptions = { - contractsDir: argv.contractsDir, - networkId: argv.networkId, - optimizerEnabled: argv.shouldOptimize ? 1 : 0, - artifactsDir: argv.artifactsDir, - }; - await commands.compileAsync(opts); + const opts: CompilerOptions = { + contractsDir: argv.contractsDir, + networkId: argv.networkId, + optimizerEnabled: argv.shouldOptimize ? 1 : 0, + artifactsDir: argv.artifactsDir, + }; + await commands.compileAsync(opts); } /** * Compiles all contracts and runs migration script with options passed in through CLI. @@ -34,123 +34,123 @@ async function onCompileCommand(argv: CliOptions): Promise<void> { * @param argv Instance of process.argv provided by yargs. */ async function onMigrateCommand(argv: CliOptions): Promise<void> { - const url = `http://localhost:${argv.jsonrpcPort}`; - const web3Provider = new Web3.providers.HttpProvider(url); - const web3Wrapper = new Web3Wrapper(web3Provider); - const networkId = await web3Wrapper.getNetworkIdAsync(); - const compilerOpts: CompilerOptions = { - contractsDir: argv.contractsDir, - networkId, - optimizerEnabled: argv.shouldOptimize ? 1 : 0, - artifactsDir: argv.artifactsDir, - }; - await commands.compileAsync(compilerOpts); + const url = `http://localhost:${argv.jsonrpcPort}`; + const web3Provider = new Web3.providers.HttpProvider(url); + const web3Wrapper = new Web3Wrapper(web3Provider); + const networkId = await web3Wrapper.getNetworkIdAsync(); + const compilerOpts: CompilerOptions = { + contractsDir: argv.contractsDir, + networkId, + optimizerEnabled: argv.shouldOptimize ? 1 : 0, + artifactsDir: argv.artifactsDir, + }; + await commands.compileAsync(compilerOpts); - const defaults = { - gasPrice: new BigNumber(argv.gasPrice), - from: argv.account, - }; - const deployerOpts = { - artifactsDir: argv.artifactsDir, - jsonrpcPort: argv.jsonrpcPort, - networkId, - defaults, - }; - await commands.migrateAsync(deployerOpts); + const defaults = { + gasPrice: new BigNumber(argv.gasPrice), + from: argv.account, + }; + const deployerOpts = { + artifactsDir: argv.artifactsDir, + jsonrpcPort: argv.jsonrpcPort, + networkId, + defaults, + }; + await commands.migrateAsync(deployerOpts); } /** * Deploys a single contract with provided name and args. * @param argv Instance of process.argv provided by yargs. */ async function onDeployCommand(argv: CliOptions): Promise<void> { - const url = `http://localhost:${argv.jsonrpcPort}`; - const web3Provider = new Web3.providers.HttpProvider(url); - const web3Wrapper = new Web3Wrapper(web3Provider); - const networkId = await web3Wrapper.getNetworkIdAsync(); - const compilerOpts: CompilerOptions = { - contractsDir: argv.contractsDir, - networkId, - optimizerEnabled: argv.shouldOptimize ? 1 : 0, - artifactsDir: argv.artifactsDir, - }; - await commands.compileAsync(compilerOpts); + const url = `http://localhost:${argv.jsonrpcPort}`; + const web3Provider = new Web3.providers.HttpProvider(url); + const web3Wrapper = new Web3Wrapper(web3Provider); + const networkId = await web3Wrapper.getNetworkIdAsync(); + const compilerOpts: CompilerOptions = { + contractsDir: argv.contractsDir, + networkId, + optimizerEnabled: argv.shouldOptimize ? 1 : 0, + artifactsDir: argv.artifactsDir, + }; + await commands.compileAsync(compilerOpts); - const defaults = { - gasPrice: new BigNumber(argv.gasPrice), - from: argv.account, - }; - const deployerOpts: DeployerOptions = { - artifactsDir: argv.artifactsDir, - jsonrpcPort: argv.jsonrpcPort, - networkId, - defaults, - }; - const deployerArgsString = argv.args; - const deployerArgs = deployerArgsString.split(','); - await commands.deployAsync(argv.contract, deployerArgs, deployerOpts); + const defaults = { + gasPrice: new BigNumber(argv.gasPrice), + from: argv.account, + }; + const deployerOpts: DeployerOptions = { + artifactsDir: argv.artifactsDir, + jsonrpcPort: argv.jsonrpcPort, + networkId, + defaults, + }; + const deployerArgsString = argv.args; + const deployerArgs = deployerArgsString.split(','); + await commands.deployAsync(argv.contract, deployerArgs, deployerOpts); } /** * Provides extra required options for deploy command. * @param yargsInstance yargs instance provided in builder function callback. */ function deployCommandBuilder(yargsInstance: any) { - return yargsInstance - .option('contract', { - type: 'string', - description: 'name of contract to deploy, exluding .sol extension', - }) - .option('args', { - type: 'string', - description: 'comma separated list of constructor args to deploy contract with', - }) - .demandOption(['contract', 'args']) - .help().argv; + return yargsInstance + .option('contract', { + type: 'string', + description: 'name of contract to deploy, exluding .sol extension', + }) + .option('args', { + type: 'string', + description: 'comma separated list of constructor args to deploy contract with', + }) + .demandOption(['contract', 'args']) + .help().argv; } (() => { - const identityCommandBuilder = _.identity; - return yargs - .option('contracts-dir', { - type: 'string', - default: DEFAULT_CONTRACTS_DIR, - description: 'path of contracts directory to compile', - }) - .option('network-id', { - type: 'number', - default: DEFAULT_NETWORK_ID, - description: 'mainnet=1, kovan=42, testrpc=50', - }) - .option('should-optimize', { - type: 'boolean', - default: DEFAULT_OPTIMIZER_ENABLED, - description: 'enable optimizer', - }) - .option('artifacts-dir', { - type: 'string', - default: DEFAULT_ARTIFACTS_DIR, - description: 'path to write contracts artifacts to', - }) - .option('jsonrpc-port', { - type: 'number', - default: DEFAULT_JSONRPC_PORT, - description: 'port connected to JSON RPC', - }) - .option('gas-price', { - type: 'string', - default: DEFAULT_GAS_PRICE, - description: 'gasPrice to be used for transactions', - }) - .option('account', { - type: 'string', - description: 'account to use for deploying contracts', - }) - .command('compile', 'compile contracts', identityCommandBuilder, onCompileCommand) - .command( - 'migrate', - 'compile and deploy contracts using migration scripts', - identityCommandBuilder, - onMigrateCommand, - ) - .command('deploy', 'deploy a single contract with provided arguments', deployCommandBuilder, onDeployCommand) - .help().argv; + const identityCommandBuilder = _.identity; + return yargs + .option('contracts-dir', { + type: 'string', + default: DEFAULT_CONTRACTS_DIR, + description: 'path of contracts directory to compile', + }) + .option('network-id', { + type: 'number', + default: DEFAULT_NETWORK_ID, + description: 'mainnet=1, kovan=42, testrpc=50', + }) + .option('should-optimize', { + type: 'boolean', + default: DEFAULT_OPTIMIZER_ENABLED, + description: 'enable optimizer', + }) + .option('artifacts-dir', { + type: 'string', + default: DEFAULT_ARTIFACTS_DIR, + description: 'path to write contracts artifacts to', + }) + .option('jsonrpc-port', { + type: 'number', + default: DEFAULT_JSONRPC_PORT, + description: 'port connected to JSON RPC', + }) + .option('gas-price', { + type: 'string', + default: DEFAULT_GAS_PRICE, + description: 'gasPrice to be used for transactions', + }) + .option('account', { + type: 'string', + description: 'account to use for deploying contracts', + }) + .command('compile', 'compile contracts', identityCommandBuilder, onCompileCommand) + .command( + 'migrate', + 'compile and deploy contracts using migration scripts', + identityCommandBuilder, + onMigrateCommand, + ) + .command('deploy', 'deploy a single contract with provided arguments', deployCommandBuilder, onDeployCommand) + .help().argv; })(); diff --git a/packages/deployer/src/commands.ts b/packages/deployer/src/commands.ts index 63e75685e..2acef8e8f 100644 --- a/packages/deployer/src/commands.ts +++ b/packages/deployer/src/commands.ts @@ -4,16 +4,16 @@ import { Deployer } from './deployer'; import { CompilerOptions, DeployerOptions } from './utils/types'; export const commands = { - async compileAsync(opts: CompilerOptions): Promise<void> { - const compiler = new Compiler(opts); - await compiler.compileAllAsync(); - }, - async migrateAsync(opts: DeployerOptions): Promise<void> { - const deployer = new Deployer(opts); - await migrator.runMigrationsAsync(deployer); - }, - async deployAsync(contractName: string, args: any[], opts: DeployerOptions): Promise<void> { - const deployer = new Deployer(opts); - await deployer.deployAndSaveAsync(contractName, args); - }, + async compileAsync(opts: CompilerOptions): Promise<void> { + const compiler = new Compiler(opts); + await compiler.compileAllAsync(); + }, + async migrateAsync(opts: DeployerOptions): Promise<void> { + const deployer = new Deployer(opts); + await migrator.runMigrationsAsync(deployer); + }, + async deployAsync(contractName: string, args: any[], opts: DeployerOptions): Promise<void> { + const deployer = new Deployer(opts); + await deployer.deployAndSaveAsync(contractName, args); + }, }; diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts index bc003149c..63db6c865 100644 --- a/packages/deployer/src/compiler.ts +++ b/packages/deployer/src/compiler.ts @@ -7,245 +7,245 @@ import * as Web3 from 'web3'; import { binPaths } from './solc/bin_paths'; import { fsWrapper } from './utils/fs_wrapper'; import { - CompilerOptions, - ContractArtifact, - ContractData, - ContractNetworks, - ContractSources, - ImportContents, + CompilerOptions, + ContractArtifact, + ContractData, + ContractNetworks, + ContractSources, + ImportContents, } from './utils/types'; import { utils } from './utils/utils'; const SOLIDITY_FILE_EXTENSION = '.sol'; export class Compiler { - private _contractsDir: string; - private _networkId: number; - private _optimizerEnabled: number; - private _artifactsDir: string; - private _contractSourcesIfExists?: ContractSources; - private _solcErrors: Set<string>; - /** - * Recursively retrieves Solidity source code from directory. - * @param dirPath Directory to search. - * @return Mapping of contract name to contract source. - */ - private static async _getContractSourcesAsync(dirPath: string): Promise<ContractSources> { - let dirContents: string[] = []; - try { - dirContents = await fsWrapper.readdirAsync(dirPath); - } catch (err) { - throw new Error(`No directory found at ${dirPath}`); - } - let sources: ContractSources = {}; - for (const name of dirContents) { - const contentPath = `${dirPath}/${name}`; - if (path.extname(name) === SOLIDITY_FILE_EXTENSION) { - try { - const opts = { - encoding: 'utf8', - }; - sources[name] = await fsWrapper.readFileAsync(contentPath, opts); - utils.consoleLog(`Reading ${name} source...`); - } catch (err) { - utils.consoleLog(`Could not find file at ${contentPath}`); - } - } else { - try { - const nestedSources = await Compiler._getContractSourcesAsync(contentPath); - sources = { - ...sources, - ...nestedSources, - }; - } catch (err) { - utils.consoleLog(`${contentPath} is not a directory or ${SOLIDITY_FILE_EXTENSION} file`); - } - } - } - return sources; - } - /** - * Searches Solidity source code for compiler version. - * @param source Source code of contract. - * @return Solc compiler version. - */ - private static _parseSolidityVersion(source: string): string { - const solcVersionMatch = source.match(/(?:solidity\s\^?)([0-9]{1,2}[.][0-9]{1,2}[.][0-9]{1,2})/); - if (_.isNull(solcVersionMatch)) { - throw new Error('Could not find Solidity version in source'); - } - const solcVersion = solcVersionMatch[1]; - return solcVersion; - } - /** - * Normalizes the path found in the error message. - * Example: converts 'base/Token.sol:6:46: Warning: Unused local variable' - * to 'Token.sol:6:46: Warning: Unused local variable' - * This is used to prevent logging the same error multiple times. - * @param errMsg An error message from the compiled output. - * @return The error message with directories truncated from the contract path. - */ - private static _getNormalizedErrMsg(errMsg: string): string { - const errPathMatch = errMsg.match(/(.*\.sol)/); - if (_.isNull(errPathMatch)) { - throw new Error('Could not find a path in error message'); - } - const errPath = errPathMatch[0]; - const baseContract = path.basename(errPath); - const normalizedErrMsg = errMsg.replace(errPath, baseContract); - return normalizedErrMsg; - } - /** - * Instantiates a new instance of the Compiler class. - * @param opts Options specifying directories, network, and optimization settings. - * @return An instance of the Compiler class. - */ - constructor(opts: CompilerOptions) { - this._contractsDir = opts.contractsDir; - this._networkId = opts.networkId; - this._optimizerEnabled = opts.optimizerEnabled; - this._artifactsDir = opts.artifactsDir; - this._solcErrors = new Set(); - } - /** - * Compiles all Solidity files found in contractsDir and writes JSON artifacts to artifactsDir. - */ - public async compileAllAsync(): Promise<void> { - await this._createArtifactsDirIfDoesNotExistAsync(); - this._contractSourcesIfExists = await Compiler._getContractSourcesAsync(this._contractsDir); - const contractBaseNames = _.keys(this._contractSourcesIfExists); - const compiledContractPromises = _.map(contractBaseNames, async (contractBaseName: string): Promise<void> => { - return this._compileContractAsync(contractBaseName); - }); - await Promise.all(compiledContractPromises); + private _contractsDir: string; + private _networkId: number; + private _optimizerEnabled: number; + private _artifactsDir: string; + private _contractSourcesIfExists?: ContractSources; + private _solcErrors: Set<string>; + /** + * Recursively retrieves Solidity source code from directory. + * @param dirPath Directory to search. + * @return Mapping of contract name to contract source. + */ + private static async _getContractSourcesAsync(dirPath: string): Promise<ContractSources> { + let dirContents: string[] = []; + try { + dirContents = await fsWrapper.readdirAsync(dirPath); + } catch (err) { + throw new Error(`No directory found at ${dirPath}`); + } + let sources: ContractSources = {}; + for (const name of dirContents) { + const contentPath = `${dirPath}/${name}`; + if (path.extname(name) === SOLIDITY_FILE_EXTENSION) { + try { + const opts = { + encoding: 'utf8', + }; + sources[name] = await fsWrapper.readFileAsync(contentPath, opts); + utils.consoleLog(`Reading ${name} source...`); + } catch (err) { + utils.consoleLog(`Could not find file at ${contentPath}`); + } + } else { + try { + const nestedSources = await Compiler._getContractSourcesAsync(contentPath); + sources = { + ...sources, + ...nestedSources, + }; + } catch (err) { + utils.consoleLog(`${contentPath} is not a directory or ${SOLIDITY_FILE_EXTENSION} file`); + } + } + } + return sources; + } + /** + * Searches Solidity source code for compiler version. + * @param source Source code of contract. + * @return Solc compiler version. + */ + private static _parseSolidityVersion(source: string): string { + const solcVersionMatch = source.match(/(?:solidity\s\^?)([0-9]{1,2}[.][0-9]{1,2}[.][0-9]{1,2})/); + if (_.isNull(solcVersionMatch)) { + throw new Error('Could not find Solidity version in source'); + } + const solcVersion = solcVersionMatch[1]; + return solcVersion; + } + /** + * Normalizes the path found in the error message. + * Example: converts 'base/Token.sol:6:46: Warning: Unused local variable' + * to 'Token.sol:6:46: Warning: Unused local variable' + * This is used to prevent logging the same error multiple times. + * @param errMsg An error message from the compiled output. + * @return The error message with directories truncated from the contract path. + */ + private static _getNormalizedErrMsg(errMsg: string): string { + const errPathMatch = errMsg.match(/(.*\.sol)/); + if (_.isNull(errPathMatch)) { + throw new Error('Could not find a path in error message'); + } + const errPath = errPathMatch[0]; + const baseContract = path.basename(errPath); + const normalizedErrMsg = errMsg.replace(errPath, baseContract); + return normalizedErrMsg; + } + /** + * Instantiates a new instance of the Compiler class. + * @param opts Options specifying directories, network, and optimization settings. + * @return An instance of the Compiler class. + */ + constructor(opts: CompilerOptions) { + this._contractsDir = opts.contractsDir; + this._networkId = opts.networkId; + this._optimizerEnabled = opts.optimizerEnabled; + this._artifactsDir = opts.artifactsDir; + this._solcErrors = new Set(); + } + /** + * Compiles all Solidity files found in contractsDir and writes JSON artifacts to artifactsDir. + */ + public async compileAllAsync(): Promise<void> { + await this._createArtifactsDirIfDoesNotExistAsync(); + this._contractSourcesIfExists = await Compiler._getContractSourcesAsync(this._contractsDir); + const contractBaseNames = _.keys(this._contractSourcesIfExists); + const compiledContractPromises = _.map(contractBaseNames, async (contractBaseName: string): Promise<void> => { + return this._compileContractAsync(contractBaseName); + }); + await Promise.all(compiledContractPromises); - this._solcErrors.forEach(errMsg => { - utils.consoleLog(errMsg); - }); - } - /** - * Compiles contract and saves artifact to artifactsDir. - * @param contractBaseName Name of contract with '.sol' extension. - */ - private async _compileContractAsync(contractBaseName: string): Promise<void> { - if (_.isUndefined(this._contractSourcesIfExists)) { - throw new Error('Contract sources not yet initialized'); - } + this._solcErrors.forEach(errMsg => { + utils.consoleLog(errMsg); + }); + } + /** + * Compiles contract and saves artifact to artifactsDir. + * @param contractBaseName Name of contract with '.sol' extension. + */ + private async _compileContractAsync(contractBaseName: string): Promise<void> { + if (_.isUndefined(this._contractSourcesIfExists)) { + throw new Error('Contract sources not yet initialized'); + } - const source = this._contractSourcesIfExists[contractBaseName]; - const contractName = path.basename(contractBaseName, SOLIDITY_FILE_EXTENSION); - const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`; - const sourceHash = `0x${ethUtil.sha3(source).toString('hex')}`; + const source = this._contractSourcesIfExists[contractBaseName]; + const contractName = path.basename(contractBaseName, SOLIDITY_FILE_EXTENSION); + const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`; + const sourceHash = `0x${ethUtil.sha3(source).toString('hex')}`; - let currentArtifactString: string; - let currentArtifact: ContractArtifact; - let oldNetworks: ContractNetworks; - let shouldCompile: boolean; - try { - const opts = { - encoding: 'utf8', - }; - currentArtifactString = await fsWrapper.readFileAsync(currentArtifactPath, opts); - currentArtifact = JSON.parse(currentArtifactString); - oldNetworks = currentArtifact.networks; - const oldNetwork: ContractData = oldNetworks[this._networkId]; - shouldCompile = - _.isUndefined(oldNetwork) || - oldNetwork.keccak256 !== sourceHash || - oldNetwork.optimizer_enabled !== this._optimizerEnabled; - } catch (err) { - shouldCompile = true; - } + let currentArtifactString: string; + let currentArtifact: ContractArtifact; + let oldNetworks: ContractNetworks; + let shouldCompile: boolean; + try { + const opts = { + encoding: 'utf8', + }; + currentArtifactString = await fsWrapper.readFileAsync(currentArtifactPath, opts); + currentArtifact = JSON.parse(currentArtifactString); + oldNetworks = currentArtifact.networks; + const oldNetwork: ContractData = oldNetworks[this._networkId]; + shouldCompile = + _.isUndefined(oldNetwork) || + oldNetwork.keccak256 !== sourceHash || + oldNetwork.optimizer_enabled !== this._optimizerEnabled; + } catch (err) { + shouldCompile = true; + } - if (!shouldCompile) { - return; - } + if (!shouldCompile) { + return; + } - const input = { - [contractBaseName]: source, - }; - const solcVersion = Compiler._parseSolidityVersion(source); - const fullSolcVersion = binPaths[solcVersion]; - const solcBinPath = `./solc/solc_bin/${fullSolcVersion}`; - const solcBin = require(solcBinPath); - const solcInstance = solc.setupMethods(solcBin); + const input = { + [contractBaseName]: source, + }; + const solcVersion = Compiler._parseSolidityVersion(source); + const fullSolcVersion = binPaths[solcVersion]; + const solcBinPath = `./solc/solc_bin/${fullSolcVersion}`; + const solcBin = require(solcBinPath); + const solcInstance = solc.setupMethods(solcBin); - utils.consoleLog(`Compiling ${contractBaseName}...`); - const sourcesToCompile = { - sources: input, - }; - const compiled = solcInstance.compile( - sourcesToCompile, - this._optimizerEnabled, - this._findImportsIfSourcesExist.bind(this), - ); + utils.consoleLog(`Compiling ${contractBaseName}...`); + const sourcesToCompile = { + sources: input, + }; + const compiled = solcInstance.compile( + sourcesToCompile, + this._optimizerEnabled, + this._findImportsIfSourcesExist.bind(this), + ); - if (!_.isUndefined(compiled.errors)) { - _.each(compiled.errors, errMsg => { - const normalizedErrMsg = Compiler._getNormalizedErrMsg(errMsg); - this._solcErrors.add(normalizedErrMsg); - }); - } + if (!_.isUndefined(compiled.errors)) { + _.each(compiled.errors, errMsg => { + const normalizedErrMsg = Compiler._getNormalizedErrMsg(errMsg); + this._solcErrors.add(normalizedErrMsg); + }); + } - const contractIdentifier = `${contractBaseName}:${contractName}`; - const abi: Web3.ContractAbi = JSON.parse(compiled.contracts[contractIdentifier].interface); - const unlinked_binary = `0x${compiled.contracts[contractIdentifier].bytecode}`; - const updated_at = Date.now(); - const contractData: ContractData = { - solc_version: solcVersion, - keccak256: sourceHash, - optimizer_enabled: this._optimizerEnabled, - abi, - unlinked_binary, - updated_at, - }; + const contractIdentifier = `${contractBaseName}:${contractName}`; + const abi: Web3.ContractAbi = JSON.parse(compiled.contracts[contractIdentifier].interface); + const unlinked_binary = `0x${compiled.contracts[contractIdentifier].bytecode}`; + const updated_at = Date.now(); + const contractData: ContractData = { + solc_version: solcVersion, + keccak256: sourceHash, + optimizer_enabled: this._optimizerEnabled, + abi, + unlinked_binary, + updated_at, + }; - let newArtifact: ContractArtifact; - if (!_.isUndefined(currentArtifactString)) { - newArtifact = { - ...currentArtifact, - networks: { - ...oldNetworks, - [this._networkId]: contractData, - }, - }; - } else { - newArtifact = { - contract_name: contractName, - networks: { - [this._networkId]: contractData, - }, - }; - } + let newArtifact: ContractArtifact; + if (!_.isUndefined(currentArtifactString)) { + newArtifact = { + ...currentArtifact, + networks: { + ...oldNetworks, + [this._networkId]: contractData, + }, + }; + } else { + newArtifact = { + contract_name: contractName, + networks: { + [this._networkId]: contractData, + }, + }; + } - const artifactString = utils.stringifyWithFormatting(newArtifact); - await fsWrapper.writeFileAsync(currentArtifactPath, artifactString); - utils.consoleLog(`${contractBaseName} artifact saved!`); - } - /** - * Callback to resolve dependencies with `solc.compile`. - * Throws error if contractSources not yet initialized. - * @param importPath Path to an imported dependency. - * @return Import contents object containing source code of dependency. - */ - private _findImportsIfSourcesExist(importPath: string): ImportContents { - if (_.isUndefined(this._contractSourcesIfExists)) { - throw new Error('Contract sources not yet initialized'); - } - const contractBaseName = path.basename(importPath); - const source = this._contractSourcesIfExists[contractBaseName]; - const importContents: ImportContents = { - contents: source, - }; - return importContents; - } - /** - * Creates the artifacts directory if it does not already exist. - */ - private async _createArtifactsDirIfDoesNotExistAsync(): Promise<void> { - if (!fsWrapper.doesPathExistSync(this._artifactsDir)) { - utils.consoleLog('Creating artifacts directory...'); - await fsWrapper.mkdirAsync(this._artifactsDir); - } - } + const artifactString = utils.stringifyWithFormatting(newArtifact); + await fsWrapper.writeFileAsync(currentArtifactPath, artifactString); + utils.consoleLog(`${contractBaseName} artifact saved!`); + } + /** + * Callback to resolve dependencies with `solc.compile`. + * Throws error if contractSources not yet initialized. + * @param importPath Path to an imported dependency. + * @return Import contents object containing source code of dependency. + */ + private _findImportsIfSourcesExist(importPath: string): ImportContents { + if (_.isUndefined(this._contractSourcesIfExists)) { + throw new Error('Contract sources not yet initialized'); + } + const contractBaseName = path.basename(importPath); + const source = this._contractSourcesIfExists[contractBaseName]; + const importContents: ImportContents = { + contents: source, + }; + return importContents; + } + /** + * Creates the artifacts directory if it does not already exist. + */ + private async _createArtifactsDirIfDoesNotExistAsync(): Promise<void> { + if (!fsWrapper.doesPathExistSync(this._artifactsDir)) { + utils.consoleLog('Creating artifacts directory...'); + await fsWrapper.mkdirAsync(this._artifactsDir); + } + } } diff --git a/packages/deployer/src/deployer.ts b/packages/deployer/src/deployer.ts index 34e20a197..6f03581e8 100644 --- a/packages/deployer/src/deployer.ts +++ b/packages/deployer/src/deployer.ts @@ -13,168 +13,168 @@ import { utils } from './utils/utils'; const EXTRA_GAS = 200000; export class Deployer { - public web3Wrapper: Web3Wrapper; - private _artifactsDir: string; - private _jsonrpcPort: number; - private _networkId: number; - private _defaults: Partial<TxData>; + public web3Wrapper: Web3Wrapper; + private _artifactsDir: string; + private _jsonrpcPort: number; + private _networkId: number; + private _defaults: Partial<TxData>; - constructor(opts: DeployerOptions) { - this._artifactsDir = opts.artifactsDir; - this._jsonrpcPort = opts.jsonrpcPort; - this._networkId = opts.networkId; - const jsonrpcUrl = `http://localhost:${this._jsonrpcPort}`; - const web3Provider = new Web3.providers.HttpProvider(jsonrpcUrl); - this._defaults = opts.defaults; - this.web3Wrapper = new Web3Wrapper(web3Provider, this._defaults); - } - /** - * Loads contract artifact and deploys contract with given arguments. - * @param contractName Name of the contract to deploy. Must match name of an artifact in artifacts directory. - * @param args Array of contract constructor arguments. - * @return Deployed contract instance. - */ - public async deployAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> { - const contractArtifact: ContractArtifact = this._loadContractArtifactIfExists(contractName); - const contractData: ContractData = this._getContractDataFromArtifactIfExists(contractArtifact); - const data = contractData.unlinked_binary; - const from = await this._getFromAddressAsync(); - const gas = await this._getAllowableGasEstimateAsync(data); - const txData = { - gasPrice: this._defaults.gasPrice, - from, - data, - gas, - }; - const abi = contractData.abi; - const web3ContractInstance = await this._deployFromAbiAsync(abi, args, txData); - utils.consoleLog(`${contractName}.sol successfully deployed at ${web3ContractInstance.address}`); - const contractInstance = new Contract(web3ContractInstance, this._defaults); - return contractInstance; - } - /** - * Loads contract artifact, deploys with given arguments, and saves updated data to artifact. - * @param contractName Name of the contract to deploy. Must match name of an artifact in artifacts directory. - * @param args Array of contract constructor arguments. - * @return Deployed contract instance. - */ - public async deployAndSaveAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> { - const contractInstance = await this.deployAsync(contractName, args); - await this._saveContractDataToArtifactAsync(contractName, contractInstance.address, args); - return contractInstance; - } - /** - * Deploys a contract given its ABI, arguments, and transaction data. - * @param abi ABI of contract to deploy. - * @param args Constructor arguments to use in deployment. - * @param txData Tx options used for deployment. - * @return Promise that resolves to a web3 contract instance. - */ - private async _deployFromAbiAsync(abi: Web3.ContractAbi, args: any[], txData: Web3.TxData): Promise<any> { - const contract: Web3.Contract<Web3.ContractInstance> = this.web3Wrapper.getContractFromAbi(abi); - const deployPromise = new Promise((resolve, reject) => { - /** - * Contract is inferred as 'any' because TypeScript - * is not able to read 'new' from the Contract interface - */ - (contract as any).new(...args, txData, (err: Error, res: any): any => { - if (err) { - reject(err); - } else if (_.isUndefined(res.address) && !_.isUndefined(res.transactionHash)) { - utils.consoleLog(`transactionHash: ${res.transactionHash}`); - } else { - resolve(res); - } - }); - }); - return deployPromise; - } - /** - * Updates a contract artifact's address and encoded constructor arguments. - * @param contractName Name of contract. Must match an existing artifact. - * @param contractAddress Contract address to save to artifact. - * @param args Contract constructor arguments that will be encoded and saved to artifact. - */ - private async _saveContractDataToArtifactAsync( - contractName: string, - contractAddress: string, - args: any[], - ): Promise<void> { - const contractArtifact: ContractArtifact = this._loadContractArtifactIfExists(contractName); - const contractData: ContractData = this._getContractDataFromArtifactIfExists(contractArtifact); - const abi = contractData.abi; - const encodedConstructorArgs = encoder.encodeConstructorArgsFromAbi(args, abi); - const newContractData = { - ...contractData, - address: contractAddress, - constructor_args: encodedConstructorArgs, - }; - const newArtifact = { - ...contractArtifact, - networks: { - ...contractArtifact.networks, - [this._networkId]: newContractData, - }, - }; - const artifactString = utils.stringifyWithFormatting(newArtifact); - const artifactPath = `${this._artifactsDir}/${contractName}.json`; - await fsWrapper.writeFileAsync(artifactPath, artifactString); - } - /** - * Loads a contract artifact, if it exists. - * @param contractName Name of the contract, without the extension. - * @return The contract artifact. - */ - private _loadContractArtifactIfExists(contractName: string): ContractArtifact { - const artifactPath = `${this._artifactsDir}/${contractName}.json`; - try { - const contractArtifact: ContractArtifact = require(artifactPath); - return contractArtifact; - } catch (err) { - throw new Error(`Artifact not found for contract: ${contractName}`); - } - } - /** - * Gets data for current networkId stored in artifact. - * @param contractArtifact The contract artifact. - * @return Network specific contract data. - */ - private _getContractDataFromArtifactIfExists(contractArtifact: ContractArtifact): ContractData { - const contractData = contractArtifact.networks[this._networkId]; - if (_.isUndefined(contractData)) { - throw new Error(`Data not found in artifact for contract: ${contractArtifact.contract_name}`); - } - return contractData; - } - /** - * Gets the address to use for sending a transaction. - * @return The default from address. If not specified, returns the first address accessible by web3. - */ - private async _getFromAddressAsync(): Promise<string> { - let from: string; - if (_.isUndefined(this._defaults.from)) { - const accounts = await this.web3Wrapper.getAvailableAddressesAsync(); - from = accounts[0]; - } else { - from = this._defaults.from; - } - return from; - } - /** - * Estimates the gas required for a transaction. - * If gas would be over the block gas limit, the max allowable gas is returned instead. - * @param data Bytecode to estimate gas for. - * @return Gas estimate for transaction data. - */ - private async _getAllowableGasEstimateAsync(data: string): Promise<number> { - const block = await this.web3Wrapper.getBlockAsync('latest'); - let gas: number; - try { - const gasEstimate: number = await this.web3Wrapper.estimateGasAsync(data); - gas = Math.min(gasEstimate + EXTRA_GAS, block.gasLimit); - } catch (err) { - gas = block.gasLimit; - } - return gas; - } + constructor(opts: DeployerOptions) { + this._artifactsDir = opts.artifactsDir; + this._jsonrpcPort = opts.jsonrpcPort; + this._networkId = opts.networkId; + const jsonrpcUrl = `http://localhost:${this._jsonrpcPort}`; + const web3Provider = new Web3.providers.HttpProvider(jsonrpcUrl); + this._defaults = opts.defaults; + this.web3Wrapper = new Web3Wrapper(web3Provider, this._defaults); + } + /** + * Loads contract artifact and deploys contract with given arguments. + * @param contractName Name of the contract to deploy. Must match name of an artifact in artifacts directory. + * @param args Array of contract constructor arguments. + * @return Deployed contract instance. + */ + public async deployAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> { + const contractArtifact: ContractArtifact = this._loadContractArtifactIfExists(contractName); + const contractData: ContractData = this._getContractDataFromArtifactIfExists(contractArtifact); + const data = contractData.unlinked_binary; + const from = await this._getFromAddressAsync(); + const gas = await this._getAllowableGasEstimateAsync(data); + const txData = { + gasPrice: this._defaults.gasPrice, + from, + data, + gas, + }; + const abi = contractData.abi; + const web3ContractInstance = await this._deployFromAbiAsync(abi, args, txData); + utils.consoleLog(`${contractName}.sol successfully deployed at ${web3ContractInstance.address}`); + const contractInstance = new Contract(web3ContractInstance, this._defaults); + return contractInstance; + } + /** + * Loads contract artifact, deploys with given arguments, and saves updated data to artifact. + * @param contractName Name of the contract to deploy. Must match name of an artifact in artifacts directory. + * @param args Array of contract constructor arguments. + * @return Deployed contract instance. + */ + public async deployAndSaveAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> { + const contractInstance = await this.deployAsync(contractName, args); + await this._saveContractDataToArtifactAsync(contractName, contractInstance.address, args); + return contractInstance; + } + /** + * Deploys a contract given its ABI, arguments, and transaction data. + * @param abi ABI of contract to deploy. + * @param args Constructor arguments to use in deployment. + * @param txData Tx options used for deployment. + * @return Promise that resolves to a web3 contract instance. + */ + private async _deployFromAbiAsync(abi: Web3.ContractAbi, args: any[], txData: Web3.TxData): Promise<any> { + const contract: Web3.Contract<Web3.ContractInstance> = this.web3Wrapper.getContractFromAbi(abi); + const deployPromise = new Promise((resolve, reject) => { + /** + * Contract is inferred as 'any' because TypeScript + * is not able to read 'new' from the Contract interface + */ + (contract as any).new(...args, txData, (err: Error, res: any): any => { + if (err) { + reject(err); + } else if (_.isUndefined(res.address) && !_.isUndefined(res.transactionHash)) { + utils.consoleLog(`transactionHash: ${res.transactionHash}`); + } else { + resolve(res); + } + }); + }); + return deployPromise; + } + /** + * Updates a contract artifact's address and encoded constructor arguments. + * @param contractName Name of contract. Must match an existing artifact. + * @param contractAddress Contract address to save to artifact. + * @param args Contract constructor arguments that will be encoded and saved to artifact. + */ + private async _saveContractDataToArtifactAsync( + contractName: string, + contractAddress: string, + args: any[], + ): Promise<void> { + const contractArtifact: ContractArtifact = this._loadContractArtifactIfExists(contractName); + const contractData: ContractData = this._getContractDataFromArtifactIfExists(contractArtifact); + const abi = contractData.abi; + const encodedConstructorArgs = encoder.encodeConstructorArgsFromAbi(args, abi); + const newContractData = { + ...contractData, + address: contractAddress, + constructor_args: encodedConstructorArgs, + }; + const newArtifact = { + ...contractArtifact, + networks: { + ...contractArtifact.networks, + [this._networkId]: newContractData, + }, + }; + const artifactString = utils.stringifyWithFormatting(newArtifact); + const artifactPath = `${this._artifactsDir}/${contractName}.json`; + await fsWrapper.writeFileAsync(artifactPath, artifactString); + } + /** + * Loads a contract artifact, if it exists. + * @param contractName Name of the contract, without the extension. + * @return The contract artifact. + */ + private _loadContractArtifactIfExists(contractName: string): ContractArtifact { + const artifactPath = `${this._artifactsDir}/${contractName}.json`; + try { + const contractArtifact: ContractArtifact = require(artifactPath); + return contractArtifact; + } catch (err) { + throw new Error(`Artifact not found for contract: ${contractName}`); + } + } + /** + * Gets data for current networkId stored in artifact. + * @param contractArtifact The contract artifact. + * @return Network specific contract data. + */ + private _getContractDataFromArtifactIfExists(contractArtifact: ContractArtifact): ContractData { + const contractData = contractArtifact.networks[this._networkId]; + if (_.isUndefined(contractData)) { + throw new Error(`Data not found in artifact for contract: ${contractArtifact.contract_name}`); + } + return contractData; + } + /** + * Gets the address to use for sending a transaction. + * @return The default from address. If not specified, returns the first address accessible by web3. + */ + private async _getFromAddressAsync(): Promise<string> { + let from: string; + if (_.isUndefined(this._defaults.from)) { + const accounts = await this.web3Wrapper.getAvailableAddressesAsync(); + from = accounts[0]; + } else { + from = this._defaults.from; + } + return from; + } + /** + * Estimates the gas required for a transaction. + * If gas would be over the block gas limit, the max allowable gas is returned instead. + * @param data Bytecode to estimate gas for. + * @return Gas estimate for transaction data. + */ + private async _getAllowableGasEstimateAsync(data: string): Promise<number> { + const block = await this.web3Wrapper.getBlockAsync('latest'); + let gas: number; + try { + const gasEstimate: number = await this.web3Wrapper.estimateGasAsync(data); + gas = Math.min(gasEstimate + EXTRA_GAS, block.gasLimit); + } catch (err) { + gas = block.gasLimit; + } + return gas; + } } diff --git a/packages/deployer/src/globals.d.ts b/packages/deployer/src/globals.d.ts index d61f54870..0a35002da 100644 --- a/packages/deployer/src/globals.d.ts +++ b/packages/deployer/src/globals.d.ts @@ -1,8 +1,8 @@ declare module 'solc' { - export function compile(sources: any, optimizerEnabled: number, findImports: (importPath: string) => any): any; - export function setupMethods(solcBin: any): any; + export function compile(sources: any, optimizerEnabled: number, findImports: (importPath: string) => any): any; + export function setupMethods(solcBin: any): any; } declare module 'web3-eth-abi' { - export function encodeParameters(typesArray: string[], parameters: any[]): string; + export function encodeParameters(typesArray: string[], parameters: any[]): string; } diff --git a/packages/deployer/src/migrations/config/multisig_sample.ts b/packages/deployer/src/migrations/config/multisig_sample.ts index d65ab06d2..dabeb7a4f 100644 --- a/packages/deployer/src/migrations/config/multisig_sample.ts +++ b/packages/deployer/src/migrations/config/multisig_sample.ts @@ -2,9 +2,9 @@ import { MultiSigConfigByNetwork } from '../../types'; // Make a copy of this file named `multisig.js` and input custom params as needed export const multiSig: MultiSigConfigByNetwork = { - kovan: { - owners: [], - confirmationsRequired: 0, - secondsRequired: 0, - }, + kovan: { + owners: [], + confirmationsRequired: 0, + secondsRequired: 0, + }, }; diff --git a/packages/deployer/src/migrations/config/token_info.ts b/packages/deployer/src/migrations/config/token_info.ts index ebcd3f796..7e822fc3b 100644 --- a/packages/deployer/src/migrations/config/token_info.ts +++ b/packages/deployer/src/migrations/config/token_info.ts @@ -2,39 +2,39 @@ import { constants } from '../../utils/constants'; import { Token } from '../../types'; export const tokenInfo: Token[] = [ - { - name: 'Augur Reputation Token', - symbol: 'REP', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'Digix DAO Token', - symbol: 'DGD', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'Golem Network Token', - symbol: 'GNT', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'MakerDAO', - symbol: 'MKR', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'Melon Token', - symbol: 'MLN', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, + { + name: 'Augur Reputation Token', + symbol: 'REP', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + name: 'Digix DAO Token', + symbol: 'DGD', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + name: 'Golem Network Token', + symbol: 'GNT', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + name: 'MakerDAO', + symbol: 'MKR', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, + { + name: 'Melon Token', + symbol: 'MLN', + decimals: 18, + ipfsHash: constants.NULL_BYTES, + swarmHash: constants.NULL_BYTES, + }, ]; diff --git a/packages/deployer/src/migrations/migrate.ts b/packages/deployer/src/migrations/migrate.ts index 48cf93000..393806b45 100644 --- a/packages/deployer/src/migrations/migrate.ts +++ b/packages/deployer/src/migrations/migrate.ts @@ -7,84 +7,84 @@ import { constants } from '../utils/constants'; import { tokenInfo } from './config/token_info'; export const migrator = { - /** - * Custom migrations should be defined in this function. This will be called with the CLI 'migrate' command. - * Some operations might be completed in parallel, but we don't do that on purpose. - * That way the addresses are deterministic. - * @param deployer Deployer instance. - */ - async runMigrationsAsync(deployer: Deployer): Promise<void> { - const web3Wrapper: Web3Wrapper = deployer.web3Wrapper; - const accounts: string[] = await web3Wrapper.getAvailableAddressesAsync(); + /** + * Custom migrations should be defined in this function. This will be called with the CLI 'migrate' command. + * Some operations might be completed in parallel, but we don't do that on purpose. + * That way the addresses are deterministic. + * @param deployer Deployer instance. + */ + async runMigrationsAsync(deployer: Deployer): Promise<void> { + const web3Wrapper: Web3Wrapper = deployer.web3Wrapper; + const accounts: string[] = await web3Wrapper.getAvailableAddressesAsync(); - const tokenTransferProxy = await deployer.deployAndSaveAsync('TokenTransferProxy'); - const zrxToken = await deployer.deployAndSaveAsync('ZRXToken'); - const etherToken = await deployer.deployAndSaveAsync('WETH9'); - const tokenReg = await deployer.deployAndSaveAsync('TokenRegistry'); + const tokenTransferProxy = await deployer.deployAndSaveAsync('TokenTransferProxy'); + const zrxToken = await deployer.deployAndSaveAsync('ZRXToken'); + const etherToken = await deployer.deployAndSaveAsync('WETH9'); + const tokenReg = await deployer.deployAndSaveAsync('TokenRegistry'); - const exchangeArgs = [zrxToken.address, tokenTransferProxy.address]; - const owners = [accounts[0], accounts[1]]; - const confirmationsRequired = new BigNumber(2); - const secondsRequired = new BigNumber(0); - const multiSigArgs = [owners, confirmationsRequired, secondsRequired, tokenTransferProxy.address]; - const exchange = await deployer.deployAndSaveAsync('Exchange', exchangeArgs); - const multiSig = await deployer.deployAndSaveAsync( - 'MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', - multiSigArgs, - ); + const exchangeArgs = [zrxToken.address, tokenTransferProxy.address]; + const owners = [accounts[0], accounts[1]]; + const confirmationsRequired = new BigNumber(2); + const secondsRequired = new BigNumber(0); + const multiSigArgs = [owners, confirmationsRequired, secondsRequired, tokenTransferProxy.address]; + const exchange = await deployer.deployAndSaveAsync('Exchange', exchangeArgs); + const multiSig = await deployer.deployAndSaveAsync( + 'MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', + multiSigArgs, + ); - const owner = accounts[0]; - await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner }); - await tokenTransferProxy.transferOwnership.sendTransactionAsync(multiSig.address, { from: owner }); - const addTokenGasEstimate = await tokenReg.addToken.estimateGasAsync( - zrxToken.address, - tokenInfo[0].name, - tokenInfo[0].symbol, - tokenInfo[0].decimals, - tokenInfo[0].ipfsHash, - tokenInfo[0].swarmHash, - { from: owner }, - ); - await tokenReg.addToken.sendTransactionAsync( - zrxToken.address, - '0x Protocol Token', - 'ZRX', - 18, - constants.NULL_BYTES, - constants.NULL_BYTES, - { - from: owner, - gas: addTokenGasEstimate, - }, - ); - await tokenReg.addToken.sendTransactionAsync( - etherToken.address, - 'Ether Token', - 'WETH', - 18, - constants.NULL_BYTES, - constants.NULL_BYTES, - { - from: owner, - gas: addTokenGasEstimate, - }, - ); - for (const token of tokenInfo) { - const totalSupply = new BigNumber(0); - const args = [token.name, token.symbol, token.decimals, totalSupply]; - const dummyToken = await deployer.deployAsync('DummyToken', args); - await tokenReg.addToken.sendTransactionAsync( - dummyToken.address, - token.name, - token.symbol, - token.decimals, - token.ipfsHash, - token.swarmHash, - { - from: owner, - gas: addTokenGasEstimate, - }, - ); - } - }, + const owner = accounts[0]; + await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner }); + await tokenTransferProxy.transferOwnership.sendTransactionAsync(multiSig.address, { from: owner }); + const addTokenGasEstimate = await tokenReg.addToken.estimateGasAsync( + zrxToken.address, + tokenInfo[0].name, + tokenInfo[0].symbol, + tokenInfo[0].decimals, + tokenInfo[0].ipfsHash, + tokenInfo[0].swarmHash, + { from: owner }, + ); + await tokenReg.addToken.sendTransactionAsync( + zrxToken.address, + '0x Protocol Token', + 'ZRX', + 18, + constants.NULL_BYTES, + constants.NULL_BYTES, + { + from: owner, + gas: addTokenGasEstimate, + }, + ); + await tokenReg.addToken.sendTransactionAsync( + etherToken.address, + 'Ether Token', + 'WETH', + 18, + constants.NULL_BYTES, + constants.NULL_BYTES, + { + from: owner, + gas: addTokenGasEstimate, + }, + ); + for (const token of tokenInfo) { + const totalSupply = new BigNumber(0); + const args = [token.name, token.symbol, token.decimals, totalSupply]; + const dummyToken = await deployer.deployAsync('DummyToken', args); + await tokenReg.addToken.sendTransactionAsync( + dummyToken.address, + token.name, + token.symbol, + token.decimals, + token.ipfsHash, + token.swarmHash, + { + from: owner, + gas: addTokenGasEstimate, + }, + ); + } + }, }; diff --git a/packages/deployer/src/solc/bin_paths.ts b/packages/deployer/src/solc/bin_paths.ts index 7a22f911a..e5b107101 100644 --- a/packages/deployer/src/solc/bin_paths.ts +++ b/packages/deployer/src/solc/bin_paths.ts @@ -1,15 +1,15 @@ export interface BinaryPaths { - [key: string]: string; + [key: string]: string; } export const binPaths: BinaryPaths = { - '0.4.10': 'soljson-v0.4.10+commit.f0d539ae.js', - '0.4.11': 'soljson-v0.4.11+commit.68ef5810.js', - '0.4.12': 'soljson-v0.4.12+commit.194ff033.js', - '0.4.13': 'soljson-v0.4.13+commit.fb4cb1a.js', - '0.4.14': 'soljson-v0.4.14+commit.c2215d46.js', - '0.4.15': 'soljson-v0.4.15+commit.bbb8e64f.js', - '0.4.16': 'soljson-v0.4.16+commit.d7661dd9.js', - '0.4.17': 'soljson-v0.4.17+commit.bdeb9e52.js', - '0.4.18': 'soljson-v0.4.18+commit.9cf6e910.js', + '0.4.10': 'soljson-v0.4.10+commit.f0d539ae.js', + '0.4.11': 'soljson-v0.4.11+commit.68ef5810.js', + '0.4.12': 'soljson-v0.4.12+commit.194ff033.js', + '0.4.13': 'soljson-v0.4.13+commit.fb4cb1a.js', + '0.4.14': 'soljson-v0.4.14+commit.c2215d46.js', + '0.4.15': 'soljson-v0.4.15+commit.bbb8e64f.js', + '0.4.16': 'soljson-v0.4.16+commit.d7661dd9.js', + '0.4.17': 'soljson-v0.4.17+commit.bdeb9e52.js', + '0.4.18': 'soljson-v0.4.18+commit.9cf6e910.js', }; diff --git a/packages/deployer/src/types.ts b/packages/deployer/src/types.ts index aeff39d46..58d1e5b4f 100644 --- a/packages/deployer/src/types.ts +++ b/packages/deployer/src/types.ts @@ -1,23 +1,23 @@ export interface MultiSigConfig { - owners: string[]; - confirmationsRequired: number; - secondsRequired: number; + owners: string[]; + confirmationsRequired: number; + secondsRequired: number; } export interface MultiSigConfigByNetwork { - [networkName: string]: MultiSigConfig; + [networkName: string]: MultiSigConfig; } export interface Token { - address?: string; - name: string; - symbol: string; - decimals: number; - ipfsHash: string; - swarmHash: string; + address?: string; + name: string; + symbol: string; + decimals: number; + ipfsHash: string; + swarmHash: string; } export interface TokenInfoByNetwork { - development: Token[]; - live: Token[]; + development: Token[]; + live: Token[]; } diff --git a/packages/deployer/src/utils/constants.ts b/packages/deployer/src/utils/constants.ts index 9f22114e7..8871a470d 100644 --- a/packages/deployer/src/utils/constants.ts +++ b/packages/deployer/src/utils/constants.ts @@ -1,3 +1,3 @@ export const constants = { - NULL_BYTES: '0x', + NULL_BYTES: '0x', }; diff --git a/packages/deployer/src/utils/contract.ts b/packages/deployer/src/utils/contract.ts index 473ca95c8..546e82dfb 100644 --- a/packages/deployer/src/utils/contract.ts +++ b/packages/deployer/src/utils/contract.ts @@ -6,76 +6,76 @@ import * as Web3 from 'web3'; import { AbiType } from './types'; export class Contract implements Web3.ContractInstance { - public address: string; - public abi: Web3.ContractAbi; - private _contract: Web3.ContractInstance; - private _defaults: Partial<Web3.TxData>; - private _validator: SchemaValidator; - // This class instance is going to be populated with functions and events depending on the ABI - // and we don't know their types in advance - [name: string]: any; - constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<Web3.TxData>) { - this._contract = web3ContractInstance; - this.address = web3ContractInstance.address; - this.abi = web3ContractInstance.abi; - this._defaults = defaults; - this._populateEvents(); - this._populateFunctions(); - this._validator = new SchemaValidator(); - } - private _populateFunctions(): void { - const functionsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Function) as Web3.FunctionAbi[]; - _.forEach(functionsAbi, (functionAbi: Web3.MethodAbi) => { - if (functionAbi.constant) { - const cbStyleCallFunction = this._contract[functionAbi.name].call; - this[functionAbi.name] = { - callAsync: promisify(cbStyleCallFunction, this._contract), - }; - } else { - const cbStyleFunction = this._contract[functionAbi.name]; - const cbStyleEstimateGasFunction = this._contract[functionAbi.name].estimateGas; - this[functionAbi.name] = { - estimateGasAsync: promisify(cbStyleEstimateGasFunction, this._contract), - sendTransactionAsync: this._promisifyWithDefaultParams(cbStyleFunction), - }; - } - }); - } - private _populateEvents(): void { - const eventsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Event) as Web3.EventAbi[]; - _.forEach(eventsAbi, (eventAbi: Web3.EventAbi) => { - this[eventAbi.name] = this._contract[eventAbi.name]; - }); - } - private _promisifyWithDefaultParams(fn: (...args: any[]) => void): (...args: any[]) => Promise<any> { - const promisifiedWithDefaultParams = async (...args: any[]) => { - const promise = new Promise((resolve, reject) => { - const lastArg = args[args.length - 1]; - let txData: Partial<Web3.TxData> = {}; - if (this._isTxData(lastArg)) { - txData = args.pop(); - } - txData = { - ...this._defaults, - ...txData, - }; - const callback = (err: Error, data: any) => { - if (_.isNull(err)) { - resolve(data); - } else { - reject(err); - } - }; - args.push(txData); - args.push(callback); - fn.apply(this._contract, args); - }); - return promise; - }; - return promisifiedWithDefaultParams; - } - private _isTxData(lastArg: any): boolean { - const isValid = this._validator.isValid(lastArg, schemas.txDataSchema); - return isValid; - } + public address: string; + public abi: Web3.ContractAbi; + private _contract: Web3.ContractInstance; + private _defaults: Partial<Web3.TxData>; + private _validator: SchemaValidator; + // This class instance is going to be populated with functions and events depending on the ABI + // and we don't know their types in advance + [name: string]: any; + constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<Web3.TxData>) { + this._contract = web3ContractInstance; + this.address = web3ContractInstance.address; + this.abi = web3ContractInstance.abi; + this._defaults = defaults; + this._populateEvents(); + this._populateFunctions(); + this._validator = new SchemaValidator(); + } + private _populateFunctions(): void { + const functionsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Function) as Web3.FunctionAbi[]; + _.forEach(functionsAbi, (functionAbi: Web3.MethodAbi) => { + if (functionAbi.constant) { + const cbStyleCallFunction = this._contract[functionAbi.name].call; + this[functionAbi.name] = { + callAsync: promisify(cbStyleCallFunction, this._contract), + }; + } else { + const cbStyleFunction = this._contract[functionAbi.name]; + const cbStyleEstimateGasFunction = this._contract[functionAbi.name].estimateGas; + this[functionAbi.name] = { + estimateGasAsync: promisify(cbStyleEstimateGasFunction, this._contract), + sendTransactionAsync: this._promisifyWithDefaultParams(cbStyleFunction), + }; + } + }); + } + private _populateEvents(): void { + const eventsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Event) as Web3.EventAbi[]; + _.forEach(eventsAbi, (eventAbi: Web3.EventAbi) => { + this[eventAbi.name] = this._contract[eventAbi.name]; + }); + } + private _promisifyWithDefaultParams(fn: (...args: any[]) => void): (...args: any[]) => Promise<any> { + const promisifiedWithDefaultParams = async (...args: any[]) => { + const promise = new Promise((resolve, reject) => { + const lastArg = args[args.length - 1]; + let txData: Partial<Web3.TxData> = {}; + if (this._isTxData(lastArg)) { + txData = args.pop(); + } + txData = { + ...this._defaults, + ...txData, + }; + const callback = (err: Error, data: any) => { + if (_.isNull(err)) { + resolve(data); + } else { + reject(err); + } + }; + args.push(txData); + args.push(callback); + fn.apply(this._contract, args); + }); + return promise; + }; + return promisifiedWithDefaultParams; + } + private _isTxData(lastArg: any): boolean { + const isValid = this._validator.isValid(lastArg, schemas.txDataSchema); + return isValid; + } } diff --git a/packages/deployer/src/utils/encoder.ts b/packages/deployer/src/utils/encoder.ts index 30772b4bc..d5f807774 100644 --- a/packages/deployer/src/utils/encoder.ts +++ b/packages/deployer/src/utils/encoder.ts @@ -5,16 +5,16 @@ import * as web3Abi from 'web3-eth-abi'; import { AbiType } from './types'; export const encoder = { - encodeConstructorArgsFromAbi(args: any[], abi: Web3.ContractAbi): string { - const constructorTypes: string[] = []; - _.each(abi, (element: Web3.AbiDefinition) => { - if (element.type === AbiType.Constructor) { - _.each(element.inputs, (input: Web3.FunctionParameter) => { - constructorTypes.push(input.type); - }); - } - }); - const encodedParameters = web3Abi.encodeParameters(constructorTypes, args); - return encodedParameters; - }, + encodeConstructorArgsFromAbi(args: any[], abi: Web3.ContractAbi): string { + const constructorTypes: string[] = []; + _.each(abi, (element: Web3.AbiDefinition) => { + if (element.type === AbiType.Constructor) { + _.each(element.inputs, (input: Web3.FunctionParameter) => { + constructorTypes.push(input.type); + }); + } + }); + const encodedParameters = web3Abi.encodeParameters(constructorTypes, args); + return encodedParameters; + }, }; diff --git a/packages/deployer/src/utils/fs_wrapper.ts b/packages/deployer/src/utils/fs_wrapper.ts index 42bdc9969..34c7caa0e 100644 --- a/packages/deployer/src/utils/fs_wrapper.ts +++ b/packages/deployer/src/utils/fs_wrapper.ts @@ -2,10 +2,10 @@ import { promisify } from '@0xproject/utils'; import * as fs from 'fs'; export const fsWrapper = { - readdirAsync: promisify<string[]>(fs.readdir), - readFileAsync: promisify<string>(fs.readFile), - writeFileAsync: promisify<undefined>(fs.writeFile), - mkdirAsync: promisify<undefined>(fs.mkdir), - doesPathExistSync: fs.existsSync, - removeFileAsync: promisify<undefined>(fs.unlink), + readdirAsync: promisify<string[]>(fs.readdir), + readFileAsync: promisify<string>(fs.readFile), + writeFileAsync: promisify<undefined>(fs.writeFile), + mkdirAsync: promisify<undefined>(fs.mkdir), + doesPathExistSync: fs.existsSync, + removeFileAsync: promisify<undefined>(fs.unlink), }; diff --git a/packages/deployer/src/utils/types.ts b/packages/deployer/src/utils/types.ts index 9e1378e24..e054b9cc2 100644 --- a/packages/deployer/src/utils/types.ts +++ b/packages/deployer/src/utils/types.ts @@ -3,95 +3,95 @@ import * as Web3 from 'web3'; import * as yargs from 'yargs'; export enum AbiType { - Function = 'function', - Constructor = 'constructor', - Event = 'event', - Fallback = 'fallback', + Function = 'function', + Constructor = 'constructor', + Event = 'event', + Fallback = 'fallback', } export interface ContractArtifact { - contract_name: string; - networks: ContractNetworks; + contract_name: string; + networks: ContractNetworks; } export interface ContractNetworks { - [key: number]: ContractData; + [key: number]: ContractData; } export interface ContractData { - solc_version: string; - optimizer_enabled: number; - keccak256: string; - abi: Web3.ContractAbi; - unlinked_binary: string; - address?: string; - constructor_args?: string; - updated_at: number; + solc_version: string; + optimizer_enabled: number; + keccak256: string; + abi: Web3.ContractAbi; + unlinked_binary: string; + address?: string; + constructor_args?: string; + updated_at: number; } export interface SolcErrors { - [key: string]: boolean; + [key: string]: boolean; } export interface CliOptions extends yargs.Arguments { - artifactsDir: string; - contractsDir: string; - jsonrpcPort: number; - networkId: number; - shouldOptimize: boolean; - gasPrice: string; - account?: string; - contract?: string; - args?: string; + artifactsDir: string; + contractsDir: string; + jsonrpcPort: number; + networkId: number; + shouldOptimize: boolean; + gasPrice: string; + account?: string; + contract?: string; + args?: string; } export interface CompilerOptions { - contractsDir: string; - networkId: number; - optimizerEnabled: number; - artifactsDir: string; + contractsDir: string; + networkId: number; + optimizerEnabled: number; + artifactsDir: string; } export interface DeployerOptions { - artifactsDir: string; - jsonrpcPort: number; - networkId: number; - defaults: Partial<TxData>; + artifactsDir: string; + jsonrpcPort: number; + networkId: number; + defaults: Partial<TxData>; } export interface ContractSources { - [key: string]: string; + [key: string]: string; } export interface ImportContents { - contents: string; + contents: string; } // TODO: Consolidate with 0x.js definitions once types are moved into a separate package. export enum ZeroExError { - ContractDoesNotExist = 'CONTRACT_DOES_NOT_EXIST', - ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST', - UnhandledError = 'UNHANDLED_ERROR', - UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', - InvalidSignature = 'INVALID_SIGNATURE', - ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK', - InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER', - InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER', - InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT', - InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL', - InvalidJump = 'INVALID_JUMP', - OutOfGas = 'OUT_OF_GAS', - NoNetworkId = 'NO_NETWORK_ID', - SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND', + ContractDoesNotExist = 'CONTRACT_DOES_NOT_EXIST', + ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST', + UnhandledError = 'UNHANDLED_ERROR', + UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', + InvalidSignature = 'INVALID_SIGNATURE', + ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK', + InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER', + InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER', + InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT', + InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL', + InvalidJump = 'INVALID_JUMP', + OutOfGas = 'OUT_OF_GAS', + NoNetworkId = 'NO_NETWORK_ID', + SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND', } export interface Token { - address?: string; - name: string; - symbol: string; - decimals: number; - ipfsHash: string; - swarmHash: string; + address?: string; + name: string; + symbol: string; + decimals: number; + ipfsHash: string; + swarmHash: string; } export type DoneCallback = (err?: Error) => void; diff --git a/packages/deployer/src/utils/utils.ts b/packages/deployer/src/utils/utils.ts index 931ad6f73..4390d8813 100644 --- a/packages/deployer/src/utils/utils.ts +++ b/packages/deployer/src/utils/utils.ts @@ -1,13 +1,13 @@ export const utils = { - consoleLog(message: string): void { - /* tslint:disable */ - console.log(message); - /* tslint:enable */ - }, - stringifyWithFormatting(obj: any): string { - const jsonReplacer: null = null; - const numberOfJsonSpaces = 4; - const stringifiedObj = JSON.stringify(obj, jsonReplacer, numberOfJsonSpaces); - return stringifiedObj; - }, + consoleLog(message: string): void { + /* tslint:disable */ + console.log(message); + /* tslint:enable */ + }, + stringifyWithFormatting(obj: any): string { + const jsonReplacer: null = null; + const numberOfJsonSpaces = 4; + const stringifiedObj = JSON.stringify(obj, jsonReplacer, numberOfJsonSpaces); + return stringifiedObj; + }, }; diff --git a/packages/deployer/test/deploy_test.ts b/packages/deployer/test/deploy_test.ts index 583601154..5df729a04 100644 --- a/packages/deployer/test/deploy_test.ts +++ b/packages/deployer/test/deploy_test.ts @@ -13,79 +13,79 @@ const artifactsDir = `${__dirname}/fixtures/artifacts`; const contractsDir = `${__dirname}/fixtures/contracts`; const exchangeArtifactPath = `${artifactsDir}/Exchange.json`; const compilerOpts: CompilerOptions = { - artifactsDir, - contractsDir, - networkId: constants.networkId, - optimizerEnabled: constants.optimizerEnabled, + artifactsDir, + contractsDir, + networkId: constants.networkId, + optimizerEnabled: constants.optimizerEnabled, }; const compiler = new Compiler(compilerOpts); const deployerOpts = { - artifactsDir, - networkId: constants.networkId, - jsonrpcPort: constants.jsonrpcPort, - defaults: { - gasPrice: constants.gasPrice, - }, + artifactsDir, + networkId: constants.networkId, + jsonrpcPort: constants.jsonrpcPort, + defaults: { + gasPrice: constants.gasPrice, + }, }; const deployer = new Deployer(deployerOpts); /* tslint:disable */ beforeEach(function(done: DoneCallback) { - this.timeout(constants.timeoutMs); - (async () => { - if (fsWrapper.doesPathExistSync(exchangeArtifactPath)) { - await fsWrapper.removeFileAsync(exchangeArtifactPath); - } - await compiler.compileAllAsync(); - done(); - })().catch(done); + this.timeout(constants.timeoutMs); + (async () => { + if (fsWrapper.doesPathExistSync(exchangeArtifactPath)) { + await fsWrapper.removeFileAsync(exchangeArtifactPath); + } + await compiler.compileAllAsync(); + done(); + })().catch(done); }); /* tslint:enable */ describe('#Compiler', () => { - it('should create an Exchange artifact with the correct unlinked binary', async () => { - const opts = { - encoding: 'utf8', - }; - const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts); - const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString); - const exchangeContractData: ContractData = exchangeArtifact.networks[constants.networkId]; - // The last 43 bytes of the binaries are metadata which may not be equivalent - const unlinkedBinaryWithoutMetadata = exchangeContractData.unlinked_binary.slice(0, -86); - const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -86); - expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata); - }); + it('should create an Exchange artifact with the correct unlinked binary', async () => { + const opts = { + encoding: 'utf8', + }; + const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts); + const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString); + const exchangeContractData: ContractData = exchangeArtifact.networks[constants.networkId]; + // The last 43 bytes of the binaries are metadata which may not be equivalent + const unlinkedBinaryWithoutMetadata = exchangeContractData.unlinked_binary.slice(0, -86); + const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -86); + expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata); + }); }); describe('#Deployer', () => { - describe('#deployAsync', () => { - it('should deploy the Exchange contract without updating the Exchange artifact', async () => { - const exchangeConstructorArgs = [constants.zrxTokenAddress, constants.tokenTransferProxyAddress]; - const exchangeContractInstance = await deployer.deployAsync('Exchange', exchangeConstructorArgs); - const opts = { - encoding: 'utf8', - }; - const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts); - const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString); - const exchangeContractData: ContractData = exchangeArtifact.networks[constants.networkId]; - const exchangeAddress = exchangeContractInstance.address; - expect(exchangeAddress).to.not.equal(undefined); - expect(exchangeContractData.address).to.equal(undefined); - expect(exchangeContractData.constructor_args).to.equal(undefined); - }); - }); - describe('#deployAndSaveAsync', () => { - it('should save the correct contract address and constructor arguments to the Exchange artifact', async () => { - const exchangeConstructorArgs = [constants.zrxTokenAddress, constants.tokenTransferProxyAddress]; - const exchangeContractInstance = await deployer.deployAndSaveAsync('Exchange', exchangeConstructorArgs); - const opts = { - encoding: 'utf8', - }; - const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts); - const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString); - const exchangeContractData: ContractData = exchangeArtifact.networks[constants.networkId]; - const exchangeAddress = exchangeContractInstance.address; - expect(exchangeAddress).to.be.equal(exchangeContractData.address); - expect(constructor_args).to.be.equal(exchangeContractData.constructor_args); - }); - }); + describe('#deployAsync', () => { + it('should deploy the Exchange contract without updating the Exchange artifact', async () => { + const exchangeConstructorArgs = [constants.zrxTokenAddress, constants.tokenTransferProxyAddress]; + const exchangeContractInstance = await deployer.deployAsync('Exchange', exchangeConstructorArgs); + const opts = { + encoding: 'utf8', + }; + const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts); + const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString); + const exchangeContractData: ContractData = exchangeArtifact.networks[constants.networkId]; + const exchangeAddress = exchangeContractInstance.address; + expect(exchangeAddress).to.not.equal(undefined); + expect(exchangeContractData.address).to.equal(undefined); + expect(exchangeContractData.constructor_args).to.equal(undefined); + }); + }); + describe('#deployAndSaveAsync', () => { + it('should save the correct contract address and constructor arguments to the Exchange artifact', async () => { + const exchangeConstructorArgs = [constants.zrxTokenAddress, constants.tokenTransferProxyAddress]; + const exchangeContractInstance = await deployer.deployAndSaveAsync('Exchange', exchangeConstructorArgs); + const opts = { + encoding: 'utf8', + }; + const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts); + const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString); + const exchangeContractData: ContractData = exchangeArtifact.networks[constants.networkId]; + const exchangeAddress = exchangeContractInstance.address; + expect(exchangeAddress).to.be.equal(exchangeContractData.address); + expect(constructor_args).to.be.equal(exchangeContractData.constructor_args); + }); + }); }); diff --git a/packages/deployer/test/fixtures/exchange_bin.ts b/packages/deployer/test/fixtures/exchange_bin.ts index 9c58b5e02..a6eae515e 100644 --- a/packages/deployer/test/fixtures/exchange_bin.ts +++ b/packages/deployer/test/fixtures/exchange_bin.ts @@ -1,4 +1,4 @@ export const constructor_args = - '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000008da0d80f5007ef1e431dd2127178d224e32c2ef4'; + '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000008da0d80f5007ef1e431dd2127178d224e32c2ef4'; export const exchange_binary = - '0x6060604052341561000f57600080fd5b604051604080612c4d833981016040528080519060200190919080519060200190919050505b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b50505b612b84806100c96000396000f300606060405236156100fa576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806314df96ee146100ff578063288cdc911461014c5780632ac1262214610187578063363349be146101c2578063394c21e7146103bc5780633b30ba591461044b5780634f150787146104a0578063741bcc93146106b25780637e9abb50146107535780638163681e1461078e57806398024a8b14610812578063add1cbc51461085b578063b7b2c7d6146108b0578063baa0181d14610acd578063bc61394a14610c1f578063cfc4d0ec14610cdf578063f06bbf7514610d6d578063ffa1ad7414610d9e575b600080fd5b341561010a57600080fd5b6101326004808035906020019091908035906020019091908035906020019091905050610e2d565b604051808215151515815260200191505060405180910390f35b341561015757600080fd5b610171600480803560001916906020019091905050610e7c565b6040518082815260200191505060405180910390f35b341561019257600080fd5b6101ac600480803560001916906020019091905050610e94565b6040518082815260200191505060405180910390f35b34156101cd57600080fd5b6103a660048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b8282101561024857848483905060a002016005806020026040519081016040528092919082600560200280828437820191505050505081526020019060010190610203565b5050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b828210156102c457848483905060c00201600680602002604051908101604052809291908260066020028082843782019150505050508152602001906001019061027f565b5050505050919080359060200190919080351515906020019091908035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437820191505050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091905050610eac565b6040518082815260200191505060405180910390f35b34156103c757600080fd5b6104356004808060a001906005806020026040519081016040528092919082600560200280828437820191505050505091908060c001906006806020026040519081016040528092919082600660200280828437820191505050505091908035906020019091905050611013565b6040518082815260200191505060405180910390f35b341561045657600080fd5b61045e6114fb565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156104ab57600080fd5b6106b060048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b8282101561052657848483905060a0020160058060200260405190810160405280929190826005602002808284378201915050505050815260200190600101906104e1565b5050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b828210156105a257848483905060c00201600680602002604051908101604052809291908260066020028082843782019150505050508152602001906001019061055d565b50505050509190803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091908035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437820191505050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091905050611520565b005b34156106bd57600080fd5b6107516004808060a001906005806020026040519081016040528092919082600560200280828437820191505050505091908060c00190600680602002604051908101604052809291908260066020028082843782019150505050509190803590602001909190803560ff1690602001909190803560001916906020019091908035600019169060200190919050506115df565b005b341561075e57600080fd5b610778600480803560001916906020019091905050611605565b6040518082815260200191505060405180910390f35b341561079957600080fd5b6107f8600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080356000191690602001909190803560ff16906020019091908035600019169060200190919080356000191690602001909190505061164f565b604051808215151515815260200191505060405180910390f35b341561081d57600080fd5b6108456004808035906020019091908035906020019091908035906020019091905050611757565b6040518082815260200191505060405180910390f35b341561086657600080fd5b61086e611776565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156108bb57600080fd5b610acb60048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b8282101561093657848483905060a0020160058060200260405190810160405280929190826005602002808284378201915050505050815260200190600101906108f1565b5050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b828210156109b257848483905060c00201600680602002604051908101604052809291908260066020028082843782019150505050508152602001906001019061096d565b50505050509190803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091908035151590602001909190803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091908035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437820191505050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190505061179c565b005b3415610ad857600080fd5b610c1d60048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b82821015610b5357848483905060a002016005806020026040519081016040528092919082600560200280828437820191505050505081526020019060010190610b0e565b5050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b82821015610bcf57848483905060c002016006806020026040519081016040528092919082600660200280828437820191505050505081526020019060010190610b8a565b5050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190505061185e565b005b3415610c2a57600080fd5b610cc96004808060a001906005806020026040519081016040528092919082600560200280828437820191505050505091908060c001906006806020026040519081016040528092919082600660200280828437820191505050505091908035906020019091908035151590602001909190803560ff1690602001909190803560001916906020019091908035600019169060200190919050506118d3565b6040518082815260200191505060405180910390f35b3415610cea57600080fd5b610d4f6004808060a001906005806020026040519081016040528092919082600560200280828437820191505050505091908060c001906006806020026040519081016040528092919082600660200280828437820191505050505091905050612073565b60405180826000191660001916815260200191505060405180910390f35b3415610d7857600080fd5b610d8061231f565b604051808261ffff1661ffff16815260200191505060405180910390f35b3415610da957600080fd5b610db1612325565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610df25780820151818401525b602081019050610dd6565b50505050905090810190601f168015610e1f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60008060008486850991506000821415610e4a5760009250610e73565b610e69610e5a83620f424061235e565b610e64888761235e565b612392565b90506103e8811192505b50509392505050565b60026020528060005260406000206000915090505481565b60036020528060005260406000206000915090505481565b6000806000809150600090505b895181101561100257896000815181101515610ed157fe5b906020019060200201516003600581101515610ee957fe5b602002015173ffffffffffffffffffffffffffffffffffffffff168a82815181101515610f1257fe5b906020019060200201516003600581101515610f2a57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff16141515610f5157600080fd5b610fe582610fe08c84815181101515610f6657fe5b906020019060200201518c85815181101515610f7e57fe5b90602001906020020151610f928d886123ae565b8c8c88815181101515610fa157fe5b906020019060200201518c89815181101515610fb957fe5b906020019060200201518c8a815181101515610fd157fe5b906020019060200201516118d3565b6123c8565b915087821415610ff457611002565b5b8080600101915050610eb9565b8192505b5050979650505050505050565b600061101d612a8c565b6000806101606040519081016040528088600060058110151561103c57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff16815260200188600160058110151561106b57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff16815260200188600260058110151561109a57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018860036005811015156110c957fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018860046005811015156110f857fe5b602002015173ffffffffffffffffffffffffffffffffffffffff16815260200187600060068110151561112757fe5b6020020151815260200187600160068110151561114057fe5b6020020151815260200187600260068110151561115957fe5b6020020151815260200187600360068110151561117257fe5b6020020151815260200187600460068110151561118b57fe5b6020020151815260200161119f8989612073565b6000191681525092503373ffffffffffffffffffffffffffffffffffffffff16836000015173ffffffffffffffffffffffffffffffffffffffff161415156111e657600080fd5b60008360a001511180156111fe575060008360c00151115b801561120a5750600085115b151561121557600080fd5b8261012001514210151561127257826101400151600019166000600381111561123a57fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a3600093506114f1565b61128d8360c00151611288856101400151611605565b6123ae565b915061129985836123e7565b905060008114156112f35782610140015160001916600160038111156112bb57fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a3600093506114f1565b61131d600360008561014001516000191660001916815260200190815260200160002054826123c8565b60036000856101400151600019166000191681526020019081526020016000208190555082604001518360600151604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c0100000000000000000000000002815260140192505050604051809103902060001916836080015173ffffffffffffffffffffffffffffffffffffffff16846000015173ffffffffffffffffffffffffffffffffffffffff167f67d66f160bc93d925d05dae1794c90d2d6d6688b29b84ff069398a9b0458713186604001518760600151611455878a60c001518b60a00151611757565b878a6101400151604051808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200184815260200183815260200182600019166000191681526020019550505050505060405180910390a48093505b5050509392505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008090505b86518110156115d5576115c7878281518110151561154057fe5b90602001906020020151878381518110151561155857fe5b90602001906020020151878481518110151561157057fe5b90602001906020020151878581518110151561158857fe5b9060200190602002015187868151811015156115a057fe5b9060200190602002015187878151811015156115b857fe5b906020019060200201516115df565b5b8080600101915050611526565b5b50505050505050565b836115f087878760008888886118d3565b1415156115fc57600080fd5b5b505050505050565b600061164760026000846000191660001916815260200190815260200160002054600360008560001916600019168152602001908152602001600020546123c8565b90505b919050565b600060018560405180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c0182600019166000191681526020019150506040518091039020858585604051600081526020016040526000604051602001526040518085600019166000191681526020018460ff1660ff16815260200183600019166000191681526020018260001916600019168152602001945050505050602060405160208103908084039060008661646e5a03f1151561171457600080fd5b50506020604051035173ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161490505b95945050505050565b600061176c611766858461235e565b84612392565b90505b9392505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008090505b87518110156118535761184488828151811015156117bc57fe5b9060200190602002015188838151811015156117d457fe5b9060200190602002015188848151811015156117ec57fe5b9060200190602002015188888681518110151561180557fe5b90602001906020020151888781518110151561181d57fe5b90602001906020020151888881518110151561183557fe5b906020019060200201516118d3565b505b80806001019150506117a2565b5b5050505050505050565b60008090505b83518110156118cc576118bd848281518110151561187e57fe5b90602001906020020151848381518110151561189657fe5b9060200190602002015184848151811015156118ae57fe5b90602001906020020151611013565b505b8080600101915050611864565b5b50505050565b60006118dd612a8c565b600080600080610160604051908101604052808e60006005811015156118ff57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018e600160058110151561192e57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018e600260058110151561195d57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018e600360058110151561198c57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018e60046005811015156119bb57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018d60006006811015156119ea57fe5b602002015181526020018d6001600681101515611a0357fe5b602002015181526020018d6002600681101515611a1c57fe5b602002015181526020018d6003600681101515611a3557fe5b602002015181526020018d6004600681101515611a4e57fe5b60200201518152602001611a628f8f612073565b600019168152509450600073ffffffffffffffffffffffffffffffffffffffff16856020015173ffffffffffffffffffffffffffffffffffffffff161480611ad957503373ffffffffffffffffffffffffffffffffffffffff16856020015173ffffffffffffffffffffffffffffffffffffffff16145b1515611ae457600080fd5b60008560a00151118015611afc575060008560c00151115b8015611b08575060008b115b1515611b1357600080fd5b611b2985600001518661014001518b8b8b61164f565b1515611b3457600080fd5b84610120015142101515611b91578461014001516000191660006003811115611b5957fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a360009550612063565b611bac8560c00151611ba7876101400151611605565b6123ae565b9350611bb88b856123e7565b95506000861415611c12578461014001516000191660016003811115611bda57fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a360009550612063565b611c25868660c001518760a00151610e2d565b15611c79578461014001516000191660026003811115611c4157fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a360009550612063565b89158015611c8e5750611c8c8587612401565b155b15611ce15784610140015160001916600380811115611ca957fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a360009550612063565b611cf4868660c001518760a00151611757565b9250611d20600260008761014001516000191660001916815260200190815260200160002054876123c8565b600260008761014001516000191660001916815260200190815260200160002081905550611d58856040015186600001513386612751565b1515611d6357600080fd5b611d77856060015133876000015189612751565b1515611d8257600080fd5b600073ffffffffffffffffffffffffffffffffffffffff16856080015173ffffffffffffffffffffffffffffffffffffffff16141515611e815760008560e001511115611e1f57611ddc868660c001518760e00151611757565b9150611e136000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168660000151876080015185612751565b1515611e1e57600080fd5b5b60008561010001511115611e8057611e41868660c00151876101000151611757565b9050611e746000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1633876080015184612751565b1515611e7f57600080fd5b5b5b84604001518560600151604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c0100000000000000000000000002815260140192505050604051809103902060001916856080015173ffffffffffffffffffffffffffffffffffffffff16866000015173ffffffffffffffffffffffffffffffffffffffff167f0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb33389604001518a60600151898d8a8a8f6101400151604051808973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200186815260200185815260200184815260200183815260200182600019166000191681526020019850505050505050505060405180910390a48595505b5050505050979650505050505050565b60003083600060058110151561208557fe5b602002015184600160058110151561209957fe5b60200201518560026005811015156120ad57fe5b60200201518660036005811015156120c157fe5b60200201518760046005811015156120d557fe5b60200201518760006006811015156120e957fe5b60200201518860016006811015156120fd57fe5b602002015189600260068110151561211157fe5b60200201518a600360068110151561212557fe5b60200201518b600460068110151561213957fe5b60200201518c600560068110151561214d57fe5b6020020151604051808d73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018781526020018681526020018581526020018481526020018381526020018281526020019c50505050505050505050505050604051809103902090505b92915050565b61138781565b6040805190810160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6000808284029050600084148061237f575082848281151561237c57fe5b04145b151561238757fe5b8091505b5092915050565b60008082848115156123a057fe5b0490508091505b5092915050565b60008282111515156123bc57fe5b81830390505b92915050565b60008082840190508381101515156123dc57fe5b8091505b5092915050565b60008183106123f657816123f8565b825b90505b92915050565b60008060008060008060008060003397506124258a8c60c001518d60a00151611757565b9650600073ffffffffffffffffffffffffffffffffffffffff168b6080015173ffffffffffffffffffffffffffffffffffffffff161415156126d2576000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168b6040015173ffffffffffffffffffffffffffffffffffffffff161495506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168b6060015173ffffffffffffffffffffffffffffffffffffffff161494506125208a8c60c001518d60e00151611757565b93506125368a8c60c001518d6101000151611757565b925085612543578361254e565b61254d87856123c8565b5b91508461255b5782612566565b6125658a846123c8565b5b9050816125986000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168d600001516128ae565b10806125d15750816125cf6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168d60000151612972565b105b806126055750806126036000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168a6128ae565b105b806126395750806126376000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168a612972565b105b156126475760009850612743565b851580156126805750866126638c604001518d600001516128ae565b108061267f57508661267d8c604001518d60000151612972565b105b5b1561268e5760009850612743565b841580156126bf5750896126a68c606001518a6128ae565b10806126be5750896126bc8c606001518a612972565b105b5b156126cd5760009850612743565b61273e565b866126e58c604001518d600001516128ae565b10806127015750866126ff8c604001518d60000151612972565b105b806127185750896127168c606001518a6128ae565b105b8061272f57508961272d8c606001518a612972565b105b1561273d5760009850612743565b5b600198505b505050505050505092915050565b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166315dacbea868686866000604051602001526040518563ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001945050505050602060405180830381600087803b151561288857600080fd5b6102c65a03f1151561289957600080fd5b5050506040518051905090505b949350505050565b60008273ffffffffffffffffffffffffffffffffffffffff166370a0823161138761ffff16846040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600088803b151561295157600080fd5b87f1151561295e57600080fd5b505050506040518051905090505b92915050565b60008273ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e61138761ffff1684600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040518463ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200192505050602060405180830381600088803b1515612a6b57600080fd5b87f11515612a7857600080fd5b505050506040518051905090505b92915050565b61016060405190810160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff16815260200160008152602001600081526020016000815260200160008152602001600081526020016000801916815250905600a165627a7a72305820df5cabdc3a116e993e10bfb14823d18d9b798038d4c463a1703f9a584c456b7e0029'; + '0x6060604052341561000f57600080fd5b604051604080612c4d833981016040528080519060200190919080519060200190919050505b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b50505b612b84806100c96000396000f300606060405236156100fa576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806314df96ee146100ff578063288cdc911461014c5780632ac1262214610187578063363349be146101c2578063394c21e7146103bc5780633b30ba591461044b5780634f150787146104a0578063741bcc93146106b25780637e9abb50146107535780638163681e1461078e57806398024a8b14610812578063add1cbc51461085b578063b7b2c7d6146108b0578063baa0181d14610acd578063bc61394a14610c1f578063cfc4d0ec14610cdf578063f06bbf7514610d6d578063ffa1ad7414610d9e575b600080fd5b341561010a57600080fd5b6101326004808035906020019091908035906020019091908035906020019091905050610e2d565b604051808215151515815260200191505060405180910390f35b341561015757600080fd5b610171600480803560001916906020019091905050610e7c565b6040518082815260200191505060405180910390f35b341561019257600080fd5b6101ac600480803560001916906020019091905050610e94565b6040518082815260200191505060405180910390f35b34156101cd57600080fd5b6103a660048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b8282101561024857848483905060a002016005806020026040519081016040528092919082600560200280828437820191505050505081526020019060010190610203565b5050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b828210156102c457848483905060c00201600680602002604051908101604052809291908260066020028082843782019150505050508152602001906001019061027f565b5050505050919080359060200190919080351515906020019091908035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437820191505050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091905050610eac565b6040518082815260200191505060405180910390f35b34156103c757600080fd5b6104356004808060a001906005806020026040519081016040528092919082600560200280828437820191505050505091908060c001906006806020026040519081016040528092919082600660200280828437820191505050505091908035906020019091905050611013565b6040518082815260200191505060405180910390f35b341561045657600080fd5b61045e6114fb565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156104ab57600080fd5b6106b060048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b8282101561052657848483905060a0020160058060200260405190810160405280929190826005602002808284378201915050505050815260200190600101906104e1565b5050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b828210156105a257848483905060c00201600680602002604051908101604052809291908260066020028082843782019150505050508152602001906001019061055d565b50505050509190803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091908035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437820191505050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091905050611520565b005b34156106bd57600080fd5b6107516004808060a001906005806020026040519081016040528092919082600560200280828437820191505050505091908060c00190600680602002604051908101604052809291908260066020028082843782019150505050509190803590602001909190803560ff1690602001909190803560001916906020019091908035600019169060200190919050506115df565b005b341561075e57600080fd5b610778600480803560001916906020019091905050611605565b6040518082815260200191505060405180910390f35b341561079957600080fd5b6107f8600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080356000191690602001909190803560ff16906020019091908035600019169060200190919080356000191690602001909190505061164f565b604051808215151515815260200191505060405180910390f35b341561081d57600080fd5b6108456004808035906020019091908035906020019091908035906020019091905050611757565b6040518082815260200191505060405180910390f35b341561086657600080fd5b61086e611776565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156108bb57600080fd5b610acb60048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b8282101561093657848483905060a0020160058060200260405190810160405280929190826005602002808284378201915050505050815260200190600101906108f1565b5050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b828210156109b257848483905060c00201600680602002604051908101604052809291908260066020028082843782019150505050508152602001906001019061096d565b50505050509190803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091908035151590602001909190803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091908035906020019082018035906020019080806020026020016040519081016040528093929190818152602001838360200280828437820191505050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190505061179c565b005b3415610ad857600080fd5b610c1d60048080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b82821015610b5357848483905060a002016005806020026040519081016040528092919082600560200280828437820191505050505081526020019060010190610b0e565b5050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020016000905b82821015610bcf57848483905060c002016006806020026040519081016040528092919082600660200280828437820191505050505081526020019060010190610b8a565b5050505050919080359060200190820180359060200190808060200260200160405190810160405280939291908181526020018383602002808284378201915050505050509190505061185e565b005b3415610c2a57600080fd5b610cc96004808060a001906005806020026040519081016040528092919082600560200280828437820191505050505091908060c001906006806020026040519081016040528092919082600660200280828437820191505050505091908035906020019091908035151590602001909190803560ff1690602001909190803560001916906020019091908035600019169060200190919050506118d3565b6040518082815260200191505060405180910390f35b3415610cea57600080fd5b610d4f6004808060a001906005806020026040519081016040528092919082600560200280828437820191505050505091908060c001906006806020026040519081016040528092919082600660200280828437820191505050505091905050612073565b60405180826000191660001916815260200191505060405180910390f35b3415610d7857600080fd5b610d8061231f565b604051808261ffff1661ffff16815260200191505060405180910390f35b3415610da957600080fd5b610db1612325565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610df25780820151818401525b602081019050610dd6565b50505050905090810190601f168015610e1f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60008060008486850991506000821415610e4a5760009250610e73565b610e69610e5a83620f424061235e565b610e64888761235e565b612392565b90506103e8811192505b50509392505050565b60026020528060005260406000206000915090505481565b60036020528060005260406000206000915090505481565b6000806000809150600090505b895181101561100257896000815181101515610ed157fe5b906020019060200201516003600581101515610ee957fe5b602002015173ffffffffffffffffffffffffffffffffffffffff168a82815181101515610f1257fe5b906020019060200201516003600581101515610f2a57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff16141515610f5157600080fd5b610fe582610fe08c84815181101515610f6657fe5b906020019060200201518c85815181101515610f7e57fe5b90602001906020020151610f928d886123ae565b8c8c88815181101515610fa157fe5b906020019060200201518c89815181101515610fb957fe5b906020019060200201518c8a815181101515610fd157fe5b906020019060200201516118d3565b6123c8565b915087821415610ff457611002565b5b8080600101915050610eb9565b8192505b5050979650505050505050565b600061101d612a8c565b6000806101606040519081016040528088600060058110151561103c57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff16815260200188600160058110151561106b57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff16815260200188600260058110151561109a57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018860036005811015156110c957fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018860046005811015156110f857fe5b602002015173ffffffffffffffffffffffffffffffffffffffff16815260200187600060068110151561112757fe5b6020020151815260200187600160068110151561114057fe5b6020020151815260200187600260068110151561115957fe5b6020020151815260200187600360068110151561117257fe5b6020020151815260200187600460068110151561118b57fe5b6020020151815260200161119f8989612073565b6000191681525092503373ffffffffffffffffffffffffffffffffffffffff16836000015173ffffffffffffffffffffffffffffffffffffffff161415156111e657600080fd5b60008360a001511180156111fe575060008360c00151115b801561120a5750600085115b151561121557600080fd5b8261012001514210151561127257826101400151600019166000600381111561123a57fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a3600093506114f1565b61128d8360c00151611288856101400151611605565b6123ae565b915061129985836123e7565b905060008114156112f35782610140015160001916600160038111156112bb57fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a3600093506114f1565b61131d600360008561014001516000191660001916815260200190815260200160002054826123c8565b60036000856101400151600019166000191681526020019081526020016000208190555082604001518360600151604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c0100000000000000000000000002815260140192505050604051809103902060001916836080015173ffffffffffffffffffffffffffffffffffffffff16846000015173ffffffffffffffffffffffffffffffffffffffff167f67d66f160bc93d925d05dae1794c90d2d6d6688b29b84ff069398a9b0458713186604001518760600151611455878a60c001518b60a00151611757565b878a6101400151604051808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200184815260200183815260200182600019166000191681526020019550505050505060405180910390a48093505b5050509392505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008090505b86518110156115d5576115c7878281518110151561154057fe5b90602001906020020151878381518110151561155857fe5b90602001906020020151878481518110151561157057fe5b90602001906020020151878581518110151561158857fe5b9060200190602002015187868151811015156115a057fe5b9060200190602002015187878151811015156115b857fe5b906020019060200201516115df565b5b8080600101915050611526565b5b50505050505050565b836115f087878760008888886118d3565b1415156115fc57600080fd5b5b505050505050565b600061164760026000846000191660001916815260200190815260200160002054600360008560001916600019168152602001908152602001600020546123c8565b90505b919050565b600060018560405180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c0182600019166000191681526020019150506040518091039020858585604051600081526020016040526000604051602001526040518085600019166000191681526020018460ff1660ff16815260200183600019166000191681526020018260001916600019168152602001945050505050602060405160208103908084039060008661646e5a03f1151561171457600080fd5b50506020604051035173ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161490505b95945050505050565b600061176c611766858461235e565b84612392565b90505b9392505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008090505b87518110156118535761184488828151811015156117bc57fe5b9060200190602002015188838151811015156117d457fe5b9060200190602002015188848151811015156117ec57fe5b9060200190602002015188888681518110151561180557fe5b90602001906020020151888781518110151561181d57fe5b90602001906020020151888881518110151561183557fe5b906020019060200201516118d3565b505b80806001019150506117a2565b5b5050505050505050565b60008090505b83518110156118cc576118bd848281518110151561187e57fe5b90602001906020020151848381518110151561189657fe5b9060200190602002015184848151811015156118ae57fe5b90602001906020020151611013565b505b8080600101915050611864565b5b50505050565b60006118dd612a8c565b600080600080610160604051908101604052808e60006005811015156118ff57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018e600160058110151561192e57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018e600260058110151561195d57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018e600360058110151561198c57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018e60046005811015156119bb57fe5b602002015173ffffffffffffffffffffffffffffffffffffffff1681526020018d60006006811015156119ea57fe5b602002015181526020018d6001600681101515611a0357fe5b602002015181526020018d6002600681101515611a1c57fe5b602002015181526020018d6003600681101515611a3557fe5b602002015181526020018d6004600681101515611a4e57fe5b60200201518152602001611a628f8f612073565b600019168152509450600073ffffffffffffffffffffffffffffffffffffffff16856020015173ffffffffffffffffffffffffffffffffffffffff161480611ad957503373ffffffffffffffffffffffffffffffffffffffff16856020015173ffffffffffffffffffffffffffffffffffffffff16145b1515611ae457600080fd5b60008560a00151118015611afc575060008560c00151115b8015611b08575060008b115b1515611b1357600080fd5b611b2985600001518661014001518b8b8b61164f565b1515611b3457600080fd5b84610120015142101515611b91578461014001516000191660006003811115611b5957fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a360009550612063565b611bac8560c00151611ba7876101400151611605565b6123ae565b9350611bb88b856123e7565b95506000861415611c12578461014001516000191660016003811115611bda57fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a360009550612063565b611c25868660c001518760a00151610e2d565b15611c79578461014001516000191660026003811115611c4157fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a360009550612063565b89158015611c8e5750611c8c8587612401565b155b15611ce15784610140015160001916600380811115611ca957fe5b60ff167f36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e9060405160405180910390a360009550612063565b611cf4868660c001518760a00151611757565b9250611d20600260008761014001516000191660001916815260200190815260200160002054876123c8565b600260008761014001516000191660001916815260200190815260200160002081905550611d58856040015186600001513386612751565b1515611d6357600080fd5b611d77856060015133876000015189612751565b1515611d8257600080fd5b600073ffffffffffffffffffffffffffffffffffffffff16856080015173ffffffffffffffffffffffffffffffffffffffff16141515611e815760008560e001511115611e1f57611ddc868660c001518760e00151611757565b9150611e136000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168660000151876080015185612751565b1515611e1e57600080fd5b5b60008561010001511115611e8057611e41868660c00151876101000151611757565b9050611e746000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1633876080015184612751565b1515611e7f57600080fd5b5b5b84604001518560600151604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c0100000000000000000000000002815260140192505050604051809103902060001916856080015173ffffffffffffffffffffffffffffffffffffffff16866000015173ffffffffffffffffffffffffffffffffffffffff167f0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb33389604001518a60600151898d8a8a8f6101400151604051808973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200186815260200185815260200184815260200183815260200182600019166000191681526020019850505050505050505060405180910390a48595505b5050505050979650505050505050565b60003083600060058110151561208557fe5b602002015184600160058110151561209957fe5b60200201518560026005811015156120ad57fe5b60200201518660036005811015156120c157fe5b60200201518760046005811015156120d557fe5b60200201518760006006811015156120e957fe5b60200201518860016006811015156120fd57fe5b602002015189600260068110151561211157fe5b60200201518a600360068110151561212557fe5b60200201518b600460068110151561213957fe5b60200201518c600560068110151561214d57fe5b6020020151604051808d73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166c010000000000000000000000000281526014018781526020018681526020018581526020018481526020018381526020018281526020019c50505050505050505050505050604051809103902090505b92915050565b61138781565b6040805190810160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6000808284029050600084148061237f575082848281151561237c57fe5b04145b151561238757fe5b8091505b5092915050565b60008082848115156123a057fe5b0490508091505b5092915050565b60008282111515156123bc57fe5b81830390505b92915050565b60008082840190508381101515156123dc57fe5b8091505b5092915050565b60008183106123f657816123f8565b825b90505b92915050565b60008060008060008060008060003397506124258a8c60c001518d60a00151611757565b9650600073ffffffffffffffffffffffffffffffffffffffff168b6080015173ffffffffffffffffffffffffffffffffffffffff161415156126d2576000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168b6040015173ffffffffffffffffffffffffffffffffffffffff161495506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168b6060015173ffffffffffffffffffffffffffffffffffffffff161494506125208a8c60c001518d60e00151611757565b93506125368a8c60c001518d6101000151611757565b925085612543578361254e565b61254d87856123c8565b5b91508461255b5782612566565b6125658a846123c8565b5b9050816125986000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168d600001516128ae565b10806125d15750816125cf6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168d60000151612972565b105b806126055750806126036000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168a6128ae565b105b806126395750806126376000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168a612972565b105b156126475760009850612743565b851580156126805750866126638c604001518d600001516128ae565b108061267f57508661267d8c604001518d60000151612972565b105b5b1561268e5760009850612743565b841580156126bf5750896126a68c606001518a6128ae565b10806126be5750896126bc8c606001518a612972565b105b5b156126cd5760009850612743565b61273e565b866126e58c604001518d600001516128ae565b10806127015750866126ff8c604001518d60000151612972565b105b806127185750896127168c606001518a6128ae565b105b8061272f57508961272d8c606001518a612972565b105b1561273d5760009850612743565b5b600198505b505050505050505092915050565b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166315dacbea868686866000604051602001526040518563ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001945050505050602060405180830381600087803b151561288857600080fd5b6102c65a03f1151561289957600080fd5b5050506040518051905090505b949350505050565b60008273ffffffffffffffffffffffffffffffffffffffff166370a0823161138761ffff16846040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600088803b151561295157600080fd5b87f1151561295e57600080fd5b505050506040518051905090505b92915050565b60008273ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e61138761ffff1684600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040518463ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200192505050602060405180830381600088803b1515612a6b57600080fd5b87f11515612a7857600080fd5b505050506040518051905090505b92915050565b61016060405190810160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff16815260200160008152602001600081526020016000815260200160008152602001600081526020016000801916815250905600a165627a7a72305820df5cabdc3a116e993e10bfb14823d18d9b798038d4c463a1703f9a584c456b7e0029'; diff --git a/packages/deployer/test/util/constants.ts b/packages/deployer/test/util/constants.ts index d69ce5385..d52076607 100644 --- a/packages/deployer/test/util/constants.ts +++ b/packages/deployer/test/util/constants.ts @@ -1,11 +1,11 @@ import { BigNumber } from '@0xproject/utils'; export const constants = { - networkId: 0, - jsonrpcPort: 8545, - optimizerEnabled: 0, - gasPrice: new BigNumber(20000000000), - timeoutMs: 20000, - zrxTokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498', - tokenTransferProxyAddress: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', + networkId: 0, + jsonrpcPort: 8545, + optimizerEnabled: 0, + gasPrice: new BigNumber(20000000000), + timeoutMs: 20000, + zrxTokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498', + tokenTransferProxyAddress: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', }; diff --git a/packages/deployer/tsconfig.json b/packages/deployer/tsconfig.json index f11c9245e..4e1edb510 100644 --- a/packages/deployer/tsconfig.json +++ b/packages/deployer/tsconfig.json @@ -1,16 +1,16 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib", - "strictFunctionTypes": false, - "strictNullChecks": false - }, - "include": [ - "./src/**/*", - "./test/**/*", - "../../node_modules/types-bn/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts", - "../../node_modules/chai-typescript-typings/index.d.ts", - "../../node_modules/web3-typescript-typings/index.d.ts" - ] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "strictFunctionTypes": false, + "strictNullChecks": false + }, + "include": [ + "./src/**/*", + "./test/**/*", + "../../node_modules/types-bn/index.d.ts", + "../../node_modules/types-ethereumjs-util/index.d.ts", + "../../node_modules/chai-typescript-typings/index.d.ts", + "../../node_modules/web3-typescript-typings/index.d.ts" + ] } diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index d7c764285..ea940fb1d 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,37 +1,37 @@ { - "name": "@0xproject/dev-utils", - "version": "0.0.7", - "description": "0x dev TS utils", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "scripts": { - "build": "tsc", - "clean": "shx rm -rf lib", - "lint": "tslint --project . 'src/**/*.ts'" - }, - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/dev-utils/README.md", - "devDependencies": { - "@0xproject/tslint-config": "^0.4.4", - "@types/lodash": "^4.14.86", - "npm-run-all": "^4.1.2", - "shx": "^0.2.2", - "tslint": "5.8.0", - "types-bn": "^0.0.1", - "types-ethereumjs-util": "0xProject/types-ethereumjs-util", - "typescript": "~2.6.1" - }, - "dependencies": { - "@0xproject/utils": "^0.2.2", - "ethereumjs-util": "^5.1.2", - "lodash": "^4.17.4", - "request-promise-native": "^1.0.5" - } + "name": "@0xproject/dev-utils", + "version": "0.0.7", + "description": "0x dev TS utils", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "tsc", + "clean": "shx rm -rf lib", + "lint": "tslint --project . 'src/**/*.ts'" + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/dev-utils/README.md", + "devDependencies": { + "@0xproject/tslint-config": "^0.4.4", + "@types/lodash": "^4.14.86", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.8.0", + "types-bn": "^0.0.1", + "types-ethereumjs-util": "0xProject/types-ethereumjs-util", + "typescript": "~2.6.1" + }, + "dependencies": { + "@0xproject/utils": "^0.2.2", + "ethereumjs-util": "^5.1.2", + "lodash": "^4.17.4", + "request-promise-native": "^1.0.5" + } } diff --git a/packages/dev-utils/src/blockchain_lifecycle.ts b/packages/dev-utils/src/blockchain_lifecycle.ts index fafa62c84..18f5d5c61 100644 --- a/packages/dev-utils/src/blockchain_lifecycle.ts +++ b/packages/dev-utils/src/blockchain_lifecycle.ts @@ -1,23 +1,23 @@ import { RPC } from './rpc'; export class BlockchainLifecycle { - private _rpc: RPC; - private _snapshotIdsStack: number[]; - constructor(url: string) { - this._rpc = new RPC(url); - this._snapshotIdsStack = []; - } - // TODO: In order to run these tests on an actual node, we should check if we are running against - // TestRPC, if so, use snapshots, otherwise re-deploy contracts before every test - public async startAsync(): Promise<void> { - const snapshotId = await this._rpc.takeSnapshotAsync(); - this._snapshotIdsStack.push(snapshotId); - } - public async revertAsync(): Promise<void> { - const snapshotId = this._snapshotIdsStack.pop() as number; - const didRevert = await this._rpc.revertSnapshotAsync(snapshotId); - if (!didRevert) { - throw new Error(`Snapshot with id #${snapshotId} failed to revert`); - } - } + private _rpc: RPC; + private _snapshotIdsStack: number[]; + constructor(url: string) { + this._rpc = new RPC(url); + this._snapshotIdsStack = []; + } + // TODO: In order to run these tests on an actual node, we should check if we are running against + // TestRPC, if so, use snapshots, otherwise re-deploy contracts before every test + public async startAsync(): Promise<void> { + const snapshotId = await this._rpc.takeSnapshotAsync(); + this._snapshotIdsStack.push(snapshotId); + } + public async revertAsync(): Promise<void> { + const snapshotId = this._snapshotIdsStack.pop() as number; + const didRevert = await this._rpc.revertSnapshotAsync(snapshotId); + if (!didRevert) { + throw new Error(`Snapshot with id #${snapshotId} failed to revert`); + } + } } diff --git a/packages/dev-utils/src/rpc.ts b/packages/dev-utils/src/rpc.ts index 6241ab971..36f8b1ef9 100644 --- a/packages/dev-utils/src/rpc.ts +++ b/packages/dev-utils/src/rpc.ts @@ -2,59 +2,59 @@ import * as ethUtil from 'ethereumjs-util'; import * as request from 'request-promise-native'; export class RPC { - private _url: string; - private _id: number; - constructor(url: string) { - this._url = url; - this._id = 0; - } - public async takeSnapshotAsync(): Promise<number> { - const method = 'evm_snapshot'; - const params: any[] = []; - const payload = this._toPayload(method, params); - const snapshotIdHex = await this._sendAsync(payload); - const snapshotId = ethUtil.bufferToInt(ethUtil.toBuffer(snapshotIdHex)); - return snapshotId; - } - public async revertSnapshotAsync(snapshotId: number): Promise<boolean> { - const method = 'evm_revert'; - const params = [snapshotId]; - const payload = this._toPayload(method, params); - const didRevert = await this._sendAsync(payload); - return didRevert; - } - public async increaseTimeAsync(time: number) { - const method = 'evm_increaseTime'; - const params = [time]; - const payload = this._toPayload(method, params); - return this._sendAsync(payload); - } - public async mineBlockAsync(): Promise<void> { - const method = 'evm_mine'; - const params: any[] = []; - const payload = this._toPayload(method, params); - await this._sendAsync(payload); - } - private _toPayload(method: string, params: any[] = []): string { - const payload = JSON.stringify({ - id: this._id, - method, - params, - }); - this._id += 1; - return payload; - } - private async _sendAsync(payload: string): Promise<any> { - const opts = { - method: 'POST', - uri: this._url, - body: payload, - headers: { - 'content-type': 'application/json', - }, - }; - const bodyString = await request(opts); - const body = JSON.parse(bodyString); - return body.result; - } + private _url: string; + private _id: number; + constructor(url: string) { + this._url = url; + this._id = 0; + } + public async takeSnapshotAsync(): Promise<number> { + const method = 'evm_snapshot'; + const params: any[] = []; + const payload = this._toPayload(method, params); + const snapshotIdHex = await this._sendAsync(payload); + const snapshotId = ethUtil.bufferToInt(ethUtil.toBuffer(snapshotIdHex)); + return snapshotId; + } + public async revertSnapshotAsync(snapshotId: number): Promise<boolean> { + const method = 'evm_revert'; + const params = [snapshotId]; + const payload = this._toPayload(method, params); + const didRevert = await this._sendAsync(payload); + return didRevert; + } + public async increaseTimeAsync(time: number) { + const method = 'evm_increaseTime'; + const params = [time]; + const payload = this._toPayload(method, params); + return this._sendAsync(payload); + } + public async mineBlockAsync(): Promise<void> { + const method = 'evm_mine'; + const params: any[] = []; + const payload = this._toPayload(method, params); + await this._sendAsync(payload); + } + private _toPayload(method: string, params: any[] = []): string { + const payload = JSON.stringify({ + id: this._id, + method, + params, + }); + this._id += 1; + return payload; + } + private async _sendAsync(payload: string): Promise<any> { + const opts = { + method: 'POST', + uri: this._url, + body: payload, + headers: { + 'content-type': 'application/json', + }, + }; + const bodyString = await request(opts); + const body = JSON.parse(bodyString); + return body.result; + } } diff --git a/packages/dev-utils/tsconfig.json b/packages/dev-utils/tsconfig.json index 83a0e2451..b28e45170 100644 --- a/packages/dev-utils/tsconfig.json +++ b/packages/dev-utils/tsconfig.json @@ -1,11 +1,11 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": [ - "./src/**/*", - "../../node_modules/types-bn/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts" - ] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": [ + "./src/**/*", + "../../node_modules/types-bn/index.d.ts", + "../../node_modules/types-ethereumjs-util/index.d.ts" + ] } diff --git a/packages/dev-utils/tslint.json b/packages/dev-utils/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/dev-utils/tslint.json +++ b/packages/dev-utils/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/json-schemas/package.json b/packages/json-schemas/package.json index 30cccb12f..d67cb64f7 100644 --- a/packages/json-schemas/package.json +++ b/packages/json-schemas/package.json @@ -1,45 +1,45 @@ { - "name": "@0xproject/json-schemas", - "version": "0.7.5", - "description": "0x-related json schemas", - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", - "scripts": { - "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", - "test": "run-s clean build run_mocha", - "test:circleci": "yarn test", - "run_mocha": "mocha lib/test/**/*_test.js", - "clean": "shx rm -rf _bundles lib test_temp", - "build": "tsc" - }, - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "author": "", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/json-schemas/README.md", - "dependencies": { - "jsonschema": "^1.2.0", - "lodash.values": "^4.3.0" - }, - "devDependencies": { - "@0xproject/tslint-config": "^0.4.4", - "@0xproject/utils": "^0.2.2", - "@types/lodash.foreach": "^4.5.3", - "@types/lodash.values": "^4.3.3", - "@types/mocha": "^2.2.42", - "chai": "^4.0.1", - "chai-typescript-typings": "^0.0.2", - "dirty-chai": "^2.0.1", - "lodash.foreach": "^4.5.0", - "mocha": "^4.0.1", - "npm-run-all": "^4.1.2", - "shx": "^0.2.2", - "tslint": "5.8.0", - "typescript": "~2.6.1" - } + "name": "@0xproject/json-schemas", + "version": "0.7.5", + "description": "0x-related json schemas", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", + "test": "run-s clean build run_mocha", + "test:circleci": "yarn test", + "run_mocha": "mocha lib/test/**/*_test.js", + "clean": "shx rm -rf _bundles lib test_temp", + "build": "tsc" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "author": "", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/json-schemas/README.md", + "dependencies": { + "jsonschema": "^1.2.0", + "lodash.values": "^4.3.0" + }, + "devDependencies": { + "@0xproject/tslint-config": "^0.4.4", + "@0xproject/utils": "^0.2.2", + "@types/lodash.foreach": "^4.5.3", + "@types/lodash.values": "^4.3.3", + "@types/mocha": "^2.2.42", + "chai": "^4.0.1", + "chai-typescript-typings": "^0.0.2", + "dirty-chai": "^2.0.1", + "lodash.foreach": "^4.5.0", + "mocha": "^4.0.1", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.8.0", + "typescript": "~2.6.1" + } } diff --git a/packages/json-schemas/schemas/basic_type_schemas.ts b/packages/json-schemas/schemas/basic_type_schemas.ts index aa2eea867..9d81ff333 100644 --- a/packages/json-schemas/schemas/basic_type_schemas.ts +++ b/packages/json-schemas/schemas/basic_type_schemas.ts @@ -1,11 +1,11 @@ export const addressSchema = { - id: '/Address', - type: 'string', - pattern: '^0x[0-9a-f]{40}$', + id: '/Address', + type: 'string', + pattern: '^0x[0-9a-f]{40}$', }; export const numberSchema = { - id: '/Number', - type: 'string', - pattern: '^\\d+(\\.\\d+)?$', + id: '/Number', + type: 'string', + pattern: '^\\d+(\\.\\d+)?$', }; diff --git a/packages/json-schemas/schemas/block_range_schema.ts b/packages/json-schemas/schemas/block_range_schema.ts index 8697bba96..1f6a63151 100644 --- a/packages/json-schemas/schemas/block_range_schema.ts +++ b/packages/json-schemas/schemas/block_range_schema.ts @@ -1,20 +1,20 @@ export const blockParamSchema = { - id: '/BlockParam', - oneOf: [ - { - type: 'number', - }, - { - enum: ['latest', 'earliest', 'pending'], - }, - ], + id: '/BlockParam', + oneOf: [ + { + type: 'number', + }, + { + enum: ['latest', 'earliest', 'pending'], + }, + ], }; export const blockRangeSchema = { - id: '/BlockRange', - properties: { - fromBlock: { $ref: '/BlockParam' }, - toBlock: { $ref: '/BlockParam' }, - }, - type: 'object', + id: '/BlockRange', + properties: { + fromBlock: { $ref: '/BlockParam' }, + toBlock: { $ref: '/BlockParam' }, + }, + type: 'object', }; diff --git a/packages/json-schemas/schemas/ec_signature_schema.ts b/packages/json-schemas/schemas/ec_signature_schema.ts index 299ca283b..71b840dd8 100644 --- a/packages/json-schemas/schemas/ec_signature_schema.ts +++ b/packages/json-schemas/schemas/ec_signature_schema.ts @@ -1,20 +1,20 @@ export const ecSignatureParameterSchema = { - id: '/ECSignatureParameter', - type: 'string', - pattern: '^0[xX][0-9A-Fa-f]{64}$', + id: '/ECSignatureParameter', + type: 'string', + pattern: '^0[xX][0-9A-Fa-f]{64}$', }; export const ecSignatureSchema = { - id: '/ECSignature', - properties: { - v: { - type: 'number', - minimum: 27, - maximum: 28, - }, - r: { $ref: '/ECSignatureParameter' }, - s: { $ref: '/ECSignatureParameter' }, - }, - required: ['v', 'r', 's'], - type: 'object', + id: '/ECSignature', + properties: { + v: { + type: 'number', + minimum: 27, + maximum: 28, + }, + r: { $ref: '/ECSignatureParameter' }, + s: { $ref: '/ECSignatureParameter' }, + }, + required: ['v', 'r', 's'], + type: 'object', }; diff --git a/packages/json-schemas/schemas/index_filter_values_schema.ts b/packages/json-schemas/schemas/index_filter_values_schema.ts index 6e9f36c54..3374d63e0 100644 --- a/packages/json-schemas/schemas/index_filter_values_schema.ts +++ b/packages/json-schemas/schemas/index_filter_values_schema.ts @@ -1,7 +1,7 @@ export const indexFilterValuesSchema = { - id: '/IndexFilterValues', - additionalProperties: { - oneOf: [{ $ref: '/Number' }, { $ref: '/Address' }, { $ref: '/OrderHashSchema' }], - }, - type: 'object', + id: '/IndexFilterValues', + additionalProperties: { + oneOf: [{ $ref: '/Number' }, { $ref: '/Address' }, { $ref: '/OrderHashSchema' }], + }, + type: 'object', }; diff --git a/packages/json-schemas/schemas/order_cancel_schema.ts b/packages/json-schemas/schemas/order_cancel_schema.ts index f2aa80e7a..ad23d01cc 100644 --- a/packages/json-schemas/schemas/order_cancel_schema.ts +++ b/packages/json-schemas/schemas/order_cancel_schema.ts @@ -1,12 +1,12 @@ export const orderCancellationRequestsSchema = { - id: '/OrderCancellationRequests', - type: 'array', - items: { - properties: { - order: { $ref: '/Order' }, - takerTokenCancelAmount: { $ref: '/Number' }, - }, - required: ['order', 'takerTokenCancelAmount'], - type: 'object', - }, + id: '/OrderCancellationRequests', + type: 'array', + items: { + properties: { + order: { $ref: '/Order' }, + takerTokenCancelAmount: { $ref: '/Number' }, + }, + required: ['order', 'takerTokenCancelAmount'], + type: 'object', + }, }; diff --git a/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.ts b/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.ts index f13bc9cc3..61f2c8849 100644 --- a/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.ts +++ b/packages/json-schemas/schemas/order_fill_or_kill_requests_schema.ts @@ -1,12 +1,12 @@ export const orderFillOrKillRequestsSchema = { - id: '/OrderFillOrKillRequests', - type: 'array', - items: { - properties: { - signedOrder: { $ref: '/SignedOrder' }, - fillTakerAmount: { $ref: '/Number' }, - }, - required: ['signedOrder', 'fillTakerAmount'], - type: 'object', - }, + id: '/OrderFillOrKillRequests', + type: 'array', + items: { + properties: { + signedOrder: { $ref: '/SignedOrder' }, + fillTakerAmount: { $ref: '/Number' }, + }, + required: ['signedOrder', 'fillTakerAmount'], + type: 'object', + }, }; diff --git a/packages/json-schemas/schemas/order_fill_requests_schema.ts b/packages/json-schemas/schemas/order_fill_requests_schema.ts index 45cd927be..419d0670f 100644 --- a/packages/json-schemas/schemas/order_fill_requests_schema.ts +++ b/packages/json-schemas/schemas/order_fill_requests_schema.ts @@ -1,12 +1,12 @@ export const orderFillRequestsSchema = { - id: '/OrderFillRequests', - type: 'array', - items: { - properties: { - signedOrder: { $ref: '/SignedOrder' }, - takerTokenFillAmount: { $ref: '/Number' }, - }, - required: ['signedOrder', 'takerTokenFillAmount'], - type: 'object', - }, + id: '/OrderFillRequests', + type: 'array', + items: { + properties: { + signedOrder: { $ref: '/SignedOrder' }, + takerTokenFillAmount: { $ref: '/Number' }, + }, + required: ['signedOrder', 'takerTokenFillAmount'], + type: 'object', + }, }; diff --git a/packages/json-schemas/schemas/order_hash_schema.ts b/packages/json-schemas/schemas/order_hash_schema.ts index 5eed112ca..6af06927f 100644 --- a/packages/json-schemas/schemas/order_hash_schema.ts +++ b/packages/json-schemas/schemas/order_hash_schema.ts @@ -1,5 +1,5 @@ export const orderHashSchema = { - id: '/OrderHashSchema', - type: 'string', - pattern: '^0x[0-9a-fA-F]{64}$', + id: '/OrderHashSchema', + type: 'string', + pattern: '^0x[0-9a-fA-F]{64}$', }; diff --git a/packages/json-schemas/schemas/order_schemas.ts b/packages/json-schemas/schemas/order_schemas.ts index ee5f2c137..6f17224ad 100644 --- a/packages/json-schemas/schemas/order_schemas.ts +++ b/packages/json-schemas/schemas/order_schemas.ts @@ -1,43 +1,43 @@ export const orderSchema = { - id: '/Order', - properties: { - maker: { $ref: '/Address' }, - taker: { $ref: '/Address' }, - makerFee: { $ref: '/Number' }, - takerFee: { $ref: '/Number' }, - makerTokenAmount: { $ref: '/Number' }, - takerTokenAmount: { $ref: '/Number' }, - makerTokenAddress: { $ref: '/Address' }, - takerTokenAddress: { $ref: '/Address' }, - salt: { $ref: '/Number' }, - feeRecipient: { $ref: '/Address' }, - expirationUnixTimestampSec: { $ref: '/Number' }, - exchangeContractAddress: { $ref: '/Address' }, - }, - required: [ - 'maker', - 'taker', - 'makerFee', - 'takerFee', - 'makerTokenAmount', - 'takerTokenAmount', - 'salt', - 'feeRecipient', - 'expirationUnixTimestampSec', - 'exchangeContractAddress', - ], - type: 'object', + id: '/Order', + properties: { + maker: { $ref: '/Address' }, + taker: { $ref: '/Address' }, + makerFee: { $ref: '/Number' }, + takerFee: { $ref: '/Number' }, + makerTokenAmount: { $ref: '/Number' }, + takerTokenAmount: { $ref: '/Number' }, + makerTokenAddress: { $ref: '/Address' }, + takerTokenAddress: { $ref: '/Address' }, + salt: { $ref: '/Number' }, + feeRecipient: { $ref: '/Address' }, + expirationUnixTimestampSec: { $ref: '/Number' }, + exchangeContractAddress: { $ref: '/Address' }, + }, + required: [ + 'maker', + 'taker', + 'makerFee', + 'takerFee', + 'makerTokenAmount', + 'takerTokenAmount', + 'salt', + 'feeRecipient', + 'expirationUnixTimestampSec', + 'exchangeContractAddress', + ], + type: 'object', }; export const signedOrderSchema = { - id: '/SignedOrder', - allOf: [ - { $ref: '/Order' }, - { - properties: { - ecSignature: { $ref: '/ECSignature' }, - }, - required: ['ecSignature'], - }, - ], + id: '/SignedOrder', + allOf: [ + { $ref: '/Order' }, + { + properties: { + ecSignature: { $ref: '/ECSignature' }, + }, + required: ['ecSignature'], + }, + ], }; diff --git a/packages/json-schemas/schemas/relayer_api_error_response_schema.ts b/packages/json-schemas/schemas/relayer_api_error_response_schema.ts index 3d3a96364..27fdb166f 100644 --- a/packages/json-schemas/schemas/relayer_api_error_response_schema.ts +++ b/packages/json-schemas/schemas/relayer_api_error_response_schema.ts @@ -1,21 +1,21 @@ export const relayerApiErrorResponseSchema = { - id: '/RelayerApiErrorResponse', - type: 'object', - properties: { - code: { type: 'number' }, - reason: { type: 'string' }, - validationErrors: { - type: 'array', - items: { - type: 'object', - properties: { - field: { type: 'string' }, - code: { type: 'number' }, - reason: { type: 'string' }, - }, - required: ['field', 'code', 'reason'], - }, - }, - }, - required: ['code', 'reason'], + id: '/RelayerApiErrorResponse', + type: 'object', + properties: { + code: { type: 'number' }, + reason: { type: 'string' }, + validationErrors: { + type: 'array', + items: { + type: 'object', + properties: { + field: { type: 'string' }, + code: { type: 'number' }, + reason: { type: 'string' }, + }, + required: ['field', 'code', 'reason'], + }, + }, + }, + required: ['code', 'reason'], }; diff --git a/packages/json-schemas/schemas/relayer_api_fees_payload_schema.ts b/packages/json-schemas/schemas/relayer_api_fees_payload_schema.ts index f79308594..eaaf777a1 100644 --- a/packages/json-schemas/schemas/relayer_api_fees_payload_schema.ts +++ b/packages/json-schemas/schemas/relayer_api_fees_payload_schema.ts @@ -1,24 +1,24 @@ export const relayerApiFeesPayloadSchema = { - id: '/RelayerApiFeesPayload', - type: 'object', - properties: { - exchangeContractAddress: { $ref: '/Address' }, - maker: { $ref: '/Address' }, - taker: { $ref: '/Address' }, - makerTokenAddress: { $ref: '/Address' }, - takerTokenAddress: { $ref: '/Address' }, - makerTokenAmount: { $ref: '/Number' }, - takerTokenAmount: { $ref: '/Number' }, - expirationUnixTimestampSec: { $ref: '/Number' }, - salt: { $ref: '/Number' }, - }, - required: [ - 'exchangeContractAddress', - 'maker', - 'taker', - 'makerTokenAddress', - 'takerTokenAddress', - 'expirationUnixTimestampSec', - 'salt', - ], + id: '/RelayerApiFeesPayload', + type: 'object', + properties: { + exchangeContractAddress: { $ref: '/Address' }, + maker: { $ref: '/Address' }, + taker: { $ref: '/Address' }, + makerTokenAddress: { $ref: '/Address' }, + takerTokenAddress: { $ref: '/Address' }, + makerTokenAmount: { $ref: '/Number' }, + takerTokenAmount: { $ref: '/Number' }, + expirationUnixTimestampSec: { $ref: '/Number' }, + salt: { $ref: '/Number' }, + }, + required: [ + 'exchangeContractAddress', + 'maker', + 'taker', + 'makerTokenAddress', + 'takerTokenAddress', + 'expirationUnixTimestampSec', + 'salt', + ], }; diff --git a/packages/json-schemas/schemas/relayer_api_fees_response_schema.ts b/packages/json-schemas/schemas/relayer_api_fees_response_schema.ts index d87c2fcdb..e7440613f 100644 --- a/packages/json-schemas/schemas/relayer_api_fees_response_schema.ts +++ b/packages/json-schemas/schemas/relayer_api_fees_response_schema.ts @@ -1,10 +1,10 @@ export const relayerApiFeesResponseSchema = { - id: '/RelayerApiFeesResponse', - type: 'object', - properties: { - makerFee: { $ref: '/Number' }, - takerFee: { $ref: '/Number' }, - feeRecipient: { $ref: '/Address' }, - }, - required: ['makerFee', 'takerFee', 'feeRecipient'], + id: '/RelayerApiFeesResponse', + type: 'object', + properties: { + makerFee: { $ref: '/Number' }, + takerFee: { $ref: '/Number' }, + feeRecipient: { $ref: '/Address' }, + }, + required: ['makerFee', 'takerFee', 'feeRecipient'], }; diff --git a/packages/json-schemas/schemas/relayer_api_orberbook_channel_subscribe_schema.ts b/packages/json-schemas/schemas/relayer_api_orberbook_channel_subscribe_schema.ts index 1d443c625..d93fa73d6 100644 --- a/packages/json-schemas/schemas/relayer_api_orberbook_channel_subscribe_schema.ts +++ b/packages/json-schemas/schemas/relayer_api_orberbook_channel_subscribe_schema.ts @@ -1,23 +1,23 @@ export const relayerApiOrderbookChannelSubscribeSchema = { - id: '/RelayerApiOrderbookChannelSubscribe', - type: 'object', - properties: { - type: { enum: ['subscribe'] }, - channel: { enum: ['orderbook'] }, - requestId: { type: 'number' }, - payload: { $ref: '/RelayerApiOrderbookChannelSubscribePayload' }, - }, - required: ['type', 'channel', 'requestId', 'payload'], + id: '/RelayerApiOrderbookChannelSubscribe', + type: 'object', + properties: { + type: { enum: ['subscribe'] }, + channel: { enum: ['orderbook'] }, + requestId: { type: 'number' }, + payload: { $ref: '/RelayerApiOrderbookChannelSubscribePayload' }, + }, + required: ['type', 'channel', 'requestId', 'payload'], }; export const relayerApiOrderbookChannelSubscribePayload = { - id: '/RelayerApiOrderbookChannelSubscribePayload', - type: 'object', - properties: { - baseTokenAddress: { $ref: '/Address' }, - quoteTokenAddress: { $ref: '/Address' }, - snapshot: { type: 'boolean' }, - limit: { type: 'number' }, - }, - required: ['baseTokenAddress', 'quoteTokenAddress'], + id: '/RelayerApiOrderbookChannelSubscribePayload', + type: 'object', + properties: { + baseTokenAddress: { $ref: '/Address' }, + quoteTokenAddress: { $ref: '/Address' }, + snapshot: { type: 'boolean' }, + limit: { type: 'number' }, + }, + required: ['baseTokenAddress', 'quoteTokenAddress'], }; diff --git a/packages/json-schemas/schemas/relayer_api_orderbook_channel_snapshot_schema.ts b/packages/json-schemas/schemas/relayer_api_orderbook_channel_snapshot_schema.ts index e954185a8..fe1510d5b 100644 --- a/packages/json-schemas/schemas/relayer_api_orderbook_channel_snapshot_schema.ts +++ b/packages/json-schemas/schemas/relayer_api_orderbook_channel_snapshot_schema.ts @@ -1,21 +1,21 @@ export const relayerApiOrderbookChannelSnapshotSchema = { - id: '/RelayerApiOrderbookChannelSnapshot', - type: 'object', - properties: { - type: { enum: ['snapshot'] }, - channel: { enum: ['orderbook'] }, - requestId: { type: 'number' }, - payload: { $ref: '/RelayerApiOrderbookChannelSnapshotPayload' }, - }, - required: ['type', 'channel', 'requestId', 'payload'], + id: '/RelayerApiOrderbookChannelSnapshot', + type: 'object', + properties: { + type: { enum: ['snapshot'] }, + channel: { enum: ['orderbook'] }, + requestId: { type: 'number' }, + payload: { $ref: '/RelayerApiOrderbookChannelSnapshotPayload' }, + }, + required: ['type', 'channel', 'requestId', 'payload'], }; export const relayerApiOrderbookChannelSnapshotPayload = { - id: '/RelayerApiOrderbookChannelSnapshotPayload', - type: 'object', - properties: { - bids: { $ref: '/signedOrdersSchema' }, - asks: { $ref: '/signedOrdersSchema' }, - }, - required: ['bids', 'asks'], + id: '/RelayerApiOrderbookChannelSnapshotPayload', + type: 'object', + properties: { + bids: { $ref: '/signedOrdersSchema' }, + asks: { $ref: '/signedOrdersSchema' }, + }, + required: ['bids', 'asks'], }; diff --git a/packages/json-schemas/schemas/relayer_api_orderbook_channel_update_response_schema.ts b/packages/json-schemas/schemas/relayer_api_orderbook_channel_update_response_schema.ts index 7c2ba6531..9a6d83d4c 100644 --- a/packages/json-schemas/schemas/relayer_api_orderbook_channel_update_response_schema.ts +++ b/packages/json-schemas/schemas/relayer_api_orderbook_channel_update_response_schema.ts @@ -1,11 +1,11 @@ export const relayerApiOrderbookChannelUpdateSchema = { - id: '/RelayerApiOrderbookChannelUpdate', - type: 'object', - properties: { - type: { enum: ['update'] }, - channel: { enum: ['orderbook'] }, - requestId: { type: 'number' }, - payload: { $ref: '/SignedOrder' }, - }, - required: ['type', 'channel', 'requestId', 'payload'], + id: '/RelayerApiOrderbookChannelUpdate', + type: 'object', + properties: { + type: { enum: ['update'] }, + channel: { enum: ['orderbook'] }, + requestId: { type: 'number' }, + payload: { $ref: '/SignedOrder' }, + }, + required: ['type', 'channel', 'requestId', 'payload'], }; diff --git a/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.ts b/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.ts index d9abfc77d..5c409c807 100644 --- a/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.ts +++ b/packages/json-schemas/schemas/relayer_api_orderbook_response_schema.ts @@ -1,9 +1,9 @@ export const relayerApiOrderBookResponseSchema = { - id: '/RelayerApiOrderBookResponse', - type: 'object', - properties: { - bids: { $ref: '/signedOrdersSchema' }, - asks: { $ref: '/signedOrdersSchema' }, - }, - required: ['bids', 'asks'], + id: '/RelayerApiOrderBookResponse', + type: 'object', + properties: { + bids: { $ref: '/signedOrdersSchema' }, + asks: { $ref: '/signedOrdersSchema' }, + }, + required: ['bids', 'asks'], }; diff --git a/packages/json-schemas/schemas/relayer_api_token_pairs_response_schema.ts b/packages/json-schemas/schemas/relayer_api_token_pairs_response_schema.ts index 471bed7b4..5009c7955 100644 --- a/packages/json-schemas/schemas/relayer_api_token_pairs_response_schema.ts +++ b/packages/json-schemas/schemas/relayer_api_token_pairs_response_schema.ts @@ -1,24 +1,24 @@ export const relayerApiTokenPairsResponseSchema = { - id: '/RelayerApiTokenPairsResponse', - type: 'array', - items: { - properties: { - tokenA: { $ref: '/RelayerApiTokenTradeInfo' }, - tokenB: { $ref: '/RelayerApiTokenTradeInfo' }, - }, - required: ['tokenA', 'tokenB'], - type: 'object', - }, + id: '/RelayerApiTokenPairsResponse', + type: 'array', + items: { + properties: { + tokenA: { $ref: '/RelayerApiTokenTradeInfo' }, + tokenB: { $ref: '/RelayerApiTokenTradeInfo' }, + }, + required: ['tokenA', 'tokenB'], + type: 'object', + }, }; export const relayerApiTokenTradeInfoSchema = { - id: '/RelayerApiTokenTradeInfo', - type: 'object', - properties: { - address: { $ref: '/Address' }, - minAmount: { $ref: '/Number' }, - maxAmount: { $ref: '/Number' }, - precision: { type: 'number' }, - }, - required: ['address'], + id: '/RelayerApiTokenTradeInfo', + type: 'object', + properties: { + address: { $ref: '/Address' }, + minAmount: { $ref: '/Number' }, + maxAmount: { $ref: '/Number' }, + precision: { type: 'number' }, + }, + required: ['address'], }; diff --git a/packages/json-schemas/schemas/signed_orders_schema.ts b/packages/json-schemas/schemas/signed_orders_schema.ts index 7d66b7027..34d956836 100644 --- a/packages/json-schemas/schemas/signed_orders_schema.ts +++ b/packages/json-schemas/schemas/signed_orders_schema.ts @@ -1,5 +1,5 @@ export const signedOrdersSchema = { - id: '/signedOrdersSchema', - type: 'array', - items: { $ref: '/SignedOrder' }, + id: '/signedOrdersSchema', + type: 'array', + items: { $ref: '/SignedOrder' }, }; diff --git a/packages/json-schemas/schemas/token_schema.ts b/packages/json-schemas/schemas/token_schema.ts index d2bb10f09..e64565c8b 100644 --- a/packages/json-schemas/schemas/token_schema.ts +++ b/packages/json-schemas/schemas/token_schema.ts @@ -1,11 +1,11 @@ export const tokenSchema = { - id: '/Token', - properties: { - name: { type: 'string' }, - symbol: { type: 'string' }, - decimals: { type: 'number' }, - address: { $ref: '/Address' }, - }, - required: ['name', 'symbol', 'decimals', 'address'], - type: 'object', + id: '/Token', + properties: { + name: { type: 'string' }, + symbol: { type: 'string' }, + decimals: { type: 'number' }, + address: { $ref: '/Address' }, + }, + required: ['name', 'symbol', 'decimals', 'address'], + type: 'object', }; diff --git a/packages/json-schemas/schemas/tx_data_schema.ts b/packages/json-schemas/schemas/tx_data_schema.ts index 00c1d3e08..4274c553f 100644 --- a/packages/json-schemas/schemas/tx_data_schema.ts +++ b/packages/json-schemas/schemas/tx_data_schema.ts @@ -1,33 +1,33 @@ export const jsNumber = { - id: '/JsNumber', - type: 'number', - minimum: 0, + id: '/JsNumber', + type: 'number', + minimum: 0, }; export const txDataSchema = { - id: '/TxData', - properties: { - from: { $ref: '/Address' }, - to: { $ref: '/Address' }, - value: { - oneOf: [{ $ref: '/Number' }, { $ref: '/JsNumber' }], - }, - gas: { - oneOf: [{ $ref: '/Number' }, { $ref: '/JsNumber' }], - }, - gasPrice: { - oneOf: [{ $ref: '/Number' }, { $ref: '/JsNumber' }], - }, - data: { - type: 'string', - pattern: '^0x[0-9a-f]*$', - }, - nonce: { - type: 'number', - minimum: 0, - }, - }, - required: ['from'], - type: 'object', - additionalProperties: false, + id: '/TxData', + properties: { + from: { $ref: '/Address' }, + to: { $ref: '/Address' }, + value: { + oneOf: [{ $ref: '/Number' }, { $ref: '/JsNumber' }], + }, + gas: { + oneOf: [{ $ref: '/Number' }, { $ref: '/JsNumber' }], + }, + gasPrice: { + oneOf: [{ $ref: '/Number' }, { $ref: '/JsNumber' }], + }, + data: { + type: 'string', + pattern: '^0x[0-9a-f]*$', + }, + nonce: { + type: 'number', + minimum: 0, + }, + }, + required: ['from'], + type: 'object', + additionalProperties: false, }; diff --git a/packages/json-schemas/src/schema_validator.ts b/packages/json-schemas/src/schema_validator.ts index 686c38d17..e13326d2a 100644 --- a/packages/json-schemas/src/schema_validator.ts +++ b/packages/json-schemas/src/schema_validator.ts @@ -4,26 +4,26 @@ import values = require('lodash.values'); import { schemas } from './schemas'; export class SchemaValidator { - private _validator: Validator; - constructor() { - this._validator = new Validator(); - for (const schema of values(schemas)) { - this._validator.addSchema(schema, schema.id); - } - } - public addSchema(schema: Schema) { - this._validator.addSchema(schema, schema.id); - } - // In order to validate a complex JS object using jsonschema, we must replace any complex - // sub-types (e.g BigNumber) with a simpler string representation. Since BigNumber and other - // complex types implement the `toString` method, we can stringify the object and - // then parse it. The resultant object can then be checked using jsonschema. - public validate(instance: any, schema: Schema): ValidatorResult { - const jsonSchemaCompatibleObject = JSON.parse(JSON.stringify(instance)); - return this._validator.validate(jsonSchemaCompatibleObject, schema); - } - public isValid(instance: any, schema: Schema): boolean { - const isValid = this.validate(instance, schema).errors.length === 0; - return isValid; - } + private _validator: Validator; + constructor() { + this._validator = new Validator(); + for (const schema of values(schemas)) { + this._validator.addSchema(schema, schema.id); + } + } + public addSchema(schema: Schema) { + this._validator.addSchema(schema, schema.id); + } + // In order to validate a complex JS object using jsonschema, we must replace any complex + // sub-types (e.g BigNumber) with a simpler string representation. Since BigNumber and other + // complex types implement the `toString` method, we can stringify the object and + // then parse it. The resultant object can then be checked using jsonschema. + public validate(instance: any, schema: Schema): ValidatorResult { + const jsonSchemaCompatibleObject = JSON.parse(JSON.stringify(instance)); + return this._validator.validate(jsonSchemaCompatibleObject, schema); + } + public isValid(instance: any, schema: Schema): boolean { + const isValid = this.validate(instance, schema).errors.length === 0; + return isValid; + } } diff --git a/packages/json-schemas/src/schemas.ts b/packages/json-schemas/src/schemas.ts index bdc227aea..5cb07acfe 100644 --- a/packages/json-schemas/src/schemas.ts +++ b/packages/json-schemas/src/schemas.ts @@ -11,50 +11,50 @@ import { relayerApiErrorResponseSchema } from '../schemas/relayer_api_error_resp import { relayerApiFeesPayloadSchema } from '../schemas/relayer_api_fees_payload_schema'; import { relayerApiFeesResponseSchema } from '../schemas/relayer_api_fees_response_schema'; import { - relayerApiOrderbookChannelSubscribePayload, - relayerApiOrderbookChannelSubscribeSchema, + relayerApiOrderbookChannelSubscribePayload, + relayerApiOrderbookChannelSubscribeSchema, } from '../schemas/relayer_api_orberbook_channel_subscribe_schema'; import { - relayerApiOrderbookChannelSnapshotPayload, - relayerApiOrderbookChannelSnapshotSchema, + relayerApiOrderbookChannelSnapshotPayload, + relayerApiOrderbookChannelSnapshotSchema, } from '../schemas/relayer_api_orderbook_channel_snapshot_schema'; import { relayerApiOrderbookChannelUpdateSchema } from '../schemas/relayer_api_orderbook_channel_update_response_schema'; import { relayerApiOrderBookResponseSchema } from '../schemas/relayer_api_orderbook_response_schema'; import { - relayerApiTokenPairsResponseSchema, - relayerApiTokenTradeInfoSchema, + relayerApiTokenPairsResponseSchema, + relayerApiTokenTradeInfoSchema, } from '../schemas/relayer_api_token_pairs_response_schema'; import { signedOrdersSchema } from '../schemas/signed_orders_schema'; import { tokenSchema } from '../schemas/token_schema'; import { jsNumber, txDataSchema } from '../schemas/tx_data_schema'; export const schemas = { - numberSchema, - addressSchema, - ecSignatureSchema, - ecSignatureParameterSchema, - indexFilterValuesSchema, - orderCancellationRequestsSchema, - orderFillOrKillRequestsSchema, - orderFillRequestsSchema, - orderHashSchema, - orderSchema, - signedOrderSchema, - signedOrdersSchema, - blockParamSchema, - blockRangeSchema, - tokenSchema, - jsNumber, - txDataSchema, - relayerApiErrorResponseSchema, - relayerApiFeesPayloadSchema, - relayerApiFeesResponseSchema, - relayerApiOrderBookResponseSchema, - relayerApiTokenPairsResponseSchema, - relayerApiTokenTradeInfoSchema, - relayerApiOrderbookChannelSubscribeSchema, - relayerApiOrderbookChannelSubscribePayload, - relayerApiOrderbookChannelUpdateSchema, - relayerApiOrderbookChannelSnapshotSchema, - relayerApiOrderbookChannelSnapshotPayload, + numberSchema, + addressSchema, + ecSignatureSchema, + ecSignatureParameterSchema, + indexFilterValuesSchema, + orderCancellationRequestsSchema, + orderFillOrKillRequestsSchema, + orderFillRequestsSchema, + orderHashSchema, + orderSchema, + signedOrderSchema, + signedOrdersSchema, + blockParamSchema, + blockRangeSchema, + tokenSchema, + jsNumber, + txDataSchema, + relayerApiErrorResponseSchema, + relayerApiFeesPayloadSchema, + relayerApiFeesResponseSchema, + relayerApiOrderBookResponseSchema, + relayerApiTokenPairsResponseSchema, + relayerApiTokenTradeInfoSchema, + relayerApiOrderbookChannelSubscribeSchema, + relayerApiOrderbookChannelSubscribePayload, + relayerApiOrderbookChannelUpdateSchema, + relayerApiOrderbookChannelSnapshotSchema, + relayerApiOrderbookChannelSnapshotPayload, }; diff --git a/packages/json-schemas/test/schema_test.ts b/packages/json-schemas/test/schema_test.ts index a6162c8e3..7b058781d 100644 --- a/packages/json-schemas/test/schema_test.ts +++ b/packages/json-schemas/test/schema_test.ts @@ -11,906 +11,906 @@ chai.use(dirtyChai); const expect = chai.expect; const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; const { - numberSchema, - addressSchema, - ecSignatureSchema, - ecSignatureParameterSchema, - orderCancellationRequestsSchema, - orderFillOrKillRequestsSchema, - orderFillRequestsSchema, - orderHashSchema, - orderSchema, - signedOrderSchema, - signedOrdersSchema, - blockParamSchema, - blockRangeSchema, - tokenSchema, - jsNumber, - txDataSchema, - relayerApiErrorResponseSchema, - relayerApiOrderBookResponseSchema, - relayerApiTokenPairsResponseSchema, - relayerApiFeesPayloadSchema, - relayerApiFeesResponseSchema, - relayerApiOrderbookChannelSubscribeSchema, - relayerApiOrderbookChannelUpdateSchema, - relayerApiOrderbookChannelSnapshotSchema, + numberSchema, + addressSchema, + ecSignatureSchema, + ecSignatureParameterSchema, + orderCancellationRequestsSchema, + orderFillOrKillRequestsSchema, + orderFillRequestsSchema, + orderHashSchema, + orderSchema, + signedOrderSchema, + signedOrdersSchema, + blockParamSchema, + blockRangeSchema, + tokenSchema, + jsNumber, + txDataSchema, + relayerApiErrorResponseSchema, + relayerApiOrderBookResponseSchema, + relayerApiTokenPairsResponseSchema, + relayerApiFeesPayloadSchema, + relayerApiFeesResponseSchema, + relayerApiOrderbookChannelSubscribeSchema, + relayerApiOrderbookChannelUpdateSchema, + relayerApiOrderbookChannelSnapshotSchema, } = schemas; describe('Schema', () => { - const validator = new SchemaValidator(); - const validateAgainstSchema = (testCases: any[], schema: any, shouldFail = false) => { - forEach(testCases, (testCase: any) => { - const validationResult = validator.validate(testCase, schema); - const hasErrors = validationResult.errors.length !== 0; - if (shouldFail) { - if (!hasErrors) { - throw new Error( - `Expected testCase: ${JSON.stringify(testCase, null, '\t')} to fail and it didn't.`, - ); - } - } else { - if (hasErrors) { - throw new Error(JSON.stringify(validationResult.errors, null, '\t')); - } - } - }); - }; - describe('#numberSchema', () => { - it('should validate valid numbers', () => { - const testCases = ['42', '0', '1.3', '0.2', '00.00']; - validateAgainstSchema(testCases, numberSchema); - }); - it('should fail for invalid numbers', () => { - const testCases = ['.3', '1.', 'abacaba', 'и', '1..0']; - const shouldFail = true; - validateAgainstSchema(testCases, numberSchema, shouldFail); - }); - }); - describe('#addressSchema', () => { - it('should validate valid addresses', () => { - const testCases = ['0x8b0292b11a196601ed2ce54b665cafeca0347d42', NULL_ADDRESS]; - validateAgainstSchema(testCases, addressSchema); - }); - it('should fail for invalid addresses', () => { - const testCases = [ - '0x', - '0', - '0x00', - '0xzzzzzzB11a196601eD2ce54B665CaFEca0347D42', - '0x8b0292B11a196601eD2ce54B665CaFEca0347D42', - ]; - const shouldFail = true; - validateAgainstSchema(testCases, addressSchema, shouldFail); - }); - }); - describe('#ecSignatureParameterSchema', () => { - it('should validate valid parameters', () => { - const testCases = [ - '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', - '0X40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - ]; - validateAgainstSchema(testCases, ecSignatureParameterSchema); - }); - it('should fail for invalid parameters', () => { - const testCases = [ - '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3', // shorter - '0xzzzz9190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', // invalid characters - '40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', // no 0x - ]; - const shouldFail = true; - validateAgainstSchema(testCases, ecSignatureParameterSchema, shouldFail); - }); - }); - describe('#ecSignatureSchema', () => { - it('should validate valid signature', () => { - const signature = { - v: 27, - r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', - s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - }; - const testCases = [ - signature, - { - ...signature, - v: 28, - }, - ]; - validateAgainstSchema(testCases, ecSignatureSchema); - }); - it('should fail for invalid signature', () => { - const v = 27; - const r = '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33'; - const s = '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254'; - const testCases = [{}, { v }, { r, s, v: 31 }]; - const shouldFail = true; - validateAgainstSchema(testCases, ecSignatureSchema, shouldFail); - }); - }); - describe('#orderHashSchema', () => { - it('should validate valid order hash', () => { - const testCases = [ - '0x61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33', - '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - ]; - validateAgainstSchema(testCases, orderHashSchema); - }); - it('should fail for invalid order hash', () => { - const testCases = [ - {}, - '0x', - '0x8b0292B11a196601eD2ce54B665CaFEca0347D42', - '61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33', - ]; - const shouldFail = true; - validateAgainstSchema(testCases, orderHashSchema, shouldFail); - }); - }); - describe('#blockParamSchema', () => { - it('should validate valid block param', () => { - const testCases = [42, 'latest', 'pending', 'earliest']; - validateAgainstSchema(testCases, blockParamSchema); - }); - it('should fail for invalid block param', () => { - const testCases = [{}, '42', 'pemding']; - const shouldFail = true; - validateAgainstSchema(testCases, blockParamSchema, shouldFail); - }); - }); - describe('#blockRangeSchema', () => { - it('should validate valid subscription opts', () => { - const testCases = [{ fromBlock: 42, toBlock: 'latest' }, { fromBlock: 42 }, {}]; - validateAgainstSchema(testCases, blockRangeSchema); - }); - it('should fail for invalid subscription opts', () => { - const testCases = [{ fromBlock: '42' }]; - const shouldFail = true; - validateAgainstSchema(testCases, blockRangeSchema, shouldFail); - }); - }); - describe('#tokenSchema', () => { - const token = { - name: 'Zero Ex', - symbol: 'ZRX', - decimals: 100500, - address: '0x8b0292b11a196601ed2ce54b665cafeca0347d42', - url: 'https://0xproject.com', - }; - it('should validate valid token', () => { - const testCases = [token]; - validateAgainstSchema(testCases, tokenSchema); - }); - it('should fail for invalid token', () => { - const testCases = [ - { - ...token, - address: null, - }, - { - ...token, - decimals: undefined, - }, - [], - 4, - ]; - const shouldFail = true; - validateAgainstSchema(testCases, tokenSchema, shouldFail); - }); - }); - describe('order including schemas', () => { - const order = { - maker: NULL_ADDRESS, - taker: NULL_ADDRESS, - makerFee: '1', - takerFee: '2', - makerTokenAmount: '1', - takerTokenAmount: '2', - makerTokenAddress: NULL_ADDRESS, - takerTokenAddress: NULL_ADDRESS, - salt: '67006738228878699843088602623665307406148487219438534730168799356281242528500', - feeRecipient: NULL_ADDRESS, - exchangeContractAddress: NULL_ADDRESS, - expirationUnixTimestampSec: '42', - }; - describe('#orderSchema', () => { - it('should validate valid order', () => { - const testCases = [order]; - validateAgainstSchema(testCases, orderSchema); - }); - it('should fail for invalid order', () => { - const testCases = [ - { - ...order, - salt: undefined, - }, - { - ...order, - salt: 'salt', - }, - 'order', - ]; - const shouldFail = true; - validateAgainstSchema(testCases, orderSchema, shouldFail); - }); - }); - describe('signed order including schemas', () => { - const signedOrder = { - ...order, - ecSignature: { - v: 27, - r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', - s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - }, - }; - describe('#signedOrdersSchema', () => { - it('should validate valid signed orders', () => { - const testCases = [[signedOrder], []]; - validateAgainstSchema(testCases, signedOrdersSchema); - }); - it('should fail for invalid signed orders', () => { - const testCases = [[signedOrder, 1]]; - const shouldFail = true; - validateAgainstSchema(testCases, signedOrdersSchema, shouldFail); - }); - }); - describe('#signedOrderSchema', () => { - it('should validate valid signed order', () => { - const testCases = [signedOrder]; - validateAgainstSchema(testCases, signedOrderSchema); - }); - it('should fail for invalid signed order', () => { - const testCases = [ - { - ...signedOrder, - ecSignature: undefined, - }, - ]; - const shouldFail = true; - validateAgainstSchema(testCases, signedOrderSchema, shouldFail); - }); - }); - describe('#orderFillOrKillRequestsSchema', () => { - const orderFillOrKillRequests = [ - { - signedOrder, - fillTakerAmount: '5', - }, - ]; - it('should validate valid order fill or kill requests', () => { - const testCases = [orderFillOrKillRequests]; - validateAgainstSchema(testCases, orderFillOrKillRequestsSchema); - }); - it('should fail for invalid order fill or kill requests', () => { - const testCases = [ - [ - { - ...orderFillOrKillRequests[0], - fillTakerAmount: undefined, - }, - ], - ]; - const shouldFail = true; - validateAgainstSchema(testCases, orderFillOrKillRequestsSchema, shouldFail); - }); - }); - describe('#orderCancellationRequestsSchema', () => { - const orderCancellationRequests = [ - { - order, - takerTokenCancelAmount: '5', - }, - ]; - it('should validate valid order cancellation requests', () => { - const testCases = [orderCancellationRequests]; - validateAgainstSchema(testCases, orderCancellationRequestsSchema); - }); - it('should fail for invalid order cancellation requests', () => { - const testCases = [ - [ - { - ...orderCancellationRequests[0], - takerTokenCancelAmount: undefined, - }, - ], - ]; - const shouldFail = true; - validateAgainstSchema(testCases, orderCancellationRequestsSchema, shouldFail); - }); - }); - describe('#orderFillRequestsSchema', () => { - const orderFillRequests = [ - { - signedOrder, - takerTokenFillAmount: '5', - }, - ]; - it('should validate valid order fill requests', () => { - const testCases = [orderFillRequests]; - validateAgainstSchema(testCases, orderFillRequestsSchema); - }); - it('should fail for invalid order fill requests', () => { - const testCases = [ - [ - { - ...orderFillRequests[0], - takerTokenFillAmount: undefined, - }, - ], - ]; - const shouldFail = true; - validateAgainstSchema(testCases, orderFillRequestsSchema, shouldFail); - }); - }); - describe('#relayerApiOrderBookResponseSchema', () => { - it('should validate valid order book responses', () => { - const testCases = [ - { - bids: [], - asks: [], - }, - { - bids: [signedOrder, signedOrder], - asks: [], - }, - { - bids: [], - asks: [signedOrder, signedOrder], - }, - { - bids: [signedOrder], - asks: [signedOrder, signedOrder], - }, - ]; - validateAgainstSchema(testCases, relayerApiOrderBookResponseSchema); - }); - it('should fail for invalid order fill requests', () => { - const testCases = [ - {}, - { - bids: [signedOrder, signedOrder], - }, - { - asks: [signedOrder, signedOrder], - }, - { - bids: signedOrder, - asks: [signedOrder, signedOrder], - }, - { - bids: [signedOrder], - asks: signedOrder, - }, - ]; - const shouldFail = true; - validateAgainstSchema(testCases, relayerApiOrderBookResponseSchema, shouldFail); - }); - }); - describe('#relayerApiOrderbookChannelSubscribeSchema', () => { - it('should validate valid orderbook channel websocket subscribe message', () => { - const testCases = [ - { - type: 'subscribe', - channel: 'orderbook', - requestId: 1, - payload: { - baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - snapshot: true, - limit: 100, - }, - }, - { - type: 'subscribe', - channel: 'orderbook', - requestId: 1, - payload: { - baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - }, - }, - ]; - validateAgainstSchema(testCases, relayerApiOrderbookChannelSubscribeSchema); - }); - it('should fail for invalid orderbook channel websocket subscribe message', () => { - const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32'; - const testCases = [ - { - type: 'subscribe', - channel: 'orderbook', - payload: { - baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - snapshot: true, - limit: 100, - }, - }, - { - type: 'foo', - channel: 'orderbook', - requestId: 1, - payload: { - baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - }, - }, - { - type: 'subscribe', - channel: 'bar', - requestId: 1, - payload: { - baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - }, - }, - { - type: 'subscribe', - channel: 'orderbook', - requestId: 1, - payload: { - baseTokenAddress: checksummedAddress, - quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - }, - }, - { - type: 'subscribe', - channel: 'orderbook', - requestId: 1, - payload: { - baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - quoteTokenAddress: checksummedAddress, - }, - }, - { - type: 'subscribe', - channel: 'orderbook', - requestId: 1, - payload: { - quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - }, - }, - { - type: 'subscribe', - channel: 'orderbook', - requestId: 1, - payload: { - baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - }, - }, - { - type: 'subscribe', - channel: 'orderbook', - requestId: 1, - payload: { - baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - snapshot: 'true', - limit: 100, - }, - }, - { - type: 'subscribe', - channel: 'orderbook', - requestId: 1, - payload: { - baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - snapshot: true, - limit: '100', - }, - }, - ]; - const shouldFail = true; - validateAgainstSchema(testCases, relayerApiOrderbookChannelSubscribeSchema, shouldFail); - }); - }); - describe('#relayerApiOrderbookChannelSnapshotSchema', () => { - it('should validate valid orderbook channel websocket snapshot message', () => { - const testCases = [ - { - type: 'snapshot', - channel: 'orderbook', - requestId: 2, - payload: { - bids: [], - asks: [], - }, - }, - { - type: 'snapshot', - channel: 'orderbook', - requestId: 2, - payload: { - bids: [signedOrder], - asks: [signedOrder], - }, - }, - ]; - validateAgainstSchema(testCases, relayerApiOrderbookChannelSnapshotSchema); - }); - it('should fail for invalid orderbook channel websocket snapshot message', () => { - const testCases = [ - { - type: 'foo', - channel: 'orderbook', - requestId: 2, - payload: { - bids: [signedOrder], - asks: [signedOrder], - }, - }, - { - type: 'snapshot', - channel: 'bar', - requestId: 2, - payload: { - bids: [signedOrder], - asks: [signedOrder], - }, - }, - { - type: 'snapshot', - channel: 'orderbook', - payload: { - bids: [signedOrder], - asks: [signedOrder], - }, - }, - { - type: 'snapshot', - channel: 'orderbook', - requestId: '2', - payload: { - bids: [signedOrder], - asks: [signedOrder], - }, - }, - { - type: 'snapshot', - channel: 'orderbook', - requestId: 2, - payload: { - bids: [signedOrder], - }, - }, - { - type: 'snapshot', - channel: 'orderbook', - requestId: 2, - payload: { - asks: [signedOrder], - }, - }, - { - type: 'snapshot', - channel: 'orderbook', - requestId: 2, - payload: { - bids: [signedOrder], - asks: [{}], - }, - }, - { - type: 'snapshot', - channel: 'orderbook', - requestId: 2, - payload: { - bids: [{}], - asks: [signedOrder], - }, - }, - ]; - const shouldFail = true; - validateAgainstSchema(testCases, relayerApiOrderbookChannelSnapshotSchema, shouldFail); - }); - }); - describe('#relayerApiOrderbookChannelUpdateSchema', () => { - it('should validate valid orderbook channel websocket update message', () => { - const testCases = [ - { - type: 'update', - channel: 'orderbook', - requestId: 2, - payload: signedOrder, - }, - ]; - validateAgainstSchema(testCases, relayerApiOrderbookChannelUpdateSchema); - }); - it('should fail for invalid orderbook channel websocket update message', () => { - const testCases = [ - { - type: 'foo', - channel: 'orderbook', - requestId: 2, - payload: signedOrder, - }, - { - type: 'update', - channel: 'bar', - requestId: 2, - payload: signedOrder, - }, - { - type: 'update', - channel: 'orderbook', - requestId: 2, - payload: {}, - }, - ]; - const shouldFail = true; - validateAgainstSchema(testCases, relayerApiOrderbookChannelUpdateSchema, shouldFail); - }); - }); - }); - }); - describe('BigNumber serialization', () => { - it('should correctly serialize BigNumbers', () => { - const testCases = { - '42': '42', - '0': '0', - '1.3': '1.3', - '0.2': '0.2', - '00.00': '0', - '.3': '0.3', - }; - forEach(testCases, (serialized: string, input: string) => { - expect(JSON.parse(JSON.stringify(new BigNumber(input)))).to.be.equal(serialized); - }); - }); - }); - describe('#relayerApiErrorResponseSchema', () => { - it('should validate valid errorResponse', () => { - const testCases = [ - { - code: 102, - reason: 'Order submission disabled', - }, - { - code: 101, - reason: 'Validation failed', - validationErrors: [ - { - field: 'maker', - code: 1002, - reason: 'Invalid address', - }, - ], - }, - ]; - validateAgainstSchema(testCases, relayerApiErrorResponseSchema); - }); - it('should fail for invalid error responses', () => { - const testCases = [ - {}, - { - code: 102, - }, - { - code: '102', - reason: 'Order submission disabled', - }, - { - reason: 'Order submission disabled', - }, - { - code: 101, - reason: 'Validation failed', - validationErrors: [ - { - field: 'maker', - reason: 'Invalid address', - }, - ], - }, - { - code: 101, - reason: 'Validation failed', - validationErrors: [ - { - field: 'maker', - code: '1002', - reason: 'Invalid address', - }, - ], - }, - ]; - const shouldFail = true; - validateAgainstSchema(testCases, relayerApiErrorResponseSchema, shouldFail); - }); - }); - describe('#relayerApiFeesPayloadSchema', () => { - it('should validate valid fees payloads', () => { - const testCases = [ - { - exchangeContractAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - maker: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - taker: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', - makerTokenAmount: '10000000000000000000', - takerTokenAmount: '30000000000000000000', - expirationUnixTimestampSec: '42', - salt: '67006738228878699843088602623665307406148487219438534730168799356281242528500', - }, - ]; - validateAgainstSchema(testCases, relayerApiFeesPayloadSchema); - }); - it('should fail for invalid fees payloads', () => { - const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32'; - const testCases = [ - {}, - { - takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', - makerTokenAmount: '10000000000000000000', - takerTokenAmount: '30000000000000000000', - }, - { - taker: checksummedAddress, - makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', - makerTokenAmount: '10000000000000000000', - takerTokenAmount: '30000000000000000000', - }, - { - makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', - makerTokenAmount: 10000000000000000000, - takerTokenAmount: 30000000000000000000, - }, - ]; - const shouldFail = true; - validateAgainstSchema(testCases, relayerApiFeesPayloadSchema, shouldFail); - }); - }); - describe('#relayerApiFeesResponseSchema', () => { - it('should validate valid fees responses', () => { - const testCases = [ - { - makerFee: '10000000000000000', - takerFee: '30000000000000000', - feeRecipient: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - }, - ]; - validateAgainstSchema(testCases, relayerApiFeesResponseSchema); - }); - it('should fail for invalid fees responses', () => { - const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32'; - const testCases = [ - {}, - { - makerFee: 10000000000000000, - takerFee: 30000000000000000, - }, - { - feeRecipient: checksummedAddress, - takerToSpecify: checksummedAddress, - makerFee: '10000000000000000', - takerFee: '30000000000000000', - }, - ]; - const shouldFail = true; - validateAgainstSchema(testCases, relayerApiFeesResponseSchema, shouldFail); - }); - }); - describe('#relayerApiTokenPairsResponseSchema', () => { - it('should validate valid tokenPairs response', () => { - const testCases = [ - [], - [ - { - tokenA: { - address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - minAmount: '0', - maxAmount: '10000000000000000000', - precision: 5, - }, - tokenB: { - address: '0xef7fff64389b814a946f3e92105513705ca6b990', - minAmount: '0', - maxAmount: '50000000000000000000', - precision: 5, - }, - }, - ], - [ - { - tokenA: { - address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - }, - tokenB: { - address: '0xef7fff64389b814a946f3e92105513705ca6b990', - }, - }, - ], - ]; - validateAgainstSchema(testCases, relayerApiTokenPairsResponseSchema); - }); - it('should fail for invalid tokenPairs responses', () => { - const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32'; - const testCases = [ - [ - { - tokenA: { - address: checksummedAddress, - }, - tokenB: { - address: checksummedAddress, - }, - }, - ], - [ - { - tokenA: { - address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - minAmount: 0, - maxAmount: 10000000000000000000, - }, - tokenB: { - address: '0xef7fff64389b814a946f3e92105513705ca6b990', - minAmount: 0, - maxAmount: 50000000000000000000, - }, - }, - ], - [ - { - tokenA: { - address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', - precision: '5', - }, - tokenB: { - address: '0xef7fff64389b814a946f3e92105513705ca6b990', - precision: '5', - }, - }, - ], - ]; - const shouldFail = true; - validateAgainstSchema(testCases, relayerApiTokenPairsResponseSchema, shouldFail); - }); - }); - describe('#jsNumberSchema', () => { - it('should validate valid js number', () => { - const testCases = [1, 42]; - validateAgainstSchema(testCases, jsNumber); - }); - it('should fail for invalid js number', () => { - const testCases = [NaN, -1, new BigNumber(1)]; - const shouldFail = true; - validateAgainstSchema(testCases, jsNumber, shouldFail); - }); - }); - describe('#txDataSchema', () => { - it('should validate valid txData', () => { - const testCases = [ - { - from: NULL_ADDRESS, - }, - { - from: NULL_ADDRESS, - gas: new BigNumber(42), - }, - { - from: NULL_ADDRESS, - gas: 42, - }, - ]; - validateAgainstSchema(testCases, txDataSchema); - }); - it('should fail for invalid txData', () => { - const testCases = [ - { - gas: new BigNumber(42), - }, - { - from: NULL_ADDRESS, - unknownProp: 'here', - }, - {}, - [], - new BigNumber(1), - ]; - const shouldFail = true; - validateAgainstSchema(testCases, txDataSchema, shouldFail); - }); - }); + const validator = new SchemaValidator(); + const validateAgainstSchema = (testCases: any[], schema: any, shouldFail = false) => { + forEach(testCases, (testCase: any) => { + const validationResult = validator.validate(testCase, schema); + const hasErrors = validationResult.errors.length !== 0; + if (shouldFail) { + if (!hasErrors) { + throw new Error( + `Expected testCase: ${JSON.stringify(testCase, null, '\t')} to fail and it didn't.`, + ); + } + } else { + if (hasErrors) { + throw new Error(JSON.stringify(validationResult.errors, null, '\t')); + } + } + }); + }; + describe('#numberSchema', () => { + it('should validate valid numbers', () => { + const testCases = ['42', '0', '1.3', '0.2', '00.00']; + validateAgainstSchema(testCases, numberSchema); + }); + it('should fail for invalid numbers', () => { + const testCases = ['.3', '1.', 'abacaba', 'и', '1..0']; + const shouldFail = true; + validateAgainstSchema(testCases, numberSchema, shouldFail); + }); + }); + describe('#addressSchema', () => { + it('should validate valid addresses', () => { + const testCases = ['0x8b0292b11a196601ed2ce54b665cafeca0347d42', NULL_ADDRESS]; + validateAgainstSchema(testCases, addressSchema); + }); + it('should fail for invalid addresses', () => { + const testCases = [ + '0x', + '0', + '0x00', + '0xzzzzzzB11a196601eD2ce54B665CaFEca0347D42', + '0x8b0292B11a196601eD2ce54B665CaFEca0347D42', + ]; + const shouldFail = true; + validateAgainstSchema(testCases, addressSchema, shouldFail); + }); + }); + describe('#ecSignatureParameterSchema', () => { + it('should validate valid parameters', () => { + const testCases = [ + '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', + '0X40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + ]; + validateAgainstSchema(testCases, ecSignatureParameterSchema); + }); + it('should fail for invalid parameters', () => { + const testCases = [ + '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3', // shorter + '0xzzzz9190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', // invalid characters + '40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', // no 0x + ]; + const shouldFail = true; + validateAgainstSchema(testCases, ecSignatureParameterSchema, shouldFail); + }); + }); + describe('#ecSignatureSchema', () => { + it('should validate valid signature', () => { + const signature = { + v: 27, + r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', + s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + }; + const testCases = [ + signature, + { + ...signature, + v: 28, + }, + ]; + validateAgainstSchema(testCases, ecSignatureSchema); + }); + it('should fail for invalid signature', () => { + const v = 27; + const r = '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33'; + const s = '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254'; + const testCases = [{}, { v }, { r, s, v: 31 }]; + const shouldFail = true; + validateAgainstSchema(testCases, ecSignatureSchema, shouldFail); + }); + }); + describe('#orderHashSchema', () => { + it('should validate valid order hash', () => { + const testCases = [ + '0x61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33', + '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + ]; + validateAgainstSchema(testCases, orderHashSchema); + }); + it('should fail for invalid order hash', () => { + const testCases = [ + {}, + '0x', + '0x8b0292B11a196601eD2ce54B665CaFEca0347D42', + '61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33', + ]; + const shouldFail = true; + validateAgainstSchema(testCases, orderHashSchema, shouldFail); + }); + }); + describe('#blockParamSchema', () => { + it('should validate valid block param', () => { + const testCases = [42, 'latest', 'pending', 'earliest']; + validateAgainstSchema(testCases, blockParamSchema); + }); + it('should fail for invalid block param', () => { + const testCases = [{}, '42', 'pemding']; + const shouldFail = true; + validateAgainstSchema(testCases, blockParamSchema, shouldFail); + }); + }); + describe('#blockRangeSchema', () => { + it('should validate valid subscription opts', () => { + const testCases = [{ fromBlock: 42, toBlock: 'latest' }, { fromBlock: 42 }, {}]; + validateAgainstSchema(testCases, blockRangeSchema); + }); + it('should fail for invalid subscription opts', () => { + const testCases = [{ fromBlock: '42' }]; + const shouldFail = true; + validateAgainstSchema(testCases, blockRangeSchema, shouldFail); + }); + }); + describe('#tokenSchema', () => { + const token = { + name: 'Zero Ex', + symbol: 'ZRX', + decimals: 100500, + address: '0x8b0292b11a196601ed2ce54b665cafeca0347d42', + url: 'https://0xproject.com', + }; + it('should validate valid token', () => { + const testCases = [token]; + validateAgainstSchema(testCases, tokenSchema); + }); + it('should fail for invalid token', () => { + const testCases = [ + { + ...token, + address: null, + }, + { + ...token, + decimals: undefined, + }, + [], + 4, + ]; + const shouldFail = true; + validateAgainstSchema(testCases, tokenSchema, shouldFail); + }); + }); + describe('order including schemas', () => { + const order = { + maker: NULL_ADDRESS, + taker: NULL_ADDRESS, + makerFee: '1', + takerFee: '2', + makerTokenAmount: '1', + takerTokenAmount: '2', + makerTokenAddress: NULL_ADDRESS, + takerTokenAddress: NULL_ADDRESS, + salt: '67006738228878699843088602623665307406148487219438534730168799356281242528500', + feeRecipient: NULL_ADDRESS, + exchangeContractAddress: NULL_ADDRESS, + expirationUnixTimestampSec: '42', + }; + describe('#orderSchema', () => { + it('should validate valid order', () => { + const testCases = [order]; + validateAgainstSchema(testCases, orderSchema); + }); + it('should fail for invalid order', () => { + const testCases = [ + { + ...order, + salt: undefined, + }, + { + ...order, + salt: 'salt', + }, + 'order', + ]; + const shouldFail = true; + validateAgainstSchema(testCases, orderSchema, shouldFail); + }); + }); + describe('signed order including schemas', () => { + const signedOrder = { + ...order, + ecSignature: { + v: 27, + r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', + s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + }, + }; + describe('#signedOrdersSchema', () => { + it('should validate valid signed orders', () => { + const testCases = [[signedOrder], []]; + validateAgainstSchema(testCases, signedOrdersSchema); + }); + it('should fail for invalid signed orders', () => { + const testCases = [[signedOrder, 1]]; + const shouldFail = true; + validateAgainstSchema(testCases, signedOrdersSchema, shouldFail); + }); + }); + describe('#signedOrderSchema', () => { + it('should validate valid signed order', () => { + const testCases = [signedOrder]; + validateAgainstSchema(testCases, signedOrderSchema); + }); + it('should fail for invalid signed order', () => { + const testCases = [ + { + ...signedOrder, + ecSignature: undefined, + }, + ]; + const shouldFail = true; + validateAgainstSchema(testCases, signedOrderSchema, shouldFail); + }); + }); + describe('#orderFillOrKillRequestsSchema', () => { + const orderFillOrKillRequests = [ + { + signedOrder, + fillTakerAmount: '5', + }, + ]; + it('should validate valid order fill or kill requests', () => { + const testCases = [orderFillOrKillRequests]; + validateAgainstSchema(testCases, orderFillOrKillRequestsSchema); + }); + it('should fail for invalid order fill or kill requests', () => { + const testCases = [ + [ + { + ...orderFillOrKillRequests[0], + fillTakerAmount: undefined, + }, + ], + ]; + const shouldFail = true; + validateAgainstSchema(testCases, orderFillOrKillRequestsSchema, shouldFail); + }); + }); + describe('#orderCancellationRequestsSchema', () => { + const orderCancellationRequests = [ + { + order, + takerTokenCancelAmount: '5', + }, + ]; + it('should validate valid order cancellation requests', () => { + const testCases = [orderCancellationRequests]; + validateAgainstSchema(testCases, orderCancellationRequestsSchema); + }); + it('should fail for invalid order cancellation requests', () => { + const testCases = [ + [ + { + ...orderCancellationRequests[0], + takerTokenCancelAmount: undefined, + }, + ], + ]; + const shouldFail = true; + validateAgainstSchema(testCases, orderCancellationRequestsSchema, shouldFail); + }); + }); + describe('#orderFillRequestsSchema', () => { + const orderFillRequests = [ + { + signedOrder, + takerTokenFillAmount: '5', + }, + ]; + it('should validate valid order fill requests', () => { + const testCases = [orderFillRequests]; + validateAgainstSchema(testCases, orderFillRequestsSchema); + }); + it('should fail for invalid order fill requests', () => { + const testCases = [ + [ + { + ...orderFillRequests[0], + takerTokenFillAmount: undefined, + }, + ], + ]; + const shouldFail = true; + validateAgainstSchema(testCases, orderFillRequestsSchema, shouldFail); + }); + }); + describe('#relayerApiOrderBookResponseSchema', () => { + it('should validate valid order book responses', () => { + const testCases = [ + { + bids: [], + asks: [], + }, + { + bids: [signedOrder, signedOrder], + asks: [], + }, + { + bids: [], + asks: [signedOrder, signedOrder], + }, + { + bids: [signedOrder], + asks: [signedOrder, signedOrder], + }, + ]; + validateAgainstSchema(testCases, relayerApiOrderBookResponseSchema); + }); + it('should fail for invalid order fill requests', () => { + const testCases = [ + {}, + { + bids: [signedOrder, signedOrder], + }, + { + asks: [signedOrder, signedOrder], + }, + { + bids: signedOrder, + asks: [signedOrder, signedOrder], + }, + { + bids: [signedOrder], + asks: signedOrder, + }, + ]; + const shouldFail = true; + validateAgainstSchema(testCases, relayerApiOrderBookResponseSchema, shouldFail); + }); + }); + describe('#relayerApiOrderbookChannelSubscribeSchema', () => { + it('should validate valid orderbook channel websocket subscribe message', () => { + const testCases = [ + { + type: 'subscribe', + channel: 'orderbook', + requestId: 1, + payload: { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + snapshot: true, + limit: 100, + }, + }, + { + type: 'subscribe', + channel: 'orderbook', + requestId: 1, + payload: { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + }, + }, + ]; + validateAgainstSchema(testCases, relayerApiOrderbookChannelSubscribeSchema); + }); + it('should fail for invalid orderbook channel websocket subscribe message', () => { + const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32'; + const testCases = [ + { + type: 'subscribe', + channel: 'orderbook', + payload: { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + snapshot: true, + limit: 100, + }, + }, + { + type: 'foo', + channel: 'orderbook', + requestId: 1, + payload: { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + }, + }, + { + type: 'subscribe', + channel: 'bar', + requestId: 1, + payload: { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + }, + }, + { + type: 'subscribe', + channel: 'orderbook', + requestId: 1, + payload: { + baseTokenAddress: checksummedAddress, + quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + }, + }, + { + type: 'subscribe', + channel: 'orderbook', + requestId: 1, + payload: { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + quoteTokenAddress: checksummedAddress, + }, + }, + { + type: 'subscribe', + channel: 'orderbook', + requestId: 1, + payload: { + quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + }, + }, + { + type: 'subscribe', + channel: 'orderbook', + requestId: 1, + payload: { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + }, + }, + { + type: 'subscribe', + channel: 'orderbook', + requestId: 1, + payload: { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + snapshot: 'true', + limit: 100, + }, + }, + { + type: 'subscribe', + channel: 'orderbook', + requestId: 1, + payload: { + baseTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + quoteTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + snapshot: true, + limit: '100', + }, + }, + ]; + const shouldFail = true; + validateAgainstSchema(testCases, relayerApiOrderbookChannelSubscribeSchema, shouldFail); + }); + }); + describe('#relayerApiOrderbookChannelSnapshotSchema', () => { + it('should validate valid orderbook channel websocket snapshot message', () => { + const testCases = [ + { + type: 'snapshot', + channel: 'orderbook', + requestId: 2, + payload: { + bids: [], + asks: [], + }, + }, + { + type: 'snapshot', + channel: 'orderbook', + requestId: 2, + payload: { + bids: [signedOrder], + asks: [signedOrder], + }, + }, + ]; + validateAgainstSchema(testCases, relayerApiOrderbookChannelSnapshotSchema); + }); + it('should fail for invalid orderbook channel websocket snapshot message', () => { + const testCases = [ + { + type: 'foo', + channel: 'orderbook', + requestId: 2, + payload: { + bids: [signedOrder], + asks: [signedOrder], + }, + }, + { + type: 'snapshot', + channel: 'bar', + requestId: 2, + payload: { + bids: [signedOrder], + asks: [signedOrder], + }, + }, + { + type: 'snapshot', + channel: 'orderbook', + payload: { + bids: [signedOrder], + asks: [signedOrder], + }, + }, + { + type: 'snapshot', + channel: 'orderbook', + requestId: '2', + payload: { + bids: [signedOrder], + asks: [signedOrder], + }, + }, + { + type: 'snapshot', + channel: 'orderbook', + requestId: 2, + payload: { + bids: [signedOrder], + }, + }, + { + type: 'snapshot', + channel: 'orderbook', + requestId: 2, + payload: { + asks: [signedOrder], + }, + }, + { + type: 'snapshot', + channel: 'orderbook', + requestId: 2, + payload: { + bids: [signedOrder], + asks: [{}], + }, + }, + { + type: 'snapshot', + channel: 'orderbook', + requestId: 2, + payload: { + bids: [{}], + asks: [signedOrder], + }, + }, + ]; + const shouldFail = true; + validateAgainstSchema(testCases, relayerApiOrderbookChannelSnapshotSchema, shouldFail); + }); + }); + describe('#relayerApiOrderbookChannelUpdateSchema', () => { + it('should validate valid orderbook channel websocket update message', () => { + const testCases = [ + { + type: 'update', + channel: 'orderbook', + requestId: 2, + payload: signedOrder, + }, + ]; + validateAgainstSchema(testCases, relayerApiOrderbookChannelUpdateSchema); + }); + it('should fail for invalid orderbook channel websocket update message', () => { + const testCases = [ + { + type: 'foo', + channel: 'orderbook', + requestId: 2, + payload: signedOrder, + }, + { + type: 'update', + channel: 'bar', + requestId: 2, + payload: signedOrder, + }, + { + type: 'update', + channel: 'orderbook', + requestId: 2, + payload: {}, + }, + ]; + const shouldFail = true; + validateAgainstSchema(testCases, relayerApiOrderbookChannelUpdateSchema, shouldFail); + }); + }); + }); + }); + describe('BigNumber serialization', () => { + it('should correctly serialize BigNumbers', () => { + const testCases = { + '42': '42', + '0': '0', + '1.3': '1.3', + '0.2': '0.2', + '00.00': '0', + '.3': '0.3', + }; + forEach(testCases, (serialized: string, input: string) => { + expect(JSON.parse(JSON.stringify(new BigNumber(input)))).to.be.equal(serialized); + }); + }); + }); + describe('#relayerApiErrorResponseSchema', () => { + it('should validate valid errorResponse', () => { + const testCases = [ + { + code: 102, + reason: 'Order submission disabled', + }, + { + code: 101, + reason: 'Validation failed', + validationErrors: [ + { + field: 'maker', + code: 1002, + reason: 'Invalid address', + }, + ], + }, + ]; + validateAgainstSchema(testCases, relayerApiErrorResponseSchema); + }); + it('should fail for invalid error responses', () => { + const testCases = [ + {}, + { + code: 102, + }, + { + code: '102', + reason: 'Order submission disabled', + }, + { + reason: 'Order submission disabled', + }, + { + code: 101, + reason: 'Validation failed', + validationErrors: [ + { + field: 'maker', + reason: 'Invalid address', + }, + ], + }, + { + code: 101, + reason: 'Validation failed', + validationErrors: [ + { + field: 'maker', + code: '1002', + reason: 'Invalid address', + }, + ], + }, + ]; + const shouldFail = true; + validateAgainstSchema(testCases, relayerApiErrorResponseSchema, shouldFail); + }); + }); + describe('#relayerApiFeesPayloadSchema', () => { + it('should validate valid fees payloads', () => { + const testCases = [ + { + exchangeContractAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + maker: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + taker: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', + makerTokenAmount: '10000000000000000000', + takerTokenAmount: '30000000000000000000', + expirationUnixTimestampSec: '42', + salt: '67006738228878699843088602623665307406148487219438534730168799356281242528500', + }, + ]; + validateAgainstSchema(testCases, relayerApiFeesPayloadSchema); + }); + it('should fail for invalid fees payloads', () => { + const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32'; + const testCases = [ + {}, + { + takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', + makerTokenAmount: '10000000000000000000', + takerTokenAmount: '30000000000000000000', + }, + { + taker: checksummedAddress, + makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', + makerTokenAmount: '10000000000000000000', + takerTokenAmount: '30000000000000000000', + }, + { + makerTokenAddress: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + takerTokenAddress: '0xef7fff64389b814a946f3e92105513705ca6b990', + makerTokenAmount: 10000000000000000000, + takerTokenAmount: 30000000000000000000, + }, + ]; + const shouldFail = true; + validateAgainstSchema(testCases, relayerApiFeesPayloadSchema, shouldFail); + }); + }); + describe('#relayerApiFeesResponseSchema', () => { + it('should validate valid fees responses', () => { + const testCases = [ + { + makerFee: '10000000000000000', + takerFee: '30000000000000000', + feeRecipient: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + }, + ]; + validateAgainstSchema(testCases, relayerApiFeesResponseSchema); + }); + it('should fail for invalid fees responses', () => { + const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32'; + const testCases = [ + {}, + { + makerFee: 10000000000000000, + takerFee: 30000000000000000, + }, + { + feeRecipient: checksummedAddress, + takerToSpecify: checksummedAddress, + makerFee: '10000000000000000', + takerFee: '30000000000000000', + }, + ]; + const shouldFail = true; + validateAgainstSchema(testCases, relayerApiFeesResponseSchema, shouldFail); + }); + }); + describe('#relayerApiTokenPairsResponseSchema', () => { + it('should validate valid tokenPairs response', () => { + const testCases = [ + [], + [ + { + tokenA: { + address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + minAmount: '0', + maxAmount: '10000000000000000000', + precision: 5, + }, + tokenB: { + address: '0xef7fff64389b814a946f3e92105513705ca6b990', + minAmount: '0', + maxAmount: '50000000000000000000', + precision: 5, + }, + }, + ], + [ + { + tokenA: { + address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + }, + tokenB: { + address: '0xef7fff64389b814a946f3e92105513705ca6b990', + }, + }, + ], + ]; + validateAgainstSchema(testCases, relayerApiTokenPairsResponseSchema); + }); + it('should fail for invalid tokenPairs responses', () => { + const checksummedAddress = '0xA2b31daCf30a9C50ca473337c01d8A201ae33e32'; + const testCases = [ + [ + { + tokenA: { + address: checksummedAddress, + }, + tokenB: { + address: checksummedAddress, + }, + }, + ], + [ + { + tokenA: { + address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + minAmount: 0, + maxAmount: 10000000000000000000, + }, + tokenB: { + address: '0xef7fff64389b814a946f3e92105513705ca6b990', + minAmount: 0, + maxAmount: 50000000000000000000, + }, + }, + ], + [ + { + tokenA: { + address: '0x323b5d4c32345ced77393b3530b1eed0f346429d', + precision: '5', + }, + tokenB: { + address: '0xef7fff64389b814a946f3e92105513705ca6b990', + precision: '5', + }, + }, + ], + ]; + const shouldFail = true; + validateAgainstSchema(testCases, relayerApiTokenPairsResponseSchema, shouldFail); + }); + }); + describe('#jsNumberSchema', () => { + it('should validate valid js number', () => { + const testCases = [1, 42]; + validateAgainstSchema(testCases, jsNumber); + }); + it('should fail for invalid js number', () => { + const testCases = [NaN, -1, new BigNumber(1)]; + const shouldFail = true; + validateAgainstSchema(testCases, jsNumber, shouldFail); + }); + }); + describe('#txDataSchema', () => { + it('should validate valid txData', () => { + const testCases = [ + { + from: NULL_ADDRESS, + }, + { + from: NULL_ADDRESS, + gas: new BigNumber(42), + }, + { + from: NULL_ADDRESS, + gas: 42, + }, + ]; + validateAgainstSchema(testCases, txDataSchema); + }); + it('should fail for invalid txData', () => { + const testCases = [ + { + gas: new BigNumber(42), + }, + { + from: NULL_ADDRESS, + unknownProp: 'here', + }, + {}, + [], + new BigNumber(1), + ]; + const shouldFail = true; + validateAgainstSchema(testCases, txDataSchema, shouldFail); + }); + }); }); // tslint:disable:max-file-line-count diff --git a/packages/json-schemas/tsconfig.json b/packages/json-schemas/tsconfig.json index 8314a9459..88a467ccb 100644 --- a/packages/json-schemas/tsconfig.json +++ b/packages/json-schemas/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./src/**/*", "./test/**/*", "../../node_modules/chai-typescript-typings/index.d.ts"] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*", "./test/**/*", "../../node_modules/chai-typescript-typings/index.d.ts"] } diff --git a/packages/json-schemas/tslint.json b/packages/json-schemas/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/json-schemas/tslint.json +++ b/packages/json-schemas/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index 31eaf6eca..2bf2aa796 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -1,34 +1,34 @@ { - "name": "@0xproject/monorepo-scripts", - "version": "0.1.6", - "private": true, - "description": "Helper scripts for the monorepo", - "scripts": { - "deps_versions": "node ./lib/deps_versions.js", - "lint": "tslint --project . 'src/**/*.ts'", - "clean": "shx rm -rf lib", - "build": "tsc" - }, - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/monorepo-scripts/README.md", - "devDependencies": { - "@0xproject/tslint-config": "^0.4.4", - "@types/glob": "^5.0.33", - "@types/node": "^8.0.53", - "shx": "^0.2.2", - "tslint": "5.8.0", - "typescript": "~2.6.1" - }, - "dependencies": { - "chalk": "^2.3.0", - "glob": "^7.1.2", - "lodash": "^4.17.4" - } + "name": "@0xproject/monorepo-scripts", + "version": "0.1.6", + "private": true, + "description": "Helper scripts for the monorepo", + "scripts": { + "deps_versions": "node ./lib/deps_versions.js", + "lint": "tslint --project . 'src/**/*.ts'", + "clean": "shx rm -rf lib", + "build": "tsc" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/monorepo-scripts/README.md", + "devDependencies": { + "@0xproject/tslint-config": "^0.4.4", + "@types/glob": "^5.0.33", + "@types/node": "^8.0.53", + "shx": "^0.2.2", + "tslint": "5.8.0", + "typescript": "~2.6.1" + }, + "dependencies": { + "chalk": "^2.3.0", + "glob": "^7.1.2", + "lodash": "^4.17.4" + } } diff --git a/packages/monorepo-scripts/src/deps_versions.ts b/packages/monorepo-scripts/src/deps_versions.ts index 2c0aa6c54..5c9a2d6ff 100644 --- a/packages/monorepo-scripts/src/deps_versions.ts +++ b/packages/monorepo-scripts/src/deps_versions.ts @@ -6,51 +6,51 @@ import { sync as globSync } from 'glob'; import * as _ from 'lodash'; interface Dependencies { - [depName: string]: string; + [depName: string]: string; } interface Versions { - [packageName: string]: string; + [packageName: string]: string; } interface VersionsByDependency { - [depName: string]: Versions; + [depName: string]: Versions; } const PACKAGE_JSON_GLOB = '../*/package.json'; function log(...args: any[]) { - console.log(...args); // tslint:disable-line:no-console + console.log(...args); // tslint:disable-line:no-console } function getDependencies(path: string): Dependencies { - const file = fs.readFileSync(path).toString(); - const parsed = JSON.parse(file); - const dependencies = { - ...parsed.dependencies, - ...parsed.devDependencies, - }; - return dependencies; + const file = fs.readFileSync(path).toString(); + const parsed = JSON.parse(file); + const dependencies = { + ...parsed.dependencies, + ...parsed.devDependencies, + }; + return dependencies; } const files = globSync(PACKAGE_JSON_GLOB); const versionsByDependency: VersionsByDependency = {}; files.map(path => { - const [_1, packageName, _2] = path.split('/'); - const dependencies = getDependencies(path); - _.map(dependencies, (version: string, depName: string) => { - if (_.isUndefined(versionsByDependency[depName])) { - versionsByDependency[depName] = {}; - } - versionsByDependency[depName][packageName] = version; - }); + const [_1, packageName, _2] = path.split('/'); + const dependencies = getDependencies(path); + _.map(dependencies, (version: string, depName: string) => { + if (_.isUndefined(versionsByDependency[depName])) { + versionsByDependency[depName] = {}; + } + versionsByDependency[depName][packageName] = version; + }); }); _.map(versionsByDependency, (versions: Versions, depName: string) => { - if (_.uniq(_.values(versions)).length === 1) { - delete versionsByDependency[depName]; - } else { - log(chalk.bold(depName)); - _.map(versions, (version: string, packageName: string) => { - log(`├── ${packageName} -> ${version}`); - }); - } + if (_.uniq(_.values(versions)).length === 1) { + delete versionsByDependency[depName]; + } else { + log(chalk.bold(depName)); + _.map(versions, (version: string, packageName: string) => { + log(`├── ${packageName} -> ${version}`); + }); + } }); diff --git a/packages/monorepo-scripts/tsconfig.json b/packages/monorepo-scripts/tsconfig.json index ad674c2b0..c56d255d5 100644 --- a/packages/monorepo-scripts/tsconfig.json +++ b/packages/monorepo-scripts/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./src/**/*"] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*"] } diff --git a/packages/monorepo-scripts/tslint.json b/packages/monorepo-scripts/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/monorepo-scripts/tslint.json +++ b/packages/monorepo-scripts/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index 1da019e2d..146c94496 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -1,55 +1,55 @@ { - "name": "@0xproject/subproviders", - "version": "0.3.3", - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", - "license": "Apache-2.0", - "scripts": { - "clean": "shx rm -rf lib", - "build": "tsc", - "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", - "run_mocha_unit": "mocha lib/test/unit/**/*_test.js --timeout 10000 --bail --exit", - "run_mocha_integration": "mocha lib/test/integration/**/*_test.js --timeout 10000 --bail --exit", - "test": "npm run test:unit", - "test:circleci": "npm run test:unit", - "test:all": "run-s test:unit test:integration", - "test:unit": "run-s clean build run_mocha_unit", - "test:integration": "run-s clean build run_mocha_integration" - }, - "dependencies": { - "@0xproject/assert": "^0.0.13", - "@0xproject/utils": "^0.2.2", - "bn.js": "^4.11.8", - "es6-promisify": "^5.0.0", - "ethereumjs-tx": "^1.3.3", - "ethereumjs-util": "^5.1.1", - "hdkey": "^0.7.1", - "ledgerco": "0xProject/ledger-node-js-api", - "lodash": "^4.17.4", - "semaphore-async-await": "^1.5.1", - "web3": "^0.20.0", - "web3-provider-engine": "^13.0.1" - }, - "devDependencies": { - "@0xproject/tslint-config": "^0.4.4", - "@0xproject/utils": "^0.2.2", - "@types/lodash": "^4.14.86", - "@types/mocha": "^2.2.42", - "@types/node": "^8.0.53", - "awesome-typescript-loader": "^3.1.3", - "chai": "^4.0.1", - "chai-as-promised": "^7.1.0", - "chai-as-promised-typescript-typings": "^0.0.6", - "chai-typescript-typings": "^0.0.2", - "dirty-chai": "^2.0.1", - "mocha": "^4.0.1", - "npm-run-all": "^4.1.2", - "shx": "^0.2.2", - "tslint": "5.8.0", - "types-bn": "^0.0.1", - "types-ethereumjs-util": "0xproject/types-ethereumjs-util", - "typescript": "~2.6.1", - "web3-typescript-typings": "^0.7.2", - "webpack": "^3.1.0" - } + "name": "@0xproject/subproviders", + "version": "0.3.3", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "license": "Apache-2.0", + "scripts": { + "clean": "shx rm -rf lib", + "build": "tsc", + "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", + "run_mocha_unit": "mocha lib/test/unit/**/*_test.js --timeout 10000 --bail --exit", + "run_mocha_integration": "mocha lib/test/integration/**/*_test.js --timeout 10000 --bail --exit", + "test": "npm run test:unit", + "test:circleci": "npm run test:unit", + "test:all": "run-s test:unit test:integration", + "test:unit": "run-s clean build run_mocha_unit", + "test:integration": "run-s clean build run_mocha_integration" + }, + "dependencies": { + "@0xproject/assert": "^0.0.13", + "@0xproject/utils": "^0.2.2", + "bn.js": "^4.11.8", + "es6-promisify": "^5.0.0", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.1", + "hdkey": "^0.7.1", + "ledgerco": "0xProject/ledger-node-js-api", + "lodash": "^4.17.4", + "semaphore-async-await": "^1.5.1", + "web3": "^0.20.0", + "web3-provider-engine": "^13.0.1" + }, + "devDependencies": { + "@0xproject/tslint-config": "^0.4.4", + "@0xproject/utils": "^0.2.2", + "@types/lodash": "^4.14.86", + "@types/mocha": "^2.2.42", + "@types/node": "^8.0.53", + "awesome-typescript-loader": "^3.1.3", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-as-promised-typescript-typings": "^0.0.6", + "chai-typescript-typings": "^0.0.2", + "dirty-chai": "^2.0.1", + "mocha": "^4.0.1", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.8.0", + "types-bn": "^0.0.1", + "types-ethereumjs-util": "0xproject/types-ethereumjs-util", + "typescript": "~2.6.1", + "web3-typescript-typings": "^0.7.2", + "webpack": "^3.1.0" + } } diff --git a/packages/subproviders/src/globals.d.ts b/packages/subproviders/src/globals.d.ts index ed4fbc145..53457fa24 100644 --- a/packages/subproviders/src/globals.d.ts +++ b/packages/subproviders/src/globals.d.ts @@ -8,103 +8,103 @@ declare module 'es6-promisify'; // Ethereumjs-tx declarations declare module 'ethereumjs-tx' { - class EthereumTx { - public raw: Buffer[]; - public r: Buffer; - public s: Buffer; - public v: Buffer; - public serialize(): Buffer; - constructor(txParams: any); - } - export = EthereumTx; + class EthereumTx { + public raw: Buffer[]; + public r: Buffer; + public s: Buffer; + public v: Buffer; + public serialize(): Buffer; + constructor(txParams: any); + } + export = EthereumTx; } // Ledgerco declarations interface ECSignatureString { - v: string; - r: string; - s: string; + v: string; + r: string; + s: string; } interface ECSignature { - v: number; - r: string; - s: string; + v: number; + r: string; + s: string; } declare module 'ledgerco' { - interface comm { - close_async(): Promise<void>; - } - export class comm_node implements comm { - public static create_async(timeoutMilliseconds?: number): Promise<comm_node>; - public close_async(): Promise<void>; - } - export class comm_u2f implements comm { - public static create_async(): Promise<comm_u2f>; - public close_async(): Promise<void>; - } - export class eth { - public comm: comm; - constructor(comm: comm); - public getAddress_async( - path: string, - display?: boolean, - chaincode?: boolean, - ): Promise<{ publicKey: string; address: string; chainCode: string }>; - public signTransaction_async(path: string, rawTxHex: string): Promise<ECSignatureString>; - public getAppConfiguration_async(): Promise<{ - arbitraryDataEnabled: number; - version: string; - }>; - public signPersonalMessage_async(path: string, messageHex: string): Promise<ECSignature>; - } + interface comm { + close_async(): Promise<void>; + } + export class comm_node implements comm { + public static create_async(timeoutMilliseconds?: number): Promise<comm_node>; + public close_async(): Promise<void>; + } + export class comm_u2f implements comm { + public static create_async(): Promise<comm_u2f>; + public close_async(): Promise<void>; + } + export class eth { + public comm: comm; + constructor(comm: comm); + public getAddress_async( + path: string, + display?: boolean, + chaincode?: boolean, + ): Promise<{ publicKey: string; address: string; chainCode: string }>; + public signTransaction_async(path: string, rawTxHex: string): Promise<ECSignatureString>; + public getAppConfiguration_async(): Promise<{ + arbitraryDataEnabled: number; + version: string; + }>; + public signPersonalMessage_async(path: string, messageHex: string): Promise<ECSignature>; + } } // Semaphore-async-await declarations declare module 'semaphore-async-await' { - class Semaphore { - constructor(permits: number); - public wait(): Promise<void>; - public signal(): void; - } - export default Semaphore; + class Semaphore { + constructor(permits: number); + public wait(): Promise<void>; + public signal(): void; + } + export default Semaphore; } // web3-provider-engine declarations declare module 'web3-provider-engine/subproviders/subprovider' { - class Subprovider {} - export = Subprovider; + class Subprovider {} + export = Subprovider; } declare module 'web3-provider-engine/subproviders/rpc' { - import * as Web3 from 'web3'; - class RpcSubprovider { - constructor(options: { rpcUrl: string }); - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, data?: any) => void, - ): void; - } - export = RpcSubprovider; + import * as Web3 from 'web3'; + class RpcSubprovider { + constructor(options: { rpcUrl: string }); + public handleRequest( + payload: Web3.JSONRPCRequestPayload, + next: () => void, + end: (err: Error | null, data?: any) => void, + ): void; + } + export = RpcSubprovider; } declare module 'web3-provider-engine' { - class Web3ProviderEngine { - public on(event: string, handler: () => void): void; - public send(payload: any): void; - public sendAsync(payload: any, callback: (error: any, response: any) => void): void; - public addProvider(provider: any): void; - public start(): void; - public stop(): void; - } - export = Web3ProviderEngine; + class Web3ProviderEngine { + public on(event: string, handler: () => void): void; + public send(payload: any): void; + public sendAsync(payload: any, callback: (error: any, response: any) => void): void; + public addProvider(provider: any): void; + public start(): void; + public stop(): void; + } + export = Web3ProviderEngine; } // hdkey declarations declare module 'hdkey' { - class HDNode { - public publicKey: Buffer; - public chainCode: Buffer; - public constructor(); - public derive(path: string): HDNode; - } - export = HDNode; + class HDNode { + public publicKey: Buffer; + public chainCode: Buffer; + public constructor(); + public derive(path: string): HDNode; + } + export = HDNode; } diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts index ecba186df..720c4362f 100644 --- a/packages/subproviders/src/index.ts +++ b/packages/subproviders/src/index.ts @@ -1,7 +1,7 @@ import { - comm_node as LedgerNodeCommunication, - comm_u2f as LedgerBrowserCommunication, - eth as LedgerEthereumClientFn, + comm_node as LedgerNodeCommunication, + comm_u2f as LedgerBrowserCommunication, + eth as LedgerEthereumClientFn, } from 'ledgerco'; import { LedgerEthereumClient } from './types'; @@ -16,9 +16,9 @@ export { ECSignature, LedgerWalletSubprovider, LedgerCommunicationClient } from * @return LedgerEthereumClient A browser client */ export async function ledgerEthereumBrowserClientFactoryAsync(): Promise<LedgerEthereumClient> { - const ledgerConnection = await LedgerBrowserCommunication.create_async(); - const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection); - return ledgerEthClient; + const ledgerConnection = await LedgerBrowserCommunication.create_async(); + const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection); + return ledgerEthClient; } /** @@ -26,7 +26,7 @@ export async function ledgerEthereumBrowserClientFactoryAsync(): Promise<LedgerE * @return LedgerEthereumClient A Node.js client */ export async function ledgerEthereumNodeJsClientFactoryAsync(): Promise<LedgerEthereumClient> { - const ledgerConnection = await LedgerNodeCommunication.create_async(); - const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection); - return ledgerEthClient; + const ledgerConnection = await LedgerNodeCommunication.create_async(); + const ledgerEthClient = new LedgerEthereumClientFn(ledgerConnection); + return ledgerEthClient; } diff --git a/packages/subproviders/src/subproviders/injected_web3.ts b/packages/subproviders/src/subproviders/injected_web3.ts index dc1b8b24c..bd29acb22 100644 --- a/packages/subproviders/src/subproviders/injected_web3.ts +++ b/packages/subproviders/src/subproviders/injected_web3.ts @@ -9,41 +9,41 @@ import Web3ProviderEngine = require('web3-provider-engine'); * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js */ export class InjectedWeb3Subprovider { - private _injectedWeb3: Web3; - constructor(injectedWeb3: Web3) { - this._injectedWeb3 = injectedWeb3; - } - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, result: any) => void, - ) { - switch (payload.method) { - case 'web3_clientVersion': - this._injectedWeb3.version.getNode(end); - return; - case 'eth_accounts': - this._injectedWeb3.eth.getAccounts(end); - return; + private _injectedWeb3: Web3; + constructor(injectedWeb3: Web3) { + this._injectedWeb3 = injectedWeb3; + } + public handleRequest( + payload: Web3.JSONRPCRequestPayload, + next: () => void, + end: (err: Error | null, result: any) => void, + ) { + switch (payload.method) { + case 'web3_clientVersion': + this._injectedWeb3.version.getNode(end); + return; + case 'eth_accounts': + this._injectedWeb3.eth.getAccounts(end); + return; - case 'eth_sendTransaction': - const [txParams] = payload.params; - this._injectedWeb3.eth.sendTransaction(txParams, end); - return; + case 'eth_sendTransaction': + const [txParams] = payload.params; + this._injectedWeb3.eth.sendTransaction(txParams, end); + return; - case 'eth_sign': - const [address, message] = payload.params; - this._injectedWeb3.eth.sign(address, message, end); - return; + case 'eth_sign': + const [address, message] = payload.params; + this._injectedWeb3.eth.sign(address, message, end); + return; - default: - next(); - return; - } - } - // Required to implement this method despite not needing it for this subprovider - // tslint:disable-next-line:prefer-function-over-method - public setEngine(engine: Web3ProviderEngine) { - // noop - } + default: + next(); + return; + } + } + // Required to implement this method despite not needing it for this subprovider + // tslint:disable-next-line:prefer-function-over-method + public setEngine(engine: Web3ProviderEngine) { + // noop + } } diff --git a/packages/subproviders/src/subproviders/ledger.ts b/packages/subproviders/src/subproviders/ledger.ts index 00649873b..7267a793e 100644 --- a/packages/subproviders/src/subproviders/ledger.ts +++ b/packages/subproviders/src/subproviders/ledger.ts @@ -8,12 +8,12 @@ import Semaphore from 'semaphore-async-await'; import Web3 = require('web3'); import { - LedgerEthereumClient, - LedgerEthereumClientFactoryAsync, - LedgerSubproviderConfigs, - LedgerSubproviderErrors, - PartialTxParams, - ResponseWithTxParams, + LedgerEthereumClient, + LedgerEthereumClientFactoryAsync, + LedgerSubproviderConfigs, + LedgerSubproviderErrors, + PartialTxParams, + ResponseWithTxParams, } from '../types'; import { Subprovider } from './subprovider'; @@ -24,284 +24,284 @@ const ASK_FOR_ON_DEVICE_CONFIRMATION = false; const SHOULD_GET_CHAIN_CODE = true; export class LedgerSubprovider extends Subprovider { - private _nonceLock: Semaphore; - private _connectionLock: Semaphore; - private _networkId: number; - private _derivationPath: string; - private _derivationPathIndex: number; - private _ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync; - private _ledgerClientIfExists?: LedgerEthereumClient; - private _shouldAlwaysAskForConfirmation: boolean; - private static _validateSender(sender: string) { - if (_.isUndefined(sender) || !addressUtils.isAddress(sender)) { - throw new Error(LedgerSubproviderErrors.SenderInvalidOrNotSupplied); - } - } - constructor(config: LedgerSubproviderConfigs) { - super(); - this._nonceLock = new Semaphore(1); - this._connectionLock = new Semaphore(1); - this._networkId = config.networkId; - this._ledgerEthereumClientFactoryAsync = config.ledgerEthereumClientFactoryAsync; - this._derivationPath = config.derivationPath || DEFAULT_DERIVATION_PATH; - this._shouldAlwaysAskForConfirmation = - !_.isUndefined(config.accountFetchingConfigs) && - !_.isUndefined(config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation) - ? config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation - : ASK_FOR_ON_DEVICE_CONFIRMATION; - this._derivationPathIndex = 0; - } - public getPath(): string { - return this._derivationPath; - } - public setPath(derivationPath: string) { - this._derivationPath = derivationPath; - } - public setPathIndex(pathIndex: number) { - this._derivationPathIndex = pathIndex; - } - public async handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, result?: any) => void, - ) { - let accounts; - let txParams; - switch (payload.method) { - case 'eth_coinbase': - try { - accounts = await this.getAccountsAsync(); - end(null, accounts[0]); - } catch (err) { - end(err); - } - return; + private _nonceLock: Semaphore; + private _connectionLock: Semaphore; + private _networkId: number; + private _derivationPath: string; + private _derivationPathIndex: number; + private _ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync; + private _ledgerClientIfExists?: LedgerEthereumClient; + private _shouldAlwaysAskForConfirmation: boolean; + private static _validateSender(sender: string) { + if (_.isUndefined(sender) || !addressUtils.isAddress(sender)) { + throw new Error(LedgerSubproviderErrors.SenderInvalidOrNotSupplied); + } + } + constructor(config: LedgerSubproviderConfigs) { + super(); + this._nonceLock = new Semaphore(1); + this._connectionLock = new Semaphore(1); + this._networkId = config.networkId; + this._ledgerEthereumClientFactoryAsync = config.ledgerEthereumClientFactoryAsync; + this._derivationPath = config.derivationPath || DEFAULT_DERIVATION_PATH; + this._shouldAlwaysAskForConfirmation = + !_.isUndefined(config.accountFetchingConfigs) && + !_.isUndefined(config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation) + ? config.accountFetchingConfigs.shouldAskForOnDeviceConfirmation + : ASK_FOR_ON_DEVICE_CONFIRMATION; + this._derivationPathIndex = 0; + } + public getPath(): string { + return this._derivationPath; + } + public setPath(derivationPath: string) { + this._derivationPath = derivationPath; + } + public setPathIndex(pathIndex: number) { + this._derivationPathIndex = pathIndex; + } + public async handleRequest( + payload: Web3.JSONRPCRequestPayload, + next: () => void, + end: (err: Error | null, result?: any) => void, + ) { + let accounts; + let txParams; + switch (payload.method) { + case 'eth_coinbase': + try { + accounts = await this.getAccountsAsync(); + end(null, accounts[0]); + } catch (err) { + end(err); + } + return; - case 'eth_accounts': - try { - accounts = await this.getAccountsAsync(); - end(null, accounts); - } catch (err) { - end(err); - } - return; + case 'eth_accounts': + try { + accounts = await this.getAccountsAsync(); + end(null, accounts); + } catch (err) { + end(err); + } + return; - case 'eth_sendTransaction': - txParams = payload.params[0]; - try { - LedgerSubprovider._validateSender(txParams.from); - const result = await this._sendTransactionAsync(txParams); - end(null, result); - } catch (err) { - end(err); - } - return; + case 'eth_sendTransaction': + txParams = payload.params[0]; + try { + LedgerSubprovider._validateSender(txParams.from); + const result = await this._sendTransactionAsync(txParams); + end(null, result); + } catch (err) { + end(err); + } + return; - case 'eth_signTransaction': - txParams = payload.params[0]; - try { - const result = await this._signTransactionWithoutSendingAsync(txParams); - end(null, result); - } catch (err) { - end(err); - } - return; + case 'eth_signTransaction': + txParams = payload.params[0]; + try { + const result = await this._signTransactionWithoutSendingAsync(txParams); + end(null, result); + } catch (err) { + end(err); + } + return; - case 'eth_sign': - case 'personal_sign': - const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0]; - try { - if (_.isUndefined(data)) { - throw new Error(LedgerSubproviderErrors.DataMissingForSignPersonalMessage); - } - assert.isHexString('data', data); - const ecSignatureHex = await this.signPersonalMessageAsync(data); - end(null, ecSignatureHex); - } catch (err) { - end(err); - } - return; + case 'eth_sign': + case 'personal_sign': + const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0]; + try { + if (_.isUndefined(data)) { + throw new Error(LedgerSubproviderErrors.DataMissingForSignPersonalMessage); + } + assert.isHexString('data', data); + const ecSignatureHex = await this.signPersonalMessageAsync(data); + end(null, ecSignatureHex); + } catch (err) { + end(err); + } + return; - default: - next(); - return; - } - } - public async getAccountsAsync(): Promise<string[]> { - this._ledgerClientIfExists = await this._createLedgerClientAsync(); + default: + next(); + return; + } + } + public async getAccountsAsync(): Promise<string[]> { + this._ledgerClientIfExists = await this._createLedgerClientAsync(); - let ledgerResponse; - try { - ledgerResponse = await this._ledgerClientIfExists.getAddress_async( - this._derivationPath, - this._shouldAlwaysAskForConfirmation, - SHOULD_GET_CHAIN_CODE, - ); - } finally { - await this._destroyLedgerClientAsync(); - } + let ledgerResponse; + try { + ledgerResponse = await this._ledgerClientIfExists.getAddress_async( + this._derivationPath, + this._shouldAlwaysAskForConfirmation, + SHOULD_GET_CHAIN_CODE, + ); + } finally { + await this._destroyLedgerClientAsync(); + } - const hdKey = new HDNode(); - hdKey.publicKey = new Buffer(ledgerResponse.publicKey, 'hex'); - hdKey.chainCode = new Buffer(ledgerResponse.chainCode, 'hex'); + const hdKey = new HDNode(); + hdKey.publicKey = new Buffer(ledgerResponse.publicKey, 'hex'); + hdKey.chainCode = new Buffer(ledgerResponse.chainCode, 'hex'); - const accounts = []; - for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) { - const derivedHDNode = hdKey.derive(`m/${i + this._derivationPathIndex}`); - const derivedPublicKey = derivedHDNode.publicKey; - const shouldSanitizePublicKey = true; - const ethereumAddressUnprefixed = ethUtil - .publicToAddress(derivedPublicKey, shouldSanitizePublicKey) - .toString('hex'); - const ethereumAddressPrefixed = ethUtil.addHexPrefix(ethereumAddressUnprefixed); - accounts.push(ethereumAddressPrefixed.toLowerCase()); - } - return accounts; - } - public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { - this._ledgerClientIfExists = await this._createLedgerClientAsync(); + const accounts = []; + for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) { + const derivedHDNode = hdKey.derive(`m/${i + this._derivationPathIndex}`); + const derivedPublicKey = derivedHDNode.publicKey; + const shouldSanitizePublicKey = true; + const ethereumAddressUnprefixed = ethUtil + .publicToAddress(derivedPublicKey, shouldSanitizePublicKey) + .toString('hex'); + const ethereumAddressPrefixed = ethUtil.addHexPrefix(ethereumAddressUnprefixed); + accounts.push(ethereumAddressPrefixed.toLowerCase()); + } + return accounts; + } + public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { + this._ledgerClientIfExists = await this._createLedgerClientAsync(); - const tx = new EthereumTx(txParams); + const tx = new EthereumTx(txParams); - // Set the EIP155 bits - tx.raw[6] = Buffer.from([this._networkId]); // v - tx.raw[7] = Buffer.from([]); // r - tx.raw[8] = Buffer.from([]); // s + // Set the EIP155 bits + tx.raw[6] = Buffer.from([this._networkId]); // v + tx.raw[7] = Buffer.from([]); // r + tx.raw[8] = Buffer.from([]); // s - const txHex = tx.serialize().toString('hex'); - try { - const derivationPath = this._getDerivationPath(); - const result = await this._ledgerClientIfExists.signTransaction_async(derivationPath, txHex); - // Store signature in transaction - tx.r = Buffer.from(result.r, 'hex'); - tx.s = Buffer.from(result.s, 'hex'); - tx.v = Buffer.from(result.v, 'hex'); + const txHex = tx.serialize().toString('hex'); + try { + const derivationPath = this._getDerivationPath(); + const result = await this._ledgerClientIfExists.signTransaction_async(derivationPath, txHex); + // Store signature in transaction + tx.r = Buffer.from(result.r, 'hex'); + tx.s = Buffer.from(result.s, 'hex'); + tx.v = Buffer.from(result.v, 'hex'); - // EIP155: v should be chain_id * 2 + {35, 36} - const signedChainId = Math.floor((tx.v[0] - 35) / 2); - if (signedChainId !== this._networkId) { - await this._destroyLedgerClientAsync(); - const err = new Error(LedgerSubproviderErrors.TooOldLedgerFirmware); - throw err; - } + // EIP155: v should be chain_id * 2 + {35, 36} + const signedChainId = Math.floor((tx.v[0] - 35) / 2); + if (signedChainId !== this._networkId) { + await this._destroyLedgerClientAsync(); + const err = new Error(LedgerSubproviderErrors.TooOldLedgerFirmware); + throw err; + } - const signedTxHex = `0x${tx.serialize().toString('hex')}`; - await this._destroyLedgerClientAsync(); - return signedTxHex; - } catch (err) { - await this._destroyLedgerClientAsync(); - throw err; - } - } - public async signPersonalMessageAsync(data: string): Promise<string> { - this._ledgerClientIfExists = await this._createLedgerClientAsync(); - try { - const derivationPath = this._getDerivationPath(); - const result = await this._ledgerClientIfExists.signPersonalMessage_async( - derivationPath, - ethUtil.stripHexPrefix(data), - ); - const v = result.v - 27; - let vHex = v.toString(16); - if (vHex.length < 2) { - vHex = `0${v}`; - } - const signature = `0x${result.r}${result.s}${vHex}`; - await this._destroyLedgerClientAsync(); - return signature; - } catch (err) { - await this._destroyLedgerClientAsync(); - throw err; - } - } - private _getDerivationPath() { - const derivationPath = `${this.getPath()}/${this._derivationPathIndex}`; - return derivationPath; - } - private async _createLedgerClientAsync(): Promise<LedgerEthereumClient> { - await this._connectionLock.wait(); - if (!_.isUndefined(this._ledgerClientIfExists)) { - this._connectionLock.signal(); - throw new Error(LedgerSubproviderErrors.MultipleOpenConnectionsDisallowed); - } - const ledgerEthereumClient = await this._ledgerEthereumClientFactoryAsync(); - this._connectionLock.signal(); - return ledgerEthereumClient; - } - private async _destroyLedgerClientAsync() { - await this._connectionLock.wait(); - if (_.isUndefined(this._ledgerClientIfExists)) { - this._connectionLock.signal(); - return; - } - await this._ledgerClientIfExists.comm.close_async(); - this._ledgerClientIfExists = undefined; - this._connectionLock.signal(); - } - private async _sendTransactionAsync(txParams: PartialTxParams): Promise<string> { - await this._nonceLock.wait(); - try { - // fill in the extras - const filledParams = await this._populateMissingTxParamsAsync(txParams); - // sign it - const signedTx = await this.signTransactionAsync(filledParams); - // emit a submit - const payload = { - method: 'eth_sendRawTransaction', - params: [signedTx], - }; - const result = await this.emitPayloadAsync(payload); - this._nonceLock.signal(); - return result.result; - } catch (err) { - this._nonceLock.signal(); - throw err; - } - } - private async _signTransactionWithoutSendingAsync(txParams: PartialTxParams): Promise<ResponseWithTxParams> { - await this._nonceLock.wait(); - try { - // fill in the extras - const filledParams = await this._populateMissingTxParamsAsync(txParams); - // sign it - const signedTx = await this.signTransactionAsync(filledParams); + const signedTxHex = `0x${tx.serialize().toString('hex')}`; + await this._destroyLedgerClientAsync(); + return signedTxHex; + } catch (err) { + await this._destroyLedgerClientAsync(); + throw err; + } + } + public async signPersonalMessageAsync(data: string): Promise<string> { + this._ledgerClientIfExists = await this._createLedgerClientAsync(); + try { + const derivationPath = this._getDerivationPath(); + const result = await this._ledgerClientIfExists.signPersonalMessage_async( + derivationPath, + ethUtil.stripHexPrefix(data), + ); + const v = result.v - 27; + let vHex = v.toString(16); + if (vHex.length < 2) { + vHex = `0${v}`; + } + const signature = `0x${result.r}${result.s}${vHex}`; + await this._destroyLedgerClientAsync(); + return signature; + } catch (err) { + await this._destroyLedgerClientAsync(); + throw err; + } + } + private _getDerivationPath() { + const derivationPath = `${this.getPath()}/${this._derivationPathIndex}`; + return derivationPath; + } + private async _createLedgerClientAsync(): Promise<LedgerEthereumClient> { + await this._connectionLock.wait(); + if (!_.isUndefined(this._ledgerClientIfExists)) { + this._connectionLock.signal(); + throw new Error(LedgerSubproviderErrors.MultipleOpenConnectionsDisallowed); + } + const ledgerEthereumClient = await this._ledgerEthereumClientFactoryAsync(); + this._connectionLock.signal(); + return ledgerEthereumClient; + } + private async _destroyLedgerClientAsync() { + await this._connectionLock.wait(); + if (_.isUndefined(this._ledgerClientIfExists)) { + this._connectionLock.signal(); + return; + } + await this._ledgerClientIfExists.comm.close_async(); + this._ledgerClientIfExists = undefined; + this._connectionLock.signal(); + } + private async _sendTransactionAsync(txParams: PartialTxParams): Promise<string> { + await this._nonceLock.wait(); + try { + // fill in the extras + const filledParams = await this._populateMissingTxParamsAsync(txParams); + // sign it + const signedTx = await this.signTransactionAsync(filledParams); + // emit a submit + const payload = { + method: 'eth_sendRawTransaction', + params: [signedTx], + }; + const result = await this.emitPayloadAsync(payload); + this._nonceLock.signal(); + return result.result; + } catch (err) { + this._nonceLock.signal(); + throw err; + } + } + private async _signTransactionWithoutSendingAsync(txParams: PartialTxParams): Promise<ResponseWithTxParams> { + await this._nonceLock.wait(); + try { + // fill in the extras + const filledParams = await this._populateMissingTxParamsAsync(txParams); + // sign it + const signedTx = await this.signTransactionAsync(filledParams); - this._nonceLock.signal(); - const result = { - raw: signedTx, - tx: txParams, - }; - return result; - } catch (err) { - this._nonceLock.signal(); - throw err; - } - } - private async _populateMissingTxParamsAsync(txParams: PartialTxParams): Promise<PartialTxParams> { - if (_.isUndefined(txParams.gasPrice)) { - const gasPriceResult = await this.emitPayloadAsync({ - method: 'eth_gasPrice', - params: [], - }); - const gasPrice = gasPriceResult.result.toString(); - txParams.gasPrice = gasPrice; - } - if (_.isUndefined(txParams.nonce)) { - const nonceResult = await this.emitPayloadAsync({ - method: 'eth_getTransactionCount', - params: [txParams.from, 'pending'], - }); - const nonce = nonceResult.result; - txParams.nonce = nonce; - } - if (_.isUndefined(txParams.gas)) { - const gasResult = await this.emitPayloadAsync({ - method: 'eth_estimateGas', - params: [txParams], - }); - const gas = gasResult.result.toString(); - txParams.gas = gas; - } - return txParams; - } + this._nonceLock.signal(); + const result = { + raw: signedTx, + tx: txParams, + }; + return result; + } catch (err) { + this._nonceLock.signal(); + throw err; + } + } + private async _populateMissingTxParamsAsync(txParams: PartialTxParams): Promise<PartialTxParams> { + if (_.isUndefined(txParams.gasPrice)) { + const gasPriceResult = await this.emitPayloadAsync({ + method: 'eth_gasPrice', + params: [], + }); + const gasPrice = gasPriceResult.result.toString(); + txParams.gasPrice = gasPrice; + } + if (_.isUndefined(txParams.nonce)) { + const nonceResult = await this.emitPayloadAsync({ + method: 'eth_getTransactionCount', + params: [txParams.from, 'pending'], + }); + const nonce = nonceResult.result; + txParams.nonce = nonce; + } + if (_.isUndefined(txParams.gas)) { + const gasResult = await this.emitPayloadAsync({ + method: 'eth_estimateGas', + params: [txParams], + }); + const gas = gasResult.result.toString(); + txParams.gas = gas; + } + return txParams; + } } diff --git a/packages/subproviders/src/subproviders/redundant_rpc.ts b/packages/subproviders/src/subproviders/redundant_rpc.ts index 92d7ae8ae..a3cb463a8 100644 --- a/packages/subproviders/src/subproviders/redundant_rpc.ts +++ b/packages/subproviders/src/subproviders/redundant_rpc.ts @@ -7,46 +7,46 @@ import { JSONRPCPayload } from '../types'; import { Subprovider } from './subprovider'; export class RedundantRPCSubprovider extends Subprovider { - private _rpcs: RpcSubprovider[]; - private static async _firstSuccessAsync( - rpcs: RpcSubprovider[], - payload: JSONRPCPayload, - next: () => void, - ): Promise<any> { - let lastErr: Error | undefined; - for (const rpc of rpcs) { - try { - const data = await promisify(rpc.handleRequest.bind(rpc))(payload, next); - return data; - } catch (err) { - lastErr = err; - continue; - } - } - if (!_.isUndefined(lastErr)) { - throw lastErr; - } - } - constructor(endpoints: string[]) { - super(); - this._rpcs = _.map(endpoints, endpoint => { - return new RpcSubprovider({ - rpcUrl: endpoint, - }); - }); - } - // tslint:disable-next-line:async-suffix - public async handleRequest( - payload: JSONRPCPayload, - next: () => void, - end: (err: Error | null, data?: any) => void, - ): Promise<void> { - const rpcsCopy = this._rpcs.slice(); - try { - const data = await RedundantRPCSubprovider._firstSuccessAsync(rpcsCopy, payload, next); - end(null, data); - } catch (err) { - end(err); - } - } + private _rpcs: RpcSubprovider[]; + private static async _firstSuccessAsync( + rpcs: RpcSubprovider[], + payload: JSONRPCPayload, + next: () => void, + ): Promise<any> { + let lastErr: Error | undefined; + for (const rpc of rpcs) { + try { + const data = await promisify(rpc.handleRequest.bind(rpc))(payload, next); + return data; + } catch (err) { + lastErr = err; + continue; + } + } + if (!_.isUndefined(lastErr)) { + throw lastErr; + } + } + constructor(endpoints: string[]) { + super(); + this._rpcs = _.map(endpoints, endpoint => { + return new RpcSubprovider({ + rpcUrl: endpoint, + }); + }); + } + // tslint:disable-next-line:async-suffix + public async handleRequest( + payload: JSONRPCPayload, + next: () => void, + end: (err: Error | null, data?: any) => void, + ): Promise<void> { + const rpcsCopy = this._rpcs.slice(); + try { + const data = await RedundantRPCSubprovider._firstSuccessAsync(rpcsCopy, payload, next); + end(null, data); + } catch (err) { + end(err); + } + } } diff --git a/packages/subproviders/src/subproviders/subprovider.ts b/packages/subproviders/src/subproviders/subprovider.ts index b6bb16918..6435c9f65 100644 --- a/packages/subproviders/src/subproviders/subprovider.ts +++ b/packages/subproviders/src/subproviders/subprovider.ts @@ -8,33 +8,33 @@ import { JSONRPCPayload } from '../types'; * Altered version of: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js */ export class Subprovider { - private _engine: any; - // Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js - private static _getRandomId() { - const extraDigits = 3; - // 13 time digits - const datePart = new Date().getTime() * Math.pow(10, extraDigits); - // 3 random digits - const extraPart = Math.floor(Math.random() * Math.pow(10, extraDigits)); - // 16 digits - return datePart + extraPart; - } - private static _createFinalPayload(payload: JSONRPCPayload): Web3.JSONRPCRequestPayload { - const finalPayload = { - // defaults - id: Subprovider._getRandomId(), - jsonrpc: '2.0', - params: [], - ...payload, - }; - return finalPayload; - } - public setEngine(engine: any): void { - this._engine = engine; - } - public async emitPayloadAsync(payload: JSONRPCPayload): Promise<any> { - const finalPayload = Subprovider._createFinalPayload(payload); - const response = await promisify(this._engine.sendAsync, this._engine)(finalPayload); - return response; - } + private _engine: any; + // Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js + private static _getRandomId() { + const extraDigits = 3; + // 13 time digits + const datePart = new Date().getTime() * Math.pow(10, extraDigits); + // 3 random digits + const extraPart = Math.floor(Math.random() * Math.pow(10, extraDigits)); + // 16 digits + return datePart + extraPart; + } + private static _createFinalPayload(payload: JSONRPCPayload): Web3.JSONRPCRequestPayload { + const finalPayload = { + // defaults + id: Subprovider._getRandomId(), + jsonrpc: '2.0', + params: [], + ...payload, + }; + return finalPayload; + } + public setEngine(engine: any): void { + this._engine = engine; + } + public async emitPayloadAsync(payload: JSONRPCPayload): Promise<any> { + const finalPayload = Subprovider._createFinalPayload(payload); + const response = await promisify(this._engine.sendAsync, this._engine)(finalPayload); + return response; + } } diff --git a/packages/subproviders/src/types.ts b/packages/subproviders/src/types.ts index ec1eedd34..3db8be943 100644 --- a/packages/subproviders/src/types.ts +++ b/packages/subproviders/src/types.ts @@ -1,7 +1,7 @@ import * as _ from 'lodash'; export interface LedgerCommunicationClient { - close_async: () => Promise<void>; + close_async: () => Promise<void>; } /* @@ -10,28 +10,28 @@ export interface LedgerCommunicationClient { * NodeJs and Browser communication are supported. */ export interface LedgerEthereumClient { - // shouldGetChainCode is defined as `true` instead of `boolean` because other types rely on the assumption - // that we get back the chain code and we don't have dependent types to express it properly - getAddress_async: ( - derivationPath: string, - askForDeviceConfirmation: boolean, - shouldGetChainCode: true, - ) => Promise<LedgerGetAddressResult>; - signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<ECSignature>; - signTransaction_async: (derivationPath: string, txHex: string) => Promise<ECSignatureString>; - comm: LedgerCommunicationClient; + // shouldGetChainCode is defined as `true` instead of `boolean` because other types rely on the assumption + // that we get back the chain code and we don't have dependent types to express it properly + getAddress_async: ( + derivationPath: string, + askForDeviceConfirmation: boolean, + shouldGetChainCode: true, + ) => Promise<LedgerGetAddressResult>; + signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<ECSignature>; + signTransaction_async: (derivationPath: string, txHex: string) => Promise<ECSignatureString>; + comm: LedgerCommunicationClient; } export interface ECSignatureString { - v: string; - r: string; - s: string; + v: string; + r: string; + s: string; } export interface ECSignature { - v: number; - r: string; - s: string; + v: number; + r: string; + s: string; } export type LedgerEthereumClientFactoryAsync = () => Promise<LedgerEthereumClient>; @@ -43,10 +43,10 @@ export type LedgerEthereumClientFactoryAsync = () => Promise<LedgerEthereumClien * accountFetchingConfigs: configs related to fetching accounts from a Ledger */ export interface LedgerSubproviderConfigs { - networkId: number; - ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync; - derivationPath?: string; - accountFetchingConfigs?: AccountFetchingConfigs; + networkId: number; + ledgerEthereumClientFactoryAsync: LedgerEthereumClientFactoryAsync; + derivationPath?: string; + accountFetchingConfigs?: AccountFetchingConfigs; } /* @@ -55,60 +55,60 @@ export interface LedgerSubproviderConfigs { * before fetching their addresses */ export interface AccountFetchingConfigs { - numAddressesToReturn?: number; - shouldAskForOnDeviceConfirmation?: boolean; + numAddressesToReturn?: number; + shouldAskForOnDeviceConfirmation?: boolean; } export interface SignatureData { - hash: string; - r: string; - s: string; - v: number; + hash: string; + r: string; + s: string; + v: number; } export interface LedgerGetAddressResult { - address: string; - publicKey: string; - chainCode: string; + address: string; + publicKey: string; + chainCode: string; } export interface LedgerWalletSubprovider { - getPath: () => string; - setPath: (path: string) => void; - setPathIndex: (pathIndex: number) => void; + getPath: () => string; + setPath: (path: string) => void; + setPathIndex: (pathIndex: number) => void; } export interface PartialTxParams { - nonce: string; - gasPrice?: string; - gas: string; - to: string; - from?: string; - value?: string; - data?: string; - chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3 + nonce: string; + gasPrice?: string; + gas: string; + to: string; + from?: string; + value?: string; + data?: string; + chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3 } export type DoneCallback = (err?: Error) => void; export interface JSONRPCPayload { - params: any[]; - method: string; + params: any[]; + method: string; } export interface LedgerCommunication { - close_async: () => Promise<void>; + close_async: () => Promise<void>; } export interface ResponseWithTxParams { - raw: string; - tx: PartialTxParams; + raw: string; + tx: PartialTxParams; } export enum LedgerSubproviderErrors { - TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE', - FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID', - DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE', - SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED', - MultipleOpenConnectionsDisallowed = 'MULTIPLE_OPEN_CONNECTIONS_DISALLOWED', + TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE', + FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID', + DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE', + SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED', + MultipleOpenConnectionsDisallowed = 'MULTIPLE_OPEN_CONNECTIONS_DISALLOWED', } diff --git a/packages/subproviders/test/chai_setup.ts b/packages/subproviders/test/chai_setup.ts index 941a141d5..a281bab6c 100644 --- a/packages/subproviders/test/chai_setup.ts +++ b/packages/subproviders/test/chai_setup.ts @@ -3,9 +3,9 @@ import chaiAsPromised = require('chai-as-promised'); import * as dirtyChai from 'dirty-chai'; export const chaiSetup = { - configure() { - chai.config.includeStack = true; - chai.use(dirtyChai); - chai.use(chaiAsPromised); - }, + configure() { + chai.config.includeStack = true; + chai.use(dirtyChai); + chai.use(chaiAsPromised); + }, }; diff --git a/packages/subproviders/test/integration/ledger_subprovider_test.ts b/packages/subproviders/test/integration/ledger_subprovider_test.ts index 6c1de46aa..628b532d7 100644 --- a/packages/subproviders/test/integration/ledger_subprovider_test.ts +++ b/packages/subproviders/test/integration/ledger_subprovider_test.ts @@ -17,168 +17,168 @@ const expect = chai.expect; const TEST_RPC_ACCOUNT_0 = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; describe('LedgerSubprovider', () => { - let ledgerSubprovider: LedgerSubprovider; - const networkId: number = 42; - before(async () => { - ledgerSubprovider = new LedgerSubprovider({ - networkId, - ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync, - }); - }); - describe('direct method calls', () => { - it('returns a list of accounts', async () => { - const accounts = await ledgerSubprovider.getAccountsAsync(); - expect(accounts[0]).to.not.be.an('undefined'); - expect(accounts.length).to.be.equal(10); - }); - it('signs a personal message', async () => { - const data = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); - const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(data); - expect(ecSignatureHex.length).to.be.equal(132); - expect(ecSignatureHex.substr(0, 2)).to.be.equal('0x'); - }); - it('signs a transaction', async () => { - const tx = { - nonce: '0x00', - gas: '0x2710', - to: '0x0000000000000000000000000000000000000000', - value: '0x00', - chainId: 3, - }; - const txHex = await ledgerSubprovider.signTransactionAsync(tx); - expect(txHex).to.be.equal( - '0xf85f8080822710940000000000000000000000000000000000000000808077a088a95ef1378487bc82be558e82c8478baf840c545d5b887536bb1da63673a98ba0019f4a4b9a107d1e6752bf7f701e275f28c13791d6e76af895b07373462cefaa', - ); - }); - }); - describe('calls through a provider', () => { - let defaultProvider: Web3ProviderEngine; - let ledgerProvider: Web3ProviderEngine; - before(() => { - ledgerProvider = new Web3ProviderEngine(); - ledgerProvider.addProvider(ledgerSubprovider); - const httpProvider = new RpcSubprovider({ - rpcUrl: 'http://localhost:8545', - }); - ledgerProvider.addProvider(httpProvider); - ledgerProvider.start(); + let ledgerSubprovider: LedgerSubprovider; + const networkId: number = 42; + before(async () => { + ledgerSubprovider = new LedgerSubprovider({ + networkId, + ledgerEthereumClientFactoryAsync: ledgerEthereumNodeJsClientFactoryAsync, + }); + }); + describe('direct method calls', () => { + it('returns a list of accounts', async () => { + const accounts = await ledgerSubprovider.getAccountsAsync(); + expect(accounts[0]).to.not.be.an('undefined'); + expect(accounts.length).to.be.equal(10); + }); + it('signs a personal message', async () => { + const data = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); + const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(data); + expect(ecSignatureHex.length).to.be.equal(132); + expect(ecSignatureHex.substr(0, 2)).to.be.equal('0x'); + }); + it('signs a transaction', async () => { + const tx = { + nonce: '0x00', + gas: '0x2710', + to: '0x0000000000000000000000000000000000000000', + value: '0x00', + chainId: 3, + }; + const txHex = await ledgerSubprovider.signTransactionAsync(tx); + expect(txHex).to.be.equal( + '0xf85f8080822710940000000000000000000000000000000000000000808077a088a95ef1378487bc82be558e82c8478baf840c545d5b887536bb1da63673a98ba0019f4a4b9a107d1e6752bf7f701e275f28c13791d6e76af895b07373462cefaa', + ); + }); + }); + describe('calls through a provider', () => { + let defaultProvider: Web3ProviderEngine; + let ledgerProvider: Web3ProviderEngine; + before(() => { + ledgerProvider = new Web3ProviderEngine(); + ledgerProvider.addProvider(ledgerSubprovider); + const httpProvider = new RpcSubprovider({ + rpcUrl: 'http://localhost:8545', + }); + ledgerProvider.addProvider(httpProvider); + ledgerProvider.start(); - defaultProvider = new Web3ProviderEngine(); - defaultProvider.addProvider(httpProvider); - defaultProvider.start(); - }); - it('returns a list of accounts', (done: DoneCallback) => { - const payload = { - jsonrpc: '2.0', - method: 'eth_accounts', - params: [], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.be.a('null'); - expect(response.result.length).to.be.equal(10); - done(); - }); - ledgerProvider.sendAsync(payload, callback); - }); - it('signs a personal message with eth_sign', (done: DoneCallback) => { - (async () => { - const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); - const accounts = await ledgerSubprovider.getAccountsAsync(); - const signer = accounts[0]; - const payload = { - jsonrpc: '2.0', - method: 'eth_sign', - params: [signer, messageHex], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.be.a('null'); - expect(response.result.length).to.be.equal(132); - expect(response.result.substr(0, 2)).to.be.equal('0x'); - done(); - }); - ledgerProvider.sendAsync(payload, callback); - })().catch(done); - }); - it('signs a personal message with personal_sign', (done: DoneCallback) => { - (async () => { - const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); - const accounts = await ledgerSubprovider.getAccountsAsync(); - const signer = accounts[0]; - const payload = { - jsonrpc: '2.0', - method: 'personal_sign', - params: [messageHex, signer], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.be.a('null'); - expect(response.result.length).to.be.equal(132); - expect(response.result.substr(0, 2)).to.be.equal('0x'); - done(); - }); - ledgerProvider.sendAsync(payload, callback); - })().catch(done); - }); - it('signs a transaction', (done: DoneCallback) => { - const tx = { - to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', - value: '0x00', - }; - const payload = { - jsonrpc: '2.0', - method: 'eth_signTransaction', - params: [tx], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.be.a('null'); - expect(response.result.raw.length).to.be.equal(206); - expect(response.result.raw.substr(0, 2)).to.be.equal('0x'); - done(); - }); - ledgerProvider.sendAsync(payload, callback); - }); - it('signs and sends a transaction', (done: DoneCallback) => { - (async () => { - const accounts = await ledgerSubprovider.getAccountsAsync(); + defaultProvider = new Web3ProviderEngine(); + defaultProvider.addProvider(httpProvider); + defaultProvider.start(); + }); + it('returns a list of accounts', (done: DoneCallback) => { + const payload = { + jsonrpc: '2.0', + method: 'eth_accounts', + params: [], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.length).to.be.equal(10); + done(); + }); + ledgerProvider.sendAsync(payload, callback); + }); + it('signs a personal message with eth_sign', (done: DoneCallback) => { + (async () => { + const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); + const accounts = await ledgerSubprovider.getAccountsAsync(); + const signer = accounts[0]; + const payload = { + jsonrpc: '2.0', + method: 'eth_sign', + params: [signer, messageHex], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.length).to.be.equal(132); + expect(response.result.substr(0, 2)).to.be.equal('0x'); + done(); + }); + ledgerProvider.sendAsync(payload, callback); + })().catch(done); + }); + it('signs a personal message with personal_sign', (done: DoneCallback) => { + (async () => { + const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); + const accounts = await ledgerSubprovider.getAccountsAsync(); + const signer = accounts[0]; + const payload = { + jsonrpc: '2.0', + method: 'personal_sign', + params: [messageHex, signer], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.length).to.be.equal(132); + expect(response.result.substr(0, 2)).to.be.equal('0x'); + done(); + }); + ledgerProvider.sendAsync(payload, callback); + })().catch(done); + }); + it('signs a transaction', (done: DoneCallback) => { + const tx = { + to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', + value: '0x00', + }; + const payload = { + jsonrpc: '2.0', + method: 'eth_signTransaction', + params: [tx], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.raw.length).to.be.equal(206); + expect(response.result.raw.substr(0, 2)).to.be.equal('0x'); + done(); + }); + ledgerProvider.sendAsync(payload, callback); + }); + it('signs and sends a transaction', (done: DoneCallback) => { + (async () => { + const accounts = await ledgerSubprovider.getAccountsAsync(); - // Give first account on Ledger sufficient ETH to complete tx send - let tx = { - to: accounts[0], - from: TEST_RPC_ACCOUNT_0, - value: '0x8ac7230489e80000', // 10 ETH - }; - let payload = { - jsonrpc: '2.0', - method: 'eth_sendTransaction', - params: [tx], - id: 1, - }; - await promisify(defaultProvider.sendAsync, defaultProvider)(payload); + // Give first account on Ledger sufficient ETH to complete tx send + let tx = { + to: accounts[0], + from: TEST_RPC_ACCOUNT_0, + value: '0x8ac7230489e80000', // 10 ETH + }; + let payload = { + jsonrpc: '2.0', + method: 'eth_sendTransaction', + params: [tx], + id: 1, + }; + await promisify(defaultProvider.sendAsync, defaultProvider)(payload); - // Send transaction from Ledger - tx = { - to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', - from: accounts[0], - value: '0xde0b6b3a7640000', - }; - payload = { - jsonrpc: '2.0', - method: 'eth_sendTransaction', - params: [tx], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.be.a('null'); - const result = response.result.result; - expect(result.length).to.be.equal(66); - expect(result.substr(0, 2)).to.be.equal('0x'); - done(); - }); - ledgerProvider.sendAsync(payload, callback); - })().catch(done); - }); - }); + // Send transaction from Ledger + tx = { + to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', + from: accounts[0], + value: '0xde0b6b3a7640000', + }; + payload = { + jsonrpc: '2.0', + method: 'eth_sendTransaction', + params: [tx], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + const result = response.result.result; + expect(result.length).to.be.equal(66); + expect(result.substr(0, 2)).to.be.equal('0x'); + done(); + }); + ledgerProvider.sendAsync(payload, callback); + })().catch(done); + }); + }); }); diff --git a/packages/subproviders/test/unit/ledger_subprovider_test.ts b/packages/subproviders/test/unit/ledger_subprovider_test.ts index 101b6b906..1c70dd3a6 100644 --- a/packages/subproviders/test/unit/ledger_subprovider_test.ts +++ b/packages/subproviders/test/unit/ledger_subprovider_test.ts @@ -15,230 +15,230 @@ const expect = chai.expect; const FAKE_ADDRESS = '0xb088a3bc93f71b4de97b9de773e9647645983688'; describe('LedgerSubprovider', () => { - const networkId: number = 42; - let ledgerSubprovider: LedgerSubprovider; - before(async () => { - const ledgerEthereumClientFactoryAsync = async () => { - // tslint:disable:no-object-literal-type-assertion - const ledgerEthClient = { - getAddress_async: async () => { - const publicKey = - '04f428290f4c5ed6a198f71b8205f488141dbb3f0840c923bbfa798ecbee6370986c03b5575d94d506772fb48a6a44e345e4ebd4f028a6f609c44b655d6d3e71a1'; - const chainCode = 'ac055a5537c0c7e9e02d14a197cad6b857836da2a12043b46912a37d959b5ae8'; - const address = '0xBa388BA5e5EEF2c6cE42d831c2B3A28D3c99bdB1'; - return { - publicKey, - address, - chainCode, - }; - }, - signPersonalMessage_async: async () => { - const ecSignature = { - v: 28, - r: 'a6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae49148', - s: '0652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d0', - }; - return ecSignature; - }, - signTransaction_async: async (derivationPath: string, txHex: string) => { - const ecSignature = { - v: '77', - r: '88a95ef1378487bc82be558e82c8478baf840c545d5b887536bb1da63673a98b', - s: '019f4a4b9a107d1e6752bf7f701e275f28c13791d6e76af895b07373462cefaa', - }; - return ecSignature; - }, - comm: { - close_async: _.noop, - } as LedgerCommunicationClient, - }; - // tslint:enable:no-object-literal-type-assertion - return ledgerEthClient; - }; - ledgerSubprovider = new LedgerSubprovider({ - networkId, - ledgerEthereumClientFactoryAsync, - }); - }); - describe('direct method calls', () => { - describe('success cases', () => { - it('returns a list of accounts', async () => { - const accounts = await ledgerSubprovider.getAccountsAsync(); - expect(accounts[0]).to.be.equal(FAKE_ADDRESS); - expect(accounts.length).to.be.equal(10); - }); - it('signs a personal message', async () => { - const data = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); - const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(data); - expect(ecSignatureHex).to.be.equal( - '0xa6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae491480652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d001', - ); - }); - }); - describe('failure cases', () => { - it('cannot open multiple simultaneous connections to the Ledger device', async () => { - const data = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); - return expect( - Promise.all([ - ledgerSubprovider.getAccountsAsync(), - ledgerSubprovider.signPersonalMessageAsync(data), - ]), - ).to.be.rejectedWith(LedgerSubproviderErrors.MultipleOpenConnectionsDisallowed); - }); - }); - }); - describe('calls through a provider', () => { - let provider: Web3ProviderEngine; - before(() => { - provider = new Web3ProviderEngine(); - provider.addProvider(ledgerSubprovider); - const httpProvider = new RpcSubprovider({ - rpcUrl: 'http://localhost:8545', - }); - provider.addProvider(httpProvider); - provider.start(); - }); - describe('success cases', () => { - it('returns a list of accounts', (done: DoneCallback) => { - const payload = { - jsonrpc: '2.0', - method: 'eth_accounts', - params: [], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.be.a('null'); - expect(response.result.length).to.be.equal(10); - expect(response.result[0]).to.be.equal(FAKE_ADDRESS); - done(); - }); - provider.sendAsync(payload, callback); - }); - it('signs a personal message with eth_sign', (done: DoneCallback) => { - const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); - const payload = { - jsonrpc: '2.0', - method: 'eth_sign', - params: ['0x0000000000000000000000000000000000000000', messageHex], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.be.a('null'); - expect(response.result).to.be.equal( - '0xa6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae491480652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d001', - ); - done(); - }); - provider.sendAsync(payload, callback); - }); - it('signs a personal message with personal_sign', (done: DoneCallback) => { - const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); - const payload = { - jsonrpc: '2.0', - method: 'personal_sign', - params: [messageHex, '0x0000000000000000000000000000000000000000'], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.be.a('null'); - expect(response.result).to.be.equal( - '0xa6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae491480652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d001', - ); - done(); - }); - provider.sendAsync(payload, callback); - }); - it('signs a transaction', (done: DoneCallback) => { - const tx = { - to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', - value: '0x00', - gasPrice: '0x00', - nonce: '0x00', - gas: '0x00', - }; - const payload = { - jsonrpc: '2.0', - method: 'eth_signTransaction', - params: [tx], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.be.a('null'); - expect(response.result.raw.length).to.be.equal(192); - expect(response.result.raw.substr(0, 2)).to.be.equal('0x'); - done(); - }); - provider.sendAsync(payload, callback); - }); - }); - describe('failure cases', () => { - it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => { - const nonHexMessage = 'hello world'; - const payload = { - jsonrpc: '2.0', - method: 'eth_sign', - params: ['0x0000000000000000000000000000000000000000', nonHexMessage], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.not.be.a('null'); - expect(err.message).to.be.equal('Expected data to be of type HexString, encountered: hello world'); - done(); - }); - provider.sendAsync(payload, callback); - }); - it('should throw if `data` param not hex when calling personal_sign', (done: DoneCallback) => { - const nonHexMessage = 'hello world'; - const payload = { - jsonrpc: '2.0', - method: 'personal_sign', - params: [nonHexMessage, '0x0000000000000000000000000000000000000000'], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.not.be.a('null'); - expect(err.message).to.be.equal('Expected data to be of type HexString, encountered: hello world'); - done(); - }); - provider.sendAsync(payload, callback); - }); - it('should throw if `from` param missing when calling eth_sendTransaction', (done: DoneCallback) => { - const tx = { - to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', - value: '0xde0b6b3a7640000', - }; - const payload = { - jsonrpc: '2.0', - method: 'eth_sendTransaction', - params: [tx], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.not.be.a('null'); - expect(err.message).to.be.equal(LedgerSubproviderErrors.SenderInvalidOrNotSupplied); - done(); - }); - provider.sendAsync(payload, callback); - }); - it('should throw if `from` param invalid address when calling eth_sendTransaction', (done: DoneCallback) => { - const tx = { - to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', - from: '0xIncorrectEthereumAddress', - value: '0xde0b6b3a7640000', - }; - const payload = { - jsonrpc: '2.0', - method: 'eth_sendTransaction', - params: [tx], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.not.be.a('null'); - expect(err.message).to.be.equal(LedgerSubproviderErrors.SenderInvalidOrNotSupplied); - done(); - }); - provider.sendAsync(payload, callback); - }); - }); - }); + const networkId: number = 42; + let ledgerSubprovider: LedgerSubprovider; + before(async () => { + const ledgerEthereumClientFactoryAsync = async () => { + // tslint:disable:no-object-literal-type-assertion + const ledgerEthClient = { + getAddress_async: async () => { + const publicKey = + '04f428290f4c5ed6a198f71b8205f488141dbb3f0840c923bbfa798ecbee6370986c03b5575d94d506772fb48a6a44e345e4ebd4f028a6f609c44b655d6d3e71a1'; + const chainCode = 'ac055a5537c0c7e9e02d14a197cad6b857836da2a12043b46912a37d959b5ae8'; + const address = '0xBa388BA5e5EEF2c6cE42d831c2B3A28D3c99bdB1'; + return { + publicKey, + address, + chainCode, + }; + }, + signPersonalMessage_async: async () => { + const ecSignature = { + v: 28, + r: 'a6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae49148', + s: '0652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d0', + }; + return ecSignature; + }, + signTransaction_async: async (derivationPath: string, txHex: string) => { + const ecSignature = { + v: '77', + r: '88a95ef1378487bc82be558e82c8478baf840c545d5b887536bb1da63673a98b', + s: '019f4a4b9a107d1e6752bf7f701e275f28c13791d6e76af895b07373462cefaa', + }; + return ecSignature; + }, + comm: { + close_async: _.noop, + } as LedgerCommunicationClient, + }; + // tslint:enable:no-object-literal-type-assertion + return ledgerEthClient; + }; + ledgerSubprovider = new LedgerSubprovider({ + networkId, + ledgerEthereumClientFactoryAsync, + }); + }); + describe('direct method calls', () => { + describe('success cases', () => { + it('returns a list of accounts', async () => { + const accounts = await ledgerSubprovider.getAccountsAsync(); + expect(accounts[0]).to.be.equal(FAKE_ADDRESS); + expect(accounts.length).to.be.equal(10); + }); + it('signs a personal message', async () => { + const data = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); + const ecSignatureHex = await ledgerSubprovider.signPersonalMessageAsync(data); + expect(ecSignatureHex).to.be.equal( + '0xa6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae491480652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d001', + ); + }); + }); + describe('failure cases', () => { + it('cannot open multiple simultaneous connections to the Ledger device', async () => { + const data = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); + return expect( + Promise.all([ + ledgerSubprovider.getAccountsAsync(), + ledgerSubprovider.signPersonalMessageAsync(data), + ]), + ).to.be.rejectedWith(LedgerSubproviderErrors.MultipleOpenConnectionsDisallowed); + }); + }); + }); + describe('calls through a provider', () => { + let provider: Web3ProviderEngine; + before(() => { + provider = new Web3ProviderEngine(); + provider.addProvider(ledgerSubprovider); + const httpProvider = new RpcSubprovider({ + rpcUrl: 'http://localhost:8545', + }); + provider.addProvider(httpProvider); + provider.start(); + }); + describe('success cases', () => { + it('returns a list of accounts', (done: DoneCallback) => { + const payload = { + jsonrpc: '2.0', + method: 'eth_accounts', + params: [], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.length).to.be.equal(10); + expect(response.result[0]).to.be.equal(FAKE_ADDRESS); + done(); + }); + provider.sendAsync(payload, callback); + }); + it('signs a personal message with eth_sign', (done: DoneCallback) => { + const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); + const payload = { + jsonrpc: '2.0', + method: 'eth_sign', + params: ['0x0000000000000000000000000000000000000000', messageHex], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result).to.be.equal( + '0xa6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae491480652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d001', + ); + done(); + }); + provider.sendAsync(payload, callback); + }); + it('signs a personal message with personal_sign', (done: DoneCallback) => { + const messageHex = ethUtils.bufferToHex(ethUtils.toBuffer('hello world')); + const payload = { + jsonrpc: '2.0', + method: 'personal_sign', + params: [messageHex, '0x0000000000000000000000000000000000000000'], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result).to.be.equal( + '0xa6cc284bff14b42bdf5e9286730c152be91719d478605ec46b3bebcd0ae491480652a1a7b742ceb0213d1e744316e285f41f878d8af0b8e632cbca4c279132d001', + ); + done(); + }); + provider.sendAsync(payload, callback); + }); + it('signs a transaction', (done: DoneCallback) => { + const tx = { + to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', + value: '0x00', + gasPrice: '0x00', + nonce: '0x00', + gas: '0x00', + }; + const payload = { + jsonrpc: '2.0', + method: 'eth_signTransaction', + params: [tx], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.raw.length).to.be.equal(192); + expect(response.result.raw.substr(0, 2)).to.be.equal('0x'); + done(); + }); + provider.sendAsync(payload, callback); + }); + }); + describe('failure cases', () => { + it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => { + const nonHexMessage = 'hello world'; + const payload = { + jsonrpc: '2.0', + method: 'eth_sign', + params: ['0x0000000000000000000000000000000000000000', nonHexMessage], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.not.be.a('null'); + expect(err.message).to.be.equal('Expected data to be of type HexString, encountered: hello world'); + done(); + }); + provider.sendAsync(payload, callback); + }); + it('should throw if `data` param not hex when calling personal_sign', (done: DoneCallback) => { + const nonHexMessage = 'hello world'; + const payload = { + jsonrpc: '2.0', + method: 'personal_sign', + params: [nonHexMessage, '0x0000000000000000000000000000000000000000'], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.not.be.a('null'); + expect(err.message).to.be.equal('Expected data to be of type HexString, encountered: hello world'); + done(); + }); + provider.sendAsync(payload, callback); + }); + it('should throw if `from` param missing when calling eth_sendTransaction', (done: DoneCallback) => { + const tx = { + to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', + value: '0xde0b6b3a7640000', + }; + const payload = { + jsonrpc: '2.0', + method: 'eth_sendTransaction', + params: [tx], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.not.be.a('null'); + expect(err.message).to.be.equal(LedgerSubproviderErrors.SenderInvalidOrNotSupplied); + done(); + }); + provider.sendAsync(payload, callback); + }); + it('should throw if `from` param invalid address when calling eth_sendTransaction', (done: DoneCallback) => { + const tx = { + to: '0xafa3f8684e54059998bc3a7b0d2b0da075154d66', + from: '0xIncorrectEthereumAddress', + value: '0xde0b6b3a7640000', + }; + const payload = { + jsonrpc: '2.0', + method: 'eth_sendTransaction', + params: [tx], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.not.be.a('null'); + expect(err.message).to.be.equal(LedgerSubproviderErrors.SenderInvalidOrNotSupplied); + done(); + }); + provider.sendAsync(payload, callback); + }); + }); + }); }); diff --git a/packages/subproviders/test/unit/redundant_rpc_subprovider_test.ts b/packages/subproviders/test/unit/redundant_rpc_subprovider_test.ts index defb741dd..c3170745c 100644 --- a/packages/subproviders/test/unit/redundant_rpc_subprovider_test.ts +++ b/packages/subproviders/test/unit/redundant_rpc_subprovider_test.ts @@ -12,45 +12,45 @@ const expect = chai.expect; chaiSetup.configure(); describe('RedundantRpcSubprovider', () => { - let provider: Web3ProviderEngine; - it('succeeds when supplied a healthy endpoint', (done: DoneCallback) => { - provider = new Web3ProviderEngine(); - const endpoints = ['http://localhost:8545']; - const redundantSubprovider = new RedundantRPCSubprovider(endpoints); - provider.addProvider(redundantSubprovider); - provider.start(); + let provider: Web3ProviderEngine; + it('succeeds when supplied a healthy endpoint', (done: DoneCallback) => { + provider = new Web3ProviderEngine(); + const endpoints = ['http://localhost:8545']; + const redundantSubprovider = new RedundantRPCSubprovider(endpoints); + provider.addProvider(redundantSubprovider); + provider.start(); - const payload = { - jsonrpc: '2.0', - method: 'eth_accounts', - params: [], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.be.a('null'); - expect(response.result.length).to.be.equal(10); - done(); - }); - provider.sendAsync(payload, callback); - }); - it('succeeds when supplied at least one healthy endpoint', (done: DoneCallback) => { - provider = new Web3ProviderEngine(); - const endpoints = ['http://does-not-exist:3000', 'http://localhost:8545']; - const redundantSubprovider = new RedundantRPCSubprovider(endpoints); - provider.addProvider(redundantSubprovider); - provider.start(); + const payload = { + jsonrpc: '2.0', + method: 'eth_accounts', + params: [], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.length).to.be.equal(10); + done(); + }); + provider.sendAsync(payload, callback); + }); + it('succeeds when supplied at least one healthy endpoint', (done: DoneCallback) => { + provider = new Web3ProviderEngine(); + const endpoints = ['http://does-not-exist:3000', 'http://localhost:8545']; + const redundantSubprovider = new RedundantRPCSubprovider(endpoints); + provider.addProvider(redundantSubprovider); + provider.start(); - const payload = { - jsonrpc: '2.0', - method: 'eth_accounts', - params: [], - id: 1, - }; - const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { - expect(err).to.be.a('null'); - expect(response.result.length).to.be.equal(10); - done(); - }); - provider.sendAsync(payload, callback); - }); + const payload = { + jsonrpc: '2.0', + method: 'eth_accounts', + params: [], + id: 1, + }; + const callback = reportCallbackErrors(done)((err: Error, response: Web3.JSONRPCResponsePayload) => { + expect(err).to.be.a('null'); + expect(response.result.length).to.be.equal(10); + done(); + }); + provider.sendAsync(payload, callback); + }); }); diff --git a/packages/subproviders/test/utils/report_callback_errors.ts b/packages/subproviders/test/utils/report_callback_errors.ts index e6aadfa84..8a8f4d966 100644 --- a/packages/subproviders/test/utils/report_callback_errors.ts +++ b/packages/subproviders/test/utils/report_callback_errors.ts @@ -1,14 +1,14 @@ import { DoneCallback } from '../../src/types'; export const reportCallbackErrors = (done: DoneCallback) => { - return (f: (...args: any[]) => void) => { - const wrapped = async (...args: any[]) => { - try { - f(...args); - } catch (err) { - done(err); - } - }; - return wrapped; - }; + return (f: (...args: any[]) => void) => { + const wrapped = async (...args: any[]) => { + try { + f(...args); + } catch (err) { + done(err); + } + }; + return wrapped; + }; }; diff --git a/packages/subproviders/tsconfig.json b/packages/subproviders/tsconfig.json index 4d92e88a5..9a65a0a97 100644 --- a/packages/subproviders/tsconfig.json +++ b/packages/subproviders/tsconfig.json @@ -1,15 +1,15 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": [ - "./src/**/*", - "./test/**/*", - "../../node_modules/chai-typescript-typings/index.d.ts", - "../../node_modules/web3-typescript-typings/index.d.ts", - "../../node_modules/types-bn/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts", - "../../node_modules/chai-as-promised-typescript-typings/index.d.ts" - ] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": [ + "./src/**/*", + "./test/**/*", + "../../node_modules/chai-typescript-typings/index.d.ts", + "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/types-bn/index.d.ts", + "../../node_modules/types-ethereumjs-util/index.d.ts", + "../../node_modules/chai-as-promised-typescript-typings/index.d.ts" + ] } diff --git a/packages/subproviders/tslint.json b/packages/subproviders/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/subproviders/tslint.json +++ b/packages/subproviders/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/testnet-faucets/README.md b/packages/testnet-faucets/README.md index 36322e76e..a52d16a00 100644 --- a/packages/testnet-faucets/README.md +++ b/packages/testnet-faucets/README.md @@ -63,26 +63,26 @@ Returns a JSON payload describing the state of the queues for each network. For ```json { - "3": { - "ether": { - "full": false, - "size": 4 - }, - "zrx": { - "full": false, - "size": 6 - } - }, - "42": { - "ether": { - "full": false, - "size": 8 - }, - "zrx": { - "full": false, - "size": 20 - } - } + "3": { + "ether": { + "full": false, + "size": 4 + }, + "zrx": { + "full": false, + "size": 6 + } + }, + "42": { + "ether": { + "full": false, + "size": 8 + }, + "zrx": { + "full": false, + "size": 20 + } + } } ``` diff --git a/packages/testnet-faucets/package.json b/packages/testnet-faucets/package.json index 4f8bcfdcf..6c27df0d5 100644 --- a/packages/testnet-faucets/package.json +++ b/packages/testnet-faucets/package.json @@ -1,43 +1,43 @@ { - "private": true, - "name": "@0xproject/testnet-faucets", - "version": "1.0.7", - "description": "A faucet micro-service that dispenses test ERC20 tokens or Ether", - "main": "server.js", - "scripts": { - "build": "node ../../node_modules/gulp/bin/gulp.js build", - "dev": "node ../../node_modules/gulp/bin/gulp.js run", - "start": "node ./bin/server.js", - "lint": "tslint --project . 'src/**/*.ts'", - "clean": "shx rm -rf bin" - }, - "author": "Fabio Berger", - "license": "Apache-2.0", - "dependencies": { - "0x.js": "^0.30.2", - "@0xproject/utils": "^0.2.2", - "body-parser": "^1.17.1", - "ethereumjs-tx": "^1.3.3", - "express": "^4.15.2", - "lodash": "^4.17.4", - "rollbar": "^0.6.5", - "web3": "^0.20.0", - "web3-provider-engine": "^13.0.1" - }, - "devDependencies": { - "@0xproject/tslint-config": "^0.4.4", - "@types/body-parser": "^1.16.1", - "@types/express": "^4.0.35", - "@types/lodash": "^4.14.86", - "awesome-typescript-loader": "^3.1.3", - "gulp": "^3.9.1", - "nodemon": "^1.11.0", - "shx": "^0.2.2", - "source-map-loader": "^0.1.6", - "tslint": "5.8.0", - "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.6", - "webpack": "^3.1.0", - "webpack-node-externals": "^1.6.0" - } + "private": true, + "name": "@0xproject/testnet-faucets", + "version": "1.0.7", + "description": "A faucet micro-service that dispenses test ERC20 tokens or Ether", + "main": "server.js", + "scripts": { + "build": "node ../../node_modules/gulp/bin/gulp.js build", + "dev": "node ../../node_modules/gulp/bin/gulp.js run", + "start": "node ./bin/server.js", + "lint": "tslint --project . 'src/**/*.ts'", + "clean": "shx rm -rf bin" + }, + "author": "Fabio Berger", + "license": "Apache-2.0", + "dependencies": { + "0x.js": "^0.30.2", + "@0xproject/utils": "^0.2.2", + "body-parser": "^1.17.1", + "ethereumjs-tx": "^1.3.3", + "express": "^4.15.2", + "lodash": "^4.17.4", + "rollbar": "^0.6.5", + "web3": "^0.20.0", + "web3-provider-engine": "^13.0.1" + }, + "devDependencies": { + "@0xproject/tslint-config": "^0.4.4", + "@types/body-parser": "^1.16.1", + "@types/express": "^4.0.35", + "@types/lodash": "^4.14.86", + "awesome-typescript-loader": "^3.1.3", + "gulp": "^3.9.1", + "nodemon": "^1.11.0", + "shx": "^0.2.2", + "source-map-loader": "^0.1.6", + "tslint": "5.8.0", + "typescript": "~2.6.1", + "web3-typescript-typings": "^0.9.6", + "webpack": "^3.1.0", + "webpack-node-externals": "^1.6.0" + } } diff --git a/packages/testnet-faucets/src/ts/configs.ts b/packages/testnet-faucets/src/ts/configs.ts index 038df5322..038c8e22a 100644 --- a/packages/testnet-faucets/src/ts/configs.ts +++ b/packages/testnet-faucets/src/ts/configs.ts @@ -1,7 +1,7 @@ export const configs = { - DISPENSER_ADDRESS: (process.env.DISPENSER_ADDRESS as string).toLowerCase(), - DISPENSER_PRIVATE_KEY: process.env.DISPENSER_PRIVATE_KEY, - ENVIRONMENT: process.env.FAUCET_ENVIRONMENT, - INFURA_API_KEY: process.env.INFURA_API_KEY, - ROLLBAR_ACCESS_KEY: process.env.FAUCET_ROLLBAR_ACCESS_KEY, + DISPENSER_ADDRESS: (process.env.DISPENSER_ADDRESS as string).toLowerCase(), + DISPENSER_PRIVATE_KEY: process.env.DISPENSER_PRIVATE_KEY, + ENVIRONMENT: process.env.FAUCET_ENVIRONMENT, + INFURA_API_KEY: process.env.INFURA_API_KEY, + ROLLBAR_ACCESS_KEY: process.env.FAUCET_ROLLBAR_ACCESS_KEY, }; diff --git a/packages/testnet-faucets/src/ts/error_reporter.ts b/packages/testnet-faucets/src/ts/error_reporter.ts index 0cef6cfa8..6865d3893 100644 --- a/packages/testnet-faucets/src/ts/error_reporter.ts +++ b/packages/testnet-faucets/src/ts/error_reporter.ts @@ -5,36 +5,36 @@ import { configs } from './configs'; import { utils } from './utils'; export const errorReporter = { - setup() { - rollbar.init(configs.ROLLBAR_ACCESS_KEY, { - environment: configs.ENVIRONMENT, - }); + setup() { + rollbar.init(configs.ROLLBAR_ACCESS_KEY, { + environment: configs.ENVIRONMENT, + }); - rollbar.handleUncaughtExceptions(configs.ROLLBAR_ACCESS_KEY); + rollbar.handleUncaughtExceptions(configs.ROLLBAR_ACCESS_KEY); - process.on('unhandledRejection', async (err: Error) => { - utils.consoleLog(`Uncaught exception ${err}. Stack: ${err.stack}`); - await this.reportAsync(err); - process.exit(1); - }); - }, - async reportAsync(err: Error, req?: express.Request): Promise<any> { - if (configs.ENVIRONMENT === 'development') { - return; // Do not log development environment errors - } + process.on('unhandledRejection', async (err: Error) => { + utils.consoleLog(`Uncaught exception ${err}. Stack: ${err.stack}`); + await this.reportAsync(err); + process.exit(1); + }); + }, + async reportAsync(err: Error, req?: express.Request): Promise<any> { + if (configs.ENVIRONMENT === 'development') { + return; // Do not log development environment errors + } - return new Promise((resolve, reject) => { - rollbar.handleError(err, req, (rollbarErr: Error) => { - if (rollbarErr) { - utils.consoleLog(`Error reporting to rollbar, ignoring: ${rollbarErr}`); - reject(rollbarErr); - } else { - resolve(); - } - }); - }); - }, - errorHandler() { - return rollbar.errorHandler(configs.ROLLBAR_ACCESS_KEY); - }, + return new Promise((resolve, reject) => { + rollbar.handleError(err, req, (rollbarErr: Error) => { + if (rollbarErr) { + utils.consoleLog(`Error reporting to rollbar, ignoring: ${rollbarErr}`); + reject(rollbarErr); + } else { + resolve(); + } + }); + }); + }, + errorHandler() { + return rollbar.errorHandler(configs.ROLLBAR_ACCESS_KEY); + }, }; diff --git a/packages/testnet-faucets/src/ts/ether_request_queue.ts b/packages/testnet-faucets/src/ts/ether_request_queue.ts index aa75a00e4..1c4b19ab9 100644 --- a/packages/testnet-faucets/src/ts/ether_request_queue.ts +++ b/packages/testnet-faucets/src/ts/ether_request_queue.ts @@ -9,19 +9,19 @@ import { utils } from './utils'; const DISPENSE_AMOUNT_ETHER = 0.1; export class EtherRequestQueue extends RequestQueue { - protected async processNextRequestFireAndForgetAsync(recipientAddress: string) { - utils.consoleLog(`Processing ETH ${recipientAddress}`); - const sendTransactionAsync = promisify(this.web3.eth.sendTransaction); - try { - const txHash = await sendTransactionAsync({ - from: configs.DISPENSER_ADDRESS, - to: recipientAddress, - value: this.web3.toWei(DISPENSE_AMOUNT_ETHER, 'ether'), - }); - utils.consoleLog(`Sent ${DISPENSE_AMOUNT_ETHER} ETH to ${recipientAddress} tx: ${txHash}`); - } catch (err) { - utils.consoleLog(`Unexpected err: ${err} - ${JSON.stringify(err)}`); - await errorReporter.reportAsync(err); - } - } + protected async processNextRequestFireAndForgetAsync(recipientAddress: string) { + utils.consoleLog(`Processing ETH ${recipientAddress}`); + const sendTransactionAsync = promisify(this.web3.eth.sendTransaction); + try { + const txHash = await sendTransactionAsync({ + from: configs.DISPENSER_ADDRESS, + to: recipientAddress, + value: this.web3.toWei(DISPENSE_AMOUNT_ETHER, 'ether'), + }); + utils.consoleLog(`Sent ${DISPENSE_AMOUNT_ETHER} ETH to ${recipientAddress} tx: ${txHash}`); + } catch (err) { + utils.consoleLog(`Unexpected err: ${err} - ${JSON.stringify(err)}`); + await errorReporter.reportAsync(err); + } + } } diff --git a/packages/testnet-faucets/src/ts/global.d.ts b/packages/testnet-faucets/src/ts/global.d.ts index 90866565d..97cd35680 100644 --- a/packages/testnet-faucets/src/ts/global.d.ts +++ b/packages/testnet-faucets/src/ts/global.d.ts @@ -5,22 +5,22 @@ declare module 'web3-provider-engine/subproviders/nonce-tracker'; declare module 'web3-provider-engine/subproviders/hooked-wallet'; declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ } // Ethereumjs-tx declarations declare module 'ethereumjs-tx' { - class EthereumTx { - public raw: Buffer[]; - public r: Buffer; - public s: Buffer; - public v: Buffer; - public serialize(): Buffer; - public sign(buffer: Buffer): void; - constructor(txParams: any); - } - export = EthereumTx; + class EthereumTx { + public raw: Buffer[]; + public r: Buffer; + public s: Buffer; + public v: Buffer; + public serialize(): Buffer; + public sign(buffer: Buffer): void; + constructor(txParams: any); + } + export = EthereumTx; } diff --git a/packages/testnet-faucets/src/ts/handler.ts b/packages/testnet-faucets/src/ts/handler.ts index 455a925e3..bf5b3e81e 100644 --- a/packages/testnet-faucets/src/ts/handler.ts +++ b/packages/testnet-faucets/src/ts/handler.ts @@ -20,95 +20,95 @@ import { ZRXRequestQueue } from './zrx_request_queue'; import * as Web3 from 'web3'; interface RequestQueueByNetworkId { - [networkId: string]: RequestQueue; + [networkId: string]: RequestQueue; } enum QueueType { - ETH = 'ETH', - ZRX = 'ZRX', + ETH = 'ETH', + ZRX = 'ZRX', } const DEFAULT_NETWORK_ID = 42; // kovan export class Handler { - private _etherRequestQueueByNetworkId: RequestQueueByNetworkId = {}; - private _zrxRequestQueueByNetworkId: RequestQueueByNetworkId = {}; - constructor() { - _.forIn(rpcUrls, (rpcUrl: string, networkId: string) => { - const providerObj = this._createProviderEngine(rpcUrl); - const web3 = new Web3(providerObj); - this._etherRequestQueueByNetworkId[networkId] = new EtherRequestQueue(web3); - this._zrxRequestQueueByNetworkId[networkId] = new ZRXRequestQueue(web3, +networkId); - }); - } - public getQueueInfo(req: express.Request, res: express.Response) { - res.setHeader('Content-Type', 'application/json'); - const queueInfo = _.mapValues(rpcUrls, (rpcUrl: string, networkId: string) => { - utils.consoleLog(networkId); - const etherRequestQueue = this._etherRequestQueueByNetworkId[networkId]; - const zrxRequestQueue = this._zrxRequestQueueByNetworkId[networkId]; - return { - ether: { - full: etherRequestQueue.isFull(), - size: etherRequestQueue.size(), - }, - zrx: { - full: zrxRequestQueue.isFull(), - size: zrxRequestQueue.size(), - }, - }; - }); - const payload = JSON.stringify(queueInfo); - res.status(200).send(payload); - } - public dispenseEther(req: express.Request, res: express.Response) { - this._dispense(req, res, this._etherRequestQueueByNetworkId, QueueType.ETH); - } - public dispenseZRX(req: express.Request, res: express.Response) { - this._dispense(req, res, this._zrxRequestQueueByNetworkId, QueueType.ZRX); - } - private _dispense( - req: express.Request, - res: express.Response, - requestQueueByNetworkId: RequestQueueByNetworkId, - queueType: QueueType, - ) { - const recipientAddress = req.params.recipient; - if (_.isUndefined(recipientAddress) || !this._isValidEthereumAddress(recipientAddress)) { - res.status(400).send('INVALID_RECIPIENT_ADDRESS'); - return; - } - const networkId = _.get(req.query, 'networkId', DEFAULT_NETWORK_ID); - const requestQueue = _.get(requestQueueByNetworkId, networkId); - if (_.isUndefined(requestQueue)) { - res.status(400).send('INVALID_NETWORK_ID'); - return; - } - const lowerCaseRecipientAddress = recipientAddress.toLowerCase(); - const didAddToQueue = requestQueue.add(lowerCaseRecipientAddress); - if (!didAddToQueue) { - res.status(503).send('QUEUE_IS_FULL'); - return; - } - utils.consoleLog(`Added ${lowerCaseRecipientAddress} to queue: ${queueType} networkId: ${networkId}`); - res.status(200).end(); - } - // tslint:disable-next-line:prefer-function-over-method - private _createProviderEngine(rpcUrl: string) { - const engine = new ProviderEngine(); - engine.addProvider(new NonceSubprovider()); - engine.addProvider(new HookedWalletSubprovider(idManagement)); - engine.addProvider( - new RpcSubprovider({ - rpcUrl, - }), - ); - engine.start(); - return engine; - } - // tslint:disable-next-line:prefer-function-over-method - private _isValidEthereumAddress(address: string): boolean { - const lowercaseAddress = address.toLowerCase(); - return addressUtils.isAddress(lowercaseAddress); - } + private _etherRequestQueueByNetworkId: RequestQueueByNetworkId = {}; + private _zrxRequestQueueByNetworkId: RequestQueueByNetworkId = {}; + constructor() { + _.forIn(rpcUrls, (rpcUrl: string, networkId: string) => { + const providerObj = this._createProviderEngine(rpcUrl); + const web3 = new Web3(providerObj); + this._etherRequestQueueByNetworkId[networkId] = new EtherRequestQueue(web3); + this._zrxRequestQueueByNetworkId[networkId] = new ZRXRequestQueue(web3, +networkId); + }); + } + public getQueueInfo(req: express.Request, res: express.Response) { + res.setHeader('Content-Type', 'application/json'); + const queueInfo = _.mapValues(rpcUrls, (rpcUrl: string, networkId: string) => { + utils.consoleLog(networkId); + const etherRequestQueue = this._etherRequestQueueByNetworkId[networkId]; + const zrxRequestQueue = this._zrxRequestQueueByNetworkId[networkId]; + return { + ether: { + full: etherRequestQueue.isFull(), + size: etherRequestQueue.size(), + }, + zrx: { + full: zrxRequestQueue.isFull(), + size: zrxRequestQueue.size(), + }, + }; + }); + const payload = JSON.stringify(queueInfo); + res.status(200).send(payload); + } + public dispenseEther(req: express.Request, res: express.Response) { + this._dispense(req, res, this._etherRequestQueueByNetworkId, QueueType.ETH); + } + public dispenseZRX(req: express.Request, res: express.Response) { + this._dispense(req, res, this._zrxRequestQueueByNetworkId, QueueType.ZRX); + } + private _dispense( + req: express.Request, + res: express.Response, + requestQueueByNetworkId: RequestQueueByNetworkId, + queueType: QueueType, + ) { + const recipientAddress = req.params.recipient; + if (_.isUndefined(recipientAddress) || !this._isValidEthereumAddress(recipientAddress)) { + res.status(400).send('INVALID_RECIPIENT_ADDRESS'); + return; + } + const networkId = _.get(req.query, 'networkId', DEFAULT_NETWORK_ID); + const requestQueue = _.get(requestQueueByNetworkId, networkId); + if (_.isUndefined(requestQueue)) { + res.status(400).send('INVALID_NETWORK_ID'); + return; + } + const lowerCaseRecipientAddress = recipientAddress.toLowerCase(); + const didAddToQueue = requestQueue.add(lowerCaseRecipientAddress); + if (!didAddToQueue) { + res.status(503).send('QUEUE_IS_FULL'); + return; + } + utils.consoleLog(`Added ${lowerCaseRecipientAddress} to queue: ${queueType} networkId: ${networkId}`); + res.status(200).end(); + } + // tslint:disable-next-line:prefer-function-over-method + private _createProviderEngine(rpcUrl: string) { + const engine = new ProviderEngine(); + engine.addProvider(new NonceSubprovider()); + engine.addProvider(new HookedWalletSubprovider(idManagement)); + engine.addProvider( + new RpcSubprovider({ + rpcUrl, + }), + ); + engine.start(); + return engine; + } + // tslint:disable-next-line:prefer-function-over-method + private _isValidEthereumAddress(address: string): boolean { + const lowercaseAddress = address.toLowerCase(); + return addressUtils.isAddress(lowercaseAddress); + } } diff --git a/packages/testnet-faucets/src/ts/id_management.ts b/packages/testnet-faucets/src/ts/id_management.ts index 2f04b5ff9..db9b610a3 100644 --- a/packages/testnet-faucets/src/ts/id_management.ts +++ b/packages/testnet-faucets/src/ts/id_management.ts @@ -5,17 +5,17 @@ import { configs } from './configs'; type Callback = (err: Error | null, accounts: any) => void; export const idManagement = { - getAccounts(callback: Callback) { - callback(null, [configs.DISPENSER_ADDRESS]); - }, - approveTransaction(txData: object, callback: Callback) { - callback(null, true); - }, - signTransaction(txData: object, callback: Callback) { - const tx = new EthereumTx(txData); - const privateKeyBuffer = new Buffer(configs.DISPENSER_PRIVATE_KEY as string, 'hex'); - tx.sign(privateKeyBuffer); - const rawTx = `0x${tx.serialize().toString('hex')}`; - callback(null, rawTx); - }, + getAccounts(callback: Callback) { + callback(null, [configs.DISPENSER_ADDRESS]); + }, + approveTransaction(txData: object, callback: Callback) { + callback(null, true); + }, + signTransaction(txData: object, callback: Callback) { + const tx = new EthereumTx(txData); + const privateKeyBuffer = new Buffer(configs.DISPENSER_PRIVATE_KEY as string, 'hex'); + tx.sign(privateKeyBuffer); + const rawTx = `0x${tx.serialize().toString('hex')}`; + callback(null, rawTx); + }, }; diff --git a/packages/testnet-faucets/src/ts/request_queue.ts b/packages/testnet-faucets/src/ts/request_queue.ts index 37b5505f8..20f2833a1 100644 --- a/packages/testnet-faucets/src/ts/request_queue.ts +++ b/packages/testnet-faucets/src/ts/request_queue.ts @@ -11,46 +11,46 @@ const MAX_QUEUE_SIZE = 500; const DEFAULT_QUEUE_INTERVAL_MS = 1000; export class RequestQueue { - protected queueIntervalMs: number; - protected queue: string[]; - protected queueIntervalId: NodeJS.Timer; - protected web3: Web3; - constructor(web3: any) { - this.queueIntervalMs = DEFAULT_QUEUE_INTERVAL_MS; - this.queue = []; + protected queueIntervalMs: number; + protected queue: string[]; + protected queueIntervalId: NodeJS.Timer; + protected web3: Web3; + constructor(web3: any) { + this.queueIntervalMs = DEFAULT_QUEUE_INTERVAL_MS; + this.queue = []; - this.web3 = web3; + this.web3 = web3; - this.start(); - } - public add(recipientAddress: string): boolean { - if (this.isFull()) { - return false; - } - this.queue.push(recipientAddress); - return true; - } - public size(): number { - return this.queue.length; - } - public isFull(): boolean { - return this.size() >= MAX_QUEUE_SIZE; - } - protected start() { - this.queueIntervalId = timers.setInterval(() => { - const recipientAddress = this.queue.shift(); - if (_.isUndefined(recipientAddress)) { - return; - } - // tslint:disable-next-line:no-floating-promises - this.processNextRequestFireAndForgetAsync(recipientAddress); - }, this.queueIntervalMs); - } - protected stop() { - clearInterval(this.queueIntervalId); - } - // tslint:disable-next-line:prefer-function-over-method - protected async processNextRequestFireAndForgetAsync(recipientAddress: string) { - throw new Error('Expected processNextRequestFireAndForgetAsync to be implemented by a subclass'); - } + this.start(); + } + public add(recipientAddress: string): boolean { + if (this.isFull()) { + return false; + } + this.queue.push(recipientAddress); + return true; + } + public size(): number { + return this.queue.length; + } + public isFull(): boolean { + return this.size() >= MAX_QUEUE_SIZE; + } + protected start() { + this.queueIntervalId = timers.setInterval(() => { + const recipientAddress = this.queue.shift(); + if (_.isUndefined(recipientAddress)) { + return; + } + // tslint:disable-next-line:no-floating-promises + this.processNextRequestFireAndForgetAsync(recipientAddress); + }, this.queueIntervalMs); + } + protected stop() { + clearInterval(this.queueIntervalId); + } + // tslint:disable-next-line:prefer-function-over-method + protected async processNextRequestFireAndForgetAsync(recipientAddress: string) { + throw new Error('Expected processNextRequestFireAndForgetAsync to be implemented by a subclass'); + } } diff --git a/packages/testnet-faucets/src/ts/rpc_urls.ts b/packages/testnet-faucets/src/ts/rpc_urls.ts index 9e12bca37..25a3b938f 100644 --- a/packages/testnet-faucets/src/ts/rpc_urls.ts +++ b/packages/testnet-faucets/src/ts/rpc_urls.ts @@ -1,13 +1,13 @@ import { configs } from './configs'; const productionRpcUrls = { - '2': `https://ropsten.infura.io/${configs.INFURA_API_KEY}`, - '3': `https://rinkeby.infura.io/${configs.INFURA_API_KEY}`, - '42': `https://kovan.infura.io/${configs.INFURA_API_KEY}`, + '2': `https://ropsten.infura.io/${configs.INFURA_API_KEY}`, + '3': `https://rinkeby.infura.io/${configs.INFURA_API_KEY}`, + '42': `https://kovan.infura.io/${configs.INFURA_API_KEY}`, }; const developmentRpcUrls = { - '50': 'http://127.0.0.1:8545', + '50': 'http://127.0.0.1:8545', }; export const rpcUrls = configs.ENVIRONMENT === 'development' ? developmentRpcUrls : productionRpcUrls; diff --git a/packages/testnet-faucets/src/ts/server.ts b/packages/testnet-faucets/src/ts/server.ts index 6fb555538..26edfff5a 100644 --- a/packages/testnet-faucets/src/ts/server.ts +++ b/packages/testnet-faucets/src/ts/server.ts @@ -10,14 +10,14 @@ errorReporter.setup(); const app = express(); app.use(bodyParser.json()); // for parsing application/json app.use((req, res, next) => { - res.header('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); - next(); + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); + next(); }); const handler = new Handler(); app.get('/ping', (req: express.Request, res: express.Response) => { - res.status(200).send('pong'); + res.status(200).send('pong'); }); app.get('/info', handler.getQueueInfo.bind(handler)); app.get('/ether/:recipient', handler.dispenseEther.bind(handler)); diff --git a/packages/testnet-faucets/src/ts/utils.ts b/packages/testnet-faucets/src/ts/utils.ts index 821fe453c..893f82ca3 100644 --- a/packages/testnet-faucets/src/ts/utils.ts +++ b/packages/testnet-faucets/src/ts/utils.ts @@ -1,7 +1,7 @@ export const utils = { - consoleLog(message: string) { - /* tslint:disable */ - console.log(message); - /* tslint:enable */ - }, + consoleLog(message: string) { + /* tslint:disable */ + console.log(message); + /* tslint:enable */ + }, }; diff --git a/packages/testnet-faucets/src/ts/zrx_request_queue.ts b/packages/testnet-faucets/src/ts/zrx_request_queue.ts index b81a44cd1..3d73f9dd2 100644 --- a/packages/testnet-faucets/src/ts/zrx_request_queue.ts +++ b/packages/testnet-faucets/src/ts/zrx_request_queue.ts @@ -17,30 +17,30 @@ const DISPENSE_AMOUNT_ZRX = new BigNumber(0.1); const QUEUE_INTERVAL_MS = 5000; export class ZRXRequestQueue extends RequestQueue { - private _zeroEx: ZeroEx; - constructor(web3: Web3, networkId: number) { - super(web3); - this.queueIntervalMs = QUEUE_INTERVAL_MS; - const zeroExConfig = { - networkId, - }; - this._zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig); - } - protected async processNextRequestFireAndForgetAsync(recipientAddress: string) { - utils.consoleLog(`Processing ZRX ${recipientAddress}`); - const baseUnitAmount = ZeroEx.toBaseUnitAmount(DISPENSE_AMOUNT_ZRX, 18); - try { - const zrxTokenAddress = this._zeroEx.exchange.getZRXTokenAddress(); - const txHash = await this._zeroEx.token.transferAsync( - zrxTokenAddress, - configs.DISPENSER_ADDRESS, - recipientAddress, - baseUnitAmount, - ); - utils.consoleLog(`Sent ${DISPENSE_AMOUNT_ZRX} ZRX to ${recipientAddress} tx: ${txHash}`); - } catch (err) { - utils.consoleLog(`Unexpected err: ${err} - ${JSON.stringify(err)}`); - await errorReporter.reportAsync(err); - } - } + private _zeroEx: ZeroEx; + constructor(web3: Web3, networkId: number) { + super(web3); + this.queueIntervalMs = QUEUE_INTERVAL_MS; + const zeroExConfig = { + networkId, + }; + this._zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig); + } + protected async processNextRequestFireAndForgetAsync(recipientAddress: string) { + utils.consoleLog(`Processing ZRX ${recipientAddress}`); + const baseUnitAmount = ZeroEx.toBaseUnitAmount(DISPENSE_AMOUNT_ZRX, 18); + try { + const zrxTokenAddress = this._zeroEx.exchange.getZRXTokenAddress(); + const txHash = await this._zeroEx.token.transferAsync( + zrxTokenAddress, + configs.DISPENSER_ADDRESS, + recipientAddress, + baseUnitAmount, + ); + utils.consoleLog(`Sent ${DISPENSE_AMOUNT_ZRX} ZRX to ${recipientAddress} tx: ${txHash}`); + } catch (err) { + utils.consoleLog(`Unexpected err: ${err} - ${JSON.stringify(err)}`); + await errorReporter.reportAsync(err); + } + } } diff --git a/packages/testnet-faucets/tsconfig.json b/packages/testnet-faucets/tsconfig.json index e3353cc95..7f0c084ff 100644 --- a/packages/testnet-faucets/tsconfig.json +++ b/packages/testnet-faucets/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./src/ts/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/ts/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] } diff --git a/packages/testnet-faucets/tslint.json b/packages/testnet-faucets/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/testnet-faucets/tslint.json +++ b/packages/testnet-faucets/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/tslint-config/README.md b/packages/tslint-config/README.md index 71fdd9772..8a6fa8a2f 100644 --- a/packages/tslint-config/README.md +++ b/packages/tslint-config/README.md @@ -14,7 +14,7 @@ Add the following to your `tslint.json` file ```json { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } ``` diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json index 95bd67ac6..8dd884370 100644 --- a/packages/tslint-config/package.json +++ b/packages/tslint-config/package.json @@ -1,37 +1,37 @@ { - "name": "@0xproject/tslint-config", - "version": "0.4.4", - "description": "Lint rules related to 0xProject for TSLint", - "main": "tslint.json", - "scripts": { - "build": "tsc", - "clean": "shx rm -rf lib", - "lint": "tslint --project . 'rules/**/*.ts'" - }, - "files": ["tslint.js", "README.md", "LICENSE"], - "repository": { - "type": "git", - "url": "git://github.com/0xProject/0x.js.git" - }, - "keywords": ["tslint", "config", "0xProject", "typescript", "ts"], - "author": { - "name": "Fabio Berger", - "email": "fabio@0xproject.com" - }, - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/tslint-config/README.md", - "devDependencies": { - "@types/lodash": "^4.14.86", - "shx": "^0.2.2", - "tslint": "5.8.0", - "tslint-eslint-rules": "^4.1.1", - "typescript": "~2.6.1" - }, - "dependencies": { - "lodash": "^4.17.4", - "tslint-react": "^3.2.0" - } + "name": "@0xproject/tslint-config", + "version": "0.4.4", + "description": "Lint rules related to 0xProject for TSLint", + "main": "tslint.json", + "scripts": { + "build": "tsc", + "clean": "shx rm -rf lib", + "lint": "tslint --project . 'rules/**/*.ts'" + }, + "files": ["tslint.js", "README.md", "LICENSE"], + "repository": { + "type": "git", + "url": "git://github.com/0xProject/0x.js.git" + }, + "keywords": ["tslint", "config", "0xProject", "typescript", "ts"], + "author": { + "name": "Fabio Berger", + "email": "fabio@0xproject.com" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/tslint-config/README.md", + "devDependencies": { + "@types/lodash": "^4.14.86", + "shx": "^0.2.2", + "tslint": "5.8.0", + "tslint-eslint-rules": "^4.1.1", + "typescript": "~2.6.1" + }, + "dependencies": { + "lodash": "^4.17.4", + "tslint-react": "^3.2.0" + } } diff --git a/packages/tslint-config/rules/asyncSuffixRule.ts b/packages/tslint-config/rules/asyncSuffixRule.ts index 8af9d88d4..5215c7151 100644 --- a/packages/tslint-config/rules/asyncSuffixRule.ts +++ b/packages/tslint-config/rules/asyncSuffixRule.ts @@ -4,7 +4,7 @@ import * as ts from 'typescript'; import { AsyncSuffixWalker } from './walkers/async_suffix'; export class Rule extends Lint.Rules.AbstractRule { - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - return this.applyWithWalker(new AsyncSuffixWalker(sourceFile, this.getOptions())); - } + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new AsyncSuffixWalker(sourceFile, this.getOptions())); + } } diff --git a/packages/tslint-config/rules/underscorePrivatesRule.ts b/packages/tslint-config/rules/underscorePrivatesRule.ts index 4d9ee69f7..472ea09ff 100644 --- a/packages/tslint-config/rules/underscorePrivatesRule.ts +++ b/packages/tslint-config/rules/underscorePrivatesRule.ts @@ -4,58 +4,58 @@ import * as ts from 'typescript'; const UNDERSCORE = '_'; type RelevantClassMember = - | ts.MethodDeclaration - | ts.PropertyDeclaration - | ts.GetAccessorDeclaration - | ts.SetAccessorDeclaration; + | ts.MethodDeclaration + | ts.PropertyDeclaration + | ts.GetAccessorDeclaration + | ts.SetAccessorDeclaration; // Copied from: https://github.com/DanielRosenwasser/underscore-privates-tslint-rule // The version on github is not published on npm export class Rule extends Lint.Rules.AbstractRule { - public static FAILURE_STRING = 'private members must be prefixed with an underscore'; + public static FAILURE_STRING = 'private members must be prefixed with an underscore'; - public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { - return this.applyWithFunction(sourceFile, walk); - } + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithFunction(sourceFile, walk); + } } function walk(ctx: Lint.WalkContext<void>): void { - traverse(ctx.sourceFile); + traverse(ctx.sourceFile); - function traverse(node: ts.Node): void { - checkNodeForViolations(ctx, node); - return ts.forEachChild(node, traverse); - } + function traverse(node: ts.Node): void { + checkNodeForViolations(ctx, node); + return ts.forEachChild(node, traverse); + } } function checkNodeForViolations(ctx: Lint.WalkContext<void>, node: ts.Node): void { - if (!isRelevantClassMember(node)) { - return; - } - // The declaration might have a computed property name or a numeric name. - const name = node.name; - if (!nameIsIdentifier(name)) { - return; - } - if (!nameStartsWithUnderscore(name.text) && memberIsPrivate(node)) { - ctx.addFailureAtNode(name, Rule.FAILURE_STRING); - } + if (!isRelevantClassMember(node)) { + return; + } + // The declaration might have a computed property name or a numeric name. + const name = node.name; + if (!nameIsIdentifier(name)) { + return; + } + if (!nameStartsWithUnderscore(name.text) && memberIsPrivate(node)) { + ctx.addFailureAtNode(name, Rule.FAILURE_STRING); + } } function isRelevantClassMember(node: ts.Node): node is RelevantClassMember { - switch (node.kind) { - case ts.SyntaxKind.MethodDeclaration: - case ts.SyntaxKind.PropertyDeclaration: - case ts.SyntaxKind.GetAccessor: - case ts.SyntaxKind.SetAccessor: - return true; - default: - return false; - } + switch (node.kind) { + case ts.SyntaxKind.MethodDeclaration: + case ts.SyntaxKind.PropertyDeclaration: + case ts.SyntaxKind.GetAccessor: + case ts.SyntaxKind.SetAccessor: + return true; + default: + return false; + } } function nameStartsWithUnderscore(text: string) { - return text.charCodeAt(0) === UNDERSCORE.charCodeAt(0); + return text.charCodeAt(0) === UNDERSCORE.charCodeAt(0); } function memberIsPrivate(node: ts.Declaration) { - return Lint.hasModifier(node.modifiers, ts.SyntaxKind.PrivateKeyword); + return Lint.hasModifier(node.modifiers, ts.SyntaxKind.PrivateKeyword); } function nameIsIdentifier(node: ts.Node): node is ts.Identifier { - return node.kind === ts.SyntaxKind.Identifier; + return node.kind === ts.SyntaxKind.Identifier; } diff --git a/packages/tslint-config/rules/walkers/async_suffix.ts b/packages/tslint-config/rules/walkers/async_suffix.ts index e1138de8d..eaec9c5f6 100644 --- a/packages/tslint-config/rules/walkers/async_suffix.ts +++ b/packages/tslint-config/rules/walkers/async_suffix.ts @@ -3,24 +3,24 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; export class AsyncSuffixWalker extends Lint.RuleWalker { - public static FAILURE_STRING = 'async functions must have an Async suffix'; - public visitMethodDeclaration(node: ts.MethodDeclaration): void { - const methodNameNode = node.name; - const methodName = methodNameNode.getText(); - if (!_.isUndefined(node.type)) { - if (node.type.kind === ts.SyntaxKind.TypeReference) { - // tslint:disable-next-line:no-unnecessary-type-assertion - const returnTypeName = (node.type as ts.TypeReferenceNode).typeName.getText(); - if (returnTypeName === 'Promise' && !methodName.endsWith('Async')) { - const failure = this.createFailure( - methodNameNode.getStart(), - methodNameNode.getWidth(), - AsyncSuffixWalker.FAILURE_STRING, - ); - this.addFailure(failure); - } - } - } - super.visitMethodDeclaration(node); - } + public static FAILURE_STRING = 'async functions must have an Async suffix'; + public visitMethodDeclaration(node: ts.MethodDeclaration): void { + const methodNameNode = node.name; + const methodName = methodNameNode.getText(); + if (!_.isUndefined(node.type)) { + if (node.type.kind === ts.SyntaxKind.TypeReference) { + // tslint:disable-next-line:no-unnecessary-type-assertion + const returnTypeName = (node.type as ts.TypeReferenceNode).typeName.getText(); + if (returnTypeName === 'Promise' && !methodName.endsWith('Async')) { + const failure = this.createFailure( + methodNameNode.getStart(), + methodNameNode.getWidth(), + AsyncSuffixWalker.FAILURE_STRING, + ); + this.addFailure(failure); + } + } + } + super.visitMethodDeclaration(node); + } } diff --git a/packages/tslint-config/tsconfig.json b/packages/tslint-config/tsconfig.json index 83db0fcd2..15da53092 100644 --- a/packages/tslint-config/tsconfig.json +++ b/packages/tslint-config/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./rules/**/*"] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./rules/**/*"] } diff --git a/packages/tslint-config/tslint.json b/packages/tslint-config/tslint.json index f2a9558b9..971588b08 100644 --- a/packages/tslint-config/tslint.json +++ b/packages/tslint-config/tslint.json @@ -1,101 +1,101 @@ { - "extends": ["tslint:latest", "tslint-react", "tslint-eslint-rules"], - "rules": { - "adjacent-overload-signatures": true, - "arrow-parens": [true, "ban-single-arg-parens"], - "arrow-return-shorthand": true, - "async-suffix": true, - "await-promise": true, - "binary-expression-operand-order": true, - "callable-types": true, - "class-name": true, - "completed-docs": [ - true, - { - "functions": { "visibilities": ["exported"] }, - "methods": { "locations": "instance", "privacies": ["public", "protected"] } - } - ], - "curly": true, - "eofline": true, - "encoding": true, - "import-spacing": true, - "indent": [true, "spaces", 4], - "interface-name": false, - "interface-over-type-literal": true, - "linebreak-style": [true, "LF"], - "max-classes-per-file": false, - "max-classes-per-file": [true, 1], - "max-line-length": false, - "max-file-line-count": [true, 500], - "member-access": true, - "member-ordering": [true, "public-before-private", "static-before-instance", "variables-before-functions"], - "newline-before-return": false, - "new-parens": true, - "no-angle-bracket-type-assertion": true, - "no-boolean-literal-compare": true, - "no-default-export": true, - "no-empty-interface": false, - "no-floating-promises": true, - "no-non-null-assertion": true, - "no-parameter-reassignment": true, - "no-redundant-jsdoc": true, - "no-return-await": true, - "no-string-throw": true, - "no-submodule-imports": false, - "no-unnecessary-type-assertion": true, - "no-unused-variable": [true, { "ignore-pattern": "^_\\d*" }], - "no-implicit-dependencies": [true, "dev"], - "number-literal-format": true, - "object-literal-sort-keys": false, - "object-literal-key-quotes": false, - "ordered-imports": [ - true, - { - "grouped-imports": true - } - ], - "prefer-const": true, - "prefer-for-of": true, - "prefer-function-over-method": true, - "promise-function-async": true, - "quotemark": [true, "single", "avoid-escape", "jsx-double"], - "restrict-plus-operands": true, - "semicolon": [true, "always"], - "space-before-function-paren": [ - true, - { - "anonymous": "never", - "named": "never", - "method": "never", - "constructor": "never", - "asyncArrow": "always" - } - ], - "space-within-parens": false, - "type-literal-delimiter": true, - "underscore-privates": true, - "variable-name": [true, "ban-keywords", "allow-pascal-case"], - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-rest-spread", - "check-type", - "check-typecast", - "check-preblock" - ], - "jsx-alignment": true, - "jsx-boolean-value": true, - "jsx-curly-spacing": [true, "never"], - "jsx-no-lambda": true, - "jsx-no-multiline-js": false, - "jsx-no-string-ref": true, - "jsx-self-close": true, - "jsx-wrap-multiline": false, - "jsx-no-bind": false - }, - "rulesDirectory": "lib" + "extends": ["tslint:latest", "tslint-react", "tslint-eslint-rules"], + "rules": { + "adjacent-overload-signatures": true, + "arrow-parens": [true, "ban-single-arg-parens"], + "arrow-return-shorthand": true, + "async-suffix": true, + "await-promise": true, + "binary-expression-operand-order": true, + "callable-types": true, + "class-name": true, + "completed-docs": [ + true, + { + "functions": { "visibilities": ["exported"] }, + "methods": { "locations": "instance", "privacies": ["public", "protected"] } + } + ], + "curly": true, + "eofline": true, + "encoding": true, + "import-spacing": true, + "indent": [true, "spaces", 4], + "interface-name": false, + "interface-over-type-literal": true, + "linebreak-style": [true, "LF"], + "max-classes-per-file": false, + "max-classes-per-file": [true, 1], + "max-line-length": false, + "max-file-line-count": [true, 500], + "member-access": true, + "member-ordering": [true, "public-before-private", "static-before-instance", "variables-before-functions"], + "newline-before-return": false, + "new-parens": true, + "no-angle-bracket-type-assertion": true, + "no-boolean-literal-compare": true, + "no-default-export": true, + "no-empty-interface": false, + "no-floating-promises": true, + "no-non-null-assertion": true, + "no-parameter-reassignment": true, + "no-redundant-jsdoc": true, + "no-return-await": true, + "no-string-throw": true, + "no-submodule-imports": false, + "no-unnecessary-type-assertion": true, + "no-unused-variable": [true, { "ignore-pattern": "^_\\d*" }], + "no-implicit-dependencies": [true, "dev"], + "number-literal-format": true, + "object-literal-sort-keys": false, + "object-literal-key-quotes": false, + "ordered-imports": [ + true, + { + "grouped-imports": true + } + ], + "prefer-const": true, + "prefer-for-of": true, + "prefer-function-over-method": true, + "promise-function-async": true, + "quotemark": [true, "single", "avoid-escape", "jsx-double"], + "restrict-plus-operands": true, + "semicolon": [true, "always"], + "space-before-function-paren": [ + true, + { + "anonymous": "never", + "named": "never", + "method": "never", + "constructor": "never", + "asyncArrow": "always" + } + ], + "space-within-parens": false, + "type-literal-delimiter": true, + "underscore-privates": true, + "variable-name": [true, "ban-keywords", "allow-pascal-case"], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-rest-spread", + "check-type", + "check-typecast", + "check-preblock" + ], + "jsx-alignment": true, + "jsx-boolean-value": true, + "jsx-curly-spacing": [true, "never"], + "jsx-no-lambda": true, + "jsx-no-multiline-js": false, + "jsx-no-string-ref": true, + "jsx-self-close": true, + "jsx-wrap-multiline": false, + "jsx-no-bind": false + }, + "rulesDirectory": "lib" } diff --git a/packages/types/package.json b/packages/types/package.json index a7df01bde..42a2be4e0 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,31 +1,31 @@ { - "name": "@0xproject/types", - "version": "0.1.6", - "description": "0x types", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "scripts": { - "build": "tsc", - "clean": "shx rm -rf lib", - "lint": "tslint --project . 'src/**/*.ts'" - }, - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/types/README.md", - "devDependencies": { - "@0xproject/tslint-config": "^0.4.4", - "shx": "^0.2.2", - "tslint": "5.8.0", - "typescript": "~2.6.1" - }, - "dependencies": { - "@0xproject/utils": "^0.2.2", - "web3": "^0.20.0" - } + "name": "@0xproject/types", + "version": "0.1.6", + "description": "0x types", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "tsc", + "clean": "shx rm -rf lib", + "lint": "tslint --project . 'src/**/*.ts'" + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/types/README.md", + "devDependencies": { + "@0xproject/tslint-config": "^0.4.4", + "shx": "^0.2.2", + "tslint": "5.8.0", + "typescript": "~2.6.1" + }, + "dependencies": { + "@0xproject/utils": "^0.2.2", + "web3": "^0.20.0" + } } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index f8d211052..9cf9bc7af 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -2,26 +2,26 @@ import { BigNumber } from '@0xproject/utils'; import * as Web3 from 'web3'; export interface TxData { - from?: string; - gas?: number; - gasPrice?: BigNumber; - nonce?: number; + from?: string; + gas?: number; + gasPrice?: BigNumber; + nonce?: number; } export interface TxDataPayable extends TxData { - value?: BigNumber; + value?: BigNumber; } export interface TransactionReceipt { - blockHash: string; - blockNumber: number; - transactionHash: string; - transactionIndex: number; - from: string; - to: string; - status: null | 0 | 1; - cumulativeGasUsed: number; - gasUsed: number; - contractAddress: string | null; - logs: Web3.LogEntry[]; + blockHash: string; + blockNumber: number; + transactionHash: string; + transactionIndex: number; + from: string; + to: string; + status: null | 0 | 1; + cumulativeGasUsed: number; + gasUsed: number; + contractAddress: string | null; + logs: Web3.LogEntry[]; } diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json index fc88c962d..3d967d05f 100644 --- a/packages/types/tsconfig.json +++ b/packages/types/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./src/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] } diff --git a/packages/types/tslint.json b/packages/types/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/types/tslint.json +++ b/packages/types/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/utils/package.json b/packages/utils/package.json index 7a5a10337..47a297859 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,34 +1,34 @@ { - "name": "@0xproject/utils", - "version": "0.2.2", - "description": "0x TS utils", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "scripts": { - "build": "tsc", - "clean": "shx rm -rf lib", - "lint": "tslint --project . 'src/**/*.ts'" - }, - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/utils/README.md", - "devDependencies": { - "@0xproject/tslint-config": "^0.4.4", - "@types/lodash": "^4.14.86", - "npm-run-all": "^4.1.2", - "shx": "^0.2.2", - "tslint": "5.8.0", - "typescript": "~2.6.1" - }, - "dependencies": { - "bignumber.js": "~4.1.0", - "js-sha3": "^0.7.0", - "lodash": "^4.17.4" - } + "name": "@0xproject/utils", + "version": "0.2.2", + "description": "0x TS utils", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "tsc", + "clean": "shx rm -rf lib", + "lint": "tslint --project . 'src/**/*.ts'" + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/utils/README.md", + "devDependencies": { + "@0xproject/tslint-config": "^0.4.4", + "@types/lodash": "^4.14.86", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.8.0", + "typescript": "~2.6.1" + }, + "dependencies": { + "bignumber.js": "~4.1.0", + "js-sha3": "^0.7.0", + "lodash": "^4.17.4" + } } diff --git a/packages/utils/src/address_utils.ts b/packages/utils/src/address_utils.ts index 07042cb43..f94985441 100644 --- a/packages/utils/src/address_utils.ts +++ b/packages/utils/src/address_utils.ts @@ -4,33 +4,33 @@ const BASIC_ADDRESS_REGEX = /^(0x)?[0-9a-f]{40}$/i; const SAME_CASE_ADDRESS_REGEX = /^(0x)?([0-9a-f]{40}|[0-9A-F]{40})$/; export const addressUtils = { - isChecksumAddress(address: string): boolean { - // Check each case - const unprefixedAddress = address.replace('0x', ''); - const addressHash = jsSHA3.keccak256(unprefixedAddress.toLowerCase()); + isChecksumAddress(address: string): boolean { + // Check each case + const unprefixedAddress = address.replace('0x', ''); + const addressHash = jsSHA3.keccak256(unprefixedAddress.toLowerCase()); - for (let i = 0; i < 40; i++) { - // The nth letter should be uppercase if the nth digit of casemap is 1 - if ( - (parseInt(addressHash[i], 16) > 7 && unprefixedAddress[i].toUpperCase() !== unprefixedAddress[i]) || - (parseInt(addressHash[i], 16) <= 7 && unprefixedAddress[i].toLowerCase() !== unprefixedAddress[i]) - ) { - return false; - } - } - return true; - }, - isAddress(address: string): boolean { - if (!BASIC_ADDRESS_REGEX.test(address)) { - // Check if it has the basic requirements of an address - return false; - } else if (SAME_CASE_ADDRESS_REGEX.test(address)) { - // If it's all small caps or all all caps, return true - return true; - } else { - // Otherwise check each case - const isValidChecksummedAddress = addressUtils.isChecksumAddress(address); - return isValidChecksummedAddress; - } - }, + for (let i = 0; i < 40; i++) { + // The nth letter should be uppercase if the nth digit of casemap is 1 + if ( + (parseInt(addressHash[i], 16) > 7 && unprefixedAddress[i].toUpperCase() !== unprefixedAddress[i]) || + (parseInt(addressHash[i], 16) <= 7 && unprefixedAddress[i].toLowerCase() !== unprefixedAddress[i]) + ) { + return false; + } + } + return true; + }, + isAddress(address: string): boolean { + if (!BASIC_ADDRESS_REGEX.test(address)) { + // Check if it has the basic requirements of an address + return false; + } else if (SAME_CASE_ADDRESS_REGEX.test(address)) { + // If it's all small caps or all all caps, return true + return true; + } else { + // Otherwise check each case + const isValidChecksummedAddress = addressUtils.isChecksumAddress(address); + return isValidChecksummedAddress; + } + }, }; diff --git a/packages/utils/src/class_utils.ts b/packages/utils/src/class_utils.ts index 7fb011f91..04e60ee57 100644 --- a/packages/utils/src/class_utils.ts +++ b/packages/utils/src/class_utils.ts @@ -1,18 +1,18 @@ import * as _ from 'lodash'; export const classUtils = { - // This is useful for classes that have nested methods. Nested methods don't get bound out of the box. - bindAll(self: any, exclude: string[] = ['contructor'], thisArg?: any): void { - for (const key of Object.getOwnPropertyNames(self)) { - const val = self[key]; - if (!_.includes(exclude, key)) { - if (_.isFunction(val)) { - self[key] = val.bind(thisArg || self); - } else if (_.isObject(val)) { - classUtils.bindAll(val, exclude, self); - } - } - } - return self; - }, + // This is useful for classes that have nested methods. Nested methods don't get bound out of the box. + bindAll(self: any, exclude: string[] = ['contructor'], thisArg?: any): void { + for (const key of Object.getOwnPropertyNames(self)) { + const val = self[key]; + if (!_.includes(exclude, key)) { + if (_.isFunction(val)) { + self[key] = val.bind(thisArg || self); + } else if (_.isObject(val)) { + classUtils.bindAll(val, exclude, self); + } + } + } + return self; + }, }; diff --git a/packages/utils/src/configured_bignumber.ts b/packages/utils/src/configured_bignumber.ts index d819e9365..e44c062c2 100644 --- a/packages/utils/src/configured_bignumber.ts +++ b/packages/utils/src/configured_bignumber.ts @@ -3,7 +3,7 @@ import { BigNumber } from 'bignumber.js'; // By default BigNumber's `toString` method converts to exponential notation if the value has // more then 20 digits. We want to avoid this behavior, so we set EXPONENTIAL_AT to a high number BigNumber.config({ - EXPONENTIAL_AT: 1000, + EXPONENTIAL_AT: 1000, }); export { BigNumber }; diff --git a/packages/utils/src/interval_utils.ts b/packages/utils/src/interval_utils.ts index 65e51677b..ebecc7015 100644 --- a/packages/utils/src/interval_utils.ts +++ b/packages/utils/src/interval_utils.ts @@ -1,37 +1,37 @@ import * as _ from 'lodash'; export const intervalUtils = { - setAsyncExcludingInterval(fn: () => Promise<void>, intervalMs: number, onError: (err: Error) => void) { - let locked = false; - const intervalId = setInterval(async () => { - if (locked) { - return; - } else { - locked = true; - try { - await fn(); - } catch (err) { - onError(err); - } - locked = false; - } - }, intervalMs); - return intervalId; - }, - clearAsyncExcludingInterval(intervalId: NodeJS.Timer): void { - clearInterval(intervalId); - }, - setInterval(fn: () => void, intervalMs: number, onError: (err: Error) => void) { - const intervalId = setInterval(() => { - try { - fn(); - } catch (err) { - onError(err); - } - }, intervalMs); - return intervalId; - }, - clearInterval(intervalId: NodeJS.Timer): void { - clearInterval(intervalId); - }, + setAsyncExcludingInterval(fn: () => Promise<void>, intervalMs: number, onError: (err: Error) => void) { + let locked = false; + const intervalId = setInterval(async () => { + if (locked) { + return; + } else { + locked = true; + try { + await fn(); + } catch (err) { + onError(err); + } + locked = false; + } + }, intervalMs); + return intervalId; + }, + clearAsyncExcludingInterval(intervalId: NodeJS.Timer): void { + clearInterval(intervalId); + }, + setInterval(fn: () => void, intervalMs: number, onError: (err: Error) => void) { + const intervalId = setInterval(() => { + try { + fn(); + } catch (err) { + onError(err); + } + }, intervalMs); + return intervalId; + }, + clearInterval(intervalId: NodeJS.Timer): void { + clearInterval(intervalId); + }, }; diff --git a/packages/utils/src/promisify.ts b/packages/utils/src/promisify.ts index 6b29664f9..29d626b61 100644 --- a/packages/utils/src/promisify.ts +++ b/packages/utils/src/promisify.ts @@ -7,13 +7,13 @@ import * as _ from 'lodash'; */ // HACK: This can't be properly typed without variadic kinds https://github.com/Microsoft/TypeScript/issues/5453 export function promisify<T>(originalFn: (...args: any[]) => void, thisArg?: any): (...callArgs: any[]) => Promise<T> { - const promisifiedFunction = async (...callArgs: any[]): Promise<T> => { - return new Promise<T>((resolve, reject) => { - const callback = (err: Error | null, data?: T) => { - _.isNull(err) ? resolve(data) : reject(err); - }; - originalFn.apply(thisArg, [...callArgs, callback]); - }); - }; - return promisifiedFunction; + const promisifiedFunction = async (...callArgs: any[]): Promise<T> => { + return new Promise<T>((resolve, reject) => { + const callback = (err: Error | null, data?: T) => { + _.isNull(err) ? resolve(data) : reject(err); + }; + originalFn.apply(thisArg, [...callArgs, callback]); + }); + }; + return promisifiedFunction; } diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index fc88c962d..3d967d05f 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./src/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] } diff --git a/packages/utils/tslint.json b/packages/utils/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/utils/tslint.json +++ b/packages/utils/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/web3-typescript-typings/index.d.ts b/packages/web3-typescript-typings/index.d.ts index b610d0e48..cd34759ed 100644 --- a/packages/web3-typescript-typings/index.d.ts +++ b/packages/web3-typescript-typings/index.d.ts @@ -1,422 +1,422 @@ declare module 'web3' { - import * as BigNumber from 'bignumber.js'; - - type MixedData = string | number | object | any[] | BigNumber.BigNumber; - - class Web3 { - public static providers: typeof providers; - public currentProvider: Web3.Provider; - - public eth: Web3.EthApi; - public personal: Web3.PersonalApi | undefined; - public version: Web3.VersionApi; - public net: Web3.NetApi; - - public constructor(provider?: Web3.Provider); - - public isConnected(): boolean; - public setProvider(provider: Web3.Provider): void; - public reset(keepIsSyncing: boolean): void; - public toHex(data: MixedData): string; - public toAscii(hex: string): string; - public fromAscii(ascii: string, padding?: number): string; - public toDecimal(hex: string): number; - public fromDecimal(value: number | string): string; - public fromWei(value: number | string, unit: Web3.Unit): string; - public fromWei(value: BigNumber.BigNumber, unit: Web3.Unit): BigNumber.BigNumber; - public toWei(amount: number | string, unit: Web3.Unit): string; - public toWei(amount: BigNumber.BigNumber, unit: Web3.Unit): BigNumber.BigNumber; - public toBigNumber(value: number | string): BigNumber.BigNumber; - public isAddress(address: string): boolean; - public isChecksumAddress(address: string): boolean; - public sha3(value: string, options?: Web3.Sha3Options): string; - } - - namespace providers { - class HttpProvider implements Web3.Provider { - constructor(url?: string, timeout?: number, username?: string, password?: string); - public sendAsync( - payload: Web3.JSONRPCRequestPayload, - callback: (err: Error, result: Web3.JSONRPCResponsePayload) => void, - ): void; - } - } - - namespace Web3 { - type ContractAbi = AbiDefinition[]; - - type AbiDefinition = FunctionAbi | EventAbi; - - type FunctionAbi = MethodAbi | ConstructorAbi | FallbackAbi; - - enum AbiType { - Function = 'function', - Constructor = 'constructor', - Event = 'event', - Fallback = 'fallback', - } - - type ConstructorStateMutability = 'nonpayable' | 'payable'; - type StateMutability = 'pure' | 'view' | ConstructorStateMutability; - - interface MethodAbi { - type: AbiType.Function; - name: string; - inputs: FunctionParameter[]; - outputs: FunctionParameter[]; - constant: boolean; - stateMutability: StateMutability; - payable: boolean; - } - - interface ConstructorAbi { - type: AbiType.Constructor; - inputs: FunctionParameter[]; - payable: boolean; - stateMutability: ConstructorStateMutability; - } - - interface FallbackAbi { - type: AbiType.Fallback; - payable: boolean; - } - - interface EventParameter { - name: string; - type: string; - indexed: boolean; - } - - interface EventAbi { - type: AbiType.Event; - name: string; - inputs: EventParameter[]; - anonymous: boolean; - } - - interface FunctionParameter { - name: string; - type: string; - } - - interface ContractInstance { - address: string; - abi: Web3.ContractAbi; - [name: string]: any; - } - - interface Contract<A extends ContractInstance> { - at(address: string): A; - getData(...args: any[]): string; - 'new'(...args: any[]): A; - } - - interface FilterObject { - fromBlock?: number | string; - toBlock?: number | string; - address?: string; - topics?: LogTopic[]; - } - - type LogTopic = null | string | string[]; - - interface DecodedLogEntry<A> extends LogEntry { - event: string; - args: A; - } - - interface DecodedLogEntryEvent<A> extends DecodedLogEntry<A> { - removed: boolean; - } - - interface LogEntryEvent extends LogEntry { - removed: boolean; - } - - interface FilterResult { - get(callback: () => void): void; - watch(callback: (err: Error, result: LogEntryEvent) => void): void; - stopWatching(callback?: () => void): void; - } - - export interface JSONRPCRequestPayload { - params: any[]; - method: string; - id: number; - jsonrpc: string; - } - - export interface JSONRPCResponsePayload { - result: any; - id: number; - jsonrpc: string; - } - - interface Provider { - sendAsync( - payload: JSONRPCRequestPayload, - callback: (err: Error, result: JSONRPCResponsePayload) => void, - ): void; - } - - interface Sha3Options { - encoding: 'hex'; - } - - interface EthApi { - coinbase: string; - mining: boolean; - hashrate: number; - gasPrice: BigNumber.BigNumber; - accounts: string[]; - blockNumber: number; - defaultAccount?: string; - defaultBlock: Web3.BlockParam; - syncing: Web3.SyncingResult; - compile: { - solidity(sourceString: string, cb?: (err: Error, result: any) => void): object; - }; - getMining(cd: (err: Error, mining: boolean) => void): void; - getHashrate(cd: (err: Error, hashrate: number) => void): void; - getGasPrice(cd: (err: Error, gasPrice: BigNumber.BigNumber) => void): void; - getAccounts(cd: (err: Error, accounts: string[]) => void): void; - getBlockNumber(callback: (err: Error, blockNumber: number) => void): void; - getSyncing(cd: (err: Error, syncing: Web3.SyncingResult) => void): void; - isSyncing(cb: (err: Error, isSyncing: boolean, syncingState: Web3.SyncingState) => void): Web3.IsSyncing; - - getBlock(hashStringOrBlockNumber: string | Web3.BlockParam): Web3.BlockWithoutTransactionData; - getBlock( - hashStringOrBlockNumber: string | Web3.BlockParam, - callback: (err: Error, blockObj: Web3.BlockWithoutTransactionData) => void, - ): void; - getBlock( - hashStringOrBlockNumber: string | Web3.BlockParam, - returnTransactionObjects: true, - ): Web3.BlockWithTransactionData; - getBlock( - hashStringOrBlockNumber: string | Web3.BlockParam, - returnTransactionObjects: true, - callback: (err: Error, blockObj: Web3.BlockWithTransactionData) => void, - ): void; - - getBlockTransactionCount(hashStringOrBlockNumber: string | Web3.BlockParam): number; - getBlockTransactionCount( - hashStringOrBlockNumber: string | Web3.BlockParam, - callback: (err: Error, blockTransactionCount: number) => void, - ): void; - - // TODO returnTransactionObjects - getUncle( - hashStringOrBlockNumber: string | Web3.BlockParam, - uncleNumber: number, - ): Web3.BlockWithoutTransactionData; - getUncle( - hashStringOrBlockNumber: string | Web3.BlockParam, - uncleNumber: number, - callback: (err: Error, uncle: Web3.BlockWithoutTransactionData) => void, - ): void; - - getTransaction(transactionHash: string): Web3.Transaction; - getTransaction( - transactionHash: string, - callback: (err: Error, transaction: Web3.Transaction) => void, - ): void; - - getTransactionFromBlock( - hashStringOrBlockNumber: string | Web3.BlockParam, - indexNumber: number, - ): Web3.Transaction; - getTransactionFromBlock( - hashStringOrBlockNumber: string | Web3.BlockParam, - indexNumber: number, - callback: (err: Error, transaction: Web3.Transaction) => void, - ): void; - - contract(abi: Web3.AbiDefinition[]): Web3.Contract<any>; - - // TODO block param - getBalance(addressHexString: string): BigNumber.BigNumber; - getBalance(addressHexString: string, callback: (err: Error, result: BigNumber.BigNumber) => void): void; - - // TODO block param - getStorageAt(address: string, position: number): string; - getStorageAt(address: string, position: number, callback: (err: Error, storage: string) => void): void; - - // TODO block param - getCode(addressHexString: string): string; - getCode(addressHexString: string, callback: (err: Error, code: string) => void): void; - - filter(value: string | Web3.FilterObject): Web3.FilterResult; - - sendTransaction(txData: Web3.TxData): string; - sendTransaction(txData: Web3.TxData, callback: (err: Error, value: string) => void): void; - - sendRawTransaction(rawTxData: string): string; - sendRawTransaction(rawTxData: string, callback: (err: Error, value: string) => void): void; - - sign(address: string, data: string): string; - sign(address: string, data: string, callback: (err: Error, signature: string) => void): void; - - getTransactionReceipt(txHash: string): Web3.TransactionReceipt | null; - getTransactionReceipt( - txHash: string, - callback: (err: Error, receipt: Web3.TransactionReceipt | null) => void, - ): void; - - // TODO block param - call(callData: Web3.CallData): string; - call(callData: Web3.CallData, callback: (err: Error, result: string) => void): void; - - estimateGas(callData: Web3.CallData): number; - estimateGas(callData: Web3.CallData, callback: (err: Error, gas: number) => void): void; - - // TODO defaultBlock - getTransactionCount(address: string): number; - getTransactionCount(address: string, callback: (err: Error, count: number) => void): void; - } - - interface VersionApi { - api: string; - network: string; - node: string; - ethereum: string; - whisper: string; - getNetwork(cd: (err: Error, networkId: string) => void): void; - getNode(cd: (err: Error, nodeVersion: string) => void): void; - getEthereum(cd: (err: Error, ethereum: string) => void): void; - getWhisper(cd: (err: Error, whisper: string) => void): void; - } - - interface PersonalApi { - listAccounts: string[] | undefined; - newAccount(password?: string): string; - unlockAccount(address: string, password?: string, duration?: number): boolean; - lockAccount(address: string): boolean; - sign(message: string, account: string, password: string): string; - sign(hexMessage: string, account: string, callback: (error: Error, signature: string) => void): void; - } - - interface NetApi { - listening: boolean; - peerCount: boolean; - getListening(cd: (err: Error, listening: boolean) => void): void; - getPeerCount(cd: (err: Error, peerCount: number) => void): void; - } - - type BlockParam = number | 'earliest' | 'latest' | 'pending'; - - type Unit = - | 'kwei' - | 'ada' - | 'mwei' - | 'babbage' - | 'gwei' - | 'shannon' - | 'szabo' - | 'finney' - | 'ether' - | 'kether' - | 'grand' - | 'einstein' - | 'mether' - | 'gether' - | 'tether'; - - interface SyncingState { - startingBlock: number; - currentBlock: number; - highestBlock: number; - } - type SyncingResult = false | SyncingState; - - interface IsSyncing { - addCallback(cb: (err: Error, isSyncing: boolean, syncingState: SyncingState) => void): void; - stopWatching(): void; - } - - interface AbstractBlock { - number: number | null; - hash: string | null; - parentHash: string; - nonce: string | null; - sha3Uncles: string; - logsBloom: string | null; - transactionsRoot: string; - stateRoot: string; - miner: string; - difficulty: BigNumber.BigNumber; - totalDifficulty: BigNumber.BigNumber; - extraData: string; - size: number; - gasLimit: number; - gasUsed: number; - timestamp: number; - uncles: string[]; - } - interface BlockWithoutTransactionData extends AbstractBlock { - transactions: string[]; - } - interface BlockWithTransactionData extends AbstractBlock { - transactions: Transaction[]; - } - - interface Transaction { - hash: string; - nonce: number; - blockHash: string | null; - blockNumber: number | null; - transactionIndex: number | null; - from: string; - to: string | null; - value: BigNumber.BigNumber; - gasPrice: BigNumber.BigNumber; - gas: number; - input: string; - } - - interface CallTxDataBase { - to?: string; - value?: number | string | BigNumber.BigNumber; - gas?: number | string | BigNumber.BigNumber; - gasPrice?: number | string | BigNumber.BigNumber; - data?: string; - nonce?: number; - } - - interface TxData extends CallTxDataBase { - from: string; - } - - interface CallData extends CallTxDataBase { - from?: string; - } - - interface TransactionReceipt { - blockHash: string; - blockNumber: number; - transactionHash: string; - transactionIndex: number; - from: string; - to: string; - status: null | string | 0 | 1; - cumulativeGasUsed: number; - gasUsed: number; - contractAddress: string | null; - logs: LogEntry[]; - } - - interface LogEntry { - logIndex: number | null; - transactionIndex: number | null; - transactionHash: string; - blockHash: string | null; - blockNumber: number | null; - address: string; - data: string; - topics: string[]; - } - } - /* tslint:disable */ - export = Web3; - /* tslint:enable */ + import * as BigNumber from 'bignumber.js'; + + type MixedData = string | number | object | any[] | BigNumber.BigNumber; + + class Web3 { + public static providers: typeof providers; + public currentProvider: Web3.Provider; + + public eth: Web3.EthApi; + public personal: Web3.PersonalApi | undefined; + public version: Web3.VersionApi; + public net: Web3.NetApi; + + public constructor(provider?: Web3.Provider); + + public isConnected(): boolean; + public setProvider(provider: Web3.Provider): void; + public reset(keepIsSyncing: boolean): void; + public toHex(data: MixedData): string; + public toAscii(hex: string): string; + public fromAscii(ascii: string, padding?: number): string; + public toDecimal(hex: string): number; + public fromDecimal(value: number | string): string; + public fromWei(value: number | string, unit: Web3.Unit): string; + public fromWei(value: BigNumber.BigNumber, unit: Web3.Unit): BigNumber.BigNumber; + public toWei(amount: number | string, unit: Web3.Unit): string; + public toWei(amount: BigNumber.BigNumber, unit: Web3.Unit): BigNumber.BigNumber; + public toBigNumber(value: number | string): BigNumber.BigNumber; + public isAddress(address: string): boolean; + public isChecksumAddress(address: string): boolean; + public sha3(value: string, options?: Web3.Sha3Options): string; + } + + namespace providers { + class HttpProvider implements Web3.Provider { + constructor(url?: string, timeout?: number, username?: string, password?: string); + public sendAsync( + payload: Web3.JSONRPCRequestPayload, + callback: (err: Error, result: Web3.JSONRPCResponsePayload) => void, + ): void; + } + } + + namespace Web3 { + type ContractAbi = AbiDefinition[]; + + type AbiDefinition = FunctionAbi | EventAbi; + + type FunctionAbi = MethodAbi | ConstructorAbi | FallbackAbi; + + enum AbiType { + Function = 'function', + Constructor = 'constructor', + Event = 'event', + Fallback = 'fallback', + } + + type ConstructorStateMutability = 'nonpayable' | 'payable'; + type StateMutability = 'pure' | 'view' | ConstructorStateMutability; + + interface MethodAbi { + type: AbiType.Function; + name: string; + inputs: FunctionParameter[]; + outputs: FunctionParameter[]; + constant: boolean; + stateMutability: StateMutability; + payable: boolean; + } + + interface ConstructorAbi { + type: AbiType.Constructor; + inputs: FunctionParameter[]; + payable: boolean; + stateMutability: ConstructorStateMutability; + } + + interface FallbackAbi { + type: AbiType.Fallback; + payable: boolean; + } + + interface EventParameter { + name: string; + type: string; + indexed: boolean; + } + + interface EventAbi { + type: AbiType.Event; + name: string; + inputs: EventParameter[]; + anonymous: boolean; + } + + interface FunctionParameter { + name: string; + type: string; + } + + interface ContractInstance { + address: string; + abi: Web3.ContractAbi; + [name: string]: any; + } + + interface Contract<A extends ContractInstance> { + at(address: string): A; + getData(...args: any[]): string; + 'new'(...args: any[]): A; + } + + interface FilterObject { + fromBlock?: number | string; + toBlock?: number | string; + address?: string; + topics?: LogTopic[]; + } + + type LogTopic = null | string | string[]; + + interface DecodedLogEntry<A> extends LogEntry { + event: string; + args: A; + } + + interface DecodedLogEntryEvent<A> extends DecodedLogEntry<A> { + removed: boolean; + } + + interface LogEntryEvent extends LogEntry { + removed: boolean; + } + + interface FilterResult { + get(callback: () => void): void; + watch(callback: (err: Error, result: LogEntryEvent) => void): void; + stopWatching(callback?: () => void): void; + } + + export interface JSONRPCRequestPayload { + params: any[]; + method: string; + id: number; + jsonrpc: string; + } + + export interface JSONRPCResponsePayload { + result: any; + id: number; + jsonrpc: string; + } + + interface Provider { + sendAsync( + payload: JSONRPCRequestPayload, + callback: (err: Error, result: JSONRPCResponsePayload) => void, + ): void; + } + + interface Sha3Options { + encoding: 'hex'; + } + + interface EthApi { + coinbase: string; + mining: boolean; + hashrate: number; + gasPrice: BigNumber.BigNumber; + accounts: string[]; + blockNumber: number; + defaultAccount?: string; + defaultBlock: Web3.BlockParam; + syncing: Web3.SyncingResult; + compile: { + solidity(sourceString: string, cb?: (err: Error, result: any) => void): object; + }; + getMining(cd: (err: Error, mining: boolean) => void): void; + getHashrate(cd: (err: Error, hashrate: number) => void): void; + getGasPrice(cd: (err: Error, gasPrice: BigNumber.BigNumber) => void): void; + getAccounts(cd: (err: Error, accounts: string[]) => void): void; + getBlockNumber(callback: (err: Error, blockNumber: number) => void): void; + getSyncing(cd: (err: Error, syncing: Web3.SyncingResult) => void): void; + isSyncing(cb: (err: Error, isSyncing: boolean, syncingState: Web3.SyncingState) => void): Web3.IsSyncing; + + getBlock(hashStringOrBlockNumber: string | Web3.BlockParam): Web3.BlockWithoutTransactionData; + getBlock( + hashStringOrBlockNumber: string | Web3.BlockParam, + callback: (err: Error, blockObj: Web3.BlockWithoutTransactionData) => void, + ): void; + getBlock( + hashStringOrBlockNumber: string | Web3.BlockParam, + returnTransactionObjects: true, + ): Web3.BlockWithTransactionData; + getBlock( + hashStringOrBlockNumber: string | Web3.BlockParam, + returnTransactionObjects: true, + callback: (err: Error, blockObj: Web3.BlockWithTransactionData) => void, + ): void; + + getBlockTransactionCount(hashStringOrBlockNumber: string | Web3.BlockParam): number; + getBlockTransactionCount( + hashStringOrBlockNumber: string | Web3.BlockParam, + callback: (err: Error, blockTransactionCount: number) => void, + ): void; + + // TODO returnTransactionObjects + getUncle( + hashStringOrBlockNumber: string | Web3.BlockParam, + uncleNumber: number, + ): Web3.BlockWithoutTransactionData; + getUncle( + hashStringOrBlockNumber: string | Web3.BlockParam, + uncleNumber: number, + callback: (err: Error, uncle: Web3.BlockWithoutTransactionData) => void, + ): void; + + getTransaction(transactionHash: string): Web3.Transaction; + getTransaction( + transactionHash: string, + callback: (err: Error, transaction: Web3.Transaction) => void, + ): void; + + getTransactionFromBlock( + hashStringOrBlockNumber: string | Web3.BlockParam, + indexNumber: number, + ): Web3.Transaction; + getTransactionFromBlock( + hashStringOrBlockNumber: string | Web3.BlockParam, + indexNumber: number, + callback: (err: Error, transaction: Web3.Transaction) => void, + ): void; + + contract(abi: Web3.AbiDefinition[]): Web3.Contract<any>; + + // TODO block param + getBalance(addressHexString: string): BigNumber.BigNumber; + getBalance(addressHexString: string, callback: (err: Error, result: BigNumber.BigNumber) => void): void; + + // TODO block param + getStorageAt(address: string, position: number): string; + getStorageAt(address: string, position: number, callback: (err: Error, storage: string) => void): void; + + // TODO block param + getCode(addressHexString: string): string; + getCode(addressHexString: string, callback: (err: Error, code: string) => void): void; + + filter(value: string | Web3.FilterObject): Web3.FilterResult; + + sendTransaction(txData: Web3.TxData): string; + sendTransaction(txData: Web3.TxData, callback: (err: Error, value: string) => void): void; + + sendRawTransaction(rawTxData: string): string; + sendRawTransaction(rawTxData: string, callback: (err: Error, value: string) => void): void; + + sign(address: string, data: string): string; + sign(address: string, data: string, callback: (err: Error, signature: string) => void): void; + + getTransactionReceipt(txHash: string): Web3.TransactionReceipt | null; + getTransactionReceipt( + txHash: string, + callback: (err: Error, receipt: Web3.TransactionReceipt | null) => void, + ): void; + + // TODO block param + call(callData: Web3.CallData): string; + call(callData: Web3.CallData, callback: (err: Error, result: string) => void): void; + + estimateGas(callData: Web3.CallData): number; + estimateGas(callData: Web3.CallData, callback: (err: Error, gas: number) => void): void; + + // TODO defaultBlock + getTransactionCount(address: string): number; + getTransactionCount(address: string, callback: (err: Error, count: number) => void): void; + } + + interface VersionApi { + api: string; + network: string; + node: string; + ethereum: string; + whisper: string; + getNetwork(cd: (err: Error, networkId: string) => void): void; + getNode(cd: (err: Error, nodeVersion: string) => void): void; + getEthereum(cd: (err: Error, ethereum: string) => void): void; + getWhisper(cd: (err: Error, whisper: string) => void): void; + } + + interface PersonalApi { + listAccounts: string[] | undefined; + newAccount(password?: string): string; + unlockAccount(address: string, password?: string, duration?: number): boolean; + lockAccount(address: string): boolean; + sign(message: string, account: string, password: string): string; + sign(hexMessage: string, account: string, callback: (error: Error, signature: string) => void): void; + } + + interface NetApi { + listening: boolean; + peerCount: boolean; + getListening(cd: (err: Error, listening: boolean) => void): void; + getPeerCount(cd: (err: Error, peerCount: number) => void): void; + } + + type BlockParam = number | 'earliest' | 'latest' | 'pending'; + + type Unit = + | 'kwei' + | 'ada' + | 'mwei' + | 'babbage' + | 'gwei' + | 'shannon' + | 'szabo' + | 'finney' + | 'ether' + | 'kether' + | 'grand' + | 'einstein' + | 'mether' + | 'gether' + | 'tether'; + + interface SyncingState { + startingBlock: number; + currentBlock: number; + highestBlock: number; + } + type SyncingResult = false | SyncingState; + + interface IsSyncing { + addCallback(cb: (err: Error, isSyncing: boolean, syncingState: SyncingState) => void): void; + stopWatching(): void; + } + + interface AbstractBlock { + number: number | null; + hash: string | null; + parentHash: string; + nonce: string | null; + sha3Uncles: string; + logsBloom: string | null; + transactionsRoot: string; + stateRoot: string; + miner: string; + difficulty: BigNumber.BigNumber; + totalDifficulty: BigNumber.BigNumber; + extraData: string; + size: number; + gasLimit: number; + gasUsed: number; + timestamp: number; + uncles: string[]; + } + interface BlockWithoutTransactionData extends AbstractBlock { + transactions: string[]; + } + interface BlockWithTransactionData extends AbstractBlock { + transactions: Transaction[]; + } + + interface Transaction { + hash: string; + nonce: number; + blockHash: string | null; + blockNumber: number | null; + transactionIndex: number | null; + from: string; + to: string | null; + value: BigNumber.BigNumber; + gasPrice: BigNumber.BigNumber; + gas: number; + input: string; + } + + interface CallTxDataBase { + to?: string; + value?: number | string | BigNumber.BigNumber; + gas?: number | string | BigNumber.BigNumber; + gasPrice?: number | string | BigNumber.BigNumber; + data?: string; + nonce?: number; + } + + interface TxData extends CallTxDataBase { + from: string; + } + + interface CallData extends CallTxDataBase { + from?: string; + } + + interface TransactionReceipt { + blockHash: string; + blockNumber: number; + transactionHash: string; + transactionIndex: number; + from: string; + to: string; + status: null | string | 0 | 1; + cumulativeGasUsed: number; + gasUsed: number; + contractAddress: string | null; + logs: LogEntry[]; + } + + interface LogEntry { + logIndex: number | null; + transactionIndex: number | null; + transactionHash: string; + blockHash: string | null; + blockNumber: number | null; + address: string; + data: string; + topics: string[]; + } + } + /* tslint:disable */ + export = Web3; + /* tslint:enable */ } diff --git a/packages/web3-typescript-typings/package.json b/packages/web3-typescript-typings/package.json index edfdd9701..e0e433c93 100644 --- a/packages/web3-typescript-typings/package.json +++ b/packages/web3-typescript-typings/package.json @@ -1,30 +1,30 @@ { - "name": "web3-typescript-typings", - "version": "0.9.6", - "description": "Typescript type definitions for web3", - "main": "index.d.ts", - "types": "index.d.ts", - "scripts": { - "lint": "tslint index.d.ts" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/0xProject/0x.js.git" - }, - "author": "Fabio Berger", - "contributors": ["Leonid Logvinov <logvinov.leon@gmail.com>"], - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/web3-typescript-typings#readme", - "devDependencies": { - "@types/bignumber.js": "^4.0.2", - "tslint": "^5.5.0", - "tslint-config-0xproject": "^0.0.2", - "typescript": "~2.6.1" - }, - "dependencies": { - "bignumber.js": "~4.1.0" - } + "name": "web3-typescript-typings", + "version": "0.9.6", + "description": "Typescript type definitions for web3", + "main": "index.d.ts", + "types": "index.d.ts", + "scripts": { + "lint": "tslint index.d.ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/0xProject/0x.js.git" + }, + "author": "Fabio Berger", + "contributors": ["Leonid Logvinov <logvinov.leon@gmail.com>"], + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/web3-typescript-typings#readme", + "devDependencies": { + "@types/bignumber.js": "^4.0.2", + "tslint": "^5.5.0", + "tslint-config-0xproject": "^0.0.2", + "typescript": "~2.6.1" + }, + "dependencies": { + "bignumber.js": "~4.1.0" + } } diff --git a/packages/web3-typescript-typings/tslint.json b/packages/web3-typescript-typings/tslint.json index ef528b22e..9a93a1f74 100644 --- a/packages/web3-typescript-typings/tslint.json +++ b/packages/web3-typescript-typings/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["tslint-config-0xproject"] + "extends": ["tslint-config-0xproject"] } diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index d45480b35..6a65582f9 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -1,36 +1,36 @@ { - "name": "@0xproject/web3-wrapper", - "version": "0.1.7", - "description": "Wraps around web3 and gives a nicer interface", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "scripts": { - "build": "tsc", - "clean": "shx rm -rf lib", - "lint": "tslint --project . 'src/**/*.ts'" - }, - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/web3-wrapper/README.md", - "devDependencies": { - "@0xproject/tslint-config": "^0.4.4", - "@0xproject/types": "^0.1.6", - "@types/lodash": "^4.14.86", - "npm-run-all": "^4.1.2", - "shx": "^0.2.2", - "tslint": "5.8.0", - "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.6" - }, - "dependencies": { - "@0xproject/utils": "^0.2.2", - "lodash": "^4.17.4", - "web3": "^0.20.0" - } + "name": "@0xproject/web3-wrapper", + "version": "0.1.7", + "description": "Wraps around web3 and gives a nicer interface", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "tsc", + "clean": "shx rm -rf lib", + "lint": "tslint --project . 'src/**/*.ts'" + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js.git" + }, + "bugs": { + "url": "https://github.com/0xProject/0x.js/issues" + }, + "homepage": "https://github.com/0xProject/0x.js/packages/web3-wrapper/README.md", + "devDependencies": { + "@0xproject/tslint-config": "^0.4.4", + "@0xproject/types": "^0.1.6", + "@types/lodash": "^4.14.86", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.8.0", + "typescript": "~2.6.1", + "web3-typescript-typings": "^0.9.6" + }, + "dependencies": { + "@0xproject/utils": "^0.2.2", + "lodash": "^4.17.4", + "web3": "^0.20.0" + } } diff --git a/packages/web3-wrapper/src/index.ts b/packages/web3-wrapper/src/index.ts index 859333448..c4826f2be 100644 --- a/packages/web3-wrapper/src/index.ts +++ b/packages/web3-wrapper/src/index.ts @@ -4,170 +4,170 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; interface RawLogEntry { - logIndex: string | null; - transactionIndex: string | null; - transactionHash: string; - blockHash: string | null; - blockNumber: string | null; - address: string; - data: string; - topics: string[]; + logIndex: string | null; + transactionIndex: string | null; + transactionHash: string; + blockHash: string | null; + blockNumber: string | null; + address: string; + data: string; + topics: string[]; } export class Web3Wrapper { - private _web3: Web3; - private _defaults: Partial<TxData>; - private _jsonRpcRequestId: number; - constructor(provider: Web3.Provider, defaults?: Partial<TxData>) { - if (_.isUndefined((provider as any).sendAsync)) { - // Web3@1.0 provider doesn't support synchronous http requests, - // so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x` - // We re-assign the send method so that Web3@1.0 providers work with 0x.js - (provider as any).sendAsync = (provider as any).send; - } - this._web3 = new Web3(); - this._web3.setProvider(provider); - this._defaults = defaults || {}; - this._jsonRpcRequestId = 0; - } - public getContractDefaults(): Partial<TxData> { - return this._defaults; - } - public setProvider(provider: Web3.Provider) { - this._web3.setProvider(provider); - } - public isAddress(address: string): boolean { - return this._web3.isAddress(address); - } - public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> { - const addresses = await this.getAvailableAddressesAsync(); - return _.includes(addresses, senderAddress); - } - public async getNodeVersionAsync(): Promise<string> { - const nodeVersion = await promisify<string>(this._web3.version.getNode)(); - return nodeVersion; - } - public async getNetworkIdAsync(): Promise<number> { - const networkIdStr = await promisify<string>(this._web3.version.getNetwork)(); - const networkId = _.parseInt(networkIdStr); - return networkId; - } - public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> { - const transactionReceipt = await promisify<TransactionReceipt>(this._web3.eth.getTransactionReceipt)(txHash); - if (!_.isNull(transactionReceipt)) { - transactionReceipt.status = this._normalizeTxReceiptStatus(transactionReceipt.status); - } - return transactionReceipt; - } - public getCurrentProvider(): Web3.Provider { - return this._web3.currentProvider; - } - public toWei(ethAmount: BigNumber): BigNumber { - const balanceWei = this._web3.toWei(ethAmount, 'ether'); - return balanceWei; - } - public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> { - let balanceInWei = await promisify<BigNumber>(this._web3.eth.getBalance)(owner); - // Rewrap in a new BigNumber - balanceInWei = new BigNumber(balanceInWei); - return balanceInWei; - } - public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { - const code = await promisify<string>(this._web3.eth.getCode)(address); - // Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients - const codeIsEmpty = /^0x0{0,40}$/i.test(code); - return !codeIsEmpty; - } - public async signTransactionAsync(address: string, message: string): Promise<string> { - const signData = await promisify<string>(this._web3.eth.sign)(address, message); - return signData; - } - public async getBlockNumberAsync(): Promise<number> { - const blockNumber = await promisify<number>(this._web3.eth.getBlockNumber)(); - return blockNumber; - } - public async getBlockAsync(blockParam: string | Web3.BlockParam): Promise<Web3.BlockWithoutTransactionData> { - const block = await promisify<Web3.BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockParam); - return block; - } - public async getBlockTimestampAsync(blockParam: string | Web3.BlockParam): Promise<number> { - const { timestamp } = await this.getBlockAsync(blockParam); - return timestamp; - } - public async getAvailableAddressesAsync(): Promise<string[]> { - const addresses = await promisify<string[]>(this._web3.eth.getAccounts)(); - return addresses; - } - public async getLogsAsync(filter: Web3.FilterObject): Promise<Web3.LogEntry[]> { - let fromBlock = filter.fromBlock; - if (_.isNumber(fromBlock)) { - fromBlock = this._web3.toHex(fromBlock); - } - let toBlock = filter.toBlock; - if (_.isNumber(toBlock)) { - toBlock = this._web3.toHex(toBlock); - } - const serializedFilter = { - ...filter, - fromBlock, - toBlock, - }; - const payload = { - jsonrpc: '2.0', - id: this._jsonRpcRequestId++, - method: 'eth_getLogs', - params: [serializedFilter], - }; - const rawLogs = await this._sendRawPayloadAsync<RawLogEntry[]>(payload); - const formattedLogs = _.map(rawLogs, this._formatLog.bind(this)); - return formattedLogs; - } - public getContractFromAbi(abi: Web3.ContractAbi): Web3.Contract<any> { - const web3Contract = this._web3.eth.contract(abi); - return web3Contract; - } - public getContractInstance(abi: Web3.ContractAbi, address: string): Web3.ContractInstance { - const web3ContractInstance = this.getContractFromAbi(abi).at(address); - return web3ContractInstance; - } - public async estimateGasAsync(data: string): Promise<number> { - const gas = await promisify<number>(this._web3.eth.estimateGas)({ data }); - return gas; - } - private async _sendRawPayloadAsync<A>(payload: Web3.JSONRPCRequestPayload): Promise<A> { - const sendAsync = this._web3.currentProvider.sendAsync.bind(this._web3.currentProvider); - const response = await promisify<Web3.JSONRPCResponsePayload>(sendAsync)(payload); - const result = response.result; - return result; - } - private _normalizeTxReceiptStatus(status: undefined | null | string | 0 | 1): null | 0 | 1 { - // Transaction status might have four values - // undefined - Testrpc and other old clients - // null - New clients on old transactions - // number - Parity - // hex - Geth - if (_.isString(status)) { - return this._web3.toDecimal(status) as 0 | 1; - } else if (_.isUndefined(status)) { - return null; - } else { - return status; - } - } - private _formatLog(rawLog: RawLogEntry): Web3.LogEntry { - const formattedLog = { - ...rawLog, - logIndex: this._hexToDecimal(rawLog.logIndex), - blockNumber: this._hexToDecimal(rawLog.blockNumber), - transactionIndex: this._hexToDecimal(rawLog.transactionIndex), - }; - return formattedLog; - } - private _hexToDecimal(hex: string | null): number | null { - if (_.isNull(hex)) { - return null; - } - const decimal = this._web3.toDecimal(hex); - return decimal; - } + private _web3: Web3; + private _defaults: Partial<TxData>; + private _jsonRpcRequestId: number; + constructor(provider: Web3.Provider, defaults?: Partial<TxData>) { + if (_.isUndefined((provider as any).sendAsync)) { + // Web3@1.0 provider doesn't support synchronous http requests, + // so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x` + // We re-assign the send method so that Web3@1.0 providers work with 0x.js + (provider as any).sendAsync = (provider as any).send; + } + this._web3 = new Web3(); + this._web3.setProvider(provider); + this._defaults = defaults || {}; + this._jsonRpcRequestId = 0; + } + public getContractDefaults(): Partial<TxData> { + return this._defaults; + } + public setProvider(provider: Web3.Provider) { + this._web3.setProvider(provider); + } + public isAddress(address: string): boolean { + return this._web3.isAddress(address); + } + public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> { + const addresses = await this.getAvailableAddressesAsync(); + return _.includes(addresses, senderAddress); + } + public async getNodeVersionAsync(): Promise<string> { + const nodeVersion = await promisify<string>(this._web3.version.getNode)(); + return nodeVersion; + } + public async getNetworkIdAsync(): Promise<number> { + const networkIdStr = await promisify<string>(this._web3.version.getNetwork)(); + const networkId = _.parseInt(networkIdStr); + return networkId; + } + public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> { + const transactionReceipt = await promisify<TransactionReceipt>(this._web3.eth.getTransactionReceipt)(txHash); + if (!_.isNull(transactionReceipt)) { + transactionReceipt.status = this._normalizeTxReceiptStatus(transactionReceipt.status); + } + return transactionReceipt; + } + public getCurrentProvider(): Web3.Provider { + return this._web3.currentProvider; + } + public toWei(ethAmount: BigNumber): BigNumber { + const balanceWei = this._web3.toWei(ethAmount, 'ether'); + return balanceWei; + } + public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> { + let balanceInWei = await promisify<BigNumber>(this._web3.eth.getBalance)(owner); + // Rewrap in a new BigNumber + balanceInWei = new BigNumber(balanceInWei); + return balanceInWei; + } + public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { + const code = await promisify<string>(this._web3.eth.getCode)(address); + // Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients + const codeIsEmpty = /^0x0{0,40}$/i.test(code); + return !codeIsEmpty; + } + public async signTransactionAsync(address: string, message: string): Promise<string> { + const signData = await promisify<string>(this._web3.eth.sign)(address, message); + return signData; + } + public async getBlockNumberAsync(): Promise<number> { + const blockNumber = await promisify<number>(this._web3.eth.getBlockNumber)(); + return blockNumber; + } + public async getBlockAsync(blockParam: string | Web3.BlockParam): Promise<Web3.BlockWithoutTransactionData> { + const block = await promisify<Web3.BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockParam); + return block; + } + public async getBlockTimestampAsync(blockParam: string | Web3.BlockParam): Promise<number> { + const { timestamp } = await this.getBlockAsync(blockParam); + return timestamp; + } + public async getAvailableAddressesAsync(): Promise<string[]> { + const addresses = await promisify<string[]>(this._web3.eth.getAccounts)(); + return addresses; + } + public async getLogsAsync(filter: Web3.FilterObject): Promise<Web3.LogEntry[]> { + let fromBlock = filter.fromBlock; + if (_.isNumber(fromBlock)) { + fromBlock = this._web3.toHex(fromBlock); + } + let toBlock = filter.toBlock; + if (_.isNumber(toBlock)) { + toBlock = this._web3.toHex(toBlock); + } + const serializedFilter = { + ...filter, + fromBlock, + toBlock, + }; + const payload = { + jsonrpc: '2.0', + id: this._jsonRpcRequestId++, + method: 'eth_getLogs', + params: [serializedFilter], + }; + const rawLogs = await this._sendRawPayloadAsync<RawLogEntry[]>(payload); + const formattedLogs = _.map(rawLogs, this._formatLog.bind(this)); + return formattedLogs; + } + public getContractFromAbi(abi: Web3.ContractAbi): Web3.Contract<any> { + const web3Contract = this._web3.eth.contract(abi); + return web3Contract; + } + public getContractInstance(abi: Web3.ContractAbi, address: string): Web3.ContractInstance { + const web3ContractInstance = this.getContractFromAbi(abi).at(address); + return web3ContractInstance; + } + public async estimateGasAsync(data: string): Promise<number> { + const gas = await promisify<number>(this._web3.eth.estimateGas)({ data }); + return gas; + } + private async _sendRawPayloadAsync<A>(payload: Web3.JSONRPCRequestPayload): Promise<A> { + const sendAsync = this._web3.currentProvider.sendAsync.bind(this._web3.currentProvider); + const response = await promisify<Web3.JSONRPCResponsePayload>(sendAsync)(payload); + const result = response.result; + return result; + } + private _normalizeTxReceiptStatus(status: undefined | null | string | 0 | 1): null | 0 | 1 { + // Transaction status might have four values + // undefined - Testrpc and other old clients + // null - New clients on old transactions + // number - Parity + // hex - Geth + if (_.isString(status)) { + return this._web3.toDecimal(status) as 0 | 1; + } else if (_.isUndefined(status)) { + return null; + } else { + return status; + } + } + private _formatLog(rawLog: RawLogEntry): Web3.LogEntry { + const formattedLog = { + ...rawLog, + logIndex: this._hexToDecimal(rawLog.logIndex), + blockNumber: this._hexToDecimal(rawLog.blockNumber), + transactionIndex: this._hexToDecimal(rawLog.transactionIndex), + }; + return formattedLog; + } + private _hexToDecimal(hex: string | null): number | null { + if (_.isNull(hex)) { + return null; + } + const decimal = this._web3.toDecimal(hex); + return decimal; + } } diff --git a/packages/web3-wrapper/tsconfig.json b/packages/web3-wrapper/tsconfig.json index fc88c962d..3d967d05f 100644 --- a/packages/web3-wrapper/tsconfig.json +++ b/packages/web3-wrapper/tsconfig.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./src/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] } diff --git a/packages/web3-wrapper/tslint.json b/packages/web3-wrapper/tslint.json index e63054bfc..ffaefe83a 100644 --- a/packages/web3-wrapper/tslint.json +++ b/packages/web3-wrapper/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } diff --git a/packages/website/contracts/Mintable.json b/packages/website/contracts/Mintable.json index eaadcf68f..dc46dc829 100644 --- a/packages/website/contracts/Mintable.json +++ b/packages/website/contracts/Mintable.json @@ -1,190 +1,190 @@ { - "contract_name": "Mintable", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_value", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": true, - "name": "_spender", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - } - ], - "unlinked_binary": - "0x6060604052341561000c57fe5b5b6105018061001c6000396000f300606060405236156100675763ffffffff60e060020a600035041663095ea7b3811461006957806318160ddd1461009c57806323b872dd146100be57806370a08231146100f7578063a0712d6814610125578063a9059cbb1461013a578063dd62ed3e1461016d575bfe5b341561007157fe5b610088600160a060020a03600435166024356101a1565b604080519115158252519081900360200190f35b34156100a457fe5b6100ac61020c565b60408051918252519081900360200190f35b34156100c657fe5b610088600160a060020a0360043581169060243516604435610212565b604080519115158252519081900360200190f35b34156100ff57fe5b6100ac600160a060020a0360043516610335565b60408051918252519081900360200190f35b341561012d57fe5b610138600435610354565b005b341561014257fe5b610088600160a060020a03600435166024356103bc565b604080519115158252519081900360200190f35b341561017557fe5b6100ac600160a060020a036004358116906024351661046e565b60408051918252519081900360200190f35b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a03808416600081815260016020908152604080832033909516835293815283822054928252819052918220548390108015906102555750828110155b801561027b5750600160a060020a03841660009081526020819052604090205483810110155b1561032757600160a060020a03808516600090815260208190526040808220805487019055918716815220805484900390556000198110156102e557600160a060020a03808616600090815260016020908152604080832033909416835292905220805484900390555b83600160a060020a031685600160a060020a03166000805160206104b6833981519152856040518082815260200191505060405180910390a36001915061032c565b600091505b5b509392505050565b600160a060020a0381166000908152602081905260409020545b919050565b68056bc75e2d6310000081111561036b5760006000fd5b600160a060020a03331660009081526020819052604090205461038f90829061049b565b600160a060020a0333166000908152602081905260409020556002546103b5908261049b565b6002555b50565b600160a060020a0333166000908152602081905260408120548290108015906103ff5750600160a060020a03831660009081526020819052604090205482810110155b1561045f57600160a060020a0333811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191936000805160206104b6833981519152929081900390910190a3506001610206565b506000610206565b5b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b6000828201838110156104aa57fe5b8091505b50929150505600ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa165627a7a72305820998c8326b9629e063eb4867166e72c68a8c2e3ebca6a9d35ebc78c041c7aa47b0029", - "networks": {}, - "schema_version": "0.0.5", - "updated_at": 1503413048892 + "contract_name": "Mintable", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_value", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_spender", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + } + ], + "unlinked_binary": + "0x6060604052341561000c57fe5b5b6105018061001c6000396000f300606060405236156100675763ffffffff60e060020a600035041663095ea7b3811461006957806318160ddd1461009c57806323b872dd146100be57806370a08231146100f7578063a0712d6814610125578063a9059cbb1461013a578063dd62ed3e1461016d575bfe5b341561007157fe5b610088600160a060020a03600435166024356101a1565b604080519115158252519081900360200190f35b34156100a457fe5b6100ac61020c565b60408051918252519081900360200190f35b34156100c657fe5b610088600160a060020a0360043581169060243516604435610212565b604080519115158252519081900360200190f35b34156100ff57fe5b6100ac600160a060020a0360043516610335565b60408051918252519081900360200190f35b341561012d57fe5b610138600435610354565b005b341561014257fe5b610088600160a060020a03600435166024356103bc565b604080519115158252519081900360200190f35b341561017557fe5b6100ac600160a060020a036004358116906024351661046e565b60408051918252519081900360200190f35b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a03808416600081815260016020908152604080832033909516835293815283822054928252819052918220548390108015906102555750828110155b801561027b5750600160a060020a03841660009081526020819052604090205483810110155b1561032757600160a060020a03808516600090815260208190526040808220805487019055918716815220805484900390556000198110156102e557600160a060020a03808616600090815260016020908152604080832033909416835292905220805484900390555b83600160a060020a031685600160a060020a03166000805160206104b6833981519152856040518082815260200191505060405180910390a36001915061032c565b600091505b5b509392505050565b600160a060020a0381166000908152602081905260409020545b919050565b68056bc75e2d6310000081111561036b5760006000fd5b600160a060020a03331660009081526020819052604090205461038f90829061049b565b600160a060020a0333166000908152602081905260409020556002546103b5908261049b565b6002555b50565b600160a060020a0333166000908152602081905260408120548290108015906103ff5750600160a060020a03831660009081526020819052604090205482810110155b1561045f57600160a060020a0333811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191936000805160206104b6833981519152929081900390910190a3506001610206565b506000610206565b5b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b6000828201838110156104aa57fe5b8091505b50929150505600ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa165627a7a72305820998c8326b9629e063eb4867166e72c68a8c2e3ebca6a9d35ebc78c041c7aa47b0029", + "networks": {}, + "schema_version": "0.0.5", + "updated_at": 1503413048892 } diff --git a/packages/website/md/docs/0xjs/async.md b/packages/website/md/docs/0xjs/async.md index fa92e5bab..8abaef331 100644 --- a/packages/website/md/docs/0xjs/async.md +++ b/packages/website/md/docs/0xjs/async.md @@ -14,13 +14,13 @@ _Promise syntax:_ ```javascript zeroEx - .getAvailableAddressesAsync() - .then(function(availableAddresses) { - console.log(availableAddresses); - }) - .catch(function(error) { - console.log('Caught error: ', error); - }); + .getAvailableAddressesAsync() + .then(function(availableAddresses) { + console.log(availableAddresses); + }) + .catch(function(error) { + console.log('Caught error: ', error); + }); ``` As is the convention with promise-based libraries, if an error occurs, it is thrown. It is the callers responsibility to catch thrown errors and to handle them appropriately. diff --git a/packages/website/package.json b/packages/website/package.json index ac1bd2c13..6db74a20e 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,109 +1,109 @@ { - "name": "@0xproject/website", - "version": "0.0.9", - "private": true, - "description": "Website and 0x portal dapp", - "scripts": { - "build": "NODE_ENV=production webpack; exit 0;", - "clean": "shx rm -f public/bundle*", - "lint": "tslint --project . 'ts/**/*.ts' 'ts/**/*.tsx'", - "dev": "webpack-dev-server --content-base public --https", - "update_contracts": - "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../website/contracts; done;", - "deploy_staging": - "npm run build; aws s3 sync ./public/. s3://staging-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers", - "deploy_live": - "npm run build; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers" - }, - "config": { - "artifacts": "Mintable" - }, - "author": "Fabio Berger", - "license": "Apache-2.0", - "dependencies": { - "0x.js": "^0.30.2", - "@0xproject/subproviders": "^0.3.3", - "@0xproject/utils": "^0.2.2", - "accounting": "^0.4.1", - "basscss": "^8.0.3", - "blockies": "^0.0.2", - "compare-versions": "^3.0.1", - "dateformat": "^2.0.0", - "deep-equal": "^1.0.1", - "dharma-loan-frame": "^0.0.12", - "ethereumjs-tx": "^1.3.3", - "ethereumjs-util": "^5.1.1", - "find-versions": "^2.0.0", - "is-mobile": "^0.2.2", - "jsonschema": "^1.2.0", - "ledgerco": "0xProject/ledger-node-js-api", - "less": "^2.7.2", - "lodash": "^4.17.4", - "material-ui": "^0.17.1", - "moment": "^2.18.1", - "query-string": "^5.0.1", - "react": "15.6.1", - "react-copy-to-clipboard": "^4.2.3", - "react-document-title": "^2.0.3", - "react-dom": "15.6.1", - "react-highlight": "^0.10.0", - "react-html5video": "^2.1.0", - "react-inlinesvg": "^0.5.5", - "react-markdown": "^2.5.0", - "react-recaptcha": "^2.3.2", - "react-redux": "^5.0.3", - "react-router-dom": "^4.1.1", - "react-router-hash-link": "^1.1.0", - "react-scroll": "^1.5.2", - "react-tap-event-plugin": "^2.0.1", - "react-tooltip": "^3.2.7", - "react-waypoint": "^7.0.4", - "redux": "^3.6.0", - "scroll-to-element": "^2.0.0", - "semver-sort": "0.0.4", - "thenby": "^1.2.3", - "truffle-contract": "2.0.1", - "tslint-config-0xproject": "^0.0.2", - "web3": "^0.20.0", - "web3-provider-engine": "^13.0.1", - "whatwg-fetch": "^2.0.3", - "xml-js": "^1.3.2" - }, - "devDependencies": { - "@types/accounting": "^0.4.1", - "@types/dateformat": "^1.0.1", - "@types/deep-equal": "^1.0.0", - "@types/jsonschema": "^1.1.1", - "@types/lodash": "^4.14.86", - "@types/material-ui": "0.18.0", - "@types/moment": "^2.13.0", - "@types/node": "^8.0.53", - "@types/query-string": "^5.0.1", - "@types/react": "^15.0.15", - "@types/react-copy-to-clipboard": "^4.2.0", - "@types/react-dom": "^0.14.23", - "@types/react-redux": "^4.4.37", - "@types/react-router-dom": "^4.0.4", - "@types/react-scroll": "0.0.31", - "@types/react-tap-event-plugin": "0.0.30", - "@types/redux": "^3.6.0", - "awesome-typescript-loader": "^3.1.3", - "copy-webpack-plugin": "^4.0.1", - "copyfiles": "^1.2.0", - "css-loader": "0.23.x", - "exports-loader": "0.6.x", - "imports-loader": "0.6.x", - "json-loader": "^0.5.4", - "less-loader": "^2.2.3", - "raw-loader": "^0.5.1", - "shx": "^0.2.2", - "source-map-loader": "^0.1.6", - "style-loader": "0.13.x", - "tslint": "5.8.0", - "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.6", - "webpack": "^3.1.0", - "webpack-dev-middleware": "^1.10.0", - "webpack-dev-server": "^2.5.0" - } + "name": "@0xproject/website", + "version": "0.0.9", + "private": true, + "description": "Website and 0x portal dapp", + "scripts": { + "build": "NODE_ENV=production webpack; exit 0;", + "clean": "shx rm -f public/bundle*", + "lint": "tslint --project . 'ts/**/*.ts' 'ts/**/*.tsx'", + "dev": "webpack-dev-server --content-base public --https", + "update_contracts": + "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../website/contracts; done;", + "deploy_staging": + "npm run build; aws s3 sync ./public/. s3://staging-0xproject --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers", + "deploy_live": + "npm run build; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers" + }, + "config": { + "artifacts": "Mintable" + }, + "author": "Fabio Berger", + "license": "Apache-2.0", + "dependencies": { + "0x.js": "^0.30.2", + "@0xproject/subproviders": "^0.3.3", + "@0xproject/utils": "^0.2.2", + "accounting": "^0.4.1", + "basscss": "^8.0.3", + "blockies": "^0.0.2", + "compare-versions": "^3.0.1", + "dateformat": "^2.0.0", + "deep-equal": "^1.0.1", + "dharma-loan-frame": "^0.0.12", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.1", + "find-versions": "^2.0.0", + "is-mobile": "^0.2.2", + "jsonschema": "^1.2.0", + "ledgerco": "0xProject/ledger-node-js-api", + "less": "^2.7.2", + "lodash": "^4.17.4", + "material-ui": "^0.17.1", + "moment": "^2.18.1", + "query-string": "^5.0.1", + "react": "15.6.1", + "react-copy-to-clipboard": "^4.2.3", + "react-document-title": "^2.0.3", + "react-dom": "15.6.1", + "react-highlight": "^0.10.0", + "react-html5video": "^2.1.0", + "react-inlinesvg": "^0.5.5", + "react-markdown": "^2.5.0", + "react-recaptcha": "^2.3.2", + "react-redux": "^5.0.3", + "react-router-dom": "^4.1.1", + "react-router-hash-link": "^1.1.0", + "react-scroll": "^1.5.2", + "react-tap-event-plugin": "^2.0.1", + "react-tooltip": "^3.2.7", + "react-waypoint": "^7.0.4", + "redux": "^3.6.0", + "scroll-to-element": "^2.0.0", + "semver-sort": "0.0.4", + "thenby": "^1.2.3", + "truffle-contract": "2.0.1", + "tslint-config-0xproject": "^0.0.2", + "web3": "^0.20.0", + "web3-provider-engine": "^13.0.1", + "whatwg-fetch": "^2.0.3", + "xml-js": "^1.3.2" + }, + "devDependencies": { + "@types/accounting": "^0.4.1", + "@types/dateformat": "^1.0.1", + "@types/deep-equal": "^1.0.0", + "@types/jsonschema": "^1.1.1", + "@types/lodash": "^4.14.86", + "@types/material-ui": "0.18.0", + "@types/moment": "^2.13.0", + "@types/node": "^8.0.53", + "@types/query-string": "^5.0.1", + "@types/react": "^15.0.15", + "@types/react-copy-to-clipboard": "^4.2.0", + "@types/react-dom": "^0.14.23", + "@types/react-redux": "^4.4.37", + "@types/react-router-dom": "^4.0.4", + "@types/react-scroll": "0.0.31", + "@types/react-tap-event-plugin": "0.0.30", + "@types/redux": "^3.6.0", + "awesome-typescript-loader": "^3.1.3", + "copy-webpack-plugin": "^4.0.1", + "copyfiles": "^1.2.0", + "css-loader": "0.23.x", + "exports-loader": "0.6.x", + "imports-loader": "0.6.x", + "json-loader": "^0.5.4", + "less-loader": "^2.2.3", + "raw-loader": "^0.5.1", + "shx": "^0.2.2", + "source-map-loader": "^0.1.6", + "style-loader": "0.13.x", + "tslint": "5.8.0", + "typescript": "~2.6.1", + "web3-typescript-typings": "^0.9.6", + "webpack": "^3.1.0", + "webpack-dev-middleware": "^1.10.0", + "webpack-dev-server": "^2.5.0" + } } diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 711c3329d..5530701c0 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -1,25 +1,25 @@ import { - BlockParam, - BlockRange, - DecodedLogEvent, - ExchangeContractEventArgs, - ExchangeEvents, - IndexedFilterValues, - LogCancelContractEventArgs, - LogFillContractEventArgs, - LogWithDecodedArgs, - Order, - SignedOrder, - Token as ZeroExToken, - TransactionReceiptWithDecodedLogs, - ZeroEx, + BlockParam, + BlockRange, + DecodedLogEvent, + ExchangeContractEventArgs, + ExchangeEvents, + IndexedFilterValues, + LogCancelContractEventArgs, + LogFillContractEventArgs, + LogWithDecodedArgs, + Order, + SignedOrder, + Token as ZeroExToken, + TransactionReceiptWithDecodedLogs, + ZeroEx, } from '0x.js'; import { - InjectedWeb3Subprovider, - ledgerEthereumBrowserClientFactoryAsync, - LedgerSubprovider, - LedgerWalletSubprovider, - RedundantRPCSubprovider, + InjectedWeb3Subprovider, + ledgerEthereumBrowserClientFactoryAsync, + LedgerSubprovider, + LedgerWalletSubprovider, + RedundantRPCSubprovider, } from '@0xproject/subproviders'; import { BigNumber, intervalUtils, promisify } from '@0xproject/utils'; import * as _ from 'lodash'; @@ -31,16 +31,16 @@ import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; import { tradeHistoryStorage } from 'ts/local_storage/trade_history_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { - BlockchainCallErrs, - BlockchainErrs, - ContractInstance, - EtherscanLinkSuffixes, - ProviderType, - Side, - SignatureData, - Token, - TokenByAddress, - TokenStateByAddress, + BlockchainCallErrs, + BlockchainErrs, + ContractInstance, + EtherscanLinkSuffixes, + ProviderType, + Side, + SignatureData, + Token, + TokenByAddress, + TokenStateByAddress, } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -56,727 +56,727 @@ import * as MintableArtifacts from '../contracts/Mintable.json'; const BLOCK_NUMBER_BACK_TRACK = 50; export class Blockchain { - public networkId: number; - public nodeVersion: string; - private _zeroEx: ZeroEx; - private _dispatcher: Dispatcher; - private _web3Wrapper?: Web3Wrapper; - private _exchangeAddress: string; - private _userAddress: string; - private _cachedProvider: Web3.Provider; - private _ledgerSubprovider: LedgerWalletSubprovider; - private _zrxPollIntervalId: NodeJS.Timer; - private static async _onPageLoadAsync(): Promise<void> { - if (document.readyState === 'complete') { - return; // Already loaded - } - return new Promise<void>((resolve, reject) => { - window.onload = () => resolve(); - }); - } - private static _getNameGivenProvider(provider: Web3.Provider): string { - if (!_.isUndefined((provider as any).isMetaMask)) { - return constants.PROVIDER_NAME_METAMASK; - } - - // HACK: We use the fact that Parity Signer's provider is an instance of their - // internal `Web3FrameProvider` class. - const isParitySigner = _.startsWith(provider.constructor.toString(), 'function Web3FrameProvider'); - if (isParitySigner) { - return constants.PROVIDER_NAME_PARITY_SIGNER; - } - - return constants.PROVIDER_NAME_GENERIC; - } - private static async _getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number) { - const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); - const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; - const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); - - let provider; - if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) { - // We catch all requests involving a users account and send it to the injectedWeb3 - // instance. All other requests go to the public hosted node. - provider = new ProviderEngine(); - provider.addProvider(new InjectedWeb3Subprovider(injectedWeb3)); - provider.addProvider(new FilterSubprovider()); - provider.addProvider(new RedundantRPCSubprovider(publicNodeUrlsIfExistsForNetworkId)); - provider.start(); - } else if (doesInjectedWeb3Exist) { - // Since no public node for this network, all requests go to injectedWeb3 instance - provider = injectedWeb3.currentProvider; - } else { - // If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node - // We do this so that users can still browse the 0x Portal DApp even if they do not have web3 - // injected into their browser. - provider = new ProviderEngine(); - provider.addProvider(new FilterSubprovider()); - const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET; - provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); - provider.start(); - } - - return provider; - } - constructor(dispatcher: Dispatcher, isSalePage: boolean = false) { - this._dispatcher = dispatcher; - this._userAddress = ''; - // tslint:disable-next-line:no-floating-promises - this._onPageLoadInitFireAndForgetAsync(); - } - public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number) { - const isConnected = !_.isUndefined(newNetworkId); - if (!isConnected) { - this.networkId = newNetworkId; - this._dispatcher.encounteredBlockchainError(BlockchainErrs.DisconnectedFromEthereumNode); - this._dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - } else if (this.networkId !== newNetworkId) { - this.networkId = newNetworkId; - this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError); - await this._fetchTokenInformationAsync(); - await this._rehydrateStoreWithContractEvents(); - } - } - public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) { - if (this._userAddress !== newUserAddress) { - this._userAddress = newUserAddress; - await this._fetchTokenInformationAsync(); - await this._rehydrateStoreWithContractEvents(); - } - } - public async nodeVersionUpdatedFireAndForgetAsync(nodeVersion: string) { - if (this.nodeVersion !== nodeVersion) { - this.nodeVersion = nodeVersion; - } - } - public async isAddressInTokenRegistryAsync(tokenAddress: string): Promise<boolean> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - // HACK: temporarily whitelist the new WETH token address `as if` they were - // already in the tokenRegistry. - // TODO: Remove this hack once we've updated the TokenRegistries - // Airtable task: https://airtable.com/tblFe0Q9JuKJPYbTn/viwsOG2Y97qdIeCIO/recv3VGmIorFzHBVz - if (configs.SHOULD_DEPRECATE_OLD_WETH_TOKEN && tokenAddress === configs.NEW_WRAPPED_ETHERS[this.networkId]) { - return true; - } - const tokenIfExists = await this._zeroEx.tokenRegistry.getTokenIfExistsAsync(tokenAddress); - return !_.isUndefined(tokenIfExists); - } - public getLedgerDerivationPathIfExists(): string { - if (_.isUndefined(this._ledgerSubprovider)) { - return undefined; - } - const path = this._ledgerSubprovider.getPath(); - return path; - } - public updateLedgerDerivationPathIfExists(path: string) { - if (_.isUndefined(this._ledgerSubprovider)) { - return; // noop - } - this._ledgerSubprovider.setPath(path); - } - public updateLedgerDerivationIndex(pathIndex: number) { - if (_.isUndefined(this._ledgerSubprovider)) { - return; // noop - } - this._ledgerSubprovider.setPathIndex(pathIndex); - } - public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - // Should actually be Web3.Provider|ProviderEngine union type but it causes issues - // later on in the logic. - let provider; - switch (providerType) { - case ProviderType.Ledger: { - const isU2FSupported = await utils.isU2FSupportedAsync(); - if (!isU2FSupported) { - throw new Error('Cannot update providerType to LEDGER without U2F support'); - } - - // Cache injected provider so that we can switch the user back to it easily - this._cachedProvider = this._web3Wrapper.getProviderObj(); - - this._dispatcher.updateUserAddress(''); // Clear old userAddress - - provider = new ProviderEngine(); - const ledgerWalletConfigs = { - networkId: this.networkId, - ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync, - }; - this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs); - provider.addProvider(this._ledgerSubprovider); - provider.addProvider(new FilterSubprovider()); - const networkId = configs.IS_MAINNET_ENABLED - ? constants.NETWORK_ID_MAINNET - : constants.NETWORK_ID_TESTNET; - provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); - provider.start(); - this._web3Wrapper.destroy(); - const shouldPollUserAddress = false; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); - this._zeroEx.setProvider(provider, networkId); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - break; - } - - case ProviderType.Injected: { - if (_.isUndefined(this._cachedProvider)) { - return; // Going from injected to injected, so we noop - } - provider = this._cachedProvider; - const shouldPollUserAddress = true; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); - this._zeroEx.setProvider(provider, this.networkId); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - delete this._ledgerSubprovider; - delete this._cachedProvider; - break; - } - - default: - throw utils.spawnSwitchErr('providerType', providerType); - } - - await this._fetchTokenInformationAsync(); - } - public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> { - utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - - const txHash = await this._zeroEx.token.setProxyAllowanceAsync( - token.address, - this._userAddress, - amountInBaseUnits, - ); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const allowance = amountInBaseUnits; - this._dispatcher.replaceTokenAllowanceByAddress(token.address, allowance); - } - public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> { - const txHash = await this._zeroEx.token.transferAsync( - token.address, - this._userAddress, - toAddress, - amountInBaseUnits, - ); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx); - this._dispatcher.showFlashMessage( - React.createElement(TokenSendCompleted, { - etherScanLinkIfExists, - token, - toAddress, - amountInBaseUnits, - }), - ); - } - public portalOrderToSignedOrder( - maker: string, - taker: string, - makerTokenAddress: string, - takerTokenAddress: string, - makerTokenAmount: BigNumber, - takerTokenAmount: BigNumber, - makerFee: BigNumber, - takerFee: BigNumber, - expirationUnixTimestampSec: BigNumber, - feeRecipient: string, - signatureData: SignatureData, - salt: BigNumber, - ): SignedOrder { - const ecSignature = signatureData; - const exchangeContractAddress = this.getExchangeContractAddressIfExists(); - const takerOrNullAddress = _.isEmpty(taker) ? constants.NULL_ADDRESS : taker; - const signedOrder = { - ecSignature, - exchangeContractAddress, - expirationUnixTimestampSec, - feeRecipient, - maker, - makerFee, - makerTokenAddress, - makerTokenAmount, - salt, - taker: takerOrNullAddress, - takerFee, - takerTokenAddress, - takerTokenAmount, - }; - return signedOrder; - } - public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber): Promise<BigNumber> { - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const shouldThrowOnInsufficientBalanceOrAllowance = true; - - const txHash = await this._zeroEx.exchange.fillOrderAsync( - signedOrder, - fillTakerTokenAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - this._userAddress, - ); - const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any; - this._zeroEx.exchange.throwLogErrorsAsErrors(logs); - const logFill = _.find(logs, { event: 'LogFill' }); - const args = (logFill.args as any) as LogFillContractEventArgs; - const filledTakerTokenAmount = args.filledTakerTokenAmount; - return filledTakerTokenAmount; - } - public async cancelOrderAsync(signedOrder: SignedOrder, cancelTakerTokenAmount: BigNumber): Promise<BigNumber> { - const txHash = await this._zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerTokenAmount); - const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any; - this._zeroEx.exchange.throwLogErrorsAsErrors(logs); - const logCancel = _.find(logs, { event: ExchangeEvents.LogCancel }); - const args = (logCancel.args as any) as LogCancelContractEventArgs; - const cancelledTakerTokenAmount = args.cancelledTakerTokenAmount; - return cancelledTakerTokenAmount; - } - public async getUnavailableTakerAmountAsync(orderHash: string): Promise<BigNumber> { - utils.assert(ZeroEx.isValidOrderHash(orderHash), 'Must be valid orderHash'); - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - const unavailableTakerAmount = await this._zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); - return unavailableTakerAmount; - } - public getExchangeContractAddressIfExists() { - return this._exchangeAddress; - } - public async validateFillOrderThrowIfInvalidAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - ): Promise<void> { - await this._zeroEx.exchange.validateFillOrderThrowIfInvalidAsync( - signedOrder, - fillTakerTokenAmount, - takerAddress, - ); - } - public async validateCancelOrderThrowIfInvalidAsync( - order: Order, - cancelTakerTokenAmount: BigNumber, - ): Promise<void> { - await this._zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount); - } - public isValidAddress(address: string): boolean { - const lowercaseAddress = address.toLowerCase(); - return this._web3Wrapper.isAddress(lowercaseAddress); - } - public async pollTokenBalanceAsync(token: Token) { - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); - - this._zrxPollIntervalId = intervalUtils.setAsyncExcludingInterval( - async () => { - const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); - if (!balance.eq(currBalance)) { - this._dispatcher.replaceTokenBalanceByAddress(token.address, balance); - intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); - delete this._zrxPollIntervalId; - } - }, - 5000, - (err: Error) => { - utils.consoleLog(`Polling tokenBalance failed: ${err}`); - intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); - delete this._zrxPollIntervalId; - }, - ); - } - public async signOrderHashAsync(orderHash: string): Promise<SignatureData> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - const makerAddress = this._userAddress; - // If makerAddress is undefined, this means they have a web3 instance injected into their browser - // but no account addresses associated with it. - if (_.isUndefined(makerAddress)) { - throw new Error('Tried to send a sign request but user has no associated addresses'); - } - const ecSignature = await this._zeroEx.signOrderHashAsync(orderHash, makerAddress); - const signatureData = _.extend({}, ecSignature, { - hash: orderHash, - }); - this._dispatcher.updateSignatureData(signatureData); - return signatureData; - } - public async mintTestTokensAsync(token: Token) { - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address); - await mintableContract.mint(constants.MINT_AMOUNT, { - from: this._userAddress, - }); - const balanceDelta = constants.MINT_AMOUNT; - this._dispatcher.updateTokenBalanceByAddress(token.address, balanceDelta); - } - public async getBalanceInEthAsync(owner: string): Promise<BigNumber> { - const balance = await this._web3Wrapper.getBalanceInEthAsync(owner); - return balance; - } - public async convertEthToWrappedEthTokensAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - } - public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress); - await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - } - public async doesContractExistAtAddressAsync(address: string) { - const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(address); - return doesContractExist; - } - public async getCurrentUserTokenBalanceAndAllowanceAsync(tokenAddress: string): Promise<BigNumber[]> { - const tokenBalanceAndAllowance = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, tokenAddress); - return tokenBalanceAndAllowance; - } - public async getTokenBalanceAndAllowanceAsync(ownerAddress: string, tokenAddress: string): Promise<BigNumber[]> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - - if (_.isEmpty(ownerAddress)) { - const zero = new BigNumber(0); - return [zero, zero]; - } - let balance = new BigNumber(0); - let allowance = new BigNumber(0); - if (this._doesUserAddressExist()) { - balance = await this._zeroEx.token.getBalanceAsync(tokenAddress, ownerAddress); - allowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, ownerAddress); - } - return [balance, allowance]; - } - public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) { - const tokenStateByAddress: TokenStateByAddress = {}; - for (const token of tokens) { - let balance = new BigNumber(0); - let allowance = new BigNumber(0); - if (this._doesUserAddressExist()) { - [balance, allowance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); - } - const tokenState = { - balance, - allowance, - }; - tokenStateByAddress[token.address] = tokenState; - } - this._dispatcher.updateTokenStateByAddress(tokenStateByAddress); - } - public async getUserAccountsAsync() { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - const userAccountsIfExists = await this._zeroEx.getAvailableAddressesAsync(); - return userAccountsIfExists; - } - // HACK: When a user is using a Ledger, we simply dispatch the selected userAddress, which - // by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to - // manually update it. This should only be called by the LedgerConfigDialog. - public updateWeb3WrapperPrevUserAddress(newUserAddress: string) { - this._web3Wrapper.updatePrevUserAddress(newUserAddress); - } - public destroy() { - intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); - this._web3Wrapper.destroy(); - this._stopWatchingExchangeLogFillEvents(); - } - private async _showEtherScanLinkAndAwaitTransactionMinedAsync( - txHash: string, - ): Promise<TransactionReceiptWithDecodedLogs> { - const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx); - this._dispatcher.showFlashMessage( - React.createElement(TransactionSubmitted, { - etherScanLinkIfExists, - }), - ); - const receipt = await this._zeroEx.awaitTransactionMinedAsync(txHash); - return receipt; - } - private _doesUserAddressExist(): boolean { - return this._userAddress !== ''; - } - private async _rehydrateStoreWithContractEvents() { - // Ensure we are only ever listening to one set of events - this._stopWatchingExchangeLogFillEvents(); - - if (!this._doesUserAddressExist()) { - return; // short-circuit - } - - if (!_.isUndefined(this._zeroEx)) { - // Since we do not have an index on the `taker` address and want to show - // transactions where an account is either the `maker` or `taker`, we loop - // through all fill events, and filter/cache them client-side. - const filterIndexObj = {}; - await this._startListeningForExchangeLogFillEventsAsync(filterIndexObj); - } - } - private async _startListeningForExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise<void> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - - // Fetch historical logs - await this._fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues); - - // Start a subscription for new logs - this._zeroEx.exchange.subscribe( - ExchangeEvents.LogFill, - indexFilterValues, - async (err: Error, decodedLogEvent: DecodedLogEvent<LogFillContractEventArgs>) => { - if (err) { - // Note: it's not entirely clear from the documentation which - // errors will be thrown by `watch`. For now, let's log the error - // to rollbar and stop watching when one occurs - // tslint:disable-next-line:no-floating-promises - errorReporter.reportAsync(err); // fire and forget - return; - } else { - const decodedLog = decodedLogEvent.log; - if (!this._doesLogEventInvolveUser(decodedLog)) { - return; // We aren't interested in the fill event - } - this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); - const fill = await this._convertDecodedLogToFillAsync(decodedLog); - if (decodedLogEvent.isRemoved) { - tradeHistoryStorage.removeFillFromUser(this._userAddress, this.networkId, fill); - } else { - tradeHistoryStorage.addFillToUser(this._userAddress, this.networkId, fill); - } - } - }, - ); - } - private async _fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues) { - const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this._userAddress, this.networkId); - const blockRange: BlockRange = { - fromBlock, - toBlock: 'latest' as BlockParam, - }; - const decodedLogs = await this._zeroEx.exchange.getLogsAsync<LogFillContractEventArgs>( - ExchangeEvents.LogFill, - blockRange, - indexFilterValues, - ); - for (const decodedLog of decodedLogs) { - if (!this._doesLogEventInvolveUser(decodedLog)) { - continue; // We aren't interested in the fill event - } - this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); - const fill = await this._convertDecodedLogToFillAsync(decodedLog); - tradeHistoryStorage.addFillToUser(this._userAddress, this.networkId, fill); - } - } - private async _convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { - const args = decodedLog.args; - const blockTimestamp = await this._web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash); - const fill = { - filledTakerTokenAmount: args.filledTakerTokenAmount, - filledMakerTokenAmount: args.filledMakerTokenAmount, - logIndex: decodedLog.logIndex, - maker: args.maker, - orderHash: args.orderHash, - taker: args.taker, - makerToken: args.makerToken, - takerToken: args.takerToken, - paidMakerFee: args.paidMakerFee, - paidTakerFee: args.paidTakerFee, - transactionHash: decodedLog.transactionHash, - blockTimestamp, - }; - return fill; - } - private _doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { - const args = decodedLog.args; - const isUserMakerOrTaker = args.maker === this._userAddress || args.taker === this._userAddress; - return isUserMakerOrTaker; - } - private _updateLatestFillsBlockIfNeeded(blockNumber: number) { - const isBlockPending = _.isNull(blockNumber); - if (!isBlockPending) { - // Hack: I've observed the behavior where a client won't register certain fill events - // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply - // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user - // would still attempt to re-fetch events from the previous 50 blocks, but won't need to - // re-fetch all events in all blocks. - // TODO: Debug if this is a race condition, and apply a more precise fix - const blockNumberToSet = - blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? 0 : blockNumber - BLOCK_NUMBER_BACK_TRACK; - tradeHistoryStorage.setFillsLatestBlock(this._userAddress, this.networkId, blockNumberToSet); - } - } - private _stopWatchingExchangeLogFillEvents(): void { - this._zeroEx.exchange.unsubscribeAll(); - } - private async _getTokenRegistryTokensByAddressAsync(): Promise<TokenByAddress> { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - const tokenRegistryTokens = await this._zeroEx.tokenRegistry.getTokensAsync(); - - const tokenByAddress: TokenByAddress = {}; - _.each(tokenRegistryTokens, (t: ZeroExToken, i: number) => { - // HACK: For now we have a hard-coded list of iconUrls for the dummyTokens - // TODO: Refactor this out and pull the iconUrl directly from the TokenRegistry - const iconUrl = configs.ICON_URL_BY_SYMBOL[t.symbol]; - // HACK: Temporarily we hijack the WETH addresses fetched from the tokenRegistry - // so that we can take our time with actually updating it. This ensures that when - // we deploy the new WETH page, everyone will re-fill their trackedTokens with the - // new canonical WETH. - // TODO: Remove this hack once we've updated the TokenRegistries - // Airtable task: https://airtable.com/tblFe0Q9JuKJPYbTn/viwsOG2Y97qdIeCIO/recv3VGmIorFzHBVz - let address = t.address; - if (configs.SHOULD_DEPRECATE_OLD_WETH_TOKEN && t.symbol === 'WETH') { - const newEtherTokenAddressIfExists = configs.NEW_WRAPPED_ETHERS[this.networkId]; - if (!_.isUndefined(newEtherTokenAddressIfExists)) { - address = newEtherTokenAddressIfExists; - } - } - const token: Token = { - iconUrl, - address, - name: t.name, - symbol: t.symbol, - decimals: t.decimals, - isTracked: false, - isRegistered: true, - }; - tokenByAddress[token.address] = token; - }); - return tokenByAddress; - } - private async _onPageLoadInitFireAndForgetAsync() { - await Blockchain._onPageLoadAsync(); // wait for page to load - - // Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in - // order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot - // retrieve it from within the web3Wrapper constructor. This is and should remain the only - // call to a web3 instance outside of web3Wrapper in the entire dapp. - // In addition, if the user has an injectedWeb3 instance that is disconnected from a backing - // Ethereum node, this call will throw. We need to handle this case gracefully - const injectedWeb3 = (window as any).web3; - let networkIdIfExists: number; - if (!_.isUndefined(injectedWeb3)) { - try { - networkIdIfExists = _.parseInt(await promisify<string>(injectedWeb3.version.getNetwork)()); - } catch (err) { - // Ignore error and proceed with networkId undefined - } - } - - const provider = await Blockchain._getProviderAsync(injectedWeb3, networkIdIfExists); - const networkId = !_.isUndefined(networkIdIfExists) - ? networkIdIfExists - : configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET; - const zeroExConfigs = { - networkId, - }; - this._zeroEx = new ZeroEx(provider, zeroExConfigs); - this._updateProviderName(injectedWeb3); - const shouldPollUserAddress = true; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, networkId, shouldPollUserAddress); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - } - // This method should always be run after instantiating or updating the provider - // of the ZeroEx instance. - private async _postInstantiationOrUpdatingProviderZeroExAsync() { - utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - this._exchangeAddress = this._zeroEx.exchange.getContractAddress(); - } - private _updateProviderName(injectedWeb3: Web3) { - const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); - const providerName = doesInjectedWeb3Exist - ? Blockchain._getNameGivenProvider(injectedWeb3.currentProvider) - : constants.PROVIDER_NAME_PUBLIC; - this._dispatcher.updateInjectedProviderName(providerName); - } - private async _fetchTokenInformationAsync() { - utils.assert( - !_.isUndefined(this.networkId), - 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node', - ); - - this._dispatcher.updateBlockchainIsLoaded(false); - this._dispatcher.clearTokenByAddress(); - - const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync(); - - // HACK: We need to fetch the userAddress here because otherwise we cannot save the - // tracked tokens in localStorage under the users address nor fetch the token - // balances and allowances and we need to do this in order not to trigger the blockchain - // loading dialog to show up twice. First to load the contracts, and second to load the - // balances and allowances. - this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); - if (!_.isEmpty(this._userAddress)) { - this._dispatcher.updateUserAddress(this._userAddress); - } - - let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this._userAddress, this.networkId); - const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); - if (_.isUndefined(trackedTokensIfExists)) { - trackedTokensIfExists = _.map(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => { - const token = _.find(tokenRegistryTokens, t => t.symbol === symbol); - token.isTracked = true; - return token; - }); - _.each(trackedTokensIfExists, token => { - trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token); - }); - } else { - // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array - _.each(trackedTokensIfExists, trackedToken => { - if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) { - tokenRegistryTokensByAddress[trackedToken.address].isTracked = true; - } - }); - } - const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]); - this._dispatcher.updateTokenByAddress(allTokens); - - // Get balance/allowance for tracked tokens - await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists); - - const mostPopularTradingPairTokens: Token[] = [ - _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }), - _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }), - ]; - this._dispatcher.updateChosenAssetTokenAddress(Side.Deposit, mostPopularTradingPairTokens[0].address); - this._dispatcher.updateChosenAssetTokenAddress(Side.Receive, mostPopularTradingPairTokens[1].address); - this._dispatcher.updateBlockchainIsLoaded(true); - } - private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> { - const c = await contract(artifact); - const providerObj = this._web3Wrapper.getProviderObj(); - c.setProvider(providerObj); - - const artifactNetworkConfigs = artifact.networks[this.networkId]; - let contractAddress; - if (!_.isUndefined(address)) { - contractAddress = address; - } else if (!_.isUndefined(artifactNetworkConfigs)) { - contractAddress = artifactNetworkConfigs.address; - } - - if (!_.isUndefined(contractAddress)) { - const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress); - if (!doesContractExist) { - utils.consoleLog(`Contract does not exist: ${artifact.contract_name} at ${contractAddress}`); - throw new Error(BlockchainCallErrs.ContractDoesNotExist); - } - } - - try { - const contractInstance = _.isUndefined(address) ? await c.deployed() : 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(BlockchainCallErrs.ContractDoesNotExist); - } else { - await errorReporter.reportAsync(err); - throw new Error(BlockchainCallErrs.UnhandledError); - } - } - } + public networkId: number; + public nodeVersion: string; + private _zeroEx: ZeroEx; + private _dispatcher: Dispatcher; + private _web3Wrapper?: Web3Wrapper; + private _exchangeAddress: string; + private _userAddress: string; + private _cachedProvider: Web3.Provider; + private _ledgerSubprovider: LedgerWalletSubprovider; + private _zrxPollIntervalId: NodeJS.Timer; + private static async _onPageLoadAsync(): Promise<void> { + if (document.readyState === 'complete') { + return; // Already loaded + } + return new Promise<void>((resolve, reject) => { + window.onload = () => resolve(); + }); + } + private static _getNameGivenProvider(provider: Web3.Provider): string { + if (!_.isUndefined((provider as any).isMetaMask)) { + return constants.PROVIDER_NAME_METAMASK; + } + + // HACK: We use the fact that Parity Signer's provider is an instance of their + // internal `Web3FrameProvider` class. + const isParitySigner = _.startsWith(provider.constructor.toString(), 'function Web3FrameProvider'); + if (isParitySigner) { + return constants.PROVIDER_NAME_PARITY_SIGNER; + } + + return constants.PROVIDER_NAME_GENERIC; + } + private static async _getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number) { + const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; + const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); + + let provider; + if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) { + // We catch all requests involving a users account and send it to the injectedWeb3 + // instance. All other requests go to the public hosted node. + provider = new ProviderEngine(); + provider.addProvider(new InjectedWeb3Subprovider(injectedWeb3)); + provider.addProvider(new FilterSubprovider()); + provider.addProvider(new RedundantRPCSubprovider(publicNodeUrlsIfExistsForNetworkId)); + provider.start(); + } else if (doesInjectedWeb3Exist) { + // Since no public node for this network, all requests go to injectedWeb3 instance + provider = injectedWeb3.currentProvider; + } else { + // If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node + // We do this so that users can still browse the 0x Portal DApp even if they do not have web3 + // injected into their browser. + provider = new ProviderEngine(); + provider.addProvider(new FilterSubprovider()); + const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET; + provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); + provider.start(); + } + + return provider; + } + constructor(dispatcher: Dispatcher, isSalePage: boolean = false) { + this._dispatcher = dispatcher; + this._userAddress = ''; + // tslint:disable-next-line:no-floating-promises + this._onPageLoadInitFireAndForgetAsync(); + } + public async networkIdUpdatedFireAndForgetAsync(newNetworkId: number) { + const isConnected = !_.isUndefined(newNetworkId); + if (!isConnected) { + this.networkId = newNetworkId; + this._dispatcher.encounteredBlockchainError(BlockchainErrs.DisconnectedFromEthereumNode); + this._dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } else if (this.networkId !== newNetworkId) { + this.networkId = newNetworkId; + this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError); + await this._fetchTokenInformationAsync(); + await this._rehydrateStoreWithContractEvents(); + } + } + public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) { + if (this._userAddress !== newUserAddress) { + this._userAddress = newUserAddress; + await this._fetchTokenInformationAsync(); + await this._rehydrateStoreWithContractEvents(); + } + } + public async nodeVersionUpdatedFireAndForgetAsync(nodeVersion: string) { + if (this.nodeVersion !== nodeVersion) { + this.nodeVersion = nodeVersion; + } + } + public async isAddressInTokenRegistryAsync(tokenAddress: string): Promise<boolean> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + // HACK: temporarily whitelist the new WETH token address `as if` they were + // already in the tokenRegistry. + // TODO: Remove this hack once we've updated the TokenRegistries + // Airtable task: https://airtable.com/tblFe0Q9JuKJPYbTn/viwsOG2Y97qdIeCIO/recv3VGmIorFzHBVz + if (configs.SHOULD_DEPRECATE_OLD_WETH_TOKEN && tokenAddress === configs.NEW_WRAPPED_ETHERS[this.networkId]) { + return true; + } + const tokenIfExists = await this._zeroEx.tokenRegistry.getTokenIfExistsAsync(tokenAddress); + return !_.isUndefined(tokenIfExists); + } + public getLedgerDerivationPathIfExists(): string { + if (_.isUndefined(this._ledgerSubprovider)) { + return undefined; + } + const path = this._ledgerSubprovider.getPath(); + return path; + } + public updateLedgerDerivationPathIfExists(path: string) { + if (_.isUndefined(this._ledgerSubprovider)) { + return; // noop + } + this._ledgerSubprovider.setPath(path); + } + public updateLedgerDerivationIndex(pathIndex: number) { + if (_.isUndefined(this._ledgerSubprovider)) { + return; // noop + } + this._ledgerSubprovider.setPathIndex(pathIndex); + } + public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + // Should actually be Web3.Provider|ProviderEngine union type but it causes issues + // later on in the logic. + let provider; + switch (providerType) { + case ProviderType.Ledger: { + const isU2FSupported = await utils.isU2FSupportedAsync(); + if (!isU2FSupported) { + throw new Error('Cannot update providerType to LEDGER without U2F support'); + } + + // Cache injected provider so that we can switch the user back to it easily + this._cachedProvider = this._web3Wrapper.getProviderObj(); + + this._dispatcher.updateUserAddress(''); // Clear old userAddress + + provider = new ProviderEngine(); + const ledgerWalletConfigs = { + networkId: this.networkId, + ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync, + }; + this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs); + provider.addProvider(this._ledgerSubprovider); + provider.addProvider(new FilterSubprovider()); + const networkId = configs.IS_MAINNET_ENABLED + ? constants.NETWORK_ID_MAINNET + : constants.NETWORK_ID_TESTNET; + provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); + provider.start(); + this._web3Wrapper.destroy(); + const shouldPollUserAddress = false; + this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); + this._zeroEx.setProvider(provider, networkId); + await this._postInstantiationOrUpdatingProviderZeroExAsync(); + break; + } + + case ProviderType.Injected: { + if (_.isUndefined(this._cachedProvider)) { + return; // Going from injected to injected, so we noop + } + provider = this._cachedProvider; + const shouldPollUserAddress = true; + this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); + this._zeroEx.setProvider(provider, this.networkId); + await this._postInstantiationOrUpdatingProviderZeroExAsync(); + delete this._ledgerSubprovider; + delete this._cachedProvider; + break; + } + + default: + throw utils.spawnSwitchErr('providerType', providerType); + } + + await this._fetchTokenInformationAsync(); + } + public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> { + utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid); + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + + const txHash = await this._zeroEx.token.setProxyAllowanceAsync( + token.address, + this._userAddress, + amountInBaseUnits, + ); + await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const allowance = amountInBaseUnits; + this._dispatcher.replaceTokenAllowanceByAddress(token.address, allowance); + } + public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> { + const txHash = await this._zeroEx.token.transferAsync( + token.address, + this._userAddress, + toAddress, + amountInBaseUnits, + ); + await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx); + this._dispatcher.showFlashMessage( + React.createElement(TokenSendCompleted, { + etherScanLinkIfExists, + token, + toAddress, + amountInBaseUnits, + }), + ); + } + public portalOrderToSignedOrder( + maker: string, + taker: string, + makerTokenAddress: string, + takerTokenAddress: string, + makerTokenAmount: BigNumber, + takerTokenAmount: BigNumber, + makerFee: BigNumber, + takerFee: BigNumber, + expirationUnixTimestampSec: BigNumber, + feeRecipient: string, + signatureData: SignatureData, + salt: BigNumber, + ): SignedOrder { + const ecSignature = signatureData; + const exchangeContractAddress = this.getExchangeContractAddressIfExists(); + const takerOrNullAddress = _.isEmpty(taker) ? constants.NULL_ADDRESS : taker; + const signedOrder = { + ecSignature, + exchangeContractAddress, + expirationUnixTimestampSec, + feeRecipient, + maker, + makerFee, + makerTokenAddress, + makerTokenAmount, + salt, + taker: takerOrNullAddress, + takerFee, + takerTokenAddress, + takerTokenAmount, + }; + return signedOrder; + } + public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber): Promise<BigNumber> { + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + const shouldThrowOnInsufficientBalanceOrAllowance = true; + + const txHash = await this._zeroEx.exchange.fillOrderAsync( + signedOrder, + fillTakerTokenAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + this._userAddress, + ); + const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any; + this._zeroEx.exchange.throwLogErrorsAsErrors(logs); + const logFill = _.find(logs, { event: 'LogFill' }); + const args = (logFill.args as any) as LogFillContractEventArgs; + const filledTakerTokenAmount = args.filledTakerTokenAmount; + return filledTakerTokenAmount; + } + public async cancelOrderAsync(signedOrder: SignedOrder, cancelTakerTokenAmount: BigNumber): Promise<BigNumber> { + const txHash = await this._zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerTokenAmount); + const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any; + this._zeroEx.exchange.throwLogErrorsAsErrors(logs); + const logCancel = _.find(logs, { event: ExchangeEvents.LogCancel }); + const args = (logCancel.args as any) as LogCancelContractEventArgs; + const cancelledTakerTokenAmount = args.cancelledTakerTokenAmount; + return cancelledTakerTokenAmount; + } + public async getUnavailableTakerAmountAsync(orderHash: string): Promise<BigNumber> { + utils.assert(ZeroEx.isValidOrderHash(orderHash), 'Must be valid orderHash'); + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + const unavailableTakerAmount = await this._zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); + return unavailableTakerAmount; + } + public getExchangeContractAddressIfExists() { + return this._exchangeAddress; + } + public async validateFillOrderThrowIfInvalidAsync( + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + ): Promise<void> { + await this._zeroEx.exchange.validateFillOrderThrowIfInvalidAsync( + signedOrder, + fillTakerTokenAmount, + takerAddress, + ); + } + public async validateCancelOrderThrowIfInvalidAsync( + order: Order, + cancelTakerTokenAmount: BigNumber, + ): Promise<void> { + await this._zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount); + } + public isValidAddress(address: string): boolean { + const lowercaseAddress = address.toLowerCase(); + return this._web3Wrapper.isAddress(lowercaseAddress); + } + public async pollTokenBalanceAsync(token: Token) { + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); + + this._zrxPollIntervalId = intervalUtils.setAsyncExcludingInterval( + async () => { + const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); + if (!balance.eq(currBalance)) { + this._dispatcher.replaceTokenBalanceByAddress(token.address, balance); + intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); + delete this._zrxPollIntervalId; + } + }, + 5000, + (err: Error) => { + utils.consoleLog(`Polling tokenBalance failed: ${err}`); + intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); + delete this._zrxPollIntervalId; + }, + ); + } + public async signOrderHashAsync(orderHash: string): Promise<SignatureData> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + const makerAddress = this._userAddress; + // If makerAddress is undefined, this means they have a web3 instance injected into their browser + // but no account addresses associated with it. + if (_.isUndefined(makerAddress)) { + throw new Error('Tried to send a sign request but user has no associated addresses'); + } + const ecSignature = await this._zeroEx.signOrderHashAsync(orderHash, makerAddress); + const signatureData = _.extend({}, ecSignature, { + hash: orderHash, + }); + this._dispatcher.updateSignatureData(signatureData); + return signatureData; + } + public async mintTestTokensAsync(token: Token) { + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address); + await mintableContract.mint(constants.MINT_AMOUNT, { + from: this._userAddress, + }); + const balanceDelta = constants.MINT_AMOUNT; + this._dispatcher.updateTokenBalanceByAddress(token.address, balanceDelta); + } + public async getBalanceInEthAsync(owner: string): Promise<BigNumber> { + const balance = await this._web3Wrapper.getBalanceInEthAsync(owner); + return balance; + } + public async convertEthToWrappedEthTokensAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress); + await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + } + public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress); + await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); + } + public async doesContractExistAtAddressAsync(address: string) { + const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(address); + return doesContractExist; + } + public async getCurrentUserTokenBalanceAndAllowanceAsync(tokenAddress: string): Promise<BigNumber[]> { + const tokenBalanceAndAllowance = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, tokenAddress); + return tokenBalanceAndAllowance; + } + public async getTokenBalanceAndAllowanceAsync(ownerAddress: string, tokenAddress: string): Promise<BigNumber[]> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + + if (_.isEmpty(ownerAddress)) { + const zero = new BigNumber(0); + return [zero, zero]; + } + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (this._doesUserAddressExist()) { + balance = await this._zeroEx.token.getBalanceAsync(tokenAddress, ownerAddress); + allowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, ownerAddress); + } + return [balance, allowance]; + } + public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) { + const tokenStateByAddress: TokenStateByAddress = {}; + for (const token of tokens) { + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (this._doesUserAddressExist()) { + [balance, allowance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); + } + const tokenState = { + balance, + allowance, + }; + tokenStateByAddress[token.address] = tokenState; + } + this._dispatcher.updateTokenStateByAddress(tokenStateByAddress); + } + public async getUserAccountsAsync() { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + const userAccountsIfExists = await this._zeroEx.getAvailableAddressesAsync(); + return userAccountsIfExists; + } + // HACK: When a user is using a Ledger, we simply dispatch the selected userAddress, which + // by-passes the web3Wrapper logic for updating the prevUserAddress. We therefore need to + // manually update it. This should only be called by the LedgerConfigDialog. + public updateWeb3WrapperPrevUserAddress(newUserAddress: string) { + this._web3Wrapper.updatePrevUserAddress(newUserAddress); + } + public destroy() { + intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); + this._web3Wrapper.destroy(); + this._stopWatchingExchangeLogFillEvents(); + } + private async _showEtherScanLinkAndAwaitTransactionMinedAsync( + txHash: string, + ): Promise<TransactionReceiptWithDecodedLogs> { + const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx); + this._dispatcher.showFlashMessage( + React.createElement(TransactionSubmitted, { + etherScanLinkIfExists, + }), + ); + const receipt = await this._zeroEx.awaitTransactionMinedAsync(txHash); + return receipt; + } + private _doesUserAddressExist(): boolean { + return this._userAddress !== ''; + } + private async _rehydrateStoreWithContractEvents() { + // Ensure we are only ever listening to one set of events + this._stopWatchingExchangeLogFillEvents(); + + if (!this._doesUserAddressExist()) { + return; // short-circuit + } + + if (!_.isUndefined(this._zeroEx)) { + // Since we do not have an index on the `taker` address and want to show + // transactions where an account is either the `maker` or `taker`, we loop + // through all fill events, and filter/cache them client-side. + const filterIndexObj = {}; + await this._startListeningForExchangeLogFillEventsAsync(filterIndexObj); + } + } + private async _startListeningForExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues): Promise<void> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); + + // Fetch historical logs + await this._fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues); + + // Start a subscription for new logs + this._zeroEx.exchange.subscribe( + ExchangeEvents.LogFill, + indexFilterValues, + async (err: Error, decodedLogEvent: DecodedLogEvent<LogFillContractEventArgs>) => { + if (err) { + // Note: it's not entirely clear from the documentation which + // errors will be thrown by `watch`. For now, let's log the error + // to rollbar and stop watching when one occurs + // tslint:disable-next-line:no-floating-promises + errorReporter.reportAsync(err); // fire and forget + return; + } else { + const decodedLog = decodedLogEvent.log; + if (!this._doesLogEventInvolveUser(decodedLog)) { + return; // We aren't interested in the fill event + } + this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); + const fill = await this._convertDecodedLogToFillAsync(decodedLog); + if (decodedLogEvent.isRemoved) { + tradeHistoryStorage.removeFillFromUser(this._userAddress, this.networkId, fill); + } else { + tradeHistoryStorage.addFillToUser(this._userAddress, this.networkId, fill); + } + } + }, + ); + } + private async _fetchHistoricalExchangeLogFillEventsAsync(indexFilterValues: IndexedFilterValues) { + const fromBlock = tradeHistoryStorage.getFillsLatestBlock(this._userAddress, this.networkId); + const blockRange: BlockRange = { + fromBlock, + toBlock: 'latest' as BlockParam, + }; + const decodedLogs = await this._zeroEx.exchange.getLogsAsync<LogFillContractEventArgs>( + ExchangeEvents.LogFill, + blockRange, + indexFilterValues, + ); + for (const decodedLog of decodedLogs) { + if (!this._doesLogEventInvolveUser(decodedLog)) { + continue; // We aren't interested in the fill event + } + this._updateLatestFillsBlockIfNeeded(decodedLog.blockNumber); + const fill = await this._convertDecodedLogToFillAsync(decodedLog); + tradeHistoryStorage.addFillToUser(this._userAddress, this.networkId, fill); + } + } + private async _convertDecodedLogToFillAsync(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { + const args = decodedLog.args; + const blockTimestamp = await this._web3Wrapper.getBlockTimestampAsync(decodedLog.blockHash); + const fill = { + filledTakerTokenAmount: args.filledTakerTokenAmount, + filledMakerTokenAmount: args.filledMakerTokenAmount, + logIndex: decodedLog.logIndex, + maker: args.maker, + orderHash: args.orderHash, + taker: args.taker, + makerToken: args.makerToken, + takerToken: args.takerToken, + paidMakerFee: args.paidMakerFee, + paidTakerFee: args.paidTakerFee, + transactionHash: decodedLog.transactionHash, + blockTimestamp, + }; + return fill; + } + private _doesLogEventInvolveUser(decodedLog: LogWithDecodedArgs<LogFillContractEventArgs>) { + const args = decodedLog.args; + const isUserMakerOrTaker = args.maker === this._userAddress || args.taker === this._userAddress; + return isUserMakerOrTaker; + } + private _updateLatestFillsBlockIfNeeded(blockNumber: number) { + const isBlockPending = _.isNull(blockNumber); + if (!isBlockPending) { + // Hack: I've observed the behavior where a client won't register certain fill events + // and lowering the cache blockNumber fixes the issue. As a quick fix for now, simply + // set the cached blockNumber 50 below the one returned. This way, upon refreshing, a user + // would still attempt to re-fetch events from the previous 50 blocks, but won't need to + // re-fetch all events in all blocks. + // TODO: Debug if this is a race condition, and apply a more precise fix + const blockNumberToSet = + blockNumber - BLOCK_NUMBER_BACK_TRACK < 0 ? 0 : blockNumber - BLOCK_NUMBER_BACK_TRACK; + tradeHistoryStorage.setFillsLatestBlock(this._userAddress, this.networkId, blockNumberToSet); + } + } + private _stopWatchingExchangeLogFillEvents(): void { + this._zeroEx.exchange.unsubscribeAll(); + } + private async _getTokenRegistryTokensByAddressAsync(): Promise<TokenByAddress> { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + const tokenRegistryTokens = await this._zeroEx.tokenRegistry.getTokensAsync(); + + const tokenByAddress: TokenByAddress = {}; + _.each(tokenRegistryTokens, (t: ZeroExToken, i: number) => { + // HACK: For now we have a hard-coded list of iconUrls for the dummyTokens + // TODO: Refactor this out and pull the iconUrl directly from the TokenRegistry + const iconUrl = configs.ICON_URL_BY_SYMBOL[t.symbol]; + // HACK: Temporarily we hijack the WETH addresses fetched from the tokenRegistry + // so that we can take our time with actually updating it. This ensures that when + // we deploy the new WETH page, everyone will re-fill their trackedTokens with the + // new canonical WETH. + // TODO: Remove this hack once we've updated the TokenRegistries + // Airtable task: https://airtable.com/tblFe0Q9JuKJPYbTn/viwsOG2Y97qdIeCIO/recv3VGmIorFzHBVz + let address = t.address; + if (configs.SHOULD_DEPRECATE_OLD_WETH_TOKEN && t.symbol === 'WETH') { + const newEtherTokenAddressIfExists = configs.NEW_WRAPPED_ETHERS[this.networkId]; + if (!_.isUndefined(newEtherTokenAddressIfExists)) { + address = newEtherTokenAddressIfExists; + } + } + const token: Token = { + iconUrl, + address, + name: t.name, + symbol: t.symbol, + decimals: t.decimals, + isTracked: false, + isRegistered: true, + }; + tokenByAddress[token.address] = token; + }); + return tokenByAddress; + } + private async _onPageLoadInitFireAndForgetAsync() { + await Blockchain._onPageLoadAsync(); // wait for page to load + + // Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in + // order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot + // retrieve it from within the web3Wrapper constructor. This is and should remain the only + // call to a web3 instance outside of web3Wrapper in the entire dapp. + // In addition, if the user has an injectedWeb3 instance that is disconnected from a backing + // Ethereum node, this call will throw. We need to handle this case gracefully + const injectedWeb3 = (window as any).web3; + let networkIdIfExists: number; + if (!_.isUndefined(injectedWeb3)) { + try { + networkIdIfExists = _.parseInt(await promisify<string>(injectedWeb3.version.getNetwork)()); + } catch (err) { + // Ignore error and proceed with networkId undefined + } + } + + const provider = await Blockchain._getProviderAsync(injectedWeb3, networkIdIfExists); + const networkId = !_.isUndefined(networkIdIfExists) + ? networkIdIfExists + : configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET; + const zeroExConfigs = { + networkId, + }; + this._zeroEx = new ZeroEx(provider, zeroExConfigs); + this._updateProviderName(injectedWeb3); + const shouldPollUserAddress = true; + this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, networkId, shouldPollUserAddress); + await this._postInstantiationOrUpdatingProviderZeroExAsync(); + } + // This method should always be run after instantiating or updating the provider + // of the ZeroEx instance. + private async _postInstantiationOrUpdatingProviderZeroExAsync() { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + this._exchangeAddress = this._zeroEx.exchange.getContractAddress(); + } + private _updateProviderName(injectedWeb3: Web3) { + const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const providerName = doesInjectedWeb3Exist + ? Blockchain._getNameGivenProvider(injectedWeb3.currentProvider) + : constants.PROVIDER_NAME_PUBLIC; + this._dispatcher.updateInjectedProviderName(providerName); + } + private async _fetchTokenInformationAsync() { + utils.assert( + !_.isUndefined(this.networkId), + 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node', + ); + + this._dispatcher.updateBlockchainIsLoaded(false); + this._dispatcher.clearTokenByAddress(); + + const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync(); + + // HACK: We need to fetch the userAddress here because otherwise we cannot save the + // tracked tokens in localStorage under the users address nor fetch the token + // balances and allowances and we need to do this in order not to trigger the blockchain + // loading dialog to show up twice. First to load the contracts, and second to load the + // balances and allowances. + this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); + if (!_.isEmpty(this._userAddress)) { + this._dispatcher.updateUserAddress(this._userAddress); + } + + let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this._userAddress, this.networkId); + const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); + if (_.isUndefined(trackedTokensIfExists)) { + trackedTokensIfExists = _.map(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => { + const token = _.find(tokenRegistryTokens, t => t.symbol === symbol); + token.isTracked = true; + return token; + }); + _.each(trackedTokensIfExists, token => { + trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token); + }); + } else { + // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array + _.each(trackedTokensIfExists, trackedToken => { + if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) { + tokenRegistryTokensByAddress[trackedToken.address].isTracked = true; + } + }); + } + const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]); + this._dispatcher.updateTokenByAddress(allTokens); + + // Get balance/allowance for tracked tokens + await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists); + + const mostPopularTradingPairTokens: Token[] = [ + _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }), + _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }), + ]; + this._dispatcher.updateChosenAssetTokenAddress(Side.Deposit, mostPopularTradingPairTokens[0].address); + this._dispatcher.updateChosenAssetTokenAddress(Side.Receive, mostPopularTradingPairTokens[1].address); + this._dispatcher.updateBlockchainIsLoaded(true); + } + private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> { + const c = await contract(artifact); + const providerObj = this._web3Wrapper.getProviderObj(); + c.setProvider(providerObj); + + const artifactNetworkConfigs = artifact.networks[this.networkId]; + let contractAddress; + if (!_.isUndefined(address)) { + contractAddress = address; + } else if (!_.isUndefined(artifactNetworkConfigs)) { + contractAddress = artifactNetworkConfigs.address; + } + + if (!_.isUndefined(contractAddress)) { + const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress); + if (!doesContractExist) { + utils.consoleLog(`Contract does not exist: ${artifact.contract_name} at ${contractAddress}`); + throw new Error(BlockchainCallErrs.ContractDoesNotExist); + } + } + + try { + const contractInstance = _.isUndefined(address) ? await c.deployed() : 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(BlockchainCallErrs.ContractDoesNotExist); + } else { + await errorReporter.reportAsync(err); + throw new Error(BlockchainCallErrs.UnhandledError); + } + } + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx index e0f61a29b..f555ca6b1 100644 --- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -9,150 +9,150 @@ import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; interface BlockchainErrDialogProps { - blockchain: Blockchain; - blockchainErr: BlockchainErrs; - isOpen: boolean; - userAddress: string; - toggleDialogFn: (isOpen: boolean) => void; - networkId: number; + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + isOpen: boolean; + userAddress: string; + toggleDialogFn: (isOpen: boolean) => void; + networkId: number; } export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProps, undefined> { - public render() { - const dialogActions = [ - <FlatButton - key="blockchainErrOk" - label="Ok" - primary={true} - onTouchTap={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)} - />, - ]; + public render() { + const dialogActions = [ + <FlatButton + key="blockchainErrOk" + label="Ok" + primary={true} + onTouchTap={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)} + />, + ]; - const hasWalletAddress = this.props.userAddress !== ''; - return ( - <Dialog - title={this._getTitle(hasWalletAddress)} - titleStyle={{ fontWeight: 100 }} - actions={dialogActions} - open={this.props.isOpen} - contentStyle={{ width: 400 }} - onRequestClose={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)} - autoScrollBodyContent={true} - > - <div className="pt2" style={{ color: colors.grey700 }}> - {this._renderExplanation(hasWalletAddress)} - </div> - </Dialog> - ); - } - private _getTitle(hasWalletAddress: boolean) { - if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { - return '0x smart contracts not found'; - } else if (!hasWalletAddress) { - return 'Enable wallet communication'; - } else if (this.props.blockchainErr === BlockchainErrs.DisconnectedFromEthereumNode) { - return 'Disconnected from Ethereum network'; - } else { - return 'Unexpected error'; - } - } - private _renderExplanation(hasWalletAddress: boolean) { - if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { - return this._renderContractsNotDeployedExplanation(); - } else if (!hasWalletAddress) { - return this._renderNoWalletFoundExplanation(); - } else if (this.props.blockchainErr === BlockchainErrs.DisconnectedFromEthereumNode) { - return this._renderDisconnectedFromNode(); - } else { - return this._renderUnexpectedErrorExplanation(); - } - } - private _renderDisconnectedFromNode() { - return ( - <div> - You were disconnected from the backing Ethereum node. If using{' '} - <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> - Metamask - </a>{' '} - or{' '} - <a href={constants.URL_MIST_DOWNLOAD} target="_blank"> - Mist - </a>{' '} - try refreshing the page. If using a locally hosted Ethereum node, make sure it's still running. - </div> - ); - } - private _renderUnexpectedErrorExplanation() { - return <div>We encountered an unexpected error. Please try refreshing the page.</div>; - } - private _renderNoWalletFoundExplanation() { - return ( - <div> - <div> - We were unable to access an Ethereum wallet you control. In order to interact with the 0x portal - dApp, we need a way to interact with one of your Ethereum wallets. There are two easy ways you can - enable us to do that: - </div> - <h4>1. Metamask chrome extension</h4> - <div> - You can install the{' '} - <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> - Metamask - </a>{' '} - Chrome extension Ethereum wallet. Once installed and set up, refresh this page. - <div className="pt1"> - <span className="bold">Note:</span> If you already have Metamask installed, make sure it is - unlocked. - </div> - </div> - <h4>Parity Signer</h4> - <div> - The{' '} - <a href={constants.URL_PARITY_CHROME_STORE} target="_blank"> - Parity Signer Chrome extension - </a>{' '} - lets you connect to a locally running Parity node. Make sure you have started your local Parity node - with {configs.IS_MAINNET_ENABLED && '`parity ui` or'} `parity --chain kovan ui` in order to connect - to {configs.IS_MAINNET_ENABLED ? 'mainnet or Kovan respectively.' : 'Kovan.'} - </div> - <div className="pt2"> - <span className="bold">Note:</span> If you have done one of the above steps and are still seeing - this message, we might still be unable to retrieve an Ethereum address by calling - `web3.eth.accounts`. Make sure you have created at least one Ethereum address. - </div> - </div> - ); - } - private _renderContractsNotDeployedExplanation() { - return ( - <div> - <div> - The 0x smart contracts are not deployed on the Ethereum network you are currently connected to - (network Id: {this.props.networkId}). In order to use the 0x portal dApp, please connect to the{' '} - {constants.TESTNET_NAME} testnet (network Id: {constants.NETWORK_ID_TESTNET}) - {configs.IS_MAINNET_ENABLED - ? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).` - : `.`} - </div> - <h4>Metamask</h4> - <div> - If you are using{' '} - <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> - Metamask - </a>, you can switch networks in the top left corner of the extension popover. - </div> - <h4>Parity Signer</h4> - <div> - If using the{' '} - <a href={constants.URL_PARITY_CHROME_STORE} target="_blank"> - Parity Signer Chrome extension - </a>, make sure to start your local Parity node with{' '} - {configs.IS_MAINNET_ENABLED - ? '`parity ui` or `parity --chain Kovan ui` in order to connect to mainnet \ + const hasWalletAddress = this.props.userAddress !== ''; + return ( + <Dialog + title={this._getTitle(hasWalletAddress)} + titleStyle={{ fontWeight: 100 }} + actions={dialogActions} + open={this.props.isOpen} + contentStyle={{ width: 400 }} + onRequestClose={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)} + autoScrollBodyContent={true} + > + <div className="pt2" style={{ color: colors.grey700 }}> + {this._renderExplanation(hasWalletAddress)} + </div> + </Dialog> + ); + } + private _getTitle(hasWalletAddress: boolean) { + if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { + return '0x smart contracts not found'; + } else if (!hasWalletAddress) { + return 'Enable wallet communication'; + } else if (this.props.blockchainErr === BlockchainErrs.DisconnectedFromEthereumNode) { + return 'Disconnected from Ethereum network'; + } else { + return 'Unexpected error'; + } + } + private _renderExplanation(hasWalletAddress: boolean) { + if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { + return this._renderContractsNotDeployedExplanation(); + } else if (!hasWalletAddress) { + return this._renderNoWalletFoundExplanation(); + } else if (this.props.blockchainErr === BlockchainErrs.DisconnectedFromEthereumNode) { + return this._renderDisconnectedFromNode(); + } else { + return this._renderUnexpectedErrorExplanation(); + } + } + private _renderDisconnectedFromNode() { + return ( + <div> + You were disconnected from the backing Ethereum node. If using{' '} + <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> + Metamask + </a>{' '} + or{' '} + <a href={constants.URL_MIST_DOWNLOAD} target="_blank"> + Mist + </a>{' '} + try refreshing the page. If using a locally hosted Ethereum node, make sure it's still running. + </div> + ); + } + private _renderUnexpectedErrorExplanation() { + return <div>We encountered an unexpected error. Please try refreshing the page.</div>; + } + private _renderNoWalletFoundExplanation() { + return ( + <div> + <div> + We were unable to access an Ethereum wallet you control. In order to interact with the 0x portal + dApp, we need a way to interact with one of your Ethereum wallets. There are two easy ways you can + enable us to do that: + </div> + <h4>1. Metamask chrome extension</h4> + <div> + You can install the{' '} + <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> + Metamask + </a>{' '} + Chrome extension Ethereum wallet. Once installed and set up, refresh this page. + <div className="pt1"> + <span className="bold">Note:</span> If you already have Metamask installed, make sure it is + unlocked. + </div> + </div> + <h4>Parity Signer</h4> + <div> + The{' '} + <a href={constants.URL_PARITY_CHROME_STORE} target="_blank"> + Parity Signer Chrome extension + </a>{' '} + lets you connect to a locally running Parity node. Make sure you have started your local Parity node + with {configs.IS_MAINNET_ENABLED && '`parity ui` or'} `parity --chain kovan ui` in order to connect + to {configs.IS_MAINNET_ENABLED ? 'mainnet or Kovan respectively.' : 'Kovan.'} + </div> + <div className="pt2"> + <span className="bold">Note:</span> If you have done one of the above steps and are still seeing + this message, we might still be unable to retrieve an Ethereum address by calling + `web3.eth.accounts`. Make sure you have created at least one Ethereum address. + </div> + </div> + ); + } + private _renderContractsNotDeployedExplanation() { + return ( + <div> + <div> + The 0x smart contracts are not deployed on the Ethereum network you are currently connected to + (network Id: {this.props.networkId}). In order to use the 0x portal dApp, please connect to the{' '} + {constants.TESTNET_NAME} testnet (network Id: {constants.NETWORK_ID_TESTNET}) + {configs.IS_MAINNET_ENABLED + ? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).` + : `.`} + </div> + <h4>Metamask</h4> + <div> + If you are using{' '} + <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> + Metamask + </a>, you can switch networks in the top left corner of the extension popover. + </div> + <h4>Parity Signer</h4> + <div> + If using the{' '} + <a href={constants.URL_PARITY_CHROME_STORE} target="_blank"> + Parity Signer Chrome extension + </a>, make sure to start your local Parity node with{' '} + {configs.IS_MAINNET_ENABLED + ? '`parity ui` or `parity --chain Kovan ui` in order to connect to mainnet \ or Kovan respectively.' - : '`parity --chain kovan ui` in order to connect to Kovan.'} - </div> - </div> - ); - } + : '`parity --chain kovan ui` in order to connect to Kovan.'} + </div> + </div> + ); + } } diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx index 45ba5cc9e..661cc1d8c 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -8,156 +8,156 @@ import { Side, Token, TokenState } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface EthWethConversionDialogProps { - direction: Side; - onComplete: (direction: Side, value: BigNumber) => void; - onCancelled: () => void; - isOpen: boolean; - token: Token; - tokenState: TokenState; - etherBalance: BigNumber; + direction: Side; + onComplete: (direction: Side, value: BigNumber) => void; + onCancelled: () => void; + isOpen: boolean; + token: Token; + tokenState: TokenState; + etherBalance: BigNumber; } interface EthWethConversionDialogState { - value?: BigNumber; - shouldShowIncompleteErrs: boolean; - hasErrors: boolean; + value?: BigNumber; + shouldShowIncompleteErrs: boolean; + hasErrors: boolean; } export class EthWethConversionDialog extends React.Component< - EthWethConversionDialogProps, - EthWethConversionDialogState + EthWethConversionDialogProps, + EthWethConversionDialogState > { - constructor() { - super(); - this.state = { - shouldShowIncompleteErrs: false, - hasErrors: false, - }; - } - public render() { - const convertDialogActions = [ - <FlatButton key="cancel" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, - <FlatButton key="convert" label="Convert" primary={true} onTouchTap={this._onConvertClick.bind(this)} />, - ]; - const title = this.props.direction === Side.Deposit ? 'Wrap ETH' : 'Unwrap WETH'; - return ( - <Dialog - title={title} - titleStyle={{ fontWeight: 100 }} - actions={convertDialogActions} - contentStyle={{ width: 448 }} - open={this.props.isOpen} - > - {this._renderConversionDialogBody()} - </Dialog> - ); - } - private _renderConversionDialogBody() { - const explanation = - this.props.direction === Side.Deposit - ? 'Convert your Ether into a tokenized, tradable form.' - : "Convert your Wrapped Ether back into it's native form."; - const isWrappedVersion = this.props.direction === Side.Receive; - return ( - <div> - <div className="pb2">{explanation}</div> - <div className="mx-auto" style={{ maxWidth: 312 }}> - <div className="flex"> - {this._renderCurrency(isWrappedVersion)} - <div style={{ paddingTop: 68 }}> - <i style={{ fontSize: 28, color: colors.darkBlue }} className="zmdi zmdi-arrow-right" /> - </div> - {this._renderCurrency(!isWrappedVersion)} - </div> - <div className="pt2 mx-auto" style={{ width: 245 }}> - {this.props.direction === Side.Receive ? ( - <TokenAmountInput - token={this.props.token} - tokenState={this.props.tokenState} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - shouldCheckBalance={true} - shouldCheckAllowance={false} - onChange={this._onValueChange.bind(this)} - amount={this.state.value} - onVisitBalancesPageClick={this.props.onCancelled} - /> - ) : ( - <EthAmountInput - balance={this.props.etherBalance} - amount={this.state.value} - onChange={this._onValueChange.bind(this)} - shouldCheckBalance={true} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onCancelled} - /> - )} - <div className="pt1" style={{ fontSize: 12 }}> - <div className="left">1 ETH = 1 WETH</div> - {this.props.direction === Side.Receive && ( - <div - className="right" - onClick={this._onMaxClick.bind(this)} - style={{ - color: colors.darkBlue, - textDecoration: 'underline', - cursor: 'pointer', - }} - > - Max - </div> - )} - </div> - </div> - </div> - </div> - ); - } - private _renderCurrency(isWrappedVersion: boolean) { - const name = isWrappedVersion ? 'Wrapped Ether' : 'Ether'; - const iconUrl = isWrappedVersion ? '/images/token_icons/ether_erc20.png' : '/images/ether.png'; - const symbol = isWrappedVersion ? 'WETH' : 'ETH'; - return ( - <div className="mx-auto pt2"> - <div className="center" style={{ color: colors.darkBlue }}> - {name} - </div> - <div className="center py2"> - <img src={iconUrl} style={{ width: 60 }} /> - </div> - <div className="center" style={{ fontSize: 12 }}> - ({symbol}) - </div> - </div> - ); - } - private _onMaxClick() { - this.setState({ - value: this.props.tokenState.balance, - }); - } - private _onValueChange(isValid: boolean, amount?: BigNumber) { - this.setState({ - value: amount, - hasErrors: !isValid, - }); - } - private _onConvertClick() { - if (this.state.hasErrors) { - this.setState({ - shouldShowIncompleteErrs: true, - }); - } else { - const value = this.state.value; - this.setState({ - value: undefined, - }); - this.props.onComplete(this.props.direction, value); - } - } - private _onCancel() { - this.setState({ - value: undefined, - }); - this.props.onCancelled(); - } + constructor() { + super(); + this.state = { + shouldShowIncompleteErrs: false, + hasErrors: false, + }; + } + public render() { + const convertDialogActions = [ + <FlatButton key="cancel" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, + <FlatButton key="convert" label="Convert" primary={true} onTouchTap={this._onConvertClick.bind(this)} />, + ]; + const title = this.props.direction === Side.Deposit ? 'Wrap ETH' : 'Unwrap WETH'; + return ( + <Dialog + title={title} + titleStyle={{ fontWeight: 100 }} + actions={convertDialogActions} + contentStyle={{ width: 448 }} + open={this.props.isOpen} + > + {this._renderConversionDialogBody()} + </Dialog> + ); + } + private _renderConversionDialogBody() { + const explanation = + this.props.direction === Side.Deposit + ? 'Convert your Ether into a tokenized, tradable form.' + : "Convert your Wrapped Ether back into it's native form."; + const isWrappedVersion = this.props.direction === Side.Receive; + return ( + <div> + <div className="pb2">{explanation}</div> + <div className="mx-auto" style={{ maxWidth: 312 }}> + <div className="flex"> + {this._renderCurrency(isWrappedVersion)} + <div style={{ paddingTop: 68 }}> + <i style={{ fontSize: 28, color: colors.darkBlue }} className="zmdi zmdi-arrow-right" /> + </div> + {this._renderCurrency(!isWrappedVersion)} + </div> + <div className="pt2 mx-auto" style={{ width: 245 }}> + {this.props.direction === Side.Receive ? ( + <TokenAmountInput + token={this.props.token} + tokenState={this.props.tokenState} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + shouldCheckBalance={true} + shouldCheckAllowance={false} + onChange={this._onValueChange.bind(this)} + amount={this.state.value} + onVisitBalancesPageClick={this.props.onCancelled} + /> + ) : ( + <EthAmountInput + balance={this.props.etherBalance} + amount={this.state.value} + onChange={this._onValueChange.bind(this)} + shouldCheckBalance={true} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + onVisitBalancesPageClick={this.props.onCancelled} + /> + )} + <div className="pt1" style={{ fontSize: 12 }}> + <div className="left">1 ETH = 1 WETH</div> + {this.props.direction === Side.Receive && ( + <div + className="right" + onClick={this._onMaxClick.bind(this)} + style={{ + color: colors.darkBlue, + textDecoration: 'underline', + cursor: 'pointer', + }} + > + Max + </div> + )} + </div> + </div> + </div> + </div> + ); + } + private _renderCurrency(isWrappedVersion: boolean) { + const name = isWrappedVersion ? 'Wrapped Ether' : 'Ether'; + const iconUrl = isWrappedVersion ? '/images/token_icons/ether_erc20.png' : '/images/ether.png'; + const symbol = isWrappedVersion ? 'WETH' : 'ETH'; + return ( + <div className="mx-auto pt2"> + <div className="center" style={{ color: colors.darkBlue }}> + {name} + </div> + <div className="center py2"> + <img src={iconUrl} style={{ width: 60 }} /> + </div> + <div className="center" style={{ fontSize: 12 }}> + ({symbol}) + </div> + </div> + ); + } + private _onMaxClick() { + this.setState({ + value: this.props.tokenState.balance, + }); + } + private _onValueChange(isValid: boolean, amount?: BigNumber) { + this.setState({ + value: amount, + hasErrors: !isValid, + }); + } + private _onConvertClick() { + if (this.state.hasErrors) { + this.setState({ + shouldShowIncompleteErrs: true, + }); + } else { + const value = this.state.value; + this.setState({ + value: undefined, + }); + this.props.onComplete(this.props.direction, value); + } + } + private _onCancel() { + this.setState({ + value: undefined, + }); + this.props.onCancelled(); + } } diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 8b7760a1a..60db93c52 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -17,245 +17,245 @@ import { utils } from 'ts/utils/utils'; const VALID_ETHEREUM_DERIVATION_PATH_PREFIX = `44'/60'`; enum LedgerSteps { - CONNECT, - SELECT_ADDRESS, + CONNECT, + SELECT_ADDRESS, } interface LedgerConfigDialogProps { - isOpen: boolean; - toggleDialogFn: (isOpen: boolean) => void; - dispatcher: Dispatcher; - blockchain: Blockchain; - networkId: number; + isOpen: boolean; + toggleDialogFn: (isOpen: boolean) => void; + dispatcher: Dispatcher; + blockchain: Blockchain; + networkId: number; } interface LedgerConfigDialogState { - didConnectFail: boolean; - stepIndex: LedgerSteps; - userAddresses: string[]; - addressBalances: BigNumber[]; - derivationPath: string; - derivationErrMsg: string; + didConnectFail: boolean; + stepIndex: LedgerSteps; + userAddresses: string[]; + addressBalances: BigNumber[]; + derivationPath: string; + derivationErrMsg: string; } export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, LedgerConfigDialogState> { - constructor(props: LedgerConfigDialogProps) { - super(props); - this.state = { - didConnectFail: false, - stepIndex: LedgerSteps.CONNECT, - userAddresses: [], - addressBalances: [], - derivationPath: configs.DEFAULT_DERIVATION_PATH, - derivationErrMsg: '', - }; - } - public render() { - const dialogActions = [ - <FlatButton key="ledgerConnectCancel" label="Cancel" onTouchTap={this._onClose.bind(this)} />, - ]; - const dialogTitle = - this.state.stepIndex === LedgerSteps.CONNECT ? 'Connect to your Ledger' : 'Select desired address'; - return ( - <Dialog - title={dialogTitle} - titleStyle={{ fontWeight: 100 }} - actions={dialogActions} - open={this.props.isOpen} - onRequestClose={this._onClose.bind(this)} - autoScrollBodyContent={true} - bodyStyle={{ paddingBottom: 0 }} - > - <div style={{ color: colors.grey700, paddingTop: 1 }}> - {this.state.stepIndex === LedgerSteps.CONNECT && this._renderConnectStep()} - {this.state.stepIndex === LedgerSteps.SELECT_ADDRESS && this._renderSelectAddressStep()} - </div> - </Dialog> - ); - } - private _renderConnectStep() { - return ( - <div> - <div className="h4 pt3">Follow these instructions before proceeding:</div> - <ol> - <li className="pb1">Connect your Ledger Nano S & Open the Ethereum application</li> - <li className="pb1">Verify that Browser Support is enabled in Settings</li> - <li className="pb1"> - If no Browser Support is found in settings, verify that you have{' '} - <a href="https://www.ledgerwallet.com/apps/manager" target="_blank"> - Firmware >1.2 - </a> - </li> - </ol> - <div className="center pb3"> - <LifeCycleRaisedButton - isPrimary={true} - labelReady="Connect to Ledger" - labelLoading="Connecting..." - labelComplete="Connected!" - onClickAsyncFn={this._onConnectLedgerClickAsync.bind(this, true)} - /> - {this.state.didConnectFail && ( - <div className="pt2 left-align" style={{ color: colors.red200 }}> - Failed to connect. Follow the instructions and try again. - </div> - )} - </div> - </div> - ); - } - private _renderSelectAddressStep() { - return ( - <div> - <div> - <Table bodyStyle={{ height: 300 }} onRowSelection={this._onAddressSelected.bind(this)}> - <TableHeader displaySelectAll={false}> - <TableRow> - <TableHeaderColumn colSpan={2}>Address</TableHeaderColumn> - <TableHeaderColumn>Balance</TableHeaderColumn> - </TableRow> - </TableHeader> - <TableBody>{this._renderAddressTableRows()}</TableBody> - </Table> - </div> - <div className="flex pt2" style={{ height: 100 }}> - <div className="overflow-hidden" style={{ width: 180 }}> - <TextField - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey }} - floatingLabelText="Update path derivation (advanced)" - value={this.state.derivationPath} - errorText={this.state.derivationErrMsg} - onChange={this._onDerivationPathChanged.bind(this)} - /> - </div> - <div className="pl2" style={{ paddingTop: 28 }}> - <LifeCycleRaisedButton - labelReady="Update" - labelLoading="Updating..." - labelComplete="Updated!" - onClickAsyncFn={this._onFetchAddressesForDerivationPathAsync.bind(this)} - /> - </div> - </div> - </div> - ); - } - private _renderAddressTableRows() { - const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => { - const balance = this.state.addressBalances[i]; - const addressTooltipId = `address-${userAddress}`; - const balanceTooltipId = `balance-${userAddress}`; - const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; - // We specifically prefix kovan ETH. - // TODO: We should probably add prefixes for all networks - const isKovanNetwork = networkName === 'Kovan'; - const balanceString = `${balance.toString()} ${isKovanNetwork ? 'Kovan ' : ''}ETH`; - return ( - <TableRow key={userAddress} style={{ height: 40 }}> - <TableRowColumn colSpan={2}> - <div data-tip={true} data-for={addressTooltipId}> - {userAddress} - </div> - <ReactTooltip id={addressTooltipId}>{userAddress}</ReactTooltip> - </TableRowColumn> - <TableRowColumn> - <div data-tip={true} data-for={balanceTooltipId}> - {balanceString} - </div> - <ReactTooltip id={balanceTooltipId}>{balanceString}</ReactTooltip> - </TableRowColumn> - </TableRow> - ); - }); - return rows; - } - private _onClose() { - this.setState({ - didConnectFail: false, - }); - const isOpen = false; - this.props.toggleDialogFn(isOpen); - } - private _onAddressSelected(selectedRowIndexes: number[]) { - const selectedRowIndex = selectedRowIndexes[0]; - this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex); - const selectedAddress = this.state.userAddresses[selectedRowIndex]; - const selectAddressBalance = this.state.addressBalances[selectedRowIndex]; - this.props.dispatcher.updateUserAddress(selectedAddress); - this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress); - this.props.dispatcher.updateUserEtherBalance(selectAddressBalance); - this.setState({ - stepIndex: LedgerSteps.CONNECT, - }); - const isOpen = false; - this.props.toggleDialogFn(isOpen); - } - private async _onFetchAddressesForDerivationPathAsync(): Promise<boolean> { - const currentlySetPath = this.props.blockchain.getLedgerDerivationPathIfExists(); - let didSucceed; - if (currentlySetPath === this.state.derivationPath) { - didSucceed = true; - return didSucceed; - } - this.props.blockchain.updateLedgerDerivationPathIfExists(this.state.derivationPath); - didSucceed = await this._fetchAddressesAndBalancesAsync(); - if (!didSucceed) { - this.setState({ - derivationErrMsg: 'Failed to connect to Ledger.', - }); - } - return didSucceed; - } - private async _fetchAddressesAndBalancesAsync() { - let userAddresses: string[]; - const addressBalances: BigNumber[] = []; - try { - userAddresses = await this._getUserAddressesAsync(); - for (const address of userAddresses) { - const balance = await this.props.blockchain.getBalanceInEthAsync(address); - addressBalances.push(balance); - } - } catch (err) { - utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`); - this.setState({ - didConnectFail: true, - }); - return false; - } - this.setState({ - userAddresses, - addressBalances, - }); - return true; - } - private _onDerivationPathChanged(e: any, derivationPath: string) { - let derivationErrMsg = ''; - if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) { - derivationErrMsg = 'Must be valid Ethereum path.'; - } + constructor(props: LedgerConfigDialogProps) { + super(props); + this.state = { + didConnectFail: false, + stepIndex: LedgerSteps.CONNECT, + userAddresses: [], + addressBalances: [], + derivationPath: configs.DEFAULT_DERIVATION_PATH, + derivationErrMsg: '', + }; + } + public render() { + const dialogActions = [ + <FlatButton key="ledgerConnectCancel" label="Cancel" onTouchTap={this._onClose.bind(this)} />, + ]; + const dialogTitle = + this.state.stepIndex === LedgerSteps.CONNECT ? 'Connect to your Ledger' : 'Select desired address'; + return ( + <Dialog + title={dialogTitle} + titleStyle={{ fontWeight: 100 }} + actions={dialogActions} + open={this.props.isOpen} + onRequestClose={this._onClose.bind(this)} + autoScrollBodyContent={true} + bodyStyle={{ paddingBottom: 0 }} + > + <div style={{ color: colors.grey700, paddingTop: 1 }}> + {this.state.stepIndex === LedgerSteps.CONNECT && this._renderConnectStep()} + {this.state.stepIndex === LedgerSteps.SELECT_ADDRESS && this._renderSelectAddressStep()} + </div> + </Dialog> + ); + } + private _renderConnectStep() { + return ( + <div> + <div className="h4 pt3">Follow these instructions before proceeding:</div> + <ol> + <li className="pb1">Connect your Ledger Nano S & Open the Ethereum application</li> + <li className="pb1">Verify that Browser Support is enabled in Settings</li> + <li className="pb1"> + If no Browser Support is found in settings, verify that you have{' '} + <a href="https://www.ledgerwallet.com/apps/manager" target="_blank"> + Firmware >1.2 + </a> + </li> + </ol> + <div className="center pb3"> + <LifeCycleRaisedButton + isPrimary={true} + labelReady="Connect to Ledger" + labelLoading="Connecting..." + labelComplete="Connected!" + onClickAsyncFn={this._onConnectLedgerClickAsync.bind(this, true)} + /> + {this.state.didConnectFail && ( + <div className="pt2 left-align" style={{ color: colors.red200 }}> + Failed to connect. Follow the instructions and try again. + </div> + )} + </div> + </div> + ); + } + private _renderSelectAddressStep() { + return ( + <div> + <div> + <Table bodyStyle={{ height: 300 }} onRowSelection={this._onAddressSelected.bind(this)}> + <TableHeader displaySelectAll={false}> + <TableRow> + <TableHeaderColumn colSpan={2}>Address</TableHeaderColumn> + <TableHeaderColumn>Balance</TableHeaderColumn> + </TableRow> + </TableHeader> + <TableBody>{this._renderAddressTableRows()}</TableBody> + </Table> + </div> + <div className="flex pt2" style={{ height: 100 }}> + <div className="overflow-hidden" style={{ width: 180 }}> + <TextField + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey }} + floatingLabelText="Update path derivation (advanced)" + value={this.state.derivationPath} + errorText={this.state.derivationErrMsg} + onChange={this._onDerivationPathChanged.bind(this)} + /> + </div> + <div className="pl2" style={{ paddingTop: 28 }}> + <LifeCycleRaisedButton + labelReady="Update" + labelLoading="Updating..." + labelComplete="Updated!" + onClickAsyncFn={this._onFetchAddressesForDerivationPathAsync.bind(this)} + /> + </div> + </div> + </div> + ); + } + private _renderAddressTableRows() { + const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => { + const balance = this.state.addressBalances[i]; + const addressTooltipId = `address-${userAddress}`; + const balanceTooltipId = `balance-${userAddress}`; + const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; + // We specifically prefix kovan ETH. + // TODO: We should probably add prefixes for all networks + const isKovanNetwork = networkName === 'Kovan'; + const balanceString = `${balance.toString()} ${isKovanNetwork ? 'Kovan ' : ''}ETH`; + return ( + <TableRow key={userAddress} style={{ height: 40 }}> + <TableRowColumn colSpan={2}> + <div data-tip={true} data-for={addressTooltipId}> + {userAddress} + </div> + <ReactTooltip id={addressTooltipId}>{userAddress}</ReactTooltip> + </TableRowColumn> + <TableRowColumn> + <div data-tip={true} data-for={balanceTooltipId}> + {balanceString} + </div> + <ReactTooltip id={balanceTooltipId}>{balanceString}</ReactTooltip> + </TableRowColumn> + </TableRow> + ); + }); + return rows; + } + private _onClose() { + this.setState({ + didConnectFail: false, + }); + const isOpen = false; + this.props.toggleDialogFn(isOpen); + } + private _onAddressSelected(selectedRowIndexes: number[]) { + const selectedRowIndex = selectedRowIndexes[0]; + this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex); + const selectedAddress = this.state.userAddresses[selectedRowIndex]; + const selectAddressBalance = this.state.addressBalances[selectedRowIndex]; + this.props.dispatcher.updateUserAddress(selectedAddress); + this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress); + this.props.dispatcher.updateUserEtherBalance(selectAddressBalance); + this.setState({ + stepIndex: LedgerSteps.CONNECT, + }); + const isOpen = false; + this.props.toggleDialogFn(isOpen); + } + private async _onFetchAddressesForDerivationPathAsync(): Promise<boolean> { + const currentlySetPath = this.props.blockchain.getLedgerDerivationPathIfExists(); + let didSucceed; + if (currentlySetPath === this.state.derivationPath) { + didSucceed = true; + return didSucceed; + } + this.props.blockchain.updateLedgerDerivationPathIfExists(this.state.derivationPath); + didSucceed = await this._fetchAddressesAndBalancesAsync(); + if (!didSucceed) { + this.setState({ + derivationErrMsg: 'Failed to connect to Ledger.', + }); + } + return didSucceed; + } + private async _fetchAddressesAndBalancesAsync() { + let userAddresses: string[]; + const addressBalances: BigNumber[] = []; + try { + userAddresses = await this._getUserAddressesAsync(); + for (const address of userAddresses) { + const balance = await this.props.blockchain.getBalanceInEthAsync(address); + addressBalances.push(balance); + } + } catch (err) { + utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`); + this.setState({ + didConnectFail: true, + }); + return false; + } + this.setState({ + userAddresses, + addressBalances, + }); + return true; + } + private _onDerivationPathChanged(e: any, derivationPath: string) { + let derivationErrMsg = ''; + if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) { + derivationErrMsg = 'Must be valid Ethereum path.'; + } - this.setState({ - derivationPath, - derivationErrMsg, - }); - } - private async _onConnectLedgerClickAsync() { - const didSucceed = await this._fetchAddressesAndBalancesAsync(); - if (didSucceed) { - this.setState({ - stepIndex: LedgerSteps.SELECT_ADDRESS, - }); - } - return didSucceed; - } - private async _getUserAddressesAsync(): Promise<string[]> { - let userAddresses: string[]; - userAddresses = await this.props.blockchain.getUserAccountsAsync(); + this.setState({ + derivationPath, + derivationErrMsg, + }); + } + private async _onConnectLedgerClickAsync() { + const didSucceed = await this._fetchAddressesAndBalancesAsync(); + if (didSucceed) { + this.setState({ + stepIndex: LedgerSteps.SELECT_ADDRESS, + }); + } + return didSucceed; + } + private async _getUserAddressesAsync(): Promise<string[]> { + let userAddresses: string[]; + userAddresses = await this.props.blockchain.getUserAccountsAsync(); - if (_.isEmpty(userAddresses)) { - throw new Error('No addresses retrieved.'); - } - return userAddresses; - } + if (_.isEmpty(userAddresses)) { + throw new Error('No addresses retrieved.'); + } + return userAddresses; + } } diff --git a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx index 1c5efc978..3ecc454a0 100644 --- a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx +++ b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx @@ -4,33 +4,33 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; interface PortalDisclaimerDialogProps { - isOpen: boolean; - onToggleDialog: () => void; + isOpen: boolean; + onToggleDialog: () => void; } export function PortalDisclaimerDialog(props: PortalDisclaimerDialogProps) { - return ( - <Dialog - title="0x Portal Disclaimer" - titleStyle={{ fontWeight: 100 }} - actions={[<FlatButton key="portalAgree" label="I Agree" onTouchTap={props.onToggleDialog} />]} - open={props.isOpen} - onRequestClose={props.onToggleDialog} - autoScrollBodyContent={true} - modal={true} - > - <div className="pt2" style={{ color: colors.grey700 }}> - <div> - 0x Portal is a free software-based tool intended to help users to buy and sell ERC20-compatible - blockchain tokens through the 0x protocol on a purely peer-to-peer basis. 0x portal is not a - regulated marketplace, exchange or intermediary of any kind, and therefore, you should only use 0x - portal to exchange tokens that are not securities, commodity interests, or any other form of - regulated instrument. 0x has not attempted to screen or otherwise limit the tokens that you may - enter in 0x Portal. By clicking “I Agree” below, you understand that you are solely responsible for - using 0x Portal and buying and selling tokens using 0x Portal in compliance with all applicable laws - and regulations. - </div> - </div> - </Dialog> - ); + return ( + <Dialog + title="0x Portal Disclaimer" + titleStyle={{ fontWeight: 100 }} + actions={[<FlatButton key="portalAgree" label="I Agree" onTouchTap={props.onToggleDialog} />]} + open={props.isOpen} + onRequestClose={props.onToggleDialog} + autoScrollBodyContent={true} + modal={true} + > + <div className="pt2" style={{ color: colors.grey700 }}> + <div> + 0x Portal is a free software-based tool intended to help users to buy and sell ERC20-compatible + blockchain tokens through the 0x protocol on a purely peer-to-peer basis. 0x portal is not a + regulated marketplace, exchange or intermediary of any kind, and therefore, you should only use 0x + portal to exchange tokens that are not securities, commodity interests, or any other form of + regulated instrument. 0x has not attempted to screen or otherwise limit the tokens that you may + enter in 0x Portal. By clicking “I Agree” below, you understand that you are solely responsible for + using 0x Portal and buying and selling tokens using 0x Portal in compliance with all applicable laws + and regulations. + </div> + </div> + </Dialog> + ); } diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx index b9022cd9b..b3dbce598 100644 --- a/packages/website/ts/components/dialogs/send_dialog.tsx +++ b/packages/website/ts/components/dialogs/send_dialog.tsx @@ -8,110 +8,110 @@ import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; import { Token, TokenState } from 'ts/types'; interface SendDialogProps { - onComplete: (recipient: string, value: BigNumber) => void; - onCancelled: () => void; - isOpen: boolean; - token: Token; - tokenState: TokenState; + onComplete: (recipient: string, value: BigNumber) => void; + onCancelled: () => void; + isOpen: boolean; + token: Token; + tokenState: TokenState; } interface SendDialogState { - value?: BigNumber; - recipient: string; - shouldShowIncompleteErrs: boolean; - isAmountValid: boolean; + value?: BigNumber; + recipient: string; + shouldShowIncompleteErrs: boolean; + isAmountValid: boolean; } export class SendDialog extends React.Component<SendDialogProps, SendDialogState> { - constructor() { - super(); - this.state = { - recipient: '', - shouldShowIncompleteErrs: false, - isAmountValid: false, - }; - } - public render() { - const transferDialogActions = [ - <FlatButton key="cancelTransfer" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, - <FlatButton - key="sendTransfer" - disabled={this._hasErrors()} - label="Send" - primary={true} - onTouchTap={this._onSendClick.bind(this)} - />, - ]; - return ( - <Dialog - title="I want to send" - titleStyle={{ fontWeight: 100 }} - actions={transferDialogActions} - open={this.props.isOpen} - > - {this._renderSendDialogBody()} - </Dialog> - ); - } - private _renderSendDialogBody() { - return ( - <div className="mx-auto" style={{ maxWidth: 300 }}> - <div style={{ height: 80 }}> - <AddressInput - initialAddress={this.state.recipient} - updateAddress={this._onRecipientChange.bind(this)} - isRequired={true} - label={'Recipient address'} - hintText={'Address'} - /> - </div> - <TokenAmountInput - label="Amount to send" - token={this.props.token} - tokenState={this.props.tokenState} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - shouldCheckBalance={true} - shouldCheckAllowance={false} - onChange={this._onValueChange.bind(this)} - amount={this.state.value} - onVisitBalancesPageClick={this.props.onCancelled} - /> - </div> - ); - } - private _onRecipientChange(recipient?: string) { - this.setState({ - shouldShowIncompleteErrs: false, - recipient, - }); - } - private _onValueChange(isValid: boolean, amount?: BigNumber) { - this.setState({ - isAmountValid: isValid, - value: amount, - }); - } - private _onSendClick() { - if (this._hasErrors()) { - this.setState({ - shouldShowIncompleteErrs: true, - }); - } else { - const value = this.state.value; - this.setState({ - recipient: undefined, - value: undefined, - }); - this.props.onComplete(this.state.recipient, value); - } - } - private _onCancel() { - this.setState({ - value: undefined, - }); - this.props.onCancelled(); - } - private _hasErrors() { - return _.isUndefined(this.state.recipient) || _.isUndefined(this.state.value) || !this.state.isAmountValid; - } + constructor() { + super(); + this.state = { + recipient: '', + shouldShowIncompleteErrs: false, + isAmountValid: false, + }; + } + public render() { + const transferDialogActions = [ + <FlatButton key="cancelTransfer" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, + <FlatButton + key="sendTransfer" + disabled={this._hasErrors()} + label="Send" + primary={true} + onTouchTap={this._onSendClick.bind(this)} + />, + ]; + return ( + <Dialog + title="I want to send" + titleStyle={{ fontWeight: 100 }} + actions={transferDialogActions} + open={this.props.isOpen} + > + {this._renderSendDialogBody()} + </Dialog> + ); + } + private _renderSendDialogBody() { + return ( + <div className="mx-auto" style={{ maxWidth: 300 }}> + <div style={{ height: 80 }}> + <AddressInput + initialAddress={this.state.recipient} + updateAddress={this._onRecipientChange.bind(this)} + isRequired={true} + label={'Recipient address'} + hintText={'Address'} + /> + </div> + <TokenAmountInput + label="Amount to send" + token={this.props.token} + tokenState={this.props.tokenState} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + shouldCheckBalance={true} + shouldCheckAllowance={false} + onChange={this._onValueChange.bind(this)} + amount={this.state.value} + onVisitBalancesPageClick={this.props.onCancelled} + /> + </div> + ); + } + private _onRecipientChange(recipient?: string) { + this.setState({ + shouldShowIncompleteErrs: false, + recipient, + }); + } + private _onValueChange(isValid: boolean, amount?: BigNumber) { + this.setState({ + isAmountValid: isValid, + value: amount, + }); + } + private _onSendClick() { + if (this._hasErrors()) { + this.setState({ + shouldShowIncompleteErrs: true, + }); + } else { + const value = this.state.value; + this.setState({ + recipient: undefined, + value: undefined, + }); + this.props.onComplete(this.state.recipient, value); + } + } + private _onCancel() { + this.setState({ + value: undefined, + }); + this.props.onCancelled(); + } + private _hasErrors() { + return _.isUndefined(this.state.recipient) || _.isUndefined(this.state.value) || !this.state.isAmountValid; + } } diff --git a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx index b1804e95c..3f29d46f8 100644 --- a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx +++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx @@ -9,94 +9,94 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { Token, TokenByAddress } from 'ts/types'; interface TrackTokenConfirmationDialogProps { - tokens: Token[]; - tokenByAddress: TokenByAddress; - isOpen: boolean; - onToggleDialog: (didConfirmTokenTracking: boolean) => void; - dispatcher: Dispatcher; - networkId: number; - blockchain: Blockchain; - userAddress: string; + tokens: Token[]; + tokenByAddress: TokenByAddress; + isOpen: boolean; + onToggleDialog: (didConfirmTokenTracking: boolean) => void; + dispatcher: Dispatcher; + networkId: number; + blockchain: Blockchain; + userAddress: string; } interface TrackTokenConfirmationDialogState { - isAddingTokenToTracked: boolean; + isAddingTokenToTracked: boolean; } export class TrackTokenConfirmationDialog extends React.Component< - TrackTokenConfirmationDialogProps, - TrackTokenConfirmationDialogState + TrackTokenConfirmationDialogProps, + TrackTokenConfirmationDialogState > { - constructor(props: TrackTokenConfirmationDialogProps) { - super(props); - this.state = { - isAddingTokenToTracked: false, - }; - } - public render() { - const tokens = this.props.tokens; - return ( - <Dialog - title="Tracking confirmation" - titleStyle={{ fontWeight: 100 }} - actions={[ - <FlatButton - key="trackNo" - label="No" - onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)} - />, - <FlatButton - key="trackYes" - label="Yes" - onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)} - />, - ]} - open={this.props.isOpen} - onRequestClose={this.props.onToggleDialog.bind(this, false)} - autoScrollBodyContent={true} - > - <div className="pt2"> - <TrackTokenConfirmation - tokens={tokens} - networkId={this.props.networkId} - tokenByAddress={this.props.tokenByAddress} - isAddingTokenToTracked={this.state.isAddingTokenToTracked} - /> - </div> - </Dialog> - ); - } - private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { - if (!didUserAcceptTracking) { - this.props.onToggleDialog(didUserAcceptTracking); - return; - } - this.setState({ - isAddingTokenToTracked: true, - }); - for (const token of this.props.tokens) { - const newTokenEntry = { - ...token, - }; + constructor(props: TrackTokenConfirmationDialogProps) { + super(props); + this.state = { + isAddingTokenToTracked: false, + }; + } + public render() { + const tokens = this.props.tokens; + return ( + <Dialog + title="Tracking confirmation" + titleStyle={{ fontWeight: 100 }} + actions={[ + <FlatButton + key="trackNo" + label="No" + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)} + />, + <FlatButton + key="trackYes" + label="Yes" + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)} + />, + ]} + open={this.props.isOpen} + onRequestClose={this.props.onToggleDialog.bind(this, false)} + autoScrollBodyContent={true} + > + <div className="pt2"> + <TrackTokenConfirmation + tokens={tokens} + networkId={this.props.networkId} + tokenByAddress={this.props.tokenByAddress} + isAddingTokenToTracked={this.state.isAddingTokenToTracked} + /> + </div> + </Dialog> + ); + } + private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + if (!didUserAcceptTracking) { + this.props.onToggleDialog(didUserAcceptTracking); + return; + } + this.setState({ + isAddingTokenToTracked: true, + }); + for (const token of this.props.tokens) { + const newTokenEntry = { + ...token, + }; - newTokenEntry.isTracked = true; - trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); - this.props.dispatcher.updateTokenByAddress([newTokenEntry]); + newTokenEntry.isTracked = true; + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); + this.props.dispatcher.updateTokenByAddress([newTokenEntry]); - const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( - token.address, - ); - this.props.dispatcher.updateTokenStateByAddress({ - [token.address]: { - balance, - allowance, - }, - }); - } + const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( + token.address, + ); + this.props.dispatcher.updateTokenStateByAddress({ + [token.address]: { + balance, + allowance, + }, + }); + } - this.setState({ - isAddingTokenToTracked: false, - }); - this.props.onToggleDialog(didUserAcceptTracking); - } + this.setState({ + isAddingTokenToTracked: false, + }); + this.props.onToggleDialog(didUserAcceptTracking); + } } diff --git a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx index 2ea51d07b..098e3e26d 100644 --- a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx +++ b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx @@ -5,42 +5,42 @@ import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; interface U2fNotSupportedDialogProps { - isOpen: boolean; - onToggleDialog: () => void; + isOpen: boolean; + onToggleDialog: () => void; } export function U2fNotSupportedDialog(props: U2fNotSupportedDialogProps) { - return ( - <Dialog - title="U2F Not Supported" - titleStyle={{ fontWeight: 100 }} - actions={[<FlatButton key="u2fNo" label="Ok" onTouchTap={props.onToggleDialog.bind(this)} />]} - open={props.isOpen} - onRequestClose={props.onToggleDialog.bind(this)} - autoScrollBodyContent={true} - > - <div className="pt2" style={{ color: colors.grey700 }}> - <div> - It looks like your browser does not support U2F connections required for us to communicate with your - hardware wallet. Please use a browser that supports U2F connections and try again. - </div> - <div> - <ul> - <li className="pb1">Chrome version 38 or later</li> - <li className="pb1">Opera version 40 of later</li> - <li> - Firefox with{' '} - <a - href={constants.URL_FIREFOX_U2F_ADDON} - target="_blank" - style={{ textDecoration: 'underline' }} - > - this extension - </a>. - </li> - </ul> - </div> - </div> - </Dialog> - ); + return ( + <Dialog + title="U2F Not Supported" + titleStyle={{ fontWeight: 100 }} + actions={[<FlatButton key="u2fNo" label="Ok" onTouchTap={props.onToggleDialog.bind(this)} />]} + open={props.isOpen} + onRequestClose={props.onToggleDialog.bind(this)} + autoScrollBodyContent={true} + > + <div className="pt2" style={{ color: colors.grey700 }}> + <div> + It looks like your browser does not support U2F connections required for us to communicate with your + hardware wallet. Please use a browser that supports U2F connections and try again. + </div> + <div> + <ul> + <li className="pb1">Chrome version 38 or later</li> + <li className="pb1">Opera version 40 of later</li> + <li> + Firefox with{' '} + <a + href={constants.URL_FIREFOX_U2F_ADDON} + target="_blank" + style={{ textDecoration: 'underline' }} + > + this extension + </a>. + </li> + </ul> + </div> + </div> + </Dialog> + ); } diff --git a/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx b/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx index 98436eb50..9e91ff12d 100644 --- a/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx +++ b/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx @@ -4,30 +4,30 @@ import { colors } from 'material-ui/styles'; import * as React from 'react'; interface WrappedEthSectionNoticeDialogProps { - isOpen: boolean; - onToggleDialog: () => void; + isOpen: boolean; + onToggleDialog: () => void; } export function WrappedEthSectionNoticeDialog(props: WrappedEthSectionNoticeDialogProps) { - return ( - <Dialog - title="Dedicated Wrapped Ether Section" - titleStyle={{ fontWeight: 100 }} - actions={[ - <FlatButton key="acknowledgeWrapEthSection" label="Sounds good" onTouchTap={props.onToggleDialog} />, - ]} - open={props.isOpen} - onRequestClose={props.onToggleDialog} - autoScrollBodyContent={true} - modal={true} - > - <div className="pt2" style={{ color: colors.grey700 }}> - <div> - We have recently updated the Wrapped Ether token (WETH) used by 0x Portal. Don't worry, unwrapping - Ether tied to the old Wrapped Ether token can be done at any time by clicking on the "Wrap ETH" - section in the menu to the left. - </div> - </div> - </Dialog> - ); + return ( + <Dialog + title="Dedicated Wrapped Ether Section" + titleStyle={{ fontWeight: 100 }} + actions={[ + <FlatButton key="acknowledgeWrapEthSection" label="Sounds good" onTouchTap={props.onToggleDialog} />, + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog} + autoScrollBodyContent={true} + modal={true} + > + <div className="pt2" style={{ color: colors.grey700 }}> + <div> + We have recently updated the Wrapped Ether token (WETH) used by 0x Portal. Don't worry, unwrapping + Ether tied to the old Wrapped Ether token can be done at any time by clicking on the "Wrap ETH" + section in the menu to the left. + </div> + </div> + </Dialog> + ); } diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx index af1b33eef..300e71f1f 100644 --- a/packages/website/ts/components/eth_weth_conversion_button.tsx +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -12,115 +12,115 @@ import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface EthWethConversionButtonProps { - direction: Side; - ethToken: Token; - ethTokenState: TokenState; - dispatcher: Dispatcher; - blockchain: Blockchain; - userEtherBalance: BigNumber; - isOutdatedWrappedEther: boolean; - onConversionSuccessful?: () => void; - isDisabled?: boolean; + direction: Side; + ethToken: Token; + ethTokenState: TokenState; + dispatcher: Dispatcher; + blockchain: Blockchain; + userEtherBalance: BigNumber; + isOutdatedWrappedEther: boolean; + onConversionSuccessful?: () => void; + isDisabled?: boolean; } interface EthWethConversionButtonState { - isEthConversionDialogVisible: boolean; - isEthConversionHappening: boolean; + isEthConversionDialogVisible: boolean; + isEthConversionHappening: boolean; } export class EthWethConversionButton extends React.Component< - EthWethConversionButtonProps, - EthWethConversionButtonState + EthWethConversionButtonProps, + EthWethConversionButtonState > { - public static defaultProps: Partial<EthWethConversionButtonProps> = { - isDisabled: false, - onConversionSuccessful: _.noop, - }; - public constructor(props: EthWethConversionButtonProps) { - super(props); - this.state = { - isEthConversionDialogVisible: false, - isEthConversionHappening: false, - }; - } - public render() { - const labelStyle = this.state.isEthConversionHappening ? { fontSize: 10 } : {}; - let callToActionLabel; - let inProgressLabel; - if (this.props.direction === Side.Deposit) { - callToActionLabel = 'Wrap'; - inProgressLabel = 'Wrapping...'; - } else { - callToActionLabel = 'Unwrap'; - inProgressLabel = 'Unwrapping...'; - } - return ( - <div> - <RaisedButton - style={{ width: '100%' }} - labelStyle={labelStyle} - disabled={this.props.isDisabled || this.state.isEthConversionHappening} - label={this.state.isEthConversionHappening ? inProgressLabel : callToActionLabel} - onClick={this._toggleConversionDialog.bind(this)} - /> - <EthWethConversionDialog - direction={this.props.direction} - isOpen={this.state.isEthConversionDialogVisible} - onComplete={this._onConversionAmountSelectedAsync.bind(this)} - onCancelled={this._toggleConversionDialog.bind(this)} - etherBalance={this.props.userEtherBalance} - token={this.props.ethToken} - tokenState={this.props.ethTokenState} - /> - </div> - ); - } - private _toggleConversionDialog() { - this.setState({ - isEthConversionDialogVisible: !this.state.isEthConversionDialogVisible, - }); - } - private async _onConversionAmountSelectedAsync(direction: Side, value: BigNumber) { - this.setState({ - isEthConversionHappening: true, - }); - this._toggleConversionDialog(); - const token = this.props.ethToken; - const tokenState = this.props.ethTokenState; - let balance = tokenState.balance; - try { - if (direction === Side.Deposit) { - await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value); - const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH); - this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`); - balance = balance.plus(value); - } else { - await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value); - const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals); - this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`); - balance = balance.minus(value); - } - if (!this.props.isOutdatedWrappedEther) { - this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); - } - this.props.onConversionSuccessful(); - } catch (err) { - const errMsg = `${err}`; - if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - } else if (!_.includes(errMsg, 'User denied transaction')) { - utils.consoleLog(`Unexpected error encountered: ${err}`); - utils.consoleLog(err.stack); - const errorMsg = - direction === Side.Deposit - ? 'Failed to wrap your ETH. Please try again.' - : 'Failed to unwrap your WETH. Please try again.'; - this.props.dispatcher.showFlashMessage(errorMsg); - await errorReporter.reportAsync(err); - } - } - this.setState({ - isEthConversionHappening: false, - }); - } + public static defaultProps: Partial<EthWethConversionButtonProps> = { + isDisabled: false, + onConversionSuccessful: _.noop, + }; + public constructor(props: EthWethConversionButtonProps) { + super(props); + this.state = { + isEthConversionDialogVisible: false, + isEthConversionHappening: false, + }; + } + public render() { + const labelStyle = this.state.isEthConversionHappening ? { fontSize: 10 } : {}; + let callToActionLabel; + let inProgressLabel; + if (this.props.direction === Side.Deposit) { + callToActionLabel = 'Wrap'; + inProgressLabel = 'Wrapping...'; + } else { + callToActionLabel = 'Unwrap'; + inProgressLabel = 'Unwrapping...'; + } + return ( + <div> + <RaisedButton + style={{ width: '100%' }} + labelStyle={labelStyle} + disabled={this.props.isDisabled || this.state.isEthConversionHappening} + label={this.state.isEthConversionHappening ? inProgressLabel : callToActionLabel} + onClick={this._toggleConversionDialog.bind(this)} + /> + <EthWethConversionDialog + direction={this.props.direction} + isOpen={this.state.isEthConversionDialogVisible} + onComplete={this._onConversionAmountSelectedAsync.bind(this)} + onCancelled={this._toggleConversionDialog.bind(this)} + etherBalance={this.props.userEtherBalance} + token={this.props.ethToken} + tokenState={this.props.ethTokenState} + /> + </div> + ); + } + private _toggleConversionDialog() { + this.setState({ + isEthConversionDialogVisible: !this.state.isEthConversionDialogVisible, + }); + } + private async _onConversionAmountSelectedAsync(direction: Side, value: BigNumber) { + this.setState({ + isEthConversionHappening: true, + }); + this._toggleConversionDialog(); + const token = this.props.ethToken; + const tokenState = this.props.ethTokenState; + let balance = tokenState.balance; + try { + if (direction === Side.Deposit) { + await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value); + const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH); + this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`); + balance = balance.plus(value); + } else { + await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value); + const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals); + this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`); + balance = balance.minus(value); + } + if (!this.props.isOutdatedWrappedEther) { + this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + } + this.props.onConversionSuccessful(); + } catch (err) { + const errMsg = `${err}`; + if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } else if (!_.includes(errMsg, 'User denied transaction')) { + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + const errorMsg = + direction === Side.Deposit + ? 'Failed to wrap your ETH. Please try again.' + : 'Failed to unwrap your WETH. Please try again.'; + this.props.dispatcher.showFlashMessage(errorMsg); + await errorReporter.reportAsync(err); + } + } + this.setState({ + isEthConversionHappening: false, + }); + } } diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index 1593d51f0..d074ec787 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -10,13 +10,13 @@ import { Blockchain } from 'ts/blockchain'; import { EthWethConversionButton } from 'ts/components/eth_weth_conversion_button'; import { Dispatcher } from 'ts/redux/dispatcher'; import { - EtherscanLinkSuffixes, - OutdatedWrappedEtherByNetworkId, - Side, - Token, - TokenByAddress, - TokenState, - TokenStateByAddress, + EtherscanLinkSuffixes, + OutdatedWrappedEtherByNetworkId, + Side, + Token, + TokenByAddress, + TokenState, + TokenStateByAddress, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; @@ -30,345 +30,345 @@ const ETHER_ICON_PATH = '/images/ether.png'; const OUTDATED_WETH_ICON_PATH = '/images/wrapped_eth_gray.png'; interface OutdatedWETHAddressToIsStateLoaded { - [address: string]: boolean; + [address: string]: boolean; } interface OutdatedWETHStateByAddress { - [address: string]: TokenState; + [address: string]: TokenState; } interface EthWrappersProps { - networkId: number; - blockchain: Blockchain; - dispatcher: Dispatcher; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - userAddress: string; - userEtherBalance: BigNumber; + networkId: number; + blockchain: Blockchain; + dispatcher: Dispatcher; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userAddress: string; + userEtherBalance: BigNumber; } interface EthWrappersState { - outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded; - outdatedWETHStateByAddress: OutdatedWETHStateByAddress; + outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded; + outdatedWETHStateByAddress: OutdatedWETHStateByAddress; } export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersState> { - constructor(props: EthWrappersProps) { - super(props); - const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); - const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; - const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; - _.each(outdatedWETHAddresses, outdatedWETHAddress => { - outdatedWETHAddressToIsStateLoaded[outdatedWETHAddress] = false; - outdatedWETHStateByAddress[outdatedWETHAddress] = { - balance: new BigNumber(0), - allowance: new BigNumber(0), - }; - }); - this.state = { - outdatedWETHAddressToIsStateLoaded, - outdatedWETHStateByAddress, - }; - } - public componentDidMount() { - window.scrollTo(0, 0); - // tslint:disable-next-line:no-floating-promises - this._fetchOutdatedWETHStateAsync(); - } - public render() { - const tokens = _.values(this.props.tokenByAddress); - const etherToken = _.find(tokens, { symbol: 'WETH' }); - const etherTokenState = this.props.tokenStateByAddress[etherToken.address]; - const wethBalance = ZeroEx.toUnitAmount(etherTokenState.balance, constants.DECIMAL_PLACES_ETH); - const isBidirectional = true; - const etherscanUrl = utils.getEtherScanLinkIfExists( - etherToken.address, - this.props.networkId, - EtherscanLinkSuffixes.Address, - ); - const tokenLabel = this._renderToken('Wrapped Ether', etherToken.address, configs.ICON_URL_BY_SYMBOL.WETH); - return ( - <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> - <div className="relative"> - <h3>ETH Wrapper</h3> - <div className="absolute" style={{ top: 0, right: 0 }}> - <a target="_blank" href={constants.URL_WETH_IO} style={{ color: colors.grey }}> - <div className="flex"> - <div>About Wrapped ETH</div> - <div className="pl1"> - <i className="zmdi zmdi-open-in-new" /> - </div> - </div> - </a> - </div> - </div> - <Divider /> - <div> - <div className="py2">Wrap ETH into an ERC20-compliant Ether token. 1 ETH = 1 WETH.</div> - <div> - <Table selectable={false} style={{ backgroundColor: colors.grey50 }}> - <TableHeader displaySelectAll={false} adjustForCheckbox={false}> - <TableRow> - <TableHeaderColumn>ETH Token</TableHeaderColumn> - <TableHeaderColumn>Balance</TableHeaderColumn> - <TableHeaderColumn className="center"> - {this._renderActionColumnTitle(isBidirectional)} - </TableHeaderColumn> - </TableRow> - </TableHeader> - <TableBody displayRowCheckbox={false}> - <TableRow key="ETH"> - <TableRowColumn className="py1"> - <div className="flex"> - <img - style={{ - width: ICON_DIMENSION, - height: ICON_DIMENSION, - }} - src={ETHER_ICON_PATH} - /> - <div className="ml2 sm-hide xs-hide" style={{ marginTop: 12 }}> - ETH - </div> - </div> - </TableRowColumn> - <TableRowColumn> - {this.props.userEtherBalance.toFixed(PRECISION)} ETH - </TableRowColumn> - <TableRowColumn> - <EthWethConversionButton - isOutdatedWrappedEther={false} - direction={Side.Deposit} - ethToken={etherToken} - ethTokenState={etherTokenState} - dispatcher={this.props.dispatcher} - blockchain={this.props.blockchain} - userEtherBalance={this.props.userEtherBalance} - /> - </TableRowColumn> - </TableRow> - <TableRow key="WETH"> - <TableRowColumn className="py1"> - {this._renderTokenLink(tokenLabel, etherscanUrl)} - </TableRowColumn> - <TableRowColumn>{wethBalance.toFixed(PRECISION)} WETH</TableRowColumn> - <TableRowColumn> - <EthWethConversionButton - isOutdatedWrappedEther={false} - direction={Side.Receive} - ethToken={etherToken} - ethTokenState={etherTokenState} - dispatcher={this.props.dispatcher} - blockchain={this.props.blockchain} - userEtherBalance={this.props.userEtherBalance} - /> - </TableRowColumn> - </TableRow> - </TableBody> - </Table> - </div> - </div> - <div> - <h4>Outdated WETH</h4> - <Divider /> - <div className="pt2" style={{ lineHeight: 1.5 }}> - The{' '} - <a href="https://blog.0xproject.com/canonical-weth-a9aa7d0279dd" target="_blank"> - canonical WETH - </a>{' '} - contract is updated when necessary. Unwrap outdated WETH in order to
retrieve your ETH and move - it to the updated WETH token. - </div> - <div> - <Table selectable={false} style={{ backgroundColor: colors.grey50 }}> - <TableHeader displaySelectAll={false} adjustForCheckbox={false}> - <TableRow> - <TableHeaderColumn>WETH Version</TableHeaderColumn> - <TableHeaderColumn>Balance</TableHeaderColumn> - <TableHeaderColumn className="center"> - {this._renderActionColumnTitle(!isBidirectional)} - </TableHeaderColumn> - </TableRow> - </TableHeader> - <TableBody displayRowCheckbox={false}> - {this._renderOutdatedWeths(etherToken, etherTokenState)} - </TableBody> - </Table> - </div> - </div> - </div> - ); - } - private _renderActionColumnTitle(isBidirectional: boolean) { - let iconClass = 'zmdi-long-arrow-right'; - let leftSymbol = 'WETH'; - let rightSymbol = 'ETH'; - if (isBidirectional) { - iconClass = 'zmdi-swap'; - leftSymbol = 'ETH'; - rightSymbol = 'WETH'; - } - return ( - <div className="flex mx-auto" style={{ width: 85 }}> - <div style={{ paddingTop: 3 }}>{leftSymbol}</div> - <div className="px1"> - <i style={{ fontSize: 18 }} className={`zmdi ${iconClass}`} /> - </div> - <div style={{ paddingTop: 3 }}>{rightSymbol}</div> - </div> - ); - } - private _renderOutdatedWeths(etherToken: Token, etherTokenState: TokenState) { - const rows = _.map( - configs.OUTDATED_WRAPPED_ETHERS, - (outdatedWETHByNetworkId: OutdatedWrappedEtherByNetworkId) => { - const outdatedWETHIfExists = outdatedWETHByNetworkId[this.props.networkId]; - if (_.isUndefined(outdatedWETHIfExists)) { - return null; // noop - } - const timestampMsRange = outdatedWETHIfExists.timestampMsRange; - let dateRange: string; - if (!_.isUndefined(timestampMsRange)) { - const startMoment = moment(timestampMsRange.startTimestampMs); - const endMoment = moment(timestampMsRange.endTimestampMs); - dateRange = `${startMoment.format(DATE_FORMAT)}-${endMoment.format(DATE_FORMAT)}`; - } else { - dateRange = '-'; - } - const outdatedEtherToken = { - ...etherToken, - address: outdatedWETHIfExists.address, - }; - const isStateLoaded = this.state.outdatedWETHAddressToIsStateLoaded[outdatedWETHIfExists.address]; - const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address]; - const balanceInEthIfExists = isStateLoaded - ? ZeroEx.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed( - PRECISION, - ) - : undefined; - const onConversionSuccessful = this._onOutdatedConversionSuccessfulAsync.bind( - this, - outdatedWETHIfExists.address, - ); - const etherscanUrl = utils.getEtherScanLinkIfExists( - outdatedWETHIfExists.address, - this.props.networkId, - EtherscanLinkSuffixes.Address, - ); - const tokenLabel = this._renderToken(dateRange, outdatedEtherToken.address, OUTDATED_WETH_ICON_PATH); - return ( - <TableRow key={`weth-${outdatedWETHIfExists.address}`}> - <TableRowColumn className="py1"> - {this._renderTokenLink(tokenLabel, etherscanUrl)} - </TableRowColumn> - <TableRowColumn> - {isStateLoaded ? ( - `${balanceInEthIfExists} WETH` - ) : ( - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - )} - </TableRowColumn> - <TableRowColumn> - <EthWethConversionButton - isDisabled={!isStateLoaded} - isOutdatedWrappedEther={true} - direction={Side.Receive} - ethToken={outdatedEtherToken} - ethTokenState={outdatedEtherTokenState} - dispatcher={this.props.dispatcher} - blockchain={this.props.blockchain} - userEtherBalance={this.props.userEtherBalance} - onConversionSuccessful={onConversionSuccessful} - /> - </TableRowColumn> - </TableRow> - ); - }, - ); - return rows; - } - private _renderTokenLink(tokenLabel: React.ReactNode, etherscanUrl: string) { - return ( - <span> - {_.isUndefined(etherscanUrl) ? ( - tokenLabel - ) : ( - <a href={etherscanUrl} target="_blank" style={{ textDecoration: 'none' }}> - {tokenLabel} - </a> - )} - </span> - ); - } - private _renderToken(name: string, address: string, imgPath: string) { - const tooltipId = `tooltip-${address}`; - return ( - <div className="flex"> - <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={imgPath} /> - <div className="ml2 sm-hide xs-hide" style={{ marginTop: 12 }}> - <span data-tip={true} data-for={tooltipId}> - {name} - </span> - <ReactTooltip id={tooltipId}>{address}</ReactTooltip> - </div> - </div> - ); - } - private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) { - this.setState({ - outdatedWETHAddressToIsStateLoaded: { - ...this.state.outdatedWETHAddressToIsStateLoaded, - [outdatedWETHAddress]: false, - }, - }); - const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - this.props.userAddress, - outdatedWETHAddress, - ); - this.setState({ - outdatedWETHAddressToIsStateLoaded: { - ...this.state.outdatedWETHAddressToIsStateLoaded, - [outdatedWETHAddress]: true, - }, - outdatedWETHStateByAddress: { - ...this.state.outdatedWETHStateByAddress, - [outdatedWETHAddress]: { - balance, - allowance, - }, - }, - }); - } - private async _fetchOutdatedWETHStateAsync() { - const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); - const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; - const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; - for (const address of outdatedWETHAddresses) { - const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - this.props.userAddress, - address, - ); - outdatedWETHStateByAddress[address] = { - balance, - allowance, - }; - outdatedWETHAddressToIsStateLoaded[address] = true; - } - this.setState({ - outdatedWETHStateByAddress, - outdatedWETHAddressToIsStateLoaded, - }); - } - private _getOutdatedWETHAddresses(): string[] { - const outdatedWETHAddresses = _.compact( - _.map(configs.OUTDATED_WRAPPED_ETHERS, outdatedWrappedEtherByNetwork => { - const outdatedWrappedEtherIfExists = outdatedWrappedEtherByNetwork[this.props.networkId]; - if (_.isUndefined(outdatedWrappedEtherIfExists)) { - return undefined; - } - const address = outdatedWrappedEtherIfExists.address; - return address; - }), - ); - return outdatedWETHAddresses; - } + constructor(props: EthWrappersProps) { + super(props); + const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); + const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; + const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; + _.each(outdatedWETHAddresses, outdatedWETHAddress => { + outdatedWETHAddressToIsStateLoaded[outdatedWETHAddress] = false; + outdatedWETHStateByAddress[outdatedWETHAddress] = { + balance: new BigNumber(0), + allowance: new BigNumber(0), + }; + }); + this.state = { + outdatedWETHAddressToIsStateLoaded, + outdatedWETHStateByAddress, + }; + } + public componentDidMount() { + window.scrollTo(0, 0); + // tslint:disable-next-line:no-floating-promises + this._fetchOutdatedWETHStateAsync(); + } + public render() { + const tokens = _.values(this.props.tokenByAddress); + const etherToken = _.find(tokens, { symbol: 'WETH' }); + const etherTokenState = this.props.tokenStateByAddress[etherToken.address]; + const wethBalance = ZeroEx.toUnitAmount(etherTokenState.balance, constants.DECIMAL_PLACES_ETH); + const isBidirectional = true; + const etherscanUrl = utils.getEtherScanLinkIfExists( + etherToken.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const tokenLabel = this._renderToken('Wrapped Ether', etherToken.address, configs.ICON_URL_BY_SYMBOL.WETH); + return ( + <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> + <div className="relative"> + <h3>ETH Wrapper</h3> + <div className="absolute" style={{ top: 0, right: 0 }}> + <a target="_blank" href={constants.URL_WETH_IO} style={{ color: colors.grey }}> + <div className="flex"> + <div>About Wrapped ETH</div> + <div className="pl1"> + <i className="zmdi zmdi-open-in-new" /> + </div> + </div> + </a> + </div> + </div> + <Divider /> + <div> + <div className="py2">Wrap ETH into an ERC20-compliant Ether token. 1 ETH = 1 WETH.</div> + <div> + <Table selectable={false} style={{ backgroundColor: colors.grey50 }}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn>ETH Token</TableHeaderColumn> + <TableHeaderColumn>Balance</TableHeaderColumn> + <TableHeaderColumn className="center"> + {this._renderActionColumnTitle(isBidirectional)} + </TableHeaderColumn> + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false}> + <TableRow key="ETH"> + <TableRowColumn className="py1"> + <div className="flex"> + <img + style={{ + width: ICON_DIMENSION, + height: ICON_DIMENSION, + }} + src={ETHER_ICON_PATH} + /> + <div className="ml2 sm-hide xs-hide" style={{ marginTop: 12 }}> + ETH + </div> + </div> + </TableRowColumn> + <TableRowColumn> + {this.props.userEtherBalance.toFixed(PRECISION)} ETH + </TableRowColumn> + <TableRowColumn> + <EthWethConversionButton + isOutdatedWrappedEther={false} + direction={Side.Deposit} + ethToken={etherToken} + ethTokenState={etherTokenState} + dispatcher={this.props.dispatcher} + blockchain={this.props.blockchain} + userEtherBalance={this.props.userEtherBalance} + /> + </TableRowColumn> + </TableRow> + <TableRow key="WETH"> + <TableRowColumn className="py1"> + {this._renderTokenLink(tokenLabel, etherscanUrl)} + </TableRowColumn> + <TableRowColumn>{wethBalance.toFixed(PRECISION)} WETH</TableRowColumn> + <TableRowColumn> + <EthWethConversionButton + isOutdatedWrappedEther={false} + direction={Side.Receive} + ethToken={etherToken} + ethTokenState={etherTokenState} + dispatcher={this.props.dispatcher} + blockchain={this.props.blockchain} + userEtherBalance={this.props.userEtherBalance} + /> + </TableRowColumn> + </TableRow> + </TableBody> + </Table> + </div> + </div> + <div> + <h4>Outdated WETH</h4> + <Divider /> + <div className="pt2" style={{ lineHeight: 1.5 }}> + The{' '} + <a href="https://blog.0xproject.com/canonical-weth-a9aa7d0279dd" target="_blank"> + canonical WETH + </a>{' '} + contract is updated when necessary. Unwrap outdated WETH in order to
retrieve your ETH and move + it to the updated WETH token. + </div> + <div> + <Table selectable={false} style={{ backgroundColor: colors.grey50 }}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn>WETH Version</TableHeaderColumn> + <TableHeaderColumn>Balance</TableHeaderColumn> + <TableHeaderColumn className="center"> + {this._renderActionColumnTitle(!isBidirectional)} + </TableHeaderColumn> + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false}> + {this._renderOutdatedWeths(etherToken, etherTokenState)} + </TableBody> + </Table> + </div> + </div> + </div> + ); + } + private _renderActionColumnTitle(isBidirectional: boolean) { + let iconClass = 'zmdi-long-arrow-right'; + let leftSymbol = 'WETH'; + let rightSymbol = 'ETH'; + if (isBidirectional) { + iconClass = 'zmdi-swap'; + leftSymbol = 'ETH'; + rightSymbol = 'WETH'; + } + return ( + <div className="flex mx-auto" style={{ width: 85 }}> + <div style={{ paddingTop: 3 }}>{leftSymbol}</div> + <div className="px1"> + <i style={{ fontSize: 18 }} className={`zmdi ${iconClass}`} /> + </div> + <div style={{ paddingTop: 3 }}>{rightSymbol}</div> + </div> + ); + } + private _renderOutdatedWeths(etherToken: Token, etherTokenState: TokenState) { + const rows = _.map( + configs.OUTDATED_WRAPPED_ETHERS, + (outdatedWETHByNetworkId: OutdatedWrappedEtherByNetworkId) => { + const outdatedWETHIfExists = outdatedWETHByNetworkId[this.props.networkId]; + if (_.isUndefined(outdatedWETHIfExists)) { + return null; // noop + } + const timestampMsRange = outdatedWETHIfExists.timestampMsRange; + let dateRange: string; + if (!_.isUndefined(timestampMsRange)) { + const startMoment = moment(timestampMsRange.startTimestampMs); + const endMoment = moment(timestampMsRange.endTimestampMs); + dateRange = `${startMoment.format(DATE_FORMAT)}-${endMoment.format(DATE_FORMAT)}`; + } else { + dateRange = '-'; + } + const outdatedEtherToken = { + ...etherToken, + address: outdatedWETHIfExists.address, + }; + const isStateLoaded = this.state.outdatedWETHAddressToIsStateLoaded[outdatedWETHIfExists.address]; + const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address]; + const balanceInEthIfExists = isStateLoaded + ? ZeroEx.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed( + PRECISION, + ) + : undefined; + const onConversionSuccessful = this._onOutdatedConversionSuccessfulAsync.bind( + this, + outdatedWETHIfExists.address, + ); + const etherscanUrl = utils.getEtherScanLinkIfExists( + outdatedWETHIfExists.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const tokenLabel = this._renderToken(dateRange, outdatedEtherToken.address, OUTDATED_WETH_ICON_PATH); + return ( + <TableRow key={`weth-${outdatedWETHIfExists.address}`}> + <TableRowColumn className="py1"> + {this._renderTokenLink(tokenLabel, etherscanUrl)} + </TableRowColumn> + <TableRowColumn> + {isStateLoaded ? ( + `${balanceInEthIfExists} WETH` + ) : ( + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + )} + </TableRowColumn> + <TableRowColumn> + <EthWethConversionButton + isDisabled={!isStateLoaded} + isOutdatedWrappedEther={true} + direction={Side.Receive} + ethToken={outdatedEtherToken} + ethTokenState={outdatedEtherTokenState} + dispatcher={this.props.dispatcher} + blockchain={this.props.blockchain} + userEtherBalance={this.props.userEtherBalance} + onConversionSuccessful={onConversionSuccessful} + /> + </TableRowColumn> + </TableRow> + ); + }, + ); + return rows; + } + private _renderTokenLink(tokenLabel: React.ReactNode, etherscanUrl: string) { + return ( + <span> + {_.isUndefined(etherscanUrl) ? ( + tokenLabel + ) : ( + <a href={etherscanUrl} target="_blank" style={{ textDecoration: 'none' }}> + {tokenLabel} + </a> + )} + </span> + ); + } + private _renderToken(name: string, address: string, imgPath: string) { + const tooltipId = `tooltip-${address}`; + return ( + <div className="flex"> + <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={imgPath} /> + <div className="ml2 sm-hide xs-hide" style={{ marginTop: 12 }}> + <span data-tip={true} data-for={tooltipId}> + {name} + </span> + <ReactTooltip id={tooltipId}>{address}</ReactTooltip> + </div> + </div> + ); + } + private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) { + this.setState({ + outdatedWETHAddressToIsStateLoaded: { + ...this.state.outdatedWETHAddressToIsStateLoaded, + [outdatedWETHAddress]: false, + }, + }); + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + outdatedWETHAddress, + ); + this.setState({ + outdatedWETHAddressToIsStateLoaded: { + ...this.state.outdatedWETHAddressToIsStateLoaded, + [outdatedWETHAddress]: true, + }, + outdatedWETHStateByAddress: { + ...this.state.outdatedWETHStateByAddress, + [outdatedWETHAddress]: { + balance, + allowance, + }, + }, + }); + } + private async _fetchOutdatedWETHStateAsync() { + const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); + const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; + const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; + for (const address of outdatedWETHAddresses) { + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + address, + ); + outdatedWETHStateByAddress[address] = { + balance, + allowance, + }; + outdatedWETHAddressToIsStateLoaded[address] = true; + } + this.setState({ + outdatedWETHStateByAddress, + outdatedWETHAddressToIsStateLoaded, + }); + } + private _getOutdatedWETHAddresses(): string[] { + const outdatedWETHAddresses = _.compact( + _.map(configs.OUTDATED_WRAPPED_ETHERS, outdatedWrappedEtherByNetwork => { + const outdatedWrappedEtherIfExists = outdatedWrappedEtherByNetwork[this.props.networkId]; + if (_.isUndefined(outdatedWrappedEtherIfExists)) { + return undefined; + } + const address = outdatedWrappedEtherIfExists.address; + return address; + }), + ); + return outdatedWETHAddresses; + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx index 249ee419e..1a150e9ee 100644 --- a/packages/website/ts/components/fill_order.tsx +++ b/packages/website/ts/components/fill_order.tsx @@ -26,665 +26,665 @@ import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface FillOrderProps { - blockchain: Blockchain; - blockchainErr: BlockchainErrs; - orderFillAmount: BigNumber; - isOrderInUrl: boolean; - networkId: number; - userAddress: string; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - initialOrder: Order; - dispatcher: Dispatcher; + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + orderFillAmount: BigNumber; + isOrderInUrl: boolean; + networkId: number; + userAddress: string; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + initialOrder: Order; + dispatcher: Dispatcher; } interface FillOrderState { - didOrderValidationRun: boolean; - areAllInvolvedTokensTracked: boolean; - globalErrMsg: string; - orderJSON: string; - orderJSONErrMsg: string; - parsedOrder: Order; - didFillOrderSucceed: boolean; - didCancelOrderSucceed: boolean; - unavailableTakerAmount: BigNumber; - isMakerTokenAddressInRegistry: boolean; - isTakerTokenAddressInRegistry: boolean; - isFillWarningDialogOpen: boolean; - isFilling: boolean; - isCancelling: boolean; - isConfirmingTokenTracking: boolean; - tokensToTrack: Token[]; + didOrderValidationRun: boolean; + areAllInvolvedTokensTracked: boolean; + globalErrMsg: string; + orderJSON: string; + orderJSONErrMsg: string; + parsedOrder: Order; + didFillOrderSucceed: boolean; + didCancelOrderSucceed: boolean; + unavailableTakerAmount: BigNumber; + isMakerTokenAddressInRegistry: boolean; + isTakerTokenAddressInRegistry: boolean; + isFillWarningDialogOpen: boolean; + isFilling: boolean; + isCancelling: boolean; + isConfirmingTokenTracking: boolean; + tokensToTrack: Token[]; } export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { - private _validator: SchemaValidator; - constructor(props: FillOrderProps) { - super(props); - this.state = { - globalErrMsg: '', - didOrderValidationRun: false, - areAllInvolvedTokensTracked: false, - didFillOrderSucceed: false, - didCancelOrderSucceed: false, - orderJSON: _.isUndefined(this.props.initialOrder) ? '' : JSON.stringify(this.props.initialOrder), - orderJSONErrMsg: '', - parsedOrder: this.props.initialOrder, - unavailableTakerAmount: new BigNumber(0), - isMakerTokenAddressInRegistry: false, - isTakerTokenAddressInRegistry: false, - isFillWarningDialogOpen: false, - isFilling: false, - isCancelling: false, - isConfirmingTokenTracking: false, - tokensToTrack: [], - }; - this._validator = new SchemaValidator(); - } - public componentWillMount() { - if (!_.isEmpty(this.state.orderJSON)) { - // tslint:disable-next-line:no-floating-promises - this._validateFillOrderFireAndForgetAsync(this.state.orderJSON); - } - } - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - return ( - <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> - <h3>Fill an order</h3> - <Divider /> - <div> - {!this.props.isOrderInUrl && ( - <div> - <div className="pt2 pb2">Paste an order JSON snippet below to begin</div> - <div className="pb2">Order JSON</div> - <FillOrderJSON - blockchain={this.props.blockchain} - tokenByAddress={this.props.tokenByAddress} - networkId={this.props.networkId} - orderJSON={this.state.orderJSON} - onFillOrderJSONChanged={this._onFillOrderJSONChanged.bind(this)} - /> - {this._renderOrderJsonNotices()} - </div> - )} - <div> - {!_.isUndefined(this.state.parsedOrder) && - this.state.didOrderValidationRun && - this.state.areAllInvolvedTokensTracked && - this._renderVisualOrder()} - </div> - {this.props.isOrderInUrl && ( - <div className="pt2"> - <Card - style={{ - boxShadow: 'none', - backgroundColor: 'none', - border: '1px solid #eceaea', - }} - > - <CardHeader title="Order JSON" actAsExpander={true} showExpandableButton={true} /> - <CardText expandable={true}> - <FillOrderJSON - blockchain={this.props.blockchain} - tokenByAddress={this.props.tokenByAddress} - networkId={this.props.networkId} - orderJSON={this.state.orderJSON} - onFillOrderJSONChanged={this._onFillOrderJSONChanged.bind(this)} - /> - </CardText> - </Card> - {this._renderOrderJsonNotices()} - </div> - )} - </div> - <FillWarningDialog - isOpen={this.state.isFillWarningDialogOpen} - onToggleDialog={this._onFillWarningClosed.bind(this)} - /> - <TrackTokenConfirmationDialog - userAddress={this.props.userAddress} - networkId={this.props.networkId} - blockchain={this.props.blockchain} - tokenByAddress={this.props.tokenByAddress} - dispatcher={this.props.dispatcher} - tokens={this.state.tokensToTrack} - isOpen={this.state.isConfirmingTokenTracking} - onToggleDialog={this._onToggleTrackConfirmDialog.bind(this)} - /> - </div> - ); - } - private _renderOrderJsonNotices() { - return ( - <div> - {!_.isUndefined(this.props.initialOrder) && - !this.state.didOrderValidationRun && ( - <div className="pt2"> - <span className="pr1"> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </span> - <span>Validating order...</span> - </div> - )} - {!_.isEmpty(this.state.orderJSONErrMsg) && ( - <Alert type={AlertTypes.ERROR} message={this.state.orderJSONErrMsg} /> - )} - </div> - ); - } - private _renderVisualOrder() { - const takerTokenAddress = this.state.parsedOrder.taker.token.address; - const takerToken = this.props.tokenByAddress[takerTokenAddress]; - const orderTakerAmount = new BigNumber(this.state.parsedOrder.taker.amount); - const orderMakerAmount = new BigNumber(this.state.parsedOrder.maker.amount); - const takerAssetToken = { - amount: orderTakerAmount.minus(this.state.unavailableTakerAmount), - symbol: takerToken.symbol, - }; - const fillToken = this.props.tokenByAddress[takerToken.address]; - const fillTokenState = this.props.tokenStateByAddress[takerToken.address]; - const makerTokenAddress = this.state.parsedOrder.maker.token.address; - const makerToken = this.props.tokenByAddress[makerTokenAddress]; - const makerAssetToken = { - amount: orderMakerAmount.times(takerAssetToken.amount).div(orderTakerAmount), - symbol: makerToken.symbol, - }; - const fillAssetToken = { - amount: this.props.orderFillAmount, - symbol: takerToken.symbol, - }; - const orderTaker = !_.isEmpty(this.state.parsedOrder.taker.address) - ? this.state.parsedOrder.taker.address - : this.props.userAddress; - const parsedOrderExpiration = new BigNumber(this.state.parsedOrder.expiration); - const exchangeRate = orderMakerAmount.div(orderTakerAmount); + private _validator: SchemaValidator; + constructor(props: FillOrderProps) { + super(props); + this.state = { + globalErrMsg: '', + didOrderValidationRun: false, + areAllInvolvedTokensTracked: false, + didFillOrderSucceed: false, + didCancelOrderSucceed: false, + orderJSON: _.isUndefined(this.props.initialOrder) ? '' : JSON.stringify(this.props.initialOrder), + orderJSONErrMsg: '', + parsedOrder: this.props.initialOrder, + unavailableTakerAmount: new BigNumber(0), + isMakerTokenAddressInRegistry: false, + isTakerTokenAddressInRegistry: false, + isFillWarningDialogOpen: false, + isFilling: false, + isCancelling: false, + isConfirmingTokenTracking: false, + tokensToTrack: [], + }; + this._validator = new SchemaValidator(); + } + public componentWillMount() { + if (!_.isEmpty(this.state.orderJSON)) { + // tslint:disable-next-line:no-floating-promises + this._validateFillOrderFireAndForgetAsync(this.state.orderJSON); + } + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( + <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> + <h3>Fill an order</h3> + <Divider /> + <div> + {!this.props.isOrderInUrl && ( + <div> + <div className="pt2 pb2">Paste an order JSON snippet below to begin</div> + <div className="pb2">Order JSON</div> + <FillOrderJSON + blockchain={this.props.blockchain} + tokenByAddress={this.props.tokenByAddress} + networkId={this.props.networkId} + orderJSON={this.state.orderJSON} + onFillOrderJSONChanged={this._onFillOrderJSONChanged.bind(this)} + /> + {this._renderOrderJsonNotices()} + </div> + )} + <div> + {!_.isUndefined(this.state.parsedOrder) && + this.state.didOrderValidationRun && + this.state.areAllInvolvedTokensTracked && + this._renderVisualOrder()} + </div> + {this.props.isOrderInUrl && ( + <div className="pt2"> + <Card + style={{ + boxShadow: 'none', + backgroundColor: 'none', + border: '1px solid #eceaea', + }} + > + <CardHeader title="Order JSON" actAsExpander={true} showExpandableButton={true} /> + <CardText expandable={true}> + <FillOrderJSON + blockchain={this.props.blockchain} + tokenByAddress={this.props.tokenByAddress} + networkId={this.props.networkId} + orderJSON={this.state.orderJSON} + onFillOrderJSONChanged={this._onFillOrderJSONChanged.bind(this)} + /> + </CardText> + </Card> + {this._renderOrderJsonNotices()} + </div> + )} + </div> + <FillWarningDialog + isOpen={this.state.isFillWarningDialogOpen} + onToggleDialog={this._onFillWarningClosed.bind(this)} + /> + <TrackTokenConfirmationDialog + userAddress={this.props.userAddress} + networkId={this.props.networkId} + blockchain={this.props.blockchain} + tokenByAddress={this.props.tokenByAddress} + dispatcher={this.props.dispatcher} + tokens={this.state.tokensToTrack} + isOpen={this.state.isConfirmingTokenTracking} + onToggleDialog={this._onToggleTrackConfirmDialog.bind(this)} + /> + </div> + ); + } + private _renderOrderJsonNotices() { + return ( + <div> + {!_.isUndefined(this.props.initialOrder) && + !this.state.didOrderValidationRun && ( + <div className="pt2"> + <span className="pr1"> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </span> + <span>Validating order...</span> + </div> + )} + {!_.isEmpty(this.state.orderJSONErrMsg) && ( + <Alert type={AlertTypes.ERROR} message={this.state.orderJSONErrMsg} /> + )} + </div> + ); + } + private _renderVisualOrder() { + const takerTokenAddress = this.state.parsedOrder.taker.token.address; + const takerToken = this.props.tokenByAddress[takerTokenAddress]; + const orderTakerAmount = new BigNumber(this.state.parsedOrder.taker.amount); + const orderMakerAmount = new BigNumber(this.state.parsedOrder.maker.amount); + const takerAssetToken = { + amount: orderTakerAmount.minus(this.state.unavailableTakerAmount), + symbol: takerToken.symbol, + }; + const fillToken = this.props.tokenByAddress[takerToken.address]; + const fillTokenState = this.props.tokenStateByAddress[takerToken.address]; + const makerTokenAddress = this.state.parsedOrder.maker.token.address; + const makerToken = this.props.tokenByAddress[makerTokenAddress]; + const makerAssetToken = { + amount: orderMakerAmount.times(takerAssetToken.amount).div(orderTakerAmount), + symbol: makerToken.symbol, + }; + const fillAssetToken = { + amount: this.props.orderFillAmount, + symbol: takerToken.symbol, + }; + const orderTaker = !_.isEmpty(this.state.parsedOrder.taker.address) + ? this.state.parsedOrder.taker.address + : this.props.userAddress; + const parsedOrderExpiration = new BigNumber(this.state.parsedOrder.expiration); + const exchangeRate = orderMakerAmount.div(orderTakerAmount); - let orderReceiveAmount = 0; - if (!_.isUndefined(this.props.orderFillAmount)) { - const orderReceiveAmountBigNumber = exchangeRate.mul(this.props.orderFillAmount); - orderReceiveAmount = this._formatCurrencyAmount(orderReceiveAmountBigNumber, makerToken.decimals); - } - const isUserMaker = - !_.isUndefined(this.state.parsedOrder) && this.state.parsedOrder.maker.address === this.props.userAddress; - const expiryDate = utils.convertToReadableDateTimeFromUnixTimestamp(parsedOrderExpiration); - return ( - <div className="pt3 pb1"> - <div className="clearfix pb2" style={{ width: '100%' }}> - <div className="inline left">Order details</div> - <div className="inline right" style={{ minWidth: 208 }}> - <div className="col col-4 pl2" style={{ color: colors.grey }}> - Maker: - </div> - <div className="col col-2 pr1"> - <Identicon address={this.state.parsedOrder.maker.address} diameter={23} /> - </div> - <div className="col col-6"> - <EthereumAddress - address={this.state.parsedOrder.maker.address} - networkId={this.props.networkId} - /> - </div> - </div> - </div> - <div className="lg-px4 md-px4 sm-px0"> - <div className="lg-px4 md-px4 sm-px1 pt1"> - <VisualOrder - orderTakerAddress={orderTaker} - orderMakerAddress={this.state.parsedOrder.maker.address} - makerAssetToken={makerAssetToken} - takerAssetToken={takerAssetToken} - tokenByAddress={this.props.tokenByAddress} - makerToken={makerToken} - takerToken={takerToken} - networkId={this.props.networkId} - isMakerTokenAddressInRegistry={this.state.isMakerTokenAddressInRegistry} - isTakerTokenAddressInRegistry={this.state.isTakerTokenAddressInRegistry} - /> - <div className="center pt3 pb2">Expires: {expiryDate} UTC</div> - </div> - </div> - {!isUserMaker && ( - <div className="clearfix mx-auto relative" style={{ width: 235, height: 108 }}> - <TokenAmountInput - label="Fill amount" - onChange={this._onFillAmountChange.bind(this)} - shouldShowIncompleteErrs={false} - token={fillToken} - tokenState={fillTokenState} - amount={fillAssetToken.amount} - shouldCheckBalance={true} - shouldCheckAllowance={true} - /> - <div - className="absolute sm-hide xs-hide" - style={{ - color: colors.grey400, - right: -247, - top: 39, - width: 242, - }} - > - = {accounting.formatNumber(orderReceiveAmount, 6)} {makerToken.symbol} - </div> - </div> - )} - <div> - {isUserMaker ? ( - <div> - <RaisedButton - style={{ width: '100%' }} - disabled={this.state.isCancelling} - label={this.state.isCancelling ? 'Cancelling order...' : 'Cancel order'} - onClick={this._onCancelOrderClickFireAndForgetAsync.bind(this)} - /> - {this.state.didCancelOrderSucceed && ( - <Alert type={AlertTypes.SUCCESS} message={this._renderCancelSuccessMsg()} /> - )} - </div> - ) : ( - <div> - <RaisedButton - style={{ width: '100%' }} - disabled={this.state.isFilling} - label={this.state.isFilling ? 'Filling order...' : 'Fill order'} - onClick={this._onFillOrderClick.bind(this)} - /> - {!_.isEmpty(this.state.globalErrMsg) && ( - <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} /> - )} - {this.state.didFillOrderSucceed && ( - <Alert type={AlertTypes.SUCCESS} message={this._renderFillSuccessMsg()} /> - )} - </div> - )} - </div> - </div> - ); - } - private _renderFillSuccessMsg() { - return ( - <div> - Order successfully filled. See the trade details in your{' '} - <Link to={`${WebsitePaths.Portal}/trades`} style={{ color: colors.white }}> - trade history - </Link> - </div> - ); - } - private _renderCancelSuccessMsg() { - return <div>Order successfully cancelled.</div>; - } - private _onFillOrderClick() { - if (!this.state.isMakerTokenAddressInRegistry || !this.state.isTakerTokenAddressInRegistry) { - this.setState({ - isFillWarningDialogOpen: true, - }); - } else { - // tslint:disable-next-line:no-floating-promises - this._onFillOrderClickFireAndForgetAsync(); - } - } - private _onFillWarningClosed(didUserCancel: boolean) { - this.setState({ - isFillWarningDialogOpen: false, - }); - if (!didUserCancel) { - // tslint:disable-next-line:no-floating-promises - this._onFillOrderClickFireAndForgetAsync(); - } - } - private _onFillAmountChange(isValid: boolean, amount?: BigNumber) { - this.props.dispatcher.updateOrderFillAmount(amount); - } - private _onFillOrderJSONChanged(event: any) { - const orderJSON = event.target.value; - this.setState({ - didOrderValidationRun: _.isEmpty(orderJSON) && _.isEmpty(this.state.orderJSONErrMsg), - didFillOrderSucceed: false, - }); - // tslint:disable-next-line:no-floating-promises - this._validateFillOrderFireAndForgetAsync(orderJSON); - } - private async _checkForUntrackedTokensAndAskToAdd() { - if (!_.isEmpty(this.state.orderJSONErrMsg)) { - return; - } + let orderReceiveAmount = 0; + if (!_.isUndefined(this.props.orderFillAmount)) { + const orderReceiveAmountBigNumber = exchangeRate.mul(this.props.orderFillAmount); + orderReceiveAmount = this._formatCurrencyAmount(orderReceiveAmountBigNumber, makerToken.decimals); + } + const isUserMaker = + !_.isUndefined(this.state.parsedOrder) && this.state.parsedOrder.maker.address === this.props.userAddress; + const expiryDate = utils.convertToReadableDateTimeFromUnixTimestamp(parsedOrderExpiration); + return ( + <div className="pt3 pb1"> + <div className="clearfix pb2" style={{ width: '100%' }}> + <div className="inline left">Order details</div> + <div className="inline right" style={{ minWidth: 208 }}> + <div className="col col-4 pl2" style={{ color: colors.grey }}> + Maker: + </div> + <div className="col col-2 pr1"> + <Identicon address={this.state.parsedOrder.maker.address} diameter={23} /> + </div> + <div className="col col-6"> + <EthereumAddress + address={this.state.parsedOrder.maker.address} + networkId={this.props.networkId} + /> + </div> + </div> + </div> + <div className="lg-px4 md-px4 sm-px0"> + <div className="lg-px4 md-px4 sm-px1 pt1"> + <VisualOrder + orderTakerAddress={orderTaker} + orderMakerAddress={this.state.parsedOrder.maker.address} + makerAssetToken={makerAssetToken} + takerAssetToken={takerAssetToken} + tokenByAddress={this.props.tokenByAddress} + makerToken={makerToken} + takerToken={takerToken} + networkId={this.props.networkId} + isMakerTokenAddressInRegistry={this.state.isMakerTokenAddressInRegistry} + isTakerTokenAddressInRegistry={this.state.isTakerTokenAddressInRegistry} + /> + <div className="center pt3 pb2">Expires: {expiryDate} UTC</div> + </div> + </div> + {!isUserMaker && ( + <div className="clearfix mx-auto relative" style={{ width: 235, height: 108 }}> + <TokenAmountInput + label="Fill amount" + onChange={this._onFillAmountChange.bind(this)} + shouldShowIncompleteErrs={false} + token={fillToken} + tokenState={fillTokenState} + amount={fillAssetToken.amount} + shouldCheckBalance={true} + shouldCheckAllowance={true} + /> + <div + className="absolute sm-hide xs-hide" + style={{ + color: colors.grey400, + right: -247, + top: 39, + width: 242, + }} + > + = {accounting.formatNumber(orderReceiveAmount, 6)} {makerToken.symbol} + </div> + </div> + )} + <div> + {isUserMaker ? ( + <div> + <RaisedButton + style={{ width: '100%' }} + disabled={this.state.isCancelling} + label={this.state.isCancelling ? 'Cancelling order...' : 'Cancel order'} + onClick={this._onCancelOrderClickFireAndForgetAsync.bind(this)} + /> + {this.state.didCancelOrderSucceed && ( + <Alert type={AlertTypes.SUCCESS} message={this._renderCancelSuccessMsg()} /> + )} + </div> + ) : ( + <div> + <RaisedButton + style={{ width: '100%' }} + disabled={this.state.isFilling} + label={this.state.isFilling ? 'Filling order...' : 'Fill order'} + onClick={this._onFillOrderClick.bind(this)} + /> + {!_.isEmpty(this.state.globalErrMsg) && ( + <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} /> + )} + {this.state.didFillOrderSucceed && ( + <Alert type={AlertTypes.SUCCESS} message={this._renderFillSuccessMsg()} /> + )} + </div> + )} + </div> + </div> + ); + } + private _renderFillSuccessMsg() { + return ( + <div> + Order successfully filled. See the trade details in your{' '} + <Link to={`${WebsitePaths.Portal}/trades`} style={{ color: colors.white }}> + trade history + </Link> + </div> + ); + } + private _renderCancelSuccessMsg() { + return <div>Order successfully cancelled.</div>; + } + private _onFillOrderClick() { + if (!this.state.isMakerTokenAddressInRegistry || !this.state.isTakerTokenAddressInRegistry) { + this.setState({ + isFillWarningDialogOpen: true, + }); + } else { + // tslint:disable-next-line:no-floating-promises + this._onFillOrderClickFireAndForgetAsync(); + } + } + private _onFillWarningClosed(didUserCancel: boolean) { + this.setState({ + isFillWarningDialogOpen: false, + }); + if (!didUserCancel) { + // tslint:disable-next-line:no-floating-promises + this._onFillOrderClickFireAndForgetAsync(); + } + } + private _onFillAmountChange(isValid: boolean, amount?: BigNumber) { + this.props.dispatcher.updateOrderFillAmount(amount); + } + private _onFillOrderJSONChanged(event: any) { + const orderJSON = event.target.value; + this.setState({ + didOrderValidationRun: _.isEmpty(orderJSON) && _.isEmpty(this.state.orderJSONErrMsg), + didFillOrderSucceed: false, + }); + // tslint:disable-next-line:no-floating-promises + this._validateFillOrderFireAndForgetAsync(orderJSON); + } + private async _checkForUntrackedTokensAndAskToAdd() { + if (!_.isEmpty(this.state.orderJSONErrMsg)) { + return; + } - const makerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.maker.token.address]; - const takerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.taker.token.address]; + const makerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.maker.token.address]; + const takerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.taker.token.address]; - const tokensToTrack = []; - const isUnseenMakerToken = _.isUndefined(makerTokenIfExists); - const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && makerTokenIfExists.isTracked; - if (isUnseenMakerToken) { - tokensToTrack.push({ - ...this.state.parsedOrder.maker.token, - iconUrl: undefined, - isTracked: false, - isRegistered: false, - }); - } else if (!isMakerTokenTracked) { - tokensToTrack.push(makerTokenIfExists); - } - const isUnseenTakerToken = _.isUndefined(takerTokenIfExists); - const isTakerTokenTracked = !_.isUndefined(takerTokenIfExists) && takerTokenIfExists.isTracked; - if (isUnseenTakerToken) { - tokensToTrack.push({ - ...this.state.parsedOrder.taker.token, - iconUrl: undefined, - isTracked: false, - isRegistered: false, - }); - } else if (!isTakerTokenTracked) { - tokensToTrack.push(takerTokenIfExists); - } - if (!_.isEmpty(tokensToTrack)) { - this.setState({ - isConfirmingTokenTracking: true, - tokensToTrack, - }); - } else { - this.setState({ - areAllInvolvedTokensTracked: true, - }); - } - } - private async _validateFillOrderFireAndForgetAsync(orderJSON: string) { - let orderJSONErrMsg = ''; - let parsedOrder: Order; - try { - const order = JSON.parse(orderJSON); - const validationResult = this._validator.validate(order, orderSchema); - if (validationResult.errors.length > 0) { - orderJSONErrMsg = 'Submitted order JSON is not a valid order'; - utils.consoleLog(`Unexpected order JSON validation error: ${validationResult.errors.join(', ')}`); - return; - } - parsedOrder = order; + const tokensToTrack = []; + const isUnseenMakerToken = _.isUndefined(makerTokenIfExists); + const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && makerTokenIfExists.isTracked; + if (isUnseenMakerToken) { + tokensToTrack.push({ + ...this.state.parsedOrder.maker.token, + iconUrl: undefined, + isTracked: false, + isRegistered: false, + }); + } else if (!isMakerTokenTracked) { + tokensToTrack.push(makerTokenIfExists); + } + const isUnseenTakerToken = _.isUndefined(takerTokenIfExists); + const isTakerTokenTracked = !_.isUndefined(takerTokenIfExists) && takerTokenIfExists.isTracked; + if (isUnseenTakerToken) { + tokensToTrack.push({ + ...this.state.parsedOrder.taker.token, + iconUrl: undefined, + isTracked: false, + isRegistered: false, + }); + } else if (!isTakerTokenTracked) { + tokensToTrack.push(takerTokenIfExists); + } + if (!_.isEmpty(tokensToTrack)) { + this.setState({ + isConfirmingTokenTracking: true, + tokensToTrack, + }); + } else { + this.setState({ + areAllInvolvedTokensTracked: true, + }); + } + } + private async _validateFillOrderFireAndForgetAsync(orderJSON: string) { + let orderJSONErrMsg = ''; + let parsedOrder: Order; + try { + const order = JSON.parse(orderJSON); + const validationResult = this._validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + orderJSONErrMsg = 'Submitted order JSON is not a valid order'; + utils.consoleLog(`Unexpected order JSON validation error: ${validationResult.errors.join(', ')}`); + return; + } + parsedOrder = order; - const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); - const makerAmount = new BigNumber(parsedOrder.maker.amount); - const takerAmount = new BigNumber(parsedOrder.taker.amount); - const expiration = new BigNumber(parsedOrder.expiration); - const salt = new BigNumber(parsedOrder.salt); - const parsedMakerFee = new BigNumber(parsedOrder.maker.feeAmount); - const parsedTakerFee = new BigNumber(parsedOrder.taker.feeAmount); + const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); + const makerAmount = new BigNumber(parsedOrder.maker.amount); + const takerAmount = new BigNumber(parsedOrder.taker.amount); + const expiration = new BigNumber(parsedOrder.expiration); + const salt = new BigNumber(parsedOrder.salt); + const parsedMakerFee = new BigNumber(parsedOrder.maker.feeAmount); + const parsedTakerFee = new BigNumber(parsedOrder.taker.feeAmount); - const zeroExOrder: ZeroExOrder = { - exchangeContractAddress: parsedOrder.exchangeContract, - expirationUnixTimestampSec: expiration, - feeRecipient: parsedOrder.feeRecipient, - maker: parsedOrder.maker.address, - makerFee: parsedMakerFee, - makerTokenAddress: parsedOrder.maker.token.address, - makerTokenAmount: makerAmount, - salt, - taker: _.isEmpty(parsedOrder.taker.address) ? constants.NULL_ADDRESS : parsedOrder.taker.address, - takerFee: parsedTakerFee, - takerTokenAddress: parsedOrder.taker.token.address, - takerTokenAmount: takerAmount, - }; - const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); + const zeroExOrder: ZeroExOrder = { + exchangeContractAddress: parsedOrder.exchangeContract, + expirationUnixTimestampSec: expiration, + feeRecipient: parsedOrder.feeRecipient, + maker: parsedOrder.maker.address, + makerFee: parsedMakerFee, + makerTokenAddress: parsedOrder.maker.token.address, + makerTokenAmount: makerAmount, + salt, + taker: _.isEmpty(parsedOrder.taker.address) ? constants.NULL_ADDRESS : parsedOrder.taker.address, + takerFee: parsedTakerFee, + takerTokenAddress: parsedOrder.taker.token.address, + takerTokenAmount: takerAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); - const signature = parsedOrder.signature; - const isValidSignature = ZeroEx.isValidSignature(signature.hash, signature, parsedOrder.maker.address); - if (this.props.networkId !== parsedOrder.networkId) { - orderJSONErrMsg = `This order was made on another Ethereum network + const signature = parsedOrder.signature; + const isValidSignature = ZeroEx.isValidSignature(signature.hash, signature, parsedOrder.maker.address); + if (this.props.networkId !== parsedOrder.networkId) { + orderJSONErrMsg = `This order was made on another Ethereum network (id: ${parsedOrder.networkId}). Connect to this network to fill.`; - parsedOrder = undefined; - } else if (exchangeContractAddr !== parsedOrder.exchangeContract) { - orderJSONErrMsg = 'This order was made using a deprecated 0x Exchange contract.'; - parsedOrder = undefined; - } else if (orderHash !== signature.hash) { - orderJSONErrMsg = 'Order hash does not match supplied plaintext values'; - parsedOrder = undefined; - } else if (!isValidSignature) { - orderJSONErrMsg = 'Order signature is invalid'; - parsedOrder = undefined; - } else { - // Update user supplied order cache so that if they navigate away from fill view - // e.g to set a token allowance, when they come back, the fill order persists - this.props.dispatcher.updateUserSuppliedOrderCache(parsedOrder); - } - } catch (err) { - utils.consoleLog(`Validate order err: ${err}`); - if (!_.isEmpty(orderJSON)) { - orderJSONErrMsg = 'Submitted order JSON is not valid JSON'; - } - this.setState({ - didOrderValidationRun: true, - orderJSON, - orderJSONErrMsg, - parsedOrder, - }); - return; - } + parsedOrder = undefined; + } else if (exchangeContractAddr !== parsedOrder.exchangeContract) { + orderJSONErrMsg = 'This order was made using a deprecated 0x Exchange contract.'; + parsedOrder = undefined; + } else if (orderHash !== signature.hash) { + orderJSONErrMsg = 'Order hash does not match supplied plaintext values'; + parsedOrder = undefined; + } else if (!isValidSignature) { + orderJSONErrMsg = 'Order signature is invalid'; + parsedOrder = undefined; + } else { + // Update user supplied order cache so that if they navigate away from fill view + // e.g to set a token allowance, when they come back, the fill order persists + this.props.dispatcher.updateUserSuppliedOrderCache(parsedOrder); + } + } catch (err) { + utils.consoleLog(`Validate order err: ${err}`); + if (!_.isEmpty(orderJSON)) { + orderJSONErrMsg = 'Submitted order JSON is not valid JSON'; + } + this.setState({ + didOrderValidationRun: true, + orderJSON, + orderJSONErrMsg, + parsedOrder, + }); + return; + } - let unavailableTakerAmount = new BigNumber(0); - if (!_.isEmpty(orderJSONErrMsg)) { - // Clear cache entry if user updates orderJSON to invalid entry - this.props.dispatcher.updateUserSuppliedOrderCache(undefined); - } else { - const orderHash = parsedOrder.signature.hash; - unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); - const isMakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( - parsedOrder.maker.token.address, - ); - const isTakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( - parsedOrder.taker.token.address, - ); - this.setState({ - isMakerTokenAddressInRegistry, - isTakerTokenAddressInRegistry, - }); - } + let unavailableTakerAmount = new BigNumber(0); + if (!_.isEmpty(orderJSONErrMsg)) { + // Clear cache entry if user updates orderJSON to invalid entry + this.props.dispatcher.updateUserSuppliedOrderCache(undefined); + } else { + const orderHash = parsedOrder.signature.hash; + unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); + const isMakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( + parsedOrder.maker.token.address, + ); + const isTakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync( + parsedOrder.taker.token.address, + ); + this.setState({ + isMakerTokenAddressInRegistry, + isTakerTokenAddressInRegistry, + }); + } - this.setState({ - didOrderValidationRun: true, - orderJSON, - orderJSONErrMsg, - parsedOrder, - unavailableTakerAmount, - }); + this.setState({ + didOrderValidationRun: true, + orderJSON, + orderJSONErrMsg, + parsedOrder, + unavailableTakerAmount, + }); - await this._checkForUntrackedTokensAndAskToAdd(); - } - private async _onFillOrderClickFireAndForgetAsync(): Promise<void> { - if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return; - } + await this._checkForUntrackedTokensAndAskToAdd(); + } + private async _onFillOrderClickFireAndForgetAsync(): Promise<void> { + if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } - this.setState({ - isFilling: true, - didFillOrderSucceed: false, - }); + this.setState({ + isFilling: true, + didFillOrderSucceed: false, + }); - const parsedOrder = this.state.parsedOrder; - const takerFillAmount = this.props.orderFillAmount; + const parsedOrder = this.state.parsedOrder; + const takerFillAmount = this.props.orderFillAmount; - if (_.isUndefined(this.props.userAddress)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - this.setState({ - isFilling: false, - }); - return; - } - let globalErrMsg = ''; + if (_.isUndefined(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + isFilling: false, + }); + return; + } + let globalErrMsg = ''; - if (_.isUndefined(takerFillAmount)) { - globalErrMsg = 'You must specify a fill amount'; - } + if (_.isUndefined(takerFillAmount)) { + globalErrMsg = 'You must specify a fill amount'; + } - const signedOrder = this.props.blockchain.portalOrderToSignedOrder( - parsedOrder.maker.address, - parsedOrder.taker.address, - parsedOrder.maker.token.address, - parsedOrder.taker.token.address, - new BigNumber(parsedOrder.maker.amount), - new BigNumber(parsedOrder.taker.amount), - new BigNumber(parsedOrder.maker.feeAmount), - new BigNumber(parsedOrder.taker.feeAmount), - new BigNumber(this.state.parsedOrder.expiration), - parsedOrder.feeRecipient, - parsedOrder.signature, - new BigNumber(parsedOrder.salt), - ); - if (_.isEmpty(globalErrMsg)) { - try { - await this.props.blockchain.validateFillOrderThrowIfInvalidAsync( - signedOrder, - takerFillAmount, - this.props.userAddress, - ); - } catch (err) { - globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address); - } - } - if (!_.isEmpty(globalErrMsg)) { - this.setState({ - isFilling: false, - globalErrMsg, - }); - return; - } - try { - const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync( - signedOrder, - this.props.orderFillAmount, - ); - // After fill completes, let's update the token balances - const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address]; - const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address]; - const tokens = [makerToken, takerToken]; - await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens); - this.setState({ - isFilling: false, - didFillOrderSucceed: true, - globalErrMsg: '', - unavailableTakerAmount: this.state.unavailableTakerAmount.plus(orderFilledAmount), - }); - return; - } catch (err) { - this.setState({ - isFilling: false, - }); - const errMsg = `${err}`; - if (_.includes(errMsg, 'User denied transaction signature')) { - return; - } - globalErrMsg = 'Failed to fill order, please refresh and try again'; - utils.consoleLog(`${err}`); - this.setState({ - globalErrMsg, - }); - await errorReporter.reportAsync(err); - return; - } - } - private async _onCancelOrderClickFireAndForgetAsync(): Promise<void> { - if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return; - } + const signedOrder = this.props.blockchain.portalOrderToSignedOrder( + parsedOrder.maker.address, + parsedOrder.taker.address, + parsedOrder.maker.token.address, + parsedOrder.taker.token.address, + new BigNumber(parsedOrder.maker.amount), + new BigNumber(parsedOrder.taker.amount), + new BigNumber(parsedOrder.maker.feeAmount), + new BigNumber(parsedOrder.taker.feeAmount), + new BigNumber(this.state.parsedOrder.expiration), + parsedOrder.feeRecipient, + parsedOrder.signature, + new BigNumber(parsedOrder.salt), + ); + if (_.isEmpty(globalErrMsg)) { + try { + await this.props.blockchain.validateFillOrderThrowIfInvalidAsync( + signedOrder, + takerFillAmount, + this.props.userAddress, + ); + } catch (err) { + globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address); + } + } + if (!_.isEmpty(globalErrMsg)) { + this.setState({ + isFilling: false, + globalErrMsg, + }); + return; + } + try { + const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync( + signedOrder, + this.props.orderFillAmount, + ); + // After fill completes, let's update the token balances + const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address]; + const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address]; + const tokens = [makerToken, takerToken]; + await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens); + this.setState({ + isFilling: false, + didFillOrderSucceed: true, + globalErrMsg: '', + unavailableTakerAmount: this.state.unavailableTakerAmount.plus(orderFilledAmount), + }); + return; + } catch (err) { + this.setState({ + isFilling: false, + }); + const errMsg = `${err}`; + if (_.includes(errMsg, 'User denied transaction signature')) { + return; + } + globalErrMsg = 'Failed to fill order, please refresh and try again'; + utils.consoleLog(`${err}`); + this.setState({ + globalErrMsg, + }); + await errorReporter.reportAsync(err); + return; + } + } + private async _onCancelOrderClickFireAndForgetAsync(): Promise<void> { + if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } - this.setState({ - isCancelling: true, - didCancelOrderSucceed: false, - }); + this.setState({ + isCancelling: true, + didCancelOrderSucceed: false, + }); - const parsedOrder = this.state.parsedOrder; - const orderHash = parsedOrder.signature.hash; - const takerAddress = this.props.userAddress; + const parsedOrder = this.state.parsedOrder; + const orderHash = parsedOrder.signature.hash; + const takerAddress = this.props.userAddress; - if (_.isUndefined(takerAddress)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - this.setState({ - isFilling: false, - }); - return; - } - let globalErrMsg = ''; + if (_.isUndefined(takerAddress)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + isFilling: false, + }); + return; + } + let globalErrMsg = ''; - const takerTokenAmount = new BigNumber(parsedOrder.taker.amount); + const takerTokenAmount = new BigNumber(parsedOrder.taker.amount); - const signedOrder = this.props.blockchain.portalOrderToSignedOrder( - parsedOrder.maker.address, - parsedOrder.taker.address, - parsedOrder.maker.token.address, - parsedOrder.taker.token.address, - new BigNumber(parsedOrder.maker.amount), - takerTokenAmount, - new BigNumber(parsedOrder.maker.feeAmount), - new BigNumber(parsedOrder.taker.feeAmount), - new BigNumber(this.state.parsedOrder.expiration), - parsedOrder.feeRecipient, - parsedOrder.signature, - new BigNumber(parsedOrder.salt), - ); - const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); - const availableTakerTokenAmount = takerTokenAmount.minus(unavailableTakerAmount); - try { - await this.props.blockchain.validateCancelOrderThrowIfInvalidAsync(signedOrder, availableTakerTokenAmount); - } catch (err) { - globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address); - } - if (!_.isEmpty(globalErrMsg)) { - this.setState({ - isCancelling: false, - globalErrMsg, - }); - return; - } - try { - await this.props.blockchain.cancelOrderAsync(signedOrder, availableTakerTokenAmount); - this.setState({ - isCancelling: false, - didCancelOrderSucceed: true, - globalErrMsg: '', - unavailableTakerAmount: takerTokenAmount, - }); - return; - } catch (err) { - this.setState({ - isCancelling: false, - }); - const errMsg = `${err}`; - if (_.includes(errMsg, 'User denied transaction signature')) { - return; - } - globalErrMsg = 'Failed to cancel order, please refresh and try again'; - utils.consoleLog(`${err}`); - this.setState({ - globalErrMsg, - }); - await errorReporter.reportAsync(err); - return; - } - } - private _formatCurrencyAmount(amount: BigNumber, decimals: number): number { - const unitAmount = ZeroEx.toUnitAmount(amount, decimals); - const roundedUnitAmount = Math.round(unitAmount.toNumber() * 100000) / 100000; - return roundedUnitAmount; - } - private _onToggleTrackConfirmDialog(didConfirmTokenTracking: boolean) { - if (!didConfirmTokenTracking) { - this.setState({ - orderJSON: '', - orderJSONErrMsg: '', - parsedOrder: undefined, - }); - } else { - this.setState({ - areAllInvolvedTokensTracked: true, - }); - } - this.setState({ - isConfirmingTokenTracking: !this.state.isConfirmingTokenTracking, - tokensToTrack: [], - }); - } + const signedOrder = this.props.blockchain.portalOrderToSignedOrder( + parsedOrder.maker.address, + parsedOrder.taker.address, + parsedOrder.maker.token.address, + parsedOrder.taker.token.address, + new BigNumber(parsedOrder.maker.amount), + takerTokenAmount, + new BigNumber(parsedOrder.maker.feeAmount), + new BigNumber(parsedOrder.taker.feeAmount), + new BigNumber(this.state.parsedOrder.expiration), + parsedOrder.feeRecipient, + parsedOrder.signature, + new BigNumber(parsedOrder.salt), + ); + const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); + const availableTakerTokenAmount = takerTokenAmount.minus(unavailableTakerAmount); + try { + await this.props.blockchain.validateCancelOrderThrowIfInvalidAsync(signedOrder, availableTakerTokenAmount); + } catch (err) { + globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address); + } + if (!_.isEmpty(globalErrMsg)) { + this.setState({ + isCancelling: false, + globalErrMsg, + }); + return; + } + try { + await this.props.blockchain.cancelOrderAsync(signedOrder, availableTakerTokenAmount); + this.setState({ + isCancelling: false, + didCancelOrderSucceed: true, + globalErrMsg: '', + unavailableTakerAmount: takerTokenAmount, + }); + return; + } catch (err) { + this.setState({ + isCancelling: false, + }); + const errMsg = `${err}`; + if (_.includes(errMsg, 'User denied transaction signature')) { + return; + } + globalErrMsg = 'Failed to cancel order, please refresh and try again'; + utils.consoleLog(`${err}`); + this.setState({ + globalErrMsg, + }); + await errorReporter.reportAsync(err); + return; + } + } + private _formatCurrencyAmount(amount: BigNumber, decimals: number): number { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + const roundedUnitAmount = Math.round(unitAmount.toNumber() * 100000) / 100000; + return roundedUnitAmount; + } + private _onToggleTrackConfirmDialog(didConfirmTokenTracking: boolean) { + if (!didConfirmTokenTracking) { + this.setState({ + orderJSON: '', + orderJSONErrMsg: '', + parsedOrder: undefined, + }); + } else { + this.setState({ + areAllInvolvedTokensTracked: true, + }); + } + this.setState({ + isConfirmingTokenTracking: !this.state.isConfirmingTokenTracking, + tokensToTrack: [], + }); + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/fill_order_json.tsx b/packages/website/ts/components/fill_order_json.tsx index 3446b8a39..f8e43481a 100644 --- a/packages/website/ts/components/fill_order_json.tsx +++ b/packages/website/ts/components/fill_order_json.tsx @@ -10,71 +10,71 @@ import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; interface FillOrderJSONProps { - blockchain: Blockchain; - tokenByAddress: TokenByAddress; - networkId: number; - orderJSON: string; - onFillOrderJSONChanged: (event: any) => void; + blockchain: Blockchain; + tokenByAddress: TokenByAddress; + networkId: number; + orderJSON: string; + onFillOrderJSONChanged: (event: any) => void; } interface FillOrderJSONState {} export class FillOrderJSON extends React.Component<FillOrderJSONProps, FillOrderJSONState> { - public render() { - const tokenAddresses = _.keys(this.props.tokenByAddress); - const exchangeContract = this.props.blockchain.getExchangeContractAddressIfExists(); - const hintSideToAssetToken = { - [Side.Deposit]: { - amount: new BigNumber(35), - address: tokenAddresses[0], - }, - [Side.Receive]: { - amount: new BigNumber(89), - address: tokenAddresses[1], - }, - }; - const hintOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); - const hintSignatureData = { - hash: '0xf965a9978a0381ab58f5a2408ad967c...', - r: '0xf01103f759e2289a28593eaf22e5820032...', - s: '937862111edcba395f8a9e0cc1b2c5e12320...', - v: 27, - }; - const hintSalt = ZeroEx.generatePseudoRandomSalt(); - const feeRecipient = constants.NULL_ADDRESS; - const hintOrder = utils.generateOrder( - this.props.networkId, - exchangeContract, - hintSideToAssetToken, - hintOrderExpiryTimestamp, - '', - '', - constants.MAKER_FEE, - constants.TAKER_FEE, - feeRecipient, - hintSignatureData, - this.props.tokenByAddress, - hintSalt, - ); - const hintOrderJSON = `${JSON.stringify(hintOrder, null, '\t').substring(0, 500)}...`; - return ( - <div> - <Paper className="p1 overflow-hidden" style={{ height: 164 }}> - <TextField - id="orderJSON" - hintStyle={{ bottom: 0, top: 0 }} - fullWidth={true} - value={this.props.orderJSON} - onChange={this.props.onFillOrderJSONChanged.bind(this)} - hintText={hintOrderJSON} - multiLine={true} - rows={6} - rowsMax={6} - underlineStyle={{ display: 'none' }} - textareaStyle={{ marginTop: 0 }} - /> - </Paper> - </div> - ); - } + public render() { + const tokenAddresses = _.keys(this.props.tokenByAddress); + const exchangeContract = this.props.blockchain.getExchangeContractAddressIfExists(); + const hintSideToAssetToken = { + [Side.Deposit]: { + amount: new BigNumber(35), + address: tokenAddresses[0], + }, + [Side.Receive]: { + amount: new BigNumber(89), + address: tokenAddresses[1], + }, + }; + const hintOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); + const hintSignatureData = { + hash: '0xf965a9978a0381ab58f5a2408ad967c...', + r: '0xf01103f759e2289a28593eaf22e5820032...', + s: '937862111edcba395f8a9e0cc1b2c5e12320...', + v: 27, + }; + const hintSalt = ZeroEx.generatePseudoRandomSalt(); + const feeRecipient = constants.NULL_ADDRESS; + const hintOrder = utils.generateOrder( + this.props.networkId, + exchangeContract, + hintSideToAssetToken, + hintOrderExpiryTimestamp, + '', + '', + constants.MAKER_FEE, + constants.TAKER_FEE, + feeRecipient, + hintSignatureData, + this.props.tokenByAddress, + hintSalt, + ); + const hintOrderJSON = `${JSON.stringify(hintOrder, null, '\t').substring(0, 500)}...`; + return ( + <div> + <Paper className="p1 overflow-hidden" style={{ height: 164 }}> + <TextField + id="orderJSON" + hintStyle={{ bottom: 0, top: 0 }} + fullWidth={true} + value={this.props.orderJSON} + onChange={this.props.onFillOrderJSONChanged.bind(this)} + hintText={hintOrderJSON} + multiLine={true} + rows={6} + rowsMax={6} + underlineStyle={{ display: 'none' }} + textareaStyle={{ marginTop: 0 }} + /> + </Paper> + </div> + ); + } } diff --git a/packages/website/ts/components/fill_warning_dialog.tsx b/packages/website/ts/components/fill_warning_dialog.tsx index 40b04723d..165d21b34 100644 --- a/packages/website/ts/components/fill_warning_dialog.tsx +++ b/packages/website/ts/components/fill_warning_dialog.tsx @@ -4,42 +4,42 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; interface FillWarningDialogProps { - isOpen: boolean; - onToggleDialog: (didUserCancel: boolean) => void; + isOpen: boolean; + onToggleDialog: (didUserCancel: boolean) => void; } export function FillWarningDialog(props: FillWarningDialogProps) { - const didCancel = true; - return ( - <Dialog - title="Warning" - titleStyle={{ fontWeight: 100, color: colors.red500 }} - actions={[ - <FlatButton - key="fillWarningCancel" - label="Cancel" - onTouchTap={props.onToggleDialog.bind(this, didCancel)} - />, - <FlatButton - key="fillWarningContinue" - label="Fill Order" - onTouchTap={props.onToggleDialog.bind(this, !didCancel)} - />, - ]} - open={props.isOpen} - onRequestClose={props.onToggleDialog.bind(this)} - autoScrollBodyContent={true} - modal={true} - > - <div className="pt2" style={{ color: colors.grey700 }}> - <div> - At least one of the tokens in this order was not found in the token registry smart contract and may - be counterfeit. It is your responsibility to verify the token addresses on Etherscan ( - <a href="https://0xproject.com/wiki#Verifying-Custom-Tokens" target="_blank"> - See this how-to guide - </a>) before filling an order. <b>This action may result in the loss of funds</b>. - </div> - </div> - </Dialog> - ); + const didCancel = true; + return ( + <Dialog + title="Warning" + titleStyle={{ fontWeight: 100, color: colors.red500 }} + actions={[ + <FlatButton + key="fillWarningCancel" + label="Cancel" + onTouchTap={props.onToggleDialog.bind(this, didCancel)} + />, + <FlatButton + key="fillWarningContinue" + label="Fill Order" + onTouchTap={props.onToggleDialog.bind(this, !didCancel)} + />, + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog.bind(this)} + autoScrollBodyContent={true} + modal={true} + > + <div className="pt2" style={{ color: colors.grey700 }}> + <div> + At least one of the tokens in this order was not found in the token registry smart contract and may + be counterfeit. It is your responsibility to verify the token addresses on Etherscan ( + <a href="https://0xproject.com/wiki#Verifying-Custom-Tokens" target="_blank"> + See this how-to guide + </a>) before filling an order. <b>This action may result in the loss of funds</b>. + </div> + </div> + </Dialog> + ); } diff --git a/packages/website/ts/components/flash_messages/token_send_completed.tsx b/packages/website/ts/components/flash_messages/token_send_completed.tsx index 07fca1ddc..18f371624 100644 --- a/packages/website/ts/components/flash_messages/token_send_completed.tsx +++ b/packages/website/ts/components/flash_messages/token_send_completed.tsx @@ -7,28 +7,28 @@ import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface TokenSendCompletedProps { - etherScanLinkIfExists?: string; - token: Token; - toAddress: string; - amountInBaseUnits: BigNumber; + etherScanLinkIfExists?: string; + token: Token; + toAddress: string; + amountInBaseUnits: BigNumber; } interface TokenSendCompletedState {} export class TokenSendCompleted extends React.Component<TokenSendCompletedProps, TokenSendCompletedState> { - public render() { - const etherScanLink = !_.isUndefined(this.props.etherScanLinkIfExists) && ( - <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank"> - Verify on Etherscan - </a> - ); - const amountInUnits = ZeroEx.toUnitAmount(this.props.amountInBaseUnits, this.props.token.decimals); - const truncatedAddress = utils.getAddressBeginAndEnd(this.props.toAddress); - return ( - <div> - {`Sent ${amountInUnits} ${this.props.token.symbol} to ${truncatedAddress}: `} - {etherScanLink} - </div> - ); - } + public render() { + const etherScanLink = !_.isUndefined(this.props.etherScanLinkIfExists) && ( + <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank"> + Verify on Etherscan + </a> + ); + const amountInUnits = ZeroEx.toUnitAmount(this.props.amountInBaseUnits, this.props.token.decimals); + const truncatedAddress = utils.getAddressBeginAndEnd(this.props.toAddress); + return ( + <div> + {`Sent ${amountInUnits} ${this.props.token.symbol} to ${truncatedAddress}: `} + {etherScanLink} + </div> + ); + } } diff --git a/packages/website/ts/components/flash_messages/transaction_submitted.tsx b/packages/website/ts/components/flash_messages/transaction_submitted.tsx index 14464b923..862e382dd 100644 --- a/packages/website/ts/components/flash_messages/transaction_submitted.tsx +++ b/packages/website/ts/components/flash_messages/transaction_submitted.tsx @@ -3,24 +3,24 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; interface TransactionSubmittedProps { - etherScanLinkIfExists?: string; + etherScanLinkIfExists?: string; } interface TransactionSubmittedState {} export class TransactionSubmitted extends React.Component<TransactionSubmittedProps, TransactionSubmittedState> { - public render() { - if (_.isUndefined(this.props.etherScanLinkIfExists)) { - return <div>Transaction submitted to the network</div>; - } else { - return ( - <div> - Transaction submitted to the network:{' '} - <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank"> - Verify on Etherscan - </a> - </div> - ); - } - } + public render() { + if (_.isUndefined(this.props.etherScanLinkIfExists)) { + return <div>Transaction submitted to the network</div>; + } else { + return ( + <div> + Transaction submitted to the network:{' '} + <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank"> + Verify on Etherscan + </a> + </div> + ); + } + } } diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx index 3a342591c..a0f1a0c96 100644 --- a/packages/website/ts/components/footer.tsx +++ b/packages/website/ts/components/footer.tsx @@ -6,106 +6,106 @@ import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; interface MenuItemsBySection { - [sectionName: string]: FooterMenuItem[]; + [sectionName: string]: FooterMenuItem[]; } interface FooterMenuItem { - title: string; - path?: string; - isExternal?: boolean; + title: string; + path?: string; + isExternal?: boolean; } enum Sections { - Documentation = 'Documentation', - Community = 'Community', - Organization = 'Organization', + Documentation = 'Documentation', + Community = 'Community', + Organization = 'Organization', } const ICON_DIMENSION = 16; const menuItemsBySection: MenuItemsBySection = { - Documentation: [ - { - title: '0x.js', - path: WebsitePaths.ZeroExJs, - }, - { - title: '0x Smart Contracts', - path: WebsitePaths.SmartContracts, - }, - { - title: '0x Connect', - path: WebsitePaths.Connect, - }, - { - title: 'Whitepaper', - path: WebsitePaths.Whitepaper, - isExternal: true, - }, - { - title: 'Wiki', - path: WebsitePaths.Wiki, - }, - { - title: 'FAQ', - path: WebsitePaths.FAQ, - }, - ], - Community: [ - { - title: 'Rocket.chat', - isExternal: true, - path: constants.URL_ZEROEX_CHAT, - }, - { - title: 'Blog', - isExternal: true, - path: constants.URL_BLOG, - }, - { - title: 'Twitter', - isExternal: true, - path: constants.URL_TWITTER, - }, - { - title: 'Reddit', - isExternal: true, - path: constants.URL_REDDIT, - }, - { - title: 'Forum', - isExternal: true, - path: constants.URL_DISCOURSE_FORUM, - }, - ], - Organization: [ - { - title: 'About', - isExternal: false, - path: WebsitePaths.About, - }, - { - title: 'Careers', - isExternal: true, - path: constants.URL_ANGELLIST, - }, - { - title: 'Contact', - isExternal: true, - path: 'mailto:team@0xproject.com', - }, - ], + Documentation: [ + { + title: '0x.js', + path: WebsitePaths.ZeroExJs, + }, + { + title: '0x Smart Contracts', + path: WebsitePaths.SmartContracts, + }, + { + title: '0x Connect', + path: WebsitePaths.Connect, + }, + { + title: 'Whitepaper', + path: WebsitePaths.Whitepaper, + isExternal: true, + }, + { + title: 'Wiki', + path: WebsitePaths.Wiki, + }, + { + title: 'FAQ', + path: WebsitePaths.FAQ, + }, + ], + Community: [ + { + title: 'Rocket.chat', + isExternal: true, + path: constants.URL_ZEROEX_CHAT, + }, + { + title: 'Blog', + isExternal: true, + path: constants.URL_BLOG, + }, + { + title: 'Twitter', + isExternal: true, + path: constants.URL_TWITTER, + }, + { + title: 'Reddit', + isExternal: true, + path: constants.URL_REDDIT, + }, + { + title: 'Forum', + isExternal: true, + path: constants.URL_DISCOURSE_FORUM, + }, + ], + Organization: [ + { + title: 'About', + isExternal: false, + path: WebsitePaths.About, + }, + { + title: 'Careers', + isExternal: true, + path: constants.URL_ANGELLIST, + }, + { + title: 'Contact', + isExternal: true, + path: 'mailto:team@0xproject.com', + }, + ], }; const linkStyle = { - color: colors.white, - cursor: 'pointer', + color: colors.white, + cursor: 'pointer', }; const titleToIcon: { [title: string]: string } = { - 'Rocket.chat': 'rocketchat.png', - Blog: 'medium.png', - Twitter: 'twitter.png', - Reddit: 'reddit.png', - Forum: 'discourse.png', + 'Rocket.chat': 'rocketchat.png', + Blog: 'medium.png', + Twitter: 'twitter.png', + Reddit: 'reddit.png', + Forum: 'discourse.png', }; export interface FooterProps {} @@ -113,100 +113,100 @@ export interface FooterProps {} interface FooterState {} export class Footer extends React.Component<FooterProps, FooterState> { - public render() { - return ( - <div className="relative pb4 pt2" style={{ backgroundColor: colors.darkerGrey }}> - <div className="mx-auto max-width-4 md-px2 lg-px0 py4 clearfix" style={{ color: colors.white }}> - <div className="col lg-col-4 md-col-4 col-12 left"> - <div className="sm-mx-auto" style={{ width: 148 }}> - <div> - <img src="/images/protocol_logo_white.png" height="30" /> - </div> - <div - style={{ - fontSize: 11, - color: colors.grey, - paddingLeft: 37, - paddingTop: 2, - }} - > - © ZeroEx, Intl. - </div> - </div> - </div> - <div className="col lg-col-8 md-col-8 col-12 lg-pl4 md-pl4"> - <div className="col lg-col-4 md-col-4 col-12"> - <div className="lg-right md-right sm-center"> - {this._renderHeader(Sections.Documentation)} - {_.map(menuItemsBySection[Sections.Documentation], this._renderMenuItem.bind(this))} - </div> - </div> - <div className="col lg-col-4 md-col-4 col-12 lg-pr2 md-pr2"> - <div className="lg-right md-right sm-center"> - {this._renderHeader(Sections.Community)} - {_.map(menuItemsBySection[Sections.Community], this._renderMenuItem.bind(this))} - </div> - </div> - <div className="col lg-col-4 md-col-4 col-12"> - <div className="lg-right md-right sm-center"> - {this._renderHeader(Sections.Organization)} - {_.map(menuItemsBySection[Sections.Organization], this._renderMenuItem.bind(this))} - </div> - </div> - </div> - </div> - </div> - ); - } - private _renderIcon(fileName: string) { - return ( - <div style={{ height: ICON_DIMENSION, width: ICON_DIMENSION }}> - <img src={`/images/social/${fileName}`} style={{ width: ICON_DIMENSION }} /> - </div> - ); - } - private _renderMenuItem(item: FooterMenuItem) { - const iconIfExists = titleToIcon[item.title]; - return ( - <div key={item.title} className="sm-center" style={{ fontSize: 13, paddingTop: 25 }}> - {item.isExternal ? ( - <a className="text-decoration-none" style={linkStyle} target="_blank" href={item.path}> - {!_.isUndefined(iconIfExists) ? ( - <div className="sm-mx-auto" style={{ width: 65 }}> - <div className="flex"> - <div className="pr1">{this._renderIcon(iconIfExists)}</div> - <div>{item.title}</div> - </div> - </div> - ) : ( - item.title - )} - </a> - ) : ( - <Link to={item.path} style={linkStyle} className="text-decoration-none"> - <div> - {!_.isUndefined(iconIfExists) && ( - <div className="pr1">{this._renderIcon(iconIfExists)}</div> - )} - {item.title} - </div> - </Link> - )} - </div> - ); - } - private _renderHeader(title: string) { - const headerStyle = { - textTransform: 'uppercase', - color: colors.grey400, - letterSpacing: 2, - fontFamily: 'Roboto Mono', - fontSize: 13, - }; - return ( - <div className="lg-pb2 md-pb2 sm-pt4" style={headerStyle}> - {title} - </div> - ); - } + public render() { + return ( + <div className="relative pb4 pt2" style={{ backgroundColor: colors.darkerGrey }}> + <div className="mx-auto max-width-4 md-px2 lg-px0 py4 clearfix" style={{ color: colors.white }}> + <div className="col lg-col-4 md-col-4 col-12 left"> + <div className="sm-mx-auto" style={{ width: 148 }}> + <div> + <img src="/images/protocol_logo_white.png" height="30" /> + </div> + <div + style={{ + fontSize: 11, + color: colors.grey, + paddingLeft: 37, + paddingTop: 2, + }} + > + © ZeroEx, Intl. + </div> + </div> + </div> + <div className="col lg-col-8 md-col-8 col-12 lg-pl4 md-pl4"> + <div className="col lg-col-4 md-col-4 col-12"> + <div className="lg-right md-right sm-center"> + {this._renderHeader(Sections.Documentation)} + {_.map(menuItemsBySection[Sections.Documentation], this._renderMenuItem.bind(this))} + </div> + </div> + <div className="col lg-col-4 md-col-4 col-12 lg-pr2 md-pr2"> + <div className="lg-right md-right sm-center"> + {this._renderHeader(Sections.Community)} + {_.map(menuItemsBySection[Sections.Community], this._renderMenuItem.bind(this))} + </div> + </div> + <div className="col lg-col-4 md-col-4 col-12"> + <div className="lg-right md-right sm-center"> + {this._renderHeader(Sections.Organization)} + {_.map(menuItemsBySection[Sections.Organization], this._renderMenuItem.bind(this))} + </div> + </div> + </div> + </div> + </div> + ); + } + private _renderIcon(fileName: string) { + return ( + <div style={{ height: ICON_DIMENSION, width: ICON_DIMENSION }}> + <img src={`/images/social/${fileName}`} style={{ width: ICON_DIMENSION }} /> + </div> + ); + } + private _renderMenuItem(item: FooterMenuItem) { + const iconIfExists = titleToIcon[item.title]; + return ( + <div key={item.title} className="sm-center" style={{ fontSize: 13, paddingTop: 25 }}> + {item.isExternal ? ( + <a className="text-decoration-none" style={linkStyle} target="_blank" href={item.path}> + {!_.isUndefined(iconIfExists) ? ( + <div className="sm-mx-auto" style={{ width: 65 }}> + <div className="flex"> + <div className="pr1">{this._renderIcon(iconIfExists)}</div> + <div>{item.title}</div> + </div> + </div> + ) : ( + item.title + )} + </a> + ) : ( + <Link to={item.path} style={linkStyle} className="text-decoration-none"> + <div> + {!_.isUndefined(iconIfExists) && ( + <div className="pr1">{this._renderIcon(iconIfExists)}</div> + )} + {item.title} + </div> + </Link> + )} + </div> + ); + } + private _renderHeader(title: string) { + const headerStyle = { + textTransform: 'uppercase', + color: colors.grey400, + letterSpacing: 2, + fontFamily: 'Roboto Mono', + fontSize: 13, + }; + return ( + <div className="lg-pb2 md-pb2 sm-pt4" style={headerStyle}> + {title} + </div> + ); + } } diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx index 5eed2fabf..df7d87cfd 100644 --- a/packages/website/ts/components/generate_order/asset_picker.tsx +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -13,264 +13,264 @@ import { DialogConfigs, Token, TokenByAddress, TokenState, TokenVisibility } fro const TOKEN_ICON_DIMENSION = 100; const TILE_DIMENSION = 146; enum AssetViews { - ASSET_PICKER = 'ASSET_PICKER', - NEW_TOKEN_FORM = 'NEW_TOKEN_FORM', - CONFIRM_TRACK_TOKEN = 'CONFIRM_TRACK_TOKEN', + ASSET_PICKER = 'ASSET_PICKER', + NEW_TOKEN_FORM = 'NEW_TOKEN_FORM', + CONFIRM_TRACK_TOKEN = 'CONFIRM_TRACK_TOKEN', } interface AssetPickerProps { - userAddress: string; - blockchain: Blockchain; - dispatcher: Dispatcher; - networkId: number; - isOpen: boolean; - currentTokenAddress: string; - onTokenChosen: (tokenAddress: string) => void; - tokenByAddress: TokenByAddress; - tokenVisibility?: TokenVisibility; + userAddress: string; + blockchain: Blockchain; + dispatcher: Dispatcher; + networkId: number; + isOpen: boolean; + currentTokenAddress: string; + onTokenChosen: (tokenAddress: string) => void; + tokenByAddress: TokenByAddress; + tokenVisibility?: TokenVisibility; } interface AssetPickerState { - assetView: AssetViews; - hoveredAddress: string | undefined; - chosenTrackTokenAddress: string; - isAddingTokenToTracked: boolean; + assetView: AssetViews; + hoveredAddress: string | undefined; + chosenTrackTokenAddress: string; + isAddingTokenToTracked: boolean; } export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerState> { - public static defaultProps: Partial<AssetPickerProps> = { - tokenVisibility: TokenVisibility.ALL, - }; - private _dialogConfigsByAssetView: { [assetView: string]: DialogConfigs }; - constructor(props: AssetPickerProps) { - super(props); - this.state = { - assetView: AssetViews.ASSET_PICKER, - hoveredAddress: undefined, - chosenTrackTokenAddress: undefined, - isAddingTokenToTracked: false, - }; - this._dialogConfigsByAssetView = { - [AssetViews.ASSET_PICKER]: { - title: 'Select token', - isModal: false, - actions: [], - }, - [AssetViews.NEW_TOKEN_FORM]: { - title: 'Add an ERC20 token', - isModal: false, - actions: [], - }, - [AssetViews.CONFIRM_TRACK_TOKEN]: { - title: 'Tracking confirmation', - isModal: true, - actions: [ - <FlatButton - key="noTracking" - label="No" - onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)} - />, - <FlatButton - key="yesTrack" - label="Yes" - onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)} - />, - ], - }, - }; - } - public render() { - const dialogConfigs: DialogConfigs = this._dialogConfigsByAssetView[this.state.assetView]; - return ( - <Dialog - title={dialogConfigs.title} - titleStyle={{ fontWeight: 100 }} - modal={dialogConfigs.isModal} - open={this.props.isOpen} - actions={dialogConfigs.actions} - onRequestClose={this._onCloseDialog.bind(this)} - > - {this.state.assetView === AssetViews.ASSET_PICKER && this._renderAssetPicker()} - {this.state.assetView === AssetViews.NEW_TOKEN_FORM && ( - <NewTokenForm - blockchain={this.props.blockchain} - onNewTokenSubmitted={this._onNewTokenSubmitted.bind(this)} - tokenByAddress={this.props.tokenByAddress} - /> - )} - {this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN && this._renderConfirmTrackToken()} - </Dialog> - ); - } - private _renderConfirmTrackToken() { - const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress]; - return ( - <TrackTokenConfirmation - tokens={[token]} - tokenByAddress={this.props.tokenByAddress} - networkId={this.props.networkId} - isAddingTokenToTracked={this.state.isAddingTokenToTracked} - /> - ); - } - private _renderAssetPicker() { - return ( - <div - className="clearfix flex flex-wrap" - style={{ - overflowY: 'auto', - maxWidth: 720, - maxHeight: 356, - marginBottom: 10, - }} - > - {this._renderGridTiles()} - </div> - ); - } - private _renderGridTiles() { - let isHovered; - let tileStyles; - const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => { - if ( - (this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) || - (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked) - ) { - return null; // Skip - } - isHovered = this.state.hoveredAddress === address; - tileStyles = { - cursor: 'pointer', - opacity: isHovered ? 0.6 : 1, - }; - return ( - <div - key={address} - style={{ - width: TILE_DIMENSION, - height: TILE_DIMENSION, - ...tileStyles, - }} - className="p2 mx-auto" - onClick={this._onChooseToken.bind(this, address)} - onMouseEnter={this._onToggleHover.bind(this, address, true)} - onMouseLeave={this._onToggleHover.bind(this, address, false)} - > - <div className="p1 center"> - <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} /> - </div> - <div className="center">{token.name}</div> - </div> - ); - }); - const otherTokenKey = 'otherToken'; - isHovered = this.state.hoveredAddress === otherTokenKey; - tileStyles = { - cursor: 'pointer', - opacity: isHovered ? 0.6 : 1, - }; - if (this.props.tokenVisibility !== TokenVisibility.TRACKED) { - gridTiles.push( - <div - key={otherTokenKey} - style={{ - width: TILE_DIMENSION, - height: TILE_DIMENSION, - ...tileStyles, - }} - className="p2 mx-auto" - onClick={this._onCustomAssetChosen.bind(this)} - onMouseEnter={this._onToggleHover.bind(this, otherTokenKey, true)} - onMouseLeave={this._onToggleHover.bind(this, otherTokenKey, false)} - > - <div className="p1 center"> - <i - style={{ fontSize: 105, paddingLeft: 1, paddingRight: 1 }} - className="zmdi zmdi-plus-circle" - /> - </div> - <div className="center">Other ERC20 Token</div> - </div>, - ); - } - return gridTiles; - } - private _onToggleHover(address: string, isHovered: boolean) { - const hoveredAddress = isHovered ? address : undefined; - this.setState({ - hoveredAddress, - }); - } - private _onCloseDialog() { - this.setState({ - assetView: AssetViews.ASSET_PICKER, - }); - this.props.onTokenChosen(this.props.currentTokenAddress); - } - private _onChooseToken(tokenAddress: string) { - const token = this.props.tokenByAddress[tokenAddress]; - if (token.isTracked) { - this.props.onTokenChosen(tokenAddress); - } else { - this.setState({ - assetView: AssetViews.CONFIRM_TRACK_TOKEN, - chosenTrackTokenAddress: tokenAddress, - }); - } - } - private _onCustomAssetChosen() { - this.setState({ - assetView: AssetViews.NEW_TOKEN_FORM, - }); - } - private _onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) { - this.props.dispatcher.updateTokenStateByAddress({ - [newToken.address]: newTokenState, - }); - trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken); - this.props.dispatcher.addTokenToTokenByAddress(newToken); - this.setState({ - assetView: AssetViews.ASSET_PICKER, - }); - this.props.onTokenChosen(newToken.address); - } - private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { - if (!didUserAcceptTracking) { - this.setState({ - isAddingTokenToTracked: false, - assetView: AssetViews.ASSET_PICKER, - chosenTrackTokenAddress: undefined, - }); - this._onCloseDialog(); - return; - } - this.setState({ - isAddingTokenToTracked: true, - }); - const tokenAddress = this.state.chosenTrackTokenAddress; - const token = this.props.tokenByAddress[tokenAddress]; - const newTokenEntry = { - ...token, - }; + public static defaultProps: Partial<AssetPickerProps> = { + tokenVisibility: TokenVisibility.ALL, + }; + private _dialogConfigsByAssetView: { [assetView: string]: DialogConfigs }; + constructor(props: AssetPickerProps) { + super(props); + this.state = { + assetView: AssetViews.ASSET_PICKER, + hoveredAddress: undefined, + chosenTrackTokenAddress: undefined, + isAddingTokenToTracked: false, + }; + this._dialogConfigsByAssetView = { + [AssetViews.ASSET_PICKER]: { + title: 'Select token', + isModal: false, + actions: [], + }, + [AssetViews.NEW_TOKEN_FORM]: { + title: 'Add an ERC20 token', + isModal: false, + actions: [], + }, + [AssetViews.CONFIRM_TRACK_TOKEN]: { + title: 'Tracking confirmation', + isModal: true, + actions: [ + <FlatButton + key="noTracking" + label="No" + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)} + />, + <FlatButton + key="yesTrack" + label="Yes" + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)} + />, + ], + }, + }; + } + public render() { + const dialogConfigs: DialogConfigs = this._dialogConfigsByAssetView[this.state.assetView]; + return ( + <Dialog + title={dialogConfigs.title} + titleStyle={{ fontWeight: 100 }} + modal={dialogConfigs.isModal} + open={this.props.isOpen} + actions={dialogConfigs.actions} + onRequestClose={this._onCloseDialog.bind(this)} + > + {this.state.assetView === AssetViews.ASSET_PICKER && this._renderAssetPicker()} + {this.state.assetView === AssetViews.NEW_TOKEN_FORM && ( + <NewTokenForm + blockchain={this.props.blockchain} + onNewTokenSubmitted={this._onNewTokenSubmitted.bind(this)} + tokenByAddress={this.props.tokenByAddress} + /> + )} + {this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN && this._renderConfirmTrackToken()} + </Dialog> + ); + } + private _renderConfirmTrackToken() { + const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress]; + return ( + <TrackTokenConfirmation + tokens={[token]} + tokenByAddress={this.props.tokenByAddress} + networkId={this.props.networkId} + isAddingTokenToTracked={this.state.isAddingTokenToTracked} + /> + ); + } + private _renderAssetPicker() { + return ( + <div + className="clearfix flex flex-wrap" + style={{ + overflowY: 'auto', + maxWidth: 720, + maxHeight: 356, + marginBottom: 10, + }} + > + {this._renderGridTiles()} + </div> + ); + } + private _renderGridTiles() { + let isHovered; + let tileStyles; + const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => { + if ( + (this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) || + (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked) + ) { + return null; // Skip + } + isHovered = this.state.hoveredAddress === address; + tileStyles = { + cursor: 'pointer', + opacity: isHovered ? 0.6 : 1, + }; + return ( + <div + key={address} + style={{ + width: TILE_DIMENSION, + height: TILE_DIMENSION, + ...tileStyles, + }} + className="p2 mx-auto" + onClick={this._onChooseToken.bind(this, address)} + onMouseEnter={this._onToggleHover.bind(this, address, true)} + onMouseLeave={this._onToggleHover.bind(this, address, false)} + > + <div className="p1 center"> + <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} /> + </div> + <div className="center">{token.name}</div> + </div> + ); + }); + const otherTokenKey = 'otherToken'; + isHovered = this.state.hoveredAddress === otherTokenKey; + tileStyles = { + cursor: 'pointer', + opacity: isHovered ? 0.6 : 1, + }; + if (this.props.tokenVisibility !== TokenVisibility.TRACKED) { + gridTiles.push( + <div + key={otherTokenKey} + style={{ + width: TILE_DIMENSION, + height: TILE_DIMENSION, + ...tileStyles, + }} + className="p2 mx-auto" + onClick={this._onCustomAssetChosen.bind(this)} + onMouseEnter={this._onToggleHover.bind(this, otherTokenKey, true)} + onMouseLeave={this._onToggleHover.bind(this, otherTokenKey, false)} + > + <div className="p1 center"> + <i + style={{ fontSize: 105, paddingLeft: 1, paddingRight: 1 }} + className="zmdi zmdi-plus-circle" + /> + </div> + <div className="center">Other ERC20 Token</div> + </div>, + ); + } + return gridTiles; + } + private _onToggleHover(address: string, isHovered: boolean) { + const hoveredAddress = isHovered ? address : undefined; + this.setState({ + hoveredAddress, + }); + } + private _onCloseDialog() { + this.setState({ + assetView: AssetViews.ASSET_PICKER, + }); + this.props.onTokenChosen(this.props.currentTokenAddress); + } + private _onChooseToken(tokenAddress: string) { + const token = this.props.tokenByAddress[tokenAddress]; + if (token.isTracked) { + this.props.onTokenChosen(tokenAddress); + } else { + this.setState({ + assetView: AssetViews.CONFIRM_TRACK_TOKEN, + chosenTrackTokenAddress: tokenAddress, + }); + } + } + private _onCustomAssetChosen() { + this.setState({ + assetView: AssetViews.NEW_TOKEN_FORM, + }); + } + private _onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) { + this.props.dispatcher.updateTokenStateByAddress({ + [newToken.address]: newTokenState, + }); + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken); + this.props.dispatcher.addTokenToTokenByAddress(newToken); + this.setState({ + assetView: AssetViews.ASSET_PICKER, + }); + this.props.onTokenChosen(newToken.address); + } + private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + if (!didUserAcceptTracking) { + this.setState({ + isAddingTokenToTracked: false, + assetView: AssetViews.ASSET_PICKER, + chosenTrackTokenAddress: undefined, + }); + this._onCloseDialog(); + return; + } + this.setState({ + isAddingTokenToTracked: true, + }); + const tokenAddress = this.state.chosenTrackTokenAddress; + const token = this.props.tokenByAddress[tokenAddress]; + const newTokenEntry = { + ...token, + }; - newTokenEntry.isTracked = true; - trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); + newTokenEntry.isTracked = true; + trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); - const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( - token.address, - ); - this.props.dispatcher.updateTokenStateByAddress({ - [token.address]: { - balance, - allowance, - }, - }); - this.props.dispatcher.updateTokenByAddress([newTokenEntry]); - this.setState({ - isAddingTokenToTracked: false, - assetView: AssetViews.ASSET_PICKER, - chosenTrackTokenAddress: undefined, - }); - this.props.onTokenChosen(tokenAddress); - } + const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( + token.address, + ); + this.props.dispatcher.updateTokenStateByAddress({ + [token.address]: { + balance, + allowance, + }, + }); + this.props.dispatcher.updateTokenByAddress([newTokenEntry]); + this.setState({ + isAddingTokenToTracked: false, + assetView: AssetViews.ASSET_PICKER, + chosenTrackTokenAddress: undefined, + }); + this.props.onTokenChosen(tokenAddress); + } } diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx index b10b2d609..3ae0d48a7 100644 --- a/packages/website/ts/components/generate_order/generate_order_form.tsx +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -19,335 +19,335 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { orderSchema } from 'ts/schemas/order_schema'; import { SchemaValidator } from 'ts/schemas/validator'; import { - AlertTypes, - BlockchainErrs, - HashData, - Side, - SideToAssetToken, - SignatureData, - Token, - TokenByAddress, - TokenStateByAddress, + AlertTypes, + BlockchainErrs, + HashData, + Side, + SideToAssetToken, + SignatureData, + Token, + TokenByAddress, + TokenStateByAddress, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; enum SigningState { - UNSIGNED, - SIGNING, - SIGNED, + UNSIGNED, + SIGNING, + SIGNED, } interface GenerateOrderFormProps { - blockchain: Blockchain; - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - dispatcher: Dispatcher; - hashData: HashData; - orderExpiryTimestamp: BigNumber; - networkId: number; - userAddress: string; - orderSignatureData: SignatureData; - orderTakerAddress: string; - orderSalt: BigNumber; - sideToAssetToken: SideToAssetToken; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + hashData: HashData; + orderExpiryTimestamp: BigNumber; + networkId: number; + userAddress: string; + orderSignatureData: SignatureData; + orderTakerAddress: string; + orderSalt: BigNumber; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; } interface GenerateOrderFormState { - globalErrMsg: string; - shouldShowIncompleteErrs: boolean; - signingState: SigningState; + globalErrMsg: string; + shouldShowIncompleteErrs: boolean; + signingState: SigningState; } export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, GenerateOrderFormState> { - private _validator: SchemaValidator; - constructor(props: GenerateOrderFormProps) { - super(props); - this.state = { - globalErrMsg: '', - shouldShowIncompleteErrs: false, - signingState: SigningState.UNSIGNED, - }; - this._validator = new SchemaValidator(); - } - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - const dispatcher = this.props.dispatcher; - const depositTokenAddress = this.props.sideToAssetToken[Side.Deposit].address; - const depositToken = this.props.tokenByAddress[depositTokenAddress]; - const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress]; - const receiveTokenAddress = this.props.sideToAssetToken[Side.Receive].address; - const receiveToken = this.props.tokenByAddress[receiveTokenAddress]; - const receiveTokenState = this.props.tokenStateByAddress[receiveTokenAddress]; - const takerExplanation = - 'If a taker is specified, only they are<br> \ + private _validator: SchemaValidator; + constructor(props: GenerateOrderFormProps) { + super(props); + this.state = { + globalErrMsg: '', + shouldShowIncompleteErrs: false, + signingState: SigningState.UNSIGNED, + }; + this._validator = new SchemaValidator(); + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + const dispatcher = this.props.dispatcher; + const depositTokenAddress = this.props.sideToAssetToken[Side.Deposit].address; + const depositToken = this.props.tokenByAddress[depositTokenAddress]; + const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress]; + const receiveTokenAddress = this.props.sideToAssetToken[Side.Receive].address; + const receiveToken = this.props.tokenByAddress[receiveTokenAddress]; + const receiveTokenState = this.props.tokenStateByAddress[receiveTokenAddress]; + const takerExplanation = + 'If a taker is specified, only they are<br> \ allowed to fill this order. If no taker is<br> \ specified, anyone is able to fill it.'; - const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists(); - return ( - <div className="clearfix mb2 lg-px4 md-px4 sm-px2"> - <h3>Generate an order</h3> - <Divider /> - <div className="mx-auto" style={{ maxWidth: 580 }}> - <div className="pt3"> - <div className="mx-auto clearfix"> - <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2"> - <TokenInput - userAddress={this.props.userAddress} - blockchain={this.props.blockchain} - blockchainErr={this.props.blockchainErr} - dispatcher={this.props.dispatcher} - label="Selling" - side={Side.Deposit} - networkId={this.props.networkId} - assetToken={this.props.sideToAssetToken[Side.Deposit]} - updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)} - tokenByAddress={this.props.tokenByAddress} - /> - <TokenAmountInput - label="Sell amount" - token={depositToken} - tokenState={depositTokenState} - amount={this.props.sideToAssetToken[Side.Deposit].amount} - onChange={this._onTokenAmountChange.bind(this, depositToken, Side.Deposit)} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - shouldCheckBalance={true} - shouldCheckAllowance={true} - /> - </div> - <div className="lg-col md-col lg-col-2 md-col-2 sm-col sm-col-2 xs-hide"> - <div className="p1"> - <SwapIcon swapTokensFn={dispatcher.swapAssetTokenSymbols.bind(dispatcher)} /> - </div> - </div> - <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2"> - <TokenInput - userAddress={this.props.userAddress} - blockchain={this.props.blockchain} - blockchainErr={this.props.blockchainErr} - dispatcher={this.props.dispatcher} - label="Buying" - side={Side.Receive} - networkId={this.props.networkId} - assetToken={this.props.sideToAssetToken[Side.Receive]} - updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)} - tokenByAddress={this.props.tokenByAddress} - /> - <TokenAmountInput - label="Receive amount" - token={receiveToken} - tokenState={receiveTokenState} - amount={this.props.sideToAssetToken[Side.Receive].amount} - onChange={this._onTokenAmountChange.bind(this, receiveToken, Side.Receive)} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - shouldCheckBalance={false} - shouldCheckAllowance={false} - /> - </div> - </div> - </div> - <div className="pt1 sm-pb2 lg-px4 md-px4"> - <div className="lg-px3 md-px3"> - <div style={{ fontSize: 12, color: colors.grey }}>Expiration</div> - <ExpirationInput - orderExpiryTimestamp={this.props.orderExpiryTimestamp} - updateOrderExpiry={dispatcher.updateOrderExpiry.bind(dispatcher)} - /> - </div> - </div> - <div className="pt1 flex mx-auto"> - <IdenticonAddressInput - label="Taker" - initialAddress={this.props.orderTakerAddress} - updateOrderAddress={this._updateOrderAddress.bind(this)} - /> - <div className="pt3"> - <div className="pl1"> - <HelpTooltip explanation={takerExplanation} /> - </div> - </div> - </div> - <div> - <HashInput - blockchain={this.props.blockchain} - blockchainIsLoaded={this.props.blockchainIsLoaded} - hashData={this.props.hashData} - label="Order Hash" - /> - </div> - <div className="pt2"> - <div className="center"> - <LifeCycleRaisedButton - labelReady="Sign hash" - labelLoading="Signing..." - labelComplete="Hash signed!" - onClickAsyncFn={this._onSignClickedAsync.bind(this)} - /> - </div> - {this.state.globalErrMsg !== '' && ( - <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} /> - )} - </div> - </div> - <Dialog - title="Order JSON" - titleStyle={{ fontWeight: 100 }} - modal={false} - open={this.state.signingState === SigningState.SIGNED} - onRequestClose={this._onCloseOrderJSONDialog.bind(this)} - > - <OrderJSON - exchangeContractIfExists={exchangeContractIfExists} - orderExpiryTimestamp={this.props.orderExpiryTimestamp} - orderSignatureData={this.props.orderSignatureData} - orderTakerAddress={this.props.orderTakerAddress} - orderMakerAddress={this.props.userAddress} - orderSalt={this.props.orderSalt} - orderMakerFee={this.props.hashData.makerFee} - orderTakerFee={this.props.hashData.takerFee} - orderFeeRecipient={this.props.hashData.feeRecipientAddress} - networkId={this.props.networkId} - sideToAssetToken={this.props.sideToAssetToken} - tokenByAddress={this.props.tokenByAddress} - /> - </Dialog> - </div> - ); - } - private _onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) { - this.props.dispatcher.updateChosenAssetToken(side, { - address: token.address, - amount, - }); - } - private _onCloseOrderJSONDialog() { - // Upon closing the order JSON dialog, we update the orderSalt stored in the Redux store - // with a new value so that if a user signs the identical order again, the newly signed - // orderHash will not collide with the previously generated orderHash. - this.props.dispatcher.updateOrderSalt(ZeroEx.generatePseudoRandomSalt()); - this.setState({ - signingState: SigningState.UNSIGNED, - }); - } - private async _onSignClickedAsync(): Promise<boolean> { - if (this.props.blockchainErr !== BlockchainErrs.NoError) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return false; - } + const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists(); + return ( + <div className="clearfix mb2 lg-px4 md-px4 sm-px2"> + <h3>Generate an order</h3> + <Divider /> + <div className="mx-auto" style={{ maxWidth: 580 }}> + <div className="pt3"> + <div className="mx-auto clearfix"> + <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2"> + <TokenInput + userAddress={this.props.userAddress} + blockchain={this.props.blockchain} + blockchainErr={this.props.blockchainErr} + dispatcher={this.props.dispatcher} + label="Selling" + side={Side.Deposit} + networkId={this.props.networkId} + assetToken={this.props.sideToAssetToken[Side.Deposit]} + updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)} + tokenByAddress={this.props.tokenByAddress} + /> + <TokenAmountInput + label="Sell amount" + token={depositToken} + tokenState={depositTokenState} + amount={this.props.sideToAssetToken[Side.Deposit].amount} + onChange={this._onTokenAmountChange.bind(this, depositToken, Side.Deposit)} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + shouldCheckBalance={true} + shouldCheckAllowance={true} + /> + </div> + <div className="lg-col md-col lg-col-2 md-col-2 sm-col sm-col-2 xs-hide"> + <div className="p1"> + <SwapIcon swapTokensFn={dispatcher.swapAssetTokenSymbols.bind(dispatcher)} /> + </div> + </div> + <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2"> + <TokenInput + userAddress={this.props.userAddress} + blockchain={this.props.blockchain} + blockchainErr={this.props.blockchainErr} + dispatcher={this.props.dispatcher} + label="Buying" + side={Side.Receive} + networkId={this.props.networkId} + assetToken={this.props.sideToAssetToken[Side.Receive]} + updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)} + tokenByAddress={this.props.tokenByAddress} + /> + <TokenAmountInput + label="Receive amount" + token={receiveToken} + tokenState={receiveTokenState} + amount={this.props.sideToAssetToken[Side.Receive].amount} + onChange={this._onTokenAmountChange.bind(this, receiveToken, Side.Receive)} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + shouldCheckBalance={false} + shouldCheckAllowance={false} + /> + </div> + </div> + </div> + <div className="pt1 sm-pb2 lg-px4 md-px4"> + <div className="lg-px3 md-px3"> + <div style={{ fontSize: 12, color: colors.grey }}>Expiration</div> + <ExpirationInput + orderExpiryTimestamp={this.props.orderExpiryTimestamp} + updateOrderExpiry={dispatcher.updateOrderExpiry.bind(dispatcher)} + /> + </div> + </div> + <div className="pt1 flex mx-auto"> + <IdenticonAddressInput + label="Taker" + initialAddress={this.props.orderTakerAddress} + updateOrderAddress={this._updateOrderAddress.bind(this)} + /> + <div className="pt3"> + <div className="pl1"> + <HelpTooltip explanation={takerExplanation} /> + </div> + </div> + </div> + <div> + <HashInput + blockchain={this.props.blockchain} + blockchainIsLoaded={this.props.blockchainIsLoaded} + hashData={this.props.hashData} + label="Order Hash" + /> + </div> + <div className="pt2"> + <div className="center"> + <LifeCycleRaisedButton + labelReady="Sign hash" + labelLoading="Signing..." + labelComplete="Hash signed!" + onClickAsyncFn={this._onSignClickedAsync.bind(this)} + /> + </div> + {this.state.globalErrMsg !== '' && ( + <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} /> + )} + </div> + </div> + <Dialog + title="Order JSON" + titleStyle={{ fontWeight: 100 }} + modal={false} + open={this.state.signingState === SigningState.SIGNED} + onRequestClose={this._onCloseOrderJSONDialog.bind(this)} + > + <OrderJSON + exchangeContractIfExists={exchangeContractIfExists} + orderExpiryTimestamp={this.props.orderExpiryTimestamp} + orderSignatureData={this.props.orderSignatureData} + orderTakerAddress={this.props.orderTakerAddress} + orderMakerAddress={this.props.userAddress} + orderSalt={this.props.orderSalt} + orderMakerFee={this.props.hashData.makerFee} + orderTakerFee={this.props.hashData.takerFee} + orderFeeRecipient={this.props.hashData.feeRecipientAddress} + networkId={this.props.networkId} + sideToAssetToken={this.props.sideToAssetToken} + tokenByAddress={this.props.tokenByAddress} + /> + </Dialog> + </div> + ); + } + private _onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) { + this.props.dispatcher.updateChosenAssetToken(side, { + address: token.address, + amount, + }); + } + private _onCloseOrderJSONDialog() { + // Upon closing the order JSON dialog, we update the orderSalt stored in the Redux store + // with a new value so that if a user signs the identical order again, the newly signed + // orderHash will not collide with the previously generated orderHash. + this.props.dispatcher.updateOrderSalt(ZeroEx.generatePseudoRandomSalt()); + this.setState({ + signingState: SigningState.UNSIGNED, + }); + } + private async _onSignClickedAsync(): Promise<boolean> { + if (this.props.blockchainErr !== BlockchainErrs.NoError) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } - // Check if all required inputs were supplied - const debitToken = this.props.sideToAssetToken[Side.Deposit]; - const debitBalance = this.props.tokenStateByAddress[debitToken.address].balance; - const debitAllowance = this.props.tokenStateByAddress[debitToken.address].allowance; - const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount; - if ( - !_.isUndefined(debitToken.amount) && - !_.isUndefined(receiveAmount) && - debitToken.amount.gt(0) && - receiveAmount.gt(0) && - this.props.userAddress !== '' && - debitBalance.gte(debitToken.amount) && - debitAllowance.gte(debitToken.amount) - ) { - const didSignSuccessfully = await this._signTransactionAsync(); - if (didSignSuccessfully) { - this.setState({ - globalErrMsg: '', - shouldShowIncompleteErrs: false, - }); - } - return didSignSuccessfully; - } else { - let globalErrMsg = 'You must fix the above errors in order to generate a valid order'; - if (this.props.userAddress === '') { - globalErrMsg = 'You must enable wallet communication'; - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - } - this.setState({ - globalErrMsg, - shouldShowIncompleteErrs: true, - }); - return false; - } - } - private async _signTransactionAsync(): Promise<boolean> { - this.setState({ - signingState: SigningState.SIGNING, - }); - const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); - if (_.isUndefined(exchangeContractAddr)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - this.setState({ - signingState: SigningState.UNSIGNED, - }); - return false; - } - const hashData = this.props.hashData; + // Check if all required inputs were supplied + const debitToken = this.props.sideToAssetToken[Side.Deposit]; + const debitBalance = this.props.tokenStateByAddress[debitToken.address].balance; + const debitAllowance = this.props.tokenStateByAddress[debitToken.address].allowance; + const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount; + if ( + !_.isUndefined(debitToken.amount) && + !_.isUndefined(receiveAmount) && + debitToken.amount.gt(0) && + receiveAmount.gt(0) && + this.props.userAddress !== '' && + debitBalance.gte(debitToken.amount) && + debitAllowance.gte(debitToken.amount) + ) { + const didSignSuccessfully = await this._signTransactionAsync(); + if (didSignSuccessfully) { + this.setState({ + globalErrMsg: '', + shouldShowIncompleteErrs: false, + }); + } + return didSignSuccessfully; + } else { + let globalErrMsg = 'You must fix the above errors in order to generate a valid order'; + if (this.props.userAddress === '') { + globalErrMsg = 'You must enable wallet communication'; + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } + this.setState({ + globalErrMsg, + shouldShowIncompleteErrs: true, + }); + return false; + } + } + private async _signTransactionAsync(): Promise<boolean> { + this.setState({ + signingState: SigningState.SIGNING, + }); + const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists(); + if (_.isUndefined(exchangeContractAddr)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + this.setState({ + signingState: SigningState.UNSIGNED, + }); + return false; + } + const hashData = this.props.hashData; - const zeroExOrder: Order = { - exchangeContractAddress: exchangeContractAddr, - expirationUnixTimestampSec: hashData.orderExpiryTimestamp, - feeRecipient: hashData.feeRecipientAddress, - maker: hashData.orderMakerAddress, - makerFee: hashData.makerFee, - makerTokenAddress: hashData.depositTokenContractAddr, - makerTokenAmount: hashData.depositAmount, - salt: hashData.orderSalt, - taker: hashData.orderTakerAddress, - takerFee: hashData.takerFee, - takerTokenAddress: hashData.receiveTokenContractAddr, - takerTokenAmount: hashData.receiveAmount, - }; - const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); + const zeroExOrder: Order = { + exchangeContractAddress: exchangeContractAddr, + expirationUnixTimestampSec: hashData.orderExpiryTimestamp, + feeRecipient: hashData.feeRecipientAddress, + maker: hashData.orderMakerAddress, + makerFee: hashData.makerFee, + makerTokenAddress: hashData.depositTokenContractAddr, + makerTokenAmount: hashData.depositAmount, + salt: hashData.orderSalt, + taker: hashData.orderTakerAddress, + takerFee: hashData.takerFee, + takerTokenAddress: hashData.receiveTokenContractAddr, + takerTokenAmount: hashData.receiveAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(zeroExOrder); - let globalErrMsg = ''; - try { - const signatureData = await this.props.blockchain.signOrderHashAsync(orderHash); - const order = utils.generateOrder( - this.props.networkId, - exchangeContractAddr, - this.props.sideToAssetToken, - hashData.orderExpiryTimestamp, - this.props.orderTakerAddress, - this.props.userAddress, - hashData.makerFee, - hashData.takerFee, - hashData.feeRecipientAddress, - signatureData, - this.props.tokenByAddress, - hashData.orderSalt, - ); - const validationResult = this._validator.validate(order, orderSchema); - if (validationResult.errors.length > 0) { - globalErrMsg = 'Order signing failed. Please refresh and try again'; - utils.consoleLog(`Unexpected error occured: Order validation failed: + let globalErrMsg = ''; + try { + const signatureData = await this.props.blockchain.signOrderHashAsync(orderHash); + const order = utils.generateOrder( + this.props.networkId, + exchangeContractAddr, + this.props.sideToAssetToken, + hashData.orderExpiryTimestamp, + this.props.orderTakerAddress, + this.props.userAddress, + hashData.makerFee, + hashData.takerFee, + hashData.feeRecipientAddress, + signatureData, + this.props.tokenByAddress, + hashData.orderSalt, + ); + const validationResult = this._validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + globalErrMsg = 'Order signing failed. Please refresh and try again'; + utils.consoleLog(`Unexpected error occured: Order validation failed: ${validationResult.errors}`); - } - } catch (err) { - const errMsg = `${err}`; - if (utils.didUserDenyWeb3Request(errMsg)) { - globalErrMsg = 'User denied sign request'; - } else { - globalErrMsg = 'An unexpected error occured. Please try refreshing the page'; - utils.consoleLog(`Unexpected error occured: ${err}`); - utils.consoleLog(err.stack); - await errorReporter.reportAsync(err); - } - } - this.setState({ - signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED, - globalErrMsg, - }); - return globalErrMsg === ''; - } - private _updateOrderAddress(address?: string): void { - if (!_.isUndefined(address)) { - this.props.dispatcher.updateOrderTakerAddress(address); - } - } + } + } catch (err) { + const errMsg = `${err}`; + if (utils.didUserDenyWeb3Request(errMsg)) { + globalErrMsg = 'User denied sign request'; + } else { + globalErrMsg = 'An unexpected error occured. Please try refreshing the page'; + utils.consoleLog(`Unexpected error occured: ${err}`); + utils.consoleLog(err.stack); + await errorReporter.reportAsync(err); + } + } + this.setState({ + signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED, + globalErrMsg, + }); + return globalErrMsg === ''; + } + private _updateOrderAddress(address?: string): void { + if (!_.isUndefined(address)) { + this.props.dispatcher.updateOrderTakerAddress(address); + } + } } diff --git a/packages/website/ts/components/generate_order/new_token_form.tsx b/packages/website/ts/components/generate_order/new_token_form.tsx index d61aac92a..63645be9a 100644 --- a/packages/website/ts/components/generate_order/new_token_form.tsx +++ b/packages/website/ts/components/generate_order/new_token_form.tsx @@ -11,227 +11,227 @@ import { AlertTypes, Token, TokenByAddress, TokenState } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface NewTokenFormProps { - blockchain: Blockchain; - tokenByAddress: TokenByAddress; - onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void; + blockchain: Blockchain; + tokenByAddress: TokenByAddress; + onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void; } interface NewTokenFormState { - globalErrMsg: string; - name: string; - nameErrText: string; - symbol: string; - symbolErrText: string; - address: string; - shouldShowAddressIncompleteErr: boolean; - decimals: string; - decimalsErrText: string; + globalErrMsg: string; + name: string; + nameErrText: string; + symbol: string; + symbolErrText: string; + address: string; + shouldShowAddressIncompleteErr: boolean; + decimals: string; + decimalsErrText: string; } export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFormState> { - constructor(props: NewTokenFormProps) { - super(props); - this.state = { - address: '', - globalErrMsg: '', - name: '', - nameErrText: '', - shouldShowAddressIncompleteErr: false, - symbol: '', - symbolErrText: '', - decimals: '18', - decimalsErrText: '', - }; - } - public render() { - return ( - <div className="mx-auto pb2" style={{ width: 256 }}> - <div> - <TextField - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey }} - floatingLabelText={<RequiredLabel label="Name" />} - value={this.state.name} - errorText={this.state.nameErrText} - onChange={this._onTokenNameChanged.bind(this)} - /> - </div> - <div> - <TextField - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey }} - floatingLabelText={<RequiredLabel label="Symbol" />} - value={this.state.symbol} - errorText={this.state.symbolErrText} - onChange={this._onTokenSymbolChanged.bind(this)} - /> - </div> - <div> - <AddressInput - isRequired={true} - label="Contract address" - initialAddress="" - shouldShowIncompleteErrs={this.state.shouldShowAddressIncompleteErr} - updateAddress={this._onTokenAddressChanged.bind(this)} - /> - </div> - <div> - <TextField - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey }} - floatingLabelText={<RequiredLabel label="Decimals" />} - value={this.state.decimals} - errorText={this.state.decimalsErrText} - onChange={this._onTokenDecimalsChanged.bind(this)} - /> - </div> - <div className="pt2 mx-auto" style={{ width: 120 }}> - <LifeCycleRaisedButton - labelReady="Add" - labelLoading="Adding..." - labelComplete="Added!" - onClickAsyncFn={this._onAddNewTokenClickAsync.bind(this)} - /> - </div> - {this.state.globalErrMsg !== '' && <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />} - </div> - ); - } - private async _onAddNewTokenClickAsync() { - // Trigger validation of name and symbol - this._onTokenNameChanged(undefined, this.state.name); - this._onTokenSymbolChanged(undefined, this.state.symbol); - this._onTokenDecimalsChanged(undefined, this.state.decimals); + constructor(props: NewTokenFormProps) { + super(props); + this.state = { + address: '', + globalErrMsg: '', + name: '', + nameErrText: '', + shouldShowAddressIncompleteErr: false, + symbol: '', + symbolErrText: '', + decimals: '18', + decimalsErrText: '', + }; + } + public render() { + return ( + <div className="mx-auto pb2" style={{ width: 256 }}> + <div> + <TextField + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey }} + floatingLabelText={<RequiredLabel label="Name" />} + value={this.state.name} + errorText={this.state.nameErrText} + onChange={this._onTokenNameChanged.bind(this)} + /> + </div> + <div> + <TextField + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey }} + floatingLabelText={<RequiredLabel label="Symbol" />} + value={this.state.symbol} + errorText={this.state.symbolErrText} + onChange={this._onTokenSymbolChanged.bind(this)} + /> + </div> + <div> + <AddressInput + isRequired={true} + label="Contract address" + initialAddress="" + shouldShowIncompleteErrs={this.state.shouldShowAddressIncompleteErr} + updateAddress={this._onTokenAddressChanged.bind(this)} + /> + </div> + <div> + <TextField + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey }} + floatingLabelText={<RequiredLabel label="Decimals" />} + value={this.state.decimals} + errorText={this.state.decimalsErrText} + onChange={this._onTokenDecimalsChanged.bind(this)} + /> + </div> + <div className="pt2 mx-auto" style={{ width: 120 }}> + <LifeCycleRaisedButton + labelReady="Add" + labelLoading="Adding..." + labelComplete="Added!" + onClickAsyncFn={this._onAddNewTokenClickAsync.bind(this)} + /> + </div> + {this.state.globalErrMsg !== '' && <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />} + </div> + ); + } + private async _onAddNewTokenClickAsync() { + // Trigger validation of name and symbol + this._onTokenNameChanged(undefined, this.state.name); + this._onTokenSymbolChanged(undefined, this.state.symbol); + this._onTokenDecimalsChanged(undefined, this.state.decimals); - const isAddressIncomplete = this.state.address === ''; - let doesContractExist = false; - if (!isAddressIncomplete) { - doesContractExist = await this.props.blockchain.doesContractExistAtAddressAsync(this.state.address); - } + const isAddressIncomplete = this.state.address === ''; + let doesContractExist = false; + if (!isAddressIncomplete) { + doesContractExist = await this.props.blockchain.doesContractExistAtAddressAsync(this.state.address); + } - let hasBalanceAllowanceErr = false; - let balance = new BigNumber(0); - let allowance = new BigNumber(0); - if (doesContractExist) { - try { - [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( - this.state.address, - ); - } catch (err) { - hasBalanceAllowanceErr = true; - } - } + let hasBalanceAllowanceErr = false; + let balance = new BigNumber(0); + let allowance = new BigNumber(0); + if (doesContractExist) { + try { + [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( + this.state.address, + ); + } catch (err) { + hasBalanceAllowanceErr = true; + } + } - let globalErrMsg = ''; - if ( - this.state.nameErrText !== '' || - this.state.symbolErrText !== '' || - this.state.decimalsErrText !== '' || - isAddressIncomplete - ) { - globalErrMsg = 'Please fix the above issues'; - } else if (!doesContractExist) { - globalErrMsg = 'No contract found at supplied address'; - } else if (hasBalanceAllowanceErr) { - globalErrMsg = 'Unsuccessful call to `balanceOf` and/or `allowance` on supplied contract address'; - } else if (!isAddressIncomplete && !_.isUndefined(this.props.tokenByAddress[this.state.address])) { - globalErrMsg = 'A token already exists with this address'; - } + let globalErrMsg = ''; + if ( + this.state.nameErrText !== '' || + this.state.symbolErrText !== '' || + this.state.decimalsErrText !== '' || + isAddressIncomplete + ) { + globalErrMsg = 'Please fix the above issues'; + } else if (!doesContractExist) { + globalErrMsg = 'No contract found at supplied address'; + } else if (hasBalanceAllowanceErr) { + globalErrMsg = 'Unsuccessful call to `balanceOf` and/or `allowance` on supplied contract address'; + } else if (!isAddressIncomplete && !_.isUndefined(this.props.tokenByAddress[this.state.address])) { + globalErrMsg = 'A token already exists with this address'; + } - if (globalErrMsg !== '') { - this.setState({ - globalErrMsg, - shouldShowAddressIncompleteErr: isAddressIncomplete, - }); - return; - } + if (globalErrMsg !== '') { + this.setState({ + globalErrMsg, + shouldShowAddressIncompleteErr: isAddressIncomplete, + }); + return; + } - const newToken: Token = { - address: this.state.address, - decimals: _.parseInt(this.state.decimals), - iconUrl: undefined, - name: this.state.name, - symbol: this.state.symbol.toUpperCase(), - isTracked: true, - isRegistered: false, - }; - const newTokenState: TokenState = { - balance, - allowance, - }; - this.props.onNewTokenSubmitted(newToken, newTokenState); - } - private _onTokenNameChanged(e: any, name: string) { - let nameErrText = ''; - const maxLength = 30; - const tokens = _.values(this.props.tokenByAddress); - const tokenWithNameIfExists = _.find(tokens, { name }); - const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists); - if (name === '') { - nameErrText = 'Name is required'; - } else if (!this._isValidName(name)) { - nameErrText = 'Name should only contain letters, digits and spaces'; - } else if (name.length > maxLength) { - nameErrText = `Max length is ${maxLength}`; - } else if (tokenWithNameExists) { - nameErrText = 'Token with this name already exists'; - } + const newToken: Token = { + address: this.state.address, + decimals: _.parseInt(this.state.decimals), + iconUrl: undefined, + name: this.state.name, + symbol: this.state.symbol.toUpperCase(), + isTracked: true, + isRegistered: false, + }; + const newTokenState: TokenState = { + balance, + allowance, + }; + this.props.onNewTokenSubmitted(newToken, newTokenState); + } + private _onTokenNameChanged(e: any, name: string) { + let nameErrText = ''; + const maxLength = 30; + const tokens = _.values(this.props.tokenByAddress); + const tokenWithNameIfExists = _.find(tokens, { name }); + const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists); + if (name === '') { + nameErrText = 'Name is required'; + } else if (!this._isValidName(name)) { + nameErrText = 'Name should only contain letters, digits and spaces'; + } else if (name.length > maxLength) { + nameErrText = `Max length is ${maxLength}`; + } else if (tokenWithNameExists) { + nameErrText = 'Token with this name already exists'; + } - this.setState({ - name, - nameErrText, - }); - } - private _onTokenSymbolChanged(e: any, symbol: string) { - let symbolErrText = ''; - const maxLength = 5; - const tokens = _.values(this.props.tokenByAddress); - const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, { symbol })); - if (symbol === '') { - symbolErrText = 'Symbol is required'; - } else if (!this._isAlphanumeric(symbol)) { - symbolErrText = 'Can only include alphanumeric characters'; - } else if (symbol.length > maxLength) { - symbolErrText = `Max length is ${maxLength}`; - } else if (tokenWithSymbolExists) { - symbolErrText = 'Token with symbol already exists'; - } + this.setState({ + name, + nameErrText, + }); + } + private _onTokenSymbolChanged(e: any, symbol: string) { + let symbolErrText = ''; + const maxLength = 5; + const tokens = _.values(this.props.tokenByAddress); + const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, { symbol })); + if (symbol === '') { + symbolErrText = 'Symbol is required'; + } else if (!this._isAlphanumeric(symbol)) { + symbolErrText = 'Can only include alphanumeric characters'; + } else if (symbol.length > maxLength) { + symbolErrText = `Max length is ${maxLength}`; + } else if (tokenWithSymbolExists) { + symbolErrText = 'Token with symbol already exists'; + } - this.setState({ - symbol, - symbolErrText, - }); - } - private _onTokenDecimalsChanged(e: any, decimals: string) { - let decimalsErrText = ''; - const maxLength = 2; - if (decimals === '') { - decimalsErrText = 'Decimals is required'; - } else if (!this._isInteger(decimals)) { - decimalsErrText = 'Must be an integer'; - } else if (decimals.length > maxLength) { - decimalsErrText = `Max length is ${maxLength}`; - } + this.setState({ + symbol, + symbolErrText, + }); + } + private _onTokenDecimalsChanged(e: any, decimals: string) { + let decimalsErrText = ''; + const maxLength = 2; + if (decimals === '') { + decimalsErrText = 'Decimals is required'; + } else if (!this._isInteger(decimals)) { + decimalsErrText = 'Must be an integer'; + } else if (decimals.length > maxLength) { + decimalsErrText = `Max length is ${maxLength}`; + } - this.setState({ - decimals, - decimalsErrText, - }); - } - private _onTokenAddressChanged(address?: string) { - if (!_.isUndefined(address)) { - this.setState({ - address, - }); - } - } - private _isValidName(input: string) { - return /^[a-z0-9 ]+$/i.test(input); - } - private _isInteger(input: string) { - return /^[0-9]+$/i.test(input); - } - private _isAlphanumeric(input: string) { - return /^[a-zA-Z0-9]+$/i.test(input); - } + this.setState({ + decimals, + decimalsErrText, + }); + } + private _onTokenAddressChanged(address?: string) { + if (!_.isUndefined(address)) { + this.setState({ + address, + }); + } + } + private _isValidName(input: string) { + return /^[a-z0-9 ]+$/i.test(input); + } + private _isInteger(input: string) { + return /^[0-9]+$/i.test(input); + } + private _isAlphanumeric(input: string) { + return /^[a-zA-Z0-9]+$/i.test(input); + } } diff --git a/packages/website/ts/components/inputs/address_input.tsx b/packages/website/ts/components/inputs/address_input.tsx index 236bf9a00..dd4131140 100644 --- a/packages/website/ts/components/inputs/address_input.tsx +++ b/packages/website/ts/components/inputs/address_input.tsx @@ -6,66 +6,66 @@ import { RequiredLabel } from 'ts/components/ui/required_label'; import { colors } from 'ts/utils/colors'; interface AddressInputProps { - disabled?: boolean; - initialAddress: string; - isRequired?: boolean; - hintText?: string; - shouldHideLabel?: boolean; - label?: string; - shouldShowIncompleteErrs?: boolean; - updateAddress: (address?: string) => void; + disabled?: boolean; + initialAddress: string; + isRequired?: boolean; + hintText?: string; + shouldHideLabel?: boolean; + label?: string; + shouldShowIncompleteErrs?: boolean; + updateAddress: (address?: string) => void; } interface AddressInputState { - address: string; - errMsg: string; + address: string; + errMsg: string; } export class AddressInput extends React.Component<AddressInputProps, AddressInputState> { - constructor(props: AddressInputProps) { - super(props); - this.state = { - address: this.props.initialAddress, - errMsg: '', - }; - } - public componentWillReceiveProps(nextProps: AddressInputProps) { - if (nextProps.shouldShowIncompleteErrs && this.props.isRequired && this.state.address === '') { - this.setState({ - errMsg: 'Address is required', - }); - } - } - public render() { - const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; - const labelDisplay = this.props.shouldHideLabel ? 'hidden' : 'block'; - const hintText = this.props.hintText ? this.props.hintText : ''; - return ( - <div className="overflow-hidden"> - <TextField - id={`address-field-${this.props.label}`} - disabled={_.isUndefined(this.props.disabled) ? false : this.props.disabled} - fullWidth={true} - hintText={hintText} - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey, display: labelDisplay }} - floatingLabelText={label} - errorText={this.state.errMsg} - value={this.state.address} - onChange={this._onOrderTakerAddressUpdated.bind(this)} - /> - </div> - ); - } - private _onOrderTakerAddressUpdated(e: any) { - const address = e.target.value.toLowerCase(); - const isValidAddress = addressUtils.isAddress(address) || address === ''; - const errMsg = isValidAddress ? '' : 'Invalid ethereum address'; - this.setState({ - address, - errMsg, - }); - const addressIfValid = isValidAddress ? address : undefined; - this.props.updateAddress(addressIfValid); - } + constructor(props: AddressInputProps) { + super(props); + this.state = { + address: this.props.initialAddress, + errMsg: '', + }; + } + public componentWillReceiveProps(nextProps: AddressInputProps) { + if (nextProps.shouldShowIncompleteErrs && this.props.isRequired && this.state.address === '') { + this.setState({ + errMsg: 'Address is required', + }); + } + } + public render() { + const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; + const labelDisplay = this.props.shouldHideLabel ? 'hidden' : 'block'; + const hintText = this.props.hintText ? this.props.hintText : ''; + return ( + <div className="overflow-hidden"> + <TextField + id={`address-field-${this.props.label}`} + disabled={_.isUndefined(this.props.disabled) ? false : this.props.disabled} + fullWidth={true} + hintText={hintText} + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey, display: labelDisplay }} + floatingLabelText={label} + errorText={this.state.errMsg} + value={this.state.address} + onChange={this._onOrderTakerAddressUpdated.bind(this)} + /> + </div> + ); + } + private _onOrderTakerAddressUpdated(e: any) { + const address = e.target.value.toLowerCase(); + const isValidAddress = addressUtils.isAddress(address) || address === ''; + const errMsg = isValidAddress ? '' : 'Invalid ethereum address'; + this.setState({ + address, + errMsg, + }); + const addressIfValid = isValidAddress ? address : undefined; + this.props.updateAddress(addressIfValid); + } } diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index 245784824..da46db4f4 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -11,83 +11,83 @@ import { utils } from 'ts/utils/utils'; const DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); interface AllowanceToggleProps { - blockchain: Blockchain; - dispatcher: Dispatcher; - onErrorOccurred: (errType: BalanceErrs) => void; - token: Token; - tokenState: TokenState; - userAddress: string; + blockchain: Blockchain; + dispatcher: Dispatcher; + onErrorOccurred: (errType: BalanceErrs) => void; + token: Token; + tokenState: TokenState; + userAddress: string; } interface AllowanceToggleState { - isSpinnerVisible: boolean; - prevAllowance: BigNumber; + isSpinnerVisible: boolean; + prevAllowance: BigNumber; } export class AllowanceToggle extends React.Component<AllowanceToggleProps, AllowanceToggleState> { - constructor(props: AllowanceToggleProps) { - super(props); - this.state = { - isSpinnerVisible: false, - prevAllowance: props.tokenState.allowance, - }; - } - public componentWillReceiveProps(nextProps: AllowanceToggleProps) { - if (!nextProps.tokenState.allowance.eq(this.state.prevAllowance)) { - this.setState({ - isSpinnerVisible: false, - prevAllowance: nextProps.tokenState.allowance, - }); - } - } - public render() { - return ( - <div className="flex"> - <div> - <Toggle - disabled={this.state.isSpinnerVisible} - toggled={this._isAllowanceSet()} - onToggle={this._onToggleAllowanceAsync.bind(this)} - /> - </div> - {this.state.isSpinnerVisible && ( - <div className="pl1" style={{ paddingTop: 3 }}> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </div> - )} - </div> - ); - } - private async _onToggleAllowanceAsync(): Promise<void> { - if (this.props.userAddress === '') { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - } + constructor(props: AllowanceToggleProps) { + super(props); + this.state = { + isSpinnerVisible: false, + prevAllowance: props.tokenState.allowance, + }; + } + public componentWillReceiveProps(nextProps: AllowanceToggleProps) { + if (!nextProps.tokenState.allowance.eq(this.state.prevAllowance)) { + this.setState({ + isSpinnerVisible: false, + prevAllowance: nextProps.tokenState.allowance, + }); + } + } + public render() { + return ( + <div className="flex"> + <div> + <Toggle + disabled={this.state.isSpinnerVisible} + toggled={this._isAllowanceSet()} + onToggle={this._onToggleAllowanceAsync.bind(this)} + /> + </div> + {this.state.isSpinnerVisible && ( + <div className="pl1" style={{ paddingTop: 3 }}> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </div> + )} + </div> + ); + } + private async _onToggleAllowanceAsync(): Promise<void> { + if (this.props.userAddress === '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + } - this.setState({ - isSpinnerVisible: true, - }); + this.setState({ + isSpinnerVisible: true, + }); - let newAllowanceAmountInBaseUnits = new BigNumber(0); - if (!this._isAllowanceSet()) { - newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS; - } - try { - await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits); - } catch (err) { - this.setState({ - isSpinnerVisible: false, - }); - const errMsg = `${err}`; - if (_.includes(errMsg, 'User denied transaction')) { - return; - } - utils.consoleLog(`Unexpected error encountered: ${err}`); - utils.consoleLog(err.stack); - this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed); - await errorReporter.reportAsync(err); - } - } - private _isAllowanceSet() { - return !this.props.tokenState.allowance.eq(0); - } + let newAllowanceAmountInBaseUnits = new BigNumber(0); + if (!this._isAllowanceSet()) { + newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS; + } + try { + await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits); + } catch (err) { + this.setState({ + isSpinnerVisible: false, + }); + const errMsg = `${err}`; + if (_.includes(errMsg, 'User denied transaction')) { + return; + } + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed); + await errorReporter.reportAsync(err); + } + } + private _isAllowanceSet() { + return !this.props.tokenState.allowance.eq(0); + } } diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx index 5cc91994e..ddc434b51 100644 --- a/packages/website/ts/components/inputs/balance_bounded_input.tsx +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -9,143 +9,143 @@ import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface BalanceBoundedInputProps { - label?: string; - balance: BigNumber; - amount?: BigNumber; - onChange: ValidatedBigNumberCallback; - shouldShowIncompleteErrs?: boolean; - shouldCheckBalance: boolean; - validate?: (amount: BigNumber) => InputErrMsg; - onVisitBalancesPageClick?: () => void; - shouldHideVisitBalancesLink?: boolean; + label?: string; + balance: BigNumber; + amount?: BigNumber; + onChange: ValidatedBigNumberCallback; + shouldShowIncompleteErrs?: boolean; + shouldCheckBalance: boolean; + validate?: (amount: BigNumber) => InputErrMsg; + onVisitBalancesPageClick?: () => void; + shouldHideVisitBalancesLink?: boolean; } interface BalanceBoundedInputState { - errMsg: InputErrMsg; - amountString: string; + errMsg: InputErrMsg; + amountString: string; } export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProps, BalanceBoundedInputState> { - public static defaultProps: Partial<BalanceBoundedInputProps> = { - shouldShowIncompleteErrs: false, - shouldHideVisitBalancesLink: false, - }; - constructor(props: BalanceBoundedInputProps) { - super(props); - const amountString = this.props.amount ? this.props.amount.toString() : ''; - this.state = { - errMsg: this._validate(amountString, props.balance), - amountString, - }; - } - public componentWillReceiveProps(nextProps: BalanceBoundedInputProps) { - if (nextProps === this.props) { - return; - } - const isCurrentAmountNumeric = utils.isNumeric(this.state.amountString); - if (!_.isUndefined(nextProps.amount)) { - let shouldResetState = false; - if (!isCurrentAmountNumeric) { - shouldResetState = true; - } else { - const currentAmount = new BigNumber(this.state.amountString); - if (!currentAmount.eq(nextProps.amount) || !nextProps.balance.eq(this.props.balance)) { - shouldResetState = true; - } - } - if (shouldResetState) { - const amountString = nextProps.amount.toString(); - this.setState({ - errMsg: this._validate(amountString, nextProps.balance), - amountString, - }); - } - } else if (isCurrentAmountNumeric) { - const amountString = ''; - this.setState({ - errMsg: this._validate(amountString, nextProps.balance), - amountString, - }); - } - } - public render() { - let errorText = this.state.errMsg; - if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') { - errorText = 'This field is required'; - } - let label: React.ReactNode | string = ''; - if (!_.isUndefined(this.props.label)) { - label = <RequiredLabel label={this.props.label} />; - } - return ( - <TextField - fullWidth={true} - floatingLabelText={label} - floatingLabelFixed={true} - floatingLabelStyle={{ color: colors.grey, width: 206 }} - errorText={errorText} - value={this.state.amountString} - hintText={<span style={{ textTransform: 'capitalize' }}>amount</span>} - onChange={this._onValueChange.bind(this)} - underlineStyle={{ width: 'calc(100% + 50px)' }} - /> - ); - } - private _onValueChange(e: any, amountString: string) { - const errMsg = this._validate(amountString, this.props.balance); - this.setState( - { - amountString, - errMsg, - }, - () => { - const isValid = _.isUndefined(errMsg); - if (utils.isNumeric(amountString)) { - this.props.onChange(isValid, new BigNumber(amountString)); - } else { - this.props.onChange(isValid); - } - }, - ); - } - private _validate(amountString: string, balance: BigNumber): InputErrMsg { - if (!utils.isNumeric(amountString)) { - return amountString !== '' ? 'Must be a number' : ''; - } - const amount = new BigNumber(amountString); - if (amount.eq(0)) { - return 'Cannot be zero'; - } - if (this.props.shouldCheckBalance && amount.gt(balance)) { - return <span>Insufficient balance. {this._renderIncreaseBalanceLink()}</span>; - } - const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount); - return errMsg; - } - private _renderIncreaseBalanceLink() { - if (this.props.shouldHideVisitBalancesLink) { - return null; - } + public static defaultProps: Partial<BalanceBoundedInputProps> = { + shouldShowIncompleteErrs: false, + shouldHideVisitBalancesLink: false, + }; + constructor(props: BalanceBoundedInputProps) { + super(props); + const amountString = this.props.amount ? this.props.amount.toString() : ''; + this.state = { + errMsg: this._validate(amountString, props.balance), + amountString, + }; + } + public componentWillReceiveProps(nextProps: BalanceBoundedInputProps) { + if (nextProps === this.props) { + return; + } + const isCurrentAmountNumeric = utils.isNumeric(this.state.amountString); + if (!_.isUndefined(nextProps.amount)) { + let shouldResetState = false; + if (!isCurrentAmountNumeric) { + shouldResetState = true; + } else { + const currentAmount = new BigNumber(this.state.amountString); + if (!currentAmount.eq(nextProps.amount) || !nextProps.balance.eq(this.props.balance)) { + shouldResetState = true; + } + } + if (shouldResetState) { + const amountString = nextProps.amount.toString(); + this.setState({ + errMsg: this._validate(amountString, nextProps.balance), + amountString, + }); + } + } else if (isCurrentAmountNumeric) { + const amountString = ''; + this.setState({ + errMsg: this._validate(amountString, nextProps.balance), + amountString, + }); + } + } + public render() { + let errorText = this.state.errMsg; + if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') { + errorText = 'This field is required'; + } + let label: React.ReactNode | string = ''; + if (!_.isUndefined(this.props.label)) { + label = <RequiredLabel label={this.props.label} />; + } + return ( + <TextField + fullWidth={true} + floatingLabelText={label} + floatingLabelFixed={true} + floatingLabelStyle={{ color: colors.grey, width: 206 }} + errorText={errorText} + value={this.state.amountString} + hintText={<span style={{ textTransform: 'capitalize' }}>amount</span>} + onChange={this._onValueChange.bind(this)} + underlineStyle={{ width: 'calc(100% + 50px)' }} + /> + ); + } + private _onValueChange(e: any, amountString: string) { + const errMsg = this._validate(amountString, this.props.balance); + this.setState( + { + amountString, + errMsg, + }, + () => { + const isValid = _.isUndefined(errMsg); + if (utils.isNumeric(amountString)) { + this.props.onChange(isValid, new BigNumber(amountString)); + } else { + this.props.onChange(isValid); + } + }, + ); + } + private _validate(amountString: string, balance: BigNumber): InputErrMsg { + if (!utils.isNumeric(amountString)) { + return amountString !== '' ? 'Must be a number' : ''; + } + const amount = new BigNumber(amountString); + if (amount.eq(0)) { + return 'Cannot be zero'; + } + if (this.props.shouldCheckBalance && amount.gt(balance)) { + return <span>Insufficient balance. {this._renderIncreaseBalanceLink()}</span>; + } + const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount); + return errMsg; + } + private _renderIncreaseBalanceLink() { + if (this.props.shouldHideVisitBalancesLink) { + return null; + } - const increaseBalanceText = 'Increase balance'; - const linkStyle = { - cursor: 'pointer', - color: colors.darkestGrey, - textDecoration: 'underline', - display: 'inline', - }; - if (_.isUndefined(this.props.onVisitBalancesPageClick)) { - return ( - <Link to={`${WebsitePaths.Portal}/balances`} style={linkStyle}> - {increaseBalanceText} - </Link> - ); - } else { - return ( - <div onClick={this.props.onVisitBalancesPageClick} style={linkStyle}> - {increaseBalanceText} - </div> - ); - } - } + const increaseBalanceText = 'Increase balance'; + const linkStyle = { + cursor: 'pointer', + color: colors.darkestGrey, + textDecoration: 'underline', + display: 'inline', + }; + if (_.isUndefined(this.props.onVisitBalancesPageClick)) { + return ( + <Link to={`${WebsitePaths.Portal}/balances`} style={linkStyle}> + {increaseBalanceText} + </Link> + ); + } else { + return ( + <div onClick={this.props.onVisitBalancesPageClick} style={linkStyle}> + {increaseBalanceText} + </div> + ); + } + } } diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx index 7f9747094..a66f92c8c 100644 --- a/packages/website/ts/components/inputs/eth_amount_input.tsx +++ b/packages/website/ts/components/inputs/eth_amount_input.tsx @@ -7,43 +7,43 @@ import { ValidatedBigNumberCallback } from 'ts/types'; import { constants } from 'ts/utils/constants'; interface EthAmountInputProps { - label?: string; - balance: BigNumber; - amount?: BigNumber; - onChange: ValidatedBigNumberCallback; - shouldShowIncompleteErrs: boolean; - onVisitBalancesPageClick?: () => void; - shouldCheckBalance: boolean; - shouldHideVisitBalancesLink?: boolean; + label?: string; + balance: BigNumber; + amount?: BigNumber; + onChange: ValidatedBigNumberCallback; + shouldShowIncompleteErrs: boolean; + onVisitBalancesPageClick?: () => void; + shouldCheckBalance: boolean; + shouldHideVisitBalancesLink?: boolean; } interface EthAmountInputState {} export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmountInputState> { - public render() { - const amount = this.props.amount - ? ZeroEx.toUnitAmount(this.props.amount, constants.DECIMAL_PLACES_ETH) - : undefined; - return ( - <div className="flex overflow-hidden" style={{ height: 63 }}> - <BalanceBoundedInput - label={this.props.label} - balance={this.props.balance} - amount={amount} - onChange={this._onChange.bind(this)} - shouldCheckBalance={this.props.shouldCheckBalance} - shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} - shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink} - /> - <div style={{ paddingTop: _.isUndefined(this.props.label) ? 15 : 40 }}>ETH</div> - </div> - ); - } - private _onChange(isValid: boolean, amount?: BigNumber) { - const baseUnitAmountIfExists = _.isUndefined(amount) - ? undefined - : ZeroEx.toBaseUnitAmount(amount, constants.DECIMAL_PLACES_ETH); - this.props.onChange(isValid, baseUnitAmountIfExists); - } + public render() { + const amount = this.props.amount + ? ZeroEx.toUnitAmount(this.props.amount, constants.DECIMAL_PLACES_ETH) + : undefined; + return ( + <div className="flex overflow-hidden" style={{ height: 63 }}> + <BalanceBoundedInput + label={this.props.label} + balance={this.props.balance} + amount={amount} + onChange={this._onChange.bind(this)} + shouldCheckBalance={this.props.shouldCheckBalance} + shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} + onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} + shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink} + /> + <div style={{ paddingTop: _.isUndefined(this.props.label) ? 15 : 40 }}>ETH</div> + </div> + ); + } + private _onChange(isValid: boolean, amount?: BigNumber) { + const baseUnitAmountIfExists = _.isUndefined(amount) + ? undefined + : ZeroEx.toBaseUnitAmount(amount, constants.DECIMAL_PLACES_ETH); + this.props.onChange(isValid, baseUnitAmountIfExists); + } } diff --git a/packages/website/ts/components/inputs/expiration_input.tsx b/packages/website/ts/components/inputs/expiration_input.tsx index cb4ed7bd0..e473648d2 100644 --- a/packages/website/ts/components/inputs/expiration_input.tsx +++ b/packages/website/ts/components/inputs/expiration_input.tsx @@ -7,94 +7,94 @@ import * as React from 'react'; import { utils } from 'ts/utils/utils'; interface ExpirationInputProps { - orderExpiryTimestamp: BigNumber; - updateOrderExpiry: (unixTimestampSec: BigNumber) => void; + orderExpiryTimestamp: BigNumber; + updateOrderExpiry: (unixTimestampSec: BigNumber) => void; } interface ExpirationInputState { - dateMoment: moment.Moment; - timeMoment: moment.Moment; + dateMoment: moment.Moment; + timeMoment: moment.Moment; } export class ExpirationInput extends React.Component<ExpirationInputProps, ExpirationInputState> { - private _earliestPickableMoment: moment.Moment; - constructor(props: ExpirationInputProps) { - super(props); - // Set the earliest pickable date to today at 00:00, so users can only pick the current or later dates - this._earliestPickableMoment = moment().startOf('day'); - const expirationMoment = utils.convertToMomentFromUnixTimestamp(props.orderExpiryTimestamp); - const initialOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); - const didUserSetExpiry = !initialOrderExpiryTimestamp.eq(props.orderExpiryTimestamp); - this.state = { - dateMoment: didUserSetExpiry ? expirationMoment : undefined, - timeMoment: didUserSetExpiry ? expirationMoment : undefined, - }; - } - public render() { - const date = this.state.dateMoment ? this.state.dateMoment.toDate() : undefined; - const time = this.state.timeMoment ? this.state.timeMoment.toDate() : undefined; - return ( - <div className="clearfix"> - <div className="col col-6 overflow-hidden pr3 flex relative"> - <DatePicker - className="overflow-hidden" - hintText="Date" - mode="landscape" - autoOk={true} - value={date} - onChange={this._onDateChanged.bind(this)} - shouldDisableDate={this._shouldDisableDate.bind(this)} - /> - <div className="absolute" style={{ fontSize: 20, right: 40, top: 13, pointerEvents: 'none' }}> - <i className="zmdi zmdi-calendar" /> - </div> - </div> - <div className="col col-5 overflow-hidden flex relative"> - <TimePicker - className="overflow-hidden" - hintText="Time" - autoOk={true} - value={time} - onChange={this._onTimeChanged.bind(this)} - /> - <div className="absolute" style={{ fontSize: 20, right: 9, top: 13, pointerEvents: 'none' }}> - <i className="zmdi zmdi-time" /> - </div> - </div> - <div onClick={this._clearDates.bind(this)} className="col col-1 pt2" style={{ textAlign: 'right' }}> - <i style={{ fontSize: 16, cursor: 'pointer' }} className="zmdi zmdi-close" /> - </div> - </div> - ); - } - private _shouldDisableDate(date: Date): boolean { - return moment(date) - .startOf('day') - .isBefore(this._earliestPickableMoment); - } - private _clearDates() { - this.setState({ - dateMoment: undefined, - timeMoment: undefined, - }); - const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec(); - this.props.updateOrderExpiry(defaultDateTime); - } - private _onDateChanged(e: any, date: Date) { - const dateMoment = moment(date); - this.setState({ - dateMoment, - }); - const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment); - this.props.updateOrderExpiry(timestamp); - } - private _onTimeChanged(e: any, time: Date) { - const timeMoment = moment(time); - this.setState({ - timeMoment, - }); - const dateMoment = _.isUndefined(this.state.dateMoment) ? moment() : this.state.dateMoment; - const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, timeMoment); - this.props.updateOrderExpiry(timestamp); - } + private _earliestPickableMoment: moment.Moment; + constructor(props: ExpirationInputProps) { + super(props); + // Set the earliest pickable date to today at 00:00, so users can only pick the current or later dates + this._earliestPickableMoment = moment().startOf('day'); + const expirationMoment = utils.convertToMomentFromUnixTimestamp(props.orderExpiryTimestamp); + const initialOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); + const didUserSetExpiry = !initialOrderExpiryTimestamp.eq(props.orderExpiryTimestamp); + this.state = { + dateMoment: didUserSetExpiry ? expirationMoment : undefined, + timeMoment: didUserSetExpiry ? expirationMoment : undefined, + }; + } + public render() { + const date = this.state.dateMoment ? this.state.dateMoment.toDate() : undefined; + const time = this.state.timeMoment ? this.state.timeMoment.toDate() : undefined; + return ( + <div className="clearfix"> + <div className="col col-6 overflow-hidden pr3 flex relative"> + <DatePicker + className="overflow-hidden" + hintText="Date" + mode="landscape" + autoOk={true} + value={date} + onChange={this._onDateChanged.bind(this)} + shouldDisableDate={this._shouldDisableDate.bind(this)} + /> + <div className="absolute" style={{ fontSize: 20, right: 40, top: 13, pointerEvents: 'none' }}> + <i className="zmdi zmdi-calendar" /> + </div> + </div> + <div className="col col-5 overflow-hidden flex relative"> + <TimePicker + className="overflow-hidden" + hintText="Time" + autoOk={true} + value={time} + onChange={this._onTimeChanged.bind(this)} + /> + <div className="absolute" style={{ fontSize: 20, right: 9, top: 13, pointerEvents: 'none' }}> + <i className="zmdi zmdi-time" /> + </div> + </div> + <div onClick={this._clearDates.bind(this)} className="col col-1 pt2" style={{ textAlign: 'right' }}> + <i style={{ fontSize: 16, cursor: 'pointer' }} className="zmdi zmdi-close" /> + </div> + </div> + ); + } + private _shouldDisableDate(date: Date): boolean { + return moment(date) + .startOf('day') + .isBefore(this._earliestPickableMoment); + } + private _clearDates() { + this.setState({ + dateMoment: undefined, + timeMoment: undefined, + }); + const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec(); + this.props.updateOrderExpiry(defaultDateTime); + } + private _onDateChanged(e: any, date: Date) { + const dateMoment = moment(date); + this.setState({ + dateMoment, + }); + const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment); + this.props.updateOrderExpiry(timestamp); + } + private _onTimeChanged(e: any, time: Date) { + const timeMoment = moment(time); + this.setState({ + timeMoment, + }); + const dateMoment = _.isUndefined(this.state.dateMoment) ? moment() : this.state.dateMoment; + const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, timeMoment); + this.props.updateOrderExpiry(timestamp); + } } diff --git a/packages/website/ts/components/inputs/hash_input.tsx b/packages/website/ts/components/inputs/hash_input.tsx index 36d7e6140..5a3d34fe6 100644 --- a/packages/website/ts/components/inputs/hash_input.tsx +++ b/packages/website/ts/components/inputs/hash_input.tsx @@ -8,55 +8,55 @@ import { HashData, Styles } from 'ts/types'; import { constants } from 'ts/utils/constants'; const styles: Styles = { - textField: { - overflow: 'hidden', - paddingTop: 8, - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - }, + textField: { + overflow: 'hidden', + paddingTop: 8, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, }; interface HashInputProps { - blockchain: Blockchain; - blockchainIsLoaded: boolean; - hashData: HashData; - label: string; + blockchain: Blockchain; + blockchainIsLoaded: boolean; + hashData: HashData; + label: string; } interface HashInputState {} export class HashInput extends React.Component<HashInputProps, HashInputState> { - public render() { - const msgHashHex = this.props.blockchainIsLoaded ? this._generateMessageHashHex() : ''; - return ( - <div> - <FakeTextField label={this.props.label}> - <div style={styles.textField} data-tip={true} data-for="hashTooltip"> - {msgHashHex} - </div> - </FakeTextField> - <ReactTooltip id="hashTooltip">{msgHashHex}</ReactTooltip> - </div> - ); - } - private _generateMessageHashHex() { - const exchangeContractAddress = this.props.blockchain.getExchangeContractAddressIfExists(); - const hashData = this.props.hashData; - const order: Order = { - exchangeContractAddress, - expirationUnixTimestampSec: hashData.orderExpiryTimestamp, - feeRecipient: hashData.feeRecipientAddress, - maker: _.isEmpty(hashData.orderMakerAddress) ? constants.NULL_ADDRESS : hashData.orderMakerAddress, - makerFee: hashData.makerFee, - makerTokenAddress: hashData.depositTokenContractAddr, - makerTokenAmount: hashData.depositAmount, - salt: hashData.orderSalt, - taker: hashData.orderTakerAddress, - takerFee: hashData.takerFee, - takerTokenAddress: hashData.receiveTokenContractAddr, - takerTokenAmount: hashData.receiveAmount, - }; - const orderHash = ZeroEx.getOrderHashHex(order); - return orderHash; - } + public render() { + const msgHashHex = this.props.blockchainIsLoaded ? this._generateMessageHashHex() : ''; + return ( + <div> + <FakeTextField label={this.props.label}> + <div style={styles.textField} data-tip={true} data-for="hashTooltip"> + {msgHashHex} + </div> + </FakeTextField> + <ReactTooltip id="hashTooltip">{msgHashHex}</ReactTooltip> + </div> + ); + } + private _generateMessageHashHex() { + const exchangeContractAddress = this.props.blockchain.getExchangeContractAddressIfExists(); + const hashData = this.props.hashData; + const order: Order = { + exchangeContractAddress, + expirationUnixTimestampSec: hashData.orderExpiryTimestamp, + feeRecipient: hashData.feeRecipientAddress, + maker: _.isEmpty(hashData.orderMakerAddress) ? constants.NULL_ADDRESS : hashData.orderMakerAddress, + makerFee: hashData.makerFee, + makerTokenAddress: hashData.depositTokenContractAddr, + makerTokenAmount: hashData.depositAmount, + salt: hashData.orderSalt, + taker: hashData.orderTakerAddress, + takerFee: hashData.takerFee, + takerTokenAddress: hashData.receiveTokenContractAddr, + takerTokenAmount: hashData.receiveAmount, + }; + const orderHash = ZeroEx.getOrderHashHex(order); + return orderHash; + } } diff --git a/packages/website/ts/components/inputs/identicon_address_input.tsx b/packages/website/ts/components/inputs/identicon_address_input.tsx index f14cb4e9c..4cf9af64d 100644 --- a/packages/website/ts/components/inputs/identicon_address_input.tsx +++ b/packages/website/ts/components/inputs/identicon_address_input.tsx @@ -6,48 +6,48 @@ import { InputLabel } from 'ts/components/ui/input_label'; import { RequiredLabel } from 'ts/components/ui/required_label'; interface IdenticonAddressInputProps { - initialAddress: string; - isRequired?: boolean; - label: string; - updateOrderAddress: (address?: string) => void; + initialAddress: string; + isRequired?: boolean; + label: string; + updateOrderAddress: (address?: string) => void; } interface IdenticonAddressInputState { - address: string; + address: string; } export class IdenticonAddressInput extends React.Component<IdenticonAddressInputProps, IdenticonAddressInputState> { - constructor(props: IdenticonAddressInputProps) { - super(props); - this.state = { - address: props.initialAddress, - }; - } - public render() { - const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; - return ( - <div className="relative" style={{ width: '100%' }}> - <InputLabel text={label} /> - <div className="flex"> - <div className="col col-1 pb1 pr1" style={{ paddingTop: 13 }}> - <Identicon address={this.state.address} diameter={26} /> - </div> - <div className="col col-11 pb1 pl1" style={{ height: 65 }}> - <AddressInput - hintText="e.g 0x75bE4F78AA3699B3A348c84bDB2a96c3Db..." - shouldHideLabel={true} - initialAddress={this.props.initialAddress} - updateAddress={this._updateAddress.bind(this)} - /> - </div> - </div> - </div> - ); - } - private _updateAddress(address?: string): void { - this.setState({ - address, - }); - this.props.updateOrderAddress(address); - } + constructor(props: IdenticonAddressInputProps) { + super(props); + this.state = { + address: props.initialAddress, + }; + } + public render() { + const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; + return ( + <div className="relative" style={{ width: '100%' }}> + <InputLabel text={label} /> + <div className="flex"> + <div className="col col-1 pb1 pr1" style={{ paddingTop: 13 }}> + <Identicon address={this.state.address} diameter={26} /> + </div> + <div className="col col-11 pb1 pl1" style={{ height: 65 }}> + <AddressInput + hintText="e.g 0x75bE4F78AA3699B3A348c84bDB2a96c3Db..." + shouldHideLabel={true} + initialAddress={this.props.initialAddress} + updateAddress={this._updateAddress.bind(this)} + /> + </div> + </div> + </div> + ); + } + private _updateAddress(address?: string): void { + this.setState({ + address, + }); + this.props.updateOrderAddress(address); + } } diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index 0a71b2c00..63966d759 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -8,63 +8,63 @@ import { InputErrMsg, Token, TokenState, ValidatedBigNumberCallback, WebsitePath import { colors } from 'ts/utils/colors'; interface TokenAmountInputProps { - token: Token; - tokenState: TokenState; - label?: string; - amount?: BigNumber; - shouldShowIncompleteErrs: boolean; - shouldCheckBalance: boolean; - shouldCheckAllowance: boolean; - onChange: ValidatedBigNumberCallback; - onVisitBalancesPageClick?: () => void; + token: Token; + tokenState: TokenState; + label?: string; + amount?: BigNumber; + shouldShowIncompleteErrs: boolean; + shouldCheckBalance: boolean; + shouldCheckAllowance: boolean; + onChange: ValidatedBigNumberCallback; + onVisitBalancesPageClick?: () => void; } interface TokenAmountInputState {} export class TokenAmountInput extends React.Component<TokenAmountInputProps, TokenAmountInputState> { - public render() { - const amount = this.props.amount - ? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) - : undefined; - const hasLabel = !_.isUndefined(this.props.label); - return ( - <div className="flex overflow-hidden" style={{ height: hasLabel ? 84 : 62 }}> - <BalanceBoundedInput - label={this.props.label} - amount={amount} - balance={ZeroEx.toUnitAmount(this.props.tokenState.balance, this.props.token.decimals)} - onChange={this._onChange.bind(this)} - validate={this._validate.bind(this)} - shouldCheckBalance={this.props.shouldCheckBalance} - shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} - /> - <div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div> - </div> - ); - } - private _onChange(isValid: boolean, amount?: BigNumber) { - let baseUnitAmount; - if (!_.isUndefined(amount)) { - baseUnitAmount = ZeroEx.toBaseUnitAmount(amount, this.props.token.decimals); - } - this.props.onChange(isValid, baseUnitAmount); - } - private _validate(amount: BigNumber): InputErrMsg { - if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) { - return ( - <span> - Insufficient allowance.{' '} - <Link - to={`${WebsitePaths.Portal}/balances`} - style={{ cursor: 'pointer', color: colors.darkestGrey }} - > - Set allowance - </Link> - </span> - ); - } else { - return undefined; - } - } + public render() { + const amount = this.props.amount + ? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) + : undefined; + const hasLabel = !_.isUndefined(this.props.label); + return ( + <div className="flex overflow-hidden" style={{ height: hasLabel ? 84 : 62 }}> + <BalanceBoundedInput + label={this.props.label} + amount={amount} + balance={ZeroEx.toUnitAmount(this.props.tokenState.balance, this.props.token.decimals)} + onChange={this._onChange.bind(this)} + validate={this._validate.bind(this)} + shouldCheckBalance={this.props.shouldCheckBalance} + shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} + onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} + /> + <div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div> + </div> + ); + } + private _onChange(isValid: boolean, amount?: BigNumber) { + let baseUnitAmount; + if (!_.isUndefined(amount)) { + baseUnitAmount = ZeroEx.toBaseUnitAmount(amount, this.props.token.decimals); + } + this.props.onChange(isValid, baseUnitAmount); + } + private _validate(amount: BigNumber): InputErrMsg { + if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) { + return ( + <span> + Insufficient allowance.{' '} + <Link + to={`${WebsitePaths.Portal}/balances`} + style={{ cursor: 'pointer', color: colors.darkestGrey }} + > + Set allowance + </Link> + </span> + ); + } else { + return undefined; + } + } } diff --git a/packages/website/ts/components/inputs/token_input.tsx b/packages/website/ts/components/inputs/token_input.tsx index 3aceacb22..5df19b28c 100644 --- a/packages/website/ts/components/inputs/token_input.tsx +++ b/packages/website/ts/components/inputs/token_input.tsx @@ -12,93 +12,93 @@ import { colors } from 'ts/utils/colors'; const TOKEN_ICON_DIMENSION = 80; interface TokenInputProps { - blockchain: Blockchain; - blockchainErr: BlockchainErrs; - dispatcher: Dispatcher; - label: string; - side: Side; - networkId: number; - assetToken: AssetToken; - updateChosenAssetToken: (side: Side, token: AssetToken) => void; - tokenByAddress: TokenByAddress; - userAddress: string; + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + dispatcher: Dispatcher; + label: string; + side: Side; + networkId: number; + assetToken: AssetToken; + updateChosenAssetToken: (side: Side, token: AssetToken) => void; + tokenByAddress: TokenByAddress; + userAddress: string; } interface TokenInputState { - isHoveringIcon: boolean; - isPickerOpen: boolean; - trackCandidateTokenIfExists?: Token; + isHoveringIcon: boolean; + isPickerOpen: boolean; + trackCandidateTokenIfExists?: Token; } export class TokenInput extends React.Component<TokenInputProps, TokenInputState> { - constructor(props: TokenInputProps) { - super(props); - this.state = { - isHoveringIcon: false, - isPickerOpen: false, - }; - } - public render() { - const token = this.props.tokenByAddress[this.props.assetToken.address]; - const iconStyles = { - cursor: 'pointer', - opacity: this.state.isHoveringIcon ? 0.5 : 1, - }; - return ( - <div className="relative"> - <div className="pb1"> - <InputLabel text={this.props.label} /> - </div> - <Paper - zDepth={1} - style={{ cursor: 'pointer' }} - onMouseEnter={this._onToggleHover.bind(this, true)} - onMouseLeave={this._onToggleHover.bind(this, false)} - onClick={this._onAssetClicked.bind(this)} - > - <div className="mx-auto pt2" style={{ width: TOKEN_ICON_DIMENSION, ...iconStyles }}> - <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} /> - </div> - <div className="py1 center" style={{ color: colors.grey }}> - {token.name} - </div> - </Paper> - <AssetPicker - userAddress={this.props.userAddress} - networkId={this.props.networkId} - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - isOpen={this.state.isPickerOpen} - currentTokenAddress={this.props.assetToken.address} - onTokenChosen={this._onTokenChosen.bind(this)} - tokenByAddress={this.props.tokenByAddress} - /> - </div> - ); - } - private _onTokenChosen(tokenAddress: string) { - const assetToken: AssetToken = { - address: tokenAddress, - amount: this.props.assetToken.amount, - }; - this.props.updateChosenAssetToken(this.props.side, assetToken); - this.setState({ - isPickerOpen: false, - }); - } - private _onToggleHover(isHoveringIcon: boolean) { - this.setState({ - isHoveringIcon, - }); - } - private _onAssetClicked() { - if (this.props.blockchainErr !== BlockchainErrs.NoError) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return; - } + constructor(props: TokenInputProps) { + super(props); + this.state = { + isHoveringIcon: false, + isPickerOpen: false, + }; + } + public render() { + const token = this.props.tokenByAddress[this.props.assetToken.address]; + const iconStyles = { + cursor: 'pointer', + opacity: this.state.isHoveringIcon ? 0.5 : 1, + }; + return ( + <div className="relative"> + <div className="pb1"> + <InputLabel text={this.props.label} /> + </div> + <Paper + zDepth={1} + style={{ cursor: 'pointer' }} + onMouseEnter={this._onToggleHover.bind(this, true)} + onMouseLeave={this._onToggleHover.bind(this, false)} + onClick={this._onAssetClicked.bind(this)} + > + <div className="mx-auto pt2" style={{ width: TOKEN_ICON_DIMENSION, ...iconStyles }}> + <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} /> + </div> + <div className="py1 center" style={{ color: colors.grey }}> + {token.name} + </div> + </Paper> + <AssetPicker + userAddress={this.props.userAddress} + networkId={this.props.networkId} + blockchain={this.props.blockchain} + dispatcher={this.props.dispatcher} + isOpen={this.state.isPickerOpen} + currentTokenAddress={this.props.assetToken.address} + onTokenChosen={this._onTokenChosen.bind(this)} + tokenByAddress={this.props.tokenByAddress} + /> + </div> + ); + } + private _onTokenChosen(tokenAddress: string) { + const assetToken: AssetToken = { + address: tokenAddress, + amount: this.props.assetToken.amount, + }; + this.props.updateChosenAssetToken(this.props.side, assetToken); + this.setState({ + isPickerOpen: false, + }); + } + private _onToggleHover(isHoveringIcon: boolean) { + this.setState({ + isHoveringIcon, + }); + } + private _onAssetClicked() { + if (this.props.blockchainErr !== BlockchainErrs.NoError) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } - this.setState({ - isPickerOpen: true, - }); - } + this.setState({ + isPickerOpen: true, + }); + } } diff --git a/packages/website/ts/components/order_json.tsx b/packages/website/ts/components/order_json.tsx index 1640a178e..1b6b32a04 100644 --- a/packages/website/ts/components/order_json.tsx +++ b/packages/website/ts/components/order_json.tsx @@ -11,172 +11,172 @@ import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface OrderJSONProps { - exchangeContractIfExists: string; - orderExpiryTimestamp: BigNumber; - orderSignatureData: SignatureData; - orderTakerAddress: string; - orderMakerAddress: string; - orderSalt: BigNumber; - orderMakerFee: BigNumber; - orderTakerFee: BigNumber; - orderFeeRecipient: string; - networkId: number; - sideToAssetToken: SideToAssetToken; - tokenByAddress: TokenByAddress; + exchangeContractIfExists: string; + orderExpiryTimestamp: BigNumber; + orderSignatureData: SignatureData; + orderTakerAddress: string; + orderMakerAddress: string; + orderSalt: BigNumber; + orderMakerFee: BigNumber; + orderTakerFee: BigNumber; + orderFeeRecipient: string; + networkId: number; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; } interface OrderJSONState { - shareLink: string; + shareLink: string; } export class OrderJSON extends React.Component<OrderJSONProps, OrderJSONState> { - constructor(props: OrderJSONProps) { - super(props); - this.state = { - shareLink: '', - }; - // tslint:disable-next-line:no-floating-promises - this._setShareLinkAsync(); - } - public render() { - const order = utils.generateOrder( - this.props.networkId, - this.props.exchangeContractIfExists, - this.props.sideToAssetToken, - this.props.orderExpiryTimestamp, - this.props.orderTakerAddress, - this.props.orderMakerAddress, - this.props.orderMakerFee, - this.props.orderTakerFee, - this.props.orderFeeRecipient, - this.props.orderSignatureData, - this.props.tokenByAddress, - this.props.orderSalt, - ); - const orderJSON = JSON.stringify(order); - return ( - <div> - <div className="pb2"> - You have successfully generated and cryptographically signed an order! The following JSON contains - the order parameters and cryptographic signature that your counterparty will need to execute a trade - with you. - </div> - <div className="pb2 flex"> - <div className="inline-block pl1" style={{ top: 1 }}> - <CopyIcon data={orderJSON} callToAction="Copy" /> - </div> - </div> - <Paper className="center overflow-hidden"> - <TextField - id="orderJSON" - style={{ width: 710 }} - value={JSON.stringify(order, null, '\t')} - multiLine={true} - rows={2} - rowsMax={8} - underlineStyle={{ display: 'none' }} - /> - </Paper> - <div className="pt3 pb2 center"> - <div>Share your signed order!</div> - <div> - <div className="mx-auto overflow-hidden" style={{ width: 152 }}> - <TextField id={`${this.state.shareLink}-bitly`} value={this.state.shareLink} /> - </div> - </div> - <div className="mx-auto pt1 flex" style={{ width: 91 }}> - <div> - <i - style={{ cursor: 'pointer', fontSize: 29 }} - onClick={this._shareViaFacebook.bind(this)} - className="zmdi zmdi-facebook-box" - /> - </div> - <div className="pl1" style={{ position: 'relative', width: 28 }}> - <i - style={{ - cursor: 'pointer', - fontSize: 32, - position: 'absolute', - top: -2, - left: 8, - }} - onClick={this._shareViaEmailAsync.bind(this)} - className="zmdi zmdi-email" - /> - </div> - <div className="pl1"> - <i - style={{ cursor: 'pointer', fontSize: 29 }} - onClick={this._shareViaTwitterAsync.bind(this)} - className="zmdi zmdi-twitter-box" - /> - </div> - </div> - </div> - </div> - ); - } - private async _shareViaTwitterAsync() { - const tweetText = encodeURIComponent(`Fill my order using the 0x protocol: ${this.state.shareLink}`); - window.open(`https://twitter.com/intent/tweet?text=${tweetText}`, 'Share your order', 'width=500,height=400'); - } - private async _shareViaFacebook() { - (window as any).FB.ui( - { - display: 'popup', - href: this.state.shareLink, - method: 'share', - }, - _.noop, - ); - } - private async _shareViaEmailAsync() { - const encodedSubject = encodeURIComponent("Let's trade using the 0x protocol"); - const encodedBody = encodeURIComponent(`I generated an order with the 0x protocol. + constructor(props: OrderJSONProps) { + super(props); + this.state = { + shareLink: '', + }; + // tslint:disable-next-line:no-floating-promises + this._setShareLinkAsync(); + } + public render() { + const order = utils.generateOrder( + this.props.networkId, + this.props.exchangeContractIfExists, + this.props.sideToAssetToken, + this.props.orderExpiryTimestamp, + this.props.orderTakerAddress, + this.props.orderMakerAddress, + this.props.orderMakerFee, + this.props.orderTakerFee, + this.props.orderFeeRecipient, + this.props.orderSignatureData, + this.props.tokenByAddress, + this.props.orderSalt, + ); + const orderJSON = JSON.stringify(order); + return ( + <div> + <div className="pb2"> + You have successfully generated and cryptographically signed an order! The following JSON contains + the order parameters and cryptographic signature that your counterparty will need to execute a trade + with you. + </div> + <div className="pb2 flex"> + <div className="inline-block pl1" style={{ top: 1 }}> + <CopyIcon data={orderJSON} callToAction="Copy" /> + </div> + </div> + <Paper className="center overflow-hidden"> + <TextField + id="orderJSON" + style={{ width: 710 }} + value={JSON.stringify(order, null, '\t')} + multiLine={true} + rows={2} + rowsMax={8} + underlineStyle={{ display: 'none' }} + /> + </Paper> + <div className="pt3 pb2 center"> + <div>Share your signed order!</div> + <div> + <div className="mx-auto overflow-hidden" style={{ width: 152 }}> + <TextField id={`${this.state.shareLink}-bitly`} value={this.state.shareLink} /> + </div> + </div> + <div className="mx-auto pt1 flex" style={{ width: 91 }}> + <div> + <i + style={{ cursor: 'pointer', fontSize: 29 }} + onClick={this._shareViaFacebook.bind(this)} + className="zmdi zmdi-facebook-box" + /> + </div> + <div className="pl1" style={{ position: 'relative', width: 28 }}> + <i + style={{ + cursor: 'pointer', + fontSize: 32, + position: 'absolute', + top: -2, + left: 8, + }} + onClick={this._shareViaEmailAsync.bind(this)} + className="zmdi zmdi-email" + /> + </div> + <div className="pl1"> + <i + style={{ cursor: 'pointer', fontSize: 29 }} + onClick={this._shareViaTwitterAsync.bind(this)} + className="zmdi zmdi-twitter-box" + /> + </div> + </div> + </div> + </div> + ); + } + private async _shareViaTwitterAsync() { + const tweetText = encodeURIComponent(`Fill my order using the 0x protocol: ${this.state.shareLink}`); + window.open(`https://twitter.com/intent/tweet?text=${tweetText}`, 'Share your order', 'width=500,height=400'); + } + private async _shareViaFacebook() { + (window as any).FB.ui( + { + display: 'popup', + href: this.state.shareLink, + method: 'share', + }, + _.noop, + ); + } + private async _shareViaEmailAsync() { + const encodedSubject = encodeURIComponent("Let's trade using the 0x protocol"); + const encodedBody = encodeURIComponent(`I generated an order with the 0x protocol. You can see and fill it here: ${this.state.shareLink}`); - const mailToLink = `mailto:mail@example.org?subject=${encodedSubject}&body=${encodedBody}`; - window.open(mailToLink, '_blank'); - } - private async _setShareLinkAsync() { - const shareLink = await this._generateShareLinkAsync(); - this.setState({ - shareLink, - }); - } - private async _generateShareLinkAsync(): Promise<string> { - const longUrl = encodeURIComponent(this._getOrderUrl()); - const bitlyRequestUrl = `${constants.URL_BITLY_API}/v3/shorten?access_token=${ - configs.BITLY_ACCESS_TOKEN - }&longUrl=${longUrl}`; - const response = await fetch(bitlyRequestUrl); - const responseBody = await response.text(); - const bodyObj = JSON.parse(responseBody); - if (response.status !== 200 || bodyObj.status_code !== 200) { - // TODO: Show error message in UI - utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); - await errorReporter.reportAsync(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`)); - return ''; - } - return bodyObj.data.url; - } - private _getOrderUrl() { - const order = utils.generateOrder( - this.props.networkId, - this.props.exchangeContractIfExists, - this.props.sideToAssetToken, - this.props.orderExpiryTimestamp, - this.props.orderTakerAddress, - this.props.orderMakerAddress, - this.props.orderMakerFee, - this.props.orderTakerFee, - this.props.orderFeeRecipient, - this.props.orderSignatureData, - this.props.tokenByAddress, - this.props.orderSalt, - ); - const orderJSONString = JSON.stringify(order); - const orderUrl = `${configs.BASE_URL}${WebsitePaths.Portal}/fill?order=${orderJSONString}`; - return orderUrl; - } + const mailToLink = `mailto:mail@example.org?subject=${encodedSubject}&body=${encodedBody}`; + window.open(mailToLink, '_blank'); + } + private async _setShareLinkAsync() { + const shareLink = await this._generateShareLinkAsync(); + this.setState({ + shareLink, + }); + } + private async _generateShareLinkAsync(): Promise<string> { + const longUrl = encodeURIComponent(this._getOrderUrl()); + const bitlyRequestUrl = `${constants.URL_BITLY_API}/v3/shorten?access_token=${ + configs.BITLY_ACCESS_TOKEN + }&longUrl=${longUrl}`; + const response = await fetch(bitlyRequestUrl); + const responseBody = await response.text(); + const bodyObj = JSON.parse(responseBody); + if (response.status !== 200 || bodyObj.status_code !== 200) { + // TODO: Show error message in UI + utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); + await errorReporter.reportAsync(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`)); + return ''; + } + return bodyObj.data.url; + } + private _getOrderUrl() { + const order = utils.generateOrder( + this.props.networkId, + this.props.exchangeContractIfExists, + this.props.sideToAssetToken, + this.props.orderExpiryTimestamp, + this.props.orderTakerAddress, + this.props.orderMakerAddress, + this.props.orderMakerFee, + this.props.orderTakerFee, + this.props.orderFeeRecipient, + this.props.orderSignatureData, + this.props.tokenByAddress, + this.props.orderSalt, + ); + const orderJSONString = JSON.stringify(order); + const orderUrl = `${configs.BASE_URL}${WebsitePaths.Portal}/fill?order=${orderJSONString}`; + return orderUrl; + } } diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index e163a1fa2..e2e28e8b6 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -23,14 +23,14 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { orderSchema } from 'ts/schemas/order_schema'; import { SchemaValidator } from 'ts/schemas/validator'; import { - BlockchainErrs, - HashData, - Order, - ScreenWidths, - Token, - TokenByAddress, - TokenStateByAddress, - WebsitePaths, + BlockchainErrs, + HashData, + Order, + ScreenWidths, + Token, + TokenByAddress, + TokenStateByAddress, + WebsitePaths, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; @@ -42,320 +42,320 @@ const THROTTLE_TIMEOUT = 100; export interface PortalPassedProps {} export interface PortalAllProps { - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - dispatcher: Dispatcher; - hashData: HashData; - networkId: number; - nodeVersion: string; - orderFillAmount: BigNumber; - screenWidth: ScreenWidths; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - userEtherBalance: BigNumber; - userAddress: string; - shouldBlockchainErrDialogBeOpen: boolean; - userSuppliedOrderCache: Order; - location: Location; - flashMessage?: string | React.ReactNode; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + hashData: HashData; + networkId: number; + nodeVersion: string; + orderFillAmount: BigNumber; + screenWidth: ScreenWidths; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userEtherBalance: BigNumber; + userAddress: string; + shouldBlockchainErrDialogBeOpen: boolean; + userSuppliedOrderCache: Order; + location: Location; + flashMessage?: string | React.ReactNode; } interface PortalAllState { - prevNetworkId: number; - prevNodeVersion: string; - prevUserAddress: string; - prevPathname: string; - isDisclaimerDialogOpen: boolean; - isWethNoticeDialogOpen: boolean; + prevNetworkId: number; + prevNodeVersion: string; + prevUserAddress: string; + prevPathname: string; + isDisclaimerDialogOpen: boolean; + isWethNoticeDialogOpen: boolean; } export class Portal extends React.Component<PortalAllProps, PortalAllState> { - private _blockchain: Blockchain; - private _sharedOrderIfExists: Order; - private _throttledScreenWidthUpdate: () => void; - public static hasAlreadyDismissedWethNotice() { - const didDismissWethNotice = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE); - const hasAlreadyDismissedWethNotice = !_.isUndefined(didDismissWethNotice) && !_.isEmpty(didDismissWethNotice); - return hasAlreadyDismissedWethNotice; - } - constructor(props: PortalAllProps) { - super(props); - this._sharedOrderIfExists = this._getSharedOrderIfExists(); - this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + private _blockchain: Blockchain; + private _sharedOrderIfExists: Order; + private _throttledScreenWidthUpdate: () => void; + public static hasAlreadyDismissedWethNotice() { + const didDismissWethNotice = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE); + const hasAlreadyDismissedWethNotice = !_.isUndefined(didDismissWethNotice) && !_.isEmpty(didDismissWethNotice); + return hasAlreadyDismissedWethNotice; + } + constructor(props: PortalAllProps) { + super(props); + this._sharedOrderIfExists = this._getSharedOrderIfExists(); + this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); - const isViewingBalances = _.includes(props.location.pathname, `${WebsitePaths.Portal}/balances`); - const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); + const isViewingBalances = _.includes(props.location.pathname, `${WebsitePaths.Portal}/balances`); + const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); - const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER); - const hasAcceptedDisclaimer = - !_.isUndefined(didAcceptPortalDisclaimer) && !_.isEmpty(didAcceptPortalDisclaimer); - this.state = { - prevNetworkId: this.props.networkId, - prevNodeVersion: this.props.nodeVersion, - prevUserAddress: this.props.userAddress, - prevPathname: this.props.location.pathname, - isDisclaimerDialogOpen: !hasAcceptedDisclaimer, - isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, - }; - } - public componentDidMount() { - window.addEventListener('resize', this._throttledScreenWidthUpdate); - window.scrollTo(0, 0); - } - public componentWillMount() { - this._blockchain = new Blockchain(this.props.dispatcher); - } - public componentWillUnmount() { - this._blockchain.destroy(); - window.removeEventListener('resize', this._throttledScreenWidthUpdate); - // We re-set the entire redux state when the portal is unmounted so that when it is re-rendered - // the initialization process always occurs from the same base state. This helps avoid - // initialization inconsistencies (i.e While the portal was unrendered, the user might have - // become disconnected from their backing Ethereum node, changes user accounts, etc...) - this.props.dispatcher.resetState(); - } - public componentWillReceiveProps(nextProps: PortalAllProps) { - if (nextProps.networkId !== this.state.prevNetworkId) { - // tslint:disable-next-line:no-floating-promises - this._blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId); - this.setState({ - prevNetworkId: nextProps.networkId, - }); - } - if (nextProps.userAddress !== this.state.prevUserAddress) { - // tslint:disable-next-line:no-floating-promises - this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); - if (!_.isEmpty(nextProps.userAddress) && nextProps.blockchainIsLoaded) { - const tokens = _.values(nextProps.tokenByAddress); - // tslint:disable-next-line:no-floating-promises - this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens); - } - this.setState({ - prevUserAddress: nextProps.userAddress, - }); - } - if (nextProps.nodeVersion !== this.state.prevNodeVersion) { - // tslint:disable-next-line:no-floating-promises - this._blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion); - } - if (nextProps.location.pathname !== this.state.prevPathname) { - const isViewingBalances = _.includes(nextProps.location.pathname, `${WebsitePaths.Portal}/balances`); - const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); - this.setState({ - prevPathname: nextProps.location.pathname, - isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, - }); - } - } - public render() { - const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind( - this.props.dispatcher, - ); - const portalStyle: React.CSSProperties = { - minHeight: '100vh', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - }; - const portalMenuContainerStyle: React.CSSProperties = { - overflow: 'hidden', - backgroundColor: colors.darkestGrey, - color: colors.white, - }; - return ( - <div style={portalStyle}> - <DocumentTitle title="0x Portal DApp" /> - <TopBar - userAddress={this.props.userAddress} - blockchainIsLoaded={this.props.blockchainIsLoaded} - location={this.props.location} - /> - <div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}> - <Paper className="mb3 mt2"> - {!configs.IS_MAINNET_ENABLED && this.props.networkId === constants.NETWORK_ID_MAINNET ? ( - <div className="p3 center"> - <div className="h2 py2">Mainnet unavailable</div> - <div className="mx-auto pb2 pt2"> - <img src="/images/zrx_token.png" style={{ width: 150 }} /> - </div> - <div> - 0x portal is currently unavailable on the Ethereum mainnet. - <div>To try it out, switch to the Kovan test network (networkId: 42).</div> - <div className="py2">Check back soon!</div> - </div> - </div> - ) : ( - <div className="mx-auto flex"> - <div className="col col-2 pr2 pt1 sm-hide xs-hide" style={portalMenuContainerStyle}> - <PortalMenu menuItemStyle={{ color: colors.white }} /> - </div> - <div className="col col-12 lg-col-10 md-col-10 sm-col sm-col-12"> - <div className="py2" style={{ backgroundColor: colors.grey50 }}> - {this.props.blockchainIsLoaded ? ( - <Switch> - <Route - path={`${WebsitePaths.Portal}/weth`} - render={this._renderEthWrapper.bind(this)} - /> - <Route - path={`${WebsitePaths.Portal}/fill`} - render={this._renderFillOrder.bind(this)} - /> - <Route - path={`${WebsitePaths.Portal}/balances`} - render={this._renderTokenBalances.bind(this)} - /> - <Route - path={`${WebsitePaths.Portal}/trades`} - component={this._renderTradeHistory.bind(this)} - /> - <Route - path={`${WebsitePaths.Home}`} - render={this._renderGenerateOrderForm.bind(this)} - /> - </Switch> - ) : ( - <Loading /> - )} - </div> - </div> - </div> - )} - </Paper> - <BlockchainErrDialog - blockchain={this._blockchain} - blockchainErr={this.props.blockchainErr} - isOpen={this.props.shouldBlockchainErrDialogBeOpen} - userAddress={this.props.userAddress} - toggleDialogFn={updateShouldBlockchainErrDialogBeOpen} - networkId={this.props.networkId} - /> - <WrappedEthSectionNoticeDialog - isOpen={this.state.isWethNoticeDialogOpen} - onToggleDialog={this._onWethNoticeAccepted.bind(this)} - /> - <PortalDisclaimerDialog - isOpen={this.state.isDisclaimerDialogOpen} - onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} - /> - <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} /> - </div> - <Footer /> - </div> - ); - } - private _renderEthWrapper() { - return ( - <EthWrappers - networkId={this.props.networkId} - blockchain={this._blockchain} - dispatcher={this.props.dispatcher} - tokenByAddress={this.props.tokenByAddress} - tokenStateByAddress={this.props.tokenStateByAddress} - userAddress={this.props.userAddress} - userEtherBalance={this.props.userEtherBalance} - /> - ); - } - private _renderTradeHistory() { - return ( - <TradeHistory - tokenByAddress={this.props.tokenByAddress} - userAddress={this.props.userAddress} - networkId={this.props.networkId} - /> - ); - } - private _renderTokenBalances() { - return ( - <TokenBalances - blockchain={this._blockchain} - blockchainErr={this.props.blockchainErr} - blockchainIsLoaded={this.props.blockchainIsLoaded} - dispatcher={this.props.dispatcher} - screenWidth={this.props.screenWidth} - tokenByAddress={this.props.tokenByAddress} - tokenStateByAddress={this.props.tokenStateByAddress} - userAddress={this.props.userAddress} - userEtherBalance={this.props.userEtherBalance} - networkId={this.props.networkId} - /> - ); - } - private _renderFillOrder(match: any, location: Location, history: History) { - const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) - ? this.props.userSuppliedOrderCache - : this._sharedOrderIfExists; - return ( - <FillOrder - blockchain={this._blockchain} - blockchainErr={this.props.blockchainErr} - initialOrder={initialFillOrder} - isOrderInUrl={!_.isUndefined(this._sharedOrderIfExists)} - orderFillAmount={this.props.orderFillAmount} - networkId={this.props.networkId} - userAddress={this.props.userAddress} - tokenByAddress={this.props.tokenByAddress} - tokenStateByAddress={this.props.tokenStateByAddress} - dispatcher={this.props.dispatcher} - /> - ); - } - private _renderGenerateOrderForm(match: any, location: Location, history: History) { - return ( - <GenerateOrderForm - blockchain={this._blockchain} - hashData={this.props.hashData} - dispatcher={this.props.dispatcher} - /> - ); - } - private _onPortalDisclaimerAccepted() { - localStorage.setItem(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER, 'set'); - this.setState({ - isDisclaimerDialogOpen: false, - }); - } - private _onWethNoticeAccepted() { - localStorage.setItem(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE, 'set'); - this.setState({ - isWethNoticeDialogOpen: false, - }); - } - private _getSharedOrderIfExists(): Order | undefined { - const queryString = window.location.search; - if (queryString.length === 0) { - return undefined; - } - const queryParams = queryString.substring(1).split('&'); - const orderQueryParam = _.find(queryParams, queryParam => { - const queryPair = queryParam.split('='); - return queryPair[0] === 'order'; - }); - if (_.isUndefined(orderQueryParam)) { - return undefined; - } - const orderPair = orderQueryParam.split('='); - if (orderPair.length !== 2) { - return undefined; - } + const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER); + const hasAcceptedDisclaimer = + !_.isUndefined(didAcceptPortalDisclaimer) && !_.isEmpty(didAcceptPortalDisclaimer); + this.state = { + prevNetworkId: this.props.networkId, + prevNodeVersion: this.props.nodeVersion, + prevUserAddress: this.props.userAddress, + prevPathname: this.props.location.pathname, + isDisclaimerDialogOpen: !hasAcceptedDisclaimer, + isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, + }; + } + public componentDidMount() { + window.addEventListener('resize', this._throttledScreenWidthUpdate); + window.scrollTo(0, 0); + } + public componentWillMount() { + this._blockchain = new Blockchain(this.props.dispatcher); + } + public componentWillUnmount() { + this._blockchain.destroy(); + window.removeEventListener('resize', this._throttledScreenWidthUpdate); + // We re-set the entire redux state when the portal is unmounted so that when it is re-rendered + // the initialization process always occurs from the same base state. This helps avoid + // initialization inconsistencies (i.e While the portal was unrendered, the user might have + // become disconnected from their backing Ethereum node, changes user accounts, etc...) + this.props.dispatcher.resetState(); + } + public componentWillReceiveProps(nextProps: PortalAllProps) { + if (nextProps.networkId !== this.state.prevNetworkId) { + // tslint:disable-next-line:no-floating-promises + this._blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId); + this.setState({ + prevNetworkId: nextProps.networkId, + }); + } + if (nextProps.userAddress !== this.state.prevUserAddress) { + // tslint:disable-next-line:no-floating-promises + this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); + if (!_.isEmpty(nextProps.userAddress) && nextProps.blockchainIsLoaded) { + const tokens = _.values(nextProps.tokenByAddress); + // tslint:disable-next-line:no-floating-promises + this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens); + } + this.setState({ + prevUserAddress: nextProps.userAddress, + }); + } + if (nextProps.nodeVersion !== this.state.prevNodeVersion) { + // tslint:disable-next-line:no-floating-promises + this._blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion); + } + if (nextProps.location.pathname !== this.state.prevPathname) { + const isViewingBalances = _.includes(nextProps.location.pathname, `${WebsitePaths.Portal}/balances`); + const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); + this.setState({ + prevPathname: nextProps.location.pathname, + isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, + }); + } + } + public render() { + const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind( + this.props.dispatcher, + ); + const portalStyle: React.CSSProperties = { + minHeight: '100vh', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + }; + const portalMenuContainerStyle: React.CSSProperties = { + overflow: 'hidden', + backgroundColor: colors.darkestGrey, + color: colors.white, + }; + return ( + <div style={portalStyle}> + <DocumentTitle title="0x Portal DApp" /> + <TopBar + userAddress={this.props.userAddress} + blockchainIsLoaded={this.props.blockchainIsLoaded} + location={this.props.location} + /> + <div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}> + <Paper className="mb3 mt2"> + {!configs.IS_MAINNET_ENABLED && this.props.networkId === constants.NETWORK_ID_MAINNET ? ( + <div className="p3 center"> + <div className="h2 py2">Mainnet unavailable</div> + <div className="mx-auto pb2 pt2"> + <img src="/images/zrx_token.png" style={{ width: 150 }} /> + </div> + <div> + 0x portal is currently unavailable on the Ethereum mainnet. + <div>To try it out, switch to the Kovan test network (networkId: 42).</div> + <div className="py2">Check back soon!</div> + </div> + </div> + ) : ( + <div className="mx-auto flex"> + <div className="col col-2 pr2 pt1 sm-hide xs-hide" style={portalMenuContainerStyle}> + <PortalMenu menuItemStyle={{ color: colors.white }} /> + </div> + <div className="col col-12 lg-col-10 md-col-10 sm-col sm-col-12"> + <div className="py2" style={{ backgroundColor: colors.grey50 }}> + {this.props.blockchainIsLoaded ? ( + <Switch> + <Route + path={`${WebsitePaths.Portal}/weth`} + render={this._renderEthWrapper.bind(this)} + /> + <Route + path={`${WebsitePaths.Portal}/fill`} + render={this._renderFillOrder.bind(this)} + /> + <Route + path={`${WebsitePaths.Portal}/balances`} + render={this._renderTokenBalances.bind(this)} + /> + <Route + path={`${WebsitePaths.Portal}/trades`} + component={this._renderTradeHistory.bind(this)} + /> + <Route + path={`${WebsitePaths.Home}`} + render={this._renderGenerateOrderForm.bind(this)} + /> + </Switch> + ) : ( + <Loading /> + )} + </div> + </div> + </div> + )} + </Paper> + <BlockchainErrDialog + blockchain={this._blockchain} + blockchainErr={this.props.blockchainErr} + isOpen={this.props.shouldBlockchainErrDialogBeOpen} + userAddress={this.props.userAddress} + toggleDialogFn={updateShouldBlockchainErrDialogBeOpen} + networkId={this.props.networkId} + /> + <WrappedEthSectionNoticeDialog + isOpen={this.state.isWethNoticeDialogOpen} + onToggleDialog={this._onWethNoticeAccepted.bind(this)} + /> + <PortalDisclaimerDialog + isOpen={this.state.isDisclaimerDialogOpen} + onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} + /> + <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} /> + </div> + <Footer /> + </div> + ); + } + private _renderEthWrapper() { + return ( + <EthWrappers + networkId={this.props.networkId} + blockchain={this._blockchain} + dispatcher={this.props.dispatcher} + tokenByAddress={this.props.tokenByAddress} + tokenStateByAddress={this.props.tokenStateByAddress} + userAddress={this.props.userAddress} + userEtherBalance={this.props.userEtherBalance} + /> + ); + } + private _renderTradeHistory() { + return ( + <TradeHistory + tokenByAddress={this.props.tokenByAddress} + userAddress={this.props.userAddress} + networkId={this.props.networkId} + /> + ); + } + private _renderTokenBalances() { + return ( + <TokenBalances + blockchain={this._blockchain} + blockchainErr={this.props.blockchainErr} + blockchainIsLoaded={this.props.blockchainIsLoaded} + dispatcher={this.props.dispatcher} + screenWidth={this.props.screenWidth} + tokenByAddress={this.props.tokenByAddress} + tokenStateByAddress={this.props.tokenStateByAddress} + userAddress={this.props.userAddress} + userEtherBalance={this.props.userEtherBalance} + networkId={this.props.networkId} + /> + ); + } + private _renderFillOrder(match: any, location: Location, history: History) { + const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) + ? this.props.userSuppliedOrderCache + : this._sharedOrderIfExists; + return ( + <FillOrder + blockchain={this._blockchain} + blockchainErr={this.props.blockchainErr} + initialOrder={initialFillOrder} + isOrderInUrl={!_.isUndefined(this._sharedOrderIfExists)} + orderFillAmount={this.props.orderFillAmount} + networkId={this.props.networkId} + userAddress={this.props.userAddress} + tokenByAddress={this.props.tokenByAddress} + tokenStateByAddress={this.props.tokenStateByAddress} + dispatcher={this.props.dispatcher} + /> + ); + } + private _renderGenerateOrderForm(match: any, location: Location, history: History) { + return ( + <GenerateOrderForm + blockchain={this._blockchain} + hashData={this.props.hashData} + dispatcher={this.props.dispatcher} + /> + ); + } + private _onPortalDisclaimerAccepted() { + localStorage.setItem(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER, 'set'); + this.setState({ + isDisclaimerDialogOpen: false, + }); + } + private _onWethNoticeAccepted() { + localStorage.setItem(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE, 'set'); + this.setState({ + isWethNoticeDialogOpen: false, + }); + } + private _getSharedOrderIfExists(): Order | undefined { + const queryString = window.location.search; + if (queryString.length === 0) { + return undefined; + } + const queryParams = queryString.substring(1).split('&'); + const orderQueryParam = _.find(queryParams, queryParam => { + const queryPair = queryParam.split('='); + return queryPair[0] === 'order'; + }); + if (_.isUndefined(orderQueryParam)) { + return undefined; + } + const orderPair = orderQueryParam.split('='); + if (orderPair.length !== 2) { + return undefined; + } - const validator = new SchemaValidator(); - const order = JSON.parse(decodeURIComponent(orderPair[1])); - const validationResult = validator.validate(order, orderSchema); - if (validationResult.errors.length > 0) { - utils.consoleLog(`Invalid shared order: ${validationResult.errors}`); - return undefined; - } - return order; - } - private _updateScreenWidth() { - const newScreenWidth = utils.getScreenWidth(); - this.props.dispatcher.updateScreenWidth(newScreenWidth); - } - private async _updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) { - this.props.dispatcher.updateBlockchainIsLoaded(false); - await this._blockchain.updateTokenBalancesAndAllowancesAsync(tokens); - this.props.dispatcher.updateBlockchainIsLoaded(true); - } + const validator = new SchemaValidator(); + const order = JSON.parse(decodeURIComponent(orderPair[1])); + const validationResult = validator.validate(order, orderSchema); + if (validationResult.errors.length > 0) { + utils.consoleLog(`Invalid shared order: ${validationResult.errors}`); + return undefined; + } + return order; + } + private _updateScreenWidth() { + const newScreenWidth = utils.getScreenWidth(); + this.props.dispatcher.updateScreenWidth(newScreenWidth); + } + private async _updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) { + this.props.dispatcher.updateBlockchainIsLoaded(false); + await this._blockchain.updateTokenBalancesAndAllowancesAsync(tokens); + this.props.dispatcher.updateBlockchainIsLoaded(true); + } } diff --git a/packages/website/ts/components/portal_menu.tsx b/packages/website/ts/components/portal_menu.tsx index b025f527e..a2f9340c8 100644 --- a/packages/website/ts/components/portal_menu.tsx +++ b/packages/website/ts/components/portal_menu.tsx @@ -4,70 +4,70 @@ import { MenuItem } from 'ts/components/ui/menu_item'; import { WebsitePaths } from 'ts/types'; export interface PortalMenuProps { - menuItemStyle: React.CSSProperties; - onClick?: () => void; + menuItemStyle: React.CSSProperties; + onClick?: () => void; } interface PortalMenuState {} export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState> { - public static defaultProps: Partial<PortalMenuProps> = { - onClick: _.noop, - }; - public render() { - return ( - <div> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Generate order', 'zmdi-arrow-right-top')} - </MenuItem> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}/fill`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Fill order', 'zmdi-arrow-left-bottom')} - </MenuItem> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}/balances`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Balances', 'zmdi-balance-wallet')} - </MenuItem> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}/trades`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Trade history', 'zmdi-format-list-bulleted')} - </MenuItem> - <MenuItem - style={this.props.menuItemStyle} - className="py2" - to={`${WebsitePaths.Portal}/weth`} - onClick={this.props.onClick.bind(this)} - > - {this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')} - </MenuItem> - </div> - ); - } - private _renderMenuItemWithIcon(title: string, iconName: string) { - return ( - <div className="flex" style={{ fontWeight: 100 }}> - <div className="pr1 pl2"> - <i style={{ fontSize: 20 }} className={`zmdi ${iconName}`} /> - </div> - <div className="pl1">{title}</div> - </div> - ); - } + public static defaultProps: Partial<PortalMenuProps> = { + onClick: _.noop, + }; + public render() { + return ( + <div> + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Generate order', 'zmdi-arrow-right-top')} + </MenuItem> + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}/fill`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Fill order', 'zmdi-arrow-left-bottom')} + </MenuItem> + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}/balances`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Balances', 'zmdi-balance-wallet')} + </MenuItem> + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}/trades`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Trade history', 'zmdi-format-list-bulleted')} + </MenuItem> + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}/weth`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')} + </MenuItem> + </div> + ); + } + private _renderMenuItemWithIcon(title: string, iconName: string) { + return ( + <div className="flex" style={{ fontWeight: 100 }}> + <div className="pr1 pl2"> + <i style={{ fontSize: 20 }} className={`zmdi ${iconName}`} /> + </div> + <div className="pl1">{title}</div> + </div> + ); + } } diff --git a/packages/website/ts/components/send_button.tsx b/packages/website/ts/components/send_button.tsx index f97d9250b..f94ec346a 100644 --- a/packages/website/ts/components/send_button.tsx +++ b/packages/website/ts/components/send_button.tsx @@ -10,78 +10,78 @@ import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface SendButtonProps { - token: Token; - tokenState: TokenState; - dispatcher: Dispatcher; - blockchain: Blockchain; - onError: () => void; + token: Token; + tokenState: TokenState; + dispatcher: Dispatcher; + blockchain: Blockchain; + onError: () => void; } interface SendButtonState { - isSendDialogVisible: boolean; - isSending: boolean; + isSendDialogVisible: boolean; + isSending: boolean; } export class SendButton extends React.Component<SendButtonProps, SendButtonState> { - public constructor(props: SendButtonProps) { - super(props); - this.state = { - isSendDialogVisible: false, - isSending: false, - }; - } - public render() { - const labelStyle = this.state.isSending ? { fontSize: 10 } : {}; - return ( - <div> - <RaisedButton - style={{ width: '100%' }} - labelStyle={labelStyle} - disabled={this.state.isSending} - label={this.state.isSending ? 'Sending...' : 'Send'} - onClick={this._toggleSendDialog.bind(this)} - /> - <SendDialog - isOpen={this.state.isSendDialogVisible} - onComplete={this._onSendAmountSelectedAsync.bind(this)} - onCancelled={this._toggleSendDialog.bind(this)} - token={this.props.token} - tokenState={this.props.tokenState} - /> - </div> - ); - } - private _toggleSendDialog() { - this.setState({ - isSendDialogVisible: !this.state.isSendDialogVisible, - }); - } - private async _onSendAmountSelectedAsync(recipient: string, value: BigNumber) { - this.setState({ - isSending: true, - }); - this._toggleSendDialog(); - const token = this.props.token; - const tokenState = this.props.tokenState; - let balance = tokenState.balance; - try { - await this.props.blockchain.transferAsync(token, recipient, value); - balance = balance.minus(value); - this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); - } catch (err) { - const errMsg = `${err}`; - if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return; - } else if (!_.includes(errMsg, 'User denied transaction')) { - utils.consoleLog(`Unexpected error encountered: ${err}`); - utils.consoleLog(err.stack); - this.props.onError(); - await errorReporter.reportAsync(err); - } - } - this.setState({ - isSending: false, - }); - } + public constructor(props: SendButtonProps) { + super(props); + this.state = { + isSendDialogVisible: false, + isSending: false, + }; + } + public render() { + const labelStyle = this.state.isSending ? { fontSize: 10 } : {}; + return ( + <div> + <RaisedButton + style={{ width: '100%' }} + labelStyle={labelStyle} + disabled={this.state.isSending} + label={this.state.isSending ? 'Sending...' : 'Send'} + onClick={this._toggleSendDialog.bind(this)} + /> + <SendDialog + isOpen={this.state.isSendDialogVisible} + onComplete={this._onSendAmountSelectedAsync.bind(this)} + onCancelled={this._toggleSendDialog.bind(this)} + token={this.props.token} + tokenState={this.props.tokenState} + /> + </div> + ); + } + private _toggleSendDialog() { + this.setState({ + isSendDialogVisible: !this.state.isSendDialogVisible, + }); + } + private async _onSendAmountSelectedAsync(recipient: string, value: BigNumber) { + this.setState({ + isSending: true, + }); + this._toggleSendDialog(); + const token = this.props.token; + const tokenState = this.props.tokenState; + let balance = tokenState.balance; + try { + await this.props.blockchain.transferAsync(token, recipient, value); + balance = balance.minus(value); + this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + } catch (err) { + const errMsg = `${err}`; + if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return; + } else if (!_.includes(errMsg, 'User denied transaction')) { + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + this.props.onError(); + await errorReporter.reportAsync(err); + } + } + this.setState({ + isSending: false, + }); + } } diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 480652c34..2cef413c7 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -23,16 +23,16 @@ import { TokenIcon } from 'ts/components/ui/token_icon'; import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { - BalanceErrs, - BlockchainCallErrs, - BlockchainErrs, - EtherscanLinkSuffixes, - ScreenWidths, - Styles, - Token, - TokenByAddress, - TokenStateByAddress, - TokenVisibility, + BalanceErrs, + BlockchainCallErrs, + BlockchainErrs, + EtherscanLinkSuffixes, + ScreenWidths, + Styles, + Token, + TokenByAddress, + TokenStateByAddress, + TokenVisibility, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; @@ -53,554 +53,554 @@ const TOKEN_COL_SPAN_LG = 2; const TOKEN_COL_SPAN_SM = 1; const styles: Styles = { - bgColor: { - backgroundColor: colors.grey50, - }, + bgColor: { + backgroundColor: colors.grey50, + }, }; interface TokenBalancesProps { - blockchain: Blockchain; - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - dispatcher: Dispatcher; - screenWidth: ScreenWidths; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - userAddress: string; - userEtherBalance: BigNumber; - networkId: number; + blockchain: Blockchain; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + dispatcher: Dispatcher; + screenWidth: ScreenWidths; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userAddress: string; + userEtherBalance: BigNumber; + networkId: number; } interface TokenBalancesState { - errorType: BalanceErrs; - isBalanceSpinnerVisible: boolean; - isDharmaDialogVisible: boolean; - isZRXSpinnerVisible: boolean; - currentZrxBalance?: BigNumber; - isTokenPickerOpen: boolean; - isAddingToken: boolean; + errorType: BalanceErrs; + isBalanceSpinnerVisible: boolean; + isDharmaDialogVisible: boolean; + isZRXSpinnerVisible: boolean; + currentZrxBalance?: BigNumber; + isTokenPickerOpen: boolean; + isAddingToken: boolean; } export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> { - public constructor(props: TokenBalancesProps) { - super(props); - this.state = { - errorType: undefined, - isBalanceSpinnerVisible: false, - isZRXSpinnerVisible: false, - isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(), - isTokenPickerOpen: false, - isAddingToken: false, - }; - } - public componentWillReceiveProps(nextProps: TokenBalancesProps) { - if (nextProps.userEtherBalance !== this.props.userEtherBalance) { - if (this.state.isBalanceSpinnerVisible) { - const receivedAmount = nextProps.userEtherBalance.minus(this.props.userEtherBalance); - this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} Kovan Ether`); - } - this.setState({ - isBalanceSpinnerVisible: false, - }); - } - const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL); - const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance; - if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) { - if (this.state.isZRXSpinnerVisible) { - const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance); - const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, constants.DECIMAL_PLACES_ZRX); - this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`); - } - this.setState({ - isZRXSpinnerVisible: false, - currentZrxBalance: undefined, - }); - } - } - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - const errorDialogActions = [ - <FlatButton - key="errorOkBtn" - label="Ok" - primary={true} - onTouchTap={this._onErrorDialogToggle.bind(this, false)} - />, - ]; - const dharmaDialogActions = [ - <FlatButton - key="dharmaCloseBtn" - label="Close" - primary={true} - onTouchTap={this._onDharmaDialogToggle.bind(this, false)} - />, - ]; - const isTestNetwork = this.props.networkId === constants.NETWORK_ID_TESTNET; - const dharmaButtonColumnStyle = { - paddingLeft: 3, - display: isTestNetwork ? 'table-cell' : 'none', - }; - const stubColumnStyle = { - display: isTestNetwork ? 'none' : 'table-cell', - }; - const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT; - const tokenTableHeight = - allTokenRowHeight < MAX_TOKEN_TABLE_HEIGHT ? allTokenRowHeight : MAX_TOKEN_TABLE_HEIGHT; - const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; - const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; - const dharmaLoanExplanation = - 'If you need access to larger amounts of ether,<br> \ + public constructor(props: TokenBalancesProps) { + super(props); + this.state = { + errorType: undefined, + isBalanceSpinnerVisible: false, + isZRXSpinnerVisible: false, + isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(), + isTokenPickerOpen: false, + isAddingToken: false, + }; + } + public componentWillReceiveProps(nextProps: TokenBalancesProps) { + if (nextProps.userEtherBalance !== this.props.userEtherBalance) { + if (this.state.isBalanceSpinnerVisible) { + const receivedAmount = nextProps.userEtherBalance.minus(this.props.userEtherBalance); + this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} Kovan Ether`); + } + this.setState({ + isBalanceSpinnerVisible: false, + }); + } + const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL); + const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance; + if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) { + if (this.state.isZRXSpinnerVisible) { + const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance); + const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, constants.DECIMAL_PLACES_ZRX); + this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`); + } + this.setState({ + isZRXSpinnerVisible: false, + currentZrxBalance: undefined, + }); + } + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + const errorDialogActions = [ + <FlatButton + key="errorOkBtn" + label="Ok" + primary={true} + onTouchTap={this._onErrorDialogToggle.bind(this, false)} + />, + ]; + const dharmaDialogActions = [ + <FlatButton + key="dharmaCloseBtn" + label="Close" + primary={true} + onTouchTap={this._onDharmaDialogToggle.bind(this, false)} + />, + ]; + const isTestNetwork = this.props.networkId === constants.NETWORK_ID_TESTNET; + const dharmaButtonColumnStyle = { + paddingLeft: 3, + display: isTestNetwork ? 'table-cell' : 'none', + }; + const stubColumnStyle = { + display: isTestNetwork ? 'none' : 'table-cell', + }; + const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT; + const tokenTableHeight = + allTokenRowHeight < MAX_TOKEN_TABLE_HEIGHT ? allTokenRowHeight : MAX_TOKEN_TABLE_HEIGHT; + const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; + const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; + const dharmaLoanExplanation = + 'If you need access to larger amounts of ether,<br> \ you can request a loan from the Dharma Loan<br> \ network. Your loan should be funded in 5<br> \ minutes or less.'; - const allowanceExplanation = - '0x smart contracts require access to your<br> \ + const allowanceExplanation = + '0x smart contracts require access to your<br> \ token balances in order to execute trades.<br> \ Toggling sets an allowance for the<br> \ smart contract so you can start trading that token.'; - return ( - <div className="lg-px4 md-px4 sm-px1 pb2"> - <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3> - <Divider /> - <div className="pt2 pb2"> - {isTestNetwork - ? 'In order to try out the 0x Portal Dapp, request some test ether to pay for \ + return ( + <div className="lg-px4 md-px4 sm-px1 pb2"> + <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3> + <Divider /> + <div className="pt2 pb2"> + {isTestNetwork + ? 'In order to try out the 0x Portal Dapp, request some test ether to pay for \ gas costs. It might take a bit of time for the test ether to show up.' - : 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \ + : 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \ You can convert between Ether and Ether Tokens from the "Wrap ETH" tab.'} - </div> - <Table selectable={false} style={styles.bgColor}> - <TableHeader displaySelectAll={false} adjustForCheckbox={false}> - <TableRow> - <TableHeaderColumn>Currency</TableHeaderColumn> - <TableHeaderColumn>Balance</TableHeaderColumn> - <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> - {isTestNetwork && ( - <TableHeaderColumn style={{ paddingLeft: 3 }}> - {isSmallScreen ? 'Faucet' : 'Request from faucet'} - </TableHeaderColumn> - )} - {isTestNetwork && ( - <TableHeaderColumn style={dharmaButtonColumnStyle}> - {isSmallScreen ? 'Loan' : 'Request Dharma loan'} - <HelpTooltip style={{ paddingLeft: 4 }} explanation={dharmaLoanExplanation} /> - </TableHeaderColumn> - )} - </TableRow> - </TableHeader> - <TableBody displayRowCheckbox={false}> - <TableRow key="ETH"> - <TableRowColumn className="py1"> - <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} /> - </TableRowColumn> - <TableRowColumn> - {this.props.userEtherBalance.toFixed(PRECISION)} ETH - {this.state.isBalanceSpinnerVisible && ( - <span className="pl1"> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </span> - )} - </TableRowColumn> - <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> - {isTestNetwork && ( - <TableRowColumn style={{ paddingLeft: 3 }}> - <LifeCycleRaisedButton - labelReady="Request" - labelLoading="Sending..." - labelComplete="Sent!" - onClickAsyncFn={this._faucetRequestAsync.bind(this, true)} - /> - </TableRowColumn> - )} - {isTestNetwork && ( - <TableRowColumn style={dharmaButtonColumnStyle}> - <RaisedButton - label="Request" - style={{ width: '100%' }} - onTouchTap={this._onDharmaDialogToggle.bind(this)} - /> - </TableRowColumn> - )} - </TableRow> - </TableBody> - </Table> - <div className="clearfix" style={{ paddingBottom: 1 }}> - <div className="col col-10"> - <h3 className="pt2">{isTestNetwork ? 'Test tokens' : 'Tokens'}</h3> - </div> - <div className="col col-1 pt3 align-right"> - <FloatingActionButton mini={true} zDepth={0} onClick={this._onAddTokenClicked.bind(this)}> - <ContentAdd /> - </FloatingActionButton> - </div> - <div className="col col-1 pt3 align-right"> - <FloatingActionButton mini={true} zDepth={0} onClick={this._onRemoveTokenClicked.bind(this)}> - <ContentRemove /> - </FloatingActionButton> - </div> - </div> - <Divider /> - <div className="pt2 pb2"> - {isTestNetwork - ? "Mint some test tokens you'd like to use to generate or fill an order using 0x." - : "Set trading permissions for a token you'd like to start trading."} - </div> - <Table selectable={false} bodyStyle={{ height: tokenTableHeight }} style={styles.bgColor}> - <TableHeader displaySelectAll={false} adjustForCheckbox={false}> - <TableRow> - <TableHeaderColumn colSpan={tokenColSpan}>Token</TableHeaderColumn> - <TableHeaderColumn style={{ paddingLeft: 3 }}>Balance</TableHeaderColumn> - <TableHeaderColumn> - <div className="inline-block">Allowance</div> - <HelpTooltip style={{ paddingLeft: 4 }} explanation={allowanceExplanation} /> - </TableHeaderColumn> - <TableHeaderColumn>Action</TableHeaderColumn> - {this.props.screenWidth !== ScreenWidths.Sm && <TableHeaderColumn>Send</TableHeaderColumn>} - </TableRow> - </TableHeader> - <TableBody displayRowCheckbox={false}>{this._renderTokenTableRows()}</TableBody> - </Table> - <Dialog - title="Oh oh" - titleStyle={{ fontWeight: 100 }} - actions={errorDialogActions} - open={!_.isUndefined(this.state.errorType)} - onRequestClose={this._onErrorDialogToggle.bind(this, false)} - > - {this._renderErrorDialogBody()} - </Dialog> - <Dialog - title="Request Dharma Loan" - titleStyle={{ fontWeight: 100, backgroundColor: colors.white }} - bodyStyle={{ backgroundColor: colors.dharmaDarkGrey }} - actionsContainerStyle={{ backgroundColor: colors.white }} - autoScrollBodyContent={true} - actions={dharmaDialogActions} - open={this.state.isDharmaDialogVisible} - > - {this._renderDharmaLoanFrame()} - </Dialog> - <AssetPicker - userAddress={this.props.userAddress} - networkId={this.props.networkId} - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - isOpen={this.state.isTokenPickerOpen} - currentTokenAddress={''} - onTokenChosen={this._onAssetTokenPicked.bind(this)} - tokenByAddress={this.props.tokenByAddress} - tokenVisibility={this.state.isAddingToken ? TokenVisibility.UNTRACKED : TokenVisibility.TRACKED} - /> - </div> - ); - } - private _renderTokenTableRows() { - if (!this.props.blockchainIsLoaded || this.props.blockchainErr !== BlockchainErrs.NoError) { - return ''; - } - const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; - const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; - const actionPaddingX = isSmallScreen ? 2 : 24; - const allTokens = _.values(this.props.tokenByAddress); - const trackedTokens = _.filter(allTokens, t => t.isTracked); - const trackedTokensStartingWithEtherToken = trackedTokens.sort( - firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL) - .thenBy((t: Token) => t.symbol !== ZRX_TOKEN_SYMBOL) - .thenBy('address'), - ); - const tableRows = _.map( - trackedTokensStartingWithEtherToken, - this._renderTokenRow.bind(this, tokenColSpan, actionPaddingX), - ); - return tableRows; - } - private _renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) { - const tokenState = this.props.tokenStateByAddress[token.address]; - const tokenLink = utils.getEtherScanLinkIfExists( - token.address, - this.props.networkId, - EtherscanLinkSuffixes.Address, - ); - const isMintable = - _.includes(configs.SYMBOLS_OF_MINTABLE_TOKENS, token.symbol) && - this.props.networkId !== constants.NETWORK_ID_MAINNET; - return ( - <TableRow key={token.address} style={{ height: TOKEN_TABLE_ROW_HEIGHT }}> - <TableRowColumn colSpan={tokenColSpan}> - {_.isUndefined(tokenLink) ? ( - this._renderTokenName(token) - ) : ( - <a href={tokenLink} target="_blank" style={{ textDecoration: 'none' }}> - {this._renderTokenName(token)} - </a> - )} - </TableRowColumn> - <TableRowColumn style={{ paddingRight: 3, paddingLeft: 3 }}> - {this._renderAmount(tokenState.balance, token.decimals)} {token.symbol} - {this.state.isZRXSpinnerVisible && - token.symbol === ZRX_TOKEN_SYMBOL && ( - <span className="pl1"> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </span> - )} - </TableRowColumn> - <TableRowColumn> - <AllowanceToggle - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - token={token} - tokenState={tokenState} - onErrorOccurred={this._onErrorOccurred.bind(this)} - userAddress={this.props.userAddress} - /> - </TableRowColumn> - <TableRowColumn style={{ paddingLeft: actionPaddingX, paddingRight: actionPaddingX }}> - {isMintable && ( - <LifeCycleRaisedButton - labelReady="Mint" - labelLoading={<span style={{ fontSize: 12 }}>Minting...</span>} - labelComplete="Minted!" - onClickAsyncFn={this._onMintTestTokensAsync.bind(this, token)} - /> - )} - {token.symbol === ZRX_TOKEN_SYMBOL && - this.props.networkId === constants.NETWORK_ID_TESTNET && ( - <LifeCycleRaisedButton - labelReady="Request" - labelLoading="Sending..." - labelComplete="Sent!" - onClickAsyncFn={this._faucetRequestAsync.bind(this, false)} - /> - )} - </TableRowColumn> - {this.props.screenWidth !== ScreenWidths.Sm && ( - <TableRowColumn - style={{ - paddingLeft: actionPaddingX, - paddingRight: actionPaddingX, - }} - > - <SendButton - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - token={token} - tokenState={tokenState} - onError={this._onSendFailed.bind(this)} - /> - </TableRowColumn> - )} - </TableRow> - ); - } - private _onAssetTokenPicked(tokenAddress: string) { - if (_.isEmpty(tokenAddress)) { - this.setState({ - isTokenPickerOpen: false, - }); - return; - } - const token = this.props.tokenByAddress[tokenAddress]; - const isDefaultTrackedToken = _.includes(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, token.symbol); - if (!this.state.isAddingToken && !isDefaultTrackedToken) { - if (token.isRegistered) { - // Remove the token from tracked tokens - const newToken = { - ...token, - isTracked: false, - }; - this.props.dispatcher.updateTokenByAddress([newToken]); - } else { - this.props.dispatcher.removeTokenToTokenByAddress(token); - } - this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress); - trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress); - } else if (isDefaultTrackedToken) { - this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`); - } - this.setState({ - isTokenPickerOpen: false, - }); - } - private _onSendFailed() { - this.setState({ - errorType: BalanceErrs.sendFailed, - }); - } - private _renderAmount(amount: BigNumber, decimals: number) { - const unitAmount = ZeroEx.toUnitAmount(amount, decimals); - return unitAmount.toNumber().toFixed(PRECISION); - } - private _renderTokenName(token: Token) { - const tooltipId = `tooltip-${token.address}`; - return ( - <div className="flex"> - <TokenIcon token={token} diameter={ICON_DIMENSION} /> - <div data-tip={true} data-for={tooltipId} className="mt2 ml2 sm-hide xs-hide"> - {token.name} - </div> - <ReactTooltip id={tooltipId}>{token.address}</ReactTooltip> - </div> - ); - } - private _renderErrorDialogBody() { - switch (this.state.errorType) { - case BalanceErrs.incorrectNetworkForFaucet: - return ( - <div> - Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME} testnet - (networkId {constants.NETWORK_ID_TESTNET}). Please make sure you are connected to the{' '} - {constants.TESTNET_NAME} testnet and try requesting ether again. - </div> - ); + </div> + <Table selectable={false} style={styles.bgColor}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn>Currency</TableHeaderColumn> + <TableHeaderColumn>Balance</TableHeaderColumn> + <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> + {isTestNetwork && ( + <TableHeaderColumn style={{ paddingLeft: 3 }}> + {isSmallScreen ? 'Faucet' : 'Request from faucet'} + </TableHeaderColumn> + )} + {isTestNetwork && ( + <TableHeaderColumn style={dharmaButtonColumnStyle}> + {isSmallScreen ? 'Loan' : 'Request Dharma loan'} + <HelpTooltip style={{ paddingLeft: 4 }} explanation={dharmaLoanExplanation} /> + </TableHeaderColumn> + )} + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false}> + <TableRow key="ETH"> + <TableRowColumn className="py1"> + <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} /> + </TableRowColumn> + <TableRowColumn> + {this.props.userEtherBalance.toFixed(PRECISION)} ETH + {this.state.isBalanceSpinnerVisible && ( + <span className="pl1"> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </span> + )} + </TableRowColumn> + <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> + {isTestNetwork && ( + <TableRowColumn style={{ paddingLeft: 3 }}> + <LifeCycleRaisedButton + labelReady="Request" + labelLoading="Sending..." + labelComplete="Sent!" + onClickAsyncFn={this._faucetRequestAsync.bind(this, true)} + /> + </TableRowColumn> + )} + {isTestNetwork && ( + <TableRowColumn style={dharmaButtonColumnStyle}> + <RaisedButton + label="Request" + style={{ width: '100%' }} + onTouchTap={this._onDharmaDialogToggle.bind(this)} + /> + </TableRowColumn> + )} + </TableRow> + </TableBody> + </Table> + <div className="clearfix" style={{ paddingBottom: 1 }}> + <div className="col col-10"> + <h3 className="pt2">{isTestNetwork ? 'Test tokens' : 'Tokens'}</h3> + </div> + <div className="col col-1 pt3 align-right"> + <FloatingActionButton mini={true} zDepth={0} onClick={this._onAddTokenClicked.bind(this)}> + <ContentAdd /> + </FloatingActionButton> + </div> + <div className="col col-1 pt3 align-right"> + <FloatingActionButton mini={true} zDepth={0} onClick={this._onRemoveTokenClicked.bind(this)}> + <ContentRemove /> + </FloatingActionButton> + </div> + </div> + <Divider /> + <div className="pt2 pb2"> + {isTestNetwork + ? "Mint some test tokens you'd like to use to generate or fill an order using 0x." + : "Set trading permissions for a token you'd like to start trading."} + </div> + <Table selectable={false} bodyStyle={{ height: tokenTableHeight }} style={styles.bgColor}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn colSpan={tokenColSpan}>Token</TableHeaderColumn> + <TableHeaderColumn style={{ paddingLeft: 3 }}>Balance</TableHeaderColumn> + <TableHeaderColumn> + <div className="inline-block">Allowance</div> + <HelpTooltip style={{ paddingLeft: 4 }} explanation={allowanceExplanation} /> + </TableHeaderColumn> + <TableHeaderColumn>Action</TableHeaderColumn> + {this.props.screenWidth !== ScreenWidths.Sm && <TableHeaderColumn>Send</TableHeaderColumn>} + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false}>{this._renderTokenTableRows()}</TableBody> + </Table> + <Dialog + title="Oh oh" + titleStyle={{ fontWeight: 100 }} + actions={errorDialogActions} + open={!_.isUndefined(this.state.errorType)} + onRequestClose={this._onErrorDialogToggle.bind(this, false)} + > + {this._renderErrorDialogBody()} + </Dialog> + <Dialog + title="Request Dharma Loan" + titleStyle={{ fontWeight: 100, backgroundColor: colors.white }} + bodyStyle={{ backgroundColor: colors.dharmaDarkGrey }} + actionsContainerStyle={{ backgroundColor: colors.white }} + autoScrollBodyContent={true} + actions={dharmaDialogActions} + open={this.state.isDharmaDialogVisible} + > + {this._renderDharmaLoanFrame()} + </Dialog> + <AssetPicker + userAddress={this.props.userAddress} + networkId={this.props.networkId} + blockchain={this.props.blockchain} + dispatcher={this.props.dispatcher} + isOpen={this.state.isTokenPickerOpen} + currentTokenAddress={''} + onTokenChosen={this._onAssetTokenPicked.bind(this)} + tokenByAddress={this.props.tokenByAddress} + tokenVisibility={this.state.isAddingToken ? TokenVisibility.UNTRACKED : TokenVisibility.TRACKED} + /> + </div> + ); + } + private _renderTokenTableRows() { + if (!this.props.blockchainIsLoaded || this.props.blockchainErr !== BlockchainErrs.NoError) { + return ''; + } + const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; + const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; + const actionPaddingX = isSmallScreen ? 2 : 24; + const allTokens = _.values(this.props.tokenByAddress); + const trackedTokens = _.filter(allTokens, t => t.isTracked); + const trackedTokensStartingWithEtherToken = trackedTokens.sort( + firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL) + .thenBy((t: Token) => t.symbol !== ZRX_TOKEN_SYMBOL) + .thenBy('address'), + ); + const tableRows = _.map( + trackedTokensStartingWithEtherToken, + this._renderTokenRow.bind(this, tokenColSpan, actionPaddingX), + ); + return tableRows; + } + private _renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) { + const tokenState = this.props.tokenStateByAddress[token.address]; + const tokenLink = utils.getEtherScanLinkIfExists( + token.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const isMintable = + _.includes(configs.SYMBOLS_OF_MINTABLE_TOKENS, token.symbol) && + this.props.networkId !== constants.NETWORK_ID_MAINNET; + return ( + <TableRow key={token.address} style={{ height: TOKEN_TABLE_ROW_HEIGHT }}> + <TableRowColumn colSpan={tokenColSpan}> + {_.isUndefined(tokenLink) ? ( + this._renderTokenName(token) + ) : ( + <a href={tokenLink} target="_blank" style={{ textDecoration: 'none' }}> + {this._renderTokenName(token)} + </a> + )} + </TableRowColumn> + <TableRowColumn style={{ paddingRight: 3, paddingLeft: 3 }}> + {this._renderAmount(tokenState.balance, token.decimals)} {token.symbol} + {this.state.isZRXSpinnerVisible && + token.symbol === ZRX_TOKEN_SYMBOL && ( + <span className="pl1"> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </span> + )} + </TableRowColumn> + <TableRowColumn> + <AllowanceToggle + blockchain={this.props.blockchain} + dispatcher={this.props.dispatcher} + token={token} + tokenState={tokenState} + onErrorOccurred={this._onErrorOccurred.bind(this)} + userAddress={this.props.userAddress} + /> + </TableRowColumn> + <TableRowColumn style={{ paddingLeft: actionPaddingX, paddingRight: actionPaddingX }}> + {isMintable && ( + <LifeCycleRaisedButton + labelReady="Mint" + labelLoading={<span style={{ fontSize: 12 }}>Minting...</span>} + labelComplete="Minted!" + onClickAsyncFn={this._onMintTestTokensAsync.bind(this, token)} + /> + )} + {token.symbol === ZRX_TOKEN_SYMBOL && + this.props.networkId === constants.NETWORK_ID_TESTNET && ( + <LifeCycleRaisedButton + labelReady="Request" + labelLoading="Sending..." + labelComplete="Sent!" + onClickAsyncFn={this._faucetRequestAsync.bind(this, false)} + /> + )} + </TableRowColumn> + {this.props.screenWidth !== ScreenWidths.Sm && ( + <TableRowColumn + style={{ + paddingLeft: actionPaddingX, + paddingRight: actionPaddingX, + }} + > + <SendButton + blockchain={this.props.blockchain} + dispatcher={this.props.dispatcher} + token={token} + tokenState={tokenState} + onError={this._onSendFailed.bind(this)} + /> + </TableRowColumn> + )} + </TableRow> + ); + } + private _onAssetTokenPicked(tokenAddress: string) { + if (_.isEmpty(tokenAddress)) { + this.setState({ + isTokenPickerOpen: false, + }); + return; + } + const token = this.props.tokenByAddress[tokenAddress]; + const isDefaultTrackedToken = _.includes(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, token.symbol); + if (!this.state.isAddingToken && !isDefaultTrackedToken) { + if (token.isRegistered) { + // Remove the token from tracked tokens + const newToken = { + ...token, + isTracked: false, + }; + this.props.dispatcher.updateTokenByAddress([newToken]); + } else { + this.props.dispatcher.removeTokenToTokenByAddress(token); + } + this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress); + trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress); + } else if (isDefaultTrackedToken) { + this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`); + } + this.setState({ + isTokenPickerOpen: false, + }); + } + private _onSendFailed() { + this.setState({ + errorType: BalanceErrs.sendFailed, + }); + } + private _renderAmount(amount: BigNumber, decimals: number) { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + return unitAmount.toNumber().toFixed(PRECISION); + } + private _renderTokenName(token: Token) { + const tooltipId = `tooltip-${token.address}`; + return ( + <div className="flex"> + <TokenIcon token={token} diameter={ICON_DIMENSION} /> + <div data-tip={true} data-for={tooltipId} className="mt2 ml2 sm-hide xs-hide"> + {token.name} + </div> + <ReactTooltip id={tooltipId}>{token.address}</ReactTooltip> + </div> + ); + } + private _renderErrorDialogBody() { + switch (this.state.errorType) { + case BalanceErrs.incorrectNetworkForFaucet: + return ( + <div> + Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME} testnet + (networkId {constants.NETWORK_ID_TESTNET}). Please make sure you are connected to the{' '} + {constants.TESTNET_NAME} testnet and try requesting ether again. + </div> + ); - case BalanceErrs.faucetRequestFailed: - return ( - <div> - An unexpected error occurred while trying to request test Ether from our faucet. Please refresh - the page and try again. - </div> - ); + case BalanceErrs.faucetRequestFailed: + return ( + <div> + An unexpected error occurred while trying to request test Ether from our faucet. Please refresh + the page and try again. + </div> + ); - case BalanceErrs.faucetQueueIsFull: - return <div>Our test Ether faucet queue is full. Please try requesting test Ether again later.</div>; + case BalanceErrs.faucetQueueIsFull: + return <div>Our test Ether faucet queue is full. Please try requesting test Ether again later.</div>; - case BalanceErrs.mintingFailed: - return <div>Minting your test tokens failed unexpectedly. Please refresh the page and try again.</div>; + case BalanceErrs.mintingFailed: + return <div>Minting your test tokens failed unexpectedly. Please refresh the page and try again.</div>; - case BalanceErrs.allowanceSettingFailed: - return ( - <div> - An unexpected error occurred while trying to set your test token allowance. Please refresh the - page and try again. - </div> - ); + case BalanceErrs.allowanceSettingFailed: + return ( + <div> + An unexpected error occurred while trying to set your test token allowance. Please refresh the + page and try again. + </div> + ); - case undefined: - return null; // No error to show + case undefined: + return null; // No error to show - default: - throw utils.spawnSwitchErr('errorType', this.state.errorType); - } - } - private _renderDharmaLoanFrame() { - if (utils.isUserOnMobile()) { - return ( - <h4 style={{ textAlign: 'center' }}> - We apologize -- Dharma loan requests are not available on mobile yet. Please try again through your - desktop browser. - </h4> - ); - } else { - return ( - <DharmaLoanFrame - partner="0x" - env={utils.getCurrentEnvironment()} - screenWidth={this.props.screenWidth} - /> - ); - } - } - private _onErrorOccurred(errorType: BalanceErrs) { - this.setState({ - errorType, - }); - } - private async _onMintTestTokensAsync(token: Token): Promise<boolean> { - try { - await this.props.blockchain.mintTestTokensAsync(token); - const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals); - this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`); - return true; - } catch (err) { - const errMsg = `${err}`; - if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return false; - } - if (_.includes(errMsg, 'User denied transaction')) { - return false; - } - utils.consoleLog(`Unexpected error encountered: ${err}`); - utils.consoleLog(err.stack); - this.setState({ - errorType: BalanceErrs.mintingFailed, - }); - await errorReporter.reportAsync(err); - return false; - } - } - private async _faucetRequestAsync(isEtherRequest: boolean): Promise<boolean> { - if (this.props.userAddress === '') { - this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return false; - } + default: + throw utils.spawnSwitchErr('errorType', this.state.errorType); + } + } + private _renderDharmaLoanFrame() { + if (utils.isUserOnMobile()) { + return ( + <h4 style={{ textAlign: 'center' }}> + We apologize -- Dharma loan requests are not available on mobile yet. Please try again through your + desktop browser. + </h4> + ); + } else { + return ( + <DharmaLoanFrame + partner="0x" + env={utils.getCurrentEnvironment()} + screenWidth={this.props.screenWidth} + /> + ); + } + } + private _onErrorOccurred(errorType: BalanceErrs) { + this.setState({ + errorType, + }); + } + private async _onMintTestTokensAsync(token: Token): Promise<boolean> { + try { + await this.props.blockchain.mintTestTokensAsync(token); + const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals); + this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`); + return true; + } catch (err) { + const errMsg = `${err}`; + if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } + if (_.includes(errMsg, 'User denied transaction')) { + return false; + } + utils.consoleLog(`Unexpected error encountered: ${err}`); + utils.consoleLog(err.stack); + this.setState({ + errorType: BalanceErrs.mintingFailed, + }); + await errorReporter.reportAsync(err); + return false; + } + } + private async _faucetRequestAsync(isEtherRequest: boolean): Promise<boolean> { + if (this.props.userAddress === '') { + this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); + return false; + } - // If on another network other then the testnet our faucet serves test ether - // from, we must show user an error message - if (this.props.blockchain.networkId !== constants.NETWORK_ID_TESTNET) { - this.setState({ - errorType: BalanceErrs.incorrectNetworkForFaucet, - }); - return false; - } + // If on another network other then the testnet our faucet serves test ether + // from, we must show user an error message + if (this.props.blockchain.networkId !== constants.NETWORK_ID_TESTNET) { + this.setState({ + errorType: BalanceErrs.incorrectNetworkForFaucet, + }); + return false; + } - await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY); + await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY); - const segment = isEtherRequest ? 'ether' : 'zrx'; - const response = await fetch(`${constants.URL_ETHER_FAUCET}/${segment}/${this.props.userAddress}`); - const responseBody = await response.text(); - if (response.status !== constants.SUCCESS_STATUS) { - utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); - const errorType = - response.status === constants.UNAVAILABLE_STATUS - ? BalanceErrs.faucetQueueIsFull - : BalanceErrs.faucetRequestFailed; - this.setState({ - errorType, - }); - await errorReporter.reportAsync(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`)); - return false; - } + const segment = isEtherRequest ? 'ether' : 'zrx'; + const response = await fetch(`${constants.URL_ETHER_FAUCET}/${segment}/${this.props.userAddress}`); + const responseBody = await response.text(); + if (response.status !== constants.SUCCESS_STATUS) { + utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); + const errorType = + response.status === constants.UNAVAILABLE_STATUS + ? BalanceErrs.faucetQueueIsFull + : BalanceErrs.faucetRequestFailed; + this.setState({ + errorType, + }); + await errorReporter.reportAsync(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`)); + return false; + } - if (isEtherRequest) { - this.setState({ - isBalanceSpinnerVisible: true, - }); - } else { - const tokens = _.values(this.props.tokenByAddress); - const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL); - const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address]; - this.setState({ - isZRXSpinnerVisible: true, - currentZrxBalance: zrxTokenState.balance, - }); - // tslint:disable-next-line:no-floating-promises - this.props.blockchain.pollTokenBalanceAsync(zrxToken); - } - return true; - } - private _onErrorDialogToggle(isOpen: boolean) { - this.setState({ - errorType: undefined, - }); - } - private _onDharmaDialogToggle() { - this.setState({ - isDharmaDialogVisible: !this.state.isDharmaDialogVisible, - }); - } - private _onAddTokenClicked() { - this.setState({ - isTokenPickerOpen: true, - isAddingToken: true, - }); - } - private _onRemoveTokenClicked() { - this.setState({ - isTokenPickerOpen: true, - isAddingToken: false, - }); - } + if (isEtherRequest) { + this.setState({ + isBalanceSpinnerVisible: true, + }); + } else { + const tokens = _.values(this.props.tokenByAddress); + const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL); + const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address]; + this.setState({ + isZRXSpinnerVisible: true, + currentZrxBalance: zrxTokenState.balance, + }); + // tslint:disable-next-line:no-floating-promises + this.props.blockchain.pollTokenBalanceAsync(zrxToken); + } + return true; + } + private _onErrorDialogToggle(isOpen: boolean) { + this.setState({ + errorType: undefined, + }); + } + private _onDharmaDialogToggle() { + this.setState({ + isDharmaDialogVisible: !this.state.isDharmaDialogVisible, + }); + } + private _onAddTokenClicked() { + this.setState({ + isTokenPickerOpen: true, + isAddingToken: true, + }); + } + private _onRemoveTokenClicked() { + this.setState({ + isTokenPickerOpen: true, + isAddingToken: false, + }); + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/top_bar.tsx b/packages/website/ts/components/top_bar.tsx index 1f111cb07..11d3e7cc2 100644 --- a/packages/website/ts/components/top_bar.tsx +++ b/packages/website/ts/components/top_bar.tsx @@ -15,333 +15,333 @@ import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; interface TopBarProps { - userAddress?: string; - blockchainIsLoaded: boolean; - location: Location; - docsVersion?: string; - availableDocVersions?: string[]; - menu?: DocsMenu; - menuSubsectionsBySection?: MenuSubsectionsBySection; - shouldFullWidth?: boolean; - docsInfo?: DocsInfo; - style?: React.CSSProperties; - isNightVersion?: boolean; + userAddress?: string; + blockchainIsLoaded: boolean; + location: Location; + docsVersion?: string; + availableDocVersions?: string[]; + menu?: DocsMenu; + menuSubsectionsBySection?: MenuSubsectionsBySection; + shouldFullWidth?: boolean; + docsInfo?: DocsInfo; + style?: React.CSSProperties; + isNightVersion?: boolean; } interface TopBarState { - isDrawerOpen: boolean; + isDrawerOpen: boolean; } const styles: Styles = { - address: { - marginRight: 12, - overflow: 'hidden', - paddingTop: 4, - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - width: 70, - }, - topBar: { - backgroundcolor: colors.white, - height: 59, - width: '100%', - position: 'relative', - top: 0, - zIndex: 1100, - paddingBottom: 1, - }, - bottomBar: { - boxShadow: 'rgba(0, 0, 0, 0.187647) 0px 1px 3px', - }, - menuItem: { - fontSize: 14, - color: colors.darkestGrey, - paddingTop: 6, - paddingBottom: 6, - marginTop: 17, - cursor: 'pointer', - fontWeight: 400, - }, + address: { + marginRight: 12, + overflow: 'hidden', + paddingTop: 4, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + width: 70, + }, + topBar: { + backgroundcolor: colors.white, + height: 59, + width: '100%', + position: 'relative', + top: 0, + zIndex: 1100, + paddingBottom: 1, + }, + bottomBar: { + boxShadow: 'rgba(0, 0, 0, 0.187647) 0px 1px 3px', + }, + menuItem: { + fontSize: 14, + color: colors.darkestGrey, + paddingTop: 6, + paddingBottom: 6, + marginTop: 17, + cursor: 'pointer', + fontWeight: 400, + }, }; export class TopBar extends React.Component<TopBarProps, TopBarState> { - public static defaultProps: Partial<TopBarProps> = { - shouldFullWidth: false, - style: {}, - isNightVersion: false, - }; - constructor(props: TopBarProps) { - super(props); - this.state = { - isDrawerOpen: false, - }; - } - public render() { - const isNightVersion = this.props.isNightVersion; - const isFullWidthPage = this.props.shouldFullWidth; - const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`; - const developerSectionMenuItems = [ - <Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" /> - </Link>, - <Link key="subMenuItem-smartContracts" to={WebsitePaths.SmartContracts} className="text-decoration-none"> - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Smart Contracts" /> - </Link>, - <Link key="subMenuItem-0xconnect" to={WebsitePaths.Connect} className="text-decoration-none"> - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x Connect" /> - </Link>, - <a - key="subMenuItem-standard-relayer-api" - target="_blank" - className="text-decoration-none" - href={constants.URL_STANDARD_RELAYER_API_GITHUB} - > - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Standard Relayer API" /> - </a>, - <a - key="subMenuItem-github" - target="_blank" - className="text-decoration-none" - href={constants.URL_GITHUB_ORG} - > - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="GitHub" /> - </a>, - <a - key="subMenuItem-whitePaper" - target="_blank" - className="text-decoration-none" - href={`${WebsitePaths.Whitepaper}`} - > - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Whitepaper" /> - </a>, - ]; - const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {}; - const fullWidthClasses = isFullWidthPage ? 'pr4' : ''; - const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png'; - const menuClasses = `col col-${isFullWidthPage ? '4' : '5'} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`; - const menuIconStyle = { - fontSize: 25, - color: isNightVersion ? 'white' : 'black', - cursor: 'pointer', - paddingTop: 16, - }; - return ( - <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1"> - <div className={parentClassNames}> - <div className="col col-2 sm-pl2 md-pl2 lg-pl0" style={{ paddingTop: 15 }}> - <Link to={`${WebsitePaths.Home}`} className="text-decoration-none"> - <img src={logoUrl} height="30" /> - </Link> - </div> - <div className={`col col-${isFullWidthPage ? '8' : '9'} lg-hide md-hide`} /> - <div className={`col col-${isFullWidthPage ? '6' : '5'} sm-hide xs-hide`} /> - {!this._isViewingPortal() && ( - <div className={menuClasses}> - <div className="flex justify-between"> - <DropDownMenuItem - title="Developers" - subMenuItems={developerSectionMenuItems} - style={styles.menuItem} - isNightVersion={isNightVersion} - /> - <TopBarMenuItem - title="Wiki" - path={`${WebsitePaths.Wiki}`} - style={styles.menuItem} - isNightVersion={isNightVersion} - /> - <TopBarMenuItem - title="About" - path={`${WebsitePaths.About}`} - style={styles.menuItem} - isNightVersion={isNightVersion} - /> - <TopBarMenuItem - title="Portal DApp" - path={`${WebsitePaths.Portal}`} - isPrimary={true} - style={styles.menuItem} - className={`${isFullWidthPage && 'md-hide'}`} - isNightVersion={isNightVersion} - /> - </div> - </div> - )} - {this.props.blockchainIsLoaded && - !_.isEmpty(this.props.userAddress) && ( - <div className="col col-5 sm-hide xs-hide">{this._renderUser()}</div> - )} - <div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}> - <div style={menuIconStyle}> - <i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} /> - </div> - </div> - </div> - {this._renderDrawer()} - </div> - ); - } - private _renderDrawer() { - return ( - <Drawer - open={this.state.isDrawerOpen} - docked={false} - openSecondary={true} - onRequestChange={this._onMenuButtonClick.bind(this)} - > - {this._renderPortalMenu()} - {this._renderDocsMenu()} - {this._renderWiki()} - <div className="pl1 py1 mt3" style={{ backgroundColor: colors.lightGrey }}> - Website - </div> - <Link to={WebsitePaths.Home} className="text-decoration-none"> - <MenuItem className="py2">Home</MenuItem> - </Link> - <Link to={`${WebsitePaths.Wiki}`} className="text-decoration-none"> - <MenuItem className="py2">Wiki</MenuItem> - </Link> - {!this._isViewing0xjsDocs() && ( - <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <MenuItem className="py2">0x.js Docs</MenuItem> - </Link> - )} - {!this._isViewingConnectDocs() && ( - <Link to={WebsitePaths.Connect} className="text-decoration-none"> - <MenuItem className="py2">0x Connect Docs</MenuItem> - </Link> - )} - {!this._isViewingSmartContractsDocs() && ( - <Link to={WebsitePaths.SmartContracts} className="text-decoration-none"> - <MenuItem className="py2">Smart Contract Docs</MenuItem> - </Link> - )} - {!this._isViewingPortal() && ( - <Link to={`${WebsitePaths.Portal}`} className="text-decoration-none"> - <MenuItem className="py2">Portal DApp</MenuItem> - </Link> - )} - <a className="text-decoration-none" target="_blank" href={`${WebsitePaths.Whitepaper}`}> - <MenuItem className="py2">Whitepaper</MenuItem> - </a> - <Link to={`${WebsitePaths.About}`} className="text-decoration-none"> - <MenuItem className="py2">About</MenuItem> - </Link> - <a className="text-decoration-none" target="_blank" href={constants.URL_BLOG}> - <MenuItem className="py2">Blog</MenuItem> - </a> - <Link to={`${WebsitePaths.FAQ}`} className="text-decoration-none"> - <MenuItem className="py2" onTouchTap={this._onMenuButtonClick.bind(this)}> - FAQ - </MenuItem> - </Link> - </Drawer> - ); - } - private _renderDocsMenu(): React.ReactNode { - if ( - (!this._isViewing0xjsDocs() && !this._isViewingSmartContractsDocs() && !this._isViewingConnectDocs()) || - _.isUndefined(this.props.menu) - ) { - return undefined; - } + public static defaultProps: Partial<TopBarProps> = { + shouldFullWidth: false, + style: {}, + isNightVersion: false, + }; + constructor(props: TopBarProps) { + super(props); + this.state = { + isDrawerOpen: false, + }; + } + public render() { + const isNightVersion = this.props.isNightVersion; + const isFullWidthPage = this.props.shouldFullWidth; + const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`; + const developerSectionMenuItems = [ + <Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none"> + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" /> + </Link>, + <Link key="subMenuItem-smartContracts" to={WebsitePaths.SmartContracts} className="text-decoration-none"> + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Smart Contracts" /> + </Link>, + <Link key="subMenuItem-0xconnect" to={WebsitePaths.Connect} className="text-decoration-none"> + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x Connect" /> + </Link>, + <a + key="subMenuItem-standard-relayer-api" + target="_blank" + className="text-decoration-none" + href={constants.URL_STANDARD_RELAYER_API_GITHUB} + > + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Standard Relayer API" /> + </a>, + <a + key="subMenuItem-github" + target="_blank" + className="text-decoration-none" + href={constants.URL_GITHUB_ORG} + > + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="GitHub" /> + </a>, + <a + key="subMenuItem-whitePaper" + target="_blank" + className="text-decoration-none" + href={`${WebsitePaths.Whitepaper}`} + > + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Whitepaper" /> + </a>, + ]; + const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {}; + const fullWidthClasses = isFullWidthPage ? 'pr4' : ''; + const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png'; + const menuClasses = `col col-${isFullWidthPage ? '4' : '5'} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`; + const menuIconStyle = { + fontSize: 25, + color: isNightVersion ? 'white' : 'black', + cursor: 'pointer', + paddingTop: 16, + }; + return ( + <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1"> + <div className={parentClassNames}> + <div className="col col-2 sm-pl2 md-pl2 lg-pl0" style={{ paddingTop: 15 }}> + <Link to={`${WebsitePaths.Home}`} className="text-decoration-none"> + <img src={logoUrl} height="30" /> + </Link> + </div> + <div className={`col col-${isFullWidthPage ? '8' : '9'} lg-hide md-hide`} /> + <div className={`col col-${isFullWidthPage ? '6' : '5'} sm-hide xs-hide`} /> + {!this._isViewingPortal() && ( + <div className={menuClasses}> + <div className="flex justify-between"> + <DropDownMenuItem + title="Developers" + subMenuItems={developerSectionMenuItems} + style={styles.menuItem} + isNightVersion={isNightVersion} + /> + <TopBarMenuItem + title="Wiki" + path={`${WebsitePaths.Wiki}`} + style={styles.menuItem} + isNightVersion={isNightVersion} + /> + <TopBarMenuItem + title="About" + path={`${WebsitePaths.About}`} + style={styles.menuItem} + isNightVersion={isNightVersion} + /> + <TopBarMenuItem + title="Portal DApp" + path={`${WebsitePaths.Portal}`} + isPrimary={true} + style={styles.menuItem} + className={`${isFullWidthPage && 'md-hide'}`} + isNightVersion={isNightVersion} + /> + </div> + </div> + )} + {this.props.blockchainIsLoaded && + !_.isEmpty(this.props.userAddress) && ( + <div className="col col-5 sm-hide xs-hide">{this._renderUser()}</div> + )} + <div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}> + <div style={menuIconStyle}> + <i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} /> + </div> + </div> + </div> + {this._renderDrawer()} + </div> + ); + } + private _renderDrawer() { + return ( + <Drawer + open={this.state.isDrawerOpen} + docked={false} + openSecondary={true} + onRequestChange={this._onMenuButtonClick.bind(this)} + > + {this._renderPortalMenu()} + {this._renderDocsMenu()} + {this._renderWiki()} + <div className="pl1 py1 mt3" style={{ backgroundColor: colors.lightGrey }}> + Website + </div> + <Link to={WebsitePaths.Home} className="text-decoration-none"> + <MenuItem className="py2">Home</MenuItem> + </Link> + <Link to={`${WebsitePaths.Wiki}`} className="text-decoration-none"> + <MenuItem className="py2">Wiki</MenuItem> + </Link> + {!this._isViewing0xjsDocs() && ( + <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> + <MenuItem className="py2">0x.js Docs</MenuItem> + </Link> + )} + {!this._isViewingConnectDocs() && ( + <Link to={WebsitePaths.Connect} className="text-decoration-none"> + <MenuItem className="py2">0x Connect Docs</MenuItem> + </Link> + )} + {!this._isViewingSmartContractsDocs() && ( + <Link to={WebsitePaths.SmartContracts} className="text-decoration-none"> + <MenuItem className="py2">Smart Contract Docs</MenuItem> + </Link> + )} + {!this._isViewingPortal() && ( + <Link to={`${WebsitePaths.Portal}`} className="text-decoration-none"> + <MenuItem className="py2">Portal DApp</MenuItem> + </Link> + )} + <a className="text-decoration-none" target="_blank" href={`${WebsitePaths.Whitepaper}`}> + <MenuItem className="py2">Whitepaper</MenuItem> + </a> + <Link to={`${WebsitePaths.About}`} className="text-decoration-none"> + <MenuItem className="py2">About</MenuItem> + </Link> + <a className="text-decoration-none" target="_blank" href={constants.URL_BLOG}> + <MenuItem className="py2">Blog</MenuItem> + </a> + <Link to={`${WebsitePaths.FAQ}`} className="text-decoration-none"> + <MenuItem className="py2" onTouchTap={this._onMenuButtonClick.bind(this)}> + FAQ + </MenuItem> + </Link> + </Drawer> + ); + } + private _renderDocsMenu(): React.ReactNode { + if ( + (!this._isViewing0xjsDocs() && !this._isViewingSmartContractsDocs() && !this._isViewingConnectDocs()) || + _.isUndefined(this.props.menu) + ) { + return undefined; + } - const sectionTitle = `${this.props.docsInfo.displayName} Docs`; - return ( - <div className="lg-hide md-hide"> - <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> - {sectionTitle} - </div> - <NestedSidebarMenu - topLevelMenu={this.props.menu} - menuSubsectionsBySection={this.props.menuSubsectionsBySection} - shouldDisplaySectionHeaders={false} - onMenuItemClick={this._onMenuButtonClick.bind(this)} - selectedVersion={this.props.docsVersion} - docPath={this.props.docsInfo.websitePath} - versions={this.props.availableDocVersions} - /> - </div> - ); - } - private _renderWiki(): React.ReactNode { - if (!this._isViewingWiki()) { - return undefined; - } + const sectionTitle = `${this.props.docsInfo.displayName} Docs`; + return ( + <div className="lg-hide md-hide"> + <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> + {sectionTitle} + </div> + <NestedSidebarMenu + topLevelMenu={this.props.menu} + menuSubsectionsBySection={this.props.menuSubsectionsBySection} + shouldDisplaySectionHeaders={false} + onMenuItemClick={this._onMenuButtonClick.bind(this)} + selectedVersion={this.props.docsVersion} + docPath={this.props.docsInfo.websitePath} + versions={this.props.availableDocVersions} + /> + </div> + ); + } + private _renderWiki(): React.ReactNode { + if (!this._isViewingWiki()) { + return undefined; + } - return ( - <div className="lg-hide md-hide"> - <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> - 0x Protocol Wiki - </div> - <NestedSidebarMenu - topLevelMenu={this.props.menuSubsectionsBySection} - menuSubsectionsBySection={this.props.menuSubsectionsBySection} - shouldDisplaySectionHeaders={false} - onMenuItemClick={this._onMenuButtonClick.bind(this)} - /> - </div> - ); - } - private _renderPortalMenu(): React.ReactNode { - if (!this._isViewingPortal()) { - return undefined; - } + return ( + <div className="lg-hide md-hide"> + <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> + 0x Protocol Wiki + </div> + <NestedSidebarMenu + topLevelMenu={this.props.menuSubsectionsBySection} + menuSubsectionsBySection={this.props.menuSubsectionsBySection} + shouldDisplaySectionHeaders={false} + onMenuItemClick={this._onMenuButtonClick.bind(this)} + /> + </div> + ); + } + private _renderPortalMenu(): React.ReactNode { + if (!this._isViewingPortal()) { + return undefined; + } - return ( - <div className="lg-hide md-hide"> - <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> - Portal DApp - </div> - <PortalMenu menuItemStyle={{ color: 'black' }} onClick={this._onMenuButtonClick.bind(this)} /> - </div> - ); - } - private _renderUser() { - const userAddress = this.props.userAddress; - const identiconDiameter = 26; - return ( - <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}> - <div style={styles.address} data-tip={true} data-for="userAddressTooltip"> - {!_.isEmpty(userAddress) ? userAddress : ''} - </div> - <ReactTooltip id="userAddressTooltip">{userAddress}</ReactTooltip> - <div> - <Identicon address={userAddress} diameter={identiconDiameter} /> - </div> - </div> - ); - } - private _onMenuButtonClick() { - this.setState({ - isDrawerOpen: !this.state.isDrawerOpen, - }); - } - private _isViewingPortal() { - return _.includes(this.props.location.pathname, WebsitePaths.Portal); - } - private _isViewingFAQ() { - return _.includes(this.props.location.pathname, WebsitePaths.FAQ); - } - private _isViewing0xjsDocs() { - return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs); - } - private _isViewingConnectDocs() { - return _.includes(this.props.location.pathname, WebsitePaths.Connect); - } - private _isViewingSmartContractsDocs() { - return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts); - } - private _isViewingWiki() { - return _.includes(this.props.location.pathname, WebsitePaths.Wiki); - } - private _shouldDisplayBottomBar() { - return ( - this._isViewingWiki() || - this._isViewing0xjsDocs() || - this._isViewingFAQ() || - this._isViewingSmartContractsDocs() || - this._isViewingConnectDocs() - ); - } + return ( + <div className="lg-hide md-hide"> + <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> + Portal DApp + </div> + <PortalMenu menuItemStyle={{ color: 'black' }} onClick={this._onMenuButtonClick.bind(this)} /> + </div> + ); + } + private _renderUser() { + const userAddress = this.props.userAddress; + const identiconDiameter = 26; + return ( + <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}> + <div style={styles.address} data-tip={true} data-for="userAddressTooltip"> + {!_.isEmpty(userAddress) ? userAddress : ''} + </div> + <ReactTooltip id="userAddressTooltip">{userAddress}</ReactTooltip> + <div> + <Identicon address={userAddress} diameter={identiconDiameter} /> + </div> + </div> + ); + } + private _onMenuButtonClick() { + this.setState({ + isDrawerOpen: !this.state.isDrawerOpen, + }); + } + private _isViewingPortal() { + return _.includes(this.props.location.pathname, WebsitePaths.Portal); + } + private _isViewingFAQ() { + return _.includes(this.props.location.pathname, WebsitePaths.FAQ); + } + private _isViewing0xjsDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs); + } + private _isViewingConnectDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.Connect); + } + private _isViewingSmartContractsDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts); + } + private _isViewingWiki() { + return _.includes(this.props.location.pathname, WebsitePaths.Wiki); + } + private _shouldDisplayBottomBar() { + return ( + this._isViewingWiki() || + this._isViewing0xjsDocs() || + this._isViewingFAQ() || + this._isViewingSmartContractsDocs() || + this._isViewingConnectDocs() + ); + } } diff --git a/packages/website/ts/components/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar_menu_item.tsx index 0138740ba..96ee86142 100644 --- a/packages/website/ts/components/top_bar_menu_item.tsx +++ b/packages/website/ts/components/top_bar_menu_item.tsx @@ -4,49 +4,49 @@ import { Link } from 'react-router-dom'; import { colors } from 'ts/utils/colors'; const DEFAULT_STYLE = { - color: colors.darkestGrey, + color: colors.darkestGrey, }; interface TopBarMenuItemProps { - title: string; - path?: string; - isPrimary?: boolean; - style?: React.CSSProperties; - className?: string; - isNightVersion?: boolean; + title: string; + path?: string; + isPrimary?: boolean; + style?: React.CSSProperties; + className?: string; + isNightVersion?: boolean; } interface TopBarMenuItemState {} export class TopBarMenuItem extends React.Component<TopBarMenuItemProps, TopBarMenuItemState> { - public static defaultProps: Partial<TopBarMenuItemProps> = { - isPrimary: false, - style: DEFAULT_STYLE, - className: '', - isNightVersion: false, - }; - public render() { - const primaryStyles = this.props.isPrimary - ? { - borderRadius: 4, - border: `1px solid ${this.props.isNightVersion ? colors.grey : colors.greyishPink}`, - marginTop: 15, - paddingLeft: 9, - paddingRight: 9, - width: 77, - } - : {}; - const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color; - const linkColor = _.isUndefined(menuItemColor) ? colors.darkestGrey : menuItemColor; - return ( - <div - className={`center ${this.props.className}`} - style={{ ...this.props.style, ...primaryStyles, color: menuItemColor }} - > - <Link to={this.props.path} className="text-decoration-none" style={{ color: linkColor }}> - {this.props.title} - </Link> - </div> - ); - } + public static defaultProps: Partial<TopBarMenuItemProps> = { + isPrimary: false, + style: DEFAULT_STYLE, + className: '', + isNightVersion: false, + }; + public render() { + const primaryStyles = this.props.isPrimary + ? { + borderRadius: 4, + border: `1px solid ${this.props.isNightVersion ? colors.grey : colors.greyishPink}`, + marginTop: 15, + paddingLeft: 9, + paddingRight: 9, + width: 77, + } + : {}; + const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color; + const linkColor = _.isUndefined(menuItemColor) ? colors.darkestGrey : menuItemColor; + return ( + <div + className={`center ${this.props.className}`} + style={{ ...this.props.style, ...primaryStyles, color: menuItemColor }} + > + <Link to={this.props.path} className="text-decoration-none" style={{ color: linkColor }}> + {this.props.title} + </Link> + </div> + ); + } } diff --git a/packages/website/ts/components/track_token_confirmation.tsx b/packages/website/ts/components/track_token_confirmation.tsx index 1887bd11c..76971aefa 100644 --- a/packages/website/ts/components/track_token_confirmation.tsx +++ b/packages/website/ts/components/track_token_confirmation.tsx @@ -6,56 +6,56 @@ import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface TrackTokenConfirmationProps { - tokens: Token[]; - tokenByAddress: TokenByAddress; - networkId: number; - isAddingTokenToTracked: boolean; + tokens: Token[]; + tokenByAddress: TokenByAddress; + networkId: number; + isAddingTokenToTracked: boolean; } interface TrackTokenConfirmationState {} export class TrackTokenConfirmation extends React.Component<TrackTokenConfirmationProps, TrackTokenConfirmationState> { - public render() { - const isMultipleTokens = this.props.tokens.length > 1; - const allTokens = _.values(this.props.tokenByAddress); - return ( - <div style={{ color: colors.grey700 }}> - {this.props.isAddingTokenToTracked ? ( - <div className="py4 my4 center"> - <span className="pr1"> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </span> - <span>Adding token{isMultipleTokens && 's'}...</span> - </div> - ) : ( - <div> - <div>You do not currently track the following token{isMultipleTokens && 's'}:</div> - <div className="py2 clearfix mx-auto center" style={{ width: 355 }}> - {_.map(this.props.tokens, (token: Token) => ( - <div - key={`token-profile-${token.name}`} - className={`col col-${isMultipleTokens ? '6' : '12'} px2`} - > - <Party - label={token.name} - address={token.address} - networkId={this.props.networkId} - alternativeImage={token.iconUrl} - isInTokenRegistry={token.isRegistered} - hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, token)} - /> - </div> - ))} - </div> - <div> - Tracking a token adds it to the balances section of 0x Portal and allows you to - generate/fill orders involving the token - {isMultipleTokens && 's'}. Would you like to start tracking{' '} - {isMultipleTokens ? 'these' : 'this'} token? - </div> - </div> - )} - </div> - ); - } + public render() { + const isMultipleTokens = this.props.tokens.length > 1; + const allTokens = _.values(this.props.tokenByAddress); + return ( + <div style={{ color: colors.grey700 }}> + {this.props.isAddingTokenToTracked ? ( + <div className="py4 my4 center"> + <span className="pr1"> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </span> + <span>Adding token{isMultipleTokens && 's'}...</span> + </div> + ) : ( + <div> + <div>You do not currently track the following token{isMultipleTokens && 's'}:</div> + <div className="py2 clearfix mx-auto center" style={{ width: 355 }}> + {_.map(this.props.tokens, (token: Token) => ( + <div + key={`token-profile-${token.name}`} + className={`col col-${isMultipleTokens ? '6' : '12'} px2`} + > + <Party + label={token.name} + address={token.address} + networkId={this.props.networkId} + alternativeImage={token.iconUrl} + isInTokenRegistry={token.isRegistered} + hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, token)} + /> + </div> + ))} + </div> + <div> + Tracking a token adds it to the balances section of 0x Portal and allows you to + generate/fill orders involving the token + {isMultipleTokens && 's'}. Would you like to start tracking{' '} + {isMultipleTokens ? 'these' : 'this'} token? + </div> + </div> + )} + </div> + ); + } } diff --git a/packages/website/ts/components/trade_history/trade_history.tsx b/packages/website/ts/components/trade_history/trade_history.tsx index 3963135f7..635358627 100644 --- a/packages/website/ts/components/trade_history/trade_history.tsx +++ b/packages/website/ts/components/trade_history/trade_history.tsx @@ -10,106 +10,106 @@ import { utils } from 'ts/utils/utils'; const FILL_POLLING_INTERVAL = 1000; interface TradeHistoryProps { - tokenByAddress: TokenByAddress; - userAddress: string; - networkId: number; + tokenByAddress: TokenByAddress; + userAddress: string; + networkId: number; } interface TradeHistoryState { - sortedFills: Fill[]; + sortedFills: Fill[]; } export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistoryState> { - private _fillPollingIntervalId: number; - public constructor(props: TradeHistoryProps) { - super(props); - const sortedFills = this._getSortedFills(); - this.state = { - sortedFills, - }; - } - public componentWillMount() { - this._startPollingForFills(); - } - public componentWillUnmount() { - this._stopPollingForFills(); - } - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - return ( - <div className="lg-px4 md-px4 sm-px2"> - <h3>Trade history</h3> - <Divider /> - <div className="pt2" style={{ height: 608, overflow: 'scroll' }}> - {this._renderTrades()} - </div> - </div> - ); - } - private _renderTrades() { - const numNonCustomFills = this._numFillsWithoutCustomERC20Tokens(); - if (numNonCustomFills === 0) { - return this._renderEmptyNotice(); - } + private _fillPollingIntervalId: number; + public constructor(props: TradeHistoryProps) { + super(props); + const sortedFills = this._getSortedFills(); + this.state = { + sortedFills, + }; + } + public componentWillMount() { + this._startPollingForFills(); + } + public componentWillUnmount() { + this._stopPollingForFills(); + } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( + <div className="lg-px4 md-px4 sm-px2"> + <h3>Trade history</h3> + <Divider /> + <div className="pt2" style={{ height: 608, overflow: 'scroll' }}> + {this._renderTrades()} + </div> + </div> + ); + } + private _renderTrades() { + const numNonCustomFills = this._numFillsWithoutCustomERC20Tokens(); + if (numNonCustomFills === 0) { + return this._renderEmptyNotice(); + } - return _.map(this.state.sortedFills, (fill, index) => { - return ( - <TradeHistoryItem - key={`${fill.orderHash}-${fill.filledTakerTokenAmount}-${index}`} - fill={fill} - tokenByAddress={this.props.tokenByAddress} - userAddress={this.props.userAddress} - networkId={this.props.networkId} - /> - ); - }); - } - private _renderEmptyNotice() { - return ( - <Paper className="mt1 p2 mx-auto center" style={{ width: '80%' }}> - No filled orders yet. - </Paper> - ); - } - private _numFillsWithoutCustomERC20Tokens() { - let numNonCustomFills = 0; - const tokens = _.values(this.props.tokenByAddress); - _.each(this.state.sortedFills, fill => { - const takerToken = _.find(tokens, token => { - return token.address === fill.takerToken; - }); - const makerToken = _.find(tokens, token => { - return token.address === fill.makerToken; - }); - // For now we don't show history items for orders using custom ERC20 - // tokens the client does not know how to display. - // TODO: Try to retrieve the name/symbol of an unknown token in order to display it - // Be sure to remove similar logic in trade_history_item.tsx - if (!_.isUndefined(takerToken) && !_.isUndefined(makerToken)) { - numNonCustomFills += 1; - } - }); - return numNonCustomFills; - } - private _startPollingForFills() { - this._fillPollingIntervalId = window.setInterval(() => { - const sortedFills = this._getSortedFills(); - if (!utils.deepEqual(sortedFills, this.state.sortedFills)) { - this.setState({ - sortedFills, - }); - } - }, FILL_POLLING_INTERVAL); - } - private _stopPollingForFills() { - clearInterval(this._fillPollingIntervalId); - } - private _getSortedFills() { - const fillsByHash = tradeHistoryStorage.getUserFillsByHash(this.props.userAddress, this.props.networkId); - const fills = _.values(fillsByHash); - const sortedFills = _.sortBy(fills, [(fill: Fill) => fill.blockTimestamp * -1]); - return sortedFills; - } + return _.map(this.state.sortedFills, (fill, index) => { + return ( + <TradeHistoryItem + key={`${fill.orderHash}-${fill.filledTakerTokenAmount}-${index}`} + fill={fill} + tokenByAddress={this.props.tokenByAddress} + userAddress={this.props.userAddress} + networkId={this.props.networkId} + /> + ); + }); + } + private _renderEmptyNotice() { + return ( + <Paper className="mt1 p2 mx-auto center" style={{ width: '80%' }}> + No filled orders yet. + </Paper> + ); + } + private _numFillsWithoutCustomERC20Tokens() { + let numNonCustomFills = 0; + const tokens = _.values(this.props.tokenByAddress); + _.each(this.state.sortedFills, fill => { + const takerToken = _.find(tokens, token => { + return token.address === fill.takerToken; + }); + const makerToken = _.find(tokens, token => { + return token.address === fill.makerToken; + }); + // For now we don't show history items for orders using custom ERC20 + // tokens the client does not know how to display. + // TODO: Try to retrieve the name/symbol of an unknown token in order to display it + // Be sure to remove similar logic in trade_history_item.tsx + if (!_.isUndefined(takerToken) && !_.isUndefined(makerToken)) { + numNonCustomFills += 1; + } + }); + return numNonCustomFills; + } + private _startPollingForFills() { + this._fillPollingIntervalId = window.setInterval(() => { + const sortedFills = this._getSortedFills(); + if (!utils.deepEqual(sortedFills, this.state.sortedFills)) { + this.setState({ + sortedFills, + }); + } + }, FILL_POLLING_INTERVAL); + } + private _stopPollingForFills() { + clearInterval(this._fillPollingIntervalId); + } + private _getSortedFills() { + const fillsByHash = tradeHistoryStorage.getUserFillsByHash(this.props.userAddress, this.props.networkId); + const fills = _.values(fillsByHash); + const sortedFills = _.sortBy(fills, [(fill: Fill) => fill.blockTimestamp * -1]); + return sortedFills; + } } diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx index 118bde3b9..7e42e64e6 100644 --- a/packages/website/ts/components/trade_history/trade_history_item.tsx +++ b/packages/website/ts/components/trade_history/trade_history_item.tsx @@ -14,157 +14,157 @@ const PRECISION = 5; const IDENTICON_DIAMETER = 40; interface TradeHistoryItemProps { - fill: Fill; - tokenByAddress: TokenByAddress; - userAddress: string; - networkId: number; + fill: Fill; + tokenByAddress: TokenByAddress; + userAddress: string; + networkId: number; } interface TradeHistoryItemState {} export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, TradeHistoryItemState> { - public render() { - const fill = this.props.fill; - const tokens = _.values(this.props.tokenByAddress); - const takerToken = _.find(tokens, token => { - return token.address === fill.takerToken; - }); - const makerToken = _.find(tokens, token => { - return token.address === fill.makerToken; - }); - // For now we don't show history items for orders using custom ERC20 - // tokens the client does not know how to display. - // TODO: Try to retrieve the name/symbol of an unknown token in order to display it - // Be sure to remove similar logic in trade_history.tsx - if (_.isUndefined(takerToken) || _.isUndefined(makerToken)) { - return null; - } + public render() { + const fill = this.props.fill; + const tokens = _.values(this.props.tokenByAddress); + const takerToken = _.find(tokens, token => { + return token.address === fill.takerToken; + }); + const makerToken = _.find(tokens, token => { + return token.address === fill.makerToken; + }); + // For now we don't show history items for orders using custom ERC20 + // tokens the client does not know how to display. + // TODO: Try to retrieve the name/symbol of an unknown token in order to display it + // Be sure to remove similar logic in trade_history.tsx + if (_.isUndefined(takerToken) || _.isUndefined(makerToken)) { + return null; + } - const amountColStyle: React.CSSProperties = { - fontWeight: 100, - display: 'inline-block', - }; - const amountColClassNames = - 'col col-12 lg-col-4 md-col-4 lg-py2 md-py2 sm-py1 lg-pr2 md-pr2 \ + const amountColStyle: React.CSSProperties = { + fontWeight: 100, + display: 'inline-block', + }; + const amountColClassNames = + 'col col-12 lg-col-4 md-col-4 lg-py2 md-py2 sm-py1 lg-pr2 md-pr2 \ lg-right-align md-right-align sm-center'; - return ( - <Paper className="py1" style={{ margin: '3px 3px 15px 3px' }}> - <div className="clearfix"> - <div className="col col-12 lg-col-1 md-col-1 pt2 lg-pl3 md-pl3">{this._renderDate()}</div> - <div - className="col col-12 lg-col-6 md-col-6 lg-pl3 md-pl3" - style={{ fontSize: 12, fontWeight: 100 }} - > - <div className="flex sm-mx-auto xs-mx-auto" style={{ paddingTop: 4, width: 224 }}> - <Party - label="Maker" - address={fill.maker} - identiconDiameter={IDENTICON_DIAMETER} - networkId={this.props.networkId} - /> - <i style={{ fontSize: 30 }} className="zmdi zmdi-swap py3" /> - <Party - label="Taker" - address={fill.taker} - identiconDiameter={IDENTICON_DIAMETER} - networkId={this.props.networkId} - /> - </div> - </div> - <div className={amountColClassNames} style={amountColStyle}> - {this._renderAmounts(makerToken, takerToken)} - </div> - <div className="col col-12 lg-col-1 md-col-1 lg-pr3 md-pr3 lg-py3 md-py3 sm-pb1 sm-center"> - <div className="pt1 lg-right md-right sm-mx-auto" style={{ width: 13 }}> - <EtherScanIcon - addressOrTxHash={fill.transactionHash} - networkId={this.props.networkId} - etherscanLinkSuffixes={EtherscanLinkSuffixes.Tx} - /> - </div> - </div> - </div> - </Paper> - ); - } - private _renderAmounts(makerToken: Token, takerToken: Token) { - const fill = this.props.fill; - const filledTakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledTakerTokenAmount, takerToken.decimals); - const filledMakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledMakerTokenAmount, takerToken.decimals); - let exchangeRate = filledTakerTokenAmountInUnits.div(filledMakerTokenAmountInUnits); - const fillMakerTokenAmount = ZeroEx.toBaseUnitAmount(filledMakerTokenAmountInUnits, makerToken.decimals); + return ( + <Paper className="py1" style={{ margin: '3px 3px 15px 3px' }}> + <div className="clearfix"> + <div className="col col-12 lg-col-1 md-col-1 pt2 lg-pl3 md-pl3">{this._renderDate()}</div> + <div + className="col col-12 lg-col-6 md-col-6 lg-pl3 md-pl3" + style={{ fontSize: 12, fontWeight: 100 }} + > + <div className="flex sm-mx-auto xs-mx-auto" style={{ paddingTop: 4, width: 224 }}> + <Party + label="Maker" + address={fill.maker} + identiconDiameter={IDENTICON_DIAMETER} + networkId={this.props.networkId} + /> + <i style={{ fontSize: 30 }} className="zmdi zmdi-swap py3" /> + <Party + label="Taker" + address={fill.taker} + identiconDiameter={IDENTICON_DIAMETER} + networkId={this.props.networkId} + /> + </div> + </div> + <div className={amountColClassNames} style={amountColStyle}> + {this._renderAmounts(makerToken, takerToken)} + </div> + <div className="col col-12 lg-col-1 md-col-1 lg-pr3 md-pr3 lg-py3 md-py3 sm-pb1 sm-center"> + <div className="pt1 lg-right md-right sm-mx-auto" style={{ width: 13 }}> + <EtherScanIcon + addressOrTxHash={fill.transactionHash} + networkId={this.props.networkId} + etherscanLinkSuffixes={EtherscanLinkSuffixes.Tx} + /> + </div> + </div> + </div> + </Paper> + ); + } + private _renderAmounts(makerToken: Token, takerToken: Token) { + const fill = this.props.fill; + const filledTakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledTakerTokenAmount, takerToken.decimals); + const filledMakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledMakerTokenAmount, takerToken.decimals); + let exchangeRate = filledTakerTokenAmountInUnits.div(filledMakerTokenAmountInUnits); + const fillMakerTokenAmount = ZeroEx.toBaseUnitAmount(filledMakerTokenAmountInUnits, makerToken.decimals); - let receiveAmount; - let receiveToken; - let givenAmount; - let givenToken; - if (this.props.userAddress === fill.maker && this.props.userAddress === fill.taker) { - receiveAmount = new BigNumber(0); - givenAmount = new BigNumber(0); - receiveToken = makerToken; - givenToken = takerToken; - } else if (this.props.userAddress === fill.maker) { - receiveAmount = fill.filledTakerTokenAmount; - givenAmount = fillMakerTokenAmount; - receiveToken = takerToken; - givenToken = makerToken; - exchangeRate = new BigNumber(1).div(exchangeRate); - } else if (this.props.userAddress === fill.taker) { - receiveAmount = fillMakerTokenAmount; - givenAmount = fill.filledTakerTokenAmount; - receiveToken = makerToken; - givenToken = takerToken; - } else { - // This condition should never be hit - throw new Error("Found Fill that wasn't performed by this user"); - } + let receiveAmount; + let receiveToken; + let givenAmount; + let givenToken; + if (this.props.userAddress === fill.maker && this.props.userAddress === fill.taker) { + receiveAmount = new BigNumber(0); + givenAmount = new BigNumber(0); + receiveToken = makerToken; + givenToken = takerToken; + } else if (this.props.userAddress === fill.maker) { + receiveAmount = fill.filledTakerTokenAmount; + givenAmount = fillMakerTokenAmount; + receiveToken = takerToken; + givenToken = makerToken; + exchangeRate = new BigNumber(1).div(exchangeRate); + } else if (this.props.userAddress === fill.taker) { + receiveAmount = fillMakerTokenAmount; + givenAmount = fill.filledTakerTokenAmount; + receiveToken = makerToken; + givenToken = takerToken; + } else { + // This condition should never be hit + throw new Error("Found Fill that wasn't performed by this user"); + } - return ( - <div> - <div style={{ color: colors.green400, fontSize: 16 }}> - <span>+ </span> - {this._renderAmount(receiveAmount, receiveToken.symbol, receiveToken.decimals)} - </div> - <div className="pb1 inline-block" style={{ color: colors.red200, fontSize: 16 }}> - <span>- </span> - {this._renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)} - </div> - <div style={{ color: colors.grey400, fontSize: 14 }}> - {exchangeRate.toFixed(PRECISION)} {givenToken.symbol}/{receiveToken.symbol} - </div> - </div> - ); - } - private _renderDate() { - const blockMoment = moment.unix(this.props.fill.blockTimestamp); - if (!blockMoment.isValid()) { - return null; - } + return ( + <div> + <div style={{ color: colors.green400, fontSize: 16 }}> + <span>+ </span> + {this._renderAmount(receiveAmount, receiveToken.symbol, receiveToken.decimals)} + </div> + <div className="pb1 inline-block" style={{ color: colors.red200, fontSize: 16 }}> + <span>- </span> + {this._renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)} + </div> + <div style={{ color: colors.grey400, fontSize: 14 }}> + {exchangeRate.toFixed(PRECISION)} {givenToken.symbol}/{receiveToken.symbol} + </div> + </div> + ); + } + private _renderDate() { + const blockMoment = moment.unix(this.props.fill.blockTimestamp); + if (!blockMoment.isValid()) { + return null; + } - const dayOfMonth = blockMoment.format('D'); - const monthAbreviation = blockMoment.format('MMM'); - const formattedBlockDate = blockMoment.format('H:mmA - MMMM D, YYYY'); - const dateTooltipId = `${this.props.fill.transactionHash}-date`; + const dayOfMonth = blockMoment.format('D'); + const monthAbreviation = blockMoment.format('MMM'); + const formattedBlockDate = blockMoment.format('H:mmA - MMMM D, YYYY'); + const dateTooltipId = `${this.props.fill.transactionHash}-date`; - return ( - <div data-tip={true} data-for={dateTooltipId}> - <div className="center pt1" style={{ fontSize: 13 }}> - {monthAbreviation} - </div> - <div className="center" style={{ fontSize: 24, fontWeight: 100 }}> - {dayOfMonth} - </div> - <ReactTooltip id={dateTooltipId}>{formattedBlockDate}</ReactTooltip> - </div> - ); - } - private _renderAmount(amount: BigNumber, symbol: string, decimals: number) { - const unitAmount = ZeroEx.toUnitAmount(amount, decimals); - return ( - <span> - {unitAmount.toFixed(PRECISION)} {symbol} - </span> - ); - } + return ( + <div data-tip={true} data-for={dateTooltipId}> + <div className="center pt1" style={{ fontSize: 13 }}> + {monthAbreviation} + </div> + <div className="center" style={{ fontSize: 24, fontWeight: 100 }}> + {dayOfMonth} + </div> + <ReactTooltip id={dateTooltipId}>{formattedBlockDate}</ReactTooltip> + </div> + ); + } + private _renderAmount(amount: BigNumber, symbol: string, decimals: number) { + const unitAmount = ZeroEx.toUnitAmount(amount, decimals); + return ( + <span> + {unitAmount.toFixed(PRECISION)} {symbol} + </span> + ); + } } diff --git a/packages/website/ts/components/ui/alert.tsx b/packages/website/ts/components/ui/alert.tsx index 91ef8f76e..54881b499 100644 --- a/packages/website/ts/components/ui/alert.tsx +++ b/packages/website/ts/components/ui/alert.tsx @@ -3,23 +3,23 @@ import { AlertTypes } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface AlertProps { - type: AlertTypes; - message: string | React.ReactNode; + type: AlertTypes; + message: string | React.ReactNode; } export function Alert(props: AlertProps) { - const isAlert = props.type === AlertTypes.ERROR; - const errMsgStyles = { - background: isAlert ? colors.red200 : colors.lightestGreen, - color: colors.white, - marginTop: 10, - padding: 4, - paddingLeft: 8, - }; + const isAlert = props.type === AlertTypes.ERROR; + const errMsgStyles = { + background: isAlert ? colors.red200 : colors.lightestGreen, + color: colors.white, + marginTop: 10, + padding: 4, + paddingLeft: 8, + }; - return ( - <div className="rounded center" style={errMsgStyles}> - {props.message} - </div> - ); + return ( + <div className="rounded center" style={errMsgStyles}> + {props.message} + </div> + ); } diff --git a/packages/website/ts/components/ui/badge.tsx b/packages/website/ts/components/ui/badge.tsx index 1d2d81af6..7f7ea006e 100644 --- a/packages/website/ts/components/ui/badge.tsx +++ b/packages/website/ts/components/ui/badge.tsx @@ -3,55 +3,55 @@ import * as React from 'react'; import { Styles } from 'ts/types'; const styles: Styles = { - badge: { - width: 50, - fontSize: 11, - height: 10, - borderRadius: 5, - marginTop: 25, - lineHeight: 0.9, - fontFamily: 'Roboto Mono', - marginLeft: 3, - marginRight: 3, - }, + badge: { + width: 50, + fontSize: 11, + height: 10, + borderRadius: 5, + marginTop: 25, + lineHeight: 0.9, + fontFamily: 'Roboto Mono', + marginLeft: 3, + marginRight: 3, + }, }; interface BadgeProps { - title: string; - backgroundColor: string; + title: string; + backgroundColor: string; } interface BadgeState { - isHovering: boolean; + isHovering: boolean; } export class Badge extends React.Component<BadgeProps, BadgeState> { - constructor(props: BadgeProps) { - super(props); - this.state = { - isHovering: false, - }; - } - public render() { - const badgeStyle = { - ...styles.badge, - backgroundColor: this.props.backgroundColor, - opacity: this.state.isHovering ? 0.7 : 1, - }; - return ( - <div - className="p1 center" - style={badgeStyle} - onMouseOver={this._setHoverState.bind(this, true)} - onMouseOut={this._setHoverState.bind(this, false)} - > - {this.props.title} - </div> - ); - } - private _setHoverState(isHovering: boolean) { - this.setState({ - isHovering, - }); - } + constructor(props: BadgeProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const badgeStyle = { + ...styles.badge, + backgroundColor: this.props.backgroundColor, + opacity: this.state.isHovering ? 0.7 : 1, + }; + return ( + <div + className="p1 center" + style={badgeStyle} + onMouseOver={this._setHoverState.bind(this, true)} + onMouseOut={this._setHoverState.bind(this, false)} + > + {this.props.title} + </div> + ); + } + private _setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } } diff --git a/packages/website/ts/components/ui/copy_icon.tsx b/packages/website/ts/components/ui/copy_icon.tsx index 72ab77e1f..df55e0922 100644 --- a/packages/website/ts/components/ui/copy_icon.tsx +++ b/packages/website/ts/components/ui/copy_icon.tsx @@ -6,74 +6,74 @@ import ReactTooltip = require('react-tooltip'); import { colors } from 'ts/utils/colors'; interface CopyIconProps { - data: string; - callToAction?: string; + data: string; + callToAction?: string; } interface CopyIconState { - isHovering: boolean; + isHovering: boolean; } export class CopyIcon extends React.Component<CopyIconProps, CopyIconState> { - private _copyTooltipTimeoutId: number; - private _copyable: HTMLInputElement; - constructor(props: CopyIconProps) { - super(props); - this.state = { - isHovering: false, - }; - } - public componentDidUpdate() { - // Remove tooltip if hover away - if (!this.state.isHovering && this._copyTooltipTimeoutId) { - clearInterval(this._copyTooltipTimeoutId); - this._hideTooltip(); - } - } - public render() { - return ( - <div className="inline-block"> - <CopyToClipboard text={this.props.data} onCopy={this._onCopy.bind(this)}> - <div - className="inline flex" - style={{ cursor: 'pointer', color: colors.amber600 }} - ref={this._setRefToProperty.bind(this)} - data-tip={true} - data-for="copy" - data-event="click" - data-iscapture={true} // This let's the click event continue to propogate - onMouseOver={this._setHoverState.bind(this, true)} - onMouseOut={this._setHoverState.bind(this, false)} - > - <div> - <i style={{ fontSize: 15 }} className="zmdi zmdi-copy" /> - </div> - {this.props.callToAction && <div className="pl1">{this.props.callToAction}</div>} - </div> - </CopyToClipboard> - <ReactTooltip id="copy">Copied!</ReactTooltip> - </div> - ); - } - private _setRefToProperty(el: HTMLInputElement) { - this._copyable = el; - } - private _setHoverState(isHovering: boolean) { - this.setState({ - isHovering, - }); - } - private _onCopy() { - if (this._copyTooltipTimeoutId) { - clearInterval(this._copyTooltipTimeoutId); - } + private _copyTooltipTimeoutId: number; + private _copyable: HTMLInputElement; + constructor(props: CopyIconProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public componentDidUpdate() { + // Remove tooltip if hover away + if (!this.state.isHovering && this._copyTooltipTimeoutId) { + clearInterval(this._copyTooltipTimeoutId); + this._hideTooltip(); + } + } + public render() { + return ( + <div className="inline-block"> + <CopyToClipboard text={this.props.data} onCopy={this._onCopy.bind(this)}> + <div + className="inline flex" + style={{ cursor: 'pointer', color: colors.amber600 }} + ref={this._setRefToProperty.bind(this)} + data-tip={true} + data-for="copy" + data-event="click" + data-iscapture={true} // This let's the click event continue to propogate + onMouseOver={this._setHoverState.bind(this, true)} + onMouseOut={this._setHoverState.bind(this, false)} + > + <div> + <i style={{ fontSize: 15 }} className="zmdi zmdi-copy" /> + </div> + {this.props.callToAction && <div className="pl1">{this.props.callToAction}</div>} + </div> + </CopyToClipboard> + <ReactTooltip id="copy">Copied!</ReactTooltip> + </div> + ); + } + private _setRefToProperty(el: HTMLInputElement) { + this._copyable = el; + } + private _setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } + private _onCopy() { + if (this._copyTooltipTimeoutId) { + clearInterval(this._copyTooltipTimeoutId); + } - const tooltipLifespanMs = 1000; - this._copyTooltipTimeoutId = window.setTimeout(() => { - this._hideTooltip(); - }, tooltipLifespanMs); - } - private _hideTooltip() { - ReactTooltip.hide(ReactDOM.findDOMNode(this._copyable)); - } + const tooltipLifespanMs = 1000; + this._copyTooltipTimeoutId = window.setTimeout(() => { + this._hideTooltip(); + }, tooltipLifespanMs); + } + private _hideTooltip() { + ReactTooltip.hide(ReactDOM.findDOMNode(this._copyable)); + } } diff --git a/packages/website/ts/components/ui/drop_down_menu_item.tsx b/packages/website/ts/components/ui/drop_down_menu_item.tsx index 64f88f318..a578fb4f9 100644 --- a/packages/website/ts/components/ui/drop_down_menu_item.tsx +++ b/packages/website/ts/components/ui/drop_down_menu_item.tsx @@ -6,99 +6,99 @@ import { colors } from 'ts/utils/colors'; const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300; const DEFAULT_STYLE = { - fontSize: 14, + fontSize: 14, }; interface DropDownMenuItemProps { - title: string; - subMenuItems: React.ReactNode[]; - style?: React.CSSProperties; - menuItemStyle?: React.CSSProperties; - isNightVersion?: boolean; + title: string; + subMenuItems: React.ReactNode[]; + style?: React.CSSProperties; + menuItemStyle?: React.CSSProperties; + isNightVersion?: boolean; } interface DropDownMenuItemState { - isDropDownOpen: boolean; - anchorEl?: HTMLInputElement; + isDropDownOpen: boolean; + anchorEl?: HTMLInputElement; } export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, DropDownMenuItemState> { - public static defaultProps: Partial<DropDownMenuItemProps> = { - style: DEFAULT_STYLE, - menuItemStyle: DEFAULT_STYLE, - isNightVersion: false, - }; - private _isHovering: boolean; - private _popoverCloseCheckIntervalId: number; - constructor(props: DropDownMenuItemProps) { - super(props); - this.state = { - isDropDownOpen: false, - }; - } - public componentDidMount() { - this._popoverCloseCheckIntervalId = window.setInterval(() => { - this._checkIfShouldClosePopover(); - }, CHECK_CLOSE_POPOVER_INTERVAL_MS); - } - public componentWillUnmount() { - window.clearInterval(this._popoverCloseCheckIntervalId); - } - public render() { - const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color; - return ( - <div - style={{ ...this.props.style, color: colorStyle }} - onMouseEnter={this._onHover.bind(this)} - onMouseLeave={this._onHoverOff.bind(this)} - > - <div className="flex relative"> - <div style={{ paddingRight: 10 }}>{this.props.title}</div> - <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}> - <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} /> - </div> - </div> - <Popover - open={this.state.isDropDownOpen} - anchorEl={this.state.anchorEl} - anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }} - targetOrigin={{ horizontal: 'middle', vertical: 'top' }} - onRequestClose={this._closePopover.bind(this)} - useLayerForClickAway={false} - > - <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}> - <Menu style={{ color: colors.grey }}>{this.props.subMenuItems}</Menu> - </div> - </Popover> - </div> - ); - } - private _onHover(event: React.FormEvent<HTMLInputElement>) { - this._isHovering = true; - this._checkIfShouldOpenPopover(event); - } - private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>) { - if (this.state.isDropDownOpen) { - return; // noop - } + public static defaultProps: Partial<DropDownMenuItemProps> = { + style: DEFAULT_STYLE, + menuItemStyle: DEFAULT_STYLE, + isNightVersion: false, + }; + private _isHovering: boolean; + private _popoverCloseCheckIntervalId: number; + constructor(props: DropDownMenuItemProps) { + super(props); + this.state = { + isDropDownOpen: false, + }; + } + public componentDidMount() { + this._popoverCloseCheckIntervalId = window.setInterval(() => { + this._checkIfShouldClosePopover(); + }, CHECK_CLOSE_POPOVER_INTERVAL_MS); + } + public componentWillUnmount() { + window.clearInterval(this._popoverCloseCheckIntervalId); + } + public render() { + const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color; + return ( + <div + style={{ ...this.props.style, color: colorStyle }} + onMouseEnter={this._onHover.bind(this)} + onMouseLeave={this._onHoverOff.bind(this)} + > + <div className="flex relative"> + <div style={{ paddingRight: 10 }}>{this.props.title}</div> + <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}> + <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} /> + </div> + </div> + <Popover + open={this.state.isDropDownOpen} + anchorEl={this.state.anchorEl} + anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }} + targetOrigin={{ horizontal: 'middle', vertical: 'top' }} + onRequestClose={this._closePopover.bind(this)} + useLayerForClickAway={false} + > + <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}> + <Menu style={{ color: colors.grey }}>{this.props.subMenuItems}</Menu> + </div> + </Popover> + </div> + ); + } + private _onHover(event: React.FormEvent<HTMLInputElement>) { + this._isHovering = true; + this._checkIfShouldOpenPopover(event); + } + private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>) { + if (this.state.isDropDownOpen) { + return; // noop + } - this.setState({ - isDropDownOpen: true, - anchorEl: event.currentTarget, - }); - } - private _onHoverOff(event: React.FormEvent<HTMLInputElement>) { - this._isHovering = false; - } - private _checkIfShouldClosePopover() { - if (!this.state.isDropDownOpen || this._isHovering) { - return; // noop - } - this._closePopover(); - } - private _closePopover() { - this.setState({ - isDropDownOpen: false, - }); - } + this.setState({ + isDropDownOpen: true, + anchorEl: event.currentTarget, + }); + } + private _onHoverOff(event: React.FormEvent<HTMLInputElement>) { + this._isHovering = false; + } + private _checkIfShouldClosePopover() { + if (!this.state.isDropDownOpen || this._isHovering) { + return; // noop + } + this._closePopover(); + } + private _closePopover() { + this.setState({ + isDropDownOpen: false, + }); + } } diff --git a/packages/website/ts/components/ui/ethereum_address.tsx b/packages/website/ts/components/ui/ethereum_address.tsx index ba51135be..b75d97e39 100644 --- a/packages/website/ts/components/ui/ethereum_address.tsx +++ b/packages/website/ts/components/ui/ethereum_address.tsx @@ -5,26 +5,26 @@ import { EtherscanLinkSuffixes } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface EthereumAddressProps { - address: string; - networkId: number; + address: string; + networkId: number; } export const EthereumAddress = (props: EthereumAddressProps) => { - const tooltipId = `${props.address}-ethereum-address`; - const truncatedAddress = utils.getAddressBeginAndEnd(props.address); - return ( - <div> - <div className="inline" style={{ fontSize: 13 }} data-tip={true} data-for={tooltipId}> - {truncatedAddress} - </div> - <div className="pl1 inline"> - <EtherScanIcon - addressOrTxHash={props.address} - networkId={props.networkId} - etherscanLinkSuffixes={EtherscanLinkSuffixes.Address} - /> - </div> - <ReactTooltip id={tooltipId}>{props.address}</ReactTooltip> - </div> - ); + const tooltipId = `${props.address}-ethereum-address`; + const truncatedAddress = utils.getAddressBeginAndEnd(props.address); + return ( + <div> + <div className="inline" style={{ fontSize: 13 }} data-tip={true} data-for={tooltipId}> + {truncatedAddress} + </div> + <div className="pl1 inline"> + <EtherScanIcon + addressOrTxHash={props.address} + networkId={props.networkId} + etherscanLinkSuffixes={EtherscanLinkSuffixes.Address} + /> + </div> + <ReactTooltip id={tooltipId}>{props.address}</ReactTooltip> + </div> + ); }; diff --git a/packages/website/ts/components/ui/etherscan_icon.tsx b/packages/website/ts/components/ui/etherscan_icon.tsx index 5b224c3e1..3b17bd0fa 100644 --- a/packages/website/ts/components/ui/etherscan_icon.tsx +++ b/packages/website/ts/components/ui/etherscan_icon.tsx @@ -6,36 +6,36 @@ import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface EtherScanIconProps { - addressOrTxHash: string; - etherscanLinkSuffixes: EtherscanLinkSuffixes; - networkId: number; + addressOrTxHash: string; + etherscanLinkSuffixes: EtherscanLinkSuffixes; + networkId: number; } export const EtherScanIcon = (props: EtherScanIconProps) => { - const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( - props.addressOrTxHash, - props.networkId, - EtherscanLinkSuffixes.Address, - ); - const transactionTooltipId = `${props.addressOrTxHash}-etherscan-icon-tooltip`; - return ( - <div className="inline"> - {!_.isUndefined(etherscanLinkIfExists) ? ( - <a href={etherscanLinkIfExists} target="_blank"> - {renderIcon()} - </a> - ) : ( - <div className="inline" data-tip={true} data-for={transactionTooltipId}> - {renderIcon()} - <ReactTooltip id={transactionTooltipId}> - Your network (id: {props.networkId}) is not supported by Etherscan - </ReactTooltip> - </div> - )} - </div> - ); + const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( + props.addressOrTxHash, + props.networkId, + EtherscanLinkSuffixes.Address, + ); + const transactionTooltipId = `${props.addressOrTxHash}-etherscan-icon-tooltip`; + return ( + <div className="inline"> + {!_.isUndefined(etherscanLinkIfExists) ? ( + <a href={etherscanLinkIfExists} target="_blank"> + {renderIcon()} + </a> + ) : ( + <div className="inline" data-tip={true} data-for={transactionTooltipId}> + {renderIcon()} + <ReactTooltip id={transactionTooltipId}> + Your network (id: {props.networkId}) is not supported by Etherscan + </ReactTooltip> + </div> + )} + </div> + ); }; function renderIcon() { - return <i style={{ color: colors.amber600 }} className="zmdi zmdi-open-in-new" />; + return <i style={{ color: colors.amber600 }} className="zmdi zmdi-open-in-new" />; } diff --git a/packages/website/ts/components/ui/fake_text_field.tsx b/packages/website/ts/components/ui/fake_text_field.tsx index 6d321bb46..f3d9410f6 100644 --- a/packages/website/ts/components/ui/fake_text_field.tsx +++ b/packages/website/ts/components/ui/fake_text_field.tsx @@ -3,32 +3,32 @@ import { InputLabel } from 'ts/components/ui/input_label'; import { Styles } from 'ts/types'; const styles: Styles = { - hr: { - borderBottom: '1px solid rgb(224, 224, 224)', - borderLeft: 'none rgb(224, 224, 224)', - borderRight: 'none rgb(224, 224, 224)', - borderTop: 'none rgb(224, 224, 224)', - bottom: 6, - boxSizing: 'content-box', - margin: 0, - position: 'absolute', - width: '100%', - }, + hr: { + borderBottom: '1px solid rgb(224, 224, 224)', + borderLeft: 'none rgb(224, 224, 224)', + borderRight: 'none rgb(224, 224, 224)', + borderTop: 'none rgb(224, 224, 224)', + bottom: 6, + boxSizing: 'content-box', + margin: 0, + position: 'absolute', + width: '100%', + }, }; interface FakeTextFieldProps { - label?: React.ReactNode | string; - children?: any; + label?: React.ReactNode | string; + children?: any; } export function FakeTextField(props: FakeTextFieldProps) { - return ( - <div className="relative"> - {props.label !== '' && <InputLabel text={props.label} />} - <div className="pb2" style={{ height: 23 }}> - {props.children} - </div> - <hr style={styles.hr} /> - </div> - ); + return ( + <div className="relative"> + {props.label !== '' && <InputLabel text={props.label} />} + <div className="pb2" style={{ height: 23 }}> + {props.children} + </div> + <hr style={styles.hr} /> + </div> + ); } diff --git a/packages/website/ts/components/ui/flash_message.tsx b/packages/website/ts/components/ui/flash_message.tsx index 57a66d21f..2cb1fc764 100644 --- a/packages/website/ts/components/ui/flash_message.tsx +++ b/packages/website/ts/components/ui/flash_message.tsx @@ -6,35 +6,35 @@ import { Dispatcher } from 'ts/redux/dispatcher'; const SHOW_DURATION_MS = 4000; interface FlashMessageProps { - dispatcher: Dispatcher; - flashMessage?: string | React.ReactNode; - showDurationMs?: number; - bodyStyle?: React.CSSProperties; + dispatcher: Dispatcher; + flashMessage?: string | React.ReactNode; + showDurationMs?: number; + bodyStyle?: React.CSSProperties; } interface FlashMessageState {} export class FlashMessage extends React.Component<FlashMessageProps, FlashMessageState> { - public static defaultProps: Partial<FlashMessageProps> = { - showDurationMs: SHOW_DURATION_MS, - bodyStyle: {}, - }; - public render() { - if (!_.isUndefined(this.props.flashMessage)) { - return ( - <Snackbar - open={true} - message={this.props.flashMessage} - autoHideDuration={this.props.showDurationMs} - onRequestClose={this._onClose.bind(this)} - bodyStyle={this.props.bodyStyle} - /> - ); - } else { - return null; - } - } - private _onClose() { - this.props.dispatcher.hideFlashMessage(); - } + public static defaultProps: Partial<FlashMessageProps> = { + showDurationMs: SHOW_DURATION_MS, + bodyStyle: {}, + }; + public render() { + if (!_.isUndefined(this.props.flashMessage)) { + return ( + <Snackbar + open={true} + message={this.props.flashMessage} + autoHideDuration={this.props.showDurationMs} + onRequestClose={this._onClose.bind(this)} + bodyStyle={this.props.bodyStyle} + /> + ); + } else { + return null; + } + } + private _onClose() { + this.props.dispatcher.hideFlashMessage(); + } } diff --git a/packages/website/ts/components/ui/help_tooltip.tsx b/packages/website/ts/components/ui/help_tooltip.tsx index c946a70cb..d827eebb9 100644 --- a/packages/website/ts/components/ui/help_tooltip.tsx +++ b/packages/website/ts/components/ui/help_tooltip.tsx @@ -2,21 +2,21 @@ import * as React from 'react'; import ReactTooltip = require('react-tooltip'); interface HelpTooltipProps { - style?: React.CSSProperties; - explanation: React.ReactNode; + style?: React.CSSProperties; + explanation: React.ReactNode; } export const HelpTooltip = (props: HelpTooltipProps) => { - return ( - <div - style={{ ...props.style }} - className="inline-block" - data-tip={props.explanation} - data-for="helpTooltip" - data-multiline={true} - > - <i style={{ fontSize: 16 }} className="zmdi zmdi-help" /> - <ReactTooltip id="helpTooltip" /> - </div> - ); + return ( + <div + style={{ ...props.style }} + className="inline-block" + data-tip={props.explanation} + data-for="helpTooltip" + data-multiline={true} + > + <i style={{ fontSize: 16 }} className="zmdi zmdi-help" /> + <ReactTooltip id="helpTooltip" /> + </div> + ); }; diff --git a/packages/website/ts/components/ui/identicon.tsx b/packages/website/ts/components/ui/identicon.tsx index bad7a657d..bad6c2a78 100644 --- a/packages/website/ts/components/ui/identicon.tsx +++ b/packages/website/ts/components/ui/identicon.tsx @@ -4,45 +4,45 @@ import * as React from 'react'; import { constants } from 'ts/utils/constants'; interface IdenticonProps { - address: string; - diameter: number; - style?: React.CSSProperties; + address: string; + diameter: number; + style?: React.CSSProperties; } interface IdenticonState {} export class Identicon extends React.Component<IdenticonProps, IdenticonState> { - public static defaultProps: Partial<IdenticonProps> = { - style: {}, - }; - public render() { - let address = this.props.address; - if (_.isEmpty(address)) { - address = constants.NULL_ADDRESS; - } - const diameter = this.props.diameter; - const icon = blockies({ - seed: address.toLowerCase(), - }); - return ( - <div - className="circle mx-auto relative transitionFix" - style={{ - width: diameter, - height: diameter, - overflow: 'hidden', - ...this.props.style, - }} - > - <img - src={icon.toDataURL()} - style={{ - width: diameter, - height: diameter, - imageRendering: 'pixelated', - }} - /> - </div> - ); - } + public static defaultProps: Partial<IdenticonProps> = { + style: {}, + }; + public render() { + let address = this.props.address; + if (_.isEmpty(address)) { + address = constants.NULL_ADDRESS; + } + const diameter = this.props.diameter; + const icon = blockies({ + seed: address.toLowerCase(), + }); + return ( + <div + className="circle mx-auto relative transitionFix" + style={{ + width: diameter, + height: diameter, + overflow: 'hidden', + ...this.props.style, + }} + > + <img + src={icon.toDataURL()} + style={{ + width: diameter, + height: diameter, + imageRendering: 'pixelated', + }} + /> + </div> + ); + } } diff --git a/packages/website/ts/components/ui/input_label.tsx b/packages/website/ts/components/ui/input_label.tsx index 1cbda6692..e2009ad20 100644 --- a/packages/website/ts/components/ui/input_label.tsx +++ b/packages/website/ts/components/ui/input_label.tsx @@ -2,24 +2,24 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; export interface InputLabelProps { - text: string | Element | React.ReactNode; + text: string | Element | React.ReactNode; } const styles = { - label: { - color: colors.grey, - fontSize: 12, - pointerEvents: 'none', - textAlign: 'left', - transform: 'scale(0.75) translate(0px, -28px)', - transformOrigin: 'left top 0px', - transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', - userSelect: 'none', - width: 240, - zIndex: 1, - }, + label: { + color: colors.grey, + fontSize: 12, + pointerEvents: 'none', + textAlign: 'left', + transform: 'scale(0.75) translate(0px, -28px)', + transformOrigin: 'left top 0px', + transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms', + userSelect: 'none', + width: 240, + zIndex: 1, + }, }; export const InputLabel = (props: InputLabelProps) => { - return <label style={styles.label}>{props.text}</label>; + return <label style={styles.label}>{props.text}</label>; }; diff --git a/packages/website/ts/components/ui/lifecycle_raised_button.tsx b/packages/website/ts/components/ui/lifecycle_raised_button.tsx index fd23912f1..8ff856a75 100644 --- a/packages/website/ts/components/ui/lifecycle_raised_button.tsx +++ b/packages/website/ts/components/ui/lifecycle_raised_button.tsx @@ -7,97 +7,97 @@ import { utils } from 'ts/utils/utils'; const COMPLETE_STATE_SHOW_LENGTH_MS = 2000; enum ButtonState { - READY, - LOADING, - COMPLETE, + READY, + LOADING, + COMPLETE, } interface LifeCycleRaisedButtonProps { - isHidden?: boolean; - isDisabled?: boolean; - isPrimary?: boolean; - labelReady: React.ReactNode | string; - labelLoading: React.ReactNode | string; - labelComplete: React.ReactNode | string; - onClickAsyncFn: () => Promise<boolean>; - backgroundColor?: string; - labelColor?: string; + isHidden?: boolean; + isDisabled?: boolean; + isPrimary?: boolean; + labelReady: React.ReactNode | string; + labelLoading: React.ReactNode | string; + labelComplete: React.ReactNode | string; + onClickAsyncFn: () => Promise<boolean>; + backgroundColor?: string; + labelColor?: string; } interface LifeCycleRaisedButtonState { - buttonState: ButtonState; + buttonState: ButtonState; } export class LifeCycleRaisedButton extends React.Component<LifeCycleRaisedButtonProps, LifeCycleRaisedButtonState> { - public static defaultProps: Partial<LifeCycleRaisedButtonProps> = { - isDisabled: false, - backgroundColor: colors.white, - labelColor: colors.darkGrey, - }; - private _buttonTimeoutId: number; - private _didUnmount: boolean; - constructor(props: LifeCycleRaisedButtonProps) { - super(props); - this.state = { - buttonState: ButtonState.READY, - }; - } - public componentWillUnmount() { - clearTimeout(this._buttonTimeoutId); - this._didUnmount = true; - } - public render() { - if (this.props.isHidden) { - return <span />; - } + public static defaultProps: Partial<LifeCycleRaisedButtonProps> = { + isDisabled: false, + backgroundColor: colors.white, + labelColor: colors.darkGrey, + }; + private _buttonTimeoutId: number; + private _didUnmount: boolean; + constructor(props: LifeCycleRaisedButtonProps) { + super(props); + this.state = { + buttonState: ButtonState.READY, + }; + } + public componentWillUnmount() { + clearTimeout(this._buttonTimeoutId); + this._didUnmount = true; + } + public render() { + if (this.props.isHidden) { + return <span />; + } - let label; - switch (this.state.buttonState) { - case ButtonState.READY: - label = this.props.labelReady; - break; - case ButtonState.LOADING: - label = this.props.labelLoading; - break; - case ButtonState.COMPLETE: - label = this.props.labelComplete; - break; - default: - throw utils.spawnSwitchErr('ButtonState', this.state.buttonState); - } - return ( - <RaisedButton - primary={this.props.isPrimary} - label={label} - style={{ width: '100%' }} - backgroundColor={this.props.backgroundColor} - labelColor={this.props.labelColor} - onTouchTap={this.onClickAsync.bind(this)} - disabled={this.props.isDisabled || this.state.buttonState !== ButtonState.READY} - /> - ); - } - public async onClickAsync() { - this.setState({ - buttonState: ButtonState.LOADING, - }); - const didSucceed = await this.props.onClickAsyncFn(); - if (this._didUnmount) { - return; // noop since unmount called before async callback returned. - } - if (didSucceed) { - this.setState({ - buttonState: ButtonState.COMPLETE, - }); - this._buttonTimeoutId = window.setTimeout(() => { - this.setState({ - buttonState: ButtonState.READY, - }); - }, COMPLETE_STATE_SHOW_LENGTH_MS); - } else { - this.setState({ - buttonState: ButtonState.READY, - }); - } - } + let label; + switch (this.state.buttonState) { + case ButtonState.READY: + label = this.props.labelReady; + break; + case ButtonState.LOADING: + label = this.props.labelLoading; + break; + case ButtonState.COMPLETE: + label = this.props.labelComplete; + break; + default: + throw utils.spawnSwitchErr('ButtonState', this.state.buttonState); + } + return ( + <RaisedButton + primary={this.props.isPrimary} + label={label} + style={{ width: '100%' }} + backgroundColor={this.props.backgroundColor} + labelColor={this.props.labelColor} + onTouchTap={this.onClickAsync.bind(this)} + disabled={this.props.isDisabled || this.state.buttonState !== ButtonState.READY} + /> + ); + } + public async onClickAsync() { + this.setState({ + buttonState: ButtonState.LOADING, + }); + const didSucceed = await this.props.onClickAsyncFn(); + if (this._didUnmount) { + return; // noop since unmount called before async callback returned. + } + if (didSucceed) { + this.setState({ + buttonState: ButtonState.COMPLETE, + }); + this._buttonTimeoutId = window.setTimeout(() => { + this.setState({ + buttonState: ButtonState.READY, + }); + }, COMPLETE_STATE_SHOW_LENGTH_MS); + } else { + this.setState({ + buttonState: ButtonState.READY, + }); + } + } } diff --git a/packages/website/ts/components/ui/loading.tsx b/packages/website/ts/components/ui/loading.tsx index e9bfe3316..aa319e9e9 100644 --- a/packages/website/ts/components/ui/loading.tsx +++ b/packages/website/ts/components/ui/loading.tsx @@ -10,30 +10,30 @@ interface LoadingProps {} interface LoadingState {} export class Loading extends React.Component<LoadingProps, LoadingState> { - public render() { - return ( - <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}> - <Paper className="mx-auto" style={{ maxWidth: 400 }}> - {utils.isUserOnMobile() ? ( - <img className="p1" src="/gifs/0xAnimation.gif" width="96%" /> - ) : ( - <div style={{ pointerEvents: 'none' }}> - <Video - autoPlay={true} - loop={true} - muted={true} - controls={[]} - poster="/images/loading_poster.png" - > - <source src="/videos/0xAnimation.mp4" type="video/mp4" /> - </Video> - </div> - )} - <div className="center pt2" style={{ paddingBottom: 11 }}> - Connecting to the blockchain... - </div> - </Paper> - </div> - ); - } + public render() { + return ( + <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}> + <Paper className="mx-auto" style={{ maxWidth: 400 }}> + {utils.isUserOnMobile() ? ( + <img className="p1" src="/gifs/0xAnimation.gif" width="96%" /> + ) : ( + <div style={{ pointerEvents: 'none' }}> + <Video + autoPlay={true} + loop={true} + muted={true} + controls={[]} + poster="/images/loading_poster.png" + > + <source src="/videos/0xAnimation.mp4" type="video/mp4" /> + </Video> + </div> + )} + <div className="center pt2" style={{ paddingBottom: 11 }}> + Connecting to the blockchain... + </div> + </Paper> + </div> + ); + } } diff --git a/packages/website/ts/components/ui/menu_item.tsx b/packages/website/ts/components/ui/menu_item.tsx index 956b5eae8..3482f436c 100644 --- a/packages/website/ts/components/ui/menu_item.tsx +++ b/packages/website/ts/components/ui/menu_item.tsx @@ -3,49 +3,49 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; interface MenuItemProps { - to: string; - style?: React.CSSProperties; - onClick?: () => void; - className?: string; + to: string; + style?: React.CSSProperties; + onClick?: () => void; + className?: string; } interface MenuItemState { - isHovering: boolean; + isHovering: boolean; } export class MenuItem extends React.Component<MenuItemProps, MenuItemState> { - public static defaultProps: Partial<MenuItemProps> = { - onClick: _.noop, - className: '', - }; - public constructor(props: MenuItemProps) { - super(props); - this.state = { - isHovering: false, - }; - } - public render() { - const menuItemStyles = { - cursor: 'pointer', - opacity: this.state.isHovering ? 0.5 : 1, - }; - return ( - <Link to={this.props.to} style={{ textDecoration: 'none', ...this.props.style }}> - <div - onClick={this.props.onClick.bind(this)} - className={`mx-auto ${this.props.className}`} - style={menuItemStyles} - onMouseEnter={this._onToggleHover.bind(this, true)} - onMouseLeave={this._onToggleHover.bind(this, false)} - > - {this.props.children} - </div> - </Link> - ); - } - private _onToggleHover(isHovering: boolean) { - this.setState({ - isHovering, - }); - } + public static defaultProps: Partial<MenuItemProps> = { + onClick: _.noop, + className: '', + }; + public constructor(props: MenuItemProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const menuItemStyles = { + cursor: 'pointer', + opacity: this.state.isHovering ? 0.5 : 1, + }; + return ( + <Link to={this.props.to} style={{ textDecoration: 'none', ...this.props.style }}> + <div + onClick={this.props.onClick.bind(this)} + className={`mx-auto ${this.props.className}`} + style={menuItemStyles} + onMouseEnter={this._onToggleHover.bind(this, true)} + onMouseLeave={this._onToggleHover.bind(this, false)} + > + {this.props.children} + </div> + </Link> + ); + } + private _onToggleHover(isHovering: boolean) { + this.setState({ + isHovering, + }); + } } diff --git a/packages/website/ts/components/ui/party.tsx b/packages/website/ts/components/ui/party.tsx index ef3c7b425..ca2577b61 100644 --- a/packages/website/ts/components/ui/party.tsx +++ b/packages/website/ts/components/ui/party.tsx @@ -11,129 +11,129 @@ const IMAGE_DIMENSION = 100; const IDENTICON_DIAMETER = 95; interface PartyProps { - label: string; - address: string; - networkId: number; - alternativeImage?: string; - identiconDiameter?: number; - identiconStyle?: React.CSSProperties; - isInTokenRegistry?: boolean; - hasUniqueNameAndSymbol?: boolean; + label: string; + address: string; + networkId: number; + alternativeImage?: string; + identiconDiameter?: number; + identiconStyle?: React.CSSProperties; + isInTokenRegistry?: boolean; + hasUniqueNameAndSymbol?: boolean; } interface PartyState {} export class Party extends React.Component<PartyProps, PartyState> { - public static defaultProps: Partial<PartyProps> = { - identiconStyle: {}, - identiconDiameter: IDENTICON_DIAMETER, - }; - public render() { - const label = this.props.label; - const address = this.props.address; - const identiconDiameter = this.props.identiconDiameter; - const emptyIdenticonStyles = { - width: identiconDiameter, - height: identiconDiameter, - backgroundColor: 'lightgray', - marginTop: 13, - marginBottom: 10, - }; - const tokenImageStyle = { - width: IMAGE_DIMENSION, - height: IMAGE_DIMENSION, - }; - const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( - this.props.address, - this.props.networkId, - EtherscanLinkSuffixes.Address, - ); - const isRegistered = this.props.isInTokenRegistry; - const registeredTooltipId = `${this.props.address}-${isRegistered}-registeredTooltip`; - const uniqueNameAndSymbolTooltipId = `${this.props.address}-${isRegistered}-uniqueTooltip`; - return ( - <div style={{ overflow: 'hidden' }}> - <div className="pb1 center">{label}</div> - {_.isEmpty(address) ? ( - <div className="circle mx-auto" style={emptyIdenticonStyles} /> - ) : ( - <a href={etherscanLinkIfExists} target="_blank"> - {isRegistered && !_.isUndefined(this.props.alternativeImage) ? ( - <img style={tokenImageStyle} src={this.props.alternativeImage} /> - ) : ( - <div className="mx-auto" style={{ height: identiconDiameter, width: identiconDiameter }}> - <Identicon - address={this.props.address} - diameter={identiconDiameter} - style={this.props.identiconStyle} - /> - </div> - )} - </a> - )} - <div className="mx-auto center pt1"> - <div style={{ height: 25 }}> - <EthereumAddress address={address} networkId={this.props.networkId} /> - </div> - {!_.isUndefined(this.props.isInTokenRegistry) && ( - <div> - <div - data-tip={true} - data-for={registeredTooltipId} - className="mx-auto" - style={{ fontSize: 13, width: 127 }} - > - <span - style={{ - color: isRegistered ? colors.brightGreen : colors.red500, - }} - > - <i - className={`zmdi ${isRegistered ? 'zmdi-check-circle' : 'zmdi-alert-triangle'}`} - /> - </span>{' '} - <span>{isRegistered ? 'Registered' : 'Unregistered'} token</span> - <ReactTooltip id={registeredTooltipId}> - {isRegistered ? ( - <div> - This token address was found in the token registry<br /> - smart contract and is therefore believed to be a<br /> - legitimate token. - </div> - ) : ( - <div> - This token is not included in the token registry<br /> - smart contract. We cannot guarantee the legitimacy<br /> - of this token. Make sure to verify its address on Etherscan. - </div> - )} - </ReactTooltip> - </div> - </div> - )} - {!_.isUndefined(this.props.hasUniqueNameAndSymbol) && - !this.props.hasUniqueNameAndSymbol && ( - <div> - <div - data-tip={true} - data-for={uniqueNameAndSymbolTooltipId} - className="mx-auto" - style={{ fontSize: 13, width: 127 }} - > - <span style={{ color: colors.red500 }}> - <i className="zmdi zmdi-alert-octagon" /> - </span>{' '} - <span>Suspicious token</span> - <ReactTooltip id={uniqueNameAndSymbolTooltipId}> - This token shares it's name, symbol or both with<br /> - a token in the 0x Token Registry but it has a different<br /> - smart contract address. This is most likely a scam token! - </ReactTooltip> - </div> - </div> - )} - </div> - </div> - ); - } + public static defaultProps: Partial<PartyProps> = { + identiconStyle: {}, + identiconDiameter: IDENTICON_DIAMETER, + }; + public render() { + const label = this.props.label; + const address = this.props.address; + const identiconDiameter = this.props.identiconDiameter; + const emptyIdenticonStyles = { + width: identiconDiameter, + height: identiconDiameter, + backgroundColor: 'lightgray', + marginTop: 13, + marginBottom: 10, + }; + const tokenImageStyle = { + width: IMAGE_DIMENSION, + height: IMAGE_DIMENSION, + }; + const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( + this.props.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const isRegistered = this.props.isInTokenRegistry; + const registeredTooltipId = `${this.props.address}-${isRegistered}-registeredTooltip`; + const uniqueNameAndSymbolTooltipId = `${this.props.address}-${isRegistered}-uniqueTooltip`; + return ( + <div style={{ overflow: 'hidden' }}> + <div className="pb1 center">{label}</div> + {_.isEmpty(address) ? ( + <div className="circle mx-auto" style={emptyIdenticonStyles} /> + ) : ( + <a href={etherscanLinkIfExists} target="_blank"> + {isRegistered && !_.isUndefined(this.props.alternativeImage) ? ( + <img style={tokenImageStyle} src={this.props.alternativeImage} /> + ) : ( + <div className="mx-auto" style={{ height: identiconDiameter, width: identiconDiameter }}> + <Identicon + address={this.props.address} + diameter={identiconDiameter} + style={this.props.identiconStyle} + /> + </div> + )} + </a> + )} + <div className="mx-auto center pt1"> + <div style={{ height: 25 }}> + <EthereumAddress address={address} networkId={this.props.networkId} /> + </div> + {!_.isUndefined(this.props.isInTokenRegistry) && ( + <div> + <div + data-tip={true} + data-for={registeredTooltipId} + className="mx-auto" + style={{ fontSize: 13, width: 127 }} + > + <span + style={{ + color: isRegistered ? colors.brightGreen : colors.red500, + }} + > + <i + className={`zmdi ${isRegistered ? 'zmdi-check-circle' : 'zmdi-alert-triangle'}`} + /> + </span>{' '} + <span>{isRegistered ? 'Registered' : 'Unregistered'} token</span> + <ReactTooltip id={registeredTooltipId}> + {isRegistered ? ( + <div> + This token address was found in the token registry<br /> + smart contract and is therefore believed to be a<br /> + legitimate token. + </div> + ) : ( + <div> + This token is not included in the token registry<br /> + smart contract. We cannot guarantee the legitimacy<br /> + of this token. Make sure to verify its address on Etherscan. + </div> + )} + </ReactTooltip> + </div> + </div> + )} + {!_.isUndefined(this.props.hasUniqueNameAndSymbol) && + !this.props.hasUniqueNameAndSymbol && ( + <div> + <div + data-tip={true} + data-for={uniqueNameAndSymbolTooltipId} + className="mx-auto" + style={{ fontSize: 13, width: 127 }} + > + <span style={{ color: colors.red500 }}> + <i className="zmdi zmdi-alert-octagon" /> + </span>{' '} + <span>Suspicious token</span> + <ReactTooltip id={uniqueNameAndSymbolTooltipId}> + This token shares it's name, symbol or both with<br /> + a token in the 0x Token Registry but it has a different<br /> + smart contract address. This is most likely a scam token! + </ReactTooltip> + </div> + </div> + )} + </div> + </div> + ); + } } diff --git a/packages/website/ts/components/ui/required_label.tsx b/packages/website/ts/components/ui/required_label.tsx index 638683427..a5e7a22ce 100644 --- a/packages/website/ts/components/ui/required_label.tsx +++ b/packages/website/ts/components/ui/required_label.tsx @@ -2,14 +2,14 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; export interface RequiredLabelProps { - label: string | React.ReactNode; + label: string | React.ReactNode; } export const RequiredLabel = (props: RequiredLabelProps) => { - return ( - <span> - {props.label} - <span style={{ color: colors.red600 }}>*</span> - </span> - ); + return ( + <span> + {props.label} + <span style={{ color: colors.red600 }}>*</span> + </span> + ); }; diff --git a/packages/website/ts/components/ui/simple_loading.tsx b/packages/website/ts/components/ui/simple_loading.tsx index 9f1fd5a13..81744196d 100644 --- a/packages/website/ts/components/ui/simple_loading.tsx +++ b/packages/website/ts/components/ui/simple_loading.tsx @@ -2,16 +2,16 @@ import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; export interface SimpleLoadingProps { - message: string; + message: string; } export const SimpleLoading = (props: SimpleLoadingProps) => { - return ( - <div className="mx-auto pt3" style={{ maxWidth: 400, height: 409 }}> - <div className="relative" style={{ top: '50%', transform: 'translateY(-50%)', height: 95 }}> - <CircularProgress /> - <div className="pt3 pb3">{props.message}</div> - </div> - </div> - ); + return ( + <div className="mx-auto pt3" style={{ maxWidth: 400, height: 409 }}> + <div className="relative" style={{ top: '50%', transform: 'translateY(-50%)', height: 95 }}> + <CircularProgress /> + <div className="pt3 pb3">{props.message}</div> + </div> + </div> + ); }; diff --git a/packages/website/ts/components/ui/swap_icon.tsx b/packages/website/ts/components/ui/swap_icon.tsx index 99e3450de..c41592287 100644 --- a/packages/website/ts/components/ui/swap_icon.tsx +++ b/packages/website/ts/components/ui/swap_icon.tsx @@ -3,40 +3,40 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; interface SwapIconProps { - swapTokensFn: () => void; + swapTokensFn: () => void; } interface SwapIconState { - isHovering: boolean; + isHovering: boolean; } export class SwapIcon extends React.Component<SwapIconProps, SwapIconState> { - public constructor(props: SwapIconProps) { - super(props); - this.state = { - isHovering: false, - }; - } - public render() { - const swapStyles = { - color: this.state.isHovering ? colors.amber600 : colors.amber800, - fontSize: 50, - }; - return ( - <div - className="mx-auto pt4" - style={{ cursor: 'pointer', height: 50, width: 37.5 }} - onClick={this.props.swapTokensFn} - onMouseEnter={this._onToggleHover.bind(this, true)} - onMouseLeave={this._onToggleHover.bind(this, false)} - > - <i style={swapStyles} className="zmdi zmdi-swap" /> - </div> - ); - } - private _onToggleHover(isHovering: boolean) { - this.setState({ - isHovering, - }); - } + public constructor(props: SwapIconProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const swapStyles = { + color: this.state.isHovering ? colors.amber600 : colors.amber800, + fontSize: 50, + }; + return ( + <div + className="mx-auto pt4" + style={{ cursor: 'pointer', height: 50, width: 37.5 }} + onClick={this.props.swapTokensFn} + onMouseEnter={this._onToggleHover.bind(this, true)} + onMouseLeave={this._onToggleHover.bind(this, false)} + > + <i style={swapStyles} className="zmdi zmdi-swap" /> + </div> + ); + } + private _onToggleHover(isHovering: boolean) { + this.setState({ + isHovering, + }); + } } diff --git a/packages/website/ts/components/ui/token_icon.tsx b/packages/website/ts/components/ui/token_icon.tsx index a729821ce..ff57a96de 100644 --- a/packages/website/ts/components/ui/token_icon.tsx +++ b/packages/website/ts/components/ui/token_icon.tsx @@ -4,24 +4,24 @@ import { Identicon } from 'ts/components/ui/identicon'; import { Token } from 'ts/types'; interface TokenIconProps { - token: Token; - diameter: number; + token: Token; + diameter: number; } interface TokenIconState {} export class TokenIcon extends React.Component<TokenIconProps, TokenIconState> { - public render() { - const token = this.props.token; - const diameter = this.props.diameter; - return ( - <div> - {token.isRegistered && !_.isUndefined(token.iconUrl) ? ( - <img style={{ width: diameter, height: diameter }} src={token.iconUrl} /> - ) : ( - <Identicon address={token.address} diameter={diameter} /> - )} - </div> - ); - } + public render() { + const token = this.props.token; + const diameter = this.props.diameter; + return ( + <div> + {token.isRegistered && !_.isUndefined(token.iconUrl) ? ( + <img style={{ width: diameter, height: diameter }} src={token.iconUrl} /> + ) : ( + <Identicon address={token.address} diameter={diameter} /> + )} + </div> + ); + } } diff --git a/packages/website/ts/components/visual_order.tsx b/packages/website/ts/components/visual_order.tsx index a9779ac62..092954086 100644 --- a/packages/website/ts/components/visual_order.tsx +++ b/packages/website/ts/components/visual_order.tsx @@ -8,69 +8,69 @@ import { utils } from 'ts/utils/utils'; const PRECISION = 5; interface VisualOrderProps { - orderTakerAddress: string; - orderMakerAddress: string; - makerAssetToken: AssetToken; - takerAssetToken: AssetToken; - makerToken: Token; - takerToken: Token; - networkId: number; - tokenByAddress: TokenByAddress; - isMakerTokenAddressInRegistry: boolean; - isTakerTokenAddressInRegistry: boolean; + orderTakerAddress: string; + orderMakerAddress: string; + makerAssetToken: AssetToken; + takerAssetToken: AssetToken; + makerToken: Token; + takerToken: Token; + networkId: number; + tokenByAddress: TokenByAddress; + isMakerTokenAddressInRegistry: boolean; + isTakerTokenAddressInRegistry: boolean; } interface VisualOrderState {} export class VisualOrder extends React.Component<VisualOrderProps, VisualOrderState> { - public render() { - const allTokens = _.values(this.props.tokenByAddress); - const makerImage = this.props.makerToken.iconUrl; - const takerImage = this.props.takerToken.iconUrl; - return ( - <div> - <div className="clearfix"> - <div className="col col-5 center"> - <Party - label="Send" - address={this.props.takerToken.address} - alternativeImage={takerImage} - networkId={this.props.networkId} - isInTokenRegistry={this.props.isTakerTokenAddressInRegistry} - hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.takerToken)} - /> - </div> - <div className="col col-2 center pt1"> - <div className="pb1"> - {this._renderAmount(this.props.takerAssetToken, this.props.takerToken)} - </div> - <div className="lg-p2 md-p2 sm-p1"> - <img src="/images/trade_arrows.png" style={{ width: 47 }} /> - </div> - <div className="pt1"> - {this._renderAmount(this.props.makerAssetToken, this.props.makerToken)} - </div> - </div> - <div className="col col-5 center"> - <Party - label="Receive" - address={this.props.makerToken.address} - alternativeImage={makerImage} - networkId={this.props.networkId} - isInTokenRegistry={this.props.isMakerTokenAddressInRegistry} - hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.makerToken)} - /> - </div> - </div> - </div> - ); - } - private _renderAmount(assetToken: AssetToken, token: Token) { - const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals); - return ( - <div style={{ fontSize: 13 }}> - {unitAmount.toNumber().toFixed(PRECISION)} {token.symbol} - </div> - ); - } + public render() { + const allTokens = _.values(this.props.tokenByAddress); + const makerImage = this.props.makerToken.iconUrl; + const takerImage = this.props.takerToken.iconUrl; + return ( + <div> + <div className="clearfix"> + <div className="col col-5 center"> + <Party + label="Send" + address={this.props.takerToken.address} + alternativeImage={takerImage} + networkId={this.props.networkId} + isInTokenRegistry={this.props.isTakerTokenAddressInRegistry} + hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.takerToken)} + /> + </div> + <div className="col col-2 center pt1"> + <div className="pb1"> + {this._renderAmount(this.props.takerAssetToken, this.props.takerToken)} + </div> + <div className="lg-p2 md-p2 sm-p1"> + <img src="/images/trade_arrows.png" style={{ width: 47 }} /> + </div> + <div className="pt1"> + {this._renderAmount(this.props.makerAssetToken, this.props.makerToken)} + </div> + </div> + <div className="col col-5 center"> + <Party + label="Receive" + address={this.props.makerToken.address} + alternativeImage={makerImage} + networkId={this.props.networkId} + isInTokenRegistry={this.props.isMakerTokenAddressInRegistry} + hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.makerToken)} + /> + </div> + </div> + </div> + ); + } + private _renderAmount(assetToken: AssetToken, token: Token) { + const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals); + return ( + <div style={{ fontSize: 13 }}> + {unitAmount.toNumber().toFixed(PRECISION)} {token.symbol} + </div> + ); + } } diff --git a/packages/website/ts/containers/connect_documentation.tsx b/packages/website/ts/containers/connect_documentation.tsx index 2f5326d57..3e02a7d05 100644 --- a/packages/website/ts/containers/connect_documentation.tsx +++ b/packages/website/ts/containers/connect_documentation.tsx @@ -16,81 +16,81 @@ const InstallationMarkdown = require('md/docs/connect/installation'); /* tslint:enable:no-var-requires */ const connectDocSections = { - introduction: 'introduction', - installation: 'installation', - httpClient: 'httpClient', - webSocketOrderbookChannel: 'webSocketOrderbookChannel', - types: constants.TYPES_SECTION_NAME, + introduction: 'introduction', + installation: 'installation', + httpClient: 'httpClient', + webSocketOrderbookChannel: 'webSocketOrderbookChannel', + types: constants.TYPES_SECTION_NAME, }; const docsInfoConfig: DocsInfoConfig = { - displayName: '0x Connect', - subPackageName: 'connect', - packageUrl: 'https://github.com/0xProject/0x.js', - websitePath: WebsitePaths.Connect, - docsJsonRoot: 'https://s3.amazonaws.com/connect-docs-jsons', - menu: { - introduction: [connectDocSections.introduction], - install: [connectDocSections.installation], - httpClient: [connectDocSections.httpClient], - webSocketOrderbookChannel: [connectDocSections.webSocketOrderbookChannel], - types: [connectDocSections.types], - }, - sectionNameToMarkdown: { - [connectDocSections.introduction]: IntroMarkdown, - [connectDocSections.installation]: InstallationMarkdown, - }, - // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is - // currently no way to extract the re-exported types from index.ts via TypeDoc :( - publicTypes: [ - 'Client', - 'FeesRequest', - 'FeesResponse', - 'OrderbookChannel', - 'OrderbookChannelHandler', - 'OrderbookChannelSubscriptionOpts', - 'OrderbookRequest', - 'OrderbookResponse', - 'OrdersRequest', - 'TokenPairsItem', - 'TokenPairsRequest', - 'TokenTradeInfo', - 'Order', - 'SignedOrder', - 'ECSignature', - ], - sectionNameToModulePath: { - [connectDocSections.httpClient]: ['"src/http_client"'], - [connectDocSections.webSocketOrderbookChannel]: ['"src/ws_orderbook_channel"'], - [connectDocSections.types]: ['"src/types"'], - }, - menuSubsectionToVersionWhenIntroduced: {}, - sections: connectDocSections, - visibleConstructors: [connectDocSections.httpClient, connectDocSections.webSocketOrderbookChannel], - convertToDocAgnosticFormatFn: typeDocUtils.convertToDocAgnosticFormat.bind(typeDocUtils), + displayName: '0x Connect', + subPackageName: 'connect', + packageUrl: 'https://github.com/0xProject/0x.js', + websitePath: WebsitePaths.Connect, + docsJsonRoot: 'https://s3.amazonaws.com/connect-docs-jsons', + menu: { + introduction: [connectDocSections.introduction], + install: [connectDocSections.installation], + httpClient: [connectDocSections.httpClient], + webSocketOrderbookChannel: [connectDocSections.webSocketOrderbookChannel], + types: [connectDocSections.types], + }, + sectionNameToMarkdown: { + [connectDocSections.introduction]: IntroMarkdown, + [connectDocSections.installation]: InstallationMarkdown, + }, + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + publicTypes: [ + 'Client', + 'FeesRequest', + 'FeesResponse', + 'OrderbookChannel', + 'OrderbookChannelHandler', + 'OrderbookChannelSubscriptionOpts', + 'OrderbookRequest', + 'OrderbookResponse', + 'OrdersRequest', + 'TokenPairsItem', + 'TokenPairsRequest', + 'TokenTradeInfo', + 'Order', + 'SignedOrder', + 'ECSignature', + ], + sectionNameToModulePath: { + [connectDocSections.httpClient]: ['"src/http_client"'], + [connectDocSections.webSocketOrderbookChannel]: ['"src/ws_orderbook_channel"'], + [connectDocSections.types]: ['"src/types"'], + }, + menuSubsectionToVersionWhenIntroduced: {}, + sections: connectDocSections, + visibleConstructors: [connectDocSections.httpClient, connectDocSections.webSocketOrderbookChannel], + convertToDocAgnosticFormatFn: typeDocUtils.convertToDocAgnosticFormat.bind(typeDocUtils), }; const docsInfo = new DocsInfo(docsInfoConfig); interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; } interface ConnectedDispatch { - dispatcher: Dispatcher; + dispatcher: Dispatcher; } const mapStateToProps = (state: State, ownProps: DocumentationAllProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - docsInfo, + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + docsInfo, }); const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), + dispatcher: new Dispatcher(dispatch), }); export const Documentation: React.ComponentClass<DocumentationAllProps> = connect(mapStateToProps, mapDispatchToProps)( - DocumentationComponent, + DocumentationComponent, ); diff --git a/packages/website/ts/containers/generate_order_form.tsx b/packages/website/ts/containers/generate_order_form.tsx index c2b781557..3fd31087f 100644 --- a/packages/website/ts/containers/generate_order_form.tsx +++ b/packages/website/ts/containers/generate_order_form.tsx @@ -7,48 +7,48 @@ import { GenerateOrderForm as GenerateOrderFormComponent } from 'ts/components/g import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; import { - BlockchainErrs, - HashData, - SideToAssetToken, - SignatureData, - TokenByAddress, - TokenStateByAddress, + BlockchainErrs, + HashData, + SideToAssetToken, + SignatureData, + TokenByAddress, + TokenStateByAddress, } from 'ts/types'; interface GenerateOrderFormProps { - blockchain: Blockchain; - hashData: HashData; - dispatcher: Dispatcher; + blockchain: Blockchain; + hashData: HashData; + dispatcher: Dispatcher; } interface ConnectedState { - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - orderExpiryTimestamp: BigNumber; - orderSignatureData: SignatureData; - userAddress: string; - orderTakerAddress: string; - orderSalt: BigNumber; - networkId: number; - sideToAssetToken: SideToAssetToken; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + orderExpiryTimestamp: BigNumber; + orderSignatureData: SignatureData; + userAddress: string; + orderTakerAddress: string; + orderSalt: BigNumber; + networkId: number; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; } const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): ConnectedState => ({ - blockchainErr: state.blockchainErr, - blockchainIsLoaded: state.blockchainIsLoaded, - orderExpiryTimestamp: state.orderExpiryTimestamp, - orderSignatureData: state.orderSignatureData, - orderTakerAddress: state.orderTakerAddress, - orderSalt: state.orderSalt, - networkId: state.networkId, - sideToAssetToken: state.sideToAssetToken, - tokenByAddress: state.tokenByAddress, - tokenStateByAddress: state.tokenStateByAddress, - userAddress: state.userAddress, + blockchainErr: state.blockchainErr, + blockchainIsLoaded: state.blockchainIsLoaded, + orderExpiryTimestamp: state.orderExpiryTimestamp, + orderSignatureData: state.orderSignatureData, + orderTakerAddress: state.orderTakerAddress, + orderSalt: state.orderSalt, + networkId: state.networkId, + sideToAssetToken: state.sideToAssetToken, + tokenByAddress: state.tokenByAddress, + tokenStateByAddress: state.tokenStateByAddress, + userAddress: state.userAddress, }); export const GenerateOrderForm: React.ComponentClass<GenerateOrderFormProps> = connect(mapStateToProps)( - GenerateOrderFormComponent, + GenerateOrderFormComponent, ); diff --git a/packages/website/ts/containers/portal.tsx b/packages/website/ts/containers/portal.tsx index bc7aa213c..f0247935b 100644 --- a/packages/website/ts/containers/portal.tsx +++ b/packages/website/ts/containers/portal.tsx @@ -10,72 +10,72 @@ import { BlockchainErrs, HashData, Order, ScreenWidths, Side, TokenByAddress, To import { constants } from 'ts/utils/constants'; interface ConnectedState { - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - hashData: HashData; - networkId: number; - nodeVersion: string; - orderFillAmount: BigNumber; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - userEtherBalance: BigNumber; - screenWidth: ScreenWidths; - shouldBlockchainErrDialogBeOpen: boolean; - userAddress: string; - userSuppliedOrderCache: Order; - flashMessage?: string | React.ReactNode; + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + hashData: HashData; + networkId: number; + nodeVersion: string; + orderFillAmount: BigNumber; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userEtherBalance: BigNumber; + screenWidth: ScreenWidths; + shouldBlockchainErrDialogBeOpen: boolean; + userAddress: string; + userSuppliedOrderCache: Order; + flashMessage?: string | React.ReactNode; } interface ConnectedDispatch { - dispatcher: Dispatcher; + dispatcher: Dispatcher; } const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): ConnectedState => { - const receiveAssetToken = state.sideToAssetToken[Side.Receive]; - const depositAssetToken = state.sideToAssetToken[Side.Deposit]; - const receiveAddress = !_.isUndefined(receiveAssetToken.address) - ? receiveAssetToken.address - : constants.NULL_ADDRESS; - const depositAddress = !_.isUndefined(depositAssetToken.address) - ? depositAssetToken.address - : constants.NULL_ADDRESS; - const receiveAmount = !_.isUndefined(receiveAssetToken.amount) ? receiveAssetToken.amount : new BigNumber(0); - const depositAmount = !_.isUndefined(depositAssetToken.amount) ? depositAssetToken.amount : new BigNumber(0); - const hashData = { - depositAmount, - depositTokenContractAddr: depositAddress, - feeRecipientAddress: constants.NULL_ADDRESS, - makerFee: constants.MAKER_FEE, - orderExpiryTimestamp: state.orderExpiryTimestamp, - orderMakerAddress: state.userAddress, - orderTakerAddress: state.orderTakerAddress !== '' ? state.orderTakerAddress : constants.NULL_ADDRESS, - receiveAmount, - receiveTokenContractAddr: receiveAddress, - takerFee: constants.TAKER_FEE, - orderSalt: state.orderSalt, - }; - return { - blockchainErr: state.blockchainErr, - blockchainIsLoaded: state.blockchainIsLoaded, - networkId: state.networkId, - nodeVersion: state.nodeVersion, - orderFillAmount: state.orderFillAmount, - hashData, - screenWidth: state.screenWidth, - shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen, - tokenByAddress: state.tokenByAddress, - tokenStateByAddress: state.tokenStateByAddress, - userAddress: state.userAddress, - userEtherBalance: state.userEtherBalance, - userSuppliedOrderCache: state.userSuppliedOrderCache, - flashMessage: state.flashMessage, - }; + const receiveAssetToken = state.sideToAssetToken[Side.Receive]; + const depositAssetToken = state.sideToAssetToken[Side.Deposit]; + const receiveAddress = !_.isUndefined(receiveAssetToken.address) + ? receiveAssetToken.address + : constants.NULL_ADDRESS; + const depositAddress = !_.isUndefined(depositAssetToken.address) + ? depositAssetToken.address + : constants.NULL_ADDRESS; + const receiveAmount = !_.isUndefined(receiveAssetToken.amount) ? receiveAssetToken.amount : new BigNumber(0); + const depositAmount = !_.isUndefined(depositAssetToken.amount) ? depositAssetToken.amount : new BigNumber(0); + const hashData = { + depositAmount, + depositTokenContractAddr: depositAddress, + feeRecipientAddress: constants.NULL_ADDRESS, + makerFee: constants.MAKER_FEE, + orderExpiryTimestamp: state.orderExpiryTimestamp, + orderMakerAddress: state.userAddress, + orderTakerAddress: state.orderTakerAddress !== '' ? state.orderTakerAddress : constants.NULL_ADDRESS, + receiveAmount, + receiveTokenContractAddr: receiveAddress, + takerFee: constants.TAKER_FEE, + orderSalt: state.orderSalt, + }; + return { + blockchainErr: state.blockchainErr, + blockchainIsLoaded: state.blockchainIsLoaded, + networkId: state.networkId, + nodeVersion: state.nodeVersion, + orderFillAmount: state.orderFillAmount, + hashData, + screenWidth: state.screenWidth, + shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen, + tokenByAddress: state.tokenByAddress, + tokenStateByAddress: state.tokenStateByAddress, + userAddress: state.userAddress, + userEtherBalance: state.userEtherBalance, + userSuppliedOrderCache: state.userSuppliedOrderCache, + flashMessage: state.flashMessage, + }; }; const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), + dispatcher: new Dispatcher(dispatch), }); export const Portal: React.ComponentClass<PortalComponentAllProps> = connect(mapStateToProps, mapDispatchToProps)( - PortalComponent, + PortalComponent, ); diff --git a/packages/website/ts/containers/smart_contracts_documentation.tsx b/packages/website/ts/containers/smart_contracts_documentation.tsx index addfd1af9..8be33b546 100644 --- a/packages/website/ts/containers/smart_contracts_documentation.tsx +++ b/packages/website/ts/containers/smart_contracts_documentation.tsx @@ -14,49 +14,49 @@ const IntroMarkdown = require('md/docs/smart_contracts/introduction'); /* tslint:enable:no-var-requires */ const docsInfoConfig: DocsInfoConfig = { - displayName: '0x Smart Contracts', - packageUrl: 'https://github.com/0xProject/contracts', - websitePath: WebsitePaths.SmartContracts, - docsJsonRoot: 'https://s3.amazonaws.com/smart-contracts-docs-json', - menu: { - introduction: [Sections.Introduction], - contracts: [Sections.Exchange, Sections.TokenRegistry, Sections.ZRXToken, Sections.TokenTransferProxy], - }, - sectionNameToMarkdown: { - [Sections.Introduction]: IntroMarkdown, - }, - sections: { - Introduction: Sections.Introduction, - Exchange: Sections.Exchange, - TokenTransferProxy: Sections.TokenTransferProxy, - TokenRegistry: Sections.TokenRegistry, - ZRXToken: Sections.ZRXToken, - }, - visibleConstructors: [Sections.Exchange, Sections.TokenRegistry, Sections.ZRXToken, Sections.TokenTransferProxy], - convertToDocAgnosticFormatFn: doxityUtils.convertToDocAgnosticFormat.bind(doxityUtils), + displayName: '0x Smart Contracts', + packageUrl: 'https://github.com/0xProject/contracts', + websitePath: WebsitePaths.SmartContracts, + docsJsonRoot: 'https://s3.amazonaws.com/smart-contracts-docs-json', + menu: { + introduction: [Sections.Introduction], + contracts: [Sections.Exchange, Sections.TokenRegistry, Sections.ZRXToken, Sections.TokenTransferProxy], + }, + sectionNameToMarkdown: { + [Sections.Introduction]: IntroMarkdown, + }, + sections: { + Introduction: Sections.Introduction, + Exchange: Sections.Exchange, + TokenTransferProxy: Sections.TokenTransferProxy, + TokenRegistry: Sections.TokenRegistry, + ZRXToken: Sections.ZRXToken, + }, + visibleConstructors: [Sections.Exchange, Sections.TokenRegistry, Sections.ZRXToken, Sections.TokenTransferProxy], + convertToDocAgnosticFormatFn: doxityUtils.convertToDocAgnosticFormat.bind(doxityUtils), }; const docsInfo = new DocsInfo(docsInfoConfig); interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; + docsVersion: string; + availableDocVersions: string[]; } interface ConnectedDispatch { - dispatcher: Dispatcher; - docsInfo: DocsInfo; + dispatcher: Dispatcher; + docsInfo: DocsInfo; } const mapStateToProps = (state: State, ownProps: DocumentationAllProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, }); const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), - docsInfo, + dispatcher: new Dispatcher(dispatch), + docsInfo, }); export const Documentation: React.ComponentClass<DocumentationAllProps> = connect(mapStateToProps, mapDispatchToProps)( - DocumentationComponent, + DocumentationComponent, ); diff --git a/packages/website/ts/containers/zero_ex_js_documentation.tsx b/packages/website/ts/containers/zero_ex_js_documentation.tsx index d416cbc12..8ae6a7b73 100644 --- a/packages/website/ts/containers/zero_ex_js_documentation.tsx +++ b/packages/website/ts/containers/zero_ex_js_documentation.tsx @@ -19,152 +19,152 @@ const versioningMarkdown = require('md/docs/0xjs/versioning'); /* tslint:enable:no-var-requires */ const zeroExJsDocSections = { - introduction: 'introduction', - installation: 'installation', - testrpc: 'testrpc', - async: 'async', - errors: 'errors', - versioning: 'versioning', - zeroEx: 'zeroEx', - exchange: 'exchange', - token: 'token', - tokenRegistry: 'tokenRegistry', - etherToken: 'etherToken', - proxy: 'proxy', - orderWatcher: 'orderWatcher', - types: constants.TYPES_SECTION_NAME, + introduction: 'introduction', + installation: 'installation', + testrpc: 'testrpc', + async: 'async', + errors: 'errors', + versioning: 'versioning', + zeroEx: 'zeroEx', + exchange: 'exchange', + token: 'token', + tokenRegistry: 'tokenRegistry', + etherToken: 'etherToken', + proxy: 'proxy', + orderWatcher: 'orderWatcher', + types: constants.TYPES_SECTION_NAME, }; const docsInfoConfig: DocsInfoConfig = { - displayName: '0x.js', - packageUrl: 'https://github.com/0xProject/0x.js', - subPackageName: '0x.js', - websitePath: WebsitePaths.ZeroExJs, - docsJsonRoot: 'https://s3.amazonaws.com/0xjs-docs-jsons', - menu: { - introduction: [zeroExJsDocSections.introduction], - install: [zeroExJsDocSections.installation], - topics: [zeroExJsDocSections.async, zeroExJsDocSections.errors, zeroExJsDocSections.versioning], - zeroEx: [zeroExJsDocSections.zeroEx], - contracts: [ - zeroExJsDocSections.exchange, - zeroExJsDocSections.token, - zeroExJsDocSections.tokenRegistry, - zeroExJsDocSections.etherToken, - zeroExJsDocSections.proxy, - ], - orderWatcher: [zeroExJsDocSections.orderWatcher], - types: [zeroExJsDocSections.types], - }, - sectionNameToMarkdown: { - [zeroExJsDocSections.introduction]: IntroMarkdown, - [zeroExJsDocSections.installation]: InstallationMarkdown, - [zeroExJsDocSections.async]: AsyncMarkdown, - [zeroExJsDocSections.errors]: ErrorsMarkdown, - [zeroExJsDocSections.versioning]: versioningMarkdown, - }, - // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is - // currently no way to extract the re-exported types from index.ts via TypeDoc :( - publicTypes: [ - 'Order', - 'SignedOrder', - 'ECSignature', - 'ZeroExError', - 'EventCallback', - 'EventCallbackAsync', - 'EventCallbackSync', - 'ExchangeContractErrs', - 'ContractEvent', - 'Token', - 'ExchangeEvents', - 'IndexedFilterValues', - 'SubscriptionOpts', - 'BlockRange', - 'BlockParam', - 'OrderFillOrKillRequest', - 'OrderCancellationRequest', - 'OrderFillRequest', - 'ContractEventEmitter', - 'Web3Provider', - 'ContractEventArgs', - 'LogCancelArgs', - 'LogFillArgs', - 'LogErrorContractEventArgs', - 'LogFillContractEventArgs', - 'LogCancelContractEventArgs', - 'EtherTokenContractEventArgs', - 'WithdrawalContractEventArgs', - 'DepositContractEventArgs', - 'TokenEvents', - 'ExchangeContractEventArgs', - 'TransferContractEventArgs', - 'ApprovalContractEventArgs', - 'TokenContractEventArgs', - 'ZeroExConfig', - 'TransactionReceiptWithDecodedLogs', - 'LogWithDecodedArgs', - 'EtherTokenEvents', - 'BlockParamLiteral', - 'DecodedLogArgs', - 'MethodOpts', - 'ValidateOrderFillableOpts', - 'OrderTransactionOpts', - 'TransactionOpts', - 'ContractEventArg', - 'LogEvent', - 'LogEntry', - 'DecodedLogEvent', - 'EventWatcherCallback', - 'OnOrderStateChangeCallback', - 'OrderStateValid', - 'OrderStateInvalid', - 'OrderState', - 'FilterObject', - ], - sectionNameToModulePath: { - [zeroExJsDocSections.zeroEx]: ['"src/0x"'], - [zeroExJsDocSections.exchange]: ['"src/contract_wrappers/exchange_wrapper"'], - [zeroExJsDocSections.tokenRegistry]: ['"src/contract_wrappers/token_registry_wrapper"'], - [zeroExJsDocSections.token]: ['"src/contract_wrappers/token_wrapper"'], - [zeroExJsDocSections.etherToken]: ['"src/contract_wrappers/ether_token_wrapper"'], - [zeroExJsDocSections.proxy]: [ - '"src/contract_wrappers/proxy_wrapper"', - '"src/contract_wrappers/token_transfer_proxy_wrapper"', - ], - [zeroExJsDocSections.orderWatcher]: ['"src/order_watcher/order_state_watcher"'], - [zeroExJsDocSections.types]: ['"src/types"'], - }, - menuSubsectionToVersionWhenIntroduced: { - [zeroExJsDocSections.etherToken]: '0.7.1', - [zeroExJsDocSections.proxy]: '0.8.0', - [zeroExJsDocSections.orderWatcher]: '0.27.1', - }, - sections: zeroExJsDocSections, - visibleConstructors: [zeroExJsDocSections.zeroEx], - convertToDocAgnosticFormatFn: typeDocUtils.convertToDocAgnosticFormat.bind(typeDocUtils), + displayName: '0x.js', + packageUrl: 'https://github.com/0xProject/0x.js', + subPackageName: '0x.js', + websitePath: WebsitePaths.ZeroExJs, + docsJsonRoot: 'https://s3.amazonaws.com/0xjs-docs-jsons', + menu: { + introduction: [zeroExJsDocSections.introduction], + install: [zeroExJsDocSections.installation], + topics: [zeroExJsDocSections.async, zeroExJsDocSections.errors, zeroExJsDocSections.versioning], + zeroEx: [zeroExJsDocSections.zeroEx], + contracts: [ + zeroExJsDocSections.exchange, + zeroExJsDocSections.token, + zeroExJsDocSections.tokenRegistry, + zeroExJsDocSections.etherToken, + zeroExJsDocSections.proxy, + ], + orderWatcher: [zeroExJsDocSections.orderWatcher], + types: [zeroExJsDocSections.types], + }, + sectionNameToMarkdown: { + [zeroExJsDocSections.introduction]: IntroMarkdown, + [zeroExJsDocSections.installation]: InstallationMarkdown, + [zeroExJsDocSections.async]: AsyncMarkdown, + [zeroExJsDocSections.errors]: ErrorsMarkdown, + [zeroExJsDocSections.versioning]: versioningMarkdown, + }, + // Note: This needs to be kept in sync with the types exported in index.ts. Unfortunately there is + // currently no way to extract the re-exported types from index.ts via TypeDoc :( + publicTypes: [ + 'Order', + 'SignedOrder', + 'ECSignature', + 'ZeroExError', + 'EventCallback', + 'EventCallbackAsync', + 'EventCallbackSync', + 'ExchangeContractErrs', + 'ContractEvent', + 'Token', + 'ExchangeEvents', + 'IndexedFilterValues', + 'SubscriptionOpts', + 'BlockRange', + 'BlockParam', + 'OrderFillOrKillRequest', + 'OrderCancellationRequest', + 'OrderFillRequest', + 'ContractEventEmitter', + 'Web3Provider', + 'ContractEventArgs', + 'LogCancelArgs', + 'LogFillArgs', + 'LogErrorContractEventArgs', + 'LogFillContractEventArgs', + 'LogCancelContractEventArgs', + 'EtherTokenContractEventArgs', + 'WithdrawalContractEventArgs', + 'DepositContractEventArgs', + 'TokenEvents', + 'ExchangeContractEventArgs', + 'TransferContractEventArgs', + 'ApprovalContractEventArgs', + 'TokenContractEventArgs', + 'ZeroExConfig', + 'TransactionReceiptWithDecodedLogs', + 'LogWithDecodedArgs', + 'EtherTokenEvents', + 'BlockParamLiteral', + 'DecodedLogArgs', + 'MethodOpts', + 'ValidateOrderFillableOpts', + 'OrderTransactionOpts', + 'TransactionOpts', + 'ContractEventArg', + 'LogEvent', + 'LogEntry', + 'DecodedLogEvent', + 'EventWatcherCallback', + 'OnOrderStateChangeCallback', + 'OrderStateValid', + 'OrderStateInvalid', + 'OrderState', + 'FilterObject', + ], + sectionNameToModulePath: { + [zeroExJsDocSections.zeroEx]: ['"src/0x"'], + [zeroExJsDocSections.exchange]: ['"src/contract_wrappers/exchange_wrapper"'], + [zeroExJsDocSections.tokenRegistry]: ['"src/contract_wrappers/token_registry_wrapper"'], + [zeroExJsDocSections.token]: ['"src/contract_wrappers/token_wrapper"'], + [zeroExJsDocSections.etherToken]: ['"src/contract_wrappers/ether_token_wrapper"'], + [zeroExJsDocSections.proxy]: [ + '"src/contract_wrappers/proxy_wrapper"', + '"src/contract_wrappers/token_transfer_proxy_wrapper"', + ], + [zeroExJsDocSections.orderWatcher]: ['"src/order_watcher/order_state_watcher"'], + [zeroExJsDocSections.types]: ['"src/types"'], + }, + menuSubsectionToVersionWhenIntroduced: { + [zeroExJsDocSections.etherToken]: '0.7.1', + [zeroExJsDocSections.proxy]: '0.8.0', + [zeroExJsDocSections.orderWatcher]: '0.27.1', + }, + sections: zeroExJsDocSections, + visibleConstructors: [zeroExJsDocSections.zeroEx], + convertToDocAgnosticFormatFn: typeDocUtils.convertToDocAgnosticFormat.bind(typeDocUtils), }; const docsInfo = new DocsInfo(docsInfoConfig); interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; } interface ConnectedDispatch { - dispatcher: Dispatcher; + dispatcher: Dispatcher; } const mapStateToProps = (state: State, ownProps: DocumentationAllProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - docsInfo, + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + docsInfo, }); const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), + dispatcher: new Dispatcher(dispatch), }); export const Documentation: React.ComponentClass<DocumentationAllProps> = connect(mapStateToProps, mapDispatchToProps)( - DocumentationComponent, + DocumentationComponent, ); diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts index 736b23d9d..383e5cbe0 100644 --- a/packages/website/ts/globals.d.ts +++ b/packages/website/ts/globals.d.ts @@ -14,10 +14,10 @@ declare module 'ledgerco'; declare module 'ethereumjs-tx'; declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ } // tslint:disable:max-classes-per-file @@ -25,137 +25,137 @@ declare module '*.json' { // find-version declarations declare function findVersions(version: string): string[]; declare module 'find-versions' { - export = findVersions; + export = findVersions; } // compare-version declarations declare function compareVersions(firstVersion: string, secondVersion: string): number; declare module 'compare-versions' { - export = compareVersions; + export = compareVersions; } // semver-sort declarations declare module 'semver-sort' { - const desc: (versions: string[]) => string[]; + const desc: (versions: string[]) => string[]; } // xml-js declarations declare interface XML2JSONOpts { - compact?: boolean; - spaces?: number; + compact?: boolean; + spaces?: number; } declare module 'xml-js' { - const xml2json: (xml: string, opts: XML2JSONOpts) => string; + const xml2json: (xml: string, opts: XML2JSONOpts) => string; } // This will be defined by default in TS 2.4 // Source: https://github.com/Microsoft/TypeScript/issues/12364 interface System { - import<T>(module: string): Promise<T>; + import<T>(module: string): Promise<T>; } declare var System: System; // jsonschema declarations // Source: https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts declare interface Schema { - id?: string; - $schema?: string; - title?: string; - description?: string; - multipleOf?: number; - maximum?: number; - exclusiveMaximum?: boolean; - minimum?: number; - exclusiveMinimum?: boolean; - maxLength?: number; - minLength?: number; - pattern?: string; - additionalItems?: boolean | Schema; - items?: Schema | Schema[]; - maxItems?: number; - minItems?: number; - uniqueItems?: boolean; - maxProperties?: number; - minProperties?: number; - required?: string[]; - additionalProperties?: boolean | Schema; - definitions?: { - [name: string]: Schema; - }; - properties?: { - [name: string]: Schema; - }; - patternProperties?: { - [name: string]: Schema; - }; - dependencies?: { - [name: string]: Schema | string[]; - }; - enum?: any[]; - type?: string | string[]; - allOf?: Schema[]; - anyOf?: Schema[]; - oneOf?: Schema[]; - not?: Schema; - // This is the only property that's not defined in https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts - // There is an open issue for that: https://github.com/tdegrunt/jsonschema/issues/194 - // There is also an opened PR: https://github.com/tdegrunt/jsonschema/pull/218/files - // As soon as it gets merged we should be good to use types from 'jsonschema' package - $ref?: string; + id?: string; + $schema?: string; + title?: string; + description?: string; + multipleOf?: number; + maximum?: number; + exclusiveMaximum?: boolean; + minimum?: number; + exclusiveMinimum?: boolean; + maxLength?: number; + minLength?: number; + pattern?: string; + additionalItems?: boolean | Schema; + items?: Schema | Schema[]; + maxItems?: number; + minItems?: number; + uniqueItems?: boolean; + maxProperties?: number; + minProperties?: number; + required?: string[]; + additionalProperties?: boolean | Schema; + definitions?: { + [name: string]: Schema; + }; + properties?: { + [name: string]: Schema; + }; + patternProperties?: { + [name: string]: Schema; + }; + dependencies?: { + [name: string]: Schema | string[]; + }; + enum?: any[]; + type?: string | string[]; + allOf?: Schema[]; + anyOf?: Schema[]; + oneOf?: Schema[]; + not?: Schema; + // This is the only property that's not defined in https://github.com/tdegrunt/jsonschema/blob/master/lib/index.d.ts + // There is an open issue for that: https://github.com/tdegrunt/jsonschema/issues/194 + // There is also an opened PR: https://github.com/tdegrunt/jsonschema/pull/218/files + // As soon as it gets merged we should be good to use types from 'jsonschema' package + $ref?: string; } // blockies declarations declare interface BlockiesIcon { - toDataURL(): string; + toDataURL(): string; } declare interface BlockiesConfig { - seed: string; + seed: string; } declare function blockies(config: BlockiesConfig): BlockiesIcon; declare module 'blockies' { - export = blockies; + export = blockies; } // is-mobile declarations declare function isMobile(): boolean; declare module 'is-mobile' { - export = isMobile; + export = isMobile; } // web3-provider-engine declarations declare class Subprovider {} declare module 'web3-provider-engine/subproviders/subprovider' { - export = Subprovider; + export = Subprovider; } declare module 'web3-provider-engine/subproviders/rpc' { - import * as Web3 from 'web3'; - class RpcSubprovider { - constructor(options: { rpcUrl: string }); - public handleRequest( - payload: Web3.JSONRPCRequestPayload, - next: () => void, - end: (err: Error | null, data?: any) => void, - ): void; - } - export = RpcSubprovider; + import * as Web3 from 'web3'; + class RpcSubprovider { + constructor(options: { rpcUrl: string }); + public handleRequest( + payload: Web3.JSONRPCRequestPayload, + next: () => void, + end: (err: Error | null, data?: any) => void, + ): void; + } + export = RpcSubprovider; } declare module 'web3-provider-engine' { - class Web3ProviderEngine { - public on(event: string, handler: () => void): void; - public send(payload: any): void; - public sendAsync(payload: any, callback: (error: any, response: any) => void): void; - public addProvider(provider: any): void; - public start(): void; - public stop(): void; - } - export = Web3ProviderEngine; + class Web3ProviderEngine { + public on(event: string, handler: () => void): void; + public send(payload: any): void; + public sendAsync(payload: any, callback: (error: any, response: any) => void): void; + public addProvider(provider: any): void; + public start(): void; + public stop(): void; + } + export = Web3ProviderEngine; } declare interface Artifact { - abi: any; - networks: { - [networkId: number]: { - address: string; - }; - }; + abi: any; + networks: { + [networkId: number]: { + address: string; + }; + }; } diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx index df45c80bd..ffb551561 100644 --- a/packages/website/ts/index.tsx +++ b/packages/website/ts/index.tsx @@ -32,44 +32,44 @@ import 'less/all.less'; // At the same time webpack statically parses for System.import() to determine bundle chunk split points // so each lazy import needs it's own `System.import()` declaration. const LazyPortal = createLazyComponent('Portal', async () => - System.import<any>(/* webpackChunkName: "portal" */ 'ts/containers/portal'), + System.import<any>(/* webpackChunkName: "portal" */ 'ts/containers/portal'), ); const LazyZeroExJSDocumentation = createLazyComponent('Documentation', async () => - System.import<any>(/* webpackChunkName: "zeroExDocs" */ 'ts/containers/zero_ex_js_documentation'), + System.import<any>(/* webpackChunkName: "zeroExDocs" */ 'ts/containers/zero_ex_js_documentation'), ); const LazySmartContractsDocumentation = createLazyComponent('Documentation', async () => - System.import<any>(/* webpackChunkName: "smartContractDocs" */ 'ts/containers/smart_contracts_documentation'), + System.import<any>(/* webpackChunkName: "smartContractDocs" */ 'ts/containers/smart_contracts_documentation'), ); const LazyConnectDocumentation = createLazyComponent('Documentation', async () => - System.import<any>(/* webpackChunkName: "connectDocs" */ 'ts/containers/connect_documentation'), + System.import<any>(/* webpackChunkName: "connectDocs" */ 'ts/containers/connect_documentation'), ); const store: ReduxStore<State> = createStore(reducer); render( - <Router> - <div> - <MuiThemeProvider muiTheme={muiTheme}> - <Provider store={store}> - <div> - <Switch> - <Route exact={true} path="/" component={Landing as any} /> - <Redirect from="/otc" to={`${WebsitePaths.Portal}`} /> - <Route path={`${WebsitePaths.Portal}`} component={LazyPortal} /> - <Route path={`${WebsitePaths.FAQ}`} component={FAQ as any} /> - <Route path={`${WebsitePaths.About}`} component={About as any} /> - <Route path={`${WebsitePaths.Wiki}`} component={Wiki as any} /> - <Route path={`${WebsitePaths.ZeroExJs}/:version?`} component={LazyZeroExJSDocumentation} /> - <Route path={`${WebsitePaths.Connect}/:version?`} component={LazyConnectDocumentation} /> - <Route - path={`${WebsitePaths.SmartContracts}/:version?`} - component={LazySmartContractsDocumentation} - /> - <Route component={NotFound as any} /> - </Switch> - </div> - </Provider> - </MuiThemeProvider> - </div> - </Router>, - document.getElementById('app'), + <Router> + <div> + <MuiThemeProvider muiTheme={muiTheme}> + <Provider store={store}> + <div> + <Switch> + <Route exact={true} path="/" component={Landing as any} /> + <Redirect from="/otc" to={`${WebsitePaths.Portal}`} /> + <Route path={`${WebsitePaths.Portal}`} component={LazyPortal} /> + <Route path={`${WebsitePaths.FAQ}`} component={FAQ as any} /> + <Route path={`${WebsitePaths.About}`} component={About as any} /> + <Route path={`${WebsitePaths.Wiki}`} component={Wiki as any} /> + <Route path={`${WebsitePaths.ZeroExJs}/:version?`} component={LazyZeroExJSDocumentation} /> + <Route path={`${WebsitePaths.Connect}/:version?`} component={LazyConnectDocumentation} /> + <Route + path={`${WebsitePaths.SmartContracts}/:version?`} + component={LazySmartContractsDocumentation} + /> + <Route component={NotFound as any} /> + </Switch> + </div> + </Provider> + </MuiThemeProvider> + </div> + </Router>, + document.getElementById('app'), ); diff --git a/packages/website/ts/lazy_component.tsx b/packages/website/ts/lazy_component.tsx index 5efebb667..48800c2dd 100644 --- a/packages/website/ts/lazy_component.tsx +++ b/packages/website/ts/lazy_component.tsx @@ -2,12 +2,12 @@ import * as _ from 'lodash'; import * as React from 'react'; interface LazyComponentProps { - reactComponentPromise: Promise<React.ComponentClass<any>>; - reactComponentProps: any; + reactComponentPromise: Promise<React.ComponentClass<any>>; + reactComponentProps: any; } interface LazyComponentState { - component?: React.ComponentClass<any>; + component?: React.ComponentClass<any>; } /** @@ -15,33 +15,33 @@ interface LazyComponentState { * Source: https://reacttraining.com/react-router/web/guides/code-splitting */ export class LazyComponent extends React.Component<LazyComponentProps, LazyComponentState> { - constructor(props: LazyComponentProps) { - super(props); - this.state = { - component: undefined, - }; - } - public componentWillMount() { - // tslint:disable-next-line:no-floating-promises - this._loadComponentFireAndForgetAsync(this.props); - } - public componentWillReceiveProps(nextProps: LazyComponentProps) { - if (nextProps.reactComponentPromise !== this.props.reactComponentPromise) { - // tslint:disable-next-line:no-floating-promises - this._loadComponentFireAndForgetAsync(nextProps); - } - } - public render() { - return _.isUndefined(this.state.component) - ? null - : React.createElement(this.state.component, this.props.reactComponentProps); - } - private async _loadComponentFireAndForgetAsync(props: LazyComponentProps) { - const component = await props.reactComponentPromise; - this.setState({ - component, - }); - } + constructor(props: LazyComponentProps) { + super(props); + this.state = { + component: undefined, + }; + } + public componentWillMount() { + // tslint:disable-next-line:no-floating-promises + this._loadComponentFireAndForgetAsync(this.props); + } + public componentWillReceiveProps(nextProps: LazyComponentProps) { + if (nextProps.reactComponentPromise !== this.props.reactComponentPromise) { + // tslint:disable-next-line:no-floating-promises + this._loadComponentFireAndForgetAsync(nextProps); + } + } + public render() { + return _.isUndefined(this.state.component) + ? null + : React.createElement(this.state.component, this.props.reactComponentProps); + } + private async _loadComponentFireAndForgetAsync(props: LazyComponentProps) { + const component = await props.reactComponentPromise; + this.setState({ + component, + }); + } } /** @@ -52,15 +52,15 @@ export class LazyComponent extends React.Component<LazyComponentProps, LazyCompo * @example `const LazyPortal = createLazyComponent('Portal', () => System.import<any>('ts/containers/portal'));`` */ export const createLazyComponent = (componentName: string, lazyImport: () => Promise<any>) => { - return (props: any) => { - const reactComponentPromise = (async (): Promise<React.ComponentClass<any>> => { - const mod = await lazyImport(); - const component = mod[componentName]; - if (_.isUndefined(component)) { - throw new Error(`Did not find exported component: ${componentName}`); - } - return component; - })(); - return <LazyComponent reactComponentPromise={reactComponentPromise} reactComponentProps={props} />; - }; + return (props: any) => { + const reactComponentPromise = (async (): Promise<React.ComponentClass<any>> => { + const mod = await lazyImport(); + const component = mod[componentName]; + if (_.isUndefined(component)) { + throw new Error(`Did not find exported component: ${componentName}`); + } + return component; + })(); + return <LazyComponent reactComponentPromise={reactComponentPromise} reactComponentProps={props} />; + }; }; diff --git a/packages/website/ts/local_storage/local_storage.ts b/packages/website/ts/local_storage/local_storage.ts index 4e5b77a0a..d94e6877b 100644 --- a/packages/website/ts/local_storage/local_storage.ts +++ b/packages/website/ts/local_storage/local_storage.ts @@ -1,35 +1,35 @@ import * as _ from 'lodash'; export const localStorage = { - doesExist() { - return !!window.localStorage; - }, - getItemIfExists(key: string): string { - if (!this.doesExist) { - return undefined; - } - const item = window.localStorage.getItem(key); - if (_.isNull(item) || item === 'undefined') { - return ''; - } - return item; - }, - setItem(key: string, value: string) { - if (!this.doesExist || _.isUndefined(value)) { - return; - } - window.localStorage.setItem(key, value); - }, - removeItem(key: string) { - if (!this.doesExist) { - return; - } - window.localStorage.removeItem(key); - }, - getAllKeys(): string[] { - if (!this.doesExist) { - return []; - } - return _.keys(window.localStorage); - }, + doesExist() { + return !!window.localStorage; + }, + getItemIfExists(key: string): string { + if (!this.doesExist) { + return undefined; + } + const item = window.localStorage.getItem(key); + if (_.isNull(item) || item === 'undefined') { + return ''; + } + return item; + }, + setItem(key: string, value: string) { + if (!this.doesExist || _.isUndefined(value)) { + return; + } + window.localStorage.setItem(key, value); + }, + removeItem(key: string) { + if (!this.doesExist) { + return; + } + window.localStorage.removeItem(key); + }, + getAllKeys(): string[] { + if (!this.doesExist) { + return []; + } + return _.keys(window.localStorage); + }, }; diff --git a/packages/website/ts/local_storage/tracked_token_storage.ts b/packages/website/ts/local_storage/tracked_token_storage.ts index 2c62084e0..7733e8436 100644 --- a/packages/website/ts/local_storage/tracked_token_storage.ts +++ b/packages/website/ts/local_storage/tracked_token_storage.ts @@ -7,61 +7,61 @@ const TRACKED_TOKENS_KEY = 'trackedTokens'; const TRACKED_TOKENS_CLEAR_KEY = 'lastClearTrackedTokensDate'; export const trackedTokenStorage = { - // Clear trackedTokens localStorage if we've updated the config variable in an update - // that introduced a backward incompatible change requiring the tracked tokens to be re-set - clearIfRequired(): void { - const lastClearFillDate = localStorage.getItemIfExists(TRACKED_TOKENS_CLEAR_KEY); - if (lastClearFillDate !== configs.LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE) { - localStorage.removeItem(TRACKED_TOKENS_KEY); - } - localStorage.setItem(TRACKED_TOKENS_CLEAR_KEY, configs.LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE); - }, - addTrackedTokenToUser(userAddress: string, networkId: number, token: Token): void { - const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); - let trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; - if (_.isUndefined(trackedTokensByNetworkId)) { - trackedTokensByNetworkId = {}; - } - const trackedTokens = !_.isUndefined(trackedTokensByNetworkId[networkId]) - ? trackedTokensByNetworkId[networkId] - : []; - trackedTokens.push(token); - trackedTokensByNetworkId[networkId] = trackedTokens; - trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; - const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); - localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); - }, - getTrackedTokensByUserAddress(): TrackedTokensByUserAddress { - const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); - if (_.isEmpty(trackedTokensJSONString)) { - return {}; - } - const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); - return trackedTokensByUserAddress; - }, - getTrackedTokensIfExists(userAddress: string, networkId: number): Token[] { - const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); - if (_.isEmpty(trackedTokensJSONString)) { - return undefined; - } - const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); - const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; - if (_.isUndefined(trackedTokensByNetworkId)) { - return undefined; - } - const trackedTokens = trackedTokensByNetworkId[networkId]; - return trackedTokens; - }, - removeTrackedToken(userAddress: string, networkId: number, tokenAddress: string): void { - const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); - const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; - const trackedTokens = trackedTokensByNetworkId[networkId]; - const remainingTrackedTokens = _.filter(trackedTokens, (token: Token) => { - return token.address !== tokenAddress; - }); - trackedTokensByNetworkId[networkId] = remainingTrackedTokens; - trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; - const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); - localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); - }, + // Clear trackedTokens localStorage if we've updated the config variable in an update + // that introduced a backward incompatible change requiring the tracked tokens to be re-set + clearIfRequired(): void { + const lastClearFillDate = localStorage.getItemIfExists(TRACKED_TOKENS_CLEAR_KEY); + if (lastClearFillDate !== configs.LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE) { + localStorage.removeItem(TRACKED_TOKENS_KEY); + } + localStorage.setItem(TRACKED_TOKENS_CLEAR_KEY, configs.LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE); + }, + addTrackedTokenToUser(userAddress: string, networkId: number, token: Token): void { + const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); + let trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + if (_.isUndefined(trackedTokensByNetworkId)) { + trackedTokensByNetworkId = {}; + } + const trackedTokens = !_.isUndefined(trackedTokensByNetworkId[networkId]) + ? trackedTokensByNetworkId[networkId] + : []; + trackedTokens.push(token); + trackedTokensByNetworkId[networkId] = trackedTokens; + trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; + const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); + localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); + }, + getTrackedTokensByUserAddress(): TrackedTokensByUserAddress { + const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); + if (_.isEmpty(trackedTokensJSONString)) { + return {}; + } + const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); + return trackedTokensByUserAddress; + }, + getTrackedTokensIfExists(userAddress: string, networkId: number): Token[] { + const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); + if (_.isEmpty(trackedTokensJSONString)) { + return undefined; + } + const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); + const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + if (_.isUndefined(trackedTokensByNetworkId)) { + return undefined; + } + const trackedTokens = trackedTokensByNetworkId[networkId]; + return trackedTokens; + }, + removeTrackedToken(userAddress: string, networkId: number, tokenAddress: string): void { + const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); + const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; + const trackedTokens = trackedTokensByNetworkId[networkId]; + const remainingTrackedTokens = _.filter(trackedTokens, (token: Token) => { + return token.address !== tokenAddress; + }); + trackedTokensByNetworkId[networkId] = remainingTrackedTokens; + trackedTokensByUserAddress[userAddress] = trackedTokensByNetworkId; + const trackedTokensByUserAddressJSONString = JSON.stringify(trackedTokensByUserAddress); + localStorage.setItem(TRACKED_TOKENS_KEY, trackedTokensByUserAddressJSONString); + }, }; diff --git a/packages/website/ts/local_storage/trade_history_storage.tsx b/packages/website/ts/local_storage/trade_history_storage.tsx index b20244b29..df731236e 100644 --- a/packages/website/ts/local_storage/trade_history_storage.tsx +++ b/packages/website/ts/local_storage/trade_history_storage.tsx @@ -11,84 +11,84 @@ const FILLS_LATEST_BLOCK = 'fillsLatestBlock'; const FILL_CLEAR_KEY = 'lastClearFillDate'; export const tradeHistoryStorage = { - // Clear all fill related localStorage if we've updated the config variable in an update - // that introduced a backward incompatible change requiring the user to re-fetch the fills from - // the blockchain - clearIfRequired() { - const lastClearFillDate = localStorage.getItemIfExists(FILL_CLEAR_KEY); - if (lastClearFillDate !== configs.LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE) { - const localStorageKeys = localStorage.getAllKeys(); - _.each(localStorageKeys, key => { - if (_.startsWith(key, `${FILLS_KEY}-`) || _.startsWith(key, `${FILLS_LATEST_BLOCK}-`)) { - localStorage.removeItem(key); - } - }); - } - localStorage.setItem(FILL_CLEAR_KEY, configs.LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE); - }, - addFillToUser(userAddress: string, networkId: number, fill: Fill) { - const fillsByHash = this.getUserFillsByHash(userAddress, networkId); - const fillHash = this._getFillHash(fill); - const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); - if (doesFillExist) { - return; // noop - } - fillsByHash[fillHash] = fill; - const userFillsJSONString = JSON.stringify(fillsByHash); - const userFillsKey = this._getUserFillsKey(userAddress, networkId); - localStorage.setItem(userFillsKey, userFillsJSONString); - }, - removeFillFromUser(userAddress: string, networkId: number, fill: Fill) { - const fillsByHash = this.getUserFillsByHash(userAddress, networkId); - const fillHash = this._getFillHash(fill); - const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); - if (!doesFillExist) { - return; // noop - } - delete fillsByHash[fillHash]; - const userFillsJSONString = JSON.stringify(fillsByHash); - const userFillsKey = this._getUserFillsKey(userAddress, networkId); - localStorage.setItem(userFillsKey, userFillsJSONString); - }, - getUserFillsByHash(userAddress: string, networkId: number): { [fillHash: string]: Fill } { - const userFillsKey = this._getUserFillsKey(userAddress, networkId); - const userFillsJSONString = localStorage.getItemIfExists(userFillsKey); - if (_.isEmpty(userFillsJSONString)) { - return {}; - } - const userFillsByHash = JSON.parse(userFillsJSONString); - _.each(userFillsByHash, (fill, hash) => { - fill.paidMakerFee = new BigNumber(fill.paidMakerFee); - fill.paidTakerFee = new BigNumber(fill.paidTakerFee); - fill.filledTakerTokenAmount = new BigNumber(fill.filledTakerTokenAmount); - fill.filledMakerTokenAmount = new BigNumber(fill.filledMakerTokenAmount); - }); - return userFillsByHash; - }, - getFillsLatestBlock(userAddress: string, networkId: number): number { - const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); - const blockNumberStr = localStorage.getItemIfExists(userFillsLatestBlockKey); - if (_.isEmpty(blockNumberStr)) { - return constants.GENESIS_ORDER_BLOCK_BY_NETWORK_ID[networkId]; - } - const blockNumber = _.parseInt(blockNumberStr); - return blockNumber; - }, - setFillsLatestBlock(userAddress: string, networkId: number, blockNumber: number) { - const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); - localStorage.setItem(userFillsLatestBlockKey, `${blockNumber}`); - }, - _getUserFillsKey(userAddress: string, networkId: number) { - const userFillsKey = `${FILLS_KEY}-${userAddress}-${networkId}`; - return userFillsKey; - }, - _getFillsLatestBlockKey(userAddress: string, networkId: number) { - const userFillsLatestBlockKey = `${FILLS_LATEST_BLOCK}-${userAddress}-${networkId}`; - return userFillsLatestBlockKey; - }, - _getFillHash(fill: Fill): string { - const fillJSON = JSON.stringify(fill); - const fillHash = ethUtil.sha256(fillJSON); - return fillHash.toString('hex'); - }, + // Clear all fill related localStorage if we've updated the config variable in an update + // that introduced a backward incompatible change requiring the user to re-fetch the fills from + // the blockchain + clearIfRequired() { + const lastClearFillDate = localStorage.getItemIfExists(FILL_CLEAR_KEY); + if (lastClearFillDate !== configs.LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE) { + const localStorageKeys = localStorage.getAllKeys(); + _.each(localStorageKeys, key => { + if (_.startsWith(key, `${FILLS_KEY}-`) || _.startsWith(key, `${FILLS_LATEST_BLOCK}-`)) { + localStorage.removeItem(key); + } + }); + } + localStorage.setItem(FILL_CLEAR_KEY, configs.LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE); + }, + addFillToUser(userAddress: string, networkId: number, fill: Fill) { + const fillsByHash = this.getUserFillsByHash(userAddress, networkId); + const fillHash = this._getFillHash(fill); + const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); + if (doesFillExist) { + return; // noop + } + fillsByHash[fillHash] = fill; + const userFillsJSONString = JSON.stringify(fillsByHash); + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + localStorage.setItem(userFillsKey, userFillsJSONString); + }, + removeFillFromUser(userAddress: string, networkId: number, fill: Fill) { + const fillsByHash = this.getUserFillsByHash(userAddress, networkId); + const fillHash = this._getFillHash(fill); + const doesFillExist = !_.isUndefined(fillsByHash[fillHash]); + if (!doesFillExist) { + return; // noop + } + delete fillsByHash[fillHash]; + const userFillsJSONString = JSON.stringify(fillsByHash); + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + localStorage.setItem(userFillsKey, userFillsJSONString); + }, + getUserFillsByHash(userAddress: string, networkId: number): { [fillHash: string]: Fill } { + const userFillsKey = this._getUserFillsKey(userAddress, networkId); + const userFillsJSONString = localStorage.getItemIfExists(userFillsKey); + if (_.isEmpty(userFillsJSONString)) { + return {}; + } + const userFillsByHash = JSON.parse(userFillsJSONString); + _.each(userFillsByHash, (fill, hash) => { + fill.paidMakerFee = new BigNumber(fill.paidMakerFee); + fill.paidTakerFee = new BigNumber(fill.paidTakerFee); + fill.filledTakerTokenAmount = new BigNumber(fill.filledTakerTokenAmount); + fill.filledMakerTokenAmount = new BigNumber(fill.filledMakerTokenAmount); + }); + return userFillsByHash; + }, + getFillsLatestBlock(userAddress: string, networkId: number): number { + const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); + const blockNumberStr = localStorage.getItemIfExists(userFillsLatestBlockKey); + if (_.isEmpty(blockNumberStr)) { + return constants.GENESIS_ORDER_BLOCK_BY_NETWORK_ID[networkId]; + } + const blockNumber = _.parseInt(blockNumberStr); + return blockNumber; + }, + setFillsLatestBlock(userAddress: string, networkId: number, blockNumber: number) { + const userFillsLatestBlockKey = this._getFillsLatestBlockKey(userAddress, networkId); + localStorage.setItem(userFillsLatestBlockKey, `${blockNumber}`); + }, + _getUserFillsKey(userAddress: string, networkId: number) { + const userFillsKey = `${FILLS_KEY}-${userAddress}-${networkId}`; + return userFillsKey; + }, + _getFillsLatestBlockKey(userAddress: string, networkId: number) { + const userFillsLatestBlockKey = `${FILLS_LATEST_BLOCK}-${userAddress}-${networkId}`; + return userFillsLatestBlockKey; + }, + _getFillHash(fill: Fill): string { + const fillJSON = JSON.stringify(fill); + const fillHash = ethUtil.sha256(fillJSON); + return fillHash.toString('hex'); + }, }; diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index 411456427..c929673f5 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -10,239 +10,239 @@ import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; const teamRow1: ProfileInfo[] = [ - { - name: 'Will Warren', - title: 'Co-founder & CEO', - description: `Smart contract R&D. Previously applied physics at Los Alamos \ + { + name: 'Will Warren', + title: 'Co-founder & CEO', + description: `Smart contract R&D. Previously applied physics at Los Alamos \ Nat Lab. Mechanical engineering at UC San Diego. PhD dropout.`, - image: '/images/team/will.jpg', - linkedIn: 'https://www.linkedin.com/in/will-warren-92aab62b/', - github: 'https://github.com/willwarren89', - medium: 'https://medium.com/@willwarren89', - }, - { - name: 'Amir Bandeali', - title: 'Co-founder & CTO', - description: `Smart contract R&D. Previously fixed income trader at DRW. \ + image: '/images/team/will.jpg', + linkedIn: 'https://www.linkedin.com/in/will-warren-92aab62b/', + github: 'https://github.com/willwarren89', + medium: 'https://medium.com/@willwarren89', + }, + { + name: 'Amir Bandeali', + title: 'Co-founder & CTO', + description: `Smart contract R&D. Previously fixed income trader at DRW. \ Finance at University of Illinois, Urbana-Champaign.`, - image: '/images/team/amir.jpeg', - linkedIn: 'https://www.linkedin.com/in/abandeali1/', - github: 'https://github.com/abandeali1', - medium: 'https://medium.com/@abandeali1', - }, - { - name: 'Fabio Berger', - title: 'Senior Engineer', - description: `Full-stack blockchain engineer. Previously software engineer \ + image: '/images/team/amir.jpeg', + linkedIn: 'https://www.linkedin.com/in/abandeali1/', + github: 'https://github.com/abandeali1', + medium: 'https://medium.com/@abandeali1', + }, + { + name: 'Fabio Berger', + title: 'Senior Engineer', + description: `Full-stack blockchain engineer. Previously software engineer \ at Airtable and founder of WealthLift. Computer science at Duke.`, - image: '/images/team/fabio.jpg', - linkedIn: 'https://www.linkedin.com/in/fabio-berger-03ab261a/', - github: 'https://github.com/fabioberger', - medium: 'https://medium.com/@fabioberger', - }, + image: '/images/team/fabio.jpg', + linkedIn: 'https://www.linkedin.com/in/fabio-berger-03ab261a/', + github: 'https://github.com/fabioberger', + medium: 'https://medium.com/@fabioberger', + }, ]; const teamRow2: ProfileInfo[] = [ - { - name: 'Alex Xu', - title: 'Director of Operations', - description: `Strategy and operations. Previously digital marketing at Google \ + { + name: 'Alex Xu', + title: 'Director of Operations', + description: `Strategy and operations. Previously digital marketing at Google \ and vendor management at Amazon. Economics at UC San Diego.`, - image: '/images/team/alex.jpg', - linkedIn: 'https://www.linkedin.com/in/alex-xu/', - github: '', - medium: 'https://medium.com/@aqxu', - }, - { - name: 'Leonid Logvinov', - title: 'Engineer', - description: `Full-stack blockchain engineer. Previously blockchain engineer \ + image: '/images/team/alex.jpg', + linkedIn: 'https://www.linkedin.com/in/alex-xu/', + github: '', + medium: 'https://medium.com/@aqxu', + }, + { + name: 'Leonid Logvinov', + title: 'Engineer', + description: `Full-stack blockchain engineer. Previously blockchain engineer \ at Neufund. Computer science at University of Warsaw.`, - image: '/images/team/leonid.png', - linkedIn: 'https://www.linkedin.com/in/leonidlogvinov/', - github: 'https://github.com/LogvinovLeon', - medium: 'https://medium.com/@Logvinov', - }, - { - name: 'Ben Burns', - title: 'Designer', - description: `Product, motion, and graphic designer. Previously designer \ + image: '/images/team/leonid.png', + linkedIn: 'https://www.linkedin.com/in/leonidlogvinov/', + github: 'https://github.com/LogvinovLeon', + medium: 'https://medium.com/@Logvinov', + }, + { + name: 'Ben Burns', + title: 'Designer', + description: `Product, motion, and graphic designer. Previously designer \ at Airtable and Apple. Digital Design at University of Cincinnati.`, - image: '/images/team/ben.jpg', - linkedIn: 'https://www.linkedin.com/in/ben-burns-30170478/', - github: '', - medium: '', - }, + image: '/images/team/ben.jpg', + linkedIn: 'https://www.linkedin.com/in/ben-burns-30170478/', + github: '', + medium: '', + }, ]; const teamRow3: ProfileInfo[] = [ - { - name: 'Brandon Millman', - title: 'Senior Engineer', - description: `Full-stack engineer. Previously senior software engineer at \ + { + name: 'Brandon Millman', + title: 'Senior Engineer', + description: `Full-stack engineer. Previously senior software engineer at \ Twitter. Electrical and Computer Engineering at Duke.`, - image: '/images/team/brandon.png', - linkedIn: 'https://www.linkedin.com/in/brandon-millman-b093a022/', - github: 'https://github.com/BMillman19', - medium: 'https://medium.com/@bchillman', - }, - { - name: 'Tom Schmidt', - title: 'Product Manager', - description: `Previously engineering at Apple, product management at Facebook and Instagram. Computer Science at Stanford.`, - image: '/images/team/tom.jpg', - linkedIn: 'https://www.linkedin.com/in/tomhschmidt/', - github: 'https://github.com/tomhschmidt', - medium: '', - }, - { - name: 'Jacob Evans', - title: 'Blockchain Engineer', - description: `Previously software engineer at Qantas and RSA Security.`, - image: '/images/team/jacob.jpg', - linkedIn: 'https://www.linkedin.com/in/dekzter/', - github: 'https://github.com/dekz', - medium: '', - }, + image: '/images/team/brandon.png', + linkedIn: 'https://www.linkedin.com/in/brandon-millman-b093a022/', + github: 'https://github.com/BMillman19', + medium: 'https://medium.com/@bchillman', + }, + { + name: 'Tom Schmidt', + title: 'Product Manager', + description: `Previously engineering at Apple, product management at Facebook and Instagram. Computer Science at Stanford.`, + image: '/images/team/tom.jpg', + linkedIn: 'https://www.linkedin.com/in/tomhschmidt/', + github: 'https://github.com/tomhschmidt', + medium: '', + }, + { + name: 'Jacob Evans', + title: 'Blockchain Engineer', + description: `Previously software engineer at Qantas and RSA Security.`, + image: '/images/team/jacob.jpg', + linkedIn: 'https://www.linkedin.com/in/dekzter/', + github: 'https://github.com/dekz', + medium: '', + }, ]; const advisors: ProfileInfo[] = [ - { - name: 'Fred Ehrsam', - description: 'Co-founder of Coinbase. Previously FX trader at Goldman Sachs.', - image: '/images/advisors/fred.jpg', - linkedIn: 'https://www.linkedin.com/in/fredehrsam/', - medium: 'https://medium.com/@FEhrsam', - twitter: 'https://twitter.com/FEhrsam', - }, - { - name: 'Olaf Carlson-Wee', - image: '/images/advisors/olaf.png', - description: 'Founder of Polychain Capital. First hire at Coinbase. Angel investor.', - linkedIn: 'https://www.linkedin.com/in/olafcw/', - angellist: 'https://angel.co/olafcw', - }, - { - name: 'Joey Krug', - description: `Co-CIO at Pantera Capital. Founder of Augur. Thiel 20 Under 20 Fellow.`, - image: '/images/advisors/joey.jpg', - linkedIn: 'https://www.linkedin.com/in/joeykrug/', - github: 'https://github.com/joeykrug', - angellist: 'https://angel.co/joeykrug', - }, - { - name: 'Linda Xie', - description: 'Co-founder of Scalar Capital. Previously PM at Coinbase.', - image: '/images/advisors/linda.jpg', - linkedIn: 'https://www.linkedin.com/in/lindaxie/', - medium: 'https://medium.com/@linda.xie', - twitter: 'https://twitter.com/ljxie', - }, + { + name: 'Fred Ehrsam', + description: 'Co-founder of Coinbase. Previously FX trader at Goldman Sachs.', + image: '/images/advisors/fred.jpg', + linkedIn: 'https://www.linkedin.com/in/fredehrsam/', + medium: 'https://medium.com/@FEhrsam', + twitter: 'https://twitter.com/FEhrsam', + }, + { + name: 'Olaf Carlson-Wee', + image: '/images/advisors/olaf.png', + description: 'Founder of Polychain Capital. First hire at Coinbase. Angel investor.', + linkedIn: 'https://www.linkedin.com/in/olafcw/', + angellist: 'https://angel.co/olafcw', + }, + { + name: 'Joey Krug', + description: `Co-CIO at Pantera Capital. Founder of Augur. Thiel 20 Under 20 Fellow.`, + image: '/images/advisors/joey.jpg', + linkedIn: 'https://www.linkedin.com/in/joeykrug/', + github: 'https://github.com/joeykrug', + angellist: 'https://angel.co/joeykrug', + }, + { + name: 'Linda Xie', + description: 'Co-founder of Scalar Capital. Previously PM at Coinbase.', + image: '/images/advisors/linda.jpg', + linkedIn: 'https://www.linkedin.com/in/lindaxie/', + medium: 'https://medium.com/@linda.xie', + twitter: 'https://twitter.com/ljxie', + }, ]; export interface AboutProps { - source: string; - location: Location; + source: string; + location: Location; } interface AboutState {} const styles: Styles = { - header: { - fontFamily: 'Roboto Mono', - fontSize: 36, - color: 'black', - paddingTop: 110, - }, - weAreHiring: { - fontSize: 30, - color: colors.darkestGrey, - fontFamily: 'Roboto Mono', - letterSpacing: 7.5, - }, + header: { + fontFamily: 'Roboto Mono', + fontSize: 36, + color: 'black', + paddingTop: 110, + }, + weAreHiring: { + fontSize: 30, + color: colors.darkestGrey, + fontFamily: 'Roboto Mono', + letterSpacing: 7.5, + }, }; export class About extends React.Component<AboutProps, AboutState> { - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - return ( - <div style={{ backgroundColor: colors.lightestGrey }}> - <DocumentTitle title="0x About Us" /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - style={{ backgroundColor: colors.lightestGrey }} - /> - <div id="about" className="mx-auto max-width-4 py4" style={{ color: colors.grey800 }}> - <div className="mx-auto pb4 sm-px3" style={{ maxWidth: 435 }}> - <div style={styles.header}>About us:</div> - <div - className="pt3" - style={{ - fontSize: 17, - color: colors.darkestGrey, - lineHeight: 1.5, - }} - > - Our team is a diverse and globally distributed group with backgrounds in engineering, - research, business and design. We are passionate about decentralized technology and its - potential to act as an equalizing force in the world. - </div> - </div> - <div className="pt3 md-px4 lg-px0"> - <div className="clearfix pb3">{this._renderProfiles(teamRow1)}</div> - <div className="clearfix">{this._renderProfiles(teamRow2)}</div> - <div className="clearfix">{this._renderProfiles(teamRow3)}</div> - </div> - <div className="pt3 pb2"> - <div - className="pt2 pb3 sm-center md-pl4 lg-pl0 md-ml3" - style={{ - color: colors.grey, - fontSize: 24, - fontFamily: 'Roboto Mono', - }} - > - Advisors: - </div> - <div className="clearfix">{this._renderProfiles(advisors)}</div> - </div> - <div className="mx-auto py4 sm-px3" style={{ maxWidth: 308 }}> - <div className="pb2" style={styles.weAreHiring}> - WE'RE HIRING - </div> - <div - className="pb4 mb4" - style={{ - fontSize: 16, - color: colors.darkestGrey, - lineHeight: 1.5, - letterSpacing: '0.5px', - }} - > - We are seeking outstanding candidates to{' '} - <a href={constants.URL_ANGELLIST} target="_blank" style={{ color: 'black' }}> - join our team - </a> - . We value passion, diversity and unique perspectives. - </div> - </div> - </div> - <Footer /> - </div> - ); - } - private _renderProfiles(profiles: ProfileInfo[]) { - const numIndiv = profiles.length; - const colSize = utils.getColSize(numIndiv); - return _.map(profiles, profile => { - return ( - <div key={`profile-${profile.name}`}> - <Profile colSize={colSize} profileInfo={profile} /> - </div> - ); - }); - } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( + <div style={{ backgroundColor: colors.lightestGrey }}> + <DocumentTitle title="0x About Us" /> + <TopBar + blockchainIsLoaded={false} + location={this.props.location} + style={{ backgroundColor: colors.lightestGrey }} + /> + <div id="about" className="mx-auto max-width-4 py4" style={{ color: colors.grey800 }}> + <div className="mx-auto pb4 sm-px3" style={{ maxWidth: 435 }}> + <div style={styles.header}>About us:</div> + <div + className="pt3" + style={{ + fontSize: 17, + color: colors.darkestGrey, + lineHeight: 1.5, + }} + > + Our team is a diverse and globally distributed group with backgrounds in engineering, + research, business and design. We are passionate about decentralized technology and its + potential to act as an equalizing force in the world. + </div> + </div> + <div className="pt3 md-px4 lg-px0"> + <div className="clearfix pb3">{this._renderProfiles(teamRow1)}</div> + <div className="clearfix">{this._renderProfiles(teamRow2)}</div> + <div className="clearfix">{this._renderProfiles(teamRow3)}</div> + </div> + <div className="pt3 pb2"> + <div + className="pt2 pb3 sm-center md-pl4 lg-pl0 md-ml3" + style={{ + color: colors.grey, + fontSize: 24, + fontFamily: 'Roboto Mono', + }} + > + Advisors: + </div> + <div className="clearfix">{this._renderProfiles(advisors)}</div> + </div> + <div className="mx-auto py4 sm-px3" style={{ maxWidth: 308 }}> + <div className="pb2" style={styles.weAreHiring}> + WE'RE HIRING + </div> + <div + className="pb4 mb4" + style={{ + fontSize: 16, + color: colors.darkestGrey, + lineHeight: 1.5, + letterSpacing: '0.5px', + }} + > + We are seeking outstanding candidates to{' '} + <a href={constants.URL_ANGELLIST} target="_blank" style={{ color: 'black' }}> + join our team + </a> + . We value passion, diversity and unique perspectives. + </div> + </div> + </div> + <Footer /> + </div> + ); + } + private _renderProfiles(profiles: ProfileInfo[]) { + const numIndiv = profiles.length; + const colSize = utils.getColSize(numIndiv); + return _.map(profiles, profile => { + return ( + <div key={`profile-${profile.name}`}> + <Profile colSize={colSize} profileInfo={profile} /> + </div> + ); + }); + } } diff --git a/packages/website/ts/pages/about/profile.tsx b/packages/website/ts/pages/about/profile.tsx index f1830851f..18b4e0d5a 100644 --- a/packages/website/ts/pages/about/profile.tsx +++ b/packages/website/ts/pages/about/profile.tsx @@ -5,75 +5,75 @@ import { colors } from 'ts/utils/colors'; const IMAGE_DIMENSION = 149; const styles: Styles = { - subheader: { - textTransform: 'uppercase', - fontSize: 32, - margin: 0, - }, - imageContainer: { - width: IMAGE_DIMENSION, - height: IMAGE_DIMENSION, - boxShadow: 'rgba(0, 0, 0, 0.19) 2px 5px 10px', - }, + subheader: { + textTransform: 'uppercase', + fontSize: 32, + margin: 0, + }, + imageContainer: { + width: IMAGE_DIMENSION, + height: IMAGE_DIMENSION, + boxShadow: 'rgba(0, 0, 0, 0.19) 2px 5px 10px', + }, }; interface ProfileProps { - colSize: number; - profileInfo: ProfileInfo; + colSize: number; + profileInfo: ProfileInfo; } export function Profile(props: ProfileProps) { - return ( - <div className={`lg-col md-col lg-col-${props.colSize} md-col-6`}> - <div style={{ maxWidth: 300 }} className="mx-auto lg-px3 md-px3 sm-px4 sm-pb3"> - <div className="circle overflow-hidden mx-auto" style={styles.imageContainer}> - <img width={IMAGE_DIMENSION} src={props.profileInfo.image} /> - </div> - <div className="center" style={{ fontSize: 18, fontWeight: 'bold', paddingTop: 20 }}> - {props.profileInfo.name} - </div> - {!_.isUndefined(props.profileInfo.title) && ( - <div - className="pt1 center" - style={{ - fontSize: 14, - fontFamily: 'Roboto Mono', - color: colors.darkGrey, - }} - > - {props.profileInfo.title.toUpperCase()} - </div> - )} - <div style={{ minHeight: 60, lineHeight: 1.4 }} className="pt1 pb2 mx-auto lg-h6 md-h6 sm-h5 sm-center"> - {props.profileInfo.description} - </div> - <div className="flex pb3 mx-auto sm-hide xs-hide" style={{ width: 280, opacity: 0.5 }}> - {renderSocialMediaIcons(props.profileInfo)} - </div> - </div> - </div> - ); + return ( + <div className={`lg-col md-col lg-col-${props.colSize} md-col-6`}> + <div style={{ maxWidth: 300 }} className="mx-auto lg-px3 md-px3 sm-px4 sm-pb3"> + <div className="circle overflow-hidden mx-auto" style={styles.imageContainer}> + <img width={IMAGE_DIMENSION} src={props.profileInfo.image} /> + </div> + <div className="center" style={{ fontSize: 18, fontWeight: 'bold', paddingTop: 20 }}> + {props.profileInfo.name} + </div> + {!_.isUndefined(props.profileInfo.title) && ( + <div + className="pt1 center" + style={{ + fontSize: 14, + fontFamily: 'Roboto Mono', + color: colors.darkGrey, + }} + > + {props.profileInfo.title.toUpperCase()} + </div> + )} + <div style={{ minHeight: 60, lineHeight: 1.4 }} className="pt1 pb2 mx-auto lg-h6 md-h6 sm-h5 sm-center"> + {props.profileInfo.description} + </div> + <div className="flex pb3 mx-auto sm-hide xs-hide" style={{ width: 280, opacity: 0.5 }}> + {renderSocialMediaIcons(props.profileInfo)} + </div> + </div> + </div> + ); } function renderSocialMediaIcons(profileInfo: ProfileInfo) { - const icons = [ - renderSocialMediaIcon('zmdi-github-box', profileInfo.github), - renderSocialMediaIcon('zmdi-linkedin-box', profileInfo.linkedIn), - renderSocialMediaIcon('zmdi-twitter-box', profileInfo.twitter), - ]; - return icons; + const icons = [ + renderSocialMediaIcon('zmdi-github-box', profileInfo.github), + renderSocialMediaIcon('zmdi-linkedin-box', profileInfo.linkedIn), + renderSocialMediaIcon('zmdi-twitter-box', profileInfo.twitter), + ]; + return icons; } function renderSocialMediaIcon(iconName: string, url: string) { - if (_.isEmpty(url)) { - return null; - } + if (_.isEmpty(url)) { + return null; + } - return ( - <div key={url} className="pr1"> - <a href={url} style={{ color: 'inherit' }} target="_blank" className="text-decoration-none"> - <i className={`zmdi ${iconName}`} style={{ ...styles.socalIcon }} /> - </a> - </div> - ); + return ( + <div key={url} className="pr1"> + <a href={url} style={{ color: 'inherit' }} target="_blank" className="text-decoration-none"> + <i className={`zmdi ${iconName}`} style={{ ...styles.socalIcon }} /> + </a> + </div> + ); } diff --git a/packages/website/ts/pages/documentation/comment.tsx b/packages/website/ts/pages/documentation/comment.tsx index 6be39f586..23cfd96bd 100644 --- a/packages/website/ts/pages/documentation/comment.tsx +++ b/packages/website/ts/pages/documentation/comment.tsx @@ -4,20 +4,20 @@ import * as ReactMarkdown from 'react-markdown'; import { MarkdownCodeBlock } from 'ts/pages/shared/markdown_code_block'; interface CommentProps { - comment: string; - className?: string; + comment: string; + className?: string; } const defaultProps = { - className: '', + className: '', }; export const Comment: React.SFC<CommentProps> = (props: CommentProps) => { - return ( - <div className={`${props.className} comment`}> - <ReactMarkdown source={props.comment} renderers={{ CodeBlock: MarkdownCodeBlock }} /> - </div> - ); + return ( + <div className={`${props.className} comment`}> + <ReactMarkdown source={props.comment} renderers={{ CodeBlock: MarkdownCodeBlock }} /> + </div> + ); }; Comment.defaultProps = defaultProps; diff --git a/packages/website/ts/pages/documentation/custom_enum.tsx b/packages/website/ts/pages/documentation/custom_enum.tsx index 0006b8d7e..8d50a2f52 100644 --- a/packages/website/ts/pages/documentation/custom_enum.tsx +++ b/packages/website/ts/pages/documentation/custom_enum.tsx @@ -6,27 +6,27 @@ import { utils } from 'ts/utils/utils'; const STRING_ENUM_CODE_PREFIX = ' strEnum('; interface CustomEnumProps { - type: CustomType; + type: CustomType; } // This component renders custom string enums that was a work-around for versions of // TypeScript <2.4.0 that did not support them natively. We keep it around to support // older versions of 0x.js <0.9.0 export function CustomEnum(props: CustomEnumProps) { - const type = props.type; - if (!_.startsWith(type.defaultValue, STRING_ENUM_CODE_PREFIX)) { - utils.consoleLog('We do not yet support `Variable` types that are not strEnums'); - return null; - } - // Remove the prefix and postfix, leaving only the strEnum values without quotes. - const enumValues = type.defaultValue.slice(10, -3).replace(/'/g, ''); - return ( - <span> - {`{`} - {'\t'} - {enumValues} - <br /> - {`}`} - </span> - ); + const type = props.type; + if (!_.startsWith(type.defaultValue, STRING_ENUM_CODE_PREFIX)) { + utils.consoleLog('We do not yet support `Variable` types that are not strEnums'); + return null; + } + // Remove the prefix and postfix, leaving only the strEnum values without quotes. + const enumValues = type.defaultValue.slice(10, -3).replace(/'/g, ''); + return ( + <span> + {`{`} + {'\t'} + {enumValues} + <br /> + {`}`} + </span> + ); } diff --git a/packages/website/ts/pages/documentation/docs_info.ts b/packages/website/ts/pages/documentation/docs_info.ts index e1080f3a0..4b1ec122a 100644 --- a/packages/website/ts/pages/documentation/docs_info.ts +++ b/packages/website/ts/pages/documentation/docs_info.ts @@ -1,111 +1,111 @@ import compareVersions = require('compare-versions'); import * as _ from 'lodash'; import { - DocAgnosticFormat, - DocsInfoConfig, - DocsMenu, - DoxityDocObj, - MenuSubsectionsBySection, - SectionsMap, - TypeDocNode, + DocAgnosticFormat, + DocsInfoConfig, + DocsMenu, + DoxityDocObj, + MenuSubsectionsBySection, + SectionsMap, + TypeDocNode, } from 'ts/types'; export class DocsInfo { - public displayName: string; - public packageUrl: string; - public subPackageName?: string; - public websitePath: string; - public docsJsonRoot: string; - public menu: DocsMenu; - public sections: SectionsMap; - public sectionNameToMarkdown: { [sectionName: string]: string }; - private _docsInfo: DocsInfoConfig; - constructor(config: DocsInfoConfig) { - this.displayName = config.displayName; - this.packageUrl = config.packageUrl; - this.subPackageName = config.subPackageName; - this.websitePath = config.websitePath; - this.docsJsonRoot = config.docsJsonRoot; - this.sections = config.sections; - this.sectionNameToMarkdown = config.sectionNameToMarkdown; - this._docsInfo = config; - } - public isPublicType(typeName: string): boolean { - if (_.isUndefined(this._docsInfo.publicTypes)) { - return false; - } - const isPublic = _.includes(this._docsInfo.publicTypes, typeName); - return isPublic; - } - public getModulePathsIfExists(sectionName: string): string[] { - const modulePathsIfExists = this._docsInfo.sectionNameToModulePath[sectionName]; - return modulePathsIfExists; - } - public getMenu(selectedVersion?: string): { [section: string]: string[] } { - if (_.isUndefined(selectedVersion) || _.isUndefined(this._docsInfo.menuSubsectionToVersionWhenIntroduced)) { - return this._docsInfo.menu; - } + public displayName: string; + public packageUrl: string; + public subPackageName?: string; + public websitePath: string; + public docsJsonRoot: string; + public menu: DocsMenu; + public sections: SectionsMap; + public sectionNameToMarkdown: { [sectionName: string]: string }; + private _docsInfo: DocsInfoConfig; + constructor(config: DocsInfoConfig) { + this.displayName = config.displayName; + this.packageUrl = config.packageUrl; + this.subPackageName = config.subPackageName; + this.websitePath = config.websitePath; + this.docsJsonRoot = config.docsJsonRoot; + this.sections = config.sections; + this.sectionNameToMarkdown = config.sectionNameToMarkdown; + this._docsInfo = config; + } + public isPublicType(typeName: string): boolean { + if (_.isUndefined(this._docsInfo.publicTypes)) { + return false; + } + const isPublic = _.includes(this._docsInfo.publicTypes, typeName); + return isPublic; + } + public getModulePathsIfExists(sectionName: string): string[] { + const modulePathsIfExists = this._docsInfo.sectionNameToModulePath[sectionName]; + return modulePathsIfExists; + } + public getMenu(selectedVersion?: string): { [section: string]: string[] } { + if (_.isUndefined(selectedVersion) || _.isUndefined(this._docsInfo.menuSubsectionToVersionWhenIntroduced)) { + return this._docsInfo.menu; + } - const finalMenu = _.cloneDeep(this._docsInfo.menu); - if (_.isUndefined(finalMenu.contracts)) { - return finalMenu; - } + const finalMenu = _.cloneDeep(this._docsInfo.menu); + if (_.isUndefined(finalMenu.contracts)) { + return finalMenu; + } - // TODO: refactor to include more sections then simply the `contracts` section - finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => { - const versionIntroducedIfExists = this._docsInfo.menuSubsectionToVersionWhenIntroduced[contractName]; - if (!_.isUndefined(versionIntroducedIfExists)) { - const existsInSelectedVersion = compareVersions(selectedVersion, versionIntroducedIfExists) >= 0; - return existsInSelectedVersion; - } else { - return true; - } - }); - return finalMenu; - } - public getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection { - const menuSubsectionsBySection = {} as MenuSubsectionsBySection; - if (_.isUndefined(docAgnosticFormat)) { - return menuSubsectionsBySection; - } + // TODO: refactor to include more sections then simply the `contracts` section + finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => { + const versionIntroducedIfExists = this._docsInfo.menuSubsectionToVersionWhenIntroduced[contractName]; + if (!_.isUndefined(versionIntroducedIfExists)) { + const existsInSelectedVersion = compareVersions(selectedVersion, versionIntroducedIfExists) >= 0; + return existsInSelectedVersion; + } else { + return true; + } + }); + return finalMenu; + } + public getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection { + const menuSubsectionsBySection = {} as MenuSubsectionsBySection; + if (_.isUndefined(docAgnosticFormat)) { + return menuSubsectionsBySection; + } - const docSections = _.keys(this.sections); - _.each(docSections, sectionName => { - const docSection = docAgnosticFormat[sectionName]; - if (_.isUndefined(docSection)) { - return; // no-op - } + const docSections = _.keys(this.sections); + _.each(docSections, sectionName => { + const docSection = docAgnosticFormat[sectionName]; + if (_.isUndefined(docSection)) { + return; // no-op + } - if (!_.isUndefined(this.sections.types) && sectionName === this.sections.types) { - const sortedTypesNames = _.sortBy(docSection.types, 'name'); - const typeNames = _.map(sortedTypesNames, t => t.name); - menuSubsectionsBySection[sectionName] = typeNames; - } else { - let eventNames: string[] = []; - if (!_.isUndefined(docSection.events)) { - const sortedEventNames = _.sortBy(docSection.events, 'name'); - eventNames = _.map(sortedEventNames, m => m.name); - } - const sortedMethodNames = _.sortBy(docSection.methods, 'name'); - const methodNames = _.map(sortedMethodNames, m => m.name); - menuSubsectionsBySection[sectionName] = [...methodNames, ...eventNames]; - } - }); - return menuSubsectionsBySection; - } - public getTypeDefinitionsByName(docAgnosticFormat: DocAgnosticFormat) { - if (_.isUndefined(this.sections.types)) { - return {}; - } + if (!_.isUndefined(this.sections.types) && sectionName === this.sections.types) { + const sortedTypesNames = _.sortBy(docSection.types, 'name'); + const typeNames = _.map(sortedTypesNames, t => t.name); + menuSubsectionsBySection[sectionName] = typeNames; + } else { + let eventNames: string[] = []; + if (!_.isUndefined(docSection.events)) { + const sortedEventNames = _.sortBy(docSection.events, 'name'); + eventNames = _.map(sortedEventNames, m => m.name); + } + const sortedMethodNames = _.sortBy(docSection.methods, 'name'); + const methodNames = _.map(sortedMethodNames, m => m.name); + menuSubsectionsBySection[sectionName] = [...methodNames, ...eventNames]; + } + }); + return menuSubsectionsBySection; + } + public getTypeDefinitionsByName(docAgnosticFormat: DocAgnosticFormat) { + if (_.isUndefined(this.sections.types)) { + return {}; + } - const typeDocSection = docAgnosticFormat[this.sections.types]; - const typeDefinitionByName = _.keyBy(typeDocSection.types, 'name'); - return typeDefinitionByName; - } - public isVisibleConstructor(sectionName: string): boolean { - return _.includes(this._docsInfo.visibleConstructors, sectionName); - } - public convertToDocAgnosticFormat(docObj: DoxityDocObj | TypeDocNode): DocAgnosticFormat { - return this._docsInfo.convertToDocAgnosticFormatFn(docObj, this); - } + const typeDocSection = docAgnosticFormat[this.sections.types]; + const typeDefinitionByName = _.keyBy(typeDocSection.types, 'name'); + return typeDefinitionByName; + } + public isVisibleConstructor(sectionName: string): boolean { + return _.includes(this._docsInfo.visibleConstructors, sectionName); + } + public convertToDocAgnosticFormat(docObj: DoxityDocObj | TypeDocNode): DocAgnosticFormat { + return this._docsInfo.convertToDocAgnosticFormatFn(docObj, this); + } } diff --git a/packages/website/ts/pages/documentation/documentation.tsx b/packages/website/ts/pages/documentation/documentation.tsx index 0fc41d775..2315847ad 100644 --- a/packages/website/ts/pages/documentation/documentation.tsx +++ b/packages/website/ts/pages/documentation/documentation.tsx @@ -19,17 +19,17 @@ import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; import { SectionHeader } from 'ts/pages/shared/section_header'; import { Dispatcher } from 'ts/redux/dispatcher'; import { - AddressByContractName, - DocAgnosticFormat, - DoxityDocObj, - EtherscanLinkSuffixes, - Event, - Networks, - Property, - SolidityMethod, - Styles, - TypeDefinitionByName, - TypescriptMethod, + AddressByContractName, + DocAgnosticFormat, + DoxityDocObj, + EtherscanLinkSuffixes, + Event, + Networks, + Property, + SolidityMethod, + Styles, + TypeDefinitionByName, + TypescriptMethod, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; @@ -40,340 +40,340 @@ import { utils } from 'ts/utils/utils'; const SCROLL_TOP_ID = 'docsScrollTop'; const networkNameToColor: { [network: string]: string } = { - [Networks.kovan]: colors.purple, - [Networks.ropsten]: colors.red, - [Networks.mainnet]: colors.turquois, + [Networks.kovan]: colors.purple, + [Networks.ropsten]: colors.red, + [Networks.mainnet]: colors.turquois, }; export interface DocumentationAllProps { - source: string; - location: Location; - dispatcher: Dispatcher; - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; + source: string; + location: Location; + dispatcher: Dispatcher; + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; } interface DocumentationState { - docAgnosticFormat?: DocAgnosticFormat; + docAgnosticFormat?: DocAgnosticFormat; } const styles: Styles = { - mainContainers: { - position: 'absolute', - top: 1, - left: 0, - bottom: 0, - right: 0, - overflowZ: 'hidden', - overflowY: 'scroll', - minHeight: 'calc(100vh - 1px)', - WebkitOverflowScrolling: 'touch', - }, - menuContainer: { - borderColor: colors.grey300, - maxWidth: 330, - marginLeft: 20, - }, + mainContainers: { + position: 'absolute', + top: 1, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: 'calc(100vh - 1px)', + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, }; export class Documentation extends React.Component<DocumentationAllProps, DocumentationState> { - constructor(props: DocumentationAllProps) { - super(props); - this.state = { - docAgnosticFormat: undefined, - }; - } - public componentWillMount() { - const pathName = this.props.location.pathname; - const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1); - const versions = findVersions(lastSegment); - const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined; - // tslint:disable-next-line:no-floating-promises - this._fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); - } - public render() { - const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) - ? {} - : this.props.docsInfo.getMenuSubsectionsBySection(this.state.docAgnosticFormat); - return ( - <div> - <DocumentTitle title={`${this.props.docsInfo.displayName} Documentation`} /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - docsVersion={this.props.docsVersion} - availableDocVersions={this.props.availableDocVersions} - menu={this.props.docsInfo.getMenu(this.props.docsVersion)} - menuSubsectionsBySection={menuSubsectionsBySection} - shouldFullWidth={true} - docsInfo={this.props.docsInfo} - /> - {_.isUndefined(this.state.docAgnosticFormat) ? ( - <div className="col col-12" style={styles.mainContainers}> - <div - className="relative sm-px2 sm-pt2 sm-m1" - style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} - > - <div className="center pb2"> - <CircularProgress size={40} thickness={5} /> - </div> - <div className="center pt2" style={{ paddingBottom: 11 }}> - Loading documentation... - </div> - </div> - </div> - ) : ( - <div className="mx-auto flex" style={{ color: colors.grey800, height: 43 }}> - <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide"> - <div - className="border-right absolute" - style={{ ...styles.menuContainer, ...styles.mainContainers }} - > - <NestedSidebarMenu - selectedVersion={this.props.docsVersion} - versions={this.props.availableDocVersions} - topLevelMenu={this.props.docsInfo.getMenu(this.props.docsVersion)} - menuSubsectionsBySection={menuSubsectionsBySection} - docPath={this.props.docsInfo.websitePath} - /> - </div> - </div> - <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12"> - <div id="documentation" style={styles.mainContainers} className="absolute"> - <div id={SCROLL_TOP_ID} /> - <h1 className="md-pl2 sm-pl3"> - <a href={this.props.docsInfo.packageUrl} target="_blank"> - {this.props.docsInfo.displayName} - </a> - </h1> - {this._renderDocumentation()} - </div> - </div> - </div> - )} - </div> - ); - } - private _renderDocumentation(): React.ReactNode { - const subMenus = _.values(this.props.docsInfo.getMenu()); - const orderedSectionNames = _.flatten(subMenus); + constructor(props: DocumentationAllProps) { + super(props); + this.state = { + docAgnosticFormat: undefined, + }; + } + public componentWillMount() { + const pathName = this.props.location.pathname; + const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1); + const versions = findVersions(lastSegment); + const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined; + // tslint:disable-next-line:no-floating-promises + this._fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); + } + public render() { + const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) + ? {} + : this.props.docsInfo.getMenuSubsectionsBySection(this.state.docAgnosticFormat); + return ( + <div> + <DocumentTitle title={`${this.props.docsInfo.displayName} Documentation`} /> + <TopBar + blockchainIsLoaded={false} + location={this.props.location} + docsVersion={this.props.docsVersion} + availableDocVersions={this.props.availableDocVersions} + menu={this.props.docsInfo.getMenu(this.props.docsVersion)} + menuSubsectionsBySection={menuSubsectionsBySection} + shouldFullWidth={true} + docsInfo={this.props.docsInfo} + /> + {_.isUndefined(this.state.docAgnosticFormat) ? ( + <div className="col col-12" style={styles.mainContainers}> + <div + className="relative sm-px2 sm-pt2 sm-m1" + style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} + > + <div className="center pb2"> + <CircularProgress size={40} thickness={5} /> + </div> + <div className="center pt2" style={{ paddingBottom: 11 }}> + Loading documentation... + </div> + </div> + </div> + ) : ( + <div className="mx-auto flex" style={{ color: colors.grey800, height: 43 }}> + <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide"> + <div + className="border-right absolute" + style={{ ...styles.menuContainer, ...styles.mainContainers }} + > + <NestedSidebarMenu + selectedVersion={this.props.docsVersion} + versions={this.props.availableDocVersions} + topLevelMenu={this.props.docsInfo.getMenu(this.props.docsVersion)} + menuSubsectionsBySection={menuSubsectionsBySection} + docPath={this.props.docsInfo.websitePath} + /> + </div> + </div> + <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12"> + <div id="documentation" style={styles.mainContainers} className="absolute"> + <div id={SCROLL_TOP_ID} /> + <h1 className="md-pl2 sm-pl3"> + <a href={this.props.docsInfo.packageUrl} target="_blank"> + {this.props.docsInfo.displayName} + </a> + </h1> + {this._renderDocumentation()} + </div> + </div> + </div> + )} + </div> + ); + } + private _renderDocumentation(): React.ReactNode { + const subMenus = _.values(this.props.docsInfo.getMenu()); + const orderedSectionNames = _.flatten(subMenus); - const typeDefinitionByName = this.props.docsInfo.getTypeDefinitionsByName(this.state.docAgnosticFormat); - const renderedSections = _.map(orderedSectionNames, this._renderSection.bind(this, typeDefinitionByName)); + const typeDefinitionByName = this.props.docsInfo.getTypeDefinitionsByName(this.state.docAgnosticFormat); + const renderedSections = _.map(orderedSectionNames, this._renderSection.bind(this, typeDefinitionByName)); - return renderedSections; - } - private _renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode { - const markdownFileIfExists = this.props.docsInfo.sectionNameToMarkdown[sectionName]; - if (!_.isUndefined(markdownFileIfExists)) { - return ( - <MarkdownSection - key={`markdown-section-${sectionName}`} - sectionName={sectionName} - markdownContent={markdownFileIfExists} - /> - ); - } + return renderedSections; + } + private _renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode { + const markdownFileIfExists = this.props.docsInfo.sectionNameToMarkdown[sectionName]; + if (!_.isUndefined(markdownFileIfExists)) { + return ( + <MarkdownSection + key={`markdown-section-${sectionName}`} + sectionName={sectionName} + markdownContent={markdownFileIfExists} + /> + ); + } - const docSection = this.state.docAgnosticFormat[sectionName]; - if (_.isUndefined(docSection)) { - return null; - } + const docSection = this.state.docAgnosticFormat[sectionName]; + if (_.isUndefined(docSection)) { + return null; + } - const sortedTypes = _.sortBy(docSection.types, 'name'); - const typeDefs = _.map(sortedTypes, customType => { - return ( - <TypeDefinition - sectionName={sectionName} - key={`type-${customType.name}`} - customType={customType} - docsInfo={this.props.docsInfo} - /> - ); - }); + const sortedTypes = _.sortBy(docSection.types, 'name'); + const typeDefs = _.map(sortedTypes, customType => { + return ( + <TypeDefinition + sectionName={sectionName} + key={`type-${customType.name}`} + customType={customType} + docsInfo={this.props.docsInfo} + /> + ); + }); - const sortedProperties = _.sortBy(docSection.properties, 'name'); - const propertyDefs = _.map(sortedProperties, this._renderProperty.bind(this, sectionName)); + const sortedProperties = _.sortBy(docSection.properties, 'name'); + const propertyDefs = _.map(sortedProperties, this._renderProperty.bind(this, sectionName)); - const sortedMethods = _.sortBy(docSection.methods, 'name'); - const methodDefs = _.map(sortedMethods, method => { - const isConstructor = false; - return this._renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName); - }); + const sortedMethods = _.sortBy(docSection.methods, 'name'); + const methodDefs = _.map(sortedMethods, method => { + const isConstructor = false; + return this._renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName); + }); - const sortedEvents = _.sortBy(docSection.events, 'name'); - const eventDefs = _.map(sortedEvents, (event: Event, i: number) => { - return ( - <EventDefinition - key={`event-${event.name}-${i}`} - event={event} - sectionName={sectionName} - docsInfo={this.props.docsInfo} - /> - ); - }); - return ( - <div key={`section-${sectionName}`} className="py2 pr3 md-pl2 sm-pl3"> - <div className="flex"> - <div style={{ marginRight: 7 }}> - <SectionHeader sectionName={sectionName} /> - </div> - {this._renderNetworkBadgesIfExists(sectionName)} - </div> - {docSection.comment && <Comment comment={docSection.comment} />} - {docSection.constructors.length > 0 && - this.props.docsInfo.isVisibleConstructor(sectionName) && ( - <div> - <h2 className="thin">Constructor</h2> - {this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)} - </div> - )} - {docSection.properties.length > 0 && ( - <div> - <h2 className="thin">Properties</h2> - <div>{propertyDefs}</div> - </div> - )} - {docSection.methods.length > 0 && ( - <div> - <h2 className="thin">Methods</h2> - <div>{methodDefs}</div> - </div> - )} - {!_.isUndefined(docSection.events) && - docSection.events.length > 0 && ( - <div> - <h2 className="thin">Events</h2> - <div>{eventDefs}</div> - </div> - )} - {!_.isUndefined(typeDefs) && - typeDefs.length > 0 && ( - <div> - <div>{typeDefs}</div> - </div> - )} - </div> - ); - } - private _renderNetworkBadgesIfExists(sectionName: string) { - const networkToAddressByContractName = configs.CONTRACT_ADDRESS[this.props.docsVersion]; - const badges = _.map( - networkToAddressByContractName, - (addressByContractName: AddressByContractName, networkName: string) => { - const contractAddress = addressByContractName[sectionName]; - if (_.isUndefined(contractAddress)) { - return null; - } - const linkIfExists = utils.getEtherScanLinkIfExists( - contractAddress, - constants.NETWORK_ID_BY_NAME[networkName], - EtherscanLinkSuffixes.Address, - ); - return ( - <a - key={`badge-${networkName}-${sectionName}`} - href={linkIfExists} - target="_blank" - style={{ color: colors.white, textDecoration: 'none' }} - > - <Badge title={networkName} backgroundColor={networkNameToColor[networkName]} /> - </a> - ); - }, - ); - return badges; - } - private _renderConstructors( - constructors: SolidityMethod[] | TypescriptMethod[], - sectionName: string, - typeDefinitionByName: TypeDefinitionByName, - ): React.ReactNode { - const constructorDefs = _.map(constructors, constructor => { - return this._renderMethodBlocks(constructor, sectionName, constructor.isConstructor, typeDefinitionByName); - }); - return <div>{constructorDefs}</div>; - } - private _renderProperty(sectionName: string, property: Property): React.ReactNode { - return ( - <div key={`property-${property.name}-${property.type.name}`} className="pb3"> - <code className="hljs"> - {property.name}: - <Type type={property.type} sectionName={sectionName} docsInfo={this.props.docsInfo} /> - </code> - {property.source && ( - <SourceLink - version={this.props.docsVersion} - source={property.source} - baseUrl={this.props.docsInfo.packageUrl} - subPackageName={this.props.docsInfo.subPackageName} - /> - )} - {property.comment && <Comment comment={property.comment} className="py2" />} - </div> - ); - } - private _renderMethodBlocks( - method: SolidityMethod | TypescriptMethod, - sectionName: string, - isConstructor: boolean, - typeDefinitionByName: TypeDefinitionByName, - ): React.ReactNode { - return ( - <MethodBlock - key={`method-${method.name}-${sectionName}`} - sectionName={sectionName} - method={method} - typeDefinitionByName={typeDefinitionByName} - libraryVersion={this.props.docsVersion} - docsInfo={this.props.docsInfo} - /> - ); - } - private _scrollToHash(): void { - const hashWithPrefix = this.props.location.hash; - let hash = hashWithPrefix.slice(1); - if (_.isEmpty(hash)) { - hash = SCROLL_TOP_ID; // scroll to the top - } + const sortedEvents = _.sortBy(docSection.events, 'name'); + const eventDefs = _.map(sortedEvents, (event: Event, i: number) => { + return ( + <EventDefinition + key={`event-${event.name}-${i}`} + event={event} + sectionName={sectionName} + docsInfo={this.props.docsInfo} + /> + ); + }); + return ( + <div key={`section-${sectionName}`} className="py2 pr3 md-pl2 sm-pl3"> + <div className="flex"> + <div style={{ marginRight: 7 }}> + <SectionHeader sectionName={sectionName} /> + </div> + {this._renderNetworkBadgesIfExists(sectionName)} + </div> + {docSection.comment && <Comment comment={docSection.comment} />} + {docSection.constructors.length > 0 && + this.props.docsInfo.isVisibleConstructor(sectionName) && ( + <div> + <h2 className="thin">Constructor</h2> + {this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)} + </div> + )} + {docSection.properties.length > 0 && ( + <div> + <h2 className="thin">Properties</h2> + <div>{propertyDefs}</div> + </div> + )} + {docSection.methods.length > 0 && ( + <div> + <h2 className="thin">Methods</h2> + <div>{methodDefs}</div> + </div> + )} + {!_.isUndefined(docSection.events) && + docSection.events.length > 0 && ( + <div> + <h2 className="thin">Events</h2> + <div>{eventDefs}</div> + </div> + )} + {!_.isUndefined(typeDefs) && + typeDefs.length > 0 && ( + <div> + <div>{typeDefs}</div> + </div> + )} + </div> + ); + } + private _renderNetworkBadgesIfExists(sectionName: string) { + const networkToAddressByContractName = configs.CONTRACT_ADDRESS[this.props.docsVersion]; + const badges = _.map( + networkToAddressByContractName, + (addressByContractName: AddressByContractName, networkName: string) => { + const contractAddress = addressByContractName[sectionName]; + if (_.isUndefined(contractAddress)) { + return null; + } + const linkIfExists = utils.getEtherScanLinkIfExists( + contractAddress, + constants.NETWORK_ID_BY_NAME[networkName], + EtherscanLinkSuffixes.Address, + ); + return ( + <a + key={`badge-${networkName}-${sectionName}`} + href={linkIfExists} + target="_blank" + style={{ color: colors.white, textDecoration: 'none' }} + > + <Badge title={networkName} backgroundColor={networkNameToColor[networkName]} /> + </a> + ); + }, + ); + return badges; + } + private _renderConstructors( + constructors: SolidityMethod[] | TypescriptMethod[], + sectionName: string, + typeDefinitionByName: TypeDefinitionByName, + ): React.ReactNode { + const constructorDefs = _.map(constructors, constructor => { + return this._renderMethodBlocks(constructor, sectionName, constructor.isConstructor, typeDefinitionByName); + }); + return <div>{constructorDefs}</div>; + } + private _renderProperty(sectionName: string, property: Property): React.ReactNode { + return ( + <div key={`property-${property.name}-${property.type.name}`} className="pb3"> + <code className="hljs"> + {property.name}: + <Type type={property.type} sectionName={sectionName} docsInfo={this.props.docsInfo} /> + </code> + {property.source && ( + <SourceLink + version={this.props.docsVersion} + source={property.source} + baseUrl={this.props.docsInfo.packageUrl} + subPackageName={this.props.docsInfo.subPackageName} + /> + )} + {property.comment && <Comment comment={property.comment} className="py2" />} + </div> + ); + } + private _renderMethodBlocks( + method: SolidityMethod | TypescriptMethod, + sectionName: string, + isConstructor: boolean, + typeDefinitionByName: TypeDefinitionByName, + ): React.ReactNode { + return ( + <MethodBlock + key={`method-${method.name}-${sectionName}`} + sectionName={sectionName} + method={method} + typeDefinitionByName={typeDefinitionByName} + libraryVersion={this.props.docsVersion} + docsInfo={this.props.docsInfo} + /> + ); + } + private _scrollToHash(): void { + const hashWithPrefix = this.props.location.hash; + let hash = hashWithPrefix.slice(1); + if (_.isEmpty(hash)) { + hash = SCROLL_TOP_ID; // scroll to the top + } - scroller.scrollTo(hash, { - duration: 0, - offset: 0, - containerId: 'documentation', - }); - } - private async _fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> { - const versionToFileName = await docUtils.getVersionToFileNameAsync(this.props.docsInfo.docsJsonRoot); - const versions = _.keys(versionToFileName); - this.props.dispatcher.updateAvailableDocVersions(versions); - const sortedVersions = semverSort.desc(versions); - const latestVersion = sortedVersions[0]; + scroller.scrollTo(hash, { + duration: 0, + offset: 0, + containerId: 'documentation', + }); + } + private async _fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> { + const versionToFileName = await docUtils.getVersionToFileNameAsync(this.props.docsInfo.docsJsonRoot); + const versions = _.keys(versionToFileName); + this.props.dispatcher.updateAvailableDocVersions(versions); + const sortedVersions = semverSort.desc(versions); + const latestVersion = sortedVersions[0]; - let versionToFetch = latestVersion; - if (!_.isUndefined(preferredVersionIfExists)) { - const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists]; - if (!_.isUndefined(preferredVersionFileNameIfExists)) { - versionToFetch = preferredVersionIfExists; - } - } - this.props.dispatcher.updateCurrentDocsVersion(versionToFetch); + let versionToFetch = latestVersion; + if (!_.isUndefined(preferredVersionIfExists)) { + const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists]; + if (!_.isUndefined(preferredVersionFileNameIfExists)) { + versionToFetch = preferredVersionIfExists; + } + } + this.props.dispatcher.updateCurrentDocsVersion(versionToFetch); - const versionFileNameToFetch = versionToFileName[versionToFetch]; - const versionDocObj = await docUtils.getJSONDocFileAsync( - versionFileNameToFetch, - this.props.docsInfo.docsJsonRoot, - ); - const docAgnosticFormat = this.props.docsInfo.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj); + const versionFileNameToFetch = versionToFileName[versionToFetch]; + const versionDocObj = await docUtils.getJSONDocFileAsync( + versionFileNameToFetch, + this.props.docsInfo.docsJsonRoot, + ); + const docAgnosticFormat = this.props.docsInfo.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj); - this.setState( - { - docAgnosticFormat, - }, - () => { - this._scrollToHash(); - }, - ); - } + this.setState( + { + docAgnosticFormat, + }, + () => { + this._scrollToHash(); + }, + ); + } } diff --git a/packages/website/ts/pages/documentation/enum.tsx b/packages/website/ts/pages/documentation/enum.tsx index cfcc7f8d7..7dfdee771 100644 --- a/packages/website/ts/pages/documentation/enum.tsx +++ b/packages/website/ts/pages/documentation/enum.tsx @@ -3,20 +3,20 @@ import * as React from 'react'; import { EnumValue } from 'ts/types'; interface EnumProps { - values: EnumValue[]; + values: EnumValue[]; } export function Enum(props: EnumProps) { - const values = _.map(props.values, (value, i) => { - const defaultValueIfAny = !_.isUndefined(value.defaultValue) ? ` = ${value.defaultValue}` : ''; - return `\n\t${value.name}${defaultValueIfAny},`; - }); - return ( - <span> - {`{`} - {values} - <br /> - {`}`} - </span> - ); + const values = _.map(props.values, (value, i) => { + const defaultValueIfAny = !_.isUndefined(value.defaultValue) ? ` = ${value.defaultValue}` : ''; + return `\n\t${value.name}${defaultValueIfAny},`; + }); + return ( + <span> + {`{`} + {values} + <br /> + {`}`} + </span> + ); } diff --git a/packages/website/ts/pages/documentation/event_definition.tsx b/packages/website/ts/pages/documentation/event_definition.tsx index 9274ae512..0e53e38e7 100644 --- a/packages/website/ts/pages/documentation/event_definition.tsx +++ b/packages/website/ts/pages/documentation/event_definition.tsx @@ -7,76 +7,76 @@ import { Event, EventArg, HeaderSizes } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface EventDefinitionProps { - event: Event; - sectionName: string; - docsInfo: DocsInfo; + event: Event; + sectionName: string; + docsInfo: DocsInfo; } interface EventDefinitionState { - shouldShowAnchor: boolean; + shouldShowAnchor: boolean; } export class EventDefinition extends React.Component<EventDefinitionProps, EventDefinitionState> { - constructor(props: EventDefinitionProps) { - super(props); - this.state = { - shouldShowAnchor: false, - }; - } - public render() { - const event = this.props.event; - return ( - <div - id={`${this.props.sectionName}-${event.name}`} - className="pb2" - style={{ overflow: 'hidden', width: '100%' }} - onMouseOver={this._setAnchorVisibility.bind(this, true)} - onMouseOut={this._setAnchorVisibility.bind(this, false)} - > - <AnchorTitle - headerSize={HeaderSizes.H3} - title={`Event ${event.name}`} - id={event.name} - shouldShowAnchor={this.state.shouldShowAnchor} - /> - <div style={{ fontSize: 16 }}> - <pre> - <code className="hljs">{this._renderEventCode()}</code> - </pre> - </div> - </div> - ); - } - private _renderEventCode() { - const indexed = <span style={{ color: colors.green }}> indexed</span>; - const eventArgs = _.map(this.props.event.eventArgs, (eventArg: EventArg) => { - const type = ( - <Type type={eventArg.type} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} /> - ); - return ( - <span key={`eventArg-${eventArg.name}`}> - {eventArg.name} - {eventArg.isIndexed ? indexed : ''}: {type}, - </span> - ); - }); - const argList = _.reduce(eventArgs, (prev: React.ReactNode, curr: React.ReactNode) => { - return [prev, '\n\t', curr]; - }); - return ( - <span> - {`{`} - <br /> - {'\t'} - {argList} - <br /> - {`}`} - </span> - ); - } - private _setAnchorVisibility(shouldShowAnchor: boolean) { - this.setState({ - shouldShowAnchor, - }); - } + constructor(props: EventDefinitionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const event = this.props.event; + return ( + <div + id={`${this.props.sectionName}-${event.name}`} + className="pb2" + style={{ overflow: 'hidden', width: '100%' }} + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <AnchorTitle + headerSize={HeaderSizes.H3} + title={`Event ${event.name}`} + id={event.name} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + <div style={{ fontSize: 16 }}> + <pre> + <code className="hljs">{this._renderEventCode()}</code> + </pre> + </div> + </div> + ); + } + private _renderEventCode() { + const indexed = <span style={{ color: colors.green }}> indexed</span>; + const eventArgs = _.map(this.props.event.eventArgs, (eventArg: EventArg) => { + const type = ( + <Type type={eventArg.type} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} /> + ); + return ( + <span key={`eventArg-${eventArg.name}`}> + {eventArg.name} + {eventArg.isIndexed ? indexed : ''}: {type}, + </span> + ); + }); + const argList = _.reduce(eventArgs, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '\n\t', curr]; + }); + return ( + <span> + {`{`} + <br /> + {'\t'} + {argList} + <br /> + {`}`} + </span> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } } diff --git a/packages/website/ts/pages/documentation/interface.tsx b/packages/website/ts/pages/documentation/interface.tsx index ee07a2c50..16a772125 100644 --- a/packages/website/ts/pages/documentation/interface.tsx +++ b/packages/website/ts/pages/documentation/interface.tsx @@ -6,56 +6,56 @@ import { Type } from 'ts/pages/documentation/type'; import { CustomType, TypeDocTypes } from 'ts/types'; interface InterfaceProps { - type: CustomType; - sectionName: string; - docsInfo: DocsInfo; + type: CustomType; + sectionName: string; + docsInfo: DocsInfo; } export function Interface(props: InterfaceProps) { - const type = props.type; - const properties = _.map(type.children, property => { - return ( - <span key={`property-${property.name}-${property.type}-${type.name}`}> - {property.name}:{' '} - {property.type.typeDocType !== TypeDocTypes.Reflection ? ( - <Type type={property.type} sectionName={props.sectionName} docsInfo={props.docsInfo} /> - ) : ( - <MethodSignature - method={property.type.method} - sectionName={props.sectionName} - shouldHideMethodName={true} - shouldUseArrowSyntax={true} - docsInfo={props.docsInfo} - /> - )}, - </span> - ); - }); - const hasIndexSignature = !_.isUndefined(type.indexSignature); - if (hasIndexSignature) { - const is = type.indexSignature; - const param = ( - <span key={`indexSigParams-${is.keyName}-${is.keyType}-${type.name}`}> - {is.keyName}: <Type type={is.keyType} sectionName={props.sectionName} docsInfo={props.docsInfo} /> - </span> - ); - properties.push( - <span key={`indexSignature-${type.name}-${is.keyType.name}`}> - [{param}]: {is.valueName}, - </span>, - ); - } - const propertyList = _.reduce(properties, (prev: React.ReactNode, curr: React.ReactNode) => { - return [prev, '\n\t', curr]; - }); - return ( - <span> - {`{`} - <br /> - {'\t'} - {propertyList} - <br /> - {`}`} - </span> - ); + const type = props.type; + const properties = _.map(type.children, property => { + return ( + <span key={`property-${property.name}-${property.type}-${type.name}`}> + {property.name}:{' '} + {property.type.typeDocType !== TypeDocTypes.Reflection ? ( + <Type type={property.type} sectionName={props.sectionName} docsInfo={props.docsInfo} /> + ) : ( + <MethodSignature + method={property.type.method} + sectionName={props.sectionName} + shouldHideMethodName={true} + shouldUseArrowSyntax={true} + docsInfo={props.docsInfo} + /> + )}, + </span> + ); + }); + const hasIndexSignature = !_.isUndefined(type.indexSignature); + if (hasIndexSignature) { + const is = type.indexSignature; + const param = ( + <span key={`indexSigParams-${is.keyName}-${is.keyType}-${type.name}`}> + {is.keyName}: <Type type={is.keyType} sectionName={props.sectionName} docsInfo={props.docsInfo} /> + </span> + ); + properties.push( + <span key={`indexSignature-${type.name}-${is.keyType.name}`}> + [{param}]: {is.valueName}, + </span>, + ); + } + const propertyList = _.reduce(properties, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '\n\t', curr]; + }); + return ( + <span> + {`{`} + <br /> + {'\t'} + {propertyList} + <br /> + {`}`} + </span> + ); } diff --git a/packages/website/ts/pages/documentation/method_block.tsx b/packages/website/ts/pages/documentation/method_block.tsx index fb03cf5be..dfde5931b 100644 --- a/packages/website/ts/pages/documentation/method_block.tsx +++ b/packages/website/ts/pages/documentation/method_block.tsx @@ -10,133 +10,133 @@ import { colors } from 'ts/utils/colors'; import { typeDocUtils } from 'ts/utils/typedoc_utils'; interface MethodBlockProps { - method: SolidityMethod | TypescriptMethod; - sectionName: string; - libraryVersion: string; - typeDefinitionByName: TypeDefinitionByName; - docsInfo: DocsInfo; + method: SolidityMethod | TypescriptMethod; + sectionName: string; + libraryVersion: string; + typeDefinitionByName: TypeDefinitionByName; + docsInfo: DocsInfo; } interface MethodBlockState { - shouldShowAnchor: boolean; + shouldShowAnchor: boolean; } const styles: Styles = { - chip: { - fontSize: 13, - backgroundColor: colors.lightBlueA700, - color: colors.white, - height: 11, - borderRadius: 14, - marginTop: 19, - lineHeight: 0.8, - }, + chip: { + fontSize: 13, + backgroundColor: colors.lightBlueA700, + color: colors.white, + height: 11, + borderRadius: 14, + marginTop: 19, + lineHeight: 0.8, + }, }; export class MethodBlock extends React.Component<MethodBlockProps, MethodBlockState> { - constructor(props: MethodBlockProps) { - super(props); - this.state = { - shouldShowAnchor: false, - }; - } - public render() { - const method = this.props.method; - if (typeDocUtils.isPrivateOrProtectedProperty(method.name)) { - return null; - } + constructor(props: MethodBlockProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const method = this.props.method; + if (typeDocUtils.isPrivateOrProtectedProperty(method.name)) { + return null; + } - return ( - <div - id={`${this.props.sectionName}-${method.name}`} - style={{ overflow: 'hidden', width: '100%' }} - className="pb4" - onMouseOver={this._setAnchorVisibility.bind(this, true)} - onMouseOut={this._setAnchorVisibility.bind(this, false)} - > - {!method.isConstructor && ( - <div className="flex"> - {(method as TypescriptMethod).isStatic && this._renderChip('Static')} - {(method as SolidityMethod).isConstant && this._renderChip('Constant')} - {(method as SolidityMethod).isPayable && this._renderChip('Payable')} - <AnchorTitle - headerSize={HeaderSizes.H3} - title={method.name} - id={`${this.props.sectionName}-${method.name}`} - shouldShowAnchor={this.state.shouldShowAnchor} - /> - </div> - )} - <code className="hljs"> - <MethodSignature - method={method} - sectionName={this.props.sectionName} - typeDefinitionByName={this.props.typeDefinitionByName} - docsInfo={this.props.docsInfo} - /> - </code> - {(method as TypescriptMethod).source && ( - <SourceLink - version={this.props.libraryVersion} - source={(method as TypescriptMethod).source} - baseUrl={this.props.docsInfo.packageUrl} - subPackageName={this.props.docsInfo.subPackageName} - /> - )} - {method.comment && <Comment comment={method.comment} className="py2" />} - {method.parameters && - !_.isEmpty(method.parameters) && ( - <div> - <h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}> - ARGUMENTS - </h4> - {this._renderParameterDescriptions(method.parameters)} - </div> - )} - {method.returnComment && ( - <div className="pt1 comment"> - <h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}> - RETURNS - </h4> - <Comment comment={method.returnComment} /> - </div> - )} - </div> - ); - } - private _renderChip(text: string) { - return ( - <div className="p1 mr1" style={styles.chip}> - {text} - </div> - ); - } - private _renderParameterDescriptions(parameters: Parameter[]) { - const descriptions = _.map(parameters, parameter => { - const isOptional = parameter.isOptional; - return ( - <div - key={`param-description-${parameter.name}`} - className="flex pb1 mb2" - style={{ borderBottom: '1px solid #f0f4f7' }} - > - <div className="pl2 col lg-col-4 md-col-4 sm-col-12 col-12"> - <div className="bold">{parameter.name}</div> - <div className="pt1" style={{ color: colors.grey, fontSize: 14 }}> - {isOptional && 'optional'} - </div> - </div> - <div className="col lg-col-8 md-col-8 sm-col-12 col-12"> - {parameter.comment && <Comment comment={parameter.comment} />} - </div> - </div> - ); - }); - return descriptions; - } - private _setAnchorVisibility(shouldShowAnchor: boolean) { - this.setState({ - shouldShowAnchor, - }); - } + return ( + <div + id={`${this.props.sectionName}-${method.name}`} + style={{ overflow: 'hidden', width: '100%' }} + className="pb4" + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + {!method.isConstructor && ( + <div className="flex"> + {(method as TypescriptMethod).isStatic && this._renderChip('Static')} + {(method as SolidityMethod).isConstant && this._renderChip('Constant')} + {(method as SolidityMethod).isPayable && this._renderChip('Payable')} + <AnchorTitle + headerSize={HeaderSizes.H3} + title={method.name} + id={`${this.props.sectionName}-${method.name}`} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + </div> + )} + <code className="hljs"> + <MethodSignature + method={method} + sectionName={this.props.sectionName} + typeDefinitionByName={this.props.typeDefinitionByName} + docsInfo={this.props.docsInfo} + /> + </code> + {(method as TypescriptMethod).source && ( + <SourceLink + version={this.props.libraryVersion} + source={(method as TypescriptMethod).source} + baseUrl={this.props.docsInfo.packageUrl} + subPackageName={this.props.docsInfo.subPackageName} + /> + )} + {method.comment && <Comment comment={method.comment} className="py2" />} + {method.parameters && + !_.isEmpty(method.parameters) && ( + <div> + <h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}> + ARGUMENTS + </h4> + {this._renderParameterDescriptions(method.parameters)} + </div> + )} + {method.returnComment && ( + <div className="pt1 comment"> + <h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}> + RETURNS + </h4> + <Comment comment={method.returnComment} /> + </div> + )} + </div> + ); + } + private _renderChip(text: string) { + return ( + <div className="p1 mr1" style={styles.chip}> + {text} + </div> + ); + } + private _renderParameterDescriptions(parameters: Parameter[]) { + const descriptions = _.map(parameters, parameter => { + const isOptional = parameter.isOptional; + return ( + <div + key={`param-description-${parameter.name}`} + className="flex pb1 mb2" + style={{ borderBottom: '1px solid #f0f4f7' }} + > + <div className="pl2 col lg-col-4 md-col-4 sm-col-12 col-12"> + <div className="bold">{parameter.name}</div> + <div className="pt1" style={{ color: colors.grey, fontSize: 14 }}> + {isOptional && 'optional'} + </div> + </div> + <div className="col lg-col-8 md-col-8 sm-col-12 col-12"> + {parameter.comment && <Comment comment={parameter.comment} />} + </div> + </div> + ); + }); + return descriptions; + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } } diff --git a/packages/website/ts/pages/documentation/method_signature.tsx b/packages/website/ts/pages/documentation/method_signature.tsx index 7c6bf96d2..041dcd093 100644 --- a/packages/website/ts/pages/documentation/method_signature.tsx +++ b/packages/website/ts/pages/documentation/method_signature.tsx @@ -6,94 +6,94 @@ import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } fro import { constants } from 'ts/utils/constants'; interface MethodSignatureProps { - method: TypescriptMethod | SolidityMethod; - sectionName: string; - shouldHideMethodName?: boolean; - shouldUseArrowSyntax?: boolean; - typeDefinitionByName?: TypeDefinitionByName; - docsInfo: DocsInfo; + method: TypescriptMethod | SolidityMethod; + sectionName: string; + shouldHideMethodName?: boolean; + shouldUseArrowSyntax?: boolean; + typeDefinitionByName?: TypeDefinitionByName; + docsInfo: DocsInfo; } const defaultProps = { - shouldHideMethodName: false, - shouldUseArrowSyntax: false, + shouldHideMethodName: false, + shouldUseArrowSyntax: false, }; export const MethodSignature: React.SFC<MethodSignatureProps> = (props: MethodSignatureProps) => { - const sectionName = constants.TYPES_SECTION_NAME; - const parameters = renderParameters(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); - const paramString = _.reduce(parameters, (prev: React.ReactNode, curr: React.ReactNode) => { - return [prev, ', ', curr]; - }); - const methodName = props.shouldHideMethodName ? '' : props.method.name; - const typeParameterIfExists = _.isUndefined((props.method as TypescriptMethod).typeParameter) - ? undefined - : renderTypeParameter(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); - return ( - <span> - {props.method.callPath} - {methodName} - {typeParameterIfExists}({paramString}) - {props.shouldUseArrowSyntax ? ' => ' : ': '}{' '} - {props.method.returnType && ( - <Type - type={props.method.returnType} - sectionName={sectionName} - typeDefinitionByName={props.typeDefinitionByName} - docsInfo={props.docsInfo} - /> - )} - </span> - ); + const sectionName = constants.TYPES_SECTION_NAME; + const parameters = renderParameters(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); + const paramString = _.reduce(parameters, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, ', ', curr]; + }); + const methodName = props.shouldHideMethodName ? '' : props.method.name; + const typeParameterIfExists = _.isUndefined((props.method as TypescriptMethod).typeParameter) + ? undefined + : renderTypeParameter(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); + return ( + <span> + {props.method.callPath} + {methodName} + {typeParameterIfExists}({paramString}) + {props.shouldUseArrowSyntax ? ' => ' : ': '}{' '} + {props.method.returnType && ( + <Type + type={props.method.returnType} + sectionName={sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + /> + )} + </span> + ); }; MethodSignature.defaultProps = defaultProps; function renderParameters( - method: TypescriptMethod | SolidityMethod, - docsInfo: DocsInfo, - sectionName: string, - typeDefinitionByName?: TypeDefinitionByName, + method: TypescriptMethod | SolidityMethod, + docsInfo: DocsInfo, + sectionName: string, + typeDefinitionByName?: TypeDefinitionByName, ) { - const parameters = method.parameters; - const params = _.map(parameters, (p: Parameter) => { - const isOptional = p.isOptional; - const type = ( - <Type - type={p.type} - sectionName={sectionName} - typeDefinitionByName={typeDefinitionByName} - docsInfo={docsInfo} - /> - ); - return ( - <span key={`param-${p.type}-${p.name}`}> - {p.name} - {isOptional && '?'}: {type} - </span> - ); - }); - return params; + const parameters = method.parameters; + const params = _.map(parameters, (p: Parameter) => { + const isOptional = p.isOptional; + const type = ( + <Type + type={p.type} + sectionName={sectionName} + typeDefinitionByName={typeDefinitionByName} + docsInfo={docsInfo} + /> + ); + return ( + <span key={`param-${p.type}-${p.name}`}> + {p.name} + {isOptional && '?'}: {type} + </span> + ); + }); + return params; } function renderTypeParameter( - method: TypescriptMethod, - docsInfo: DocsInfo, - sectionName: string, - typeDefinitionByName?: TypeDefinitionByName, + method: TypescriptMethod, + docsInfo: DocsInfo, + sectionName: string, + typeDefinitionByName?: TypeDefinitionByName, ) { - const typeParameter = method.typeParameter; - const typeParam = ( - <span> - {`<${typeParameter.name} extends `} - <Type - type={typeParameter.type} - sectionName={sectionName} - typeDefinitionByName={typeDefinitionByName} - docsInfo={docsInfo} - /> - {`>`} - </span> - ); - return typeParam; + const typeParameter = method.typeParameter; + const typeParam = ( + <span> + {`<${typeParameter.name} extends `} + <Type + type={typeParameter.type} + sectionName={sectionName} + typeDefinitionByName={typeDefinitionByName} + docsInfo={docsInfo} + /> + {`>`} + </span> + ); + return typeParam; } diff --git a/packages/website/ts/pages/documentation/source_link.tsx b/packages/website/ts/pages/documentation/source_link.tsx index 32126b7da..6588ee39e 100644 --- a/packages/website/ts/pages/documentation/source_link.tsx +++ b/packages/website/ts/pages/documentation/source_link.tsx @@ -4,28 +4,28 @@ import { Source } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface SourceLinkProps { - source: Source; - baseUrl: string; - version: string; - subPackageName: string; + source: Source; + baseUrl: string; + version: string; + subPackageName: string; } const packagesWithNamespace = ['connect']; export function SourceLink(props: SourceLinkProps) { - const src = props.source; - const url = props.baseUrl; - const pkg = props.subPackageName; - let tagPrefix = pkg; - if (_.includes(packagesWithNamespace, pkg)) { - tagPrefix = `@0xproject/${pkg}`; - } - const sourceCodeUrl = `${url}/blob/${tagPrefix}%40${props.version}/packages/${pkg}/${src.fileName}#L${src.line}`; - return ( - <div className="pt2" style={{ fontSize: 14 }}> - <a href={sourceCodeUrl} target="_blank" className="underline" style={{ color: colors.grey }}> - Source - </a> - </div> - ); + const src = props.source; + const url = props.baseUrl; + const pkg = props.subPackageName; + let tagPrefix = pkg; + if (_.includes(packagesWithNamespace, pkg)) { + tagPrefix = `@0xproject/${pkg}`; + } + const sourceCodeUrl = `${url}/blob/${tagPrefix}%40${props.version}/packages/${pkg}/${src.fileName}#L${src.line}`; + return ( + <div className="pt2" style={{ fontSize: 14 }}> + <a href={sourceCodeUrl} target="_blank" className="underline" style={{ color: colors.grey }}> + Source + </a> + </div> + ); } diff --git a/packages/website/ts/pages/documentation/type.tsx b/packages/website/ts/pages/documentation/type.tsx index 9a2696e22..e989e7129 100644 --- a/packages/website/ts/pages/documentation/type.tsx +++ b/packages/website/ts/pages/documentation/type.tsx @@ -11,202 +11,202 @@ import { utils } from 'ts/utils/utils'; // Some types reference other libraries. For these types, we want to link the user to the relevant documentation. const typeToUrl: { [typeName: string]: string } = { - Web3: constants.URL_WEB3_DOCS, - Provider: constants.URL_WEB3_PROVIDER_DOCS, - BigNumber: constants.URL_BIGNUMBERJS_GITHUB, - DecodedLogEntryEvent: constants.URL_WEB3_DECODED_LOG_ENTRY_EVENT, - LogEntryEvent: constants.URL_WEB3_LOG_ENTRY_EVENT, + Web3: constants.URL_WEB3_DOCS, + Provider: constants.URL_WEB3_PROVIDER_DOCS, + BigNumber: constants.URL_BIGNUMBERJS_GITHUB, + DecodedLogEntryEvent: constants.URL_WEB3_DECODED_LOG_ENTRY_EVENT, + LogEntryEvent: constants.URL_WEB3_LOG_ENTRY_EVENT, }; const typePrefix: { [typeName: string]: string } = { - Provider: 'Web3', - DecodedLogEntryEvent: 'Web3', - LogEntryEvent: 'Web3', + Provider: 'Web3', + DecodedLogEntryEvent: 'Web3', + LogEntryEvent: 'Web3', }; const typeToSection: { [typeName: string]: string } = { - ExchangeWrapper: 'exchange', - TokenWrapper: 'token', - TokenRegistryWrapper: 'tokenRegistry', - EtherTokenWrapper: 'etherToken', - ProxyWrapper: 'proxy', - TokenTransferProxyWrapper: 'proxy', - OrderStateWatcher: 'orderWatcher', + ExchangeWrapper: 'exchange', + TokenWrapper: 'token', + TokenRegistryWrapper: 'tokenRegistry', + EtherTokenWrapper: 'etherToken', + ProxyWrapper: 'proxy', + TokenTransferProxyWrapper: 'proxy', + OrderStateWatcher: 'orderWatcher', }; interface TypeProps { - type: TypeDef; - docsInfo: DocsInfo; - sectionName: string; - typeDefinitionByName?: TypeDefinitionByName; + type: TypeDef; + docsInfo: DocsInfo; + sectionName: string; + typeDefinitionByName?: TypeDefinitionByName; } // The return type needs to be `any` here so that we can recursively define <Type /> components within // <Type /> components (e.g when rendering the union type). export function Type(props: TypeProps): any { - const type = props.type; - const isReference = type.typeDocType === TypeDocTypes.Reference; - const isArray = type.typeDocType === TypeDocTypes.Array; - let typeNameColor = 'inherit'; - let typeName: string | React.ReactNode; - let typeArgs: React.ReactNode[] = []; - switch (type.typeDocType) { - case TypeDocTypes.Intrinsic: - case TypeDocTypes.Unknown: - typeName = type.name; - typeNameColor = colors.orange; - break; + const type = props.type; + const isReference = type.typeDocType === TypeDocTypes.Reference; + const isArray = type.typeDocType === TypeDocTypes.Array; + let typeNameColor = 'inherit'; + let typeName: string | React.ReactNode; + let typeArgs: React.ReactNode[] = []; + switch (type.typeDocType) { + case TypeDocTypes.Intrinsic: + case TypeDocTypes.Unknown: + typeName = type.name; + typeNameColor = colors.orange; + break; - case TypeDocTypes.Reference: - typeName = type.name; - typeArgs = _.map(type.typeArguments, (arg: TypeDef) => { - if (arg.typeDocType === TypeDocTypes.Array) { - const key = `type-${arg.elementType.name}-${arg.elementType.typeDocType}`; - return ( - <span> - <Type - key={key} - type={arg.elementType} - sectionName={props.sectionName} - typeDefinitionByName={props.typeDefinitionByName} - docsInfo={props.docsInfo} - />[] - </span> - ); - } else { - const subType = ( - <Type - key={`type-${arg.name}-${arg.value}-${arg.typeDocType}`} - type={arg} - sectionName={props.sectionName} - typeDefinitionByName={props.typeDefinitionByName} - docsInfo={props.docsInfo} - /> - ); - return subType; - } - }); - break; + case TypeDocTypes.Reference: + typeName = type.name; + typeArgs = _.map(type.typeArguments, (arg: TypeDef) => { + if (arg.typeDocType === TypeDocTypes.Array) { + const key = `type-${arg.elementType.name}-${arg.elementType.typeDocType}`; + return ( + <span> + <Type + key={key} + type={arg.elementType} + sectionName={props.sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + />[] + </span> + ); + } else { + const subType = ( + <Type + key={`type-${arg.name}-${arg.value}-${arg.typeDocType}`} + type={arg} + sectionName={props.sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + /> + ); + return subType; + } + }); + break; - case TypeDocTypes.StringLiteral: - typeName = `'${type.value}'`; - typeNameColor = colors.green; - break; + case TypeDocTypes.StringLiteral: + typeName = `'${type.value}'`; + typeNameColor = colors.green; + break; - case TypeDocTypes.Array: - typeName = type.elementType.name; - break; + case TypeDocTypes.Array: + typeName = type.elementType.name; + break; - case TypeDocTypes.Union: - const unionTypes = _.map(type.types, t => { - return ( - <Type - key={`type-${t.name}-${t.value}-${t.typeDocType}`} - type={t} - sectionName={props.sectionName} - typeDefinitionByName={props.typeDefinitionByName} - docsInfo={props.docsInfo} - /> - ); - }); - typeName = _.reduce(unionTypes, (prev: React.ReactNode, curr: React.ReactNode) => { - return [prev, '|', curr]; - }); - break; + case TypeDocTypes.Union: + const unionTypes = _.map(type.types, t => { + return ( + <Type + key={`type-${t.name}-${t.value}-${t.typeDocType}`} + type={t} + sectionName={props.sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + /> + ); + }); + typeName = _.reduce(unionTypes, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '|', curr]; + }); + break; - case TypeDocTypes.TypeParameter: - typeName = type.name; - break; + case TypeDocTypes.TypeParameter: + typeName = type.name; + break; - default: - throw utils.spawnSwitchErr('type.typeDocType', type.typeDocType); - } - // HACK: Normalize BigNumber to simply BigNumber. For some reason the type - // name is unpredictably one or the other. - if (typeName === 'BigNumber') { - typeName = 'BigNumber'; - } - const commaSeparatedTypeArgs = _.reduce(typeArgs, (prev: React.ReactNode, curr: React.ReactNode) => { - return [prev, ', ', curr]; - }); + default: + throw utils.spawnSwitchErr('type.typeDocType', type.typeDocType); + } + // HACK: Normalize BigNumber to simply BigNumber. For some reason the type + // name is unpredictably one or the other. + if (typeName === 'BigNumber') { + typeName = 'BigNumber'; + } + const commaSeparatedTypeArgs = _.reduce(typeArgs, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, ', ', curr]; + }); - const typeNameUrlIfExists = typeToUrl[typeName as string]; - const typePrefixIfExists = typePrefix[typeName as string]; - const sectionNameIfExists = typeToSection[typeName as string]; - if (!_.isUndefined(typeNameUrlIfExists)) { - typeName = ( - <a - href={typeNameUrlIfExists} - target="_blank" - className="text-decoration-none" - style={{ color: colors.lightBlueA700 }} - > - {!_.isUndefined(typePrefixIfExists) ? `${typePrefixIfExists}.` : ''} - {typeName} - </a> - ); - } else if ( - (isReference || isArray) && - (props.docsInfo.isPublicType(typeName as string) || !_.isUndefined(sectionNameIfExists)) - ) { - const id = Math.random().toString(); - const typeDefinitionAnchorId = _.isUndefined(sectionNameIfExists) - ? `${props.sectionName}-${typeName}` - : sectionNameIfExists; - let typeDefinition; - if (props.typeDefinitionByName) { - typeDefinition = props.typeDefinitionByName[typeName as string]; - } - typeName = ( - <ScrollLink - to={typeDefinitionAnchorId} - offset={0} - duration={constants.DOCS_SCROLL_DURATION_MS} - containerId={constants.DOCS_CONTAINER_ID} - > - {_.isUndefined(typeDefinition) || utils.isUserOnMobile() ? ( - <span - onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)} - style={{ color: colors.lightBlueA700, cursor: 'pointer' }} - > - {typeName} - </span> - ) : ( - <span - data-tip={true} - data-for={id} - onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)} - style={{ - color: colors.lightBlueA700, - cursor: 'pointer', - display: 'inline-block', - }} - > - {typeName} - <ReactTooltip type="light" effect="solid" id={id} className="typeTooltip"> - <TypeDefinition - sectionName={props.sectionName} - customType={typeDefinition} - shouldAddId={false} - docsInfo={props.docsInfo} - /> - </ReactTooltip> - </span> - )} - </ScrollLink> - ); - } - return ( - <span> - <span style={{ color: typeNameColor }}>{typeName}</span> - {isArray && '[]'} - {!_.isEmpty(typeArgs) && ( - <span> - {'<'} - {commaSeparatedTypeArgs} - {'>'} - </span> - )} - </span> - ); + const typeNameUrlIfExists = typeToUrl[typeName as string]; + const typePrefixIfExists = typePrefix[typeName as string]; + const sectionNameIfExists = typeToSection[typeName as string]; + if (!_.isUndefined(typeNameUrlIfExists)) { + typeName = ( + <a + href={typeNameUrlIfExists} + target="_blank" + className="text-decoration-none" + style={{ color: colors.lightBlueA700 }} + > + {!_.isUndefined(typePrefixIfExists) ? `${typePrefixIfExists}.` : ''} + {typeName} + </a> + ); + } else if ( + (isReference || isArray) && + (props.docsInfo.isPublicType(typeName as string) || !_.isUndefined(sectionNameIfExists)) + ) { + const id = Math.random().toString(); + const typeDefinitionAnchorId = _.isUndefined(sectionNameIfExists) + ? `${props.sectionName}-${typeName}` + : sectionNameIfExists; + let typeDefinition; + if (props.typeDefinitionByName) { + typeDefinition = props.typeDefinitionByName[typeName as string]; + } + typeName = ( + <ScrollLink + to={typeDefinitionAnchorId} + offset={0} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + > + {_.isUndefined(typeDefinition) || utils.isUserOnMobile() ? ( + <span + onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)} + style={{ color: colors.lightBlueA700, cursor: 'pointer' }} + > + {typeName} + </span> + ) : ( + <span + data-tip={true} + data-for={id} + onClick={utils.setUrlHash.bind(null, typeDefinitionAnchorId)} + style={{ + color: colors.lightBlueA700, + cursor: 'pointer', + display: 'inline-block', + }} + > + {typeName} + <ReactTooltip type="light" effect="solid" id={id} className="typeTooltip"> + <TypeDefinition + sectionName={props.sectionName} + customType={typeDefinition} + shouldAddId={false} + docsInfo={props.docsInfo} + /> + </ReactTooltip> + </span> + )} + </ScrollLink> + ); + } + return ( + <span> + <span style={{ color: typeNameColor }}>{typeName}</span> + {isArray && '[]'} + {!_.isEmpty(typeArgs) && ( + <span> + {'<'} + {commaSeparatedTypeArgs} + {'>'} + </span> + )} + </span> + ); } diff --git a/packages/website/ts/pages/documentation/type_definition.tsx b/packages/website/ts/pages/documentation/type_definition.tsx index 356926157..d46eec76c 100644 --- a/packages/website/ts/pages/documentation/type_definition.tsx +++ b/packages/website/ts/pages/documentation/type_definition.tsx @@ -13,113 +13,113 @@ import { colors } from 'ts/utils/colors'; import { utils } from 'ts/utils/utils'; interface TypeDefinitionProps { - sectionName: string; - customType: CustomType; - shouldAddId?: boolean; - docsInfo: DocsInfo; + sectionName: string; + customType: CustomType; + shouldAddId?: boolean; + docsInfo: DocsInfo; } interface TypeDefinitionState { - shouldShowAnchor: boolean; + shouldShowAnchor: boolean; } export class TypeDefinition extends React.Component<TypeDefinitionProps, TypeDefinitionState> { - public static defaultProps: Partial<TypeDefinitionProps> = { - shouldAddId: true, - }; - constructor(props: TypeDefinitionProps) { - super(props); - this.state = { - shouldShowAnchor: false, - }; - } - public render() { - const customType = this.props.customType; - if (!this.props.docsInfo.isPublicType(customType.name)) { - return null; // no-op - } + public static defaultProps: Partial<TypeDefinitionProps> = { + shouldAddId: true, + }; + constructor(props: TypeDefinitionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const customType = this.props.customType; + if (!this.props.docsInfo.isPublicType(customType.name)) { + return null; // no-op + } - let typePrefix: string; - let codeSnippet: React.ReactNode; - switch (customType.kindString) { - case KindString.Interface: - typePrefix = 'Interface'; - codeSnippet = ( - <Interface type={customType} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} /> - ); - break; + let typePrefix: string; + let codeSnippet: React.ReactNode; + switch (customType.kindString) { + case KindString.Interface: + typePrefix = 'Interface'; + codeSnippet = ( + <Interface type={customType} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} /> + ); + break; - case KindString.Variable: - typePrefix = 'Enum'; - codeSnippet = <CustomEnum type={customType} />; - break; + case KindString.Variable: + typePrefix = 'Enum'; + codeSnippet = <CustomEnum type={customType} />; + break; - case KindString.Enumeration: - typePrefix = 'Enum'; - const enumValues = _.map(customType.children, (c: CustomTypeChild) => { - return { - name: c.name, - defaultValue: c.defaultValue, - }; - }); - codeSnippet = <Enum values={enumValues} />; - break; + case KindString.Enumeration: + typePrefix = 'Enum'; + const enumValues = _.map(customType.children, (c: CustomTypeChild) => { + return { + name: c.name, + defaultValue: c.defaultValue, + }; + }); + codeSnippet = <Enum values={enumValues} />; + break; - case KindString.TypeAlias: - typePrefix = 'Type Alias'; - codeSnippet = ( - <span> - <span style={{ color: colors.lightPurple }}>type</span> {customType.name} ={' '} - {customType.type.typeDocType !== TypeDocTypes.Reflection ? ( - <Type - type={customType.type} - sectionName={this.props.sectionName} - docsInfo={this.props.docsInfo} - /> - ) : ( - <MethodSignature - method={customType.type.method} - sectionName={this.props.sectionName} - shouldHideMethodName={true} - shouldUseArrowSyntax={true} - docsInfo={this.props.docsInfo} - /> - )} - </span> - ); - break; + case KindString.TypeAlias: + typePrefix = 'Type Alias'; + codeSnippet = ( + <span> + <span style={{ color: colors.lightPurple }}>type</span> {customType.name} ={' '} + {customType.type.typeDocType !== TypeDocTypes.Reflection ? ( + <Type + type={customType.type} + sectionName={this.props.sectionName} + docsInfo={this.props.docsInfo} + /> + ) : ( + <MethodSignature + method={customType.type.method} + sectionName={this.props.sectionName} + shouldHideMethodName={true} + shouldUseArrowSyntax={true} + docsInfo={this.props.docsInfo} + /> + )} + </span> + ); + break; - default: - throw utils.spawnSwitchErr('type.kindString', customType.kindString); - } + default: + throw utils.spawnSwitchErr('type.kindString', customType.kindString); + } - const typeDefinitionAnchorId = `${this.props.sectionName}-${customType.name}`; - return ( - <div - id={this.props.shouldAddId ? typeDefinitionAnchorId : ''} - className="pb2" - style={{ overflow: 'hidden', width: '100%' }} - onMouseOver={this._setAnchorVisibility.bind(this, true)} - onMouseOut={this._setAnchorVisibility.bind(this, false)} - > - <AnchorTitle - headerSize={HeaderSizes.H3} - title={`${typePrefix} ${customType.name}`} - id={this.props.shouldAddId ? typeDefinitionAnchorId : ''} - shouldShowAnchor={this.state.shouldShowAnchor} - /> - <div style={{ fontSize: 16 }}> - <pre> - <code className="hljs">{codeSnippet}</code> - </pre> - </div> - {customType.comment && <Comment comment={customType.comment} className="py2" />} - </div> - ); - } - private _setAnchorVisibility(shouldShowAnchor: boolean) { - this.setState({ - shouldShowAnchor, - }); - } + const typeDefinitionAnchorId = `${this.props.sectionName}-${customType.name}`; + return ( + <div + id={this.props.shouldAddId ? typeDefinitionAnchorId : ''} + className="pb2" + style={{ overflow: 'hidden', width: '100%' }} + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <AnchorTitle + headerSize={HeaderSizes.H3} + title={`${typePrefix} ${customType.name}`} + id={this.props.shouldAddId ? typeDefinitionAnchorId : ''} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + <div style={{ fontSize: 16 }}> + <pre> + <code className="hljs">{codeSnippet}</code> + </pre> + </div> + {customType.comment && <Comment comment={customType.comment} className="py2" />} + </div> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } } diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx index f437f5a8d..b4b5214a2 100644 --- a/packages/website/ts/pages/faq/faq.tsx +++ b/packages/website/ts/pages/faq/faq.tsx @@ -10,438 +10,438 @@ import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; export interface FAQProps { - source: string; - location: Location; + source: string; + location: Location; } interface FAQState {} const styles: Styles = { - thin: { - fontWeight: 100, - }, + thin: { + fontWeight: 100, + }, }; const sections: FAQSection[] = [ - { - name: '0x Protocol', - questions: [ - { - prompt: 'What is 0x?', - answer: ( - <div> - At its core, 0x is an open and non-rent seeking protocol that facilitates trustless, low - friction exchange of Ethereum-based assets. Developers can use 0x as a platform to build - exchange applications on top of (<a - href={`${configs.BASE_URL}${WebsitePaths.ZeroExJs}#introduction`} - target="blank" - > - 0x.js - </a>{' '} - is a Javascript library for interacting with the 0x protocol). For end users, 0x will be the - infrastructure of a wide variety of user-facing applications i.e.{' '} - <a href={`${configs.BASE_URL}${WebsitePaths.Portal}`} target="blank"> - 0x Portal - </a>, a decentralized application that facilitates trustless trading of Ethereum-based tokens - between known counterparties. - </div> - ), - }, - { - prompt: 'What problem does 0x solve?', - answer: ( - <div> - In the two years since the Ethereum blockchain’s genesis block, numerous decentralized - applications (dApps) have created Ethereum smart contracts for peer-to-peer exchange. Rapid - iteration and a lack of best practices have left the blockchain scattered with proprietary and - application-specific implementations. As a result, end users are exposed to numerous smart - contracts of varying quality and security, with unique configuration processes and learning - curves, all of which implement the same functionality. This approach imposes unnecessary costs - on the network by fragmenting end users according to the particular dApp each user happens to be - using, eliminating valuable network effects around liquidity. 0x is the solution to this problem - by acting as modular, unopinionated building blocks that may be assembled and reconfigured. - </div> - ), - }, - { - prompt: 'How is 0x different from a centralized exchange like Poloniex or ShapeShift?', - answer: ( - <div> - <ul> - <li>0x is a protocol for exchange, not a user-facing exchange application.</li> - <li> - 0x is decentralized and trustless; there is no central party which can be hacked, run - away with customer funds or be subjected to government regulations. Hacks of Mt. Gox, - Shapeshift and Bitfinex have demonstrated that these types of systemic risks are - palpable. - </li> - <li> - Rather than a proprietary system that exists to extract rent for its owners, 0x is - public infrastructure that is funded by a globally distributed community of - stakeholders. While the protocol is free to use, it enables for-profit user-facing - exchange applications to be built on top of the protocol. - </li> - </ul> - </div> - ), - }, - { - prompt: 'If 0x protocol is free to use, where do transaction fees come in?', - answer: ( - <div> - 0x protocol uses off-chain order books to massively reduce friction costs for market makers and - ensure that the blockchain is only used for trade settlement. Hosting and maintaining an - off-chain order book is a service; to incent “Relayers” to provide this service they must be - able to charge transaction fees on trading activity. Relayers are free to set their transaction - fees to any value they desire. We expect Relayers to be highly competitive and transaction fees - to approach an efficient economic equilibrium over time. - </div> - ), - }, - { - prompt: 'What are the differences between 0x protocol and state channels?', - answer: ( - <div> - <div> - Participants in a state channel pass cryptographically signed messages back and forth, - accumulating intermediate state changes without publishing them to the canonical chain until - the channel is closed. State channels are ideal for “bar tab” applications where numerous - intermediate state changes may be accumulated off-chain before being settled by a final - on-chain transaction (i.e. day trading, poker, turn-based games). - </div> - <ul> - <li> - While state channels drastically reduce the number of on-chain transactions for specific - use cases, numerous on-chain transactions and a security deposit are required to open - and safely close a state channel making them less efficient than 0x for executing - one-time trades. - </li> - <li> - State channels are isolated from the Ethereum blockchain meaning that they cannot - interact with smart contracts. 0x is designed to be integrated directly into smart - contracts so trades can be executed programmatically in a single line of Solidity code. - </li> - </ul> - </div> - ), - }, - { - prompt: 'What types of digital assets are supported by 0x?', - answer: ( - <div> - 0x supports all Ethereum-based assets that adhere to the ERC20 token standard. There are many - ERC20 tokens, worth a combined $2.2B, and more tokens are created each month. We believe that, - by 2020, thousands of assets will be tokenized and moved onto the Ethereum blockchain including - traditional securities such as equities, bonds and derivatives, fiat currencies and scarce - digital goods such as video game items. In the future, cross-blockchain solutions such as{' '} - <a href="https://cosmos.network/" target="_blank"> - Cosmos - </a>{' '} - and{' '} - <a href="http://polkadot.io/" target="_blank"> - Polkadot - </a>{' '} - will allow cryptocurrencies to freely move between blockchains and, naturally, currencies such - as Bitcoin will end up being represented as ERC20 tokens on the Ethereum blockchain. - </div> - ), - }, - { - prompt: '0x is open source: what prevents someone from forking the protocol?', - answer: ( - <div> - Ethereum and Bitcoin are both open source protocols. Each protocol has been forked, but the - resulting clone networks have seen little adoption (as measured by transaction count or market - cap). This is because users have little to no incentive to switch over to a clone network if the - original has initial network effects and a talented developer team behind it. An exception is in - the case that a protocol includes a controversial feature such as a method of rent extraction or - a monetary policy that favors one group of users over another (Zcash developer subsidy - for - better or worse - resulted in Zclassic). Perceived inequality can provide a strong enough - incentive that users will fork the original protocol’s codebase and spin up a new network that - eliminates the controversial feature. In the case of 0x, there is no rent extraction and no - users are given special permissions. 0x protocol is upgradable. Cutting-edge technical - capabilities can be integrated into 0x via decentralized governance (see section below), - eliminating incentives to fork off of the original protocol and sacrifice the network effects - surrounding liquidity that result from the shared protocol and settlement layer. - </div> - ), - }, - ], - }, - { - name: '0x Token (ZRX)', - questions: [ - { - prompt: 'Explain how the 0x protocol token (zrx) works.', - answer: ( - <div> - <div> - 0x protocol token (ZRX) is utilized in two ways: 1) to solve the{' '} - <a href="https://en.wikipedia.org/wiki/Coordination_game" target="_blank"> - coordination problem - </a>{' '} - and drive network effects around liquidity, creating a feedback loop where early adopters of - the protocol benefit from wider adoption and 2) to be used for decentralized governance over - 0x protocol's update mechanism. In more detail: - </div> - <ul> - <li> - ZRX tokens are used by Makers and Takers (market participants that generate and consume - orders, respectively) to pay transaction fees to Relayers (entities that host and - maintain public order books). - </li> - <li> - ZRX tokens are used for decentralized governance over 0x protocol’s update mechanism - which allows its underlying smart contracts to be replaced and improved over time. An - update mechanism is needed because 0x is built upon Ethereum’s rapidly evolving - technology stack, decentralized governance is needed because 0x protocol’s smart - contracts will have access to user funds and numerous dApps will need to plug into 0x - smart contracts. Decentralized governance ensures that this update process is secure and - minimizes disruption to the network. - </li> - </ul> - </div> - ), - }, - { - prompt: 'Why must transaction fees be denominated in 0x token (ZRX) rather than ETH?', - answer: ( - <div> - 0x protocol’s decentralized update mechanism is analogous to proof-of-stake. To maximize the - alignment of stakeholder and end user incentives, the staked asset must provide utility within - the protocol. - </div> - ), - }, - { - prompt: 'How will decentralized governance work?', - answer: ( - <div> - Decentralized governance is an ongoing focus of research; it will involve token voting with ZRX. - Ultimately the solution will maximize security while also maximizing the protocol’s ability to - absorb new innovations. Until the governance structure is formalized and encoded within 0x DAO, - a multi-sig will be used as a placeholder. - </div> - ), - }, - ], - }, - { - name: 'ZRX Token Launch and Fund Use', - questions: [ - { - prompt: 'What is the total supply of ZRX tokens?', - answer: <div>1,000,000,000 ZRX. Fixed supply.</div>, - }, - { - prompt: 'When is the Token Launch? will there be a pre-sale?', - answer: <div>The token launch will be on August 15th, 2017. There will not be a pre-sale.</div>, - }, - { - prompt: 'What will the token launch proceeds be used for?', - answer: ( - <div> - 100% of the proceeds raised in the token launch will be used to fund the development of free and - open source software, tools and infrastructure that support the protocol and surrounding - ecosystem. Check out our{' '} - <a - href="https://docs.google.com/document/d/1_RVa-_bkU92fWRsC8eNy4vYjcTt-WC8GtqyyjbTd-oY" - target="_blank" - > - development roadmap - </a>. - </div> - ), - }, - { - prompt: 'What will be the initial distribution of ZRX tokens?', - answer: ( - <div> - <div className="center" style={{ width: '100%' }}> - <img style={{ width: 350 }} src="/images/zrx_pie_chart.png" /> - </div> - <div className="py1"> - <div className="bold pb1">Token Launch (50%)</div> - <div> - ZRX is inherently a governance token that plays a critical role in the process of - upgrading 0x protocol. We are fully committed to formulating a functional and - theoretically sound governance model and we plan to dedicate significant resources to - R&D. - </div> - </div> - <div className="py1"> - <div className="bold pb1">Retained by 0x (15%)</div> - <div> - The 0x core development team will be able to sustain itself for approximately five years - using funds raised through the token launch. If 0x protocol proves to be as foundational - a technology as we believe it to be, the retained ZRX tokens will allow the 0x core - development team to sustain operations beyond the first 5 years. - </div> - </div> - <div className="py1"> - <div className="bold pb1">Developer Fund (15%)</div> - <div> - The Developer Fund will be used to make targeted capital injections into high potential - projects and teams that are attempting to grow the 0x ecosystem, strategic partnerships, - hackathon prizes and community development activities. - </div> - </div> - <div className="py1"> - <div className="bold pb1">Founding Team (10%)</div> - <div> - The founding team’s allocation of ZRX will vest over a traditional 4 year vesting - schedule with a one year cliff. We believe this should be standard practice for any team - that is committed to making their project a long term success. - </div> - </div> - <div className="py1"> - <div className="bold pb1">Early Backers & Advisors (10%)</div> - <div> - Our backers and advisors have provided capital, resources and guidance that have allowed - us to fill out our team, setup a robust legal entity and build a fully functional - product before launching a token. As a result, we have a proven track record and can - offer a token that holds genuine utility. - </div> - </div> - </div> - ), - }, - { - prompt: 'Can I mine ZRX tokens?', - answer: ( - <div> - No, the total supply of ZRX tokens is fixed and there is no continuous issuance model. Users - that facilitate trading over 0x protocol by operating a Relayer earn transaction fees - denominated in ZRX; as more trading activity is generated, more transaction fees are earned. - </div> - ), - }, - { - prompt: 'Will there be a lockup period for ZRX tokens sold in the token launch?', - answer: <div>No, ZRX tokens sold in the token launch will immediately be liquid.</div>, - }, - { - prompt: 'Will there be a lockup period for tokens allocated to the founding team?', - answer: ( - <div> - Yes. ZRX tokens allocated to founders, advisors and staff members will be released over a 4 year - vesting schedule with a 25% cliff upon completion of the initial token launch and 25% released - each subsequent year in monthly installments. Staff members hired after the token launch will - have a 4 year vesting schedule with a one year cliff. - </div> - ), - }, - { - prompt: 'Which cryptocurrencies will be accepted in the token launch?', - answer: <div>ETH.</div>, - }, - { - prompt: 'When will 0x be live?', - answer: ( - <div> - An alpha version of 0x has been live on our private test network since January 2017. Version 1.0 - of 0x protocol will be deployed to the canonical Ethereum blockchain after a round of security - audits and prior to the public token launch. 0x will be using the 0x protocol during our token - launch. - </div> - ), - }, - { - prompt: 'Where can I find a development roadmap?', - answer: ( - <div> - Check it out{' '} - <a - href="https://drive.google.com/open?id=14IP1N8mt3YdsAoqYTyruMnZswpklUs3THyS1VXx71fo" - target="_blank" - > - here - </a>. - </div> - ), - }, - ], - }, - { - name: 'Team', - questions: [ - { - prompt: 'Where is 0x based?', - answer: <div>0x was founded in SF and is driven by a diverse group of contributors.</div>, - }, - { - prompt: 'How can I get involved?', - answer: ( - <div> - Join our{' '} - <a href={constants.URL_ZEROEX_CHAT} target="_blank"> - Rocket.chat - </a>! As an open source project, 0x will rely on a worldwide community of passionate developers - to contribute proposals, ideas and code. - </div> - ), - }, - { - prompt: 'Why the name 0x?', - answer: ( - <div> - 0x is the prefix for hexadecimal numeric constants including Ethereum addresses. In a more - abstract context, as the first open protocol for exchange 0x represents the beginning of the end - for the exchange industry’s rent seeking oligopoly: zero exchange. - </div> - ), - }, - { - prompt: 'How do you pronounce 0x?', - answer: <div>We pronounce 0x as “zero-ex,” but you are free to pronounce it however you please.</div>, - }, - ], - }, + { + name: '0x Protocol', + questions: [ + { + prompt: 'What is 0x?', + answer: ( + <div> + At its core, 0x is an open and non-rent seeking protocol that facilitates trustless, low + friction exchange of Ethereum-based assets. Developers can use 0x as a platform to build + exchange applications on top of (<a + href={`${configs.BASE_URL}${WebsitePaths.ZeroExJs}#introduction`} + target="blank" + > + 0x.js + </a>{' '} + is a Javascript library for interacting with the 0x protocol). For end users, 0x will be the + infrastructure of a wide variety of user-facing applications i.e.{' '} + <a href={`${configs.BASE_URL}${WebsitePaths.Portal}`} target="blank"> + 0x Portal + </a>, a decentralized application that facilitates trustless trading of Ethereum-based tokens + between known counterparties. + </div> + ), + }, + { + prompt: 'What problem does 0x solve?', + answer: ( + <div> + In the two years since the Ethereum blockchain’s genesis block, numerous decentralized + applications (dApps) have created Ethereum smart contracts for peer-to-peer exchange. Rapid + iteration and a lack of best practices have left the blockchain scattered with proprietary and + application-specific implementations. As a result, end users are exposed to numerous smart + contracts of varying quality and security, with unique configuration processes and learning + curves, all of which implement the same functionality. This approach imposes unnecessary costs + on the network by fragmenting end users according to the particular dApp each user happens to be + using, eliminating valuable network effects around liquidity. 0x is the solution to this problem + by acting as modular, unopinionated building blocks that may be assembled and reconfigured. + </div> + ), + }, + { + prompt: 'How is 0x different from a centralized exchange like Poloniex or ShapeShift?', + answer: ( + <div> + <ul> + <li>0x is a protocol for exchange, not a user-facing exchange application.</li> + <li> + 0x is decentralized and trustless; there is no central party which can be hacked, run + away with customer funds or be subjected to government regulations. Hacks of Mt. Gox, + Shapeshift and Bitfinex have demonstrated that these types of systemic risks are + palpable. + </li> + <li> + Rather than a proprietary system that exists to extract rent for its owners, 0x is + public infrastructure that is funded by a globally distributed community of + stakeholders. While the protocol is free to use, it enables for-profit user-facing + exchange applications to be built on top of the protocol. + </li> + </ul> + </div> + ), + }, + { + prompt: 'If 0x protocol is free to use, where do transaction fees come in?', + answer: ( + <div> + 0x protocol uses off-chain order books to massively reduce friction costs for market makers and + ensure that the blockchain is only used for trade settlement. Hosting and maintaining an + off-chain order book is a service; to incent “Relayers” to provide this service they must be + able to charge transaction fees on trading activity. Relayers are free to set their transaction + fees to any value they desire. We expect Relayers to be highly competitive and transaction fees + to approach an efficient economic equilibrium over time. + </div> + ), + }, + { + prompt: 'What are the differences between 0x protocol and state channels?', + answer: ( + <div> + <div> + Participants in a state channel pass cryptographically signed messages back and forth, + accumulating intermediate state changes without publishing them to the canonical chain until + the channel is closed. State channels are ideal for “bar tab” applications where numerous + intermediate state changes may be accumulated off-chain before being settled by a final + on-chain transaction (i.e. day trading, poker, turn-based games). + </div> + <ul> + <li> + While state channels drastically reduce the number of on-chain transactions for specific + use cases, numerous on-chain transactions and a security deposit are required to open + and safely close a state channel making them less efficient than 0x for executing + one-time trades. + </li> + <li> + State channels are isolated from the Ethereum blockchain meaning that they cannot + interact with smart contracts. 0x is designed to be integrated directly into smart + contracts so trades can be executed programmatically in a single line of Solidity code. + </li> + </ul> + </div> + ), + }, + { + prompt: 'What types of digital assets are supported by 0x?', + answer: ( + <div> + 0x supports all Ethereum-based assets that adhere to the ERC20 token standard. There are many + ERC20 tokens, worth a combined $2.2B, and more tokens are created each month. We believe that, + by 2020, thousands of assets will be tokenized and moved onto the Ethereum blockchain including + traditional securities such as equities, bonds and derivatives, fiat currencies and scarce + digital goods such as video game items. In the future, cross-blockchain solutions such as{' '} + <a href="https://cosmos.network/" target="_blank"> + Cosmos + </a>{' '} + and{' '} + <a href="http://polkadot.io/" target="_blank"> + Polkadot + </a>{' '} + will allow cryptocurrencies to freely move between blockchains and, naturally, currencies such + as Bitcoin will end up being represented as ERC20 tokens on the Ethereum blockchain. + </div> + ), + }, + { + prompt: '0x is open source: what prevents someone from forking the protocol?', + answer: ( + <div> + Ethereum and Bitcoin are both open source protocols. Each protocol has been forked, but the + resulting clone networks have seen little adoption (as measured by transaction count or market + cap). This is because users have little to no incentive to switch over to a clone network if the + original has initial network effects and a talented developer team behind it. An exception is in + the case that a protocol includes a controversial feature such as a method of rent extraction or + a monetary policy that favors one group of users over another (Zcash developer subsidy - for + better or worse - resulted in Zclassic). Perceived inequality can provide a strong enough + incentive that users will fork the original protocol’s codebase and spin up a new network that + eliminates the controversial feature. In the case of 0x, there is no rent extraction and no + users are given special permissions. 0x protocol is upgradable. Cutting-edge technical + capabilities can be integrated into 0x via decentralized governance (see section below), + eliminating incentives to fork off of the original protocol and sacrifice the network effects + surrounding liquidity that result from the shared protocol and settlement layer. + </div> + ), + }, + ], + }, + { + name: '0x Token (ZRX)', + questions: [ + { + prompt: 'Explain how the 0x protocol token (zrx) works.', + answer: ( + <div> + <div> + 0x protocol token (ZRX) is utilized in two ways: 1) to solve the{' '} + <a href="https://en.wikipedia.org/wiki/Coordination_game" target="_blank"> + coordination problem + </a>{' '} + and drive network effects around liquidity, creating a feedback loop where early adopters of + the protocol benefit from wider adoption and 2) to be used for decentralized governance over + 0x protocol's update mechanism. In more detail: + </div> + <ul> + <li> + ZRX tokens are used by Makers and Takers (market participants that generate and consume + orders, respectively) to pay transaction fees to Relayers (entities that host and + maintain public order books). + </li> + <li> + ZRX tokens are used for decentralized governance over 0x protocol’s update mechanism + which allows its underlying smart contracts to be replaced and improved over time. An + update mechanism is needed because 0x is built upon Ethereum’s rapidly evolving + technology stack, decentralized governance is needed because 0x protocol’s smart + contracts will have access to user funds and numerous dApps will need to plug into 0x + smart contracts. Decentralized governance ensures that this update process is secure and + minimizes disruption to the network. + </li> + </ul> + </div> + ), + }, + { + prompt: 'Why must transaction fees be denominated in 0x token (ZRX) rather than ETH?', + answer: ( + <div> + 0x protocol’s decentralized update mechanism is analogous to proof-of-stake. To maximize the + alignment of stakeholder and end user incentives, the staked asset must provide utility within + the protocol. + </div> + ), + }, + { + prompt: 'How will decentralized governance work?', + answer: ( + <div> + Decentralized governance is an ongoing focus of research; it will involve token voting with ZRX. + Ultimately the solution will maximize security while also maximizing the protocol’s ability to + absorb new innovations. Until the governance structure is formalized and encoded within 0x DAO, + a multi-sig will be used as a placeholder. + </div> + ), + }, + ], + }, + { + name: 'ZRX Token Launch and Fund Use', + questions: [ + { + prompt: 'What is the total supply of ZRX tokens?', + answer: <div>1,000,000,000 ZRX. Fixed supply.</div>, + }, + { + prompt: 'When is the Token Launch? will there be a pre-sale?', + answer: <div>The token launch will be on August 15th, 2017. There will not be a pre-sale.</div>, + }, + { + prompt: 'What will the token launch proceeds be used for?', + answer: ( + <div> + 100% of the proceeds raised in the token launch will be used to fund the development of free and + open source software, tools and infrastructure that support the protocol and surrounding + ecosystem. Check out our{' '} + <a + href="https://docs.google.com/document/d/1_RVa-_bkU92fWRsC8eNy4vYjcTt-WC8GtqyyjbTd-oY" + target="_blank" + > + development roadmap + </a>. + </div> + ), + }, + { + prompt: 'What will be the initial distribution of ZRX tokens?', + answer: ( + <div> + <div className="center" style={{ width: '100%' }}> + <img style={{ width: 350 }} src="/images/zrx_pie_chart.png" /> + </div> + <div className="py1"> + <div className="bold pb1">Token Launch (50%)</div> + <div> + ZRX is inherently a governance token that plays a critical role in the process of + upgrading 0x protocol. We are fully committed to formulating a functional and + theoretically sound governance model and we plan to dedicate significant resources to + R&D. + </div> + </div> + <div className="py1"> + <div className="bold pb1">Retained by 0x (15%)</div> + <div> + The 0x core development team will be able to sustain itself for approximately five years + using funds raised through the token launch. If 0x protocol proves to be as foundational + a technology as we believe it to be, the retained ZRX tokens will allow the 0x core + development team to sustain operations beyond the first 5 years. + </div> + </div> + <div className="py1"> + <div className="bold pb1">Developer Fund (15%)</div> + <div> + The Developer Fund will be used to make targeted capital injections into high potential + projects and teams that are attempting to grow the 0x ecosystem, strategic partnerships, + hackathon prizes and community development activities. + </div> + </div> + <div className="py1"> + <div className="bold pb1">Founding Team (10%)</div> + <div> + The founding team’s allocation of ZRX will vest over a traditional 4 year vesting + schedule with a one year cliff. We believe this should be standard practice for any team + that is committed to making their project a long term success. + </div> + </div> + <div className="py1"> + <div className="bold pb1">Early Backers & Advisors (10%)</div> + <div> + Our backers and advisors have provided capital, resources and guidance that have allowed + us to fill out our team, setup a robust legal entity and build a fully functional + product before launching a token. As a result, we have a proven track record and can + offer a token that holds genuine utility. + </div> + </div> + </div> + ), + }, + { + prompt: 'Can I mine ZRX tokens?', + answer: ( + <div> + No, the total supply of ZRX tokens is fixed and there is no continuous issuance model. Users + that facilitate trading over 0x protocol by operating a Relayer earn transaction fees + denominated in ZRX; as more trading activity is generated, more transaction fees are earned. + </div> + ), + }, + { + prompt: 'Will there be a lockup period for ZRX tokens sold in the token launch?', + answer: <div>No, ZRX tokens sold in the token launch will immediately be liquid.</div>, + }, + { + prompt: 'Will there be a lockup period for tokens allocated to the founding team?', + answer: ( + <div> + Yes. ZRX tokens allocated to founders, advisors and staff members will be released over a 4 year + vesting schedule with a 25% cliff upon completion of the initial token launch and 25% released + each subsequent year in monthly installments. Staff members hired after the token launch will + have a 4 year vesting schedule with a one year cliff. + </div> + ), + }, + { + prompt: 'Which cryptocurrencies will be accepted in the token launch?', + answer: <div>ETH.</div>, + }, + { + prompt: 'When will 0x be live?', + answer: ( + <div> + An alpha version of 0x has been live on our private test network since January 2017. Version 1.0 + of 0x protocol will be deployed to the canonical Ethereum blockchain after a round of security + audits and prior to the public token launch. 0x will be using the 0x protocol during our token + launch. + </div> + ), + }, + { + prompt: 'Where can I find a development roadmap?', + answer: ( + <div> + Check it out{' '} + <a + href="https://drive.google.com/open?id=14IP1N8mt3YdsAoqYTyruMnZswpklUs3THyS1VXx71fo" + target="_blank" + > + here + </a>. + </div> + ), + }, + ], + }, + { + name: 'Team', + questions: [ + { + prompt: 'Where is 0x based?', + answer: <div>0x was founded in SF and is driven by a diverse group of contributors.</div>, + }, + { + prompt: 'How can I get involved?', + answer: ( + <div> + Join our{' '} + <a href={constants.URL_ZEROEX_CHAT} target="_blank"> + Rocket.chat + </a>! As an open source project, 0x will rely on a worldwide community of passionate developers + to contribute proposals, ideas and code. + </div> + ), + }, + { + prompt: 'Why the name 0x?', + answer: ( + <div> + 0x is the prefix for hexadecimal numeric constants including Ethereum addresses. In a more + abstract context, as the first open protocol for exchange 0x represents the beginning of the end + for the exchange industry’s rent seeking oligopoly: zero exchange. + </div> + ), + }, + { + prompt: 'How do you pronounce 0x?', + answer: <div>We pronounce 0x as “zero-ex,” but you are free to pronounce it however you please.</div>, + }, + ], + }, ]; export class FAQ extends React.Component<FAQProps, FAQState> { - public componentDidMount() { - window.scrollTo(0, 0); - } - public render() { - return ( - <div> - <DocumentTitle title="0x FAQ" /> - <TopBar blockchainIsLoaded={false} location={this.props.location} /> - <div id="faq" className="mx-auto max-width-4 pt4" style={{ color: colors.grey800 }}> - <h1 className="center" style={{ ...styles.thin }}> - 0x FAQ - </h1> - <div className="sm-px2 md-px2 lg-px0 pb4">{this._renderSections()}</div> - </div> - <Footer /> - </div> - ); - } - private _renderSections() { - const renderedSections = _.map(sections, (section: FAQSection, i: number) => { - const isFirstSection = i === 0; - return ( - <div key={section.name}> - <h3>{section.name}</h3> - {this._renderQuestions(section.questions, isFirstSection)} - </div> - ); - }); - return renderedSections; - } - private _renderQuestions(questions: FAQQuestion[], isFirstSection: boolean) { - const renderedQuestions = _.map(questions, (question: FAQQuestion, i: number) => { - const isFirstQuestion = i === 0; - return ( - <Question - key={question.prompt} - prompt={question.prompt} - answer={question.answer} - shouldDisplayExpanded={isFirstSection && isFirstQuestion} - /> - ); - }); - return renderedQuestions; - } + public componentDidMount() { + window.scrollTo(0, 0); + } + public render() { + return ( + <div> + <DocumentTitle title="0x FAQ" /> + <TopBar blockchainIsLoaded={false} location={this.props.location} /> + <div id="faq" className="mx-auto max-width-4 pt4" style={{ color: colors.grey800 }}> + <h1 className="center" style={{ ...styles.thin }}> + 0x FAQ + </h1> + <div className="sm-px2 md-px2 lg-px0 pb4">{this._renderSections()}</div> + </div> + <Footer /> + </div> + ); + } + private _renderSections() { + const renderedSections = _.map(sections, (section: FAQSection, i: number) => { + const isFirstSection = i === 0; + return ( + <div key={section.name}> + <h3>{section.name}</h3> + {this._renderQuestions(section.questions, isFirstSection)} + </div> + ); + }); + return renderedSections; + } + private _renderQuestions(questions: FAQQuestion[], isFirstSection: boolean) { + const renderedQuestions = _.map(questions, (question: FAQQuestion, i: number) => { + const isFirstQuestion = i === 0; + return ( + <Question + key={question.prompt} + prompt={question.prompt} + answer={question.answer} + shouldDisplayExpanded={isFirstSection && isFirstQuestion} + /> + ); + }); + return renderedQuestions; + } } diff --git a/packages/website/ts/pages/faq/question.tsx b/packages/website/ts/pages/faq/question.tsx index 58cf674ef..988c04bc9 100644 --- a/packages/website/ts/pages/faq/question.tsx +++ b/packages/website/ts/pages/faq/question.tsx @@ -4,48 +4,48 @@ import * as React from 'react'; import { colors } from 'ts/utils/colors'; export interface QuestionProps { - prompt: string; - answer: React.ReactNode; - shouldDisplayExpanded: boolean; + prompt: string; + answer: React.ReactNode; + shouldDisplayExpanded: boolean; } interface QuestionState { - isExpanded: boolean; + isExpanded: boolean; } export class Question extends React.Component<QuestionProps, QuestionState> { - constructor(props: QuestionProps) { - super(props); - this.state = { - isExpanded: props.shouldDisplayExpanded, - }; - } - public render() { - return ( - <div className="py1"> - <Card - initiallyExpanded={this.props.shouldDisplayExpanded} - onExpandChange={this._onExchangeChange.bind(this)} - > - <CardHeader - title={this.props.prompt} - style={{ - borderBottom: this.state.isExpanded ? '1px solid rgba(0, 0, 0, 0.19)' : 'none', - }} - titleStyle={{ color: colors.darkerGrey }} - actAsExpander={true} - showExpandableButton={true} - /> - <CardText expandable={true}> - <div style={{ lineHeight: 1.4 }}>{this.props.answer}</div> - </CardText> - </Card> - </div> - ); - } - private _onExchangeChange() { - this.setState({ - isExpanded: !this.state.isExpanded, - }); - } + constructor(props: QuestionProps) { + super(props); + this.state = { + isExpanded: props.shouldDisplayExpanded, + }; + } + public render() { + return ( + <div className="py1"> + <Card + initiallyExpanded={this.props.shouldDisplayExpanded} + onExpandChange={this._onExchangeChange.bind(this)} + > + <CardHeader + title={this.props.prompt} + style={{ + borderBottom: this.state.isExpanded ? '1px solid rgba(0, 0, 0, 0.19)' : 'none', + }} + titleStyle={{ color: colors.darkerGrey }} + actAsExpander={true} + showExpandableButton={true} + /> + <CardText expandable={true}> + <div style={{ lineHeight: 1.4 }}>{this.props.answer}</div> + </CardText> + </Card> + </div> + ); + } + private _onExchangeChange() { + this.setState({ + isExpanded: !this.state.isExpanded, + }); + } } diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index 742d94a0f..ca76497df 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -11,755 +11,755 @@ import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; interface BoxContent { - title: string; - description: string; - imageUrl: string; - classNames: string; + title: string; + description: string; + imageUrl: string; + classNames: string; } interface AssetType { - title: string; - imageUrl: string; - style?: React.CSSProperties; + title: string; + imageUrl: string; + style?: React.CSSProperties; } interface UseCase { - imageUrl: string; - type: string; - description: string; - classNames: string; - style?: React.CSSProperties; - projectIconUrls: string[]; + imageUrl: string; + type: string; + description: string; + classNames: string; + style?: React.CSSProperties; + projectIconUrls: string[]; } interface Project { - logoFileName: string; - projectUrl: string; + logoFileName: string; + projectUrl: string; } const THROTTLE_TIMEOUT = 100; const boxContents: BoxContent[] = [ - { - title: 'Trustless exchange', - description: - "Built on Ethereum's distributed network with no centralized \ + { + title: 'Trustless exchange', + description: + "Built on Ethereum's distributed network with no centralized \ point of failure and no down time, each trade is settled atomically \ and without counterparty risk.", - imageUrl: '/images/landing/distributed_network.png', - classNames: '', - }, - { - title: 'Shared liquidity', - description: - 'By sharing a standard API, relayers can easily aggregate liquidity pools, \ + imageUrl: '/images/landing/distributed_network.png', + classNames: '', + }, + { + title: 'Shared liquidity', + description: + 'By sharing a standard API, relayers can easily aggregate liquidity pools, \ creating network effects around liquidity that compound as more relayers come online.', - imageUrl: '/images/landing/liquidity.png', - classNames: 'mx-auto', - }, - { - title: 'Open source', - description: - '0x is open source, permissionless and free to use. Trade directly with a known \ + imageUrl: '/images/landing/liquidity.png', + classNames: 'mx-auto', + }, + { + title: 'Open source', + description: + '0x is open source, permissionless and free to use. Trade directly with a known \ counterparty for free or pay a relayer some ZRX tokens to access their liquidity \ pool.', - imageUrl: '/images/landing/open_source.png', - classNames: 'right', - }, + imageUrl: '/images/landing/open_source.png', + classNames: 'right', + }, ]; const projects: Project[] = [ - { - logoFileName: 'ethfinex-top.png', - projectUrl: constants.PROJECT_URL_ETHFINEX, - }, - { - logoFileName: 'radar_relay_top.png', - projectUrl: constants.PROJECT_URL_RADAR_RELAY, - }, - { - logoFileName: 'paradex_top.png', - projectUrl: constants.PROJECT_URL_PARADEX, - }, - { - logoFileName: 'the_ocean.png', - projectUrl: constants.PROJECT_URL_0CEAN, - }, - { - logoFileName: 'dydx.png', - projectUrl: constants.PROJECT_URL_DYDX, - }, - { - logoFileName: 'melonport.png', - projectUrl: constants.PROJECT_URL_MELONPORT, - }, - { - logoFileName: 'maker.png', - projectUrl: constants.PROJECT_URL_MAKER, - }, - { - logoFileName: 'dharma.png', - projectUrl: constants.PROJECT_URL_DHARMA, - }, - { - logoFileName: 'lendroid.png', - projectUrl: constants.PROJECT_URL_LENDROID, - }, - { - logoFileName: 'district0x.png', - projectUrl: constants.PROJECT_URL_DISTRICT_0X, - }, - { - logoFileName: 'aragon.png', - projectUrl: constants.PROJECT_URL_ARAGON, - }, - { - logoFileName: 'blocknet.png', - projectUrl: constants.PROJECT_URL_BLOCKNET, - }, - { - logoFileName: 'status.png', - projectUrl: constants.PROJECT_URL_STATUS, - }, - { - logoFileName: 'augur.png', - projectUrl: constants.PROJECT_URL_AUGUR, - }, - { - logoFileName: 'anx.png', - projectUrl: constants.PROJECT_URL_OPEN_ANX, - }, - { - logoFileName: 'auctus.png', - projectUrl: constants.PROJECT_URL_AUCTUS, - }, + { + logoFileName: 'ethfinex-top.png', + projectUrl: constants.PROJECT_URL_ETHFINEX, + }, + { + logoFileName: 'radar_relay_top.png', + projectUrl: constants.PROJECT_URL_RADAR_RELAY, + }, + { + logoFileName: 'paradex_top.png', + projectUrl: constants.PROJECT_URL_PARADEX, + }, + { + logoFileName: 'the_ocean.png', + projectUrl: constants.PROJECT_URL_0CEAN, + }, + { + logoFileName: 'dydx.png', + projectUrl: constants.PROJECT_URL_DYDX, + }, + { + logoFileName: 'melonport.png', + projectUrl: constants.PROJECT_URL_MELONPORT, + }, + { + logoFileName: 'maker.png', + projectUrl: constants.PROJECT_URL_MAKER, + }, + { + logoFileName: 'dharma.png', + projectUrl: constants.PROJECT_URL_DHARMA, + }, + { + logoFileName: 'lendroid.png', + projectUrl: constants.PROJECT_URL_LENDROID, + }, + { + logoFileName: 'district0x.png', + projectUrl: constants.PROJECT_URL_DISTRICT_0X, + }, + { + logoFileName: 'aragon.png', + projectUrl: constants.PROJECT_URL_ARAGON, + }, + { + logoFileName: 'blocknet.png', + projectUrl: constants.PROJECT_URL_BLOCKNET, + }, + { + logoFileName: 'status.png', + projectUrl: constants.PROJECT_URL_STATUS, + }, + { + logoFileName: 'augur.png', + projectUrl: constants.PROJECT_URL_AUGUR, + }, + { + logoFileName: 'anx.png', + projectUrl: constants.PROJECT_URL_OPEN_ANX, + }, + { + logoFileName: 'auctus.png', + projectUrl: constants.PROJECT_URL_AUCTUS, + }, ]; export interface LandingProps { - location: Location; + location: Location; } interface LandingState { - screenWidth: ScreenWidths; + screenWidth: ScreenWidths; } export class Landing extends React.Component<LandingProps, LandingState> { - private _throttledScreenWidthUpdate: () => void; - constructor(props: LandingProps) { - super(props); - this.state = { - screenWidth: utils.getScreenWidth(), - }; - this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); - } - public componentDidMount() { - window.addEventListener('resize', this._throttledScreenWidthUpdate); - window.scrollTo(0, 0); - } - public componentWillUnmount() { - window.removeEventListener('resize', this._throttledScreenWidthUpdate); - } - public render() { - return ( - <div id="landing" className="clearfix" style={{ color: colors.grey500 }}> - <DocumentTitle title="0x Protocol" /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - isNightVersion={true} - style={{ backgroundColor: colors.heroGrey, position: 'relative' }} - /> - {this._renderHero()} - {this._renderProjects()} - {this._renderTokenizationSection()} - {this._renderProtocolSection()} - {this._renderInfoBoxes()} - {this._renderBuildingBlocksSection()} - {this._renderUseCases()} - {this._renderCallToAction()} - <Footer /> - </div> - ); - } - private _renderHero() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const buttonLabelStyle: React.CSSProperties = { - textTransform: 'none', - fontSize: isSmallScreen ? 12 : 14, - fontWeight: 400, - }; - const lightButtonStyle: React.CSSProperties = { - borderRadius: 6, - border: '1px solid #D8D8D8', - lineHeight: '33px', - height: 38, - }; - const left = 'col lg-col-7 md-col-7 col-12 lg-pt4 md-pt4 sm-pt0 mt1 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center'; - return ( - <div className="clearfix py4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto max-width-4 clearfix"> - <div className="lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-my4 md-my4 sm-mt2 sm-mb4 clearfix"> - <div className="col lg-col-5 md-col-5 col-12 sm-center"> - <img src="/images/landing/hero_chip_image.png" height={isSmallScreen ? 300 : 395} /> - </div> - <div className={left} style={{ color: colors.white }}> - <div style={{ paddingLeft: isSmallScreen ? 0 : 12 }}> - <div - className="sm-pb2" - style={{ - fontFamily: 'Roboto Mono', - fontSize: isSmallScreen ? 26 : 34, - }} - > - Powering decentralized exchange - </div> - <div - className="pt2 h5 sm-mx-auto" - style={{ - maxWidth: 446, - fontFamily: 'Roboto Mono', - lineHeight: 1.7, - fontWeight: 300, - }} - > - 0x is an open, permissionless protocol allowing for ERC20 tokens to be traded on the - Ethereum blockchain. - </div> - <div className="pt3 clearfix sm-mx-auto" style={{ maxWidth: 342 }}> - <div className="lg-pr2 md-pr2 col col-6 sm-center"> - <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <RaisedButton - style={{ borderRadius: 6, minWidth: 157.36 }} - buttonStyle={{ borderRadius: 6 }} - labelStyle={buttonLabelStyle} - label="Build on 0x" - onClick={_.noop} - /> - </Link> - </div> - <div className="col col-6 sm-center"> - <a - href={constants.URL_ZEROEX_CHAT} - target="_blank" - className="text-decoration-none" - > - <RaisedButton - style={{ borderRadius: 6, minWidth: 150 }} - buttonStyle={lightButtonStyle} - labelColor="white" - backgroundColor={colors.heroGrey} - labelStyle={buttonLabelStyle} - label="Join the community" - onClick={_.noop} - /> - </a> - </div> - </div> - </div> - </div> - </div> - </div> - </div> - ); - } - private _renderProjects() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const isMediumScreen = this.state.screenWidth === ScreenWidths.Md; - const projectList = _.map(projects, (project: Project, i: number) => { - const colWidth = isSmallScreen ? 3 : isMediumScreen ? 4 : 2 - i % 2; - return ( - <div key={`project-${project.logoFileName}`} className={`col col-${colWidth} center`}> - <div> - <a href={project.projectUrl} target="_blank" className="text-decoration-none"> - <img - src={`/images/landing/project_logos/${project.logoFileName}`} - height={isSmallScreen ? 60 : 92} - /> - </a> - </div> - </div> - ); - }); - const titleStyle: React.CSSProperties = { - fontFamily: 'Roboto Mono', - color: colors.grey, - textTransform: 'uppercase', - fontWeight: 300, - letterSpacing: 3, - }; - return ( - <div className="clearfix py4" style={{ backgroundColor: colors.projectsGrey }}> - <div className="mx-auto max-width-4 clearfix sm-px3"> - <div className="h4 pb3 md-pl3 sm-pl2" style={titleStyle}> - Projects building on 0x - </div> - <div className="clearfix">{projectList}</div> - <div - className="pt3 mx-auto center" - style={{ - color: colors.landingLinkGrey, - fontFamily: 'Roboto Mono', - maxWidth: 300, - fontSize: 14, - }} - > - view the{' '} - <Link - to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`} - className="text-decoration-none underline" - style={{ color: colors.landingLinkGrey }} - > - full list - </Link> - </div> - </div> - </div> - ); - } - private _renderTokenizationSection() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - return ( - <div className="clearfix lg-py4 md-py4 sm-pb4 sm-pt2" style={{ backgroundColor: colors.grey100 }}> - <div className="mx-auto max-width-4 py4 clearfix"> - {isSmallScreen && this._renderTokenCloud()} - <div className="col lg-col-6 md-col-6 col-12"> - <div className="mx-auto" style={{ maxWidth: 385, paddingTop: 7 }}> - <div className="lg-h1 md-h1 sm-h2 sm-center sm-pt3" style={{ fontFamily: 'Roboto Mono' }}> - The world's value is becoming tokenized - </div> - <div - className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h5 sm-center" - style={{ fontFamily: 'Roboto Mono', lineHeight: 1.7 }} - > - {isSmallScreen ? ( - <span> - The Ethereum blockchain is an open, borderless financial system that represents - a wide variety of assets as cryptographic tokens. In the future, most digital - assets and goods will be tokenized. - </span> - ) : ( - <div> - <div> - The Ethereum blockchain is an open, borderless financial system that - represents - </div> - <div> - a wide variety of assets as cryptographic tokens. In the future, most - digital assets and goods will be tokenized. - </div> - </div> - )} - </div> - <div className="flex pt1 sm-px3">{this._renderAssetTypes()}</div> - </div> - </div> - {!isSmallScreen && this._renderTokenCloud()} - </div> - </div> - ); - } - private _renderProtocolSection() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - return ( - <div className="clearfix lg-py4 md-py4 sm-pt4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto max-width-4 lg-py4 md-py4 sm-pt4 clearfix"> - <div className="col lg-col-6 md-col-6 col-12 sm-center"> - <img src="/images/landing/relayer_diagram.png" height={isSmallScreen ? 326 : 426} /> - </div> - <div - className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-mx-auto" - style={{ - color: colors.beigeWhite, - paddingTop: 8, - maxWidth: isSmallScreen ? 'none' : 445, - }} - > - <div className="lg-h1 md-h1 sm-h2 pb1 sm-pt3 sm-center" style={{ fontFamily: 'Roboto Mono' }}> - <div>Off-chain order relay</div> - <div>On-chain settlement</div> - </div> - <div - className="pb2 pt2 h5 sm-center sm-px3 sm-mx-auto" - style={{ - fontFamily: 'Roboto Mono', - lineHeight: 1.7, - fontWeight: 300, - maxWidth: 445, - }} - > - In 0x protocol, orders are transported off-chain, massively reducing gas costs and - eliminating blockchain bloat. Relayers help broadcast orders and collect a fee each time - they facilitate a trade. Anyone can build a relayer. - </div> - <div - className="pt3 sm-mx-auto sm-px3" - style={{ - color: colors.landingLinkGrey, - maxWidth: isSmallScreen ? 412 : 'none', - }} - > - <div className="flex" style={{ fontSize: 18 }}> - <div - className="lg-h4 md-h4 sm-h5" - style={{ - letterSpacing: isSmallScreen ? 1 : 3, - fontFamily: 'Roboto Mono', - }} - > - RELAYERS BUILDING ON 0X - </div> - <div className="h5" style={{ marginLeft: isSmallScreen ? 26 : 49 }}> - <Link - to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`} - className="text-decoration-none underline" - style={{ - color: colors.landingLinkGrey, - fontFamily: 'Roboto Mono', - }} - > - view all - </Link> - </div> - </div> - <div className="lg-flex md-flex sm-clearfix pt3" style={{ opacity: 0.4 }}> - <div className="col col-4 sm-center"> - <img - src="/images/landing/ethfinex.png" - style={{ height: isSmallScreen ? 85 : 107 }} - /> - </div> - <div className="col col-4 center"> - <img - src="/images/landing/radar_relay.png" - style={{ height: isSmallScreen ? 85 : 107 }} - /> - </div> - <div className="col col-4 sm-center" style={{ textAlign: 'right' }}> - <img - src="/images/landing/paradex.png" - style={{ height: isSmallScreen ? 85 : 107 }} - /> - </div> - </div> - </div> - </div> - </div> - </div> - ); - } - private _renderBuildingBlocksSection() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const descriptionStyle: React.CSSProperties = { - fontFamily: 'Roboto Mono', - lineHeight: isSmallScreen ? 1.5 : 2, - fontWeight: 300, - fontSize: 15, - maxWidth: isSmallScreen ? 375 : 'none', - }; - const callToActionStyle: React.CSSProperties = { - fontFamily: 'Roboto Mono', - fontSize: 15, - fontWeight: 300, - maxWidth: isSmallScreen ? 375 : 441, - }; - return ( - <div className="clearfix lg-pt4 md-pt4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto max-width-4 lg-pt4 md-pt4 lg-mb4 md-mb4 sm-mb2 clearfix"> - {isSmallScreen && this._renderBlockChipImage()} - <div - className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-px3" - style={{ color: colors.beigeWhite }} - > - <div - className="pb1 lg-pt4 md-pt4 sm-pt3 lg-h1 md-h1 sm-h2 sm-px3 sm-center" - style={{ fontFamily: 'Roboto Mono' }} - > - A building block for dApps - </div> - <div className="pb3 pt2 sm-mx-auto sm-center" style={descriptionStyle}> - 0x protocol is a pluggable building block for dApps that require exchange functionality. - Join the many developers that are already using 0x in their web applications and smart - contracts. - </div> - <div className="sm-mx-auto sm-center" style={callToActionStyle}> - Learn how in our{' '} - <Link - to={WebsitePaths.ZeroExJs} - className="text-decoration-none underline" - style={{ color: colors.beigeWhite, fontFamily: 'Roboto Mono' }} - > - 0x.js - </Link>{' '} - and{' '} - <Link - to={WebsitePaths.SmartContracts} - className="text-decoration-none underline" - style={{ color: colors.beigeWhite, fontFamily: 'Roboto Mono' }} - > - smart contract - </Link>{' '} - docs - </div> - </div> - {!isSmallScreen && this._renderBlockChipImage()} - </div> - </div> - ); - } - private _renderBlockChipImage() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - return ( - <div className="col lg-col-6 md-col-6 col-12 sm-center"> - <img src="/images/landing/0x_chips.png" height={isSmallScreen ? 240 : 368} /> - </div> - ); - } - private _renderTokenCloud() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - return ( - <div className="col lg-col-6 md-col-6 col-12 center"> - <img src="/images/landing/tokenized_world.png" height={isSmallScreen ? 280 : 364.5} /> - </div> - ); - } - private _renderAssetTypes() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const assetTypes: AssetType[] = [ - { - title: 'Currency', - imageUrl: '/images/landing/currency.png', - }, - { - title: 'Traditional assets', - imageUrl: '/images/landing/stocks.png', - style: { - paddingLeft: isSmallScreen ? 41 : 56, - paddingRight: isSmallScreen ? 41 : 56, - }, - }, - { - title: 'Digital goods', - imageUrl: '/images/landing/digital_goods.png', - }, - ]; - const assets = _.map(assetTypes, (assetType: AssetType) => { - const style = _.isUndefined(assetType.style) ? {} : assetType.style; - return ( - <div key={`asset-${assetType.title}`} className="center" style={{ opacity: 0.8, ...style }}> - <div> - <img src={assetType.imageUrl} height="80" /> - </div> - <div - style={{ - fontFamily: 'Roboto Mono', - fontSize: 13.5, - fontWeight: 400, - opacity: 0.75, - }} - > - {assetType.title} - </div> - </div> - ); - }); - return assets; - } - private _renderInfoBoxes() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const boxStyle: React.CSSProperties = { - maxWidth: 252, - height: 386, - backgroundColor: colors.grey50, - borderRadius: 5, - padding: '10px 24px 24px', - }; - const boxes = _.map(boxContents, (boxContent: BoxContent) => { - return ( - <div key={`box-${boxContent.title}`} className="col lg-col-4 md-col-4 col-12 sm-pb4"> - <div className={`center sm-mx-auto ${!isSmallScreen && boxContent.classNames}`} style={boxStyle}> - <div> - <img src={boxContent.imageUrl} style={{ height: 210 }} /> - </div> - <div className="h3" style={{ color: 'black', fontFamily: 'Roboto Mono' }}> - {boxContent.title} - </div> - <div className="pt2 pb2" style={{ fontFamily: 'Roboto Mono', fontSize: 14 }}> - {boxContent.description} - </div> - </div> - </div> - ); - }); - return ( - <div className="clearfix" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto py4 sm-mt2 clearfix" style={{ maxWidth: '60em' }}> - {boxes} - </div> - </div> - ); - } - private _renderUseCases() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + private _throttledScreenWidthUpdate: () => void; + constructor(props: LandingProps) { + super(props); + this.state = { + screenWidth: utils.getScreenWidth(), + }; + this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + } + public componentDidMount() { + window.addEventListener('resize', this._throttledScreenWidthUpdate); + window.scrollTo(0, 0); + } + public componentWillUnmount() { + window.removeEventListener('resize', this._throttledScreenWidthUpdate); + } + public render() { + return ( + <div id="landing" className="clearfix" style={{ color: colors.grey500 }}> + <DocumentTitle title="0x Protocol" /> + <TopBar + blockchainIsLoaded={false} + location={this.props.location} + isNightVersion={true} + style={{ backgroundColor: colors.heroGrey, position: 'relative' }} + /> + {this._renderHero()} + {this._renderProjects()} + {this._renderTokenizationSection()} + {this._renderProtocolSection()} + {this._renderInfoBoxes()} + {this._renderBuildingBlocksSection()} + {this._renderUseCases()} + {this._renderCallToAction()} + <Footer /> + </div> + ); + } + private _renderHero() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const buttonLabelStyle: React.CSSProperties = { + textTransform: 'none', + fontSize: isSmallScreen ? 12 : 14, + fontWeight: 400, + }; + const lightButtonStyle: React.CSSProperties = { + borderRadius: 6, + border: '1px solid #D8D8D8', + lineHeight: '33px', + height: 38, + }; + const left = 'col lg-col-7 md-col-7 col-12 lg-pt4 md-pt4 sm-pt0 mt1 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center'; + return ( + <div className="clearfix py4" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto max-width-4 clearfix"> + <div className="lg-pt4 md-pt4 sm-pt2 lg-pb4 md-pb4 lg-my4 md-my4 sm-mt2 sm-mb4 clearfix"> + <div className="col lg-col-5 md-col-5 col-12 sm-center"> + <img src="/images/landing/hero_chip_image.png" height={isSmallScreen ? 300 : 395} /> + </div> + <div className={left} style={{ color: colors.white }}> + <div style={{ paddingLeft: isSmallScreen ? 0 : 12 }}> + <div + className="sm-pb2" + style={{ + fontFamily: 'Roboto Mono', + fontSize: isSmallScreen ? 26 : 34, + }} + > + Powering decentralized exchange + </div> + <div + className="pt2 h5 sm-mx-auto" + style={{ + maxWidth: 446, + fontFamily: 'Roboto Mono', + lineHeight: 1.7, + fontWeight: 300, + }} + > + 0x is an open, permissionless protocol allowing for ERC20 tokens to be traded on the + Ethereum blockchain. + </div> + <div className="pt3 clearfix sm-mx-auto" style={{ maxWidth: 342 }}> + <div className="lg-pr2 md-pr2 col col-6 sm-center"> + <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> + <RaisedButton + style={{ borderRadius: 6, minWidth: 157.36 }} + buttonStyle={{ borderRadius: 6 }} + labelStyle={buttonLabelStyle} + label="Build on 0x" + onClick={_.noop} + /> + </Link> + </div> + <div className="col col-6 sm-center"> + <a + href={constants.URL_ZEROEX_CHAT} + target="_blank" + className="text-decoration-none" + > + <RaisedButton + style={{ borderRadius: 6, minWidth: 150 }} + buttonStyle={lightButtonStyle} + labelColor="white" + backgroundColor={colors.heroGrey} + labelStyle={buttonLabelStyle} + label="Join the community" + onClick={_.noop} + /> + </a> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + ); + } + private _renderProjects() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const isMediumScreen = this.state.screenWidth === ScreenWidths.Md; + const projectList = _.map(projects, (project: Project, i: number) => { + const colWidth = isSmallScreen ? 3 : isMediumScreen ? 4 : 2 - i % 2; + return ( + <div key={`project-${project.logoFileName}`} className={`col col-${colWidth} center`}> + <div> + <a href={project.projectUrl} target="_blank" className="text-decoration-none"> + <img + src={`/images/landing/project_logos/${project.logoFileName}`} + height={isSmallScreen ? 60 : 92} + /> + </a> + </div> + </div> + ); + }); + const titleStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + color: colors.grey, + textTransform: 'uppercase', + fontWeight: 300, + letterSpacing: 3, + }; + return ( + <div className="clearfix py4" style={{ backgroundColor: colors.projectsGrey }}> + <div className="mx-auto max-width-4 clearfix sm-px3"> + <div className="h4 pb3 md-pl3 sm-pl2" style={titleStyle}> + Projects building on 0x + </div> + <div className="clearfix">{projectList}</div> + <div + className="pt3 mx-auto center" + style={{ + color: colors.landingLinkGrey, + fontFamily: 'Roboto Mono', + maxWidth: 300, + fontSize: 14, + }} + > + view the{' '} + <Link + to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`} + className="text-decoration-none underline" + style={{ color: colors.landingLinkGrey }} + > + full list + </Link> + </div> + </div> + </div> + ); + } + private _renderTokenizationSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + return ( + <div className="clearfix lg-py4 md-py4 sm-pb4 sm-pt2" style={{ backgroundColor: colors.grey100 }}> + <div className="mx-auto max-width-4 py4 clearfix"> + {isSmallScreen && this._renderTokenCloud()} + <div className="col lg-col-6 md-col-6 col-12"> + <div className="mx-auto" style={{ maxWidth: 385, paddingTop: 7 }}> + <div className="lg-h1 md-h1 sm-h2 sm-center sm-pt3" style={{ fontFamily: 'Roboto Mono' }}> + The world's value is becoming tokenized + </div> + <div + className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h5 sm-center" + style={{ fontFamily: 'Roboto Mono', lineHeight: 1.7 }} + > + {isSmallScreen ? ( + <span> + The Ethereum blockchain is an open, borderless financial system that represents + a wide variety of assets as cryptographic tokens. In the future, most digital + assets and goods will be tokenized. + </span> + ) : ( + <div> + <div> + The Ethereum blockchain is an open, borderless financial system that + represents + </div> + <div> + a wide variety of assets as cryptographic tokens. In the future, most + digital assets and goods will be tokenized. + </div> + </div> + )} + </div> + <div className="flex pt1 sm-px3">{this._renderAssetTypes()}</div> + </div> + </div> + {!isSmallScreen && this._renderTokenCloud()} + </div> + </div> + ); + } + private _renderProtocolSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + return ( + <div className="clearfix lg-py4 md-py4 sm-pt4" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto max-width-4 lg-py4 md-py4 sm-pt4 clearfix"> + <div className="col lg-col-6 md-col-6 col-12 sm-center"> + <img src="/images/landing/relayer_diagram.png" height={isSmallScreen ? 326 : 426} /> + </div> + <div + className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-mx-auto" + style={{ + color: colors.beigeWhite, + paddingTop: 8, + maxWidth: isSmallScreen ? 'none' : 445, + }} + > + <div className="lg-h1 md-h1 sm-h2 pb1 sm-pt3 sm-center" style={{ fontFamily: 'Roboto Mono' }}> + <div>Off-chain order relay</div> + <div>On-chain settlement</div> + </div> + <div + className="pb2 pt2 h5 sm-center sm-px3 sm-mx-auto" + style={{ + fontFamily: 'Roboto Mono', + lineHeight: 1.7, + fontWeight: 300, + maxWidth: 445, + }} + > + In 0x protocol, orders are transported off-chain, massively reducing gas costs and + eliminating blockchain bloat. Relayers help broadcast orders and collect a fee each time + they facilitate a trade. Anyone can build a relayer. + </div> + <div + className="pt3 sm-mx-auto sm-px3" + style={{ + color: colors.landingLinkGrey, + maxWidth: isSmallScreen ? 412 : 'none', + }} + > + <div className="flex" style={{ fontSize: 18 }}> + <div + className="lg-h4 md-h4 sm-h5" + style={{ + letterSpacing: isSmallScreen ? 1 : 3, + fontFamily: 'Roboto Mono', + }} + > + RELAYERS BUILDING ON 0X + </div> + <div className="h5" style={{ marginLeft: isSmallScreen ? 26 : 49 }}> + <Link + to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`} + className="text-decoration-none underline" + style={{ + color: colors.landingLinkGrey, + fontFamily: 'Roboto Mono', + }} + > + view all + </Link> + </div> + </div> + <div className="lg-flex md-flex sm-clearfix pt3" style={{ opacity: 0.4 }}> + <div className="col col-4 sm-center"> + <img + src="/images/landing/ethfinex.png" + style={{ height: isSmallScreen ? 85 : 107 }} + /> + </div> + <div className="col col-4 center"> + <img + src="/images/landing/radar_relay.png" + style={{ height: isSmallScreen ? 85 : 107 }} + /> + </div> + <div className="col col-4 sm-center" style={{ textAlign: 'right' }}> + <img + src="/images/landing/paradex.png" + style={{ height: isSmallScreen ? 85 : 107 }} + /> + </div> + </div> + </div> + </div> + </div> + </div> + ); + } + private _renderBuildingBlocksSection() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const descriptionStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + lineHeight: isSmallScreen ? 1.5 : 2, + fontWeight: 300, + fontSize: 15, + maxWidth: isSmallScreen ? 375 : 'none', + }; + const callToActionStyle: React.CSSProperties = { + fontFamily: 'Roboto Mono', + fontSize: 15, + fontWeight: 300, + maxWidth: isSmallScreen ? 375 : 441, + }; + return ( + <div className="clearfix lg-pt4 md-pt4" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto max-width-4 lg-pt4 md-pt4 lg-mb4 md-mb4 sm-mb2 clearfix"> + {isSmallScreen && this._renderBlockChipImage()} + <div + className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-px3" + style={{ color: colors.beigeWhite }} + > + <div + className="pb1 lg-pt4 md-pt4 sm-pt3 lg-h1 md-h1 sm-h2 sm-px3 sm-center" + style={{ fontFamily: 'Roboto Mono' }} + > + A building block for dApps + </div> + <div className="pb3 pt2 sm-mx-auto sm-center" style={descriptionStyle}> + 0x protocol is a pluggable building block for dApps that require exchange functionality. + Join the many developers that are already using 0x in their web applications and smart + contracts. + </div> + <div className="sm-mx-auto sm-center" style={callToActionStyle}> + Learn how in our{' '} + <Link + to={WebsitePaths.ZeroExJs} + className="text-decoration-none underline" + style={{ color: colors.beigeWhite, fontFamily: 'Roboto Mono' }} + > + 0x.js + </Link>{' '} + and{' '} + <Link + to={WebsitePaths.SmartContracts} + className="text-decoration-none underline" + style={{ color: colors.beigeWhite, fontFamily: 'Roboto Mono' }} + > + smart contract + </Link>{' '} + docs + </div> + </div> + {!isSmallScreen && this._renderBlockChipImage()} + </div> + </div> + ); + } + private _renderBlockChipImage() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + return ( + <div className="col lg-col-6 md-col-6 col-12 sm-center"> + <img src="/images/landing/0x_chips.png" height={isSmallScreen ? 240 : 368} /> + </div> + ); + } + private _renderTokenCloud() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + return ( + <div className="col lg-col-6 md-col-6 col-12 center"> + <img src="/images/landing/tokenized_world.png" height={isSmallScreen ? 280 : 364.5} /> + </div> + ); + } + private _renderAssetTypes() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const assetTypes: AssetType[] = [ + { + title: 'Currency', + imageUrl: '/images/landing/currency.png', + }, + { + title: 'Traditional assets', + imageUrl: '/images/landing/stocks.png', + style: { + paddingLeft: isSmallScreen ? 41 : 56, + paddingRight: isSmallScreen ? 41 : 56, + }, + }, + { + title: 'Digital goods', + imageUrl: '/images/landing/digital_goods.png', + }, + ]; + const assets = _.map(assetTypes, (assetType: AssetType) => { + const style = _.isUndefined(assetType.style) ? {} : assetType.style; + return ( + <div key={`asset-${assetType.title}`} className="center" style={{ opacity: 0.8, ...style }}> + <div> + <img src={assetType.imageUrl} height="80" /> + </div> + <div + style={{ + fontFamily: 'Roboto Mono', + fontSize: 13.5, + fontWeight: 400, + opacity: 0.75, + }} + > + {assetType.title} + </div> + </div> + ); + }); + return assets; + } + private _renderInfoBoxes() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const boxStyle: React.CSSProperties = { + maxWidth: 252, + height: 386, + backgroundColor: colors.grey50, + borderRadius: 5, + padding: '10px 24px 24px', + }; + const boxes = _.map(boxContents, (boxContent: BoxContent) => { + return ( + <div key={`box-${boxContent.title}`} className="col lg-col-4 md-col-4 col-12 sm-pb4"> + <div className={`center sm-mx-auto ${!isSmallScreen && boxContent.classNames}`} style={boxStyle}> + <div> + <img src={boxContent.imageUrl} style={{ height: 210 }} /> + </div> + <div className="h3" style={{ color: 'black', fontFamily: 'Roboto Mono' }}> + {boxContent.title} + </div> + <div className="pt2 pb2" style={{ fontFamily: 'Roboto Mono', fontSize: 14 }}> + {boxContent.description} + </div> + </div> + </div> + ); + }); + return ( + <div className="clearfix" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto py4 sm-mt2 clearfix" style={{ maxWidth: '60em' }}> + {boxes} + </div> + </div> + ); + } + private _renderUseCases() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const useCases: UseCase[] = [ - { - imageUrl: '/images/landing/governance_icon.png', - type: 'Decentralized governance', - description: - 'Decentralized organizations use tokens to represent ownership and \ + const useCases: UseCase[] = [ + { + imageUrl: '/images/landing/governance_icon.png', + type: 'Decentralized governance', + description: + 'Decentralized organizations use tokens to represent ownership and \ guide their governance logic. 0x allows decentralized organizations \ to seamlessly and safely trade ownership for startup capital.', - projectIconUrls: ['/images/landing/aragon.png'], - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/prediction_market_icon.png', - type: 'Prediction markets', - description: - 'Decentralized prediction market platforms generate sets of tokens that \ + projectIconUrls: ['/images/landing/aragon.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/prediction_market_icon.png', + type: 'Prediction markets', + description: + 'Decentralized prediction market platforms generate sets of tokens that \ represent a financial stake in the outcomes of real-world events. 0x allows \ these tokens to be instantly tradable.', - projectIconUrls: ['/images/landing/augur.png'], - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/stable_tokens_icon.png', - type: 'Stable tokens', - description: - 'Novel economic constructs such as stable coins require efficient, liquid \ + projectIconUrls: ['/images/landing/augur.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/stable_tokens_icon.png', + type: 'Stable tokens', + description: + 'Novel economic constructs such as stable coins require efficient, liquid \ markets to succeed. 0x will facilitate the underlying economic mechanisms \ that allow these tokens to remain stable.', - projectIconUrls: ['/images/landing/maker.png'], - classNames: 'lg-px2 md-px2', - }, - { - imageUrl: '/images/landing/loans_icon.png', - type: 'Decentralized loans', - description: - 'Efficient lending requires liquid markets where investors can buy and re-sell loans. \ + projectIconUrls: ['/images/landing/maker.png'], + classNames: 'lg-px2 md-px2', + }, + { + imageUrl: '/images/landing/loans_icon.png', + type: 'Decentralized loans', + description: + 'Efficient lending requires liquid markets where investors can buy and re-sell loans. \ 0x enables an ecosystem of lenders to self-organize and efficiently determine \ market prices for all outstanding loans.', - projectIconUrls: ['/images/landing/dharma.png', '/images/landing/lendroid.png'], - classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6', - style: { - width: 291, - float: 'right', - marginTop: !isSmallScreen ? 38 : 0, - }, - }, - { - imageUrl: '/images/landing/fund_management_icon.png', - type: 'Fund management', - description: - 'Decentralized fund management limits fund managers to investing in pre-agreed \ + projectIconUrls: ['/images/landing/dharma.png', '/images/landing/lendroid.png'], + classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6', + style: { + width: 291, + float: 'right', + marginTop: !isSmallScreen ? 38 : 0, + }, + }, + { + imageUrl: '/images/landing/fund_management_icon.png', + type: 'Fund management', + description: + 'Decentralized fund management limits fund managers to investing in pre-agreed \ upon asset classes. Embedding 0x into fund management smart contracts enables \ them to enforce these security constraints.', - projectIconUrls: ['/images/landing/melonport.png'], - classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6', - style: { width: 291, marginTop: !isSmallScreen ? 38 : 0 }, - }, - ]; + projectIconUrls: ['/images/landing/melonport.png'], + classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6', + style: { width: 291, marginTop: !isSmallScreen ? 38 : 0 }, + }, + ]; - const cases = _.map(useCases, (useCase: UseCase) => { - const style = _.isUndefined(useCase.style) || isSmallScreen ? {} : useCase.style; - const useCaseBoxStyle = { - color: colors.grey, - border: '1px solid #565656', - borderRadius: 4, - maxWidth: isSmallScreen ? 375 : 'none', - ...style, - }; - const typeStyle: React.CSSProperties = { - color: colors.lightGrey, - fontSize: 13, - textTransform: 'uppercase', - fontFamily: 'Roboto Mono', - fontWeight: 300, - }; - return ( - <div - key={`useCase-${useCase.type}`} - className={`col lg-col-4 md-col-4 col-12 sm-pt3 sm-px3 sm-pb3 ${useCase.classNames}`} - > - <div className="relative p2 pb2 sm-mx-auto" style={useCaseBoxStyle}> - <div className="absolute center" style={{ top: -35, width: 'calc(100% - 32px)' }}> - <img src={useCase.imageUrl} style={{ height: 50 }} /> - </div> - <div className="pt2 center" style={typeStyle}> - {useCase.type} - </div> - <div - className="pt2" - style={{ - lineHeight: 1.5, - fontSize: 14, - overflow: 'hidden', - height: 104, - }} - > - {useCase.description} - </div> - </div> - </div> - ); - }); - return ( - <div className="clearfix pb4 lg-pt2 md-pt2 sm-pt4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto pb4 pt3 mt1 sm-mt2 clearfix" style={{ maxWidth: '67em' }}> - {cases} - </div> - </div> - ); - } - private _renderCallToAction() { - const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const buttonLabelStyle: React.CSSProperties = { - textTransform: 'none', - fontSize: 15, - fontWeight: 400, - }; - const lightButtonStyle: React.CSSProperties = { - borderRadius: 6, - border: '1px solid #a0a0a0', - lineHeight: '33px', - height: 49, - }; - const callToActionClassNames = - 'col lg-col-8 md-col-8 col-12 lg-pr3 md-pr3 \ + const cases = _.map(useCases, (useCase: UseCase) => { + const style = _.isUndefined(useCase.style) || isSmallScreen ? {} : useCase.style; + const useCaseBoxStyle = { + color: colors.grey, + border: '1px solid #565656', + borderRadius: 4, + maxWidth: isSmallScreen ? 375 : 'none', + ...style, + }; + const typeStyle: React.CSSProperties = { + color: colors.lightGrey, + fontSize: 13, + textTransform: 'uppercase', + fontFamily: 'Roboto Mono', + fontWeight: 300, + }; + return ( + <div + key={`useCase-${useCase.type}`} + className={`col lg-col-4 md-col-4 col-12 sm-pt3 sm-px3 sm-pb3 ${useCase.classNames}`} + > + <div className="relative p2 pb2 sm-mx-auto" style={useCaseBoxStyle}> + <div className="absolute center" style={{ top: -35, width: 'calc(100% - 32px)' }}> + <img src={useCase.imageUrl} style={{ height: 50 }} /> + </div> + <div className="pt2 center" style={typeStyle}> + {useCase.type} + </div> + <div + className="pt2" + style={{ + lineHeight: 1.5, + fontSize: 14, + overflow: 'hidden', + height: 104, + }} + > + {useCase.description} + </div> + </div> + </div> + ); + }); + return ( + <div className="clearfix pb4 lg-pt2 md-pt2 sm-pt4" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto pb4 pt3 mt1 sm-mt2 clearfix" style={{ maxWidth: '67em' }}> + {cases} + </div> + </div> + ); + } + private _renderCallToAction() { + const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; + const buttonLabelStyle: React.CSSProperties = { + textTransform: 'none', + fontSize: 15, + fontWeight: 400, + }; + const lightButtonStyle: React.CSSProperties = { + borderRadius: 6, + border: '1px solid #a0a0a0', + lineHeight: '33px', + height: 49, + }; + const callToActionClassNames = + 'col lg-col-8 md-col-8 col-12 lg-pr3 md-pr3 \ lg-right-align md-right-align sm-center sm-px3 h4'; - return ( - <div className="clearfix pb4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto max-width-4 pb4 mb3 clearfix"> - <div - className={callToActionClassNames} - style={{ - fontFamily: 'Roboto Mono', - color: colors.white, - lineHeight: isSmallScreen ? 1.7 : 3, - }} - > - Get started on building the decentralized future - </div> - <div className="col lg-col-4 md-col-4 col-12 sm-center sm-pt2"> - <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <RaisedButton - style={{ borderRadius: 6, minWidth: 150 }} - buttonStyle={lightButtonStyle} - labelColor={colors.white} - backgroundColor={colors.heroGrey} - labelStyle={buttonLabelStyle} - label="Build on 0x" - onClick={_.noop} - /> - </Link> - </div> - </div> - </div> - ); - } - private _updateScreenWidth() { - const newScreenWidth = utils.getScreenWidth(); - if (newScreenWidth !== this.state.screenWidth) { - this.setState({ - screenWidth: newScreenWidth, - }); - } - } + return ( + <div className="clearfix pb4" style={{ backgroundColor: colors.heroGrey }}> + <div className="mx-auto max-width-4 pb4 mb3 clearfix"> + <div + className={callToActionClassNames} + style={{ + fontFamily: 'Roboto Mono', + color: colors.white, + lineHeight: isSmallScreen ? 1.7 : 3, + }} + > + Get started on building the decentralized future + </div> + <div className="col lg-col-4 md-col-4 col-12 sm-center sm-pt2"> + <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> + <RaisedButton + style={{ borderRadius: 6, minWidth: 150 }} + buttonStyle={lightButtonStyle} + labelColor={colors.white} + backgroundColor={colors.heroGrey} + labelStyle={buttonLabelStyle} + label="Build on 0x" + onClick={_.noop} + /> + </Link> + </div> + </div> + </div> + ); + } + private _updateScreenWidth() { + const newScreenWidth = utils.getScreenWidth(); + if (newScreenWidth !== this.state.screenWidth) { + this.setState({ + screenWidth: newScreenWidth, + }); + } + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx index 9d8d4142d..ff277c377 100644 --- a/packages/website/ts/pages/not_found.tsx +++ b/packages/website/ts/pages/not_found.tsx @@ -5,38 +5,38 @@ import { TopBar } from 'ts/components/top_bar'; import { Styles } from 'ts/types'; export interface NotFoundProps { - location: Location; + location: Location; } interface NotFoundState {} const styles: Styles = { - thin: { - fontWeight: 100, - }, + thin: { + fontWeight: 100, + }, }; export class NotFound extends React.Component<NotFoundProps, NotFoundState> { - public render() { - return ( - <div> - <TopBar blockchainIsLoaded={false} location={this.props.location} /> - <div className="mx-auto max-width-4 py4"> - <div className="center py4"> - <div className="py4"> - <div className="py4"> - <h1 style={{ ...styles.thin }}>404 Not Found</h1> - <div className="py1"> - <div className="py3"> - Hm... looks like we couldn't find what you are looking for. - </div> - </div> - </div> - </div> - </div> - </div> - <Footer /> - </div> - ); - } + public render() { + return ( + <div> + <TopBar blockchainIsLoaded={false} location={this.props.location} /> + <div className="mx-auto max-width-4 py4"> + <div className="center py4"> + <div className="py4"> + <div className="py4"> + <h1 style={{ ...styles.thin }}>404 Not Found</h1> + <div className="py1"> + <div className="py3"> + Hm... looks like we couldn't find what you are looking for. + </div> + </div> + </div> + </div> + </div> + </div> + <Footer /> + </div> + ); + } } diff --git a/packages/website/ts/pages/shared/anchor_title.tsx b/packages/website/ts/pages/shared/anchor_title.tsx index 118aa0ea5..db5be1f59 100644 --- a/packages/website/ts/pages/shared/anchor_title.tsx +++ b/packages/website/ts/pages/shared/anchor_title.tsx @@ -5,87 +5,87 @@ import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; const headerSizeToScrollOffset: { [headerSize: string]: number } = { - h2: -20, - h3: 0, + h2: -20, + h3: 0, }; interface AnchorTitleProps { - title: string | React.ReactNode; - id: string; - headerSize: HeaderSizes; - shouldShowAnchor: boolean; + title: string | React.ReactNode; + id: string; + headerSize: HeaderSizes; + shouldShowAnchor: boolean; } interface AnchorTitleState { - isHovering: boolean; + isHovering: boolean; } const styles: Styles = { - anchor: { - fontSize: 20, - transform: 'rotate(45deg)', - cursor: 'pointer', - }, - headers: { - WebkitMarginStart: 0, - WebkitMarginEnd: 0, - fontWeight: 'bold', - display: 'block', - }, - h1: { - fontSize: '1.8em', - WebkitMarginBefore: '0.83em', - WebkitMarginAfter: '0.83em', - }, - h2: { - fontSize: '1.5em', - WebkitMarginBefore: '0.83em', - WebkitMarginAfter: '0.83em', - }, - h3: { - fontSize: '1.17em', - WebkitMarginBefore: '1em', - WebkitMarginAfter: '1em', - }, + anchor: { + fontSize: 20, + transform: 'rotate(45deg)', + cursor: 'pointer', + }, + headers: { + WebkitMarginStart: 0, + WebkitMarginEnd: 0, + fontWeight: 'bold', + display: 'block', + }, + h1: { + fontSize: '1.8em', + WebkitMarginBefore: '0.83em', + WebkitMarginAfter: '0.83em', + }, + h2: { + fontSize: '1.5em', + WebkitMarginBefore: '0.83em', + WebkitMarginAfter: '0.83em', + }, + h3: { + fontSize: '1.17em', + WebkitMarginBefore: '1em', + WebkitMarginAfter: '1em', + }, }; export class AnchorTitle extends React.Component<AnchorTitleProps, AnchorTitleState> { - constructor(props: AnchorTitleProps) { - super(props); - this.state = { - isHovering: false, - }; - } - public render() { - let opacity = 0; - if (this.props.shouldShowAnchor) { - opacity = this.state.isHovering ? 0.6 : 1; - } - return ( - <div className="relative flex" style={{ ...styles[this.props.headerSize], ...styles.headers }}> - <div className="inline-block" style={{ paddingRight: 4 }}> - {this.props.title} - </div> - <ScrollLink - to={this.props.id} - offset={headerSizeToScrollOffset[this.props.headerSize]} - duration={constants.DOCS_SCROLL_DURATION_MS} - containerId={constants.DOCS_CONTAINER_ID} - > - <i - className="zmdi zmdi-link" - onClick={utils.setUrlHash.bind(utils, this.props.id)} - style={{ ...styles.anchor, opacity }} - onMouseOver={this._setHoverState.bind(this, true)} - onMouseOut={this._setHoverState.bind(this, false)} - /> - </ScrollLink> - </div> - ); - } - private _setHoverState(isHovering: boolean) { - this.setState({ - isHovering, - }); - } + constructor(props: AnchorTitleProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + let opacity = 0; + if (this.props.shouldShowAnchor) { + opacity = this.state.isHovering ? 0.6 : 1; + } + return ( + <div className="relative flex" style={{ ...styles[this.props.headerSize], ...styles.headers }}> + <div className="inline-block" style={{ paddingRight: 4 }}> + {this.props.title} + </div> + <ScrollLink + to={this.props.id} + offset={headerSizeToScrollOffset[this.props.headerSize]} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + > + <i + className="zmdi zmdi-link" + onClick={utils.setUrlHash.bind(utils, this.props.id)} + style={{ ...styles.anchor, opacity }} + onMouseOver={this._setHoverState.bind(this, true)} + onMouseOut={this._setHoverState.bind(this, false)} + /> + </ScrollLink> + </div> + ); + } + private _setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } } diff --git a/packages/website/ts/pages/shared/markdown_code_block.tsx b/packages/website/ts/pages/shared/markdown_code_block.tsx index a9d95979b..be96fda16 100644 --- a/packages/website/ts/pages/shared/markdown_code_block.tsx +++ b/packages/website/ts/pages/shared/markdown_code_block.tsx @@ -3,23 +3,23 @@ import * as React from 'react'; import * as HighLight from 'react-highlight'; interface MarkdownCodeBlockProps { - literal: string; - language: string; + literal: string; + language: string; } interface MarkdownCodeBlockState {} export class MarkdownCodeBlock extends React.Component<MarkdownCodeBlockProps, MarkdownCodeBlockState> { - // Re-rendering a codeblock causes any use selection to become de-selected. This is annoying when trying - // to copy-paste code examples. We therefore noop re-renders on this component if it's props haven't changed. - public shouldComponentUpdate(nextProps: MarkdownCodeBlockProps, nextState: MarkdownCodeBlockState) { - return nextProps.literal !== this.props.literal || nextProps.language !== this.props.language; - } - public render() { - return ( - <span style={{ fontSize: 16 }}> - <HighLight className={this.props.language || 'javascript'}>{this.props.literal}</HighLight> - </span> - ); - } + // Re-rendering a codeblock causes any use selection to become de-selected. This is annoying when trying + // to copy-paste code examples. We therefore noop re-renders on this component if it's props haven't changed. + public shouldComponentUpdate(nextProps: MarkdownCodeBlockProps, nextState: MarkdownCodeBlockState) { + return nextProps.literal !== this.props.literal || nextProps.language !== this.props.language; + } + public render() { + return ( + <span style={{ fontSize: 16 }}> + <HighLight className={this.props.language || 'javascript'}>{this.props.literal}</HighLight> + </span> + ); + } } diff --git a/packages/website/ts/pages/shared/markdown_section.tsx b/packages/website/ts/pages/shared/markdown_section.tsx index c875ab736..5487dc8cc 100644 --- a/packages/website/ts/pages/shared/markdown_section.tsx +++ b/packages/website/ts/pages/shared/markdown_section.tsx @@ -9,66 +9,66 @@ import { HeaderSizes } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface MarkdownSectionProps { - sectionName: string; - markdownContent: string; - headerSize?: HeaderSizes; - githubLink?: string; + sectionName: string; + markdownContent: string; + headerSize?: HeaderSizes; + githubLink?: string; } interface MarkdownSectionState { - shouldShowAnchor: boolean; + shouldShowAnchor: boolean; } export class MarkdownSection extends React.Component<MarkdownSectionProps, MarkdownSectionState> { - public static defaultProps: Partial<MarkdownSectionProps> = { - headerSize: HeaderSizes.H3, - }; - constructor(props: MarkdownSectionProps) { - super(props); - this.state = { - shouldShowAnchor: false, - }; - } - public render() { - const sectionName = this.props.sectionName; - const id = utils.getIdFromName(sectionName); - return ( - <div - className="pt2 pr3 md-pl2 sm-pl3 overflow-hidden" - onMouseOver={this._setAnchorVisibility.bind(this, true)} - onMouseOut={this._setAnchorVisibility.bind(this, false)} - > - <ScrollElement name={id}> - <div className="clearfix"> - <div className="col lg-col-8 md-col-8 sm-col-12"> - <span style={{ textTransform: 'capitalize' }}> - <AnchorTitle - headerSize={this.props.headerSize} - title={sectionName} - id={id} - shouldShowAnchor={this.state.shouldShowAnchor} - /> - </span> - </div> - <div className="col col-4 sm-hide xs-hide py2 right-align"> - {!_.isUndefined(this.props.githubLink) && ( - <RaisedButton - href={this.props.githubLink} - target="_blank" - label="Edit on Github" - icon={<i className="zmdi zmdi-github" style={{ fontSize: 23 }} />} - /> - )} - </div> - </div> - <ReactMarkdown source={this.props.markdownContent} renderers={{ CodeBlock: MarkdownCodeBlock }} /> - </ScrollElement> - </div> - ); - } - private _setAnchorVisibility(shouldShowAnchor: boolean) { - this.setState({ - shouldShowAnchor, - }); - } + public static defaultProps: Partial<MarkdownSectionProps> = { + headerSize: HeaderSizes.H3, + }; + constructor(props: MarkdownSectionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const sectionName = this.props.sectionName; + const id = utils.getIdFromName(sectionName); + return ( + <div + className="pt2 pr3 md-pl2 sm-pl3 overflow-hidden" + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <ScrollElement name={id}> + <div className="clearfix"> + <div className="col lg-col-8 md-col-8 sm-col-12"> + <span style={{ textTransform: 'capitalize' }}> + <AnchorTitle + headerSize={this.props.headerSize} + title={sectionName} + id={id} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + </span> + </div> + <div className="col col-4 sm-hide xs-hide py2 right-align"> + {!_.isUndefined(this.props.githubLink) && ( + <RaisedButton + href={this.props.githubLink} + target="_blank" + label="Edit on Github" + icon={<i className="zmdi zmdi-github" style={{ fontSize: 23 }} />} + /> + )} + </div> + </div> + <ReactMarkdown source={this.props.markdownContent} renderers={{ CodeBlock: MarkdownCodeBlock }} /> + </ScrollElement> + </div> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } } diff --git a/packages/website/ts/pages/shared/nested_sidebar_menu.tsx b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx index cd7ea68e6..849c33504 100644 --- a/packages/website/ts/pages/shared/nested_sidebar_menu.tsx +++ b/packages/website/ts/pages/shared/nested_sidebar_menu.tsx @@ -9,146 +9,146 @@ import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; interface NestedSidebarMenuProps { - topLevelMenu: { [topLevel: string]: string[] }; - menuSubsectionsBySection: MenuSubsectionsBySection; - shouldDisplaySectionHeaders?: boolean; - onMenuItemClick?: () => void; - selectedVersion?: string; - versions?: string[]; - docPath?: string; - isSectionHeaderClickable?: boolean; + topLevelMenu: { [topLevel: string]: string[] }; + menuSubsectionsBySection: MenuSubsectionsBySection; + shouldDisplaySectionHeaders?: boolean; + onMenuItemClick?: () => void; + selectedVersion?: string; + versions?: string[]; + docPath?: string; + isSectionHeaderClickable?: boolean; } interface NestedSidebarMenuState {} const styles: Styles = { - menuItemWithHeaders: { - minHeight: 0, - }, - menuItemWithoutHeaders: { - minHeight: 48, - }, - menuItemInnerDivWithHeaders: { - lineHeight: 2, - }, + menuItemWithHeaders: { + minHeight: 0, + }, + menuItemWithoutHeaders: { + minHeight: 48, + }, + menuItemInnerDivWithHeaders: { + lineHeight: 2, + }, }; export class NestedSidebarMenu extends React.Component<NestedSidebarMenuProps, NestedSidebarMenuState> { - public static defaultProps: Partial<NestedSidebarMenuProps> = { - shouldDisplaySectionHeaders: true, - onMenuItemClick: _.noop, - }; - public render() { - const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => { - const finalSectionName = sectionName.replace(/-/g, ' '); - if (this.props.shouldDisplaySectionHeaders) { - const id = utils.getIdFromName(sectionName); - return ( - <div key={`section-${sectionName}`} className="py1"> - <ScrollLink - to={id} - offset={-20} - duration={constants.DOCS_SCROLL_DURATION_MS} - containerId={constants.DOCS_CONTAINER_ID} - > - <div style={{ color: colors.grey, cursor: 'pointer' }} className="pb1"> - {finalSectionName.toUpperCase()} - </div> - </ScrollLink> - {this._renderMenuItems(menuItems)} - </div> - ); - } else { - return <div key={`section-${sectionName}`}>{this._renderMenuItems(menuItems)}</div>; - } - }); - return ( - <div> - {!_.isUndefined(this.props.versions) && - !_.isUndefined(this.props.selectedVersion) && - !_.isUndefined(this.props.docPath) && ( - <VersionDropDown - selectedVersion={this.props.selectedVersion} - versions={this.props.versions} - docPath={this.props.docPath} - /> - )} - {navigation} - </div> - ); - } - private _renderMenuItems(menuItemNames: string[]): React.ReactNode[] { - const menuItemStyles = this.props.shouldDisplaySectionHeaders - ? styles.menuItemWithHeaders - : styles.menuItemWithoutHeaders; - const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ? styles.menuItemInnerDivWithHeaders : {}; - const menuItems = _.map(menuItemNames, menuItemName => { - const id = utils.getIdFromName(menuItemName); - return ( - <div key={menuItemName}> - <ScrollLink - key={`menuItem-${menuItemName}`} - to={id} - offset={-10} - duration={constants.DOCS_SCROLL_DURATION_MS} - containerId={constants.DOCS_CONTAINER_ID} - > - <MenuItem - onTouchTap={this._onMenuItemClick.bind(this, menuItemName)} - style={menuItemStyles} - innerDivStyle={menuItemInnerDivStyles} - > - <span style={{ textTransform: 'capitalize' }}>{menuItemName}</span> - </MenuItem> - </ScrollLink> - {this._renderMenuItemSubsections(menuItemName)} - </div> - ); - }); - return menuItems; - } - private _renderMenuItemSubsections(menuItemName: string): React.ReactNode { - if (_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])) { - return null; - } - return this._renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]); - } - private _renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode { - return ( - <ul style={{ margin: 0, listStyleType: 'none', paddingLeft: 0 }} key={menuItemName}> - {_.map(entityNames, entityName => { - const name = `${menuItemName}-${entityName}`; - const id = utils.getIdFromName(name); - return ( - <li key={`menuItem-${entityName}`}> - <ScrollLink - to={id} - offset={0} - duration={constants.DOCS_SCROLL_DURATION_MS} - containerId={constants.DOCS_CONTAINER_ID} - onTouchTap={this._onMenuItemClick.bind(this, name)} - > - <MenuItem - onTouchTap={this._onMenuItemClick.bind(this, name)} - style={{ minHeight: 35 }} - innerDivStyle={{ - paddingLeft: 36, - fontSize: 14, - lineHeight: '35px', - }} - > - {entityName} - </MenuItem> - </ScrollLink> - </li> - ); - })} - </ul> - ); - } - private _onMenuItemClick(name: string): void { - const id = utils.getIdFromName(name); - utils.setUrlHash(id); - this.props.onMenuItemClick(); - } + public static defaultProps: Partial<NestedSidebarMenuProps> = { + shouldDisplaySectionHeaders: true, + onMenuItemClick: _.noop, + }; + public render() { + const navigation = _.map(this.props.topLevelMenu, (menuItems: string[], sectionName: string) => { + const finalSectionName = sectionName.replace(/-/g, ' '); + if (this.props.shouldDisplaySectionHeaders) { + const id = utils.getIdFromName(sectionName); + return ( + <div key={`section-${sectionName}`} className="py1"> + <ScrollLink + to={id} + offset={-20} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + > + <div style={{ color: colors.grey, cursor: 'pointer' }} className="pb1"> + {finalSectionName.toUpperCase()} + </div> + </ScrollLink> + {this._renderMenuItems(menuItems)} + </div> + ); + } else { + return <div key={`section-${sectionName}`}>{this._renderMenuItems(menuItems)}</div>; + } + }); + return ( + <div> + {!_.isUndefined(this.props.versions) && + !_.isUndefined(this.props.selectedVersion) && + !_.isUndefined(this.props.docPath) && ( + <VersionDropDown + selectedVersion={this.props.selectedVersion} + versions={this.props.versions} + docPath={this.props.docPath} + /> + )} + {navigation} + </div> + ); + } + private _renderMenuItems(menuItemNames: string[]): React.ReactNode[] { + const menuItemStyles = this.props.shouldDisplaySectionHeaders + ? styles.menuItemWithHeaders + : styles.menuItemWithoutHeaders; + const menuItemInnerDivStyles = this.props.shouldDisplaySectionHeaders ? styles.menuItemInnerDivWithHeaders : {}; + const menuItems = _.map(menuItemNames, menuItemName => { + const id = utils.getIdFromName(menuItemName); + return ( + <div key={menuItemName}> + <ScrollLink + key={`menuItem-${menuItemName}`} + to={id} + offset={-10} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + > + <MenuItem + onTouchTap={this._onMenuItemClick.bind(this, menuItemName)} + style={menuItemStyles} + innerDivStyle={menuItemInnerDivStyles} + > + <span style={{ textTransform: 'capitalize' }}>{menuItemName}</span> + </MenuItem> + </ScrollLink> + {this._renderMenuItemSubsections(menuItemName)} + </div> + ); + }); + return menuItems; + } + private _renderMenuItemSubsections(menuItemName: string): React.ReactNode { + if (_.isUndefined(this.props.menuSubsectionsBySection[menuItemName])) { + return null; + } + return this._renderMenuSubsectionsBySection(menuItemName, this.props.menuSubsectionsBySection[menuItemName]); + } + private _renderMenuSubsectionsBySection(menuItemName: string, entityNames: string[]): React.ReactNode { + return ( + <ul style={{ margin: 0, listStyleType: 'none', paddingLeft: 0 }} key={menuItemName}> + {_.map(entityNames, entityName => { + const name = `${menuItemName}-${entityName}`; + const id = utils.getIdFromName(name); + return ( + <li key={`menuItem-${entityName}`}> + <ScrollLink + to={id} + offset={0} + duration={constants.DOCS_SCROLL_DURATION_MS} + containerId={constants.DOCS_CONTAINER_ID} + onTouchTap={this._onMenuItemClick.bind(this, name)} + > + <MenuItem + onTouchTap={this._onMenuItemClick.bind(this, name)} + style={{ minHeight: 35 }} + innerDivStyle={{ + paddingLeft: 36, + fontSize: 14, + lineHeight: '35px', + }} + > + {entityName} + </MenuItem> + </ScrollLink> + </li> + ); + })} + </ul> + ); + } + private _onMenuItemClick(name: string): void { + const id = utils.getIdFromName(name); + utils.setUrlHash(id); + this.props.onMenuItemClick(); + } } diff --git a/packages/website/ts/pages/shared/section_header.tsx b/packages/website/ts/pages/shared/section_header.tsx index f9aa1a5e6..a5f5f52cf 100644 --- a/packages/website/ts/pages/shared/section_header.tsx +++ b/packages/website/ts/pages/shared/section_header.tsx @@ -5,46 +5,46 @@ import { HeaderSizes } from 'ts/types'; import { utils } from 'ts/utils/utils'; interface SectionHeaderProps { - sectionName: string; - headerSize?: HeaderSizes; + sectionName: string; + headerSize?: HeaderSizes; } interface SectionHeaderState { - shouldShowAnchor: boolean; + shouldShowAnchor: boolean; } export class SectionHeader extends React.Component<SectionHeaderProps, SectionHeaderState> { - public static defaultProps: Partial<SectionHeaderProps> = { - headerSize: HeaderSizes.H2, - }; - constructor(props: SectionHeaderProps) { - super(props); - this.state = { - shouldShowAnchor: false, - }; - } - public render() { - const sectionName = this.props.sectionName.replace(/-/g, ' '); - const id = utils.getIdFromName(sectionName); - return ( - <div - onMouseOver={this._setAnchorVisibility.bind(this, true)} - onMouseOut={this._setAnchorVisibility.bind(this, false)} - > - <ScrollElement name={id}> - <AnchorTitle - headerSize={this.props.headerSize} - title={<span style={{ textTransform: 'capitalize' }}>{sectionName}</span>} - id={id} - shouldShowAnchor={this.state.shouldShowAnchor} - /> - </ScrollElement> - </div> - ); - } - private _setAnchorVisibility(shouldShowAnchor: boolean) { - this.setState({ - shouldShowAnchor, - }); - } + public static defaultProps: Partial<SectionHeaderProps> = { + headerSize: HeaderSizes.H2, + }; + constructor(props: SectionHeaderProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const sectionName = this.props.sectionName.replace(/-/g, ' '); + const id = utils.getIdFromName(sectionName); + return ( + <div + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <ScrollElement name={id}> + <AnchorTitle + headerSize={this.props.headerSize} + title={<span style={{ textTransform: 'capitalize' }}>{sectionName}</span>} + id={id} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + </ScrollElement> + </div> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } } diff --git a/packages/website/ts/pages/shared/version_drop_down.tsx b/packages/website/ts/pages/shared/version_drop_down.tsx index a647252ba..b922e1048 100644 --- a/packages/website/ts/pages/shared/version_drop_down.tsx +++ b/packages/website/ts/pages/shared/version_drop_down.tsx @@ -4,34 +4,34 @@ import MenuItem from 'material-ui/MenuItem'; import * as React from 'react'; interface VersionDropDownProps { - selectedVersion: string; - versions: string[]; - docPath: string; + selectedVersion: string; + versions: string[]; + docPath: string; } interface VersionDropDownState {} export class VersionDropDown extends React.Component<VersionDropDownProps, VersionDropDownState> { - public render() { - return ( - <div className="mx-auto" style={{ width: 120 }}> - <DropDownMenu - maxHeight={300} - value={this.props.selectedVersion} - onChange={this._updateSelectedVersion.bind(this)} - > - {this._renderDropDownItems()} - </DropDownMenu> - </div> - ); - } - private _renderDropDownItems() { - const items = _.map(this.props.versions, version => { - return <MenuItem key={version} value={version} primaryText={`v${version}`} />; - }); - return items; - } - private _updateSelectedVersion(e: any, index: number, value: string) { - window.location.href = `${this.props.docPath}/${value}${window.location.hash}`; - } + public render() { + return ( + <div className="mx-auto" style={{ width: 120 }}> + <DropDownMenu + maxHeight={300} + value={this.props.selectedVersion} + onChange={this._updateSelectedVersion.bind(this)} + > + {this._renderDropDownItems()} + </DropDownMenu> + </div> + ); + } + private _renderDropDownItems() { + const items = _.map(this.props.versions, version => { + return <MenuItem key={version} value={version} primaryText={`v${version}`} />; + }); + return items; + } + private _updateSelectedVersion(e: any, index: number, value: string) { + window.location.href = `${this.props.docPath}/${value}${window.location.hash}`; + } } diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index dba5ed6a0..d065614ba 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -16,186 +16,186 @@ import { utils } from 'ts/utils/utils'; const WIKI_NOT_READY_BACKOUT_TIMEOUT_MS = 5000; export interface WikiProps { - source: string; - location: Location; + source: string; + location: Location; } interface WikiState { - articlesBySection: ArticlesBySection; + articlesBySection: ArticlesBySection; } const styles: Styles = { - mainContainers: { - position: 'absolute', - top: 1, - left: 0, - bottom: 0, - right: 0, - overflowZ: 'hidden', - overflowY: 'scroll', - minHeight: 'calc(100vh - 1px)', - WebkitOverflowScrolling: 'touch', - }, - menuContainer: { - borderColor: colors.grey300, - maxWidth: 330, - marginLeft: 20, - }, + mainContainers: { + position: 'absolute', + top: 1, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: 'calc(100vh - 1px)', + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, }; export class Wiki extends React.Component<WikiProps, WikiState> { - private _wikiBackoffTimeoutId: number; - constructor(props: WikiProps) { - super(props); - this.state = { - articlesBySection: undefined, - }; - } - public componentWillMount() { - // tslint:disable-next-line:no-floating-promises - this._fetchArticlesBySectionAsync(); - } - public componentWillUnmount() { - clearTimeout(this._wikiBackoffTimeoutId); - } - public render() { - const menuSubsectionsBySection = _.isUndefined(this.state.articlesBySection) - ? {} - : this._getMenuSubsectionsBySection(this.state.articlesBySection); - return ( - <div> - <DocumentTitle title="0x Protocol Wiki" /> - <TopBar - blockchainIsLoaded={false} - location={this.props.location} - menuSubsectionsBySection={menuSubsectionsBySection} - shouldFullWidth={true} - /> - {_.isUndefined(this.state.articlesBySection) ? ( - <div className="col col-12" style={styles.mainContainers}> - <div - className="relative sm-px2 sm-pt2 sm-m1" - style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} - > - <div className="center pb2"> - <CircularProgress size={40} thickness={5} /> - </div> - <div className="center pt2" style={{ paddingBottom: 11 }}> - Loading wiki... - </div> - </div> - </div> - ) : ( - <div className="mx-auto flex" style={{ color: colors.grey800, height: 43 }}> - <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide"> - <div - className="border-right absolute pt2" - style={{ ...styles.menuContainer, ...styles.mainContainers }} - > - <NestedSidebarMenu - topLevelMenu={menuSubsectionsBySection} - menuSubsectionsBySection={menuSubsectionsBySection} - isSectionHeaderClickable={true} - /> - </div> - </div> - <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12"> - <div id="documentation" style={styles.mainContainers} className="absolute"> - <div id="0xProtocolWiki" /> - <h1 className="md-pl2 sm-pl3"> - <a href={constants.URL_GITHUB_WIKI} target="_blank"> - 0x Protocol Wiki - </a> - </h1> - <div id="wiki">{this._renderWikiArticles()}</div> - </div> - </div> - </div> - )} - </div> - ); - } - private _renderWikiArticles(): React.ReactNode { - const sectionNames = _.keys(this.state.articlesBySection); - const sections = _.map(sectionNames, sectionName => this._renderSection(sectionName)); - return sections; - } - private _renderSection(sectionName: string) { - const articles = this.state.articlesBySection[sectionName]; - const renderedArticles = _.map(articles, (article: Article) => { - const githubLink = `${constants.URL_GITHUB_WIKI}/edit/master/${sectionName}/${article.fileName}`; - return ( - <div key={`markdown-section-${article.title}`}> - <MarkdownSection - sectionName={article.title} - markdownContent={article.content} - headerSize={HeaderSizes.H2} - githubLink={githubLink} - /> - <div className="mb4 mt3 p3 center" style={{ backgroundColor: colors.lightestGrey }}> - See a way to make this article better?{' '} - <a href={githubLink} target="_blank"> - Edit here → - </a> - </div> - </div> - ); - }); - return ( - <div key={`section-${sectionName}`} className="py2 pr3 md-pl2 sm-pl3"> - <SectionHeader sectionName={sectionName} headerSize={HeaderSizes.H1} /> - {renderedArticles} - </div> - ); - } - private _scrollToHash(): void { - const hashWithPrefix = this.props.location.hash; - let hash = hashWithPrefix.slice(1); - if (_.isEmpty(hash)) { - hash = '0xProtocolWiki'; // scroll to the top - } + private _wikiBackoffTimeoutId: number; + constructor(props: WikiProps) { + super(props); + this.state = { + articlesBySection: undefined, + }; + } + public componentWillMount() { + // tslint:disable-next-line:no-floating-promises + this._fetchArticlesBySectionAsync(); + } + public componentWillUnmount() { + clearTimeout(this._wikiBackoffTimeoutId); + } + public render() { + const menuSubsectionsBySection = _.isUndefined(this.state.articlesBySection) + ? {} + : this._getMenuSubsectionsBySection(this.state.articlesBySection); + return ( + <div> + <DocumentTitle title="0x Protocol Wiki" /> + <TopBar + blockchainIsLoaded={false} + location={this.props.location} + menuSubsectionsBySection={menuSubsectionsBySection} + shouldFullWidth={true} + /> + {_.isUndefined(this.state.articlesBySection) ? ( + <div className="col col-12" style={styles.mainContainers}> + <div + className="relative sm-px2 sm-pt2 sm-m1" + style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} + > + <div className="center pb2"> + <CircularProgress size={40} thickness={5} /> + </div> + <div className="center pt2" style={{ paddingBottom: 11 }}> + Loading wiki... + </div> + </div> + </div> + ) : ( + <div className="mx-auto flex" style={{ color: colors.grey800, height: 43 }}> + <div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide"> + <div + className="border-right absolute pt2" + style={{ ...styles.menuContainer, ...styles.mainContainers }} + > + <NestedSidebarMenu + topLevelMenu={menuSubsectionsBySection} + menuSubsectionsBySection={menuSubsectionsBySection} + isSectionHeaderClickable={true} + /> + </div> + </div> + <div className="relative col lg-col-9 md-col-9 sm-col-12 col-12"> + <div id="documentation" style={styles.mainContainers} className="absolute"> + <div id="0xProtocolWiki" /> + <h1 className="md-pl2 sm-pl3"> + <a href={constants.URL_GITHUB_WIKI} target="_blank"> + 0x Protocol Wiki + </a> + </h1> + <div id="wiki">{this._renderWikiArticles()}</div> + </div> + </div> + </div> + )} + </div> + ); + } + private _renderWikiArticles(): React.ReactNode { + const sectionNames = _.keys(this.state.articlesBySection); + const sections = _.map(sectionNames, sectionName => this._renderSection(sectionName)); + return sections; + } + private _renderSection(sectionName: string) { + const articles = this.state.articlesBySection[sectionName]; + const renderedArticles = _.map(articles, (article: Article) => { + const githubLink = `${constants.URL_GITHUB_WIKI}/edit/master/${sectionName}/${article.fileName}`; + return ( + <div key={`markdown-section-${article.title}`}> + <MarkdownSection + sectionName={article.title} + markdownContent={article.content} + headerSize={HeaderSizes.H2} + githubLink={githubLink} + /> + <div className="mb4 mt3 p3 center" style={{ backgroundColor: colors.lightestGrey }}> + See a way to make this article better?{' '} + <a href={githubLink} target="_blank"> + Edit here → + </a> + </div> + </div> + ); + }); + return ( + <div key={`section-${sectionName}`} className="py2 pr3 md-pl2 sm-pl3"> + <SectionHeader sectionName={sectionName} headerSize={HeaderSizes.H1} /> + {renderedArticles} + </div> + ); + } + private _scrollToHash(): void { + const hashWithPrefix = this.props.location.hash; + let hash = hashWithPrefix.slice(1); + if (_.isEmpty(hash)) { + hash = '0xProtocolWiki'; // scroll to the top + } - scroller.scrollTo(hash, { - duration: 0, - offset: 0, - containerId: 'documentation', - }); - } - private async _fetchArticlesBySectionAsync(): Promise<void> { - const endpoint = `${configs.BACKEND_BASE_URL}${WebsitePaths.Wiki}`; - const response = await fetch(endpoint); - if (response.status === constants.HTTP_NO_CONTENT_STATUS_CODE) { - // We need to backoff and try fetching again later - this._wikiBackoffTimeoutId = window.setTimeout(() => { - // tslint:disable-next-line:no-floating-promises - this._fetchArticlesBySectionAsync(); - }, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS); - return; - } - if (response.status !== 200) { - // TODO: Show the user an error message when the wiki fail to load - const errMsg = await response.text(); - utils.consoleLog(`Failed to load wiki: ${response.status} ${errMsg}`); - return; - } - const articlesBySection = await response.json(); - this.setState( - { - articlesBySection, - }, - () => { - this._scrollToHash(); - }, - ); - } - private _getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) { - const sectionNames = _.keys(articlesBySection); - const menuSubsectionsBySection: { [section: string]: string[] } = {}; - for (const sectionName of sectionNames) { - const articles = articlesBySection[sectionName]; - const articleNames = _.map(articles, article => article.title); - menuSubsectionsBySection[sectionName] = articleNames; - } - return menuSubsectionsBySection; - } + scroller.scrollTo(hash, { + duration: 0, + offset: 0, + containerId: 'documentation', + }); + } + private async _fetchArticlesBySectionAsync(): Promise<void> { + const endpoint = `${configs.BACKEND_BASE_URL}${WebsitePaths.Wiki}`; + const response = await fetch(endpoint); + if (response.status === constants.HTTP_NO_CONTENT_STATUS_CODE) { + // We need to backoff and try fetching again later + this._wikiBackoffTimeoutId = window.setTimeout(() => { + // tslint:disable-next-line:no-floating-promises + this._fetchArticlesBySectionAsync(); + }, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS); + return; + } + if (response.status !== 200) { + // TODO: Show the user an error message when the wiki fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load wiki: ${response.status} ${errMsg}`); + return; + } + const articlesBySection = await response.json(); + this.setState( + { + articlesBySection, + }, + () => { + this._scrollToHash(); + }, + ); + } + private _getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) { + const sectionNames = _.keys(articlesBySection); + const menuSubsectionsBySection: { [section: string]: string[] } = {}; + for (const sectionName of sectionNames) { + const articles = articlesBySection[sectionName]; + const articleNames = _.map(articles, article => article.title); + menuSubsectionsBySection[sectionName] = articleNames; + } + return menuSubsectionsBySection; + } } diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts index 19300e242..42989e5e1 100644 --- a/packages/website/ts/redux/dispatcher.ts +++ b/packages/website/ts/redux/dispatcher.ts @@ -2,235 +2,235 @@ import { BigNumber } from '@0xproject/utils'; import { Dispatch } from 'redux'; import { State } from 'ts/redux/reducer'; import { - ActionTypes, - AssetToken, - BlockchainErrs, - Order, - ProviderType, - ScreenWidths, - Side, - SignatureData, - Token, - TokenStateByAddress, + ActionTypes, + AssetToken, + BlockchainErrs, + Order, + ProviderType, + ScreenWidths, + Side, + SignatureData, + Token, + TokenStateByAddress, } from 'ts/types'; export class Dispatcher { - private _dispatch: Dispatch<State>; - constructor(dispatch: Dispatch<State>) { - this._dispatch = dispatch; - } - // Portal - public resetState() { - this._dispatch({ - type: ActionTypes.ResetState, - }); - } - public updateNodeVersion(nodeVersion: string) { - this._dispatch({ - data: nodeVersion, - type: ActionTypes.UpdateNodeVersion, - }); - } - public updateScreenWidth(screenWidth: ScreenWidths) { - this._dispatch({ - data: screenWidth, - type: ActionTypes.UpdateScreenWidth, - }); - } - public swapAssetTokenSymbols() { - this._dispatch({ - type: ActionTypes.SwapAssetTokens, - }); - } - public updateOrderSalt(salt: BigNumber) { - this._dispatch({ - data: salt, - type: ActionTypes.UpdateOrderSalt, - }); - } - public updateUserSuppliedOrderCache(order: Order) { - this._dispatch({ - data: order, - type: ActionTypes.UpdateUserSuppliedOrderCache, - }); - } - public updateShouldBlockchainErrDialogBeOpen(shouldBeOpen: boolean) { - this._dispatch({ - data: shouldBeOpen, - type: ActionTypes.UpdateShouldBlockchainErrDialogBeOpen, - }); - } - public updateChosenAssetToken(side: Side, token: AssetToken) { - this._dispatch({ - data: { - side, - token, - }, - type: ActionTypes.UpdateChosenAssetToken, - }); - } - public updateChosenAssetTokenAddress(side: Side, address: string) { - this._dispatch({ - data: { - address, - side, - }, - type: ActionTypes.UpdateChosenAssetTokenAddress, - }); - } - public updateOrderTakerAddress(address: string) { - this._dispatch({ - data: address, - type: ActionTypes.UpdateOrderTakerAddress, - }); - } - public updateUserAddress(address: string) { - this._dispatch({ - data: address, - type: ActionTypes.UpdateUserAddress, - }); - } - public updateOrderExpiry(unixTimestampSec: BigNumber) { - this._dispatch({ - data: unixTimestampSec, - type: ActionTypes.UpdateOrderExpiry, - }); - } - public encounteredBlockchainError(err: BlockchainErrs) { - this._dispatch({ - data: err, - type: ActionTypes.BlockchainErrEncountered, - }); - } - public updateBlockchainIsLoaded(isLoaded: boolean) { - this._dispatch({ - data: isLoaded, - type: ActionTypes.UpdateBlockchainIsLoaded, - }); - } - public addTokenToTokenByAddress(token: Token) { - this._dispatch({ - data: token, - type: ActionTypes.AddTokenToTokenByAddress, - }); - } - public removeTokenToTokenByAddress(token: Token) { - this._dispatch({ - data: token, - type: ActionTypes.RemoveTokenFromTokenByAddress, - }); - } - public clearTokenByAddress() { - this._dispatch({ - type: ActionTypes.ClearTokenByAddress, - }); - } - public updateTokenByAddress(tokens: Token[]) { - this._dispatch({ - data: tokens, - type: ActionTypes.UpdateTokenByAddress, - }); - } - public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) { - this._dispatch({ - data: tokenStateByAddress, - type: ActionTypes.UpdateTokenStateByAddress, - }); - } - public removeFromTokenStateByAddress(tokenAddress: string) { - this._dispatch({ - data: tokenAddress, - type: ActionTypes.RemoveFromTokenStateByAddress, - }); - } - public replaceTokenAllowanceByAddress(address: string, allowance: BigNumber) { - this._dispatch({ - data: { - address, - allowance, - }, - type: ActionTypes.ReplaceTokenAllowanceByAddress, - }); - } - public replaceTokenBalanceByAddress(address: string, balance: BigNumber) { - this._dispatch({ - data: { - address, - balance, - }, - type: ActionTypes.ReplaceTokenBalanceByAddress, - }); - } - public updateTokenBalanceByAddress(address: string, balanceDelta: BigNumber) { - this._dispatch({ - data: { - address, - balanceDelta, - }, - type: ActionTypes.UpdateTokenBalanceByAddress, - }); - } - public updateSignatureData(signatureData: SignatureData) { - this._dispatch({ - data: signatureData, - type: ActionTypes.UpdateOrderSignatureData, - }); - } - public updateUserEtherBalance(balance: BigNumber) { - this._dispatch({ - data: balance, - type: ActionTypes.UpdateUserEtherBalance, - }); - } - public updateNetworkId(networkId: number) { - this._dispatch({ - data: networkId, - type: ActionTypes.UpdateNetworkId, - }); - } - public updateOrderFillAmount(amount: BigNumber) { - this._dispatch({ - data: amount, - type: ActionTypes.UpdateOrderFillAmount, - }); - } + private _dispatch: Dispatch<State>; + constructor(dispatch: Dispatch<State>) { + this._dispatch = dispatch; + } + // Portal + public resetState() { + this._dispatch({ + type: ActionTypes.ResetState, + }); + } + public updateNodeVersion(nodeVersion: string) { + this._dispatch({ + data: nodeVersion, + type: ActionTypes.UpdateNodeVersion, + }); + } + public updateScreenWidth(screenWidth: ScreenWidths) { + this._dispatch({ + data: screenWidth, + type: ActionTypes.UpdateScreenWidth, + }); + } + public swapAssetTokenSymbols() { + this._dispatch({ + type: ActionTypes.SwapAssetTokens, + }); + } + public updateOrderSalt(salt: BigNumber) { + this._dispatch({ + data: salt, + type: ActionTypes.UpdateOrderSalt, + }); + } + public updateUserSuppliedOrderCache(order: Order) { + this._dispatch({ + data: order, + type: ActionTypes.UpdateUserSuppliedOrderCache, + }); + } + public updateShouldBlockchainErrDialogBeOpen(shouldBeOpen: boolean) { + this._dispatch({ + data: shouldBeOpen, + type: ActionTypes.UpdateShouldBlockchainErrDialogBeOpen, + }); + } + public updateChosenAssetToken(side: Side, token: AssetToken) { + this._dispatch({ + data: { + side, + token, + }, + type: ActionTypes.UpdateChosenAssetToken, + }); + } + public updateChosenAssetTokenAddress(side: Side, address: string) { + this._dispatch({ + data: { + address, + side, + }, + type: ActionTypes.UpdateChosenAssetTokenAddress, + }); + } + public updateOrderTakerAddress(address: string) { + this._dispatch({ + data: address, + type: ActionTypes.UpdateOrderTakerAddress, + }); + } + public updateUserAddress(address: string) { + this._dispatch({ + data: address, + type: ActionTypes.UpdateUserAddress, + }); + } + public updateOrderExpiry(unixTimestampSec: BigNumber) { + this._dispatch({ + data: unixTimestampSec, + type: ActionTypes.UpdateOrderExpiry, + }); + } + public encounteredBlockchainError(err: BlockchainErrs) { + this._dispatch({ + data: err, + type: ActionTypes.BlockchainErrEncountered, + }); + } + public updateBlockchainIsLoaded(isLoaded: boolean) { + this._dispatch({ + data: isLoaded, + type: ActionTypes.UpdateBlockchainIsLoaded, + }); + } + public addTokenToTokenByAddress(token: Token) { + this._dispatch({ + data: token, + type: ActionTypes.AddTokenToTokenByAddress, + }); + } + public removeTokenToTokenByAddress(token: Token) { + this._dispatch({ + data: token, + type: ActionTypes.RemoveTokenFromTokenByAddress, + }); + } + public clearTokenByAddress() { + this._dispatch({ + type: ActionTypes.ClearTokenByAddress, + }); + } + public updateTokenByAddress(tokens: Token[]) { + this._dispatch({ + data: tokens, + type: ActionTypes.UpdateTokenByAddress, + }); + } + public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) { + this._dispatch({ + data: tokenStateByAddress, + type: ActionTypes.UpdateTokenStateByAddress, + }); + } + public removeFromTokenStateByAddress(tokenAddress: string) { + this._dispatch({ + data: tokenAddress, + type: ActionTypes.RemoveFromTokenStateByAddress, + }); + } + public replaceTokenAllowanceByAddress(address: string, allowance: BigNumber) { + this._dispatch({ + data: { + address, + allowance, + }, + type: ActionTypes.ReplaceTokenAllowanceByAddress, + }); + } + public replaceTokenBalanceByAddress(address: string, balance: BigNumber) { + this._dispatch({ + data: { + address, + balance, + }, + type: ActionTypes.ReplaceTokenBalanceByAddress, + }); + } + public updateTokenBalanceByAddress(address: string, balanceDelta: BigNumber) { + this._dispatch({ + data: { + address, + balanceDelta, + }, + type: ActionTypes.UpdateTokenBalanceByAddress, + }); + } + public updateSignatureData(signatureData: SignatureData) { + this._dispatch({ + data: signatureData, + type: ActionTypes.UpdateOrderSignatureData, + }); + } + public updateUserEtherBalance(balance: BigNumber) { + this._dispatch({ + data: balance, + type: ActionTypes.UpdateUserEtherBalance, + }); + } + public updateNetworkId(networkId: number) { + this._dispatch({ + data: networkId, + type: ActionTypes.UpdateNetworkId, + }); + } + public updateOrderFillAmount(amount: BigNumber) { + this._dispatch({ + data: amount, + type: ActionTypes.UpdateOrderFillAmount, + }); + } - // Docs - public updateCurrentDocsVersion(version: string) { - this._dispatch({ - data: version, - type: ActionTypes.UpdateLibraryVersion, - }); - } - public updateAvailableDocVersions(versions: string[]) { - this._dispatch({ - data: versions, - type: ActionTypes.UpdateAvailableLibraryVersions, - }); - } + // Docs + public updateCurrentDocsVersion(version: string) { + this._dispatch({ + data: version, + type: ActionTypes.UpdateLibraryVersion, + }); + } + public updateAvailableDocVersions(versions: string[]) { + this._dispatch({ + data: versions, + type: ActionTypes.UpdateAvailableLibraryVersions, + }); + } - // Shared - public showFlashMessage(msg: string | React.ReactNode) { - this._dispatch({ - data: msg, - type: ActionTypes.ShowFlashMessage, - }); - } - public hideFlashMessage() { - this._dispatch({ - type: ActionTypes.HideFlashMessage, - }); - } - public updateProviderType(providerType: ProviderType) { - this._dispatch({ - type: ActionTypes.UpdateProviderType, - data: providerType, - }); - } - public updateInjectedProviderName(injectedProviderName: string) { - this._dispatch({ - type: ActionTypes.UpdateInjectedProviderName, - data: injectedProviderName, - }); - } + // Shared + public showFlashMessage(msg: string | React.ReactNode) { + this._dispatch({ + data: msg, + type: ActionTypes.ShowFlashMessage, + }); + } + public hideFlashMessage() { + this._dispatch({ + type: ActionTypes.HideFlashMessage, + }); + } + public updateProviderType(providerType: ProviderType) { + this._dispatch({ + type: ActionTypes.UpdateProviderType, + data: providerType, + }); + } + public updateInjectedProviderName(injectedProviderName: string) { + this._dispatch({ + type: ActionTypes.UpdateInjectedProviderName, + data: injectedProviderName, + }); + } } diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts index cc467eadd..06ac8b670 100644 --- a/packages/website/ts/redux/reducer.ts +++ b/packages/website/ts/redux/reducer.ts @@ -2,18 +2,18 @@ import { ZeroEx } from '0x.js'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import { - Action, - ActionTypes, - BlockchainErrs, - Order, - ProviderType, - ScreenWidths, - Side, - SideToAssetToken, - SignatureData, - TokenByAddress, - TokenState, - TokenStateByAddress, + Action, + ActionTypes, + BlockchainErrs, + Order, + ProviderType, + ScreenWidths, + Side, + SideToAssetToken, + SignatureData, + TokenByAddress, + TokenState, + TokenStateByAddress, } from 'ts/types'; import { utils } from 'ts/utils/utils'; @@ -23,367 +23,367 @@ import { utils } from 'ts/utils/utils'; const DEFAULT_DOCS_VERSION = '0.0.0'; export interface State { - // Portal - blockchainErr: BlockchainErrs; - blockchainIsLoaded: boolean; - networkId: number; - orderExpiryTimestamp: BigNumber; - orderFillAmount: BigNumber; - orderTakerAddress: string; - orderSignatureData: SignatureData; - orderSalt: BigNumber; - nodeVersion: string; - screenWidth: ScreenWidths; - shouldBlockchainErrDialogBeOpen: boolean; - sideToAssetToken: SideToAssetToken; - tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; - userAddress: string; - userEtherBalance: BigNumber; - // Note: cache of supplied orderJSON in fill order step. Do not use for anything else. - userSuppliedOrderCache: Order; - - // Docs - docsVersion: string; - availableDocVersions: string[]; - - // Shared - flashMessage: string | React.ReactNode; - providerType: ProviderType; - injectedProviderName: string; + // Portal + blockchainErr: BlockchainErrs; + blockchainIsLoaded: boolean; + networkId: number; + orderExpiryTimestamp: BigNumber; + orderFillAmount: BigNumber; + orderTakerAddress: string; + orderSignatureData: SignatureData; + orderSalt: BigNumber; + nodeVersion: string; + screenWidth: ScreenWidths; + shouldBlockchainErrDialogBeOpen: boolean; + sideToAssetToken: SideToAssetToken; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userAddress: string; + userEtherBalance: BigNumber; + // Note: cache of supplied orderJSON in fill order step. Do not use for anything else. + userSuppliedOrderCache: Order; + + // Docs + docsVersion: string; + availableDocVersions: string[]; + + // Shared + flashMessage: string | React.ReactNode; + providerType: ProviderType; + injectedProviderName: string; } const INITIAL_STATE: State = { - // Portal - blockchainErr: BlockchainErrs.NoError, - blockchainIsLoaded: false, - networkId: undefined, - orderExpiryTimestamp: utils.initialOrderExpiryUnixTimestampSec(), - orderFillAmount: undefined, - orderSignatureData: { - hash: '', - r: '', - s: '', - v: 27, - }, - orderTakerAddress: '', - orderSalt: ZeroEx.generatePseudoRandomSalt(), - nodeVersion: undefined, - screenWidth: utils.getScreenWidth(), - shouldBlockchainErrDialogBeOpen: false, - sideToAssetToken: { - [Side.Deposit]: {}, - [Side.Receive]: {}, - }, - tokenByAddress: {}, - tokenStateByAddress: {}, - userAddress: '', - userEtherBalance: new BigNumber(0), - userSuppliedOrderCache: undefined, - - // Docs - docsVersion: DEFAULT_DOCS_VERSION, - availableDocVersions: [DEFAULT_DOCS_VERSION], - - // Shared - flashMessage: undefined, - providerType: ProviderType.Injected, - injectedProviderName: '', + // Portal + blockchainErr: BlockchainErrs.NoError, + blockchainIsLoaded: false, + networkId: undefined, + orderExpiryTimestamp: utils.initialOrderExpiryUnixTimestampSec(), + orderFillAmount: undefined, + orderSignatureData: { + hash: '', + r: '', + s: '', + v: 27, + }, + orderTakerAddress: '', + orderSalt: ZeroEx.generatePseudoRandomSalt(), + nodeVersion: undefined, + screenWidth: utils.getScreenWidth(), + shouldBlockchainErrDialogBeOpen: false, + sideToAssetToken: { + [Side.Deposit]: {}, + [Side.Receive]: {}, + }, + tokenByAddress: {}, + tokenStateByAddress: {}, + userAddress: '', + userEtherBalance: new BigNumber(0), + userSuppliedOrderCache: undefined, + + // Docs + docsVersion: DEFAULT_DOCS_VERSION, + availableDocVersions: [DEFAULT_DOCS_VERSION], + + // Shared + flashMessage: undefined, + providerType: ProviderType.Injected, + injectedProviderName: '', }; export function reducer(state: State = INITIAL_STATE, action: Action) { - switch (action.type) { - // Portal - case ActionTypes.ResetState: - return INITIAL_STATE; - - case ActionTypes.UpdateOrderSalt: { - return { - ...state, - orderSalt: action.data, - }; - } - - case ActionTypes.UpdateNodeVersion: { - return { - ...state, - nodeVersion: action.data, - }; - } - - case ActionTypes.UpdateOrderFillAmount: { - return { - ...state, - orderFillAmount: action.data, - }; - } - - case ActionTypes.UpdateShouldBlockchainErrDialogBeOpen: { - return { - ...state, - shouldBlockchainErrDialogBeOpen: action.data, - }; - } - - case ActionTypes.UpdateUserEtherBalance: { - return { - ...state, - userEtherBalance: action.data, - }; - } - - case ActionTypes.UpdateUserSuppliedOrderCache: { - return { - ...state, - userSuppliedOrderCache: action.data, - }; - } - - case ActionTypes.ClearTokenByAddress: { - return { - ...state, - tokenByAddress: {}, - }; - } - - case ActionTypes.AddTokenToTokenByAddress: { - const newTokenByAddress = state.tokenByAddress; - newTokenByAddress[action.data.address] = action.data; - return { - ...state, - tokenByAddress: newTokenByAddress, - }; - } - - case ActionTypes.RemoveTokenFromTokenByAddress: { - const newTokenByAddress = state.tokenByAddress; - delete newTokenByAddress[action.data.address]; - return { - ...state, - tokenByAddress: newTokenByAddress, - }; - } - - case ActionTypes.UpdateTokenByAddress: { - const tokenByAddress = state.tokenByAddress; - const tokens = action.data; - _.each(tokens, token => { - const updatedToken = { - ...tokenByAddress[token.address], - ...token, - }; - tokenByAddress[token.address] = updatedToken; - }); - return { - ...state, - tokenByAddress, - }; - } - - case ActionTypes.UpdateTokenStateByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const updatedTokenStateByAddress = action.data; - _.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => { - const updatedTokenState = { - ...tokenStateByAddress[address], - ...tokenState, - }; - tokenStateByAddress[address] = updatedTokenState; - }); - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.RemoveFromTokenStateByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const tokenAddress = action.data; - delete tokenStateByAddress[tokenAddress]; - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.ReplaceTokenAllowanceByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const allowance = action.data.allowance; - const tokenAddress = action.data.address; - tokenStateByAddress[tokenAddress] = { - ...tokenStateByAddress[tokenAddress], - allowance, - }; - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.ReplaceTokenBalanceByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const balance = action.data.balance; - const tokenAddress = action.data.address; - tokenStateByAddress[tokenAddress] = { - ...tokenStateByAddress[tokenAddress], - balance, - }; - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.UpdateTokenBalanceByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const balanceDelta = action.data.balanceDelta; - const tokenAddress = action.data.address; - const currBalance = tokenStateByAddress[tokenAddress].balance; - tokenStateByAddress[tokenAddress] = { - ...tokenStateByAddress[tokenAddress], - balance: currBalance.plus(balanceDelta), - }; - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.UpdateOrderSignatureData: { - return { - ...state, - orderSignatureData: action.data, - }; - } - - case ActionTypes.UpdateScreenWidth: { - return { - ...state, - screenWidth: action.data, - }; - } - - case ActionTypes.UpdateBlockchainIsLoaded: { - return { - ...state, - blockchainIsLoaded: action.data, - }; - } - - case ActionTypes.BlockchainErrEncountered: { - return { - ...state, - blockchainErr: action.data, - }; - } - - case ActionTypes.UpdateNetworkId: { - return { - ...state, - networkId: action.data, - }; - } - - case ActionTypes.UpdateChosenAssetToken: { - const newSideToAssetToken = { - ...state.sideToAssetToken, - [action.data.side]: action.data.token, - }; - return { - ...state, - sideToAssetToken: newSideToAssetToken, - }; - } - - case ActionTypes.UpdateChosenAssetTokenAddress: { - const newAssetToken = state.sideToAssetToken[action.data.side]; - newAssetToken.address = action.data.address; - const newSideToAssetToken = { - ...state.sideToAssetToken, - [action.data.side]: newAssetToken, - }; - return { - ...state, - sideToAssetToken: newSideToAssetToken, - }; - } - - case ActionTypes.SwapAssetTokens: { - const newSideToAssetToken = { - [Side.Deposit]: state.sideToAssetToken[Side.Receive], - [Side.Receive]: state.sideToAssetToken[Side.Deposit], - }; - return { - ...state, - sideToAssetToken: newSideToAssetToken, - }; - } - - case ActionTypes.UpdateOrderExpiry: { - return { - ...state, - orderExpiryTimestamp: action.data, - }; - } - - case ActionTypes.UpdateOrderTakerAddress: { - return { - ...state, - orderTakerAddress: action.data, - }; - } - - case ActionTypes.UpdateUserAddress: { - return { - ...state, - userAddress: action.data, - }; - } - - // Docs - case ActionTypes.UpdateLibraryVersion: { - return { - ...state, - docsVersion: action.data, - }; - } - case ActionTypes.UpdateAvailableLibraryVersions: { - return { - ...state, - availableDocVersions: action.data, - }; - } - - // Shared - case ActionTypes.ShowFlashMessage: { - return { - ...state, - flashMessage: action.data, - }; - } - - case ActionTypes.HideFlashMessage: { - return { - ...state, - flashMessage: undefined, - }; - } - - case ActionTypes.UpdateProviderType: { - return { - ...state, - providerType: action.data, - }; - } - - case ActionTypes.UpdateInjectedProviderName: { - return { - ...state, - injectedProviderName: action.data, - }; - } - - default: - return state; - } + switch (action.type) { + // Portal + case ActionTypes.ResetState: + return INITIAL_STATE; + + case ActionTypes.UpdateOrderSalt: { + return { + ...state, + orderSalt: action.data, + }; + } + + case ActionTypes.UpdateNodeVersion: { + return { + ...state, + nodeVersion: action.data, + }; + } + + case ActionTypes.UpdateOrderFillAmount: { + return { + ...state, + orderFillAmount: action.data, + }; + } + + case ActionTypes.UpdateShouldBlockchainErrDialogBeOpen: { + return { + ...state, + shouldBlockchainErrDialogBeOpen: action.data, + }; + } + + case ActionTypes.UpdateUserEtherBalance: { + return { + ...state, + userEtherBalance: action.data, + }; + } + + case ActionTypes.UpdateUserSuppliedOrderCache: { + return { + ...state, + userSuppliedOrderCache: action.data, + }; + } + + case ActionTypes.ClearTokenByAddress: { + return { + ...state, + tokenByAddress: {}, + }; + } + + case ActionTypes.AddTokenToTokenByAddress: { + const newTokenByAddress = state.tokenByAddress; + newTokenByAddress[action.data.address] = action.data; + return { + ...state, + tokenByAddress: newTokenByAddress, + }; + } + + case ActionTypes.RemoveTokenFromTokenByAddress: { + const newTokenByAddress = state.tokenByAddress; + delete newTokenByAddress[action.data.address]; + return { + ...state, + tokenByAddress: newTokenByAddress, + }; + } + + case ActionTypes.UpdateTokenByAddress: { + const tokenByAddress = state.tokenByAddress; + const tokens = action.data; + _.each(tokens, token => { + const updatedToken = { + ...tokenByAddress[token.address], + ...token, + }; + tokenByAddress[token.address] = updatedToken; + }); + return { + ...state, + tokenByAddress, + }; + } + + case ActionTypes.UpdateTokenStateByAddress: { + const tokenStateByAddress = state.tokenStateByAddress; + const updatedTokenStateByAddress = action.data; + _.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => { + const updatedTokenState = { + ...tokenStateByAddress[address], + ...tokenState, + }; + tokenStateByAddress[address] = updatedTokenState; + }); + return { + ...state, + tokenStateByAddress, + }; + } + + case ActionTypes.RemoveFromTokenStateByAddress: { + const tokenStateByAddress = state.tokenStateByAddress; + const tokenAddress = action.data; + delete tokenStateByAddress[tokenAddress]; + return { + ...state, + tokenStateByAddress, + }; + } + + case ActionTypes.ReplaceTokenAllowanceByAddress: { + const tokenStateByAddress = state.tokenStateByAddress; + const allowance = action.data.allowance; + const tokenAddress = action.data.address; + tokenStateByAddress[tokenAddress] = { + ...tokenStateByAddress[tokenAddress], + allowance, + }; + return { + ...state, + tokenStateByAddress, + }; + } + + case ActionTypes.ReplaceTokenBalanceByAddress: { + const tokenStateByAddress = state.tokenStateByAddress; + const balance = action.data.balance; + const tokenAddress = action.data.address; + tokenStateByAddress[tokenAddress] = { + ...tokenStateByAddress[tokenAddress], + balance, + }; + return { + ...state, + tokenStateByAddress, + }; + } + + case ActionTypes.UpdateTokenBalanceByAddress: { + const tokenStateByAddress = state.tokenStateByAddress; + const balanceDelta = action.data.balanceDelta; + const tokenAddress = action.data.address; + const currBalance = tokenStateByAddress[tokenAddress].balance; + tokenStateByAddress[tokenAddress] = { + ...tokenStateByAddress[tokenAddress], + balance: currBalance.plus(balanceDelta), + }; + return { + ...state, + tokenStateByAddress, + }; + } + + case ActionTypes.UpdateOrderSignatureData: { + return { + ...state, + orderSignatureData: action.data, + }; + } + + case ActionTypes.UpdateScreenWidth: { + return { + ...state, + screenWidth: action.data, + }; + } + + case ActionTypes.UpdateBlockchainIsLoaded: { + return { + ...state, + blockchainIsLoaded: action.data, + }; + } + + case ActionTypes.BlockchainErrEncountered: { + return { + ...state, + blockchainErr: action.data, + }; + } + + case ActionTypes.UpdateNetworkId: { + return { + ...state, + networkId: action.data, + }; + } + + case ActionTypes.UpdateChosenAssetToken: { + const newSideToAssetToken = { + ...state.sideToAssetToken, + [action.data.side]: action.data.token, + }; + return { + ...state, + sideToAssetToken: newSideToAssetToken, + }; + } + + case ActionTypes.UpdateChosenAssetTokenAddress: { + const newAssetToken = state.sideToAssetToken[action.data.side]; + newAssetToken.address = action.data.address; + const newSideToAssetToken = { + ...state.sideToAssetToken, + [action.data.side]: newAssetToken, + }; + return { + ...state, + sideToAssetToken: newSideToAssetToken, + }; + } + + case ActionTypes.SwapAssetTokens: { + const newSideToAssetToken = { + [Side.Deposit]: state.sideToAssetToken[Side.Receive], + [Side.Receive]: state.sideToAssetToken[Side.Deposit], + }; + return { + ...state, + sideToAssetToken: newSideToAssetToken, + }; + } + + case ActionTypes.UpdateOrderExpiry: { + return { + ...state, + orderExpiryTimestamp: action.data, + }; + } + + case ActionTypes.UpdateOrderTakerAddress: { + return { + ...state, + orderTakerAddress: action.data, + }; + } + + case ActionTypes.UpdateUserAddress: { + return { + ...state, + userAddress: action.data, + }; + } + + // Docs + case ActionTypes.UpdateLibraryVersion: { + return { + ...state, + docsVersion: action.data, + }; + } + case ActionTypes.UpdateAvailableLibraryVersions: { + return { + ...state, + availableDocVersions: action.data, + }; + } + + // Shared + case ActionTypes.ShowFlashMessage: { + return { + ...state, + flashMessage: action.data, + }; + } + + case ActionTypes.HideFlashMessage: { + return { + ...state, + flashMessage: undefined, + }; + } + + case ActionTypes.UpdateProviderType: { + return { + ...state, + providerType: action.data, + }; + } + + case ActionTypes.UpdateInjectedProviderName: { + return { + ...state, + injectedProviderName: action.data, + }; + } + + default: + return state; + } } diff --git a/packages/website/ts/schemas/order_schema.ts b/packages/website/ts/schemas/order_schema.ts index fd0bf113a..bfbf9eb8b 100644 --- a/packages/website/ts/schemas/order_schema.ts +++ b/packages/website/ts/schemas/order_schema.ts @@ -1,15 +1,15 @@ export const orderSchema = { - id: '/Order', - properties: { - maker: { $ref: '/OrderTaker' }, - taker: { $ref: '/OrderTaker' }, - salt: { type: 'string' }, - signature: { $ref: '/SignatureData' }, - expiration: { type: 'string' }, - feeRecipient: { type: 'string' }, - exchangeContract: { type: 'string' }, - networkId: { type: 'number' }, - }, - required: ['maker', 'taker', 'salt', 'signature', 'expiration', 'feeRecipient', 'exchangeContract', 'networkId'], - type: 'object', + id: '/Order', + properties: { + maker: { $ref: '/OrderTaker' }, + taker: { $ref: '/OrderTaker' }, + salt: { type: 'string' }, + signature: { $ref: '/SignatureData' }, + expiration: { type: 'string' }, + feeRecipient: { type: 'string' }, + exchangeContract: { type: 'string' }, + networkId: { type: 'number' }, + }, + required: ['maker', 'taker', 'salt', 'signature', 'expiration', 'feeRecipient', 'exchangeContract', 'networkId'], + type: 'object', }; diff --git a/packages/website/ts/schemas/order_taker_schema.ts b/packages/website/ts/schemas/order_taker_schema.ts index c84ec4a9f..c784c29c5 100644 --- a/packages/website/ts/schemas/order_taker_schema.ts +++ b/packages/website/ts/schemas/order_taker_schema.ts @@ -1,11 +1,11 @@ export const orderTakerSchema = { - id: '/OrderTaker', - properties: { - address: { type: 'string' }, - token: { $ref: '/Token' }, - amount: { type: 'string' }, - feeAmount: { type: 'string' }, - }, - required: ['address', 'token', 'amount', 'feeAmount'], - type: 'object', + id: '/OrderTaker', + properties: { + address: { type: 'string' }, + token: { $ref: '/Token' }, + amount: { type: 'string' }, + feeAmount: { type: 'string' }, + }, + required: ['address', 'token', 'amount', 'feeAmount'], + type: 'object', }; diff --git a/packages/website/ts/schemas/signature_data_schema.ts b/packages/website/ts/schemas/signature_data_schema.ts index 8cafff9e8..8d3f15926 100644 --- a/packages/website/ts/schemas/signature_data_schema.ts +++ b/packages/website/ts/schemas/signature_data_schema.ts @@ -1,11 +1,11 @@ export const signatureDataSchema = { - id: '/SignatureData', - properties: { - hash: { type: 'string' }, - r: { type: 'string' }, - s: { type: 'string' }, - v: { type: 'number' }, - }, - required: ['hash', 'r', 's', 'v'], - type: 'object', + id: '/SignatureData', + properties: { + hash: { type: 'string' }, + r: { type: 'string' }, + s: { type: 'string' }, + v: { type: 'number' }, + }, + required: ['hash', 'r', 's', 'v'], + type: 'object', }; diff --git a/packages/website/ts/schemas/token_schema.ts b/packages/website/ts/schemas/token_schema.ts index 6a3bed786..92b53a463 100644 --- a/packages/website/ts/schemas/token_schema.ts +++ b/packages/website/ts/schemas/token_schema.ts @@ -1,11 +1,11 @@ export const tokenSchema = { - id: '/Token', - properties: { - name: { type: 'string' }, - symbol: { type: 'string' }, - decimals: { type: 'number' }, - address: { type: 'string' }, - }, - required: ['name', 'symbol', 'decimals', 'address'], - type: 'object', + id: '/Token', + properties: { + name: { type: 'string' }, + symbol: { type: 'string' }, + decimals: { type: 'number' }, + address: { type: 'string' }, + }, + required: ['name', 'symbol', 'decimals', 'address'], + type: 'object', }; diff --git a/packages/website/ts/schemas/validator.ts b/packages/website/ts/schemas/validator.ts index a3aaafc98..5177501c6 100644 --- a/packages/website/ts/schemas/validator.ts +++ b/packages/website/ts/schemas/validator.ts @@ -5,15 +5,15 @@ import { signatureDataSchema } from 'ts/schemas/signature_data_schema'; import { tokenSchema } from 'ts/schemas/token_schema'; export class SchemaValidator { - private _validator: Validator; - constructor() { - this._validator = new Validator(); - this._validator.addSchema(signatureDataSchema as JSONSchema, signatureDataSchema.id); - this._validator.addSchema(tokenSchema as JSONSchema, tokenSchema.id); - this._validator.addSchema(orderTakerSchema as JSONSchema, orderTakerSchema.id); - this._validator.addSchema(orderSchema as JSONSchema, orderSchema.id); - } - public validate(instance: object, schema: Schema) { - return this._validator.validate(instance, schema); - } + private _validator: Validator; + constructor() { + this._validator = new Validator(); + this._validator.addSchema(signatureDataSchema as JSONSchema, signatureDataSchema.id); + this._validator.addSchema(tokenSchema as JSONSchema, tokenSchema.id); + this._validator.addSchema(orderTakerSchema as JSONSchema, orderTakerSchema.id); + this._validator.addSchema(orderSchema as JSONSchema, orderSchema.id); + } + public validate(instance: object, schema: Schema) { + return this._validator.validate(instance, schema); + } } diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index a853792cb..f873f95fa 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -2,680 +2,680 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; export enum Side { - Receive = 'RECEIVE', - Deposit = 'DEPOSIT', + Receive = 'RECEIVE', + Deposit = 'DEPOSIT', } export interface Token { - iconUrl?: string; - name: string; - address: string; - symbol: string; - decimals: number; - isTracked: boolean; - isRegistered: boolean; + iconUrl?: string; + name: string; + address: string; + symbol: string; + decimals: number; + isTracked: boolean; + isRegistered: boolean; } export interface TokenByAddress { - [address: string]: Token; + [address: string]: Token; } export interface TokenState { - allowance: BigNumber; - balance: BigNumber; + allowance: BigNumber; + balance: BigNumber; } export interface TokenStateByAddress { - [address: string]: TokenState; + [address: string]: TokenState; } export interface AssetToken { - address?: string; - amount?: BigNumber; + address?: string; + amount?: BigNumber; } export interface SideToAssetToken { - [side: string]: AssetToken; + [side: string]: AssetToken; } export interface SignatureData { - hash: string; - r: string; - s: string; - v: number; + hash: string; + r: string; + s: string; + v: number; } export interface HashData { - depositAmount: BigNumber; - depositTokenContractAddr: string; - feeRecipientAddress: string; - makerFee: BigNumber; - orderExpiryTimestamp: BigNumber; - orderMakerAddress: string; - orderTakerAddress: string; - receiveAmount: BigNumber; - receiveTokenContractAddr: string; - takerFee: BigNumber; - orderSalt: BigNumber; + depositAmount: BigNumber; + depositTokenContractAddr: string; + feeRecipientAddress: string; + makerFee: BigNumber; + orderExpiryTimestamp: BigNumber; + orderMakerAddress: string; + orderTakerAddress: string; + receiveAmount: BigNumber; + receiveTokenContractAddr: string; + takerFee: BigNumber; + orderSalt: BigNumber; } export interface OrderToken { - name: string; - symbol: string; - decimals: number; - address: string; + name: string; + symbol: string; + decimals: number; + address: string; } export interface OrderParty { - address: string; - token: OrderToken; - amount: string; - feeAmount: string; + address: string; + token: OrderToken; + amount: string; + feeAmount: string; } export interface Order { - maker: OrderParty; - taker: OrderParty; - expiration: string; - feeRecipient: string; - salt: string; - signature: SignatureData; - exchangeContract: string; - networkId: number; + maker: OrderParty; + taker: OrderParty; + expiration: string; + feeRecipient: string; + salt: string; + signature: SignatureData; + exchangeContract: string; + networkId: number; } export interface Fill { - logIndex: number; - maker: string; - taker: string; - makerToken: string; - takerToken: string; - filledMakerTokenAmount: BigNumber; - filledTakerTokenAmount: BigNumber; - paidMakerFee: BigNumber; - paidTakerFee: BigNumber; - orderHash: string; - transactionHash: string; - blockTimestamp: number; + logIndex: number; + maker: string; + taker: string; + makerToken: string; + takerToken: string; + filledMakerTokenAmount: BigNumber; + filledTakerTokenAmount: BigNumber; + paidMakerFee: BigNumber; + paidTakerFee: BigNumber; + orderHash: string; + transactionHash: string; + blockTimestamp: number; } export enum BalanceErrs { - incorrectNetworkForFaucet, - faucetRequestFailed, - faucetQueueIsFull, - mintingFailed, - sendFailed, - allowanceSettingFailed, + incorrectNetworkForFaucet, + faucetRequestFailed, + faucetQueueIsFull, + mintingFailed, + sendFailed, + allowanceSettingFailed, } export enum ActionTypes { - // Portal - UpdateScreenWidth = 'UPDATE_SCREEN_WIDTH', - UpdateNodeVersion = 'UPDATE_NODE_VERSION', - ResetState = 'RESET_STATE', - AddTokenToTokenByAddress = 'ADD_TOKEN_TO_TOKEN_BY_ADDRESS', - BlockchainErrEncountered = 'BLOCKCHAIN_ERR_ENCOUNTERED', - ClearTokenByAddress = 'CLEAR_TOKEN_BY_ADDRESS', - UpdateBlockchainIsLoaded = 'UPDATE_BLOCKCHAIN_IS_LOADED', - UpdateNetworkId = 'UPDATE_NETWORK_ID', - UpdateChosenAssetToken = 'UPDATE_CHOSEN_ASSET_TOKEN', - UpdateChosenAssetTokenAddress = 'UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS', - UpdateOrderTakerAddress = 'UPDATE_ORDER_TAKER_ADDRESS', - UpdateOrderSalt = 'UPDATE_ORDER_SALT', - UpdateOrderSignatureData = 'UPDATE_ORDER_SIGNATURE_DATA', - UpdateTokenByAddress = 'UPDATE_TOKEN_BY_ADDRESS', - RemoveTokenFromTokenByAddress = 'REMOVE_TOKEN_FROM_TOKEN_BY_ADDRESS', - UpdateTokenStateByAddress = 'UPDATE_TOKEN_STATE_BY_ADDRESS', - RemoveFromTokenStateByAddress = 'REMOVE_FROM_TOKEN_STATE_BY_ADDRESS', - ReplaceTokenAllowanceByAddress = 'REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS', - ReplaceTokenBalanceByAddress = 'REPLACE_TOKEN_BALANCE_BY_ADDRESS', - UpdateTokenBalanceByAddress = 'UPDATE_TOKEN_BALANCE_BY_ADDRESS', - UpdateOrderExpiry = 'UPDATE_ORDER_EXPIRY', - SwapAssetTokens = 'SWAP_ASSET_TOKENS', - UpdateUserAddress = 'UPDATE_USER_ADDRESS', - UpdateUserEtherBalance = 'UPDATE_USER_ETHER_BALANCE', - UpdateUserSuppliedOrderCache = 'UPDATE_USER_SUPPLIED_ORDER_CACHE', - UpdateOrderFillAmount = 'UPDATE_ORDER_FILL_AMOUNT', - UpdateShouldBlockchainErrDialogBeOpen = 'UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN', - - // Docs - UpdateLibraryVersion = 'UPDATE_LIBRARY_VERSION', - UpdateAvailableLibraryVersions = 'UPDATE_AVAILABLE_LIBRARY_VERSIONS', - - // Shared - ShowFlashMessage = 'SHOW_FLASH_MESSAGE', - HideFlashMessage = 'HIDE_FLASH_MESSAGE', - UpdateProviderType = 'UPDATE_PROVIDER_TYPE', - UpdateInjectedProviderName = 'UPDATE_INJECTED_PROVIDER_NAME', + // Portal + UpdateScreenWidth = 'UPDATE_SCREEN_WIDTH', + UpdateNodeVersion = 'UPDATE_NODE_VERSION', + ResetState = 'RESET_STATE', + AddTokenToTokenByAddress = 'ADD_TOKEN_TO_TOKEN_BY_ADDRESS', + BlockchainErrEncountered = 'BLOCKCHAIN_ERR_ENCOUNTERED', + ClearTokenByAddress = 'CLEAR_TOKEN_BY_ADDRESS', + UpdateBlockchainIsLoaded = 'UPDATE_BLOCKCHAIN_IS_LOADED', + UpdateNetworkId = 'UPDATE_NETWORK_ID', + UpdateChosenAssetToken = 'UPDATE_CHOSEN_ASSET_TOKEN', + UpdateChosenAssetTokenAddress = 'UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS', + UpdateOrderTakerAddress = 'UPDATE_ORDER_TAKER_ADDRESS', + UpdateOrderSalt = 'UPDATE_ORDER_SALT', + UpdateOrderSignatureData = 'UPDATE_ORDER_SIGNATURE_DATA', + UpdateTokenByAddress = 'UPDATE_TOKEN_BY_ADDRESS', + RemoveTokenFromTokenByAddress = 'REMOVE_TOKEN_FROM_TOKEN_BY_ADDRESS', + UpdateTokenStateByAddress = 'UPDATE_TOKEN_STATE_BY_ADDRESS', + RemoveFromTokenStateByAddress = 'REMOVE_FROM_TOKEN_STATE_BY_ADDRESS', + ReplaceTokenAllowanceByAddress = 'REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS', + ReplaceTokenBalanceByAddress = 'REPLACE_TOKEN_BALANCE_BY_ADDRESS', + UpdateTokenBalanceByAddress = 'UPDATE_TOKEN_BALANCE_BY_ADDRESS', + UpdateOrderExpiry = 'UPDATE_ORDER_EXPIRY', + SwapAssetTokens = 'SWAP_ASSET_TOKENS', + UpdateUserAddress = 'UPDATE_USER_ADDRESS', + UpdateUserEtherBalance = 'UPDATE_USER_ETHER_BALANCE', + UpdateUserSuppliedOrderCache = 'UPDATE_USER_SUPPLIED_ORDER_CACHE', + UpdateOrderFillAmount = 'UPDATE_ORDER_FILL_AMOUNT', + UpdateShouldBlockchainErrDialogBeOpen = 'UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN', + + // Docs + UpdateLibraryVersion = 'UPDATE_LIBRARY_VERSION', + UpdateAvailableLibraryVersions = 'UPDATE_AVAILABLE_LIBRARY_VERSIONS', + + // Shared + ShowFlashMessage = 'SHOW_FLASH_MESSAGE', + HideFlashMessage = 'HIDE_FLASH_MESSAGE', + UpdateProviderType = 'UPDATE_PROVIDER_TYPE', + UpdateInjectedProviderName = 'UPDATE_INJECTED_PROVIDER_NAME', } export interface Action { - type: ActionTypes; - data?: any; + type: ActionTypes; + data?: any; } export interface TrackedTokensByNetworkId { - [networkId: number]: Token[]; + [networkId: number]: Token[]; } export interface TrackedTokensByUserAddress { - [userAddress: string]: TrackedTokensByNetworkId; + [userAddress: string]: TrackedTokensByNetworkId; } export interface Styles { - [name: string]: React.CSSProperties; + [name: string]: React.CSSProperties; } export interface ProfileInfo { - name: string; - title?: string; - description: string; - image: string; - linkedIn?: string; - github?: string; - angellist?: string; - medium?: string; - twitter?: string; + name: string; + title?: string; + description: string; + image: string; + linkedIn?: string; + github?: string; + angellist?: string; + medium?: string; + twitter?: string; } export interface Partner { - name: string; - logo: string; - url: string; + name: string; + logo: string; + url: string; } export interface Statistic { - title: string; - figure: string; + title: string; + figure: string; } export interface StatisticByKey { - [key: string]: Statistic; + [key: string]: Statistic; } export interface ERC20MarketInfo { - etherMarketCapUsd: number; - numLiquidERC20Tokens: number; - marketCapERC20TokensUsd: number; + etherMarketCapUsd: number; + numLiquidERC20Tokens: number; + marketCapERC20TokensUsd: number; } export enum ExchangeContractErrs { - OrderFillExpired = 'ORDER_FILL_EXPIRED', - OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', - OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', - OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', - FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR', - InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE', - InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE', - InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE', - InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE', - TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER', - InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT', + OrderFillExpired = 'ORDER_FILL_EXPIRED', + OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', + OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', + OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', + FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR', + InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE', + InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE', + InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE', + InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE', + TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER', + InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT', } export interface ContractResponse { - logs: ContractEvent[]; + logs: ContractEvent[]; } export interface ContractEvent { - event: string; - args: any; + event: string; + args: any; } export type InputErrMsg = React.ReactNode | string | undefined; export type ValidatedBigNumberCallback = (isValid: boolean, amount?: BigNumber) => void; export enum ScreenWidths { - Sm = 'SM', - Md = 'MD', - Lg = 'LG', + Sm = 'SM', + Md = 'MD', + Lg = 'LG', } export enum AlertTypes { - ERROR, - SUCCESS, + ERROR, + SUCCESS, } export enum EtherscanLinkSuffixes { - Address = 'address', - Tx = 'tx', + Address = 'address', + Tx = 'tx', } export enum BlockchainErrs { - AContractNotDeployedOnNetwork = 'A_CONTRACT_NOT_DEPLOYED_ON_NETWORK', - DisconnectedFromEthereumNode = 'DISCONNECTED_FROM_ETHEREUM_NODE', - NoError = 'NO_ERROR', + AContractNotDeployedOnNetwork = 'A_CONTRACT_NOT_DEPLOYED_ON_NETWORK', + DisconnectedFromEthereumNode = 'DISCONNECTED_FROM_ETHEREUM_NODE', + NoError = 'NO_ERROR', } export enum BlockchainCallErrs { - ContractDoesNotExist = 'CONTRACT_DOES_NOT_EXIST', - UserHasNoAssociatedAddresses = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', - UnhandledError = 'UNHANDLED_ERROR', - TokenAddressIsInvalid = 'TOKEN_ADDRESS_IS_INVALID', + ContractDoesNotExist = 'CONTRACT_DOES_NOT_EXIST', + UserHasNoAssociatedAddresses = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', + UnhandledError = 'UNHANDLED_ERROR', + TokenAddressIsInvalid = 'TOKEN_ADDRESS_IS_INVALID', } // Exception: We don't make the values uppercase because these KindString's need to // match up those returned by TypeDoc export enum KindString { - Constructor = 'Constructor', - Property = 'Property', - Method = 'Method', - Interface = 'Interface', - TypeAlias = 'Type alias', - Variable = 'Variable', - Function = 'Function', - Enumeration = 'Enumeration', + Constructor = 'Constructor', + Property = 'Property', + Method = 'Method', + Interface = 'Interface', + TypeAlias = 'Type alias', + Variable = 'Variable', + Function = 'Function', + Enumeration = 'Enumeration', } export interface EnumValue { - name: string; - defaultValue?: string; + name: string; + defaultValue?: string; } export enum Environments { - DEVELOPMENT, - PRODUCTION, + DEVELOPMENT, + PRODUCTION, } export type ContractInstance = any; // TODO: add type definition for Contract export interface TypeDocType { - type: TypeDocTypes; - value: string; - name: string; - types: TypeDocType[]; - typeArguments?: TypeDocType[]; - declaration: TypeDocNode; - elementType?: TypeDocType; + type: TypeDocTypes; + value: string; + name: string; + types: TypeDocType[]; + typeArguments?: TypeDocType[]; + declaration: TypeDocNode; + elementType?: TypeDocType; } export interface TypeDocFlags { - isStatic?: boolean; - isOptional?: boolean; - isPublic?: boolean; + isStatic?: boolean; + isOptional?: boolean; + isPublic?: boolean; } export interface TypeDocGroup { - title: string; - children: number[]; + title: string; + children: number[]; } export interface TypeDocNode { - id?: number; - name?: string; - kind?: string; - defaultValue?: string; - kindString?: string; - type?: TypeDocType; - fileName?: string; - line?: number; - comment?: TypeDocNode; - text?: string; - shortText?: string; - returns?: string; - declaration: TypeDocNode; - flags?: TypeDocFlags; - indexSignature?: TypeDocNode[]; - signatures?: TypeDocNode[]; - parameters?: TypeDocNode[]; - typeParameter?: TypeDocNode[]; - sources?: TypeDocNode[]; - children?: TypeDocNode[]; - groups?: TypeDocGroup[]; + id?: number; + name?: string; + kind?: string; + defaultValue?: string; + kindString?: string; + type?: TypeDocType; + fileName?: string; + line?: number; + comment?: TypeDocNode; + text?: string; + shortText?: string; + returns?: string; + declaration: TypeDocNode; + flags?: TypeDocFlags; + indexSignature?: TypeDocNode[]; + signatures?: TypeDocNode[]; + parameters?: TypeDocNode[]; + typeParameter?: TypeDocNode[]; + sources?: TypeDocNode[]; + children?: TypeDocNode[]; + groups?: TypeDocGroup[]; } export enum TypeDocTypes { - Intrinsic = 'intrinsic', - Reference = 'reference', - Array = 'array', - StringLiteral = 'stringLiteral', - Reflection = 'reflection', - Union = 'union', - TypeParameter = 'typeParameter', - Unknown = 'unknown', + Intrinsic = 'intrinsic', + Reference = 'reference', + Array = 'array', + StringLiteral = 'stringLiteral', + Reflection = 'reflection', + Union = 'union', + TypeParameter = 'typeParameter', + Unknown = 'unknown', } export interface DocAgnosticFormat { - [sectionName: string]: DocSection; + [sectionName: string]: DocSection; } export interface DocSection { - comment: string; - constructors: Array<TypescriptMethod | SolidityMethod>; - methods: Array<TypescriptMethod | SolidityMethod>; - properties: Property[]; - types: CustomType[]; - events?: Event[]; + comment: string; + constructors: Array<TypescriptMethod | SolidityMethod>; + methods: Array<TypescriptMethod | SolidityMethod>; + properties: Property[]; + types: CustomType[]; + events?: Event[]; } export interface Event { - name: string; - eventArgs: EventArg[]; + name: string; + eventArgs: EventArg[]; } export interface EventArg { - isIndexed: boolean; - name: string; - type: Type; + isIndexed: boolean; + name: string; + type: Type; } export interface Property { - name: string; - type: Type; - source?: Source; - comment?: string; + name: string; + type: Type; + source?: Source; + comment?: string; } export interface BaseMethod { - isConstructor: boolean; - name: string; - returnComment?: string | undefined; - callPath: string; - parameters: Parameter[]; - returnType: Type; - comment?: string; + isConstructor: boolean; + name: string; + returnComment?: string | undefined; + callPath: string; + parameters: Parameter[]; + returnType: Type; + comment?: string; } export interface TypescriptMethod extends BaseMethod { - source?: Source; - isStatic?: boolean; - typeParameter?: TypeParameter; + source?: Source; + isStatic?: boolean; + typeParameter?: TypeParameter; } export interface SolidityMethod extends BaseMethod { - isConstant?: boolean; - isPayable?: boolean; + isConstant?: boolean; + isPayable?: boolean; } export interface Source { - fileName: string; - line: number; + fileName: string; + line: number; } export interface Parameter { - name: string; - comment: string; - isOptional: boolean; - type: Type; + name: string; + comment: string; + isOptional: boolean; + type: Type; } export interface TypeParameter { - name: string; - type: Type; + name: string; + type: Type; } export interface Type { - name: string; - typeDocType: TypeDocTypes; - value?: string; - typeArguments?: Type[]; - elementType?: ElementType; - types?: Type[]; - method?: TypescriptMethod; + name: string; + typeDocType: TypeDocTypes; + value?: string; + typeArguments?: Type[]; + elementType?: ElementType; + types?: Type[]; + method?: TypescriptMethod; } export interface ElementType { - name: string; - typeDocType: TypeDocTypes; + name: string; + typeDocType: TypeDocTypes; } export interface IndexSignature { - keyName: string; - keyType: Type; - valueName: string; + keyName: string; + keyType: Type; + valueName: string; } export interface CustomType { - name: string; - kindString: string; - type?: Type; - method?: TypescriptMethod; - indexSignature?: IndexSignature; - defaultValue?: string; - comment?: string; - children?: CustomTypeChild[]; + name: string; + kindString: string; + type?: Type; + method?: TypescriptMethod; + indexSignature?: IndexSignature; + defaultValue?: string; + comment?: string; + children?: CustomTypeChild[]; } export interface CustomTypeChild { - name: string; - type?: Type; - defaultValue?: string; + name: string; + type?: Type; + defaultValue?: string; } export interface FAQQuestion { - prompt: string; - answer: React.ReactNode; + prompt: string; + answer: React.ReactNode; } export interface FAQSection { - name: string; - questions: FAQQuestion[]; + name: string; + questions: FAQQuestion[]; } export interface S3FileObject { - Key: { - _text: string; - }; + Key: { + _text: string; + }; } export interface MenuSubsectionsBySection { - [section: string]: string[]; + [section: string]: string[]; } export enum ProviderType { - Injected = 'INJECTED', - Ledger = 'LEDGER', + Injected = 'INJECTED', + Ledger = 'LEDGER', } export interface Fact { - title: string; - explanation: string; - image: string; + title: string; + explanation: string; + image: string; } interface LedgerGetAddressResult { - address: string; + address: string; } interface LedgerSignResult { - v: string; - r: string; - s: string; + v: string; + r: string; + s: string; } interface LedgerCommunication { - close_async: () => Promise<void>; + close_async: () => Promise<void>; } export interface LedgerEthConnection { - getAddress_async: ( - derivationPath: string, - askForDeviceConfirmation: boolean, - shouldGetChainCode: boolean, - ) => Promise<LedgerGetAddressResult>; - signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<LedgerSignResult>; - signTransaction_async: (derivationPath: string, txHex: string) => Promise<LedgerSignResult>; - comm: LedgerCommunication; + getAddress_async: ( + derivationPath: string, + askForDeviceConfirmation: boolean, + shouldGetChainCode: boolean, + ) => Promise<LedgerGetAddressResult>; + signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<LedgerSignResult>; + signTransaction_async: (derivationPath: string, txHex: string) => Promise<LedgerSignResult>; + comm: LedgerCommunication; } export interface SignPersonalMessageParams { - data: string; + data: string; } export interface TxParams { - nonce: string; - gasPrice?: number; - gasLimit: string; - to: string; - value?: string; - data?: string; - chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3 + nonce: string; + gasPrice?: number; + gasLimit: string; + to: string; + value?: string; + data?: string; + chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3 } export interface PublicNodeUrlsByNetworkId { - [networkId: number]: string[]; + [networkId: number]: string[]; } export interface JSONRPCPayload { - params: any[]; - method: string; + params: any[]; + method: string; } export interface BlogPost { - image: string; - date: string; - title: string; - description: string; - url: string; + image: string; + date: string; + title: string; + description: string; + url: string; } export interface TypeDefinitionByName { - [typeName: string]: CustomType; + [typeName: string]: CustomType; } export interface Article { - section: string; - title: string; - content: string; - fileName: string; + section: string; + title: string; + content: string; + fileName: string; } export interface ArticlesBySection { - [section: string]: Article[]; + [section: string]: Article[]; } export interface DialogConfigs { - title: string; - isModal: boolean; - actions: any[]; + title: string; + isModal: boolean; + actions: any[]; } export enum TokenVisibility { - ALL = 'ALL', - UNTRACKED = 'UNTRACKED', - TRACKED = 'TRACKED', + ALL = 'ALL', + UNTRACKED = 'UNTRACKED', + TRACKED = 'TRACKED', } export enum HeaderSizes { - H1 = 'h1', - H2 = 'h2', - H3 = 'h3', + H1 = 'h1', + H2 = 'h2', + H3 = 'h3', } export interface DoxityDocObj { - [contractName: string]: DoxityContractObj; + [contractName: string]: DoxityContractObj; } export interface DoxityContractObj { - title: string; - fileName: string; - name: string; - abiDocs: DoxityAbiDoc[]; + title: string; + fileName: string; + name: string; + abiDocs: DoxityAbiDoc[]; } export interface DoxityAbiDoc { - constant: boolean; - inputs: DoxityInput[]; - name: string; - outputs: DoxityOutput[]; - payable: boolean; - type: string; - details?: string; - return?: string; + constant: boolean; + inputs: DoxityInput[]; + name: string; + outputs: DoxityOutput[]; + payable: boolean; + type: string; + details?: string; + return?: string; } export interface DoxityOutput { - name: string; - type: string; + name: string; + type: string; } export interface DoxityInput { - name: string; - type: string; - description: string; - indexed?: boolean; + name: string; + type: string; + description: string; + indexed?: boolean; } export interface VersionToFileName { - [version: string]: string; + [version: string]: string; } export enum Docs { - ZeroExJs, - SmartContracts, + ZeroExJs, + SmartContracts, } export interface ContractAddresses { - [version: string]: { - [network: string]: AddressByContractName; - }; + [version: string]: { + [network: string]: AddressByContractName; + }; } export interface AddressByContractName { - [contractName: string]: string; + [contractName: string]: string; } export enum Networks { - mainnet = 'Mainnet', - kovan = 'Kovan', - ropsten = 'Ropsten', - rinkeby = 'Rinkeby', + mainnet = 'Mainnet', + kovan = 'Kovan', + ropsten = 'Ropsten', + rinkeby = 'Rinkeby', } export enum AbiTypes { - Constructor = 'constructor', - Function = 'function', - Event = 'event', + Constructor = 'constructor', + Function = 'function', + Event = 'event', } export enum WebsitePaths { - Portal = '/portal', - Wiki = '/wiki', - ZeroExJs = '/docs/0xjs', - Home = '/', - FAQ = '/faq', - About = '/about', - Whitepaper = '/pdfs/0x_white_paper.pdf', - SmartContracts = '/docs/contracts', - Connect = '/docs/connect', + Portal = '/portal', + Wiki = '/wiki', + ZeroExJs = '/docs/0xjs', + Home = '/', + FAQ = '/faq', + About = '/about', + Whitepaper = '/pdfs/0x_white_paper.pdf', + SmartContracts = '/docs/contracts', + Connect = '/docs/connect', } export interface DocsMenu { - [sectionName: string]: string[]; + [sectionName: string]: string[]; } export interface SectionsMap { - [sectionName: string]: string; + [sectionName: string]: string; } export interface DocsInfoConfig { - displayName: string; - packageUrl: string; - websitePath: string; - docsJsonRoot: string; - menu: DocsMenu; - sections: SectionsMap; - sectionNameToMarkdown: { [sectionName: string]: string }; - visibleConstructors: string[]; - convertToDocAgnosticFormatFn: (docObj: DoxityDocObj | TypeDocNode, docsInfo?: any) => DocAgnosticFormat; - subPackageName?: string; - publicTypes?: string[]; - sectionNameToModulePath?: { [sectionName: string]: string[] }; - menuSubsectionToVersionWhenIntroduced?: { [sectionName: string]: string }; + displayName: string; + packageUrl: string; + websitePath: string; + docsJsonRoot: string; + menu: DocsMenu; + sections: SectionsMap; + sectionNameToMarkdown: { [sectionName: string]: string }; + visibleConstructors: string[]; + convertToDocAgnosticFormatFn: (docObj: DoxityDocObj | TypeDocNode, docsInfo?: any) => DocAgnosticFormat; + subPackageName?: string; + publicTypes?: string[]; + sectionNameToModulePath?: { [sectionName: string]: string[] }; + menuSubsectionToVersionWhenIntroduced?: { [sectionName: string]: string }; } export interface TimestampMsRange { - startTimestampMs: number; - endTimestampMs: number; + startTimestampMs: number; + endTimestampMs: number; } export interface OutdatedWrappedEtherByNetworkId { - [networkId: number]: { - address: string; - timestampMsRange: TimestampMsRange; - }; + [networkId: number]: { + address: string; + timestampMsRange: TimestampMsRange; + }; } export enum SmartContractDocSections { - Introduction = 'Introduction', - Exchange = 'Exchange', - TokenTransferProxy = 'TokenTransferProxy', - TokenRegistry = 'TokenRegistry', - ZRXToken = 'ZRXToken', + Introduction = 'Introduction', + Exchange = 'Exchange', + TokenTransferProxy = 'TokenTransferProxy', + TokenRegistry = 'TokenRegistry', + ZRXToken = 'ZRXToken', } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/utils/colors.ts b/packages/website/ts/utils/colors.ts index dabc1fd54..58ce667e3 100644 --- a/packages/website/ts/utils/colors.ts +++ b/packages/website/ts/utils/colors.ts @@ -1,43 +1,43 @@ import { colors as materialUiColors } from 'material-ui/styles'; export const colors = { - ...materialUiColors, - grey50: '#FAFAFA', - grey100: '#F5F5F5', - lightestGrey: '#F0F0F0', - greyishPink: '#E6E5E5', - grey300: '#E0E0E0', - beigeWhite: '#E4E4E4', - grey400: '#BDBDBD', - lightGrey: '#BBBBBB', - grey500: '#9E9E9E', - grey: '#A5A5A5', - darkGrey: '#818181', - landingLinkGrey: '#919191', - grey700: '#616161', - grey800: '#424242', - darkerGrey: '#393939', - heroGrey: '#404040', - projectsGrey: '#343333', - darkestGrey: '#272727', - dharmaDarkGrey: '#252525', - lightBlue: '#60A4F4', - lightBlueA700: '#0091EA', - darkBlue: '#4D5481', - turquois: '#058789', - lightPurple: '#A81CA6', - purple: '#690596', - red200: '#EF9A9A', - red: '#E91751', - red500: '#F44336', - red600: '#E53935', - limeGreen: '#66DE75', - lightGreen: '#4DC55C', - lightestGreen: '#89C774', - brightGreen: '#00C33E', - green400: '#66BB6A', - green: '#4DA24B', - amber600: '#FFB300', - orange: '#E69D00', - amber800: '#FF8F00', + ...materialUiColors, + grey50: '#FAFAFA', + grey100: '#F5F5F5', + lightestGrey: '#F0F0F0', + greyishPink: '#E6E5E5', + grey300: '#E0E0E0', + beigeWhite: '#E4E4E4', + grey400: '#BDBDBD', + lightGrey: '#BBBBBB', + grey500: '#9E9E9E', + grey: '#A5A5A5', + darkGrey: '#818181', + landingLinkGrey: '#919191', + grey700: '#616161', + grey800: '#424242', + darkerGrey: '#393939', + heroGrey: '#404040', + projectsGrey: '#343333', + darkestGrey: '#272727', + dharmaDarkGrey: '#252525', + lightBlue: '#60A4F4', + lightBlueA700: '#0091EA', + darkBlue: '#4D5481', + turquois: '#058789', + lightPurple: '#A81CA6', + purple: '#690596', + red200: '#EF9A9A', + red: '#E91751', + red500: '#F44336', + red600: '#E53935', + limeGreen: '#66DE75', + lightGreen: '#4DC55C', + lightestGreen: '#89C774', + brightGreen: '#00C33E', + green400: '#66BB6A', + green: '#4DA24B', + amber600: '#FFB300', + orange: '#E69D00', + amber800: '#FF8F00', }; diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 6327ea05a..3d37a89ab 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -1,126 +1,126 @@ import * as _ from 'lodash'; import { - ContractAddresses, - Environments, - Networks, - OutdatedWrappedEtherByNetworkId, - PublicNodeUrlsByNetworkId, - SmartContractDocSections, + ContractAddresses, + Environments, + Networks, + OutdatedWrappedEtherByNetworkId, + PublicNodeUrlsByNetworkId, + SmartContractDocSections, } from 'ts/types'; const BASE_URL = window.location.origin; const isDevelopment = _.includes( - ['https://0xproject.localhost:3572', 'https://localhost:3572', 'https://127.0.0.1'], - BASE_URL, + ['https://0xproject.localhost:3572', 'https://localhost:3572', 'https://127.0.0.1'], + BASE_URL, ); const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs'; export const configs = { - BACKEND_BASE_URL: isDevelopment ? 'https://localhost:3001' : 'https://website-api.0xproject.com', - BASE_URL, - BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', - CONTRACT_ADDRESS: { - '1.0.0': { - [Networks.mainnet]: { - [SmartContractDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093', - [SmartContractDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', - [SmartContractDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498', - [SmartContractDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c', - }, - [Networks.ropsten]: { - [SmartContractDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac', - [SmartContractDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6', - [SmartContractDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', - [SmartContractDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed', - }, - [Networks.kovan]: { - [SmartContractDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', - [SmartContractDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4', - [SmartContractDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', - [SmartContractDocSections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f', - }, - }, - } as ContractAddresses, - DEFAULT_DERIVATION_PATH: `44'/60'/0'`, - // WARNING: ZRX & WETH MUST always be default trackedTokens - DEFAULT_TRACKED_TOKEN_SYMBOLS: ['WETH', 'ZRX'], - DOMAIN_STAGING: 'staging-0xproject.s3-website-us-east-1.amazonaws.com', - DOMAIN_DEVELOPMENT: '0xproject.localhost:3572', - DOMAIN_PRODUCTION: '0xproject.com', - ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION, - ICON_URL_BY_SYMBOL: { - REP: '/images/token_icons/augur.png', - DGD: '/images/token_icons/digixdao.png', - WETH: '/images/token_icons/ether_erc20.png', - MLN: '/images/token_icons/melon.png', - GNT: '/images/token_icons/golem.png', - MKR: '/images/token_icons/makerdao.png', - ZRX: '/images/token_icons/zero_ex.png', - ANT: '/images/token_icons/aragon.png', - BNT: '/images/token_icons/bancor.png', - BAT: '/images/token_icons/basicattentiontoken.png', - CVC: '/images/token_icons/civic.png', - EOS: '/images/token_icons/eos.png', - FUN: '/images/token_icons/funfair.png', - GNO: '/images/token_icons/gnosis.png', - ICN: '/images/token_icons/iconomi.png', - OMG: '/images/token_icons/omisego.png', - SNT: '/images/token_icons/status.png', - STORJ: '/images/token_icons/storjcoinx.png', - PAY: '/images/token_icons/tenx.png', - QTUM: '/images/token_icons/qtum.png', - DNT: '/images/token_icons/district0x.png', - SNGLS: '/images/token_icons/singularity.png', - EDG: '/images/token_icons/edgeless.png', - '1ST': '/images/token_icons/firstblood.jpg', - WINGS: '/images/token_icons/wings.png', - BQX: '/images/token_icons/bitquence.png', - LUN: '/images/token_icons/lunyr.png', - RLC: '/images/token_icons/iexec.png', - MCO: '/images/token_icons/monaco.png', - ADT: '/images/token_icons/adtoken.png', - CFI: '/images/token_icons/cofound-it.png', - ROL: '/images/token_icons/etheroll.png', - WGNT: '/images/token_icons/golem.png', - MTL: '/images/token_icons/metal.png', - NMR: '/images/token_icons/numeraire.png', - SAN: '/images/token_icons/santiment.png', - TAAS: '/images/token_icons/taas.png', - TKN: '/images/token_icons/tokencard.png', - TRST: '/images/token_icons/trust.png', - } as { [symbol: string]: string }, - IS_MAINNET_ENABLED: true, - LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22', - LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2017-12-19', - // NEW_WRAPPED_ETHERS is temporary until we remove the SHOULD_DEPRECATE_OLD_WETH_TOKEN flag - // and add the new WETHs to the tokenRegistry - NEW_WRAPPED_ETHERS: { - 1: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - 42: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', - } as { [networkId: string]: string }, - OUTDATED_WRAPPED_ETHERS: [ - { - 42: { - address: '0x05d090b51c40b020eab3bfcb6a2dff130df22e9c', - timestampMsRange: { - startTimestampMs: 1502455607000, - endTimestampMs: 1513790926000, - }, - }, - 1: { - address: '0x2956356cd2a2bf3202f771f50d3d14a367b48070', - timestampMsRange: { - startTimestampMs: 1502455607000, - endTimestampMs: 1513790926000, - }, - }, - }, - ] as OutdatedWrappedEtherByNetworkId[], - // The order matters. We first try first node and only then fall back to others. - PUBLIC_NODE_URLS_BY_NETWORK_ID: { - [1]: [`https://mainnet.infura.io/${INFURA_API_KEY}`, 'https://mainnet.0xproject.com'], - [42]: [`https://kovan.infura.io/${INFURA_API_KEY}`, 'https://kovan.0xproject.com'], - } as PublicNodeUrlsByNetworkId, - SHOULD_DEPRECATE_OLD_WETH_TOKEN: true, - SYMBOLS_OF_MINTABLE_TOKENS: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'], + BACKEND_BASE_URL: isDevelopment ? 'https://localhost:3001' : 'https://website-api.0xproject.com', + BASE_URL, + BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', + CONTRACT_ADDRESS: { + '1.0.0': { + [Networks.mainnet]: { + [SmartContractDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093', + [SmartContractDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', + [SmartContractDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498', + [SmartContractDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c', + }, + [Networks.ropsten]: { + [SmartContractDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac', + [SmartContractDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6', + [SmartContractDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', + [SmartContractDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed', + }, + [Networks.kovan]: { + [SmartContractDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', + [SmartContractDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4', + [SmartContractDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', + [SmartContractDocSections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f', + }, + }, + } as ContractAddresses, + DEFAULT_DERIVATION_PATH: `44'/60'/0'`, + // WARNING: ZRX & WETH MUST always be default trackedTokens + DEFAULT_TRACKED_TOKEN_SYMBOLS: ['WETH', 'ZRX'], + DOMAIN_STAGING: 'staging-0xproject.s3-website-us-east-1.amazonaws.com', + DOMAIN_DEVELOPMENT: '0xproject.localhost:3572', + DOMAIN_PRODUCTION: '0xproject.com', + ENVIRONMENT: isDevelopment ? Environments.DEVELOPMENT : Environments.PRODUCTION, + ICON_URL_BY_SYMBOL: { + REP: '/images/token_icons/augur.png', + DGD: '/images/token_icons/digixdao.png', + WETH: '/images/token_icons/ether_erc20.png', + MLN: '/images/token_icons/melon.png', + GNT: '/images/token_icons/golem.png', + MKR: '/images/token_icons/makerdao.png', + ZRX: '/images/token_icons/zero_ex.png', + ANT: '/images/token_icons/aragon.png', + BNT: '/images/token_icons/bancor.png', + BAT: '/images/token_icons/basicattentiontoken.png', + CVC: '/images/token_icons/civic.png', + EOS: '/images/token_icons/eos.png', + FUN: '/images/token_icons/funfair.png', + GNO: '/images/token_icons/gnosis.png', + ICN: '/images/token_icons/iconomi.png', + OMG: '/images/token_icons/omisego.png', + SNT: '/images/token_icons/status.png', + STORJ: '/images/token_icons/storjcoinx.png', + PAY: '/images/token_icons/tenx.png', + QTUM: '/images/token_icons/qtum.png', + DNT: '/images/token_icons/district0x.png', + SNGLS: '/images/token_icons/singularity.png', + EDG: '/images/token_icons/edgeless.png', + '1ST': '/images/token_icons/firstblood.jpg', + WINGS: '/images/token_icons/wings.png', + BQX: '/images/token_icons/bitquence.png', + LUN: '/images/token_icons/lunyr.png', + RLC: '/images/token_icons/iexec.png', + MCO: '/images/token_icons/monaco.png', + ADT: '/images/token_icons/adtoken.png', + CFI: '/images/token_icons/cofound-it.png', + ROL: '/images/token_icons/etheroll.png', + WGNT: '/images/token_icons/golem.png', + MTL: '/images/token_icons/metal.png', + NMR: '/images/token_icons/numeraire.png', + SAN: '/images/token_icons/santiment.png', + TAAS: '/images/token_icons/taas.png', + TKN: '/images/token_icons/tokencard.png', + TRST: '/images/token_icons/trust.png', + } as { [symbol: string]: string }, + IS_MAINNET_ENABLED: true, + LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22', + LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2017-12-19', + // NEW_WRAPPED_ETHERS is temporary until we remove the SHOULD_DEPRECATE_OLD_WETH_TOKEN flag + // and add the new WETHs to the tokenRegistry + NEW_WRAPPED_ETHERS: { + 1: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + 42: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', + } as { [networkId: string]: string }, + OUTDATED_WRAPPED_ETHERS: [ + { + 42: { + address: '0x05d090b51c40b020eab3bfcb6a2dff130df22e9c', + timestampMsRange: { + startTimestampMs: 1502455607000, + endTimestampMs: 1513790926000, + }, + }, + 1: { + address: '0x2956356cd2a2bf3202f771f50d3d14a367b48070', + timestampMsRange: { + startTimestampMs: 1502455607000, + endTimestampMs: 1513790926000, + }, + }, + }, + ] as OutdatedWrappedEtherByNetworkId[], + // The order matters. We first try first node and only then fall back to others. + PUBLIC_NODE_URLS_BY_NETWORK_ID: { + [1]: [`https://mainnet.infura.io/${INFURA_API_KEY}`, 'https://mainnet.0xproject.com'], + [42]: [`https://kovan.infura.io/${INFURA_API_KEY}`, 'https://kovan.0xproject.com'], + } as PublicNodeUrlsByNetworkId, + SHOULD_DEPRECATE_OLD_WETH_TOKEN: true, + SYMBOLS_OF_MINTABLE_TOKENS: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'], }; diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index bb6407ec5..dded82114 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -2,86 +2,86 @@ import { BigNumber } from '@0xproject/utils'; import { Networks } from 'ts/types'; export const constants = { - DECIMAL_PLACES_ETH: 18, - DECIMAL_PLACES_ZRX: 18, - DOCS_SCROLL_DURATION_MS: 0, - DOCS_CONTAINER_ID: 'documentation', - GENESIS_ORDER_BLOCK_BY_NETWORK_ID: { - 1: 4145578, - 42: 3117574, - 50: 0, - } as { [networkId: number]: number }, - HOME_SCROLL_DURATION_MS: 500, - HTTP_NO_CONTENT_STATUS_CODE: 204, - LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER: 'didAcceptPortalDisclaimer', - LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE: 'hasDismissedWethNotice', - MAKER_FEE: new BigNumber(0), - MAINNET_NAME: 'Main network', - MINT_AMOUNT: new BigNumber('100000000000000000000'), - NETWORK_ID_MAINNET: 1, - NETWORK_ID_TESTNET: 42, - NETWORK_ID_TESTRPC: 50, - NETWORK_NAME_BY_ID: { - 1: Networks.mainnet, - 3: Networks.ropsten, - 4: Networks.rinkeby, - 42: Networks.kovan, - } as { [symbol: number]: string }, - NETWORK_ID_BY_NAME: { - [Networks.mainnet]: 1, - [Networks.ropsten]: 3, - [Networks.rinkeby]: 4, - [Networks.kovan]: 42, - } as { [networkName: string]: number }, - NULL_ADDRESS: '0x0000000000000000000000000000000000000000', - PROVIDER_NAME_LEDGER: 'Ledger', - PROVIDER_NAME_METAMASK: 'Metamask', - PROVIDER_NAME_PARITY_SIGNER: 'Parity Signer', - PROVIDER_NAME_GENERIC: 'Injected Web3', - PROVIDER_NAME_PUBLIC: '0x Public', - ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65', - SUCCESS_STATUS: 200, - UNAVAILABLE_STATUS: 503, - TAKER_FEE: new BigNumber(0), - TESTNET_NAME: 'Kovan', - TYPES_SECTION_NAME: 'types', - PROJECT_URL_ETHFINEX: 'https://www.bitfinex.com/ethfinex', - PROJECT_URL_RADAR_RELAY: 'https://radarrelay.com', - PROJECT_URL_PARADEX: 'https://paradex.io', - PROJECT_URL_DYDX: 'https://dydx.exchange', - PROJECT_URL_MELONPORT: 'https://melonport.com', - PROJECT_URL_DISTRICT_0X: 'https://district0x.io', - PROJECT_URL_DHARMA: 'https://dharma.io', - PROJECT_URL_LENDROID: 'https://lendroid.com', - PROJECT_URL_MAKER: 'https://makerdao.com', - PROJECT_URL_ARAGON: 'https://aragon.one', - PROJECT_URL_BLOCKNET: 'https://blocknet.co', - PROJECT_URL_0CEAN: 'http://the0cean.com', - PROJECT_URL_STATUS: 'https://status.im', - PROJECT_URL_AUGUR: 'https://augur.net', - PROJECT_URL_AUCTUS: 'https://auctus.org', - PROJECT_URL_OPEN_ANX: 'https://www.openanx.org', - URL_ANGELLIST: 'https://angel.co/0xproject/jobs', - URL_BIGNUMBERJS_GITHUB: 'http://mikemcl.github.io/bignumber.js', - URL_BITLY_API: 'https://api-ssl.bitly.com', - URL_BLOG: 'https://blog.0xproject.com/latest', - URL_DISCOURSE_FORUM: 'https://forum.0xproject.com', - URL_FIREFOX_U2F_ADDON: 'https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/', - URL_ETHER_FAUCET: 'https://faucet.0xproject.com', - URL_GITHUB_ORG: 'https://github.com/0xProject', - URL_GITHUB_WIKI: 'https://github.com/0xProject/wiki', - URL_METAMASK_CHROME_STORE: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn', - URL_MIST_DOWNLOAD: 'https://github.com/ethereum/mist/releases', - URL_PARITY_CHROME_STORE: - 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig', - URL_REDDIT: 'https://reddit.com/r/0xproject', - URL_STANDARD_RELAYER_API_GITHUB: 'https://github.com/0xProject/standard-relayer-api/blob/master/README.md', - URL_TWITTER: 'https://twitter.com/0xproject', - URL_WEB3_DOCS: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', - URL_WEB3_DECODED_LOG_ENTRY_EVENT: - 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L123', - URL_WEB3_LOG_ENTRY_EVENT: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127', - URL_WEB3_PROVIDER_DOCS: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150', - URL_WETH_IO: 'https://weth.io/', - URL_ZEROEX_CHAT: 'https://chat.0xproject.com', + DECIMAL_PLACES_ETH: 18, + DECIMAL_PLACES_ZRX: 18, + DOCS_SCROLL_DURATION_MS: 0, + DOCS_CONTAINER_ID: 'documentation', + GENESIS_ORDER_BLOCK_BY_NETWORK_ID: { + 1: 4145578, + 42: 3117574, + 50: 0, + } as { [networkId: number]: number }, + HOME_SCROLL_DURATION_MS: 500, + HTTP_NO_CONTENT_STATUS_CODE: 204, + LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER: 'didAcceptPortalDisclaimer', + LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE: 'hasDismissedWethNotice', + MAKER_FEE: new BigNumber(0), + MAINNET_NAME: 'Main network', + MINT_AMOUNT: new BigNumber('100000000000000000000'), + NETWORK_ID_MAINNET: 1, + NETWORK_ID_TESTNET: 42, + NETWORK_ID_TESTRPC: 50, + NETWORK_NAME_BY_ID: { + 1: Networks.mainnet, + 3: Networks.ropsten, + 4: Networks.rinkeby, + 42: Networks.kovan, + } as { [symbol: number]: string }, + NETWORK_ID_BY_NAME: { + [Networks.mainnet]: 1, + [Networks.ropsten]: 3, + [Networks.rinkeby]: 4, + [Networks.kovan]: 42, + } as { [networkName: string]: number }, + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + PROVIDER_NAME_LEDGER: 'Ledger', + PROVIDER_NAME_METAMASK: 'Metamask', + PROVIDER_NAME_PARITY_SIGNER: 'Parity Signer', + PROVIDER_NAME_GENERIC: 'Injected Web3', + PROVIDER_NAME_PUBLIC: '0x Public', + ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65', + SUCCESS_STATUS: 200, + UNAVAILABLE_STATUS: 503, + TAKER_FEE: new BigNumber(0), + TESTNET_NAME: 'Kovan', + TYPES_SECTION_NAME: 'types', + PROJECT_URL_ETHFINEX: 'https://www.bitfinex.com/ethfinex', + PROJECT_URL_RADAR_RELAY: 'https://radarrelay.com', + PROJECT_URL_PARADEX: 'https://paradex.io', + PROJECT_URL_DYDX: 'https://dydx.exchange', + PROJECT_URL_MELONPORT: 'https://melonport.com', + PROJECT_URL_DISTRICT_0X: 'https://district0x.io', + PROJECT_URL_DHARMA: 'https://dharma.io', + PROJECT_URL_LENDROID: 'https://lendroid.com', + PROJECT_URL_MAKER: 'https://makerdao.com', + PROJECT_URL_ARAGON: 'https://aragon.one', + PROJECT_URL_BLOCKNET: 'https://blocknet.co', + PROJECT_URL_0CEAN: 'http://the0cean.com', + PROJECT_URL_STATUS: 'https://status.im', + PROJECT_URL_AUGUR: 'https://augur.net', + PROJECT_URL_AUCTUS: 'https://auctus.org', + PROJECT_URL_OPEN_ANX: 'https://www.openanx.org', + URL_ANGELLIST: 'https://angel.co/0xproject/jobs', + URL_BIGNUMBERJS_GITHUB: 'http://mikemcl.github.io/bignumber.js', + URL_BITLY_API: 'https://api-ssl.bitly.com', + URL_BLOG: 'https://blog.0xproject.com/latest', + URL_DISCOURSE_FORUM: 'https://forum.0xproject.com', + URL_FIREFOX_U2F_ADDON: 'https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/', + URL_ETHER_FAUCET: 'https://faucet.0xproject.com', + URL_GITHUB_ORG: 'https://github.com/0xProject', + URL_GITHUB_WIKI: 'https://github.com/0xProject/wiki', + URL_METAMASK_CHROME_STORE: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn', + URL_MIST_DOWNLOAD: 'https://github.com/ethereum/mist/releases', + URL_PARITY_CHROME_STORE: + 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig', + URL_REDDIT: 'https://reddit.com/r/0xproject', + URL_STANDARD_RELAYER_API_GITHUB: 'https://github.com/0xProject/standard-relayer-api/blob/master/README.md', + URL_TWITTER: 'https://twitter.com/0xproject', + URL_WEB3_DOCS: 'https://github.com/ethereum/wiki/wiki/JavaScript-API', + URL_WEB3_DECODED_LOG_ENTRY_EVENT: + 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L123', + URL_WEB3_LOG_ENTRY_EVENT: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127', + URL_WEB3_PROVIDER_DOCS: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150', + URL_WETH_IO: 'https://weth.io/', + URL_ZEROEX_CHAT: 'https://chat.0xproject.com', }; diff --git a/packages/website/ts/utils/doc_utils.ts b/packages/website/ts/utils/doc_utils.ts index 18bf276b4..1f5f75ee2 100644 --- a/packages/website/ts/utils/doc_utils.ts +++ b/packages/website/ts/utils/doc_utils.ts @@ -5,47 +5,47 @@ import { utils } from 'ts/utils/utils'; import convert = require('xml-js'); export const docUtils = { - async getVersionToFileNameAsync(s3DocJsonRoot: string): Promise<VersionToFileName> { - const versionFileNames = await this.getVersionFileNamesAsync(s3DocJsonRoot); - const versionToFileName: VersionToFileName = {}; - _.each(versionFileNames, fileName => { - const [version] = findVersions(fileName); - versionToFileName[version] = fileName; - }); - return versionToFileName; - }, - async getVersionFileNamesAsync(s3DocJsonRoot: string): Promise<string[]> { - const response = await fetch(s3DocJsonRoot); - if (response.status !== 200) { - // TODO: Show the user an error message when the docs fail to load - const errMsg = await response.text(); - utils.consoleLog(`Failed to load JSON file list: ${response.status} ${errMsg}`); - throw new Error(errMsg); - } - const responseXML = await response.text(); - const responseJSONString = convert.xml2json(responseXML, { - compact: true, - }); - const responseObj = JSON.parse(responseJSONString); - const fileObjs: S3FileObject[] = _.isArray(responseObj.ListBucketResult.Contents) - ? (responseObj.ListBucketResult.Contents as S3FileObject[]) - : [responseObj.ListBucketResult.Contents]; + async getVersionToFileNameAsync(s3DocJsonRoot: string): Promise<VersionToFileName> { + const versionFileNames = await this.getVersionFileNamesAsync(s3DocJsonRoot); + const versionToFileName: VersionToFileName = {}; + _.each(versionFileNames, fileName => { + const [version] = findVersions(fileName); + versionToFileName[version] = fileName; + }); + return versionToFileName; + }, + async getVersionFileNamesAsync(s3DocJsonRoot: string): Promise<string[]> { + const response = await fetch(s3DocJsonRoot); + if (response.status !== 200) { + // TODO: Show the user an error message when the docs fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load JSON file list: ${response.status} ${errMsg}`); + throw new Error(errMsg); + } + const responseXML = await response.text(); + const responseJSONString = convert.xml2json(responseXML, { + compact: true, + }); + const responseObj = JSON.parse(responseJSONString); + const fileObjs: S3FileObject[] = _.isArray(responseObj.ListBucketResult.Contents) + ? (responseObj.ListBucketResult.Contents as S3FileObject[]) + : [responseObj.ListBucketResult.Contents]; - const versionFileNames = _.map(fileObjs, fileObj => { - return fileObj.Key._text; - }); - return versionFileNames; - }, - async getJSONDocFileAsync(fileName: string, s3DocJsonRoot: string): Promise<TypeDocNode | DoxityDocObj> { - const endpoint = `${s3DocJsonRoot}/${fileName}`; - const response = await fetch(endpoint); - if (response.status !== 200) { - // TODO: Show the user an error message when the docs fail to load - const errMsg = await response.text(); - utils.consoleLog(`Failed to load Doc JSON: ${response.status} ${errMsg}`); - throw new Error(errMsg); - } - const jsonDocObj = await response.json(); - return jsonDocObj; - }, + const versionFileNames = _.map(fileObjs, fileObj => { + return fileObj.Key._text; + }); + return versionFileNames; + }, + async getJSONDocFileAsync(fileName: string, s3DocJsonRoot: string): Promise<TypeDocNode | DoxityDocObj> { + const endpoint = `${s3DocJsonRoot}/${fileName}`; + const response = await fetch(endpoint); + if (response.status !== 200) { + // TODO: Show the user an error message when the docs fail to load + const errMsg = await response.text(); + utils.consoleLog(`Failed to load Doc JSON: ${response.status} ${errMsg}`); + throw new Error(errMsg); + } + const jsonDocObj = await response.json(); + return jsonDocObj; + }, }; diff --git a/packages/website/ts/utils/doxity_utils.ts b/packages/website/ts/utils/doxity_utils.ts index f26534d51..5f1d02132 100644 --- a/packages/website/ts/utils/doxity_utils.ts +++ b/packages/website/ts/utils/doxity_utils.ts @@ -1,168 +1,168 @@ import * as _ from 'lodash'; import { - AbiTypes, - DocAgnosticFormat, - DocSection, - DoxityAbiDoc, - DoxityContractObj, - DoxityDocObj, - DoxityInput, - EventArg, - Parameter, - Property, - SolidityMethod, - Type, - TypeDocTypes, + AbiTypes, + DocAgnosticFormat, + DocSection, + DoxityAbiDoc, + DoxityContractObj, + DoxityDocObj, + DoxityInput, + EventArg, + Parameter, + Property, + SolidityMethod, + Type, + TypeDocTypes, } from 'ts/types'; export const doxityUtils = { - convertToDocAgnosticFormat(doxityDocObj: DoxityDocObj): DocAgnosticFormat { - const docAgnosticFormat: DocAgnosticFormat = {}; - _.each(doxityDocObj, (doxityContractObj: DoxityContractObj, contractName: string) => { - const doxityConstructor = _.find(doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => { - return abiDoc.type === AbiTypes.Constructor; - }); - const constructors = []; - if (!_.isUndefined(doxityConstructor)) { - const constructor = { - isConstructor: true, - name: doxityContractObj.name, - comment: doxityConstructor.details, - returnComment: doxityConstructor.return, - callPath: '', - parameters: this._convertParameters(doxityConstructor.inputs), - returnType: this._convertType(doxityContractObj.name), - }; - constructors.push(constructor); - } + convertToDocAgnosticFormat(doxityDocObj: DoxityDocObj): DocAgnosticFormat { + const docAgnosticFormat: DocAgnosticFormat = {}; + _.each(doxityDocObj, (doxityContractObj: DoxityContractObj, contractName: string) => { + const doxityConstructor = _.find(doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => { + return abiDoc.type === AbiTypes.Constructor; + }); + const constructors = []; + if (!_.isUndefined(doxityConstructor)) { + const constructor = { + isConstructor: true, + name: doxityContractObj.name, + comment: doxityConstructor.details, + returnComment: doxityConstructor.return, + callPath: '', + parameters: this._convertParameters(doxityConstructor.inputs), + returnType: this._convertType(doxityContractObj.name), + }; + constructors.push(constructor); + } - const doxityMethods: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>( - doxityContractObj.abiDocs, - (abiDoc: DoxityAbiDoc) => { - return this._isMethod(abiDoc); - }, - ); - const methods: SolidityMethod[] = _.map<DoxityAbiDoc, SolidityMethod>( - doxityMethods, - (doxityMethod: DoxityAbiDoc) => { - // We assume that none of our functions returns more then a single value - const outputIfExists = !_.isUndefined(doxityMethod.outputs) ? doxityMethod.outputs[0] : undefined; - const returnTypeIfExists = !_.isUndefined(outputIfExists) - ? this._convertType(outputIfExists.type) - : undefined; - // For ZRXToken, we want to convert it to zrxToken, rather then simply zRXToken - const callPath = - contractName !== 'ZRXToken' - ? `${contractName[0].toLowerCase()}${contractName.slice(1)}.` - : `${contractName.slice(0, 3).toLowerCase()}${contractName.slice(3)}.`; - const method = { - isConstructor: false, - isConstant: doxityMethod.constant, - isPayable: doxityMethod.payable, - name: doxityMethod.name, - comment: doxityMethod.details, - returnComment: doxityMethod.return, - callPath, - parameters: this._convertParameters(doxityMethod.inputs), - returnType: returnTypeIfExists, - }; - return method; - }, - ); + const doxityMethods: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>( + doxityContractObj.abiDocs, + (abiDoc: DoxityAbiDoc) => { + return this._isMethod(abiDoc); + }, + ); + const methods: SolidityMethod[] = _.map<DoxityAbiDoc, SolidityMethod>( + doxityMethods, + (doxityMethod: DoxityAbiDoc) => { + // We assume that none of our functions returns more then a single value + const outputIfExists = !_.isUndefined(doxityMethod.outputs) ? doxityMethod.outputs[0] : undefined; + const returnTypeIfExists = !_.isUndefined(outputIfExists) + ? this._convertType(outputIfExists.type) + : undefined; + // For ZRXToken, we want to convert it to zrxToken, rather then simply zRXToken + const callPath = + contractName !== 'ZRXToken' + ? `${contractName[0].toLowerCase()}${contractName.slice(1)}.` + : `${contractName.slice(0, 3).toLowerCase()}${contractName.slice(3)}.`; + const method = { + isConstructor: false, + isConstant: doxityMethod.constant, + isPayable: doxityMethod.payable, + name: doxityMethod.name, + comment: doxityMethod.details, + returnComment: doxityMethod.return, + callPath, + parameters: this._convertParameters(doxityMethod.inputs), + returnType: returnTypeIfExists, + }; + return method; + }, + ); - const doxityProperties: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>( - doxityContractObj.abiDocs, - (abiDoc: DoxityAbiDoc) => { - return this._isProperty(abiDoc); - }, - ); - const properties = _.map<DoxityAbiDoc, Property>(doxityProperties, (doxityProperty: DoxityAbiDoc) => { - // We assume that none of our functions return more then a single return value - let typeName = doxityProperty.outputs[0].type; - if (!_.isEmpty(doxityProperty.inputs)) { - // Properties never have more then a single input - typeName = `(${doxityProperty.inputs[0].type} => ${typeName})`; - } - const property = { - name: doxityProperty.name, - type: this._convertType(typeName), - comment: doxityProperty.details, - }; - return property; - }); + const doxityProperties: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>( + doxityContractObj.abiDocs, + (abiDoc: DoxityAbiDoc) => { + return this._isProperty(abiDoc); + }, + ); + const properties = _.map<DoxityAbiDoc, Property>(doxityProperties, (doxityProperty: DoxityAbiDoc) => { + // We assume that none of our functions return more then a single return value + let typeName = doxityProperty.outputs[0].type; + if (!_.isEmpty(doxityProperty.inputs)) { + // Properties never have more then a single input + typeName = `(${doxityProperty.inputs[0].type} => ${typeName})`; + } + const property = { + name: doxityProperty.name, + type: this._convertType(typeName), + comment: doxityProperty.details, + }; + return property; + }); - const doxityEvents = _.filter( - doxityContractObj.abiDocs, - (abiDoc: DoxityAbiDoc) => abiDoc.type === AbiTypes.Event, - ); - const events = _.map(doxityEvents, doxityEvent => { - const event = { - name: doxityEvent.name, - eventArgs: this._convertEventArgs(doxityEvent.inputs), - }; - return event; - }); + const doxityEvents = _.filter( + doxityContractObj.abiDocs, + (abiDoc: DoxityAbiDoc) => abiDoc.type === AbiTypes.Event, + ); + const events = _.map(doxityEvents, doxityEvent => { + const event = { + name: doxityEvent.name, + eventArgs: this._convertEventArgs(doxityEvent.inputs), + }; + return event; + }); - const docSection: DocSection = { - comment: doxityContractObj.title, - constructors, - methods, - properties, - types: [], - events, - }; - docAgnosticFormat[contractName] = docSection; - }); - return docAgnosticFormat; - }, - _convertParameters(inputs: DoxityInput[]): Parameter[] { - const parameters = _.map(inputs, input => { - const parameter = { - name: input.name, - comment: input.description, - isOptional: false, - type: this._convertType(input.type), - }; - return parameter; - }); - return parameters; - }, - _convertType(typeName: string): Type { - const type = { - name: typeName, - typeDocType: TypeDocTypes.Intrinsic, - }; - return type; - }, - _isMethod(abiDoc: DoxityAbiDoc) { - if (abiDoc.type !== AbiTypes.Function) { - return false; - } - const hasInputs = !_.isEmpty(abiDoc.inputs); - const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); - const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); - const isMethod = hasNamedOutputIfExists && !isNameAllCaps; - return isMethod; - }, - _isProperty(abiDoc: DoxityAbiDoc) { - if (abiDoc.type !== AbiTypes.Function) { - return false; - } - const hasInputs = !_.isEmpty(abiDoc.inputs); - const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); - const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); - const isProperty = !hasNamedOutputIfExists || isNameAllCaps; - return isProperty; - }, - _convertEventArgs(inputs: DoxityInput[]): EventArg[] { - const eventArgs = _.map(inputs, input => { - const eventArg = { - isIndexed: input.indexed, - name: input.name, - type: this._convertType(input.type), - }; - return eventArg; - }); - return eventArgs; - }, + const docSection: DocSection = { + comment: doxityContractObj.title, + constructors, + methods, + properties, + types: [], + events, + }; + docAgnosticFormat[contractName] = docSection; + }); + return docAgnosticFormat; + }, + _convertParameters(inputs: DoxityInput[]): Parameter[] { + const parameters = _.map(inputs, input => { + const parameter = { + name: input.name, + comment: input.description, + isOptional: false, + type: this._convertType(input.type), + }; + return parameter; + }); + return parameters; + }, + _convertType(typeName: string): Type { + const type = { + name: typeName, + typeDocType: TypeDocTypes.Intrinsic, + }; + return type; + }, + _isMethod(abiDoc: DoxityAbiDoc) { + if (abiDoc.type !== AbiTypes.Function) { + return false; + } + const hasInputs = !_.isEmpty(abiDoc.inputs); + const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); + const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); + const isMethod = hasNamedOutputIfExists && !isNameAllCaps; + return isMethod; + }, + _isProperty(abiDoc: DoxityAbiDoc) { + if (abiDoc.type !== AbiTypes.Function) { + return false; + } + const hasInputs = !_.isEmpty(abiDoc.inputs); + const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name); + const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase(); + const isProperty = !hasNamedOutputIfExists || isNameAllCaps; + return isProperty; + }, + _convertEventArgs(inputs: DoxityInput[]): EventArg[] { + const eventArgs = _.map(inputs, input => { + const eventArg = { + isIndexed: input.indexed, + name: input.name, + type: this._convertType(input.type), + }; + return eventArg; + }); + return eventArgs; + }, }; diff --git a/packages/website/ts/utils/error_reporter.ts b/packages/website/ts/utils/error_reporter.ts index 08d99e405..0bd247c5b 100644 --- a/packages/website/ts/utils/error_reporter.ts +++ b/packages/website/ts/utils/error_reporter.ts @@ -6,47 +6,47 @@ import { utils } from 'ts/utils/utils'; // Suggested way to include Rollbar with Webpack // https://github.com/rollbar/rollbar.js/tree/master/examples/webpack const rollbarConfig = { - accessToken: constants.ROLLBAR_ACCESS_TOKEN, - captureUncaught: true, - captureUnhandledRejections: true, - itemsPerMinute: 10, - maxItems: 500, - payload: { - environment: configs.ENVIRONMENT, - }, - uncaughtErrorLevel: 'error', - hostWhiteList: [configs.DOMAIN_PRODUCTION, configs.DOMAIN_STAGING], - ignoredMessages: [ - // Errors from the third-party scripts - 'Script error', - // Network errors or ad-blockers - 'TypeError: Failed to fetch', - 'Exchange has not been deployed to detected network (network/artifact mismatch)', - // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE - "undefined is not an object (evaluating '__gCrWeb.autofill.extractForms')", - // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging - 'SecurityError (DOM Exception 18)', - ], + accessToken: constants.ROLLBAR_ACCESS_TOKEN, + captureUncaught: true, + captureUnhandledRejections: true, + itemsPerMinute: 10, + maxItems: 500, + payload: { + environment: configs.ENVIRONMENT, + }, + uncaughtErrorLevel: 'error', + hostWhiteList: [configs.DOMAIN_PRODUCTION, configs.DOMAIN_STAGING], + ignoredMessages: [ + // Errors from the third-party scripts + 'Script error', + // Network errors or ad-blockers + 'TypeError: Failed to fetch', + 'Exchange has not been deployed to detected network (network/artifact mismatch)', + // Source: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/7VU0_VvC7mE + "undefined is not an object (evaluating '__gCrWeb.autofill.extractForms')", + // Source: http://stackoverflow.com/questions/43399818/securityerror-from-facebook-and-cross-domain-messaging + 'SecurityError (DOM Exception 18)', + ], }; import Rollbar = require('../../public/js/rollbar.umd.nojson.min.js'); const rollbar = Rollbar.init(rollbarConfig); export const errorReporter = { - async reportAsync(err: Error): Promise<any> { - if (configs.ENVIRONMENT === Environments.DEVELOPMENT) { - return; // Let's not log development errors to rollbar - } + async reportAsync(err: Error): Promise<any> { + if (configs.ENVIRONMENT === Environments.DEVELOPMENT) { + return; // Let's not log development errors to rollbar + } - return new Promise((resolve, reject) => { - rollbar.error(err, (rollbarErr: Error) => { - if (rollbarErr) { - utils.consoleLog(`Error reporting to rollbar, ignoring: ${rollbarErr}`); - // We never want to reject and cause the app to throw because of rollbar - resolve(); - } else { - resolve(); - } - }); - }); - }, + return new Promise((resolve, reject) => { + rollbar.error(err, (rollbarErr: Error) => { + if (rollbarErr) { + utils.consoleLog(`Error reporting to rollbar, ignoring: ${rollbarErr}`); + // We never want to reject and cause the app to throw because of rollbar + resolve(); + } else { + resolve(); + } + }); + }); + }, }; diff --git a/packages/website/ts/utils/mui_theme.ts b/packages/website/ts/utils/mui_theme.ts index 565d1ae4f..d73e80606 100644 --- a/packages/website/ts/utils/mui_theme.ts +++ b/packages/website/ts/utils/mui_theme.ts @@ -2,34 +2,34 @@ import { getMuiTheme } from 'material-ui/styles'; import { colors } from 'ts/utils/colors'; export const muiTheme = getMuiTheme({ - appBar: { - height: 45, - color: colors.white, - textColor: colors.black, - }, - palette: { - pickerHeaderColor: colors.lightBlue, - primary1Color: colors.lightBlue, - primary2Color: colors.lightBlue, - textColor: colors.grey700, - }, - datePicker: { - color: colors.grey700, - textColor: colors.white, - calendarTextColor: colors.white, - selectColor: colors.darkestGrey, - selectTextColor: colors.white, - }, - timePicker: { - color: colors.grey700, - textColor: colors.white, - accentColor: colors.white, - headerColor: colors.darkestGrey, - selectColor: colors.darkestGrey, - selectTextColor: colors.darkestGrey, - }, - toggle: { - thumbOnColor: colors.limeGreen, - trackOnColor: colors.lightGreen, - }, + appBar: { + height: 45, + color: colors.white, + textColor: colors.black, + }, + palette: { + pickerHeaderColor: colors.lightBlue, + primary1Color: colors.lightBlue, + primary2Color: colors.lightBlue, + textColor: colors.grey700, + }, + datePicker: { + color: colors.grey700, + textColor: colors.white, + calendarTextColor: colors.white, + selectColor: colors.darkestGrey, + selectTextColor: colors.white, + }, + timePicker: { + color: colors.grey700, + textColor: colors.white, + accentColor: colors.white, + headerColor: colors.darkestGrey, + selectColor: colors.darkestGrey, + selectTextColor: colors.darkestGrey, + }, + toggle: { + thumbOnColor: colors.limeGreen, + trackOnColor: colors.lightGreen, + }, }); diff --git a/packages/website/ts/utils/typedoc_utils.ts b/packages/website/ts/utils/typedoc_utils.ts index b0c152891..11ec8da58 100644 --- a/packages/website/ts/utils/typedoc_utils.ts +++ b/packages/website/ts/utils/typedoc_utils.ts @@ -1,365 +1,365 @@ import * as _ from 'lodash'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { - CustomType, - CustomTypeChild, - DocAgnosticFormat, - DocSection, - IndexSignature, - KindString, - Parameter, - Property, - SectionsMap, - Type, - TypeDocNode, - TypeDocType, - TypeParameter, - TypescriptMethod, + CustomType, + CustomTypeChild, + DocAgnosticFormat, + DocSection, + IndexSignature, + KindString, + Parameter, + Property, + SectionsMap, + Type, + TypeDocNode, + TypeDocType, + TypeParameter, + TypescriptMethod, } from 'ts/types'; import { utils } from 'ts/utils/utils'; export const typeDocUtils = { - isType(entity: TypeDocNode): boolean { - return ( - entity.kindString === KindString.Interface || - entity.kindString === KindString.Function || - entity.kindString === KindString.TypeAlias || - entity.kindString === KindString.Variable || - entity.kindString === KindString.Enumeration - ); - }, - isMethod(entity: TypeDocNode): boolean { - return entity.kindString === KindString.Method; - }, - isConstructor(entity: TypeDocNode): boolean { - return entity.kindString === KindString.Constructor; - }, - isProperty(entity: TypeDocNode): boolean { - return entity.kindString === KindString.Property; - }, - isPrivateOrProtectedProperty(propertyName: string): boolean { - return _.startsWith(propertyName, '_'); - }, - getModuleDefinitionBySectionNameIfExists( - versionDocObj: TypeDocNode, - modulePaths: string[], - ): TypeDocNode | undefined { - const modules = versionDocObj.children; - for (const mod of modules) { - if (_.includes(modulePaths, mod.name)) { - const moduleWithName = mod; - return moduleWithName; - } - } - return undefined; - }, - convertToDocAgnosticFormat(typeDocJson: TypeDocNode, docsInfo: DocsInfo): DocAgnosticFormat { - const subMenus = _.values(docsInfo.getMenu()); - const orderedSectionNames = _.flatten(subMenus); - const docAgnosticFormat: DocAgnosticFormat = {}; - _.each(orderedSectionNames, sectionName => { - const modulePathsIfExists = docsInfo.getModulePathsIfExists(sectionName); - if (_.isUndefined(modulePathsIfExists)) { - return; // no-op - } - const packageDefinitionIfExists = typeDocUtils.getModuleDefinitionBySectionNameIfExists( - typeDocJson, - modulePathsIfExists, - ); - if (_.isUndefined(packageDefinitionIfExists)) { - return; // no-op - } + isType(entity: TypeDocNode): boolean { + return ( + entity.kindString === KindString.Interface || + entity.kindString === KindString.Function || + entity.kindString === KindString.TypeAlias || + entity.kindString === KindString.Variable || + entity.kindString === KindString.Enumeration + ); + }, + isMethod(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Method; + }, + isConstructor(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Constructor; + }, + isProperty(entity: TypeDocNode): boolean { + return entity.kindString === KindString.Property; + }, + isPrivateOrProtectedProperty(propertyName: string): boolean { + return _.startsWith(propertyName, '_'); + }, + getModuleDefinitionBySectionNameIfExists( + versionDocObj: TypeDocNode, + modulePaths: string[], + ): TypeDocNode | undefined { + const modules = versionDocObj.children; + for (const mod of modules) { + if (_.includes(modulePaths, mod.name)) { + const moduleWithName = mod; + return moduleWithName; + } + } + return undefined; + }, + convertToDocAgnosticFormat(typeDocJson: TypeDocNode, docsInfo: DocsInfo): DocAgnosticFormat { + const subMenus = _.values(docsInfo.getMenu()); + const orderedSectionNames = _.flatten(subMenus); + const docAgnosticFormat: DocAgnosticFormat = {}; + _.each(orderedSectionNames, sectionName => { + const modulePathsIfExists = docsInfo.getModulePathsIfExists(sectionName); + if (_.isUndefined(modulePathsIfExists)) { + return; // no-op + } + const packageDefinitionIfExists = typeDocUtils.getModuleDefinitionBySectionNameIfExists( + typeDocJson, + modulePathsIfExists, + ); + if (_.isUndefined(packageDefinitionIfExists)) { + return; // no-op + } - // Since the `types.ts` file is the only file that does not export a module/class but - // instead has each type export itself, we do not need to go down two levels of nesting - // for it. - let entities; - let packageComment = ''; - if (sectionName === docsInfo.sections.types) { - entities = packageDefinitionIfExists.children; - } else { - entities = packageDefinitionIfExists.children[0].children; - const commentObj = packageDefinitionIfExists.children[0].comment; - packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment; - } + // Since the `types.ts` file is the only file that does not export a module/class but + // instead has each type export itself, we do not need to go down two levels of nesting + // for it. + let entities; + let packageComment = ''; + if (sectionName === docsInfo.sections.types) { + entities = packageDefinitionIfExists.children; + } else { + entities = packageDefinitionIfExists.children[0].children; + const commentObj = packageDefinitionIfExists.children[0].comment; + packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment; + } - const docSection = typeDocUtils._convertEntitiesToDocSection(entities, docsInfo, sectionName); - docSection.comment = packageComment; - docAgnosticFormat[sectionName] = docSection; - }); - return docAgnosticFormat; - }, - _convertEntitiesToDocSection(entities: TypeDocNode[], docsInfo: DocsInfo, sectionName: string) { - const docSection: DocSection = { - comment: '', - constructors: [], - methods: [], - properties: [], - types: [], - }; + const docSection = typeDocUtils._convertEntitiesToDocSection(entities, docsInfo, sectionName); + docSection.comment = packageComment; + docAgnosticFormat[sectionName] = docSection; + }); + return docAgnosticFormat; + }, + _convertEntitiesToDocSection(entities: TypeDocNode[], docsInfo: DocsInfo, sectionName: string) { + const docSection: DocSection = { + comment: '', + constructors: [], + methods: [], + properties: [], + types: [], + }; - let isConstructor; - _.each(entities, entity => { - switch (entity.kindString) { - case KindString.Constructor: - isConstructor = true; - const constructor = typeDocUtils._convertMethod( - entity, - isConstructor, - docsInfo.sections, - sectionName, - docsInfo.subPackageName, - ); - docSection.constructors.push(constructor); - break; + let isConstructor; + _.each(entities, entity => { + switch (entity.kindString) { + case KindString.Constructor: + isConstructor = true; + const constructor = typeDocUtils._convertMethod( + entity, + isConstructor, + docsInfo.sections, + sectionName, + docsInfo.subPackageName, + ); + docSection.constructors.push(constructor); + break; - case KindString.Method: - if (entity.flags.isPublic) { - isConstructor = false; - const method = typeDocUtils._convertMethod( - entity, - isConstructor, - docsInfo.sections, - sectionName, - docsInfo.subPackageName, - ); - docSection.methods.push(method); - } - break; + case KindString.Method: + if (entity.flags.isPublic) { + isConstructor = false; + const method = typeDocUtils._convertMethod( + entity, + isConstructor, + docsInfo.sections, + sectionName, + docsInfo.subPackageName, + ); + docSection.methods.push(method); + } + break; - case KindString.Property: - if (!typeDocUtils.isPrivateOrProtectedProperty(entity.name)) { - const property = typeDocUtils._convertProperty( - entity, - docsInfo.sections, - sectionName, - docsInfo.subPackageName, - ); - docSection.properties.push(property); - } - break; + case KindString.Property: + if (!typeDocUtils.isPrivateOrProtectedProperty(entity.name)) { + const property = typeDocUtils._convertProperty( + entity, + docsInfo.sections, + sectionName, + docsInfo.subPackageName, + ); + docSection.properties.push(property); + } + break; - case KindString.Interface: - case KindString.Function: - case KindString.Variable: - case KindString.Enumeration: - case KindString.TypeAlias: - if (docsInfo.isPublicType(entity.name)) { - const customType = typeDocUtils._convertCustomType( - entity, - docsInfo.sections, - sectionName, - docsInfo.subPackageName, - ); - docSection.types.push(customType); - } - break; + case KindString.Interface: + case KindString.Function: + case KindString.Variable: + case KindString.Enumeration: + case KindString.TypeAlias: + if (docsInfo.isPublicType(entity.name)) { + const customType = typeDocUtils._convertCustomType( + entity, + docsInfo.sections, + sectionName, + docsInfo.subPackageName, + ); + docSection.types.push(customType); + } + break; - default: - throw utils.spawnSwitchErr('kindString', entity.kindString); - } - }); - return docSection; - }, - _convertCustomType( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): CustomType { - const typeIfExists = !_.isUndefined(entity.type) - ? typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName) - : undefined; - const isConstructor = false; - const methodIfExists = !_.isUndefined(entity.declaration) - ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, subPackageName) - : undefined; - const indexSignatureIfExists = !_.isUndefined(entity.indexSignature) - ? typeDocUtils._convertIndexSignature(entity.indexSignature[0], sections, sectionName, subPackageName) - : undefined; - const commentIfExists = - !_.isUndefined(entity.comment) && !_.isUndefined(entity.comment.shortText) - ? entity.comment.shortText - : undefined; + default: + throw utils.spawnSwitchErr('kindString', entity.kindString); + } + }); + return docSection; + }, + _convertCustomType( + entity: TypeDocNode, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): CustomType { + const typeIfExists = !_.isUndefined(entity.type) + ? typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName) + : undefined; + const isConstructor = false; + const methodIfExists = !_.isUndefined(entity.declaration) + ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, subPackageName) + : undefined; + const indexSignatureIfExists = !_.isUndefined(entity.indexSignature) + ? typeDocUtils._convertIndexSignature(entity.indexSignature[0], sections, sectionName, subPackageName) + : undefined; + const commentIfExists = + !_.isUndefined(entity.comment) && !_.isUndefined(entity.comment.shortText) + ? entity.comment.shortText + : undefined; - const childrenIfExist = !_.isUndefined(entity.children) - ? _.map(entity.children, (child: TypeDocNode) => { - const childTypeIfExists = !_.isUndefined(child.type) - ? typeDocUtils._convertType(child.type, sections, sectionName, subPackageName) - : undefined; - const c: CustomTypeChild = { - name: child.name, - type: childTypeIfExists, - defaultValue: child.defaultValue, - }; - return c; - }) - : undefined; + const childrenIfExist = !_.isUndefined(entity.children) + ? _.map(entity.children, (child: TypeDocNode) => { + const childTypeIfExists = !_.isUndefined(child.type) + ? typeDocUtils._convertType(child.type, sections, sectionName, subPackageName) + : undefined; + const c: CustomTypeChild = { + name: child.name, + type: childTypeIfExists, + defaultValue: child.defaultValue, + }; + return c; + }) + : undefined; - const customType = { - name: entity.name, - kindString: entity.kindString, - type: typeIfExists, - method: methodIfExists, - indexSignature: indexSignatureIfExists, - defaultValue: entity.defaultValue, - comment: commentIfExists, - children: childrenIfExist, - }; - return customType; - }, - _convertIndexSignature( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): IndexSignature { - const key = entity.parameters[0]; - const indexSignature = { - keyName: key.name, - keyType: typeDocUtils._convertType(key.type, sections, sectionName, subPackageName), - valueName: entity.type.name, - }; - return indexSignature; - }, - _convertProperty( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): Property { - const source = entity.sources[0]; - const commentIfExists = !_.isUndefined(entity.comment) ? entity.comment.shortText : undefined; - const property = { - name: entity.name, - type: typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName), - source: { - fileName: source.fileName, - line: source.line, - }, - comment: commentIfExists, - }; - return property; - }, - _convertMethod( - entity: TypeDocNode, - isConstructor: boolean, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): TypescriptMethod { - const signature = entity.signatures[0]; - const source = entity.sources[0]; - const hasComment = !_.isUndefined(signature.comment); - const isStatic = _.isUndefined(entity.flags.isStatic) ? false : entity.flags.isStatic; + const customType = { + name: entity.name, + kindString: entity.kindString, + type: typeIfExists, + method: methodIfExists, + indexSignature: indexSignatureIfExists, + defaultValue: entity.defaultValue, + comment: commentIfExists, + children: childrenIfExist, + }; + return customType; + }, + _convertIndexSignature( + entity: TypeDocNode, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): IndexSignature { + const key = entity.parameters[0]; + const indexSignature = { + keyName: key.name, + keyType: typeDocUtils._convertType(key.type, sections, sectionName, subPackageName), + valueName: entity.type.name, + }; + return indexSignature; + }, + _convertProperty( + entity: TypeDocNode, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): Property { + const source = entity.sources[0]; + const commentIfExists = !_.isUndefined(entity.comment) ? entity.comment.shortText : undefined; + const property = { + name: entity.name, + type: typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName), + source: { + fileName: source.fileName, + line: source.line, + }, + comment: commentIfExists, + }; + return property; + }, + _convertMethod( + entity: TypeDocNode, + isConstructor: boolean, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): TypescriptMethod { + const signature = entity.signatures[0]; + const source = entity.sources[0]; + const hasComment = !_.isUndefined(signature.comment); + const isStatic = _.isUndefined(entity.flags.isStatic) ? false : entity.flags.isStatic; - // HACK: we use the fact that the sectionName is the same as the property name at the top-level - // of the public interface. In the future, we shouldn't use this hack but rather get it from the JSON. - let callPath; - if (isConstructor || entity.name === '__type') { - callPath = ''; - } else if (subPackageName === '0x.js') { - const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.'; - callPath = - !_.isUndefined(sections.zeroEx) && sectionName !== sections.zeroEx - ? `${topLevelInterface}${sectionName}.` - : topLevelInterface; - } else { - callPath = `${sectionName}.`; - } + // HACK: we use the fact that the sectionName is the same as the property name at the top-level + // of the public interface. In the future, we shouldn't use this hack but rather get it from the JSON. + let callPath; + if (isConstructor || entity.name === '__type') { + callPath = ''; + } else if (subPackageName === '0x.js') { + const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.'; + callPath = + !_.isUndefined(sections.zeroEx) && sectionName !== sections.zeroEx + ? `${topLevelInterface}${sectionName}.` + : topLevelInterface; + } else { + callPath = `${sectionName}.`; + } - const parameters = _.map(signature.parameters, param => { - return typeDocUtils._convertParameter(param, sections, sectionName, subPackageName); - }); - const returnType = typeDocUtils._convertType(signature.type, sections, sectionName, subPackageName); - const typeParameter = _.isUndefined(signature.typeParameter) - ? undefined - : typeDocUtils._convertTypeParameter(signature.typeParameter[0], sections, sectionName, subPackageName); + const parameters = _.map(signature.parameters, param => { + return typeDocUtils._convertParameter(param, sections, sectionName, subPackageName); + }); + const returnType = typeDocUtils._convertType(signature.type, sections, sectionName, subPackageName); + const typeParameter = _.isUndefined(signature.typeParameter) + ? undefined + : typeDocUtils._convertTypeParameter(signature.typeParameter[0], sections, sectionName, subPackageName); - const method = { - isConstructor, - isStatic, - name: signature.name, - comment: hasComment ? signature.comment.shortText : undefined, - returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined, - source: { - fileName: source.fileName, - line: source.line, - }, - callPath, - parameters, - returnType, - typeParameter, - }; - return method; - }, - _convertTypeParameter( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): TypeParameter { - const type = typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName); - const parameter = { - name: entity.name, - type, - }; - return parameter; - }, - _convertParameter( - entity: TypeDocNode, - sections: SectionsMap, - sectionName: string, - subPackageName: string, - ): Parameter { - let comment = '<No comment>'; - if (entity.comment && entity.comment.shortText) { - comment = entity.comment.shortText; - } else if (entity.comment && entity.comment.text) { - comment = entity.comment.text; - } + const method = { + isConstructor, + isStatic, + name: signature.name, + comment: hasComment ? signature.comment.shortText : undefined, + returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined, + source: { + fileName: source.fileName, + line: source.line, + }, + callPath, + parameters, + returnType, + typeParameter, + }; + return method; + }, + _convertTypeParameter( + entity: TypeDocNode, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): TypeParameter { + const type = typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName); + const parameter = { + name: entity.name, + type, + }; + return parameter; + }, + _convertParameter( + entity: TypeDocNode, + sections: SectionsMap, + sectionName: string, + subPackageName: string, + ): Parameter { + let comment = '<No comment>'; + if (entity.comment && entity.comment.shortText) { + comment = entity.comment.shortText; + } else if (entity.comment && entity.comment.text) { + comment = entity.comment.text; + } - const isOptional = !_.isUndefined(entity.flags.isOptional) ? entity.flags.isOptional : false; + const isOptional = !_.isUndefined(entity.flags.isOptional) ? entity.flags.isOptional : false; - const type = typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName); + const type = typeDocUtils._convertType(entity.type, sections, sectionName, subPackageName); - const parameter = { - name: entity.name, - comment, - isOptional, - type, - }; - return parameter; - }, - _convertType(entity: TypeDocType, sections: SectionsMap, sectionName: string, subPackageName: string): Type { - const typeArguments = _.map(entity.typeArguments, typeArgument => { - return typeDocUtils._convertType(typeArgument, sections, sectionName, subPackageName); - }); - const types = _.map(entity.types, t => { - return typeDocUtils._convertType(t, sections, sectionName, subPackageName); - }); + const parameter = { + name: entity.name, + comment, + isOptional, + type, + }; + return parameter; + }, + _convertType(entity: TypeDocType, sections: SectionsMap, sectionName: string, subPackageName: string): Type { + const typeArguments = _.map(entity.typeArguments, typeArgument => { + return typeDocUtils._convertType(typeArgument, sections, sectionName, subPackageName); + }); + const types = _.map(entity.types, t => { + return typeDocUtils._convertType(t, sections, sectionName, subPackageName); + }); - const isConstructor = false; - const methodIfExists = !_.isUndefined(entity.declaration) - ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, subPackageName) - : undefined; + const isConstructor = false; + const methodIfExists = !_.isUndefined(entity.declaration) + ? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, subPackageName) + : undefined; - const elementTypeIfExists = !_.isUndefined(entity.elementType) - ? { - name: entity.elementType.name, - typeDocType: entity.elementType.type, - } - : undefined; + const elementTypeIfExists = !_.isUndefined(entity.elementType) + ? { + name: entity.elementType.name, + typeDocType: entity.elementType.type, + } + : undefined; - const type = { - name: entity.name, - value: entity.value, - typeDocType: entity.type, - typeArguments, - elementType: elementTypeIfExists, - types, - method: methodIfExists, - }; - return type; - }, + const type = { + name: entity.name, + value: entity.value, + typeDocType: entity.type, + typeArguments, + elementType: elementTypeIfExists, + types, + method: methodIfExists, + }; + return type; + }, }; diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index ea5b689ae..13a6d6ae2 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -5,15 +5,15 @@ import isMobile = require('is-mobile'); import * as _ from 'lodash'; import * as moment from 'moment'; import { - EtherscanLinkSuffixes, - Networks, - Order, - ScreenWidths, - Side, - SideToAssetToken, - SignatureData, - Token, - TokenByAddress, + EtherscanLinkSuffixes, + Networks, + Order, + ScreenWidths, + Side, + SideToAssetToken, + SignatureData, + Token, + TokenByAddress, } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -23,257 +23,257 @@ const LG_MIN_EM = 64; const MD_MIN_EM = 52; export const utils = { - assert(condition: boolean, message: string) { - if (!condition) { - throw new Error(message); - } - }, - spawnSwitchErr(name: string, value: any) { - return new Error(`Unexpected switch value: ${value} encountered for ${name}`); - }, - isNumeric(n: string) { - return !isNaN(parseFloat(n)) && isFinite(Number(n)); - }, - // This default unix timestamp is used for orders where the user does not specify an expiry date. - // It is a fixed constant so that both the redux store's INITIAL_STATE and components can check for - // whether a user has set an expiry date or not. It is set unrealistically high so as not to collide - // with actual values a user would select. - initialOrderExpiryUnixTimestampSec(): BigNumber { - const m = moment('2050-01-01'); - return new BigNumber(m.unix()); - }, - convertToUnixTimestampSeconds(date: moment.Moment, time?: moment.Moment): BigNumber { - const finalMoment = date; - if (!_.isUndefined(time)) { - finalMoment.hours(time.hours()); - finalMoment.minutes(time.minutes()); - } - return new BigNumber(finalMoment.unix()); - }, - convertToMomentFromUnixTimestamp(unixTimestampSec: BigNumber): moment.Moment { - return moment.unix(unixTimestampSec.toNumber()); - }, - convertToReadableDateTimeFromUnixTimestamp(unixTimestampSec: BigNumber): string { - const m = this.convertToMomentFromUnixTimestamp(unixTimestampSec); - const formattedDate: string = m.format('h:MMa MMMM D YYYY'); - return formattedDate; - }, - generateOrder( - networkId: number, - exchangeContract: string, - sideToAssetToken: SideToAssetToken, - orderExpiryTimestamp: BigNumber, - orderTakerAddress: string, - orderMakerAddress: string, - makerFee: BigNumber, - takerFee: BigNumber, - feeRecipient: string, - signatureData: SignatureData, - tokenByAddress: TokenByAddress, - orderSalt: BigNumber, - ): Order { - const makerToken = tokenByAddress[sideToAssetToken[Side.Deposit].address]; - const takerToken = tokenByAddress[sideToAssetToken[Side.Receive].address]; - const order = { - maker: { - address: orderMakerAddress, - token: { - name: makerToken.name, - symbol: makerToken.symbol, - decimals: makerToken.decimals, - address: makerToken.address, - }, - amount: sideToAssetToken[Side.Deposit].amount.toString(), - feeAmount: makerFee.toString(), - }, - taker: { - address: orderTakerAddress, - token: { - name: takerToken.name, - symbol: takerToken.symbol, - decimals: takerToken.decimals, - address: takerToken.address, - }, - amount: sideToAssetToken[Side.Receive].amount.toString(), - feeAmount: takerFee.toString(), - }, - expiration: orderExpiryTimestamp.toString(), - feeRecipient, - salt: orderSalt.toString(), - signature: signatureData, - exchangeContract, - networkId, - }; - return order; - }, - consoleLog(message: string) { - /* tslint:disable */ - console.log(message); - /* tslint:enable */ - }, - async sleepAsync(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); - }, - deepEqual(actual: any, expected: any, opts?: { strict: boolean }) { - return deepEqual(actual, expected, opts); - }, - getColSize(items: number) { - const bassCssGridSize = 12; // Source: http://basscss.com/#basscss-grid - const colSize = bassCssGridSize / items; - if (!_.isInteger(colSize)) { - throw new Error(`Number of cols must be divisible by ${bassCssGridSize}`); - } - return colSize; - }, - getScreenWidth() { - const documentEl = document.documentElement; - const body = document.getElementsByTagName('body')[0]; - const widthInPx = window.innerWidth || documentEl.clientWidth || body.clientWidth; - const bodyStyles: any = window.getComputedStyle(document.querySelector('body')); - const widthInEm = widthInPx / parseFloat(bodyStyles['font-size']); + assert(condition: boolean, message: string) { + if (!condition) { + throw new Error(message); + } + }, + spawnSwitchErr(name: string, value: any) { + return new Error(`Unexpected switch value: ${value} encountered for ${name}`); + }, + isNumeric(n: string) { + return !isNaN(parseFloat(n)) && isFinite(Number(n)); + }, + // This default unix timestamp is used for orders where the user does not specify an expiry date. + // It is a fixed constant so that both the redux store's INITIAL_STATE and components can check for + // whether a user has set an expiry date or not. It is set unrealistically high so as not to collide + // with actual values a user would select. + initialOrderExpiryUnixTimestampSec(): BigNumber { + const m = moment('2050-01-01'); + return new BigNumber(m.unix()); + }, + convertToUnixTimestampSeconds(date: moment.Moment, time?: moment.Moment): BigNumber { + const finalMoment = date; + if (!_.isUndefined(time)) { + finalMoment.hours(time.hours()); + finalMoment.minutes(time.minutes()); + } + return new BigNumber(finalMoment.unix()); + }, + convertToMomentFromUnixTimestamp(unixTimestampSec: BigNumber): moment.Moment { + return moment.unix(unixTimestampSec.toNumber()); + }, + convertToReadableDateTimeFromUnixTimestamp(unixTimestampSec: BigNumber): string { + const m = this.convertToMomentFromUnixTimestamp(unixTimestampSec); + const formattedDate: string = m.format('h:MMa MMMM D YYYY'); + return formattedDate; + }, + generateOrder( + networkId: number, + exchangeContract: string, + sideToAssetToken: SideToAssetToken, + orderExpiryTimestamp: BigNumber, + orderTakerAddress: string, + orderMakerAddress: string, + makerFee: BigNumber, + takerFee: BigNumber, + feeRecipient: string, + signatureData: SignatureData, + tokenByAddress: TokenByAddress, + orderSalt: BigNumber, + ): Order { + const makerToken = tokenByAddress[sideToAssetToken[Side.Deposit].address]; + const takerToken = tokenByAddress[sideToAssetToken[Side.Receive].address]; + const order = { + maker: { + address: orderMakerAddress, + token: { + name: makerToken.name, + symbol: makerToken.symbol, + decimals: makerToken.decimals, + address: makerToken.address, + }, + amount: sideToAssetToken[Side.Deposit].amount.toString(), + feeAmount: makerFee.toString(), + }, + taker: { + address: orderTakerAddress, + token: { + name: takerToken.name, + symbol: takerToken.symbol, + decimals: takerToken.decimals, + address: takerToken.address, + }, + amount: sideToAssetToken[Side.Receive].amount.toString(), + feeAmount: takerFee.toString(), + }, + expiration: orderExpiryTimestamp.toString(), + feeRecipient, + salt: orderSalt.toString(), + signature: signatureData, + exchangeContract, + networkId, + }; + return order; + }, + consoleLog(message: string) { + /* tslint:disable */ + console.log(message); + /* tslint:enable */ + }, + async sleepAsync(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); + }, + deepEqual(actual: any, expected: any, opts?: { strict: boolean }) { + return deepEqual(actual, expected, opts); + }, + getColSize(items: number) { + const bassCssGridSize = 12; // Source: http://basscss.com/#basscss-grid + const colSize = bassCssGridSize / items; + if (!_.isInteger(colSize)) { + throw new Error(`Number of cols must be divisible by ${bassCssGridSize}`); + } + return colSize; + }, + getScreenWidth() { + const documentEl = document.documentElement; + const body = document.getElementsByTagName('body')[0]; + const widthInPx = window.innerWidth || documentEl.clientWidth || body.clientWidth; + const bodyStyles: any = window.getComputedStyle(document.querySelector('body')); + const widthInEm = widthInPx / parseFloat(bodyStyles['font-size']); - // This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS - // class prefixes. Do not edit these. - if (widthInEm > LG_MIN_EM) { - return ScreenWidths.Lg; - } else if (widthInEm > MD_MIN_EM) { - return ScreenWidths.Md; - } else { - return ScreenWidths.Sm; - } - }, - isUserOnMobile(): boolean { - const isUserOnMobile = isMobile(); - return isUserOnMobile; - }, - getEtherScanLinkIfExists(addressOrTxHash: string, networkId: number, suffix: EtherscanLinkSuffixes): string { - const networkName = constants.NETWORK_NAME_BY_ID[networkId]; - if (_.isUndefined(networkName)) { - return undefined; - } - const etherScanPrefix = networkName === Networks.mainnet ? '' : `${networkName.toLowerCase()}.`; - return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`; - }, - setUrlHash(anchorId: string) { - window.location.hash = anchorId; - }, - async isU2FSupportedAsync(): Promise<boolean> { - const w = window as any; - return new Promise((resolve: (isSupported: boolean) => void) => { - if (w.u2f && !w.u2f.getApiVersion) { - // u2f object was found (Firefox with extension) - resolve(true); - } else { - // u2f object was not found. Using Google polyfill - // HACK: u2f.getApiVersion will simply not return a version if the - // U2F call fails for any reason. Because of this, we set a hard 3sec - // timeout to the request on our end. - const getApiVersionTimeoutMs = 3000; - const intervalId = setTimeout(() => { - resolve(false); - }, getApiVersionTimeoutMs); - u2f.getApiVersion((version: number) => { - clearTimeout(intervalId); - resolve(true); - }); - } - }); - }, - // This checks the error message returned from an injected Web3 instance on the page - // after a user was prompted to sign a message or send a transaction and decided to - // reject the request. - didUserDenyWeb3Request(errMsg: string) { - const metamaskDenialErrMsg = 'User denied message'; - const paritySignerDenialErrMsg = 'Request has been rejected'; - const ledgerDenialErrMsg = 'Invalid status 6985'; - const isUserDeniedErrMsg = - _.includes(errMsg, metamaskDenialErrMsg) || - _.includes(errMsg, paritySignerDenialErrMsg) || - _.includes(errMsg, ledgerDenialErrMsg); - return isUserDeniedErrMsg; - }, - getCurrentEnvironment() { - switch (location.host) { - case configs.DOMAIN_DEVELOPMENT: - return 'development'; - case configs.DOMAIN_STAGING: - return 'staging'; - case configs.DOMAIN_PRODUCTION: - return 'production'; - default: - return 'production'; - } - }, - getIdFromName(name: string) { - const id = name.replace(/ /g, '-'); - return id; - }, - getAddressBeginAndEnd(address: string): string { - const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287 - return truncatedAddress; - }, - hasUniqueNameAndSymbol(tokens: Token[], token: Token) { - if (token.isRegistered) { - return true; // Since it's registered, it is the canonical token - } - const registeredTokens = _.filter(tokens, t => t.isRegistered); - const tokenWithSameNameIfExists = _.find(registeredTokens, { - name: token.name, - }); - const isUniqueName = _.isUndefined(tokenWithSameNameIfExists); - const tokenWithSameSymbolIfExists = _.find(registeredTokens, { - name: token.symbol, - }); - const isUniqueSymbol = _.isUndefined(tokenWithSameSymbolIfExists); - return isUniqueName && isUniqueSymbol; - }, - zeroExErrToHumanReadableErrMsg(error: ZeroExError | ExchangeContractErrs, takerAddress: string): string { - const ZeroExErrorToHumanReadableError: { [error: string]: string } = { - [ZeroExError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', - [ZeroExError.EtherTokenContractDoesNotExist]: 'EtherToken contract does not exist', - [ZeroExError.TokenTransferProxyContractDoesNotExist]: 'TokenTransferProxy contract does not exist', - [ZeroExError.TokenRegistryContractDoesNotExist]: 'TokenRegistry contract does not exist', - [ZeroExError.TokenContractDoesNotExist]: 'Token contract does not exist', - [ZeroExError.ZRXContractDoesNotExist]: 'ZRX contract does not exist', - [ZeroExError.UnhandledError]: 'Unhandled error occured', - [ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available', - [ZeroExError.InvalidSignature]: 'Order signature is not valid', - [ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', - [ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction', - [ZeroExError.OutOfGas]: 'Transaction ran out of gas', - [ZeroExError.NoNetworkId]: 'No network id detected', - }; - const exchangeContractErrorToHumanReadableError: { - [error: string]: string; - } = { - [ExchangeContractErrs.OrderFillExpired]: 'This order has expired', - [ExchangeContractErrs.OrderCancelExpired]: 'This order has expired', - [ExchangeContractErrs.OrderCancelAmountZero]: "Order cancel amount can't be 0", - [ExchangeContractErrs.OrderAlreadyCancelledOrFilled]: - 'This order has already been completely filled or cancelled', - [ExchangeContractErrs.OrderFillAmountZero]: "Order fill amount can't be 0", - [ExchangeContractErrs.OrderRemainingFillAmountZero]: - 'This order has already been completely filled or cancelled', - [ExchangeContractErrs.OrderFillRoundingError]: 'Rounding error will occur when filling this order', - [ExchangeContractErrs.InsufficientTakerBalance]: - 'Taker no longer has a sufficient balance to complete this order', - [ExchangeContractErrs.InsufficientTakerAllowance]: - 'Taker no longer has a sufficient allowance to complete this order', - [ExchangeContractErrs.InsufficientMakerBalance]: - 'Maker no longer has a sufficient balance to complete this order', - [ExchangeContractErrs.InsufficientMakerAllowance]: - 'Maker no longer has a sufficient allowance to complete this order', - [ExchangeContractErrs.InsufficientTakerFeeBalance]: 'Taker no longer has a sufficient balance to pay fees', - [ExchangeContractErrs.InsufficientTakerFeeAllowance]: - 'Taker no longer has a sufficient allowance to pay fees', - [ExchangeContractErrs.InsufficientMakerFeeBalance]: 'Maker no longer has a sufficient balance to pay fees', - [ExchangeContractErrs.InsufficientMakerFeeAllowance]: - 'Maker no longer has a sufficient allowance to pay fees', - [ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker]: `This order can only be filled by ${takerAddress}`, - [ExchangeContractErrs.InsufficientRemainingFillAmount]: 'Insufficient remaining fill amount', - }; - const humanReadableErrorMsg = - exchangeContractErrorToHumanReadableError[error] || ZeroExErrorToHumanReadableError[error]; - return humanReadableErrorMsg; - }, + // This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS + // class prefixes. Do not edit these. + if (widthInEm > LG_MIN_EM) { + return ScreenWidths.Lg; + } else if (widthInEm > MD_MIN_EM) { + return ScreenWidths.Md; + } else { + return ScreenWidths.Sm; + } + }, + isUserOnMobile(): boolean { + const isUserOnMobile = isMobile(); + return isUserOnMobile; + }, + getEtherScanLinkIfExists(addressOrTxHash: string, networkId: number, suffix: EtherscanLinkSuffixes): string { + const networkName = constants.NETWORK_NAME_BY_ID[networkId]; + if (_.isUndefined(networkName)) { + return undefined; + } + const etherScanPrefix = networkName === Networks.mainnet ? '' : `${networkName.toLowerCase()}.`; + return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`; + }, + setUrlHash(anchorId: string) { + window.location.hash = anchorId; + }, + async isU2FSupportedAsync(): Promise<boolean> { + const w = window as any; + return new Promise((resolve: (isSupported: boolean) => void) => { + if (w.u2f && !w.u2f.getApiVersion) { + // u2f object was found (Firefox with extension) + resolve(true); + } else { + // u2f object was not found. Using Google polyfill + // HACK: u2f.getApiVersion will simply not return a version if the + // U2F call fails for any reason. Because of this, we set a hard 3sec + // timeout to the request on our end. + const getApiVersionTimeoutMs = 3000; + const intervalId = setTimeout(() => { + resolve(false); + }, getApiVersionTimeoutMs); + u2f.getApiVersion((version: number) => { + clearTimeout(intervalId); + resolve(true); + }); + } + }); + }, + // This checks the error message returned from an injected Web3 instance on the page + // after a user was prompted to sign a message or send a transaction and decided to + // reject the request. + didUserDenyWeb3Request(errMsg: string) { + const metamaskDenialErrMsg = 'User denied message'; + const paritySignerDenialErrMsg = 'Request has been rejected'; + const ledgerDenialErrMsg = 'Invalid status 6985'; + const isUserDeniedErrMsg = + _.includes(errMsg, metamaskDenialErrMsg) || + _.includes(errMsg, paritySignerDenialErrMsg) || + _.includes(errMsg, ledgerDenialErrMsg); + return isUserDeniedErrMsg; + }, + getCurrentEnvironment() { + switch (location.host) { + case configs.DOMAIN_DEVELOPMENT: + return 'development'; + case configs.DOMAIN_STAGING: + return 'staging'; + case configs.DOMAIN_PRODUCTION: + return 'production'; + default: + return 'production'; + } + }, + getIdFromName(name: string) { + const id = name.replace(/ /g, '-'); + return id; + }, + getAddressBeginAndEnd(address: string): string { + const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287 + return truncatedAddress; + }, + hasUniqueNameAndSymbol(tokens: Token[], token: Token) { + if (token.isRegistered) { + return true; // Since it's registered, it is the canonical token + } + const registeredTokens = _.filter(tokens, t => t.isRegistered); + const tokenWithSameNameIfExists = _.find(registeredTokens, { + name: token.name, + }); + const isUniqueName = _.isUndefined(tokenWithSameNameIfExists); + const tokenWithSameSymbolIfExists = _.find(registeredTokens, { + name: token.symbol, + }); + const isUniqueSymbol = _.isUndefined(tokenWithSameSymbolIfExists); + return isUniqueName && isUniqueSymbol; + }, + zeroExErrToHumanReadableErrMsg(error: ZeroExError | ExchangeContractErrs, takerAddress: string): string { + const ZeroExErrorToHumanReadableError: { [error: string]: string } = { + [ZeroExError.ExchangeContractDoesNotExist]: 'Exchange contract does not exist', + [ZeroExError.EtherTokenContractDoesNotExist]: 'EtherToken contract does not exist', + [ZeroExError.TokenTransferProxyContractDoesNotExist]: 'TokenTransferProxy contract does not exist', + [ZeroExError.TokenRegistryContractDoesNotExist]: 'TokenRegistry contract does not exist', + [ZeroExError.TokenContractDoesNotExist]: 'Token contract does not exist', + [ZeroExError.ZRXContractDoesNotExist]: 'ZRX contract does not exist', + [ZeroExError.UnhandledError]: 'Unhandled error occured', + [ZeroExError.UserHasNoAssociatedAddress]: 'User has no addresses available', + [ZeroExError.InvalidSignature]: 'Order signature is not valid', + [ZeroExError.ContractNotDeployedOnNetwork]: 'Contract is not deployed on the detected network', + [ZeroExError.InvalidJump]: 'Invalid jump occured while executing the transaction', + [ZeroExError.OutOfGas]: 'Transaction ran out of gas', + [ZeroExError.NoNetworkId]: 'No network id detected', + }; + const exchangeContractErrorToHumanReadableError: { + [error: string]: string; + } = { + [ExchangeContractErrs.OrderFillExpired]: 'This order has expired', + [ExchangeContractErrs.OrderCancelExpired]: 'This order has expired', + [ExchangeContractErrs.OrderCancelAmountZero]: "Order cancel amount can't be 0", + [ExchangeContractErrs.OrderAlreadyCancelledOrFilled]: + 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderFillAmountZero]: "Order fill amount can't be 0", + [ExchangeContractErrs.OrderRemainingFillAmountZero]: + 'This order has already been completely filled or cancelled', + [ExchangeContractErrs.OrderFillRoundingError]: 'Rounding error will occur when filling this order', + [ExchangeContractErrs.InsufficientTakerBalance]: + 'Taker no longer has a sufficient balance to complete this order', + [ExchangeContractErrs.InsufficientTakerAllowance]: + 'Taker no longer has a sufficient allowance to complete this order', + [ExchangeContractErrs.InsufficientMakerBalance]: + 'Maker no longer has a sufficient balance to complete this order', + [ExchangeContractErrs.InsufficientMakerAllowance]: + 'Maker no longer has a sufficient allowance to complete this order', + [ExchangeContractErrs.InsufficientTakerFeeBalance]: 'Taker no longer has a sufficient balance to pay fees', + [ExchangeContractErrs.InsufficientTakerFeeAllowance]: + 'Taker no longer has a sufficient allowance to pay fees', + [ExchangeContractErrs.InsufficientMakerFeeBalance]: 'Maker no longer has a sufficient balance to pay fees', + [ExchangeContractErrs.InsufficientMakerFeeAllowance]: + 'Maker no longer has a sufficient allowance to pay fees', + [ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker]: `This order can only be filled by ${takerAddress}`, + [ExchangeContractErrs.InsufficientRemainingFillAmount]: 'Insufficient remaining fill amount', + }; + const humanReadableErrorMsg = + exchangeContractErrorToHumanReadableError[error] || ZeroExErrorToHumanReadableError[error]; + return humanReadableErrorMsg; + }, }; diff --git a/packages/website/ts/web3_wrapper.ts b/packages/website/ts/web3_wrapper.ts index 0cd2a5cc2..415df6e8b 100644 --- a/packages/website/ts/web3_wrapper.ts +++ b/packages/website/ts/web3_wrapper.ts @@ -5,154 +5,154 @@ import { utils } from 'ts/utils/utils'; import * as Web3 from 'web3'; export class Web3Wrapper { - private _dispatcher: Dispatcher; - private _web3: Web3; - private _prevNetworkId: number; - private _shouldPollUserAddress: boolean; - private _watchNetworkAndBalanceIntervalId: NodeJS.Timer; - private _prevUserEtherBalanceInEth: BigNumber; - private _prevUserAddress: string; - constructor( - dispatcher: Dispatcher, - provider: Web3.Provider, - networkIdIfExists: number, - shouldPollUserAddress: boolean, - ) { - this._dispatcher = dispatcher; - this._prevNetworkId = networkIdIfExists; - this._shouldPollUserAddress = shouldPollUserAddress; + private _dispatcher: Dispatcher; + private _web3: Web3; + private _prevNetworkId: number; + private _shouldPollUserAddress: boolean; + private _watchNetworkAndBalanceIntervalId: NodeJS.Timer; + private _prevUserEtherBalanceInEth: BigNumber; + private _prevUserAddress: string; + constructor( + dispatcher: Dispatcher, + provider: Web3.Provider, + networkIdIfExists: number, + shouldPollUserAddress: boolean, + ) { + this._dispatcher = dispatcher; + this._prevNetworkId = networkIdIfExists; + this._shouldPollUserAddress = shouldPollUserAddress; - this._web3 = new Web3(); - this._web3.setProvider(provider); + this._web3 = new Web3(); + this._web3.setProvider(provider); - // tslint:disable-next-line:no-floating-promises - this._startEmittingNetworkConnectionAndUserBalanceStateAsync(); - } - public isAddress(address: string) { - return this._web3.isAddress(address); - } - public async getAccountsAsync(): Promise<string[]> { - const addresses = await promisify<string[]>(this._web3.eth.getAccounts)(); - return addresses; - } - public async getFirstAccountIfExistsAsync() { - const addresses = await this.getAccountsAsync(); - if (_.isEmpty(addresses)) { - return ''; - } - return addresses[0]; - } - public async getNodeVersionAsync(): Promise<string> { - const nodeVersion = await promisify<string>(this._web3.version.getNode)(); - return nodeVersion; - } - public getProviderObj() { - return this._web3.currentProvider; - } - public async getNetworkIdIfExists() { - try { - const networkId = await this._getNetworkAsync(); - return Number(networkId); - } catch (err) { - return undefined; - } - } - public async getBalanceInEthAsync(owner: string): Promise<BigNumber> { - const balanceInWei: BigNumber = await promisify<BigNumber>(this._web3.eth.getBalance)(owner); - const balanceEthOldBigNumber = this._web3.fromWei(balanceInWei, 'ether'); - const balanceEth = new BigNumber(balanceEthOldBigNumber); - return balanceEth; - } - public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { - const code = await promisify<string>(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; - } - public async signTransactionAsync(address: string, message: string): Promise<string> { - const signData = await promisify<string>(this._web3.eth.sign)(address, message); - return signData; - } - public async getBlockTimestampAsync(blockHash: string): Promise<number> { - const { timestamp } = await promisify<Web3.BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockHash); - return timestamp; - } - public destroy() { - this._stopEmittingNetworkConnectionAndUserBalanceStateAsync(); - // HACK: stop() is only available on providerEngine instances - const provider = this._web3.currentProvider; - if (!_.isUndefined((provider as any).stop)) { - (provider as any).stop(); - } - } - // This should only be called from the LedgerConfigDialog - public updatePrevUserAddress(userAddress: string) { - this._prevUserAddress = userAddress; - } - private async _getNetworkAsync() { - const networkId = await promisify(this._web3.version.getNetwork)(); - return networkId; - } - private async _startEmittingNetworkConnectionAndUserBalanceStateAsync() { - if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) { - return; // we are already emitting the state - } + // tslint:disable-next-line:no-floating-promises + this._startEmittingNetworkConnectionAndUserBalanceStateAsync(); + } + public isAddress(address: string) { + return this._web3.isAddress(address); + } + public async getAccountsAsync(): Promise<string[]> { + const addresses = await promisify<string[]>(this._web3.eth.getAccounts)(); + return addresses; + } + public async getFirstAccountIfExistsAsync() { + const addresses = await this.getAccountsAsync(); + if (_.isEmpty(addresses)) { + return ''; + } + return addresses[0]; + } + public async getNodeVersionAsync(): Promise<string> { + const nodeVersion = await promisify<string>(this._web3.version.getNode)(); + return nodeVersion; + } + public getProviderObj() { + return this._web3.currentProvider; + } + public async getNetworkIdIfExists() { + try { + const networkId = await this._getNetworkAsync(); + return Number(networkId); + } catch (err) { + return undefined; + } + } + public async getBalanceInEthAsync(owner: string): Promise<BigNumber> { + const balanceInWei: BigNumber = await promisify<BigNumber>(this._web3.eth.getBalance)(owner); + const balanceEthOldBigNumber = this._web3.fromWei(balanceInWei, 'ether'); + const balanceEth = new BigNumber(balanceEthOldBigNumber); + return balanceEth; + } + public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { + const code = await promisify<string>(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; + } + public async signTransactionAsync(address: string, message: string): Promise<string> { + const signData = await promisify<string>(this._web3.eth.sign)(address, message); + return signData; + } + public async getBlockTimestampAsync(blockHash: string): Promise<number> { + const { timestamp } = await promisify<Web3.BlockWithoutTransactionData>(this._web3.eth.getBlock)(blockHash); + return timestamp; + } + public destroy() { + this._stopEmittingNetworkConnectionAndUserBalanceStateAsync(); + // HACK: stop() is only available on providerEngine instances + const provider = this._web3.currentProvider; + if (!_.isUndefined((provider as any).stop)) { + (provider as any).stop(); + } + } + // This should only be called from the LedgerConfigDialog + public updatePrevUserAddress(userAddress: string) { + this._prevUserAddress = userAddress; + } + private async _getNetworkAsync() { + const networkId = await promisify(this._web3.version.getNetwork)(); + return networkId; + } + private async _startEmittingNetworkConnectionAndUserBalanceStateAsync() { + if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) { + return; // we are already emitting the state + } - let prevNodeVersion: string; - this._prevUserEtherBalanceInEth = new BigNumber(0); - this._dispatcher.updateNetworkId(this._prevNetworkId); - this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval( - async () => { - // Check for network state changes - const currentNetworkId = await this.getNetworkIdIfExists(); - if (currentNetworkId !== this._prevNetworkId) { - this._prevNetworkId = currentNetworkId; - this._dispatcher.updateNetworkId(currentNetworkId); - } + let prevNodeVersion: string; + this._prevUserEtherBalanceInEth = new BigNumber(0); + this._dispatcher.updateNetworkId(this._prevNetworkId); + this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval( + async () => { + // Check for network state changes + const currentNetworkId = await this.getNetworkIdIfExists(); + if (currentNetworkId !== this._prevNetworkId) { + this._prevNetworkId = currentNetworkId; + this._dispatcher.updateNetworkId(currentNetworkId); + } - // Check for node version changes - const currentNodeVersion = await this.getNodeVersionAsync(); - if (currentNodeVersion !== prevNodeVersion) { - prevNodeVersion = currentNodeVersion; - this._dispatcher.updateNodeVersion(currentNodeVersion); - } + // Check for node version changes + const currentNodeVersion = await this.getNodeVersionAsync(); + if (currentNodeVersion !== prevNodeVersion) { + prevNodeVersion = currentNodeVersion; + this._dispatcher.updateNodeVersion(currentNodeVersion); + } - if (this._shouldPollUserAddress) { - const userAddressIfExists = await this.getFirstAccountIfExistsAsync(); - // Update makerAddress on network change - if (this._prevUserAddress !== userAddressIfExists) { - this._prevUserAddress = userAddressIfExists; - this._dispatcher.updateUserAddress(userAddressIfExists); - } + if (this._shouldPollUserAddress) { + const userAddressIfExists = await this.getFirstAccountIfExistsAsync(); + // Update makerAddress on network change + if (this._prevUserAddress !== userAddressIfExists) { + this._prevUserAddress = userAddressIfExists; + this._dispatcher.updateUserAddress(userAddressIfExists); + } - // Check for user ether balance changes - if (userAddressIfExists !== '') { - await this._updateUserEtherBalanceAsync(userAddressIfExists); - } - } else { - // This logic is primarily for the Ledger, since we don't regularly poll for the address - // we simply update the balance for the last fetched address. - if (!_.isEmpty(this._prevUserAddress)) { - await this._updateUserEtherBalanceAsync(this._prevUserAddress); - } - } - }, - 5000, - (err: Error) => { - utils.consoleLog(`Watching network and balances failed: ${err}`); - this._stopEmittingNetworkConnectionAndUserBalanceStateAsync(); - }, - ); - } - private async _updateUserEtherBalanceAsync(userAddress: string) { - const balance = await this.getBalanceInEthAsync(userAddress); - if (!balance.eq(this._prevUserEtherBalanceInEth)) { - this._prevUserEtherBalanceInEth = balance; - this._dispatcher.updateUserEtherBalance(balance); - } - } - private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() { - intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId); - } + // Check for user ether balance changes + if (userAddressIfExists !== '') { + await this._updateUserEtherBalanceAsync(userAddressIfExists); + } + } else { + // This logic is primarily for the Ledger, since we don't regularly poll for the address + // we simply update the balance for the last fetched address. + if (!_.isEmpty(this._prevUserAddress)) { + await this._updateUserEtherBalanceAsync(this._prevUserAddress); + } + } + }, + 5000, + (err: Error) => { + utils.consoleLog(`Watching network and balances failed: ${err}`); + this._stopEmittingNetworkConnectionAndUserBalanceStateAsync(); + }, + ); + } + private async _updateUserEtherBalanceAsync(userAddress: string) { + const balance = await this.getBalanceInEthAsync(userAddress); + if (!balance.eq(this._prevUserEtherBalanceInEth)) { + this._prevUserEtherBalanceInEth = balance; + this._dispatcher.updateUserEtherBalance(balance); + } + } + private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() { + intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId); + } } diff --git a/packages/website/tsconfig.json b/packages/website/tsconfig.json index 0ed2794d8..38b177d0b 100644 --- a/packages/website/tsconfig.json +++ b/packages/website/tsconfig.json @@ -1,17 +1,17 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "outDir": "./transpiled/", - "jsx": "react", - "baseUrl": "./", - "allowJs": true, - "strictNullChecks": false, - "noImplicitThis": false, - "declaration": false, - "paths": { - "*": ["node_modules/@types/*", "*"] - } - }, - "include": ["./ts/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] + "extends": "../../tsconfig", + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "outDir": "./transpiled/", + "jsx": "react", + "baseUrl": "./", + "allowJs": true, + "strictNullChecks": false, + "noImplicitThis": false, + "declaration": false, + "paths": { + "*": ["node_modules/@types/*", "*"] + } + }, + "include": ["./ts/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"] } diff --git a/packages/website/tslint.json b/packages/website/tslint.json index fcbd0391e..d6a5f5031 100644 --- a/packages/website/tslint.json +++ b/packages/website/tslint.json @@ -1,9 +1,9 @@ { - "extends": ["@0xproject/tslint-config"], - "rules": { - "no-implicit-dependencies": false, - "no-object-literal-type-assertion": false, - "completed-docs": false, - "prefer-function-over-method": false - } + "extends": ["@0xproject/tslint-config"], + "rules": { + "no-implicit-dependencies": false, + "no-object-literal-type-assertion": false, + "completed-docs": false, + "prefer-function-over-method": false + } } |