diff options
author | Fabio Berger <me@fabioberger.com> | 2018-11-12 05:11:10 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-11-12 05:11:10 +0800 |
commit | 0391f93490cffdc69f6cb32f11762d174ed04e37 (patch) | |
tree | 3a8b30a04de1193aa21e5b84bd572db24979a8bf | |
parent | 399a7d5fec9af4f3491a77f0c2d46738f3d8ffa7 (diff) | |
parent | 397b4e289015f9bb0831c1a0ce6fee601670b487 (diff) | |
download | dexon-sol-tools-0391f93490cffdc69f6cb32f11762d174ed04e37.tar dexon-sol-tools-0391f93490cffdc69f6cb32f11762d174ed04e37.tar.gz dexon-sol-tools-0391f93490cffdc69f6cb32f11762d174ed04e37.tar.bz2 dexon-sol-tools-0391f93490cffdc69f6cb32f11762d174ed04e37.tar.lz dexon-sol-tools-0391f93490cffdc69f6cb32f11762d174ed04e37.tar.xz dexon-sol-tools-0391f93490cffdc69f6cb32f11762d174ed04e37.tar.zst dexon-sol-tools-0391f93490cffdc69f6cb32f11762d174ed04e37.zip |
merge development
248 files changed, 5924 insertions, 2081 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index 897fca3c0..0ab512f58 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,7 @@ jobs: - restore_cache: keys: - repo-{{ .Environment.CIRCLE_SHA1 }} - - run: cd packages/website && yarn build + - run: cd packages/website && yarn build:prod test-contracts-ganache: docker: - image: circleci/node:9 @@ -162,6 +162,9 @@ jobs: working_directory: ~/repo docker: - image: circleci/python + - image: 0xorg/ganache-cli + command: | + ganache-cli --gasLimit 10000000 --noVMErrorsOnRPCResponse --db /snapshot --noVMErrorsOnRPCResponse -p 8545 --networkId 50 -m "concert load couple harbor equip island argue ramp clarify fence smart topic" steps: - checkout - run: sudo chown -R circleci:circleci /usr/local/bin diff --git a/.gitignore b/.gitignore index 3276e848a..9e43f20b0 100644 --- a/.gitignore +++ b/.gitignore @@ -99,6 +99,7 @@ packages/*/scripts/ .mypy_cache .tox python-packages/*/build +python-packages/*/dist __pycache__ python-packages/*/src/*.egg-info python-packages/*/.coverage diff --git a/.prettierignore b/.prettierignore index 79dec3d1f..7ef0f6735 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ lib /packages/contracts/generated-artifacts /packages/abi-gen-wrappers/src/generated-wrappers /packages/contract-artifacts/artifacts +/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts /packages/json-schemas/schemas /packages/metacoin/src/contract_wrappers /packages/metacoin/artifacts diff --git a/package.json b/package.json index c786e7f19..e598ac2d3 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ }, { "path": "packages/instant/public/main.bundle.js", - "maxSize": "500kB" + "maxSize": "1000kB" } ], "ci": { diff --git a/packages/0x.js/CHANGELOG.json b/packages/0x.js/CHANGELOG.json index a404ed481..7d9cb8312 100644 --- a/packages/0x.js/CHANGELOG.json +++ b/packages/0x.js/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "2.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "2.0.0", "changes": [ { diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md index 939ada519..d312b2c9b 100644 --- a/packages/0x.js/CHANGELOG.md +++ b/packages/0x.js/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.1 - _November 9, 2018_ + + * Dependencies updated + ## v2.0.0 - _October 18, 2018_ * Add support for `eth_signTypedData`. (#1102) diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index 56999d3e5..38478980f 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -1,6 +1,6 @@ { "name": "0x.js", - "version": "2.0.0", + "version": "2.0.1", "engines": { "node": ">=6.12" }, @@ -18,7 +18,7 @@ "build": "yarn build:all", "build:ci": "yarn build:commonjs", "build:all": "run-p build:umd:prod build:commonjs", - "lint": "tslint --project . --exclude **/src/generated_contract_wrappers/**/*", + "lint": "tslint --format stylish --project .", "test:circleci": "run-s test:coverage", "rebuild_and_test": "run-s build test", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", @@ -42,12 +42,12 @@ }, "license": "Apache-2.0", "devDependencies": { - "@0x/abi-gen": "^1.0.14", - "@0x/abi-gen-wrappers": "^1.0.1", - "@0x/contract-addresses": "^1.0.1", - "@0x/dev-utils": "^1.0.13", - "@0x/migrations": "^2.0.0", - "@0x/tslint-config": "^1.0.9", + "@0x/abi-gen": "^1.0.15", + "@0x/abi-gen-wrappers": "^1.0.2", + "@0x/contract-addresses": "^1.1.0", + "@0x/dev-utils": "^1.0.14", + "@0x/migrations": "^2.0.1", + "@0x/tslint-config": "^1.0.10", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "@types/node": "*", @@ -73,18 +73,18 @@ "webpack": "^4.20.2" }, "dependencies": { - "@0x/assert": "^1.0.14", - "@0x/base-contract": "^3.0.2", - "@0x/contract-wrappers": "^3.0.0", - "@0x/order-utils": "^2.0.0", - "@0x/order-watcher": "^2.2.0", - "@0x/subproviders": "^2.1.0", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/assert": "^1.0.15", + "@0x/base-contract": "^3.0.3", + "@0x/contract-wrappers": "^3.0.1", + "@0x/order-utils": "^2.0.1", + "@0x/order-watcher": "^2.2.1", + "@0x/subproviders": "^2.1.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "@types/web3-provider-engine": "^14.0.0", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethers": "~4.0.4", "lodash": "^4.17.5", "web3-provider-engine": "14.0.6" diff --git a/packages/abi-gen-wrappers/CHANGELOG.json b/packages/abi-gen-wrappers/CHANGELOG.json index 211d79d26..c3273536b 100644 --- a/packages/abi-gen-wrappers/CHANGELOG.json +++ b/packages/abi-gen-wrappers/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.0.2", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "1.0.1", "changes": [ diff --git a/packages/abi-gen-wrappers/CHANGELOG.md b/packages/abi-gen-wrappers/CHANGELOG.md index df72b112c..18bd28cb8 100644 --- a/packages/abi-gen-wrappers/CHANGELOG.md +++ b/packages/abi-gen-wrappers/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.2 - _November 9, 2018_ + + * Dependencies updated + ## v1.0.1 - _October 18, 2018_ * Dependencies updated diff --git a/packages/abi-gen-wrappers/package.json b/packages/abi-gen-wrappers/package.json index 313cc339d..a4316449f 100644 --- a/packages/abi-gen-wrappers/package.json +++ b/packages/abi-gen-wrappers/package.json @@ -1,6 +1,6 @@ { "name": "@0x/abi-gen-wrappers", - "version": "1.0.1", + "version": "1.0.2", "engines": { "node": ">=6.12" }, @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "pre_build": "yarn generate_contract_wrappers", "clean": "shx rm -rf lib wrappers", "generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/generated-wrappers --backend ethers" @@ -30,17 +30,17 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/abi-gen-wrappers/README.md", "devDependencies": { - "@0x/abi-gen": "^1.0.14", - "@0x/tslint-config": "^1.0.9", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", - "ethereum-types": "^1.1.1", + "@0x/abi-gen": "^1.0.15", + "@0x/tslint-config": "^1.0.10", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", + "ethereum-types": "^1.1.2", "ethers": "~4.0.4", "lodash": "^4.17.5", "shx": "^0.2.2" }, "dependencies": { - "@0x/base-contract": "^3.0.2" + "@0x/base-contract": "^3.0.3" }, "publishConfig": { "access": "public" diff --git a/packages/abi-gen/CHANGELOG.json b/packages/abi-gen/CHANGELOG.json index 4bec24183..658a997f4 100644 --- a/packages/abi-gen/CHANGELOG.json +++ b/packages/abi-gen/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.0.15", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "1.0.14", "changes": [ diff --git a/packages/abi-gen/CHANGELOG.md b/packages/abi-gen/CHANGELOG.md index 61c4d8487..21fda1656 100644 --- a/packages/abi-gen/CHANGELOG.md +++ b/packages/abi-gen/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.15 - _November 9, 2018_ + + * Dependencies updated + ## v1.0.14 - _October 18, 2018_ * Dependencies updated diff --git a/packages/abi-gen/package.json b/packages/abi-gen/package.json index f32bb3a31..5367c9c29 100644 --- a/packages/abi-gen/package.json +++ b/packages/abi-gen/package.json @@ -1,6 +1,6 @@ { "name": "@0x/abi-gen", - "version": "1.0.14", + "version": "1.0.15", "engines": { "node": ">=6.12" }, @@ -8,7 +8,7 @@ "main": "lib/src/index.js", "types": "lib/src/index.d.ts", "scripts": { - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "clean": "shx rm -rf lib", "build": "tsc -b", "build:ci": "yarn build", @@ -31,10 +31,10 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/abi-gen/README.md", "dependencies": { - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", "chalk": "^2.3.0", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "glob": "^7.1.2", "handlebars": "^4.0.11", "lodash": "^4.17.5", @@ -45,7 +45,7 @@ "yargs": "^10.0.3" }, "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/glob": "5.0.35", "@types/handlebars": "^4.0.36", "@types/mkdirp": "^0.5.1", diff --git a/packages/assert/CHANGELOG.json b/packages/assert/CHANGELOG.json index f5a77103b..82c0938cf 100644 --- a/packages/assert/CHANGELOG.json +++ b/packages/assert/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.0.15", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "1.0.14", "changes": [ diff --git a/packages/assert/CHANGELOG.md b/packages/assert/CHANGELOG.md index 78b5da851..7d0895eca 100644 --- a/packages/assert/CHANGELOG.md +++ b/packages/assert/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.15 - _November 9, 2018_ + + * Dependencies updated + ## v1.0.14 - _October 18, 2018_ * Dependencies updated diff --git a/packages/assert/package.json b/packages/assert/package.json index 37af8979d..cc2fdaae9 100644 --- a/packages/assert/package.json +++ b/packages/assert/package.json @@ -1,6 +1,6 @@ { "name": "@0x/assert", - "version": "1.0.14", + "version": "1.0.15", "engines": { "node": ">=6.12" }, @@ -11,7 +11,7 @@ "build": "tsc -b", "build:ci": "yarn build", "clean": "shx rm -rf lib test_temp", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit", "test": "yarn run_mocha", "rebuild_and_test": "run-s clean build test", @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/assert/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "@types/valid-url": "^1.0.2", @@ -44,9 +44,9 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/json-schemas": "^2.0.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", + "@0x/json-schemas": "^2.0.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", "lodash": "^4.17.5", "valid-url": "^1.0.9" }, diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index 7ebcd8c2f..df4531063 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -1,5 +1,38 @@ [ { + "version": "2.2.0", + "changes": [ + { + "note": "`getAssetBuyerForProvidedOrders` factory function now takes 3 args instead of 4", + "pr": 1187 + }, + { + "note": + "the `OrderProvider` now requires a new method `getAvailableMakerAssetDatasAsync` and the `StandardRelayerAPIOrderProvider` requires the network id at init.", + "pr": 1203 + }, + { + "note": "No longer require that provided orders all have the same maker and taker asset data", + "pr": 1197 + }, + { + "note": + "Fix bug where `BuyQuoteInfo` objects could return `totalEthAmount` and `feeEthAmount` that were not whole numbers", + "pr": 1207 + }, + { + "note": + "Fix bug where default values for `AssetBuyer` public facing methods could get overriden by `undefined` values", + "pr": 1207 + }, + { + "note": "Lower default expiry buffer from 5 minutes to 2 minutes", + "pr": 1217 + } + ], + "timestamp": 1541740904 + }, + { "version": "2.1.0", "changes": [ { diff --git a/packages/asset-buyer/CHANGELOG.md b/packages/asset-buyer/CHANGELOG.md index 8845e7041..d6220767e 100644 --- a/packages/asset-buyer/CHANGELOG.md +++ b/packages/asset-buyer/CHANGELOG.md @@ -5,6 +5,15 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.2.0 - _November 9, 2018_ + + * `getAssetBuyerForProvidedOrders` factory function now takes 3 args instead of 4 (#1187) + * the `OrderProvider` now requires a new method `getAvailableMakerAssetDatasAsync` and the `StandardRelayerAPIOrderProvider` requires the network id at init. (#1203) + * No longer require that provided orders all have the same maker and taker asset data (#1197) + * Fix bug where `BuyQuoteInfo` objects could return `totalEthAmount` and `feeEthAmount` that were not whole numbers (#1207) + * Fix bug where default values for `AssetBuyer` public facing methods could get overriden by `undefined` values (#1207) + * Lower default expiry buffer from 5 minutes to 2 minutes (#1217) + ## v2.1.0 - _October 18, 2018_ * Add `gasLimit` and `gasPrice` as optional properties on `BuyQuoteExecutionOpts` @@ -13,6 +22,7 @@ CHANGELOG * Add `gasLimit` and `gasPrice` as optional properties on `BuyQuoteExecutionOpts` (#1116) * Add `docs:json` command to package.json (#1139) * Add missing types to public interface (#1139) + * Throw `SignatureRequestDenied` and `TransactionValueTooLow` errors when executing buy (#1147) ## v2.0.0 - _October 4, 2018_ diff --git a/packages/asset-buyer/package.json b/packages/asset-buyer/package.json index e4b4d19c0..fc2414952 100644 --- a/packages/asset-buyer/package.json +++ b/packages/asset-buyer/package.json @@ -1,6 +1,6 @@ { "name": "@0x/asset-buyer", - "version": "2.1.0", + "version": "2.2.0", "engines": { "node": ">=6.12" }, @@ -10,7 +10,7 @@ "scripts": { "build": "yarn tsc -b", "build:ci": "yarn build", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "test": "yarn run_mocha", "rebuild_and_test": "run-s clean build test", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", @@ -36,21 +36,21 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/asset-buyer/README.md", "dependencies": { - "@0x/assert": "^1.0.14", - "@0x/connect": "^3.0.2", - "@0x/contract-wrappers": "^3.0.0", - "@0x/json-schemas": "^2.0.0", - "@0x/order-utils": "^2.0.0", - "@0x/subproviders": "^2.1.0", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", - "ethereum-types": "^1.1.1", + "@0x/assert": "^1.0.15", + "@0x/connect": "^3.0.3", + "@0x/contract-wrappers": "^3.0.1", + "@0x/json-schemas": "^2.0.1", + "@0x/order-utils": "^2.0.1", + "@0x/subproviders": "^2.1.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", + "ethereum-types": "^1.1.2", "lodash": "^4.17.10" }, "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/lodash": "^4.14.116", "@types/mocha": "^2.2.42", "@types/node": "*", diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts index 74f3cb471..934410c55 100644 --- a/packages/asset-buyer/src/asset_buyer.ts +++ b/packages/asset-buyer/src/asset_buyer.ts @@ -52,16 +52,12 @@ export class AssetBuyer { public static getAssetBuyerForProvidedOrders( provider: Provider, orders: SignedOrder[], - feeOrders: SignedOrder[] = [], options: Partial<AssetBuyerOpts> = {}, ): AssetBuyer { assert.isWeb3Provider('provider', provider); assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema); - assert.doesConformToSchema('feeOrders', feeOrders, schemas.signedOrdersSchema); - assert.areValidProvidedOrders('orders', orders); - assert.areValidProvidedOrders('feeOrders', feeOrders); assert.assert(orders.length !== 0, `Expected orders to contain at least one order`); - const orderProvider = new BasicOrderProvider(_.concat(orders, feeOrders)); + const orderProvider = new BasicOrderProvider(orders); const assetBuyer = new AssetBuyer(provider, orderProvider, options); return assetBuyer; } @@ -80,7 +76,8 @@ export class AssetBuyer { ): AssetBuyer { assert.isWeb3Provider('provider', provider); assert.isWebUri('sraApiUrl', sraApiUrl); - const orderProvider = new StandardRelayerAPIOrderProvider(sraApiUrl); + const networkId = options.networkId || constants.DEFAULT_ASSET_BUYER_OPTS.networkId; + const orderProvider = new StandardRelayerAPIOrderProvider(sraApiUrl, networkId); const assetBuyer = new AssetBuyer(provider, orderProvider, options); return assetBuyer; } @@ -93,10 +90,11 @@ export class AssetBuyer { * @return An instance of AssetBuyer */ constructor(provider: Provider, orderProvider: OrderProvider, options: Partial<AssetBuyerOpts> = {}) { - const { networkId, orderRefreshIntervalMs, expiryBufferSeconds } = { - ...constants.DEFAULT_ASSET_BUYER_OPTS, - ...options, - }; + const { networkId, orderRefreshIntervalMs, expiryBufferSeconds } = _.merge( + {}, + constants.DEFAULT_ASSET_BUYER_OPTS, + options, + ); assert.isWeb3Provider('provider', provider); assert.isValidOrderProvider('orderProvider', orderProvider); assert.isNumber('networkId', networkId); @@ -125,10 +123,11 @@ export class AssetBuyer { assetBuyAmount: BigNumber, options: Partial<BuyQuoteRequestOpts> = {}, ): Promise<BuyQuote> { - const { feePercentage, shouldForceOrderRefresh, slippagePercentage } = { - ...constants.DEFAULT_BUY_QUOTE_REQUEST_OPTS, - ...options, - }; + const { feePercentage, shouldForceOrderRefresh, slippagePercentage } = _.merge( + {}, + constants.DEFAULT_BUY_QUOTE_REQUEST_OPTS, + options, + ); assert.isString('assetData', assetData); assert.isBigNumber('assetBuyAmount', assetBuyAmount); assert.isValidPercentage('feePercentage', feePercentage); @@ -189,10 +188,11 @@ export class AssetBuyer { buyQuote: BuyQuote, options: Partial<BuyQuoteExecutionOpts> = {}, ): Promise<string> { - const { ethAmount, takerAddress, feeRecipient, gasLimit, gasPrice } = { - ...constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS, - ...options, - }; + const { ethAmount, takerAddress, feeRecipient, gasLimit, gasPrice } = _.merge( + {}, + constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS, + options, + ); assert.isValidBuyQuote('buyQuote', buyQuote); if (!_.isUndefined(ethAmount)) { assert.isBigNumber('ethAmount', ethAmount); @@ -201,6 +201,12 @@ export class AssetBuyer { assert.isETHAddressHex('takerAddress', takerAddress); } assert.isETHAddressHex('feeRecipient', feeRecipient); + if (!_.isUndefined(gasLimit)) { + assert.isNumber('gasLimit', gasLimit); + } + if (!_.isUndefined(gasPrice)) { + assert.isBigNumber('gasPrice', gasPrice); + } const { orders, feeOrders, feePercentage, assetBuyAmount, worstCaseQuoteInfo } = buyQuote; // if no takerAddress is provided, try to get one from the provider let finalTakerAddress; @@ -244,6 +250,15 @@ export class AssetBuyer { } } /** + * Get the asset data of all assets that are purchaseable with ether token (wETH) in the order provider passed in at init. + * + * @return An array of asset data strings that can be purchased using wETH. + */ + public async getAvailableAssetDatasAsync(): Promise<string[]> { + const etherTokenAssetData = this._getEtherTokenAssetDataOrThrow(); + return this.orderProvider.getAvailableMakerAssetDatasAsync(etherTokenAssetData); + } + /** * Grab orders from the map, if there is a miss or it is time to refresh, fetch and process the orders */ private async _getOrdersAndFillableAmountsAsync( diff --git a/packages/asset-buyer/src/constants.ts b/packages/asset-buyer/src/constants.ts index c912253bd..c0e1bf27d 100644 --- a/packages/asset-buyer/src/constants.ts +++ b/packages/asset-buyer/src/constants.ts @@ -9,7 +9,7 @@ const MAINNET_NETWORK_ID = 1; const DEFAULT_ASSET_BUYER_OPTS: AssetBuyerOpts = { networkId: MAINNET_NETWORK_ID, orderRefreshIntervalMs: 10000, // 10 seconds - expiryBufferSeconds: 300, // 5 minutes + expiryBufferSeconds: 120, // 2 minutes }; const DEFAULT_BUY_QUOTE_REQUEST_OPTS: BuyQuoteRequestOpts = { @@ -36,6 +36,5 @@ export const constants = { DEFAULT_ASSET_BUYER_OPTS, DEFAULT_BUY_QUOTE_EXECUTION_OPTS, DEFAULT_BUY_QUOTE_REQUEST_OPTS, - MAX_PER_PAGE: 10000, EMPTY_ORDERS_AND_FILLABLE_AMOUNTS, }; diff --git a/packages/asset-buyer/src/order_providers/basic_order_provider.ts b/packages/asset-buyer/src/order_providers/basic_order_provider.ts index 68406f19b..76685f27a 100644 --- a/packages/asset-buyer/src/order_providers/basic_order_provider.ts +++ b/packages/asset-buyer/src/order_providers/basic_order_provider.ts @@ -29,4 +29,13 @@ export class BasicOrderProvider implements OrderProvider { }); return { orders }; } + /** + * Given a taker asset data string, return all availabled paired maker asset data strings. + * @param takerAssetData A string representing the taker asset data. + * @return An array of asset data strings that can be purchased using takerAssetData. + */ + public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> { + const ordersWithTakerAssetData = _.filter(this.orders, { takerAssetData }); + return _.map(ordersWithTakerAssetData, order => order.makerAssetData); + } } diff --git a/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts b/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts index 41fd1fb30..be1fc55d6 100644 --- a/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts +++ b/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts @@ -1,5 +1,5 @@ import { HttpClient } from '@0x/connect'; -import { APIOrder, OrderbookResponse } from '@0x/types'; +import { APIOrder, AssetPairsResponse, OrderbookResponse } from '@0x/types'; import * as _ from 'lodash'; import { @@ -14,6 +14,7 @@ import { orderUtils } from '../utils/order_utils'; export class StandardRelayerAPIOrderProvider implements OrderProvider { public readonly apiUrl: string; + public readonly networkId: number; private readonly _sraClient: HttpClient; /** * Given an array of APIOrder objects from a standard relayer api, return an array @@ -44,12 +45,15 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider { } /** * Instantiates a new StandardRelayerAPIOrderProvider instance - * @param apiUrl The standard relayer API base HTTP url you would like to source orders from. + * @param apiUrl The standard relayer API base HTTP url you would like to source orders from. + * @param networkId The ethereum network id. * @return An instance of StandardRelayerAPIOrderProvider */ - constructor(apiUrl: string) { + constructor(apiUrl: string, networkId: number) { assert.isWebUri('apiUrl', apiUrl); + assert.isNumber('networkId', networkId); this.apiUrl = apiUrl; + this.networkId = networkId; this._sraClient = new HttpClient(apiUrl); } /** @@ -59,9 +63,9 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider { */ public async getOrdersAsync(orderProviderRequest: OrderProviderRequest): Promise<OrderProviderResponse> { assert.isValidOrderProviderRequest('orderProviderRequest', orderProviderRequest); - const { makerAssetData, takerAssetData, networkId } = orderProviderRequest; + const { makerAssetData, takerAssetData } = orderProviderRequest; const orderbookRequest = { baseAssetData: makerAssetData, quoteAssetData: takerAssetData }; - const requestOpts = { networkId }; + const requestOpts = { networkId: this.networkId }; let orderbook: OrderbookResponse; try { orderbook = await this._sraClient.getOrderbookAsync(orderbookRequest, requestOpts); @@ -76,4 +80,26 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider { orders, }; } + /** + * Given a taker asset data string, return all availabled paired maker asset data strings. + * @param takerAssetData A string representing the taker asset data. + * @return An array of asset data strings that can be purchased using takerAssetData. + */ + public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> { + // Return a maximum of 1000 asset datas + const maxPerPage = 1000; + const requestOpts = { networkId: this.networkId, perPage: maxPerPage }; + const assetPairsRequest = { assetDataA: takerAssetData }; + const fullRequest = { + ...requestOpts, + ...assetPairsRequest, + }; + let response: AssetPairsResponse; + try { + response = await this._sraClient.getAssetPairsAsync(fullRequest); + } catch (err) { + throw new Error(AssetBuyerError.StandardRelayerApiError); + } + return _.map(response.records, item => item.assetDataB.assetData); + } } diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts index f15e7e934..3f1e6ff21 100644 --- a/packages/asset-buyer/src/types.ts +++ b/packages/asset-buyer/src/types.ts @@ -9,7 +9,6 @@ import { BigNumber } from '@0x/utils'; export interface OrderProviderRequest { makerAssetData: string; takerAssetData: string; - networkId: number; } /** @@ -27,10 +26,12 @@ export interface SignedOrderWithRemainingFillableMakerAssetAmount extends Signed remainingFillableMakerAssetAmount?: BigNumber; } /** - * Given an OrderProviderRequest, get an OrderProviderResponse. + * gerOrdersAsync: Given an OrderProviderRequest, get an OrderProviderResponse. + * getAvailableMakerAssetDatasAsync: Given a taker asset data string, return all availabled paired maker asset data strings. */ export interface OrderProvider { getOrdersAsync: (orderProviderRequest: OrderProviderRequest) => Promise<OrderProviderResponse>; + getAvailableMakerAssetDatasAsync: (takerAssetData: string) => Promise<string[]>; } /** diff --git a/packages/asset-buyer/src/utils/assert.ts b/packages/asset-buyer/src/utils/assert.ts index e8cb7f763..2466f53a4 100644 --- a/packages/asset-buyer/src/utils/assert.ts +++ b/packages/asset-buyer/src/utils/assert.ts @@ -1,6 +1,5 @@ import { assert as sharedAssert } from '@0x/assert'; import { schemas } from '@0x/json-schemas'; -import { SignedOrder } from '@0x/types'; import * as _ from 'lodash'; import { BuyQuote, BuyQuoteInfo, OrderProvider, OrderProviderRequest } from '../types'; @@ -29,22 +28,6 @@ export const assert = { isValidOrderProviderRequest(variableName: string, orderFetcherRequest: OrderProviderRequest): void { sharedAssert.isHexString(`${variableName}.makerAssetData`, orderFetcherRequest.makerAssetData); sharedAssert.isHexString(`${variableName}.takerAssetData`, orderFetcherRequest.takerAssetData); - sharedAssert.isNumber(`${variableName}.networkId`, orderFetcherRequest.networkId); - }, - areValidProvidedOrders(variableName: string, orders: SignedOrder[]): void { - if (orders.length === 0) { - return; - } - const makerAssetData = orders[0].makerAssetData; - const takerAssetData = orders[0].takerAssetData; - const filteredOrders = _.filter( - orders, - order => order.makerAssetData === makerAssetData && order.takerAssetData === takerAssetData, - ); - sharedAssert.assert( - orders.length === filteredOrders.length, - `Expected all orders in ${variableName} to have the same makerAssetData and takerAssetData.`, - ); }, isValidPercentage(variableName: string, percentage: number): void { assert.isNumber(variableName, percentage); diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index f94ab3fa4..6a67ed1ed 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -119,7 +119,7 @@ function calculateQuoteInfo( ethAmountToBuyZrx = findEthAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset); } /// find the eth amount needed to buy the affiliate fee - const ethAmountToBuyAffiliateFee = ethAmountToBuyAsset.mul(feePercentage); + const ethAmountToBuyAffiliateFee = ethAmountToBuyAsset.mul(feePercentage).ceil(); const totalEthAmountWithoutAffiliateFee = ethAmountToBuyAsset.plus(ethAmountToBuyZrx); const ethAmountTotal = totalEthAmountWithoutAffiliateFee.plus(ethAmountToBuyAffiliateFee); // divide into the assetBuyAmount in order to find rate of makerAsset / WETH diff --git a/packages/base-contract/CHANGELOG.json b/packages/base-contract/CHANGELOG.json index 166c04408..f8a1051d6 100644 --- a/packages/base-contract/CHANGELOG.json +++ b/packages/base-contract/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "3.0.3", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "3.0.2", "changes": [ diff --git a/packages/base-contract/CHANGELOG.md b/packages/base-contract/CHANGELOG.md index 2981b5ef8..d544f4f99 100644 --- a/packages/base-contract/CHANGELOG.md +++ b/packages/base-contract/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.3 - _November 9, 2018_ + + * Dependencies updated + ## v3.0.2 - _October 18, 2018_ * Dependencies updated diff --git a/packages/base-contract/package.json b/packages/base-contract/package.json index 676e8aab9..efa4a5c81 100644 --- a/packages/base-contract/package.json +++ b/packages/base-contract/package.json @@ -1,6 +1,6 @@ { "name": "@0x/base-contract", - "version": "3.0.2", + "version": "3.0.3", "engines": { "node": ">=6.12" }, @@ -17,7 +17,7 @@ "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --bail --exit", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", - "lint": "tslint --project . --exclude **/src/contract_wrappers/**/*" + "lint": "tslint --format stylish --project ." }, "license": "Apache-2.0", "repository": { @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/base-contract/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/lodash": "4.14.104", "chai": "^4.0.1", "make-promises-safe": "^1.1.0", @@ -40,10 +40,10 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", - "ethereum-types": "^1.1.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", + "ethereum-types": "^1.1.2", "ethers": "~4.0.4", "lodash": "^4.17.5" }, diff --git a/packages/connect/CHANGELOG.json b/packages/connect/CHANGELOG.json index 3412831d5..52ad9d575 100644 --- a/packages/connect/CHANGELOG.json +++ b/packages/connect/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "3.0.3", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "3.0.2", "changes": [ diff --git a/packages/connect/CHANGELOG.md b/packages/connect/CHANGELOG.md index d20b5a5f7..8e38e16de 100644 --- a/packages/connect/CHANGELOG.md +++ b/packages/connect/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.3 - _November 9, 2018_ + + * Dependencies updated + ## v3.0.2 - _October 18, 2018_ * Dependencies updated diff --git a/packages/connect/package.json b/packages/connect/package.json index 95b1bbd7d..527ac5bdc 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -1,6 +1,6 @@ { "name": "@0x/connect", - "version": "3.0.2", + "version": "3.0.3", "engines": { "node": ">=6.12" }, @@ -19,7 +19,7 @@ "build:ci": "yarn build", "clean": "shx rm -rf lib test_temp generated_docs", "copy_test_fixtures": "copyfiles -u 2 './test/fixtures/**/*.json' ./lib/test/fixtures", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit", "test": "run-s copy_test_fixtures run_mocha", "rebuild_and_test": "run-s clean build test", @@ -44,12 +44,12 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/connect/README.md", "dependencies": { - "@0x/assert": "^1.0.14", - "@0x/json-schemas": "^2.0.0", - "@0x/order-utils": "^2.0.0", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", + "@0x/assert": "^1.0.15", + "@0x/json-schemas": "^2.0.1", + "@0x/order-utils": "^2.0.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", "lodash": "^4.17.5", "query-string": "^5.0.1", "sinon": "^4.0.0", @@ -57,7 +57,7 @@ "websocket": "^1.0.25" }, "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/fetch-mock": "^6.0.3", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index c2b44ede8..307d73545 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -1,5 +1,15 @@ [ { + "version": "1.1.0", + "changes": [ + { + "pr": 1192, + "note": "Update Forwarder addresses" + } + ], + "timestamp": 1541740904 + }, + { "version": "1.0.1", "changes": [ { diff --git a/packages/contract-addresses/CHANGELOG.md b/packages/contract-addresses/CHANGELOG.md index 82c22f2e4..20e52cd8b 100644 --- a/packages/contract-addresses/CHANGELOG.md +++ b/packages/contract-addresses/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.1.0 - _November 9, 2018_ + + * Update Forwarder addresses (#1192) + ## v1.0.1 - _October 18, 2018_ * Initial release (#1105) diff --git a/packages/contract-addresses/package.json b/packages/contract-addresses/package.json index 3136d571b..47258edef 100644 --- a/packages/contract-addresses/package.json +++ b/packages/contract-addresses/package.json @@ -1,6 +1,6 @@ { "name": "@0x/contract-addresses", - "version": "1.0.1", + "version": "1.1.0", "engines": { "node": ">=6.12" }, diff --git a/packages/contract-addresses/src/index.ts b/packages/contract-addresses/src/index.ts index 1ea8c61dd..f5fd8d0be 100644 --- a/packages/contract-addresses/src/index.ts +++ b/packages/contract-addresses/src/index.ts @@ -25,7 +25,7 @@ const networkToAddresses: { [networkId: number]: ContractAddresses } = { etherToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', exchange: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b', assetProxyOwner: '0x17992e4ffb22730138e4b62aaa6367fa9d3699a6', - forwarder: '0x7afc2d5107af94c462a194d2c21b5bdd238709d6', + forwarder: '0x5468a1dc173652ee28d249c271fa9933144746b1', orderValidator: '0x9463e518dea6810309563c81d5266c1b1d149138', }, 3: { @@ -35,7 +35,7 @@ const networkToAddresses: { [networkId: number]: ContractAddresses } = { etherToken: '0xc778417e063141139fce010982780140aa0cd5ab', exchange: '0x4530c0483a1633c7a1c97d2c53721caff2caaaaf', assetProxyOwner: '0xf5fa5b5fed2727a0e44ac67f6772e97977aa358b', - forwarder: '0x3983e204b12b3c02fb0638caf2cd406a62e0ead3', + forwarder: '0x2240dab907db71e64d3e0dba4800c83b5c502d4e', orderValidator: '0x90431a90516ab49af23a0530e04e8c7836e7122f', }, 42: { @@ -45,7 +45,7 @@ const networkToAddresses: { [networkId: number]: ContractAddresses } = { etherToken: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', exchange: '0x35dd2932454449b14cee11a94d3674a936d5d7b2', assetProxyOwner: '0x2c824d2882baa668e0d5202b1e7f2922278703f8', - forwarder: '0xd85e2fa7e7e252b27b01bf0d65c946959d2f45b8', + forwarder: '0x17992e4ffb22730138e4b62aaa6367fa9d3699a6', orderValidator: '0xb389da3d204b412df2f75c6afb3d0a7ce0bc283d', }, }; diff --git a/packages/contract-artifacts/CHANGELOG.json b/packages/contract-artifacts/CHANGELOG.json index c2b44ede8..8833255ed 100644 --- a/packages/contract-artifacts/CHANGELOG.json +++ b/packages/contract-artifacts/CHANGELOG.json @@ -1,5 +1,15 @@ [ { + "version": "1.1.0", + "changes": [ + { + "pr": 1192, + "note": "Update Forwarder artifact" + } + ], + "timestamp": 1541740904 + }, + { "version": "1.0.1", "changes": [ { diff --git a/packages/contract-artifacts/CHANGELOG.md b/packages/contract-artifacts/CHANGELOG.md index 82c22f2e4..b3c399985 100644 --- a/packages/contract-artifacts/CHANGELOG.md +++ b/packages/contract-artifacts/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.1.0 - _November 9, 2018_ + + * Update Forwarder artifact (#1192) + ## v1.0.1 - _October 18, 2018_ * Initial release (#1105) diff --git a/packages/contract-artifacts/artifacts/Forwarder.json b/packages/contract-artifacts/artifacts/Forwarder.json index 4f93b231b..a7bd62f8e 100644 --- a/packages/contract-artifacts/artifacts/Forwarder.json +++ b/packages/contract-artifacts/artifacts/Forwarder.json @@ -1,448 +1,447 @@ { - "schemaVersion": "2.0.0", - "contractName": "Forwarder", - "compilerOutput": { - "abi": [ - { - "constant": false, - "inputs": [ - { - "components": [ - { - "name": "makerAddress", - "type": "address" - }, - { - "name": "takerAddress", - "type": "address" - }, - { - "name": "feeRecipientAddress", - "type": "address" - }, - { - "name": "senderAddress", - "type": "address" - }, - { - "name": "makerAssetAmount", - "type": "uint256" - }, - { - "name": "takerAssetAmount", - "type": "uint256" - }, - { - "name": "makerFee", - "type": "uint256" - }, - { - "name": "takerFee", - "type": "uint256" - }, - { - "name": "expirationTimeSeconds", - "type": "uint256" - }, - { - "name": "salt", - "type": "uint256" - }, - { - "name": "makerAssetData", - "type": "bytes" - }, - { - "name": "takerAssetData", - "type": "bytes" - } - ], - "name": "orders", - "type": "tuple[]" - }, - { - "name": "makerAssetFillAmount", - "type": "uint256" - }, - { - "name": "signatures", - "type": "bytes[]" - }, - { - "components": [ - { - "name": "makerAddress", - "type": "address" - }, - { - "name": "takerAddress", - "type": "address" - }, - { - "name": "feeRecipientAddress", - "type": "address" - }, - { - "name": "senderAddress", - "type": "address" - }, - { - "name": "makerAssetAmount", - "type": "uint256" - }, - { - "name": "takerAssetAmount", - "type": "uint256" - }, - { - "name": "makerFee", - "type": "uint256" - }, - { - "name": "takerFee", - "type": "uint256" - }, - { - "name": "expirationTimeSeconds", - "type": "uint256" - }, - { - "name": "salt", - "type": "uint256" - }, - { - "name": "makerAssetData", - "type": "bytes" - }, - { - "name": "takerAssetData", - "type": "bytes" - } - ], - "name": "feeOrders", - "type": "tuple[]" - }, - { - "name": "feeSignatures", - "type": "bytes[]" - }, - { - "name": "feePercentage", - "type": "uint256" - }, - { - "name": "feeRecipient", - "type": "address" - } - ], - "name": "marketBuyOrdersWithEth", - "outputs": [ - { - "components": [ - { - "name": "makerAssetFilledAmount", - "type": "uint256" - }, - { - "name": "takerAssetFilledAmount", - "type": "uint256" - }, - { - "name": "makerFeePaid", - "type": "uint256" - }, - { - "name": "takerFeePaid", - "type": "uint256" - } - ], - "name": "orderFillResults", - "type": "tuple" - }, - { - "components": [ - { - "name": "makerAssetFilledAmount", - "type": "uint256" - }, - { - "name": "takerAssetFilledAmount", - "type": "uint256" - }, - { - "name": "makerFeePaid", - "type": "uint256" - }, - { - "name": "takerFeePaid", - "type": "uint256" - } - ], - "name": "feeOrderFillResults", - "type": "tuple" - } - ], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "assetData", - "type": "bytes" - }, - { - "name": "amount", - "type": "uint256" - } - ], - "name": "withdrawAsset", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "components": [ - { - "name": "makerAddress", - "type": "address" - }, - { - "name": "takerAddress", - "type": "address" - }, - { - "name": "feeRecipientAddress", - "type": "address" - }, - { - "name": "senderAddress", - "type": "address" - }, - { - "name": "makerAssetAmount", - "type": "uint256" - }, - { - "name": "takerAssetAmount", - "type": "uint256" - }, - { - "name": "makerFee", - "type": "uint256" - }, - { - "name": "takerFee", - "type": "uint256" - }, - { - "name": "expirationTimeSeconds", - "type": "uint256" - }, - { - "name": "salt", - "type": "uint256" - }, - { - "name": "makerAssetData", - "type": "bytes" - }, - { - "name": "takerAssetData", - "type": "bytes" - } - ], - "name": "orders", - "type": "tuple[]" - }, - { - "name": "signatures", - "type": "bytes[]" - }, - { - "components": [ - { - "name": "makerAddress", - "type": "address" - }, - { - "name": "takerAddress", - "type": "address" - }, - { - "name": "feeRecipientAddress", - "type": "address" - }, - { - "name": "senderAddress", - "type": "address" - }, - { - "name": "makerAssetAmount", - "type": "uint256" - }, - { - "name": "takerAssetAmount", - "type": "uint256" - }, - { - "name": "makerFee", - "type": "uint256" - }, - { - "name": "takerFee", - "type": "uint256" - }, - { - "name": "expirationTimeSeconds", - "type": "uint256" - }, - { - "name": "salt", - "type": "uint256" - }, - { - "name": "makerAssetData", - "type": "bytes" - }, - { - "name": "takerAssetData", - "type": "bytes" - } - ], - "name": "feeOrders", - "type": "tuple[]" - }, - { - "name": "feeSignatures", - "type": "bytes[]" - }, - { - "name": "feePercentage", - "type": "uint256" - }, - { - "name": "feeRecipient", - "type": "address" - } - ], - "name": "marketSellOrdersWithEth", - "outputs": [ - { - "components": [ - { - "name": "makerAssetFilledAmount", - "type": "uint256" - }, - { - "name": "takerAssetFilledAmount", - "type": "uint256" - }, - { - "name": "makerFeePaid", - "type": "uint256" - }, - { - "name": "takerFeePaid", - "type": "uint256" - } - ], - "name": "orderFillResults", - "type": "tuple" - }, - { - "components": [ - { - "name": "makerAssetFilledAmount", - "type": "uint256" - }, - { - "name": "takerAssetFilledAmount", - "type": "uint256" - }, - { - "name": "makerFeePaid", - "type": "uint256" - }, - { - "name": "takerFeePaid", - "type": "uint256" - } - ], - "name": "feeOrderFillResults", - "type": "tuple" - } - ], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "name": "_exchange", - "type": "address" - }, - { - "name": "_zrxAssetData", - "type": "bytes" - }, - { - "name": "_wethAssetData", - "type": "bytes" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "payable": true, - "stateMutability": "payable", - "type": "fallback" - } - ], - "evm": { - "bytecode": { - "linkReferences": {}, - "object": - "0x60806040523480156200001157600080fd5b5060405162002cdb38038062002cdb83398101806040526200003791908101906200051d565b6000805433600160a060020a031991821617825560018054909116600160a060020a0386161790558251849084908490849081906200007e906004906020870190620003d0565b50825162000094906005906020860190620003d0565b50620000b0836010640100000000620018f66200036f82021704565b9150620000cd846010640100000000620018f66200036f82021704565b60028054600160a060020a03948516600160a060020a031991821617909155600380549285169290911691909117905550600154604080517f4552433230546f6b656e28616464726573732900000000000000000000000000815290519081900360130181207f6070410800000000000000000000000000000000000000000000000000000000825291909216945063607041089350620001739250906004016200068e565b602060405180830381600087803b1580156200018e57600080fd5b505af1158015620001a3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620001c99190810190620004f4565b9050600160a060020a038116151562000219576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200021090620006b0565b60405180910390fd5b6002546040517f095ea7b3000000000000000000000000000000000000000000000000000000008152600160a060020a039091169063095ea7b39062000268908490600019906004016200066f565b602060405180830381600087803b1580156200028357600080fd5b505af115801562000298573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620002be9190810190620005a1565b506003546040517f095ea7b3000000000000000000000000000000000000000000000000000000008152600160a060020a039091169063095ea7b3906200030e908490600019906004016200066f565b602060405180830381600087803b1580156200032957600080fd5b505af11580156200033e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620003649190810190620005a1565b50505050506200077a565b600081601401835110151515620003b4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000210906200069e565b506014818301810151910190600160a060020a03165b92915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200041357805160ff191683800117855562000443565b8280016001018555821562000443579182015b828111156200044357825182559160200191906001019062000426565b506200045192915062000455565b5090565b6200047291905b808211156200045157600081556001016200045c565b90565b600062000483825162000711565b9392505050565b600062000483825162000742565b6000601f82018313620004aa57600080fd5b8151620004c1620004bb82620006e9565b620006c2565b91508082526020830160208301858383011115620004de57600080fd5b620004eb83828462000747565b50505092915050565b6000602082840312156200050757600080fd5b600062000515848462000475565b949350505050565b6000806000606084860312156200053357600080fd5b600062000541868662000475565b93505060208401516001604060020a038111156200055e57600080fd5b6200056c8682870162000498565b92505060408401516001604060020a038111156200058957600080fd5b620005978682870162000498565b9150509250925092565b600060208284031215620005b457600080fd5b60006200051584846200048a565b620005cd8162000711565b82525050565b620005cd816200071d565b602681527f475245415445525f4f525f455155414c5f544f5f32305f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b601881527f554e524547495354455245445f41535345545f50524f58590000000000000000602082015260400190565b620005cd8162000472565b604081016200067f8285620005c2565b62000483602083018462000664565b60208101620003ca8284620005d3565b60208082528101620003ca81620005de565b60208082528101620003ca8162000634565b6040518181016001604060020a0381118282101715620006e157600080fd5b604052919050565b60006001604060020a038211156200070057600080fd5b506020601f91909101601f19160190565b600160a060020a031690565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b151590565b60005b83811015620007645781810151838201526020016200074a565b8381111562000774576000848401525b50505050565b612551806200078a6000396000f30060806040526004361061006c5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166318978e8281146100c8578063630f1e6c146100f25780638da5cb5b146101125780639395525c14610134578063f2fde38b14610147575b60025473ffffffffffffffffffffffffffffffffffffffff1633146100c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612337565b60405180910390fd5b005b6100db6100d6366004611da0565b610167565b6040516100e9929190612437565b60405180910390f35b3480156100fe57600080fd5b506100c661010d366004611e9b565b6102f7565b34801561011e57600080fd5b50610127610388565b6040516100e991906122e6565b6100db610142366004611cba565b6103a4565b34801561015357600080fd5b506100c6610162366004611c94565b61050a565b61016f6119a9565b6101776119a9565b6000806101826105bb565b60048054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610100600188161502019095169490940493840181900481028201810190925282815261025c939092909183018282801561022d5780601f106102025761010080835404028352916020019161022d565b820191906000526020600020905b81548152906001019060200180831161021057829003601f168201915b50505050508c600081518110151561024157fe5b6020908102909101015161014001519063ffffffff61069616565b156102875761026c8b8b8b6107c3565b935061028084600001518560600151610acb565b90506102ae565b6102928b8b8b610b0d565b9350836060015191506102a68883896107c3565b845190935090505b6102c2846020015184602001518888610d1f565b6102e98b60008151811015156102d457fe5b90602001906020020151610140015182610f33565b505097509795505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610348576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123e7565b61038383838080601f01602080910402602001604051908101604052809392919081815260200183838082843750879450610f339350505050565b505050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b6103ac6119a9565b6103b46119a9565b60008060006103c16105bb565b60048054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101006001881615020190951694909404938401819004810282018101909252828152610441939092909183018282801561022d5780601f106102025761010080835404028352916020019161022d565b156104925761046a670de0b6b3a7640000610464670de0b6b3a76400008a61104f565b34611099565b92506104778b848c6110f1565b945061048b85600001518660600151610acb565b90506104d6565b6104ad670d2f13f7789f0000670de0b6b3a764000034611099565b92506104ba8b848c6110f1565b9450846060015191506104ce89838a6107c3565b855190945090505b6104ea856020015185602001518989610d1f565b6104fc8b60008151811015156102d457fe5b505050965096945050505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461055b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123e7565b73ffffffffffffffffffffffffffffffffffffffff8116156105b857600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b600034116105f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612347565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0e30db0346040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004016000604051808303818588803b15801561067b57600080fd5b505af115801561068f573d6000803e3d6000fd5b5050505050565b6000815183511480156107ba5750816040518082805190602001908083835b602083106106f257805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016106b5565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0180199092169116179052604051919093018190038120885190955088945090928392508401908083835b6020831061078757805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161074a565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902060001916145b90505b92915050565b6107cb6119a9565b60608060008060008060006107de6119a9565b8a15156107ea57610abc565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561088e5780601f106108635761010080835404028352916020019161088e565b820191906000526020600020905b81548152906001019060200180831161087157829003601f168201915b505060058054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101006001881615020190951694909404938401819004810282018101909252828152969e509194509250840190508282801561093d5780601f106109125761010080835404028352916020019161093d565b820191906000526020600020905b81548152906001019060200180831161092057829003601f168201915b50505050509650600095508b519450600093505b838514610a8257878c8581518110151561096757fe5b6020908102909101015161014001528b5187908d908690811061098657fe5b60209081029091010151610160015261099f8b87610acb565b9250610a068c858151811015156109b257fe5b9060200190602002015160a00151610a008e878151811015156109d157fe5b90602001906020020151608001518f888151811015156109ed57fe5b9060200190602002015160e00151610acb565b85611099565b9150610a4b8c85815181101515610a1957fe5b90602001906020020151610a2e84600161104f565b8c87815181101515610a3c57fe5b90602001906020020151611295565b9050610a57898261130d565b610a6989600001518a60600151610acb565b95508a8610610a7757610a82565b600190930192610951565b8a861015610abc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123c7565b50505050505050509392505050565b600082821115610b07576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612367565b50900390565b610b156119a9565b606080600080600080610b266119a9565b60008b6000815181101515610b3757fe5b6020908102919091018101516101400151600580546040805160026001841615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190931692909204601f8101869004860283018601909152808252929b5092909190830182828015610bef5780601f10610bc457610100808354040283529160200191610bef565b820191906000526020600020905b815481529060010190602001808311610bd257829003601f168201915b505050505096508b519550600094505b848614610ce557878c86815181101515610c1557fe5b6020908102909101015161014001528b5187908d9087908110610c3457fe5b6020908102909101015161016001528851610c50908c90610acb565b9350610c938c86815181101515610c6357fe5b9060200190602002015160a001518d87815181101515610c7f57fe5b906020019060200201516080015186611099565b9250610cbf8c86815181101515610ca657fe5b90602001906020020151848c88815181101515610a3c57fe5b9150610ccb898361130d565b5087518a8110610cda57610ce5565b600190940193610bff565b8a811015610abc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123c7565b600080808066b1a2bc2ec50000861115610d65576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123f7565b610d6f888861104f565b935034841115610dab576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612357565b610db53485610acb565b9250610dca86670de0b6b3a76400008a611099565b915082821115610e06576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123d7565b6000831115610f29576002546040517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90911690632e1a7d4d90610e65908690600401612453565b600060405180830381600087803b158015610e7f57600080fd5b505af1158015610e93573d6000803e3d6000fd5b505050506000821115610ee55760405173ffffffffffffffffffffffffffffffffffffffff86169083156108fc029084906000818181858888f19350505050158015610ee3573d6000803e3d6000fd5b505b610eef8383610acb565b90506000811115610f2957604051339082156108fc029083906000818181858888f19350505050158015610f27573d6000803e3d6000fd5b505b5050505050505050565b6000610f45838263ffffffff61136f16565b604080517f4552433230546f6b656e28616464726573732900000000000000000000000000815290519081900360130190209091507fffffffff0000000000000000000000000000000000000000000000000000000080831691161415610fb557610fb083836113dc565b610383565b604080517f455243373231546f6b656e28616464726573732c75696e7432353629000000008152905190819003601c0190207fffffffff000000000000000000000000000000000000000000000000000000008281169116141561101d57610fb083836115ca565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123a7565b60008282018381101561108e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612397565b8091505b5092915050565b60008083116110d4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612387565b6110e76110e185846116b2565b8461170d565b90505b9392505050565b6110f96119a9565b60608060008060006111096119a9565b89600081518110151561111857fe5b6020908102919091018101516101400151600580546040805160026001841615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190931692909204601f8101869004860283018601909152808252929950929091908301828280156111d05780601f106111a5576101008083540402835291602001916111d0565b820191906000526020600020905b8154815290600101906020018083116111b357829003601f168201915b5050505050945089519350600092505b82841461128857858a848151811015156111f657fe5b602090810290910101516101400152895185908b908590811061121557fe5b906020019060200201516101600181905250611235898860200151610acb565b91506112618a8481518110151561124857fe5b90602001906020020151838a86815181101515610a3c57fe5b905061126d878261130d565b6020870151891161127d57611288565b6001909201916111e0565b5050505050509392505050565b61129d6119a9565b606060006112ac868686611724565b600154815191935073ffffffffffffffffffffffffffffffffffffffff1691506080908390602082016000855af1801561130357825184526020830151602085015260408301516040850152606083015160608501525b5050509392505050565b8151815161131b919061104f565b825260208083015190820151611331919061104f565b60208301526040808301519082015161134a919061104f565b604083015260608083015190820151611363919061104f565b60609092019190915250565b6000816004018351101515156113b1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612417565b5001602001517fffffffff000000000000000000000000000000000000000000000000000000001690565b6000806113f084601063ffffffff6118f616565b604080517f7472616e7366657228616464726573732c75696e7432353629000000000000008152905190819003601901812091935073ffffffffffffffffffffffffffffffffffffffff8416919061144e903390879060240161231c565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931783525181519192909182919080838360005b838110156114f25781810151838201526020016114da565b50505050905090810190601f16801561151f5780820380516001836020036101000a031916815260200191505b509150506000604051808303816000865af19250505080151561156e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123b7565b3d1561158b575060003d6020141561158b5760206000803e506000515b8015156115c4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123b7565b50505050565b60008060018314611607576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612427565b61161884601063ffffffff6118f616565b915061162b84602463ffffffff61195716565b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff8316906323b872dd90611684903090339086906004016122f4565b600060405180830381600087803b15801561169e57600080fd5b505af1158015610f29573d6000803e3d6000fd5b6000808315156116c55760009150611092565b508282028284828115156116d557fe5b041461108e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612397565b600080828481151561171b57fe5b04949350505050565b604080517fb4be83d5000000000000000000000000000000000000000000000000000000006020808301919091526060602483018181528751608485019081528884015160a48601529488015160c48501529087015160e4840152608087015161010484015260a087015161012484015260c087015161014484015260e08701516101648401526101008701516101848401526101208701516101a4840152610140870180516101c485019081526101608901516101e4860152610180905251805161020485018190529394919384936044870192849261022489019291820191601f82010460005b8181101561182b57835185526020948501949093019260010161180d565b50505050818103610160808401919091528a0151805180835260209283019291820191601f82010460005b81811015611874578351855260209485019490930192600101611856565b50505089845250848103602093840190815288518083529093918201918981019190601f82010460005b818110156118bc57835185526020948501949093019260010161189e565b5050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08883030188525060405250505050509392505050565b600081601401835110151515611938576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612407565b50016014015173ffffffffffffffffffffffffffffffffffffffff1690565b60006107ba83836000816020018351101515156119a0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612377565b50016020015190565b608060405190810160405280600081526020016000815260200160008152602001600081525090565b60006107ba82356124ef565b6000601f820183136119ef57600080fd5b8135611a026119fd82612488565b612461565b81815260209384019390925082018360005b83811015611a405781358601611a2a8882611af0565b8452506020928301929190910190600101611a14565b5050505092915050565b6000601f82018313611a5b57600080fd5b8135611a696119fd82612488565b81815260209384019390925082018360005b83811015611a405781358601611a918882611b3f565b8452506020928301929190910190600101611a7b565b600080601f83018413611ab957600080fd5b50813567ffffffffffffffff811115611ad157600080fd5b602083019150836001820283011115611ae957600080fd5b9250929050565b6000601f82018313611b0157600080fd5b8135611b0f6119fd826124a9565b91508082526020830160208301858383011115611b2b57600080fd5b611b3683828461250b565b50505092915050565b60006101808284031215611b5257600080fd5b611b5d610180612461565b90506000611b6b84846119d2565b8252506020611b7c848483016119d2565b6020830152506040611b90848285016119d2565b6040830152506060611ba4848285016119d2565b6060830152506080611bb884828501611c88565b60808301525060a0611bcc84828501611c88565b60a08301525060c0611be084828501611c88565b60c08301525060e0611bf484828501611c88565b60e083015250610100611c0984828501611c88565b61010083015250610120611c1f84828501611c88565b6101208301525061014082013567ffffffffffffffff811115611c4157600080fd5b611c4d84828501611af0565b6101408301525061016082013567ffffffffffffffff811115611c6f57600080fd5b611c7b84828501611af0565b6101608301525092915050565b60006107ba8235612508565b600060208284031215611ca657600080fd5b6000611cb284846119d2565b949350505050565b60008060008060008060c08789031215611cd357600080fd5b863567ffffffffffffffff811115611cea57600080fd5b611cf689828a01611a4a565b965050602087013567ffffffffffffffff811115611d1357600080fd5b611d1f89828a016119de565b955050604087013567ffffffffffffffff811115611d3c57600080fd5b611d4889828a01611a4a565b945050606087013567ffffffffffffffff811115611d6557600080fd5b611d7189828a016119de565b9350506080611d8289828a01611c88565b92505060a0611d9389828a016119d2565b9150509295509295509295565b600080600080600080600060e0888a031215611dbb57600080fd5b873567ffffffffffffffff811115611dd257600080fd5b611dde8a828b01611a4a565b9750506020611def8a828b01611c88565b965050604088013567ffffffffffffffff811115611e0c57600080fd5b611e188a828b016119de565b955050606088013567ffffffffffffffff811115611e3557600080fd5b611e418a828b01611a4a565b945050608088013567ffffffffffffffff811115611e5e57600080fd5b611e6a8a828b016119de565b93505060a0611e7b8a828b01611c88565b92505060c0611e8c8a828b016119d2565b91505092959891949750929550565b600080600060408486031215611eb057600080fd5b833567ffffffffffffffff811115611ec757600080fd5b611ed386828701611aa7565b93509350506020611ee686828701611c88565b9150509250925092565b611ef9816124ef565b82525050565b602381527f44454641554c545f46554e4354494f4e5f574554485f434f4e54524143545f4f60208201527f4e4c590000000000000000000000000000000000000000000000000000000000604082015260600190565b601181527f494e56414c49445f4d53475f56414c5545000000000000000000000000000000602082015260400190565b600d81527f4f564552534f4c445f5745544800000000000000000000000000000000000000602082015260400190565b601181527f55494e543235365f554e444552464c4f57000000000000000000000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f33325f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b601081527f4449564953494f4e5f42595f5a45524f00000000000000000000000000000000602082015260400190565b601081527f55494e543235365f4f564552464c4f5700000000000000000000000000000000602082015260400190565b601781527f554e535550504f525445445f41535345545f50524f5859000000000000000000602082015260400190565b600f81527f5452414e534645525f4641494c45440000000000000000000000000000000000602082015260400190565b601481527f434f4d504c4554455f46494c4c5f4641494c4544000000000000000000000000602082015260400190565b601a81527f494e53554646494349454e545f4554485f52454d41494e494e47000000000000602082015260400190565b601381527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000602082015260400190565b601881527f4645455f50455243454e544147455f544f4f5f4c415247450000000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f32305f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b602581527f475245415445525f4f525f455155414c5f544f5f345f4c454e4754485f52455160208201527f5549524544000000000000000000000000000000000000000000000000000000604082015260600190565b600e81527f494e56414c49445f414d4f554e54000000000000000000000000000000000000602082015260400190565b805160808301906122a884826122dd565b5060208201516122bb60208501826122dd565b5060408201516122ce60408501826122dd565b5060608201516115c460608501825b611ef981612508565b602081016107bd8284611ef0565b606081016123028286611ef0565b61230f6020830185611ef0565b611cb260408301846122dd565b6040810161232a8285611ef0565b6110ea60208301846122dd565b602080825281016107bd81611eff565b602080825281016107bd81611f55565b602080825281016107bd81611f85565b602080825281016107bd81611fb5565b602080825281016107bd81611fe5565b602080825281016107bd8161203b565b602080825281016107bd8161206b565b602080825281016107bd8161209b565b602080825281016107bd816120cb565b602080825281016107bd816120fb565b602080825281016107bd8161212b565b602080825281016107bd8161215b565b602080825281016107bd8161218b565b602080825281016107bd816121bb565b602080825281016107bd81612211565b602080825281016107bd81612267565b61010081016124468285612297565b6110ea6080830184612297565b602081016107bd82846122dd565b60405181810167ffffffffffffffff8111828210171561248057600080fd5b604052919050565b600067ffffffffffffffff82111561249f57600080fd5b5060209081020190565b600067ffffffffffffffff8211156124c057600080fd5b506020601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160190565b73ffffffffffffffffffffffffffffffffffffffff1690565b90565b828183375060009101525600a265627a7a72305820185af4a5e8525b0c20983bf22c09773a1cab3f704716cba4c8da3b112601cd666c6578706572696d656e74616cf50037" + "schemaVersion": "2.0.0", + "contractName": "Forwarder", + "compilerOutput": { + "abi": [ + { + "constant": false, + "inputs": [ + { + "components": [ + { + "name": "makerAddress", + "type": "address" + }, + { + "name": "takerAddress", + "type": "address" + }, + { + "name": "feeRecipientAddress", + "type": "address" + }, + { + "name": "senderAddress", + "type": "address" + }, + { + "name": "makerAssetAmount", + "type": "uint256" + }, + { + "name": "takerAssetAmount", + "type": "uint256" + }, + { + "name": "makerFee", + "type": "uint256" + }, + { + "name": "takerFee", + "type": "uint256" + }, + { + "name": "expirationTimeSeconds", + "type": "uint256" + }, + { + "name": "salt", + "type": "uint256" + }, + { + "name": "makerAssetData", + "type": "bytes" + }, + { + "name": "takerAssetData", + "type": "bytes" + } + ], + "name": "orders", + "type": "tuple[]" + }, + { + "name": "makerAssetFillAmount", + "type": "uint256" + }, + { + "name": "signatures", + "type": "bytes[]" + }, + { + "components": [ + { + "name": "makerAddress", + "type": "address" + }, + { + "name": "takerAddress", + "type": "address" + }, + { + "name": "feeRecipientAddress", + "type": "address" + }, + { + "name": "senderAddress", + "type": "address" + }, + { + "name": "makerAssetAmount", + "type": "uint256" + }, + { + "name": "takerAssetAmount", + "type": "uint256" + }, + { + "name": "makerFee", + "type": "uint256" + }, + { + "name": "takerFee", + "type": "uint256" + }, + { + "name": "expirationTimeSeconds", + "type": "uint256" + }, + { + "name": "salt", + "type": "uint256" + }, + { + "name": "makerAssetData", + "type": "bytes" + }, + { + "name": "takerAssetData", + "type": "bytes" + } + ], + "name": "feeOrders", + "type": "tuple[]" + }, + { + "name": "feeSignatures", + "type": "bytes[]" + }, + { + "name": "feePercentage", + "type": "uint256" + }, + { + "name": "feeRecipient", + "type": "address" + } + ], + "name": "marketBuyOrdersWithEth", + "outputs": [ + { + "components": [ + { + "name": "makerAssetFilledAmount", + "type": "uint256" + }, + { + "name": "takerAssetFilledAmount", + "type": "uint256" + }, + { + "name": "makerFeePaid", + "type": "uint256" + }, + { + "name": "takerFeePaid", + "type": "uint256" + } + ], + "name": "orderFillResults", + "type": "tuple" + }, + { + "components": [ + { + "name": "makerAssetFilledAmount", + "type": "uint256" + }, + { + "name": "takerAssetFilledAmount", + "type": "uint256" + }, + { + "name": "makerFeePaid", + "type": "uint256" + }, + { + "name": "takerFeePaid", + "type": "uint256" + } + ], + "name": "feeOrderFillResults", + "type": "tuple" + } + ], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "assetData", + "type": "bytes" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "withdrawAsset", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "components": [ + { + "name": "makerAddress", + "type": "address" + }, + { + "name": "takerAddress", + "type": "address" + }, + { + "name": "feeRecipientAddress", + "type": "address" + }, + { + "name": "senderAddress", + "type": "address" + }, + { + "name": "makerAssetAmount", + "type": "uint256" + }, + { + "name": "takerAssetAmount", + "type": "uint256" + }, + { + "name": "makerFee", + "type": "uint256" + }, + { + "name": "takerFee", + "type": "uint256" + }, + { + "name": "expirationTimeSeconds", + "type": "uint256" + }, + { + "name": "salt", + "type": "uint256" + }, + { + "name": "makerAssetData", + "type": "bytes" + }, + { + "name": "takerAssetData", + "type": "bytes" + } + ], + "name": "orders", + "type": "tuple[]" + }, + { + "name": "signatures", + "type": "bytes[]" + }, + { + "components": [ + { + "name": "makerAddress", + "type": "address" + }, + { + "name": "takerAddress", + "type": "address" + }, + { + "name": "feeRecipientAddress", + "type": "address" + }, + { + "name": "senderAddress", + "type": "address" + }, + { + "name": "makerAssetAmount", + "type": "uint256" + }, + { + "name": "takerAssetAmount", + "type": "uint256" + }, + { + "name": "makerFee", + "type": "uint256" + }, + { + "name": "takerFee", + "type": "uint256" + }, + { + "name": "expirationTimeSeconds", + "type": "uint256" + }, + { + "name": "salt", + "type": "uint256" + }, + { + "name": "makerAssetData", + "type": "bytes" + }, + { + "name": "takerAssetData", + "type": "bytes" + } + ], + "name": "feeOrders", + "type": "tuple[]" + }, + { + "name": "feeSignatures", + "type": "bytes[]" + }, + { + "name": "feePercentage", + "type": "uint256" + }, + { + "name": "feeRecipient", + "type": "address" + } + ], + "name": "marketSellOrdersWithEth", + "outputs": [ + { + "components": [ + { + "name": "makerAssetFilledAmount", + "type": "uint256" + }, + { + "name": "takerAssetFilledAmount", + "type": "uint256" + }, + { + "name": "makerFeePaid", + "type": "uint256" + }, + { + "name": "takerFeePaid", + "type": "uint256" + } + ], + "name": "orderFillResults", + "type": "tuple" + }, + { + "components": [ + { + "name": "makerAssetFilledAmount", + "type": "uint256" + }, + { + "name": "takerAssetFilledAmount", + "type": "uint256" + }, + { + "name": "makerFeePaid", + "type": "uint256" + }, + { + "name": "takerFeePaid", + "type": "uint256" + } + ], + "name": "feeOrderFillResults", + "type": "tuple" + } + ], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "_exchange", + "type": "address" + }, + { + "name": "_zrxAssetData", + "type": "bytes" + }, + { + "name": "_wethAssetData", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "0x60806040523480156200001157600080fd5b5060405162002d2c38038062002d2c83398101806040526200003791908101906200051d565b6000805433600160a060020a031991821617825560018054909116600160a060020a0386161790558251849084908490849081906200007e906004906020870190620003d0565b50825162000094906005906020860190620003d0565b50620000b0836010640100000000620019476200036f82021704565b9150620000cd846010640100000000620019476200036f82021704565b60028054600160a060020a03948516600160a060020a031991821617909155600380549285169290911691909117905550600154604080517f4552433230546f6b656e28616464726573732900000000000000000000000000815290519081900360130181207f6070410800000000000000000000000000000000000000000000000000000000825291909216945063607041089350620001739250906004016200068e565b602060405180830381600087803b1580156200018e57600080fd5b505af1158015620001a3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620001c99190810190620004f4565b9050600160a060020a038116151562000219576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200021090620006b0565b60405180910390fd5b6002546040517f095ea7b3000000000000000000000000000000000000000000000000000000008152600160a060020a039091169063095ea7b39062000268908490600019906004016200066f565b602060405180830381600087803b1580156200028357600080fd5b505af115801562000298573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620002be9190810190620005a1565b506003546040517f095ea7b3000000000000000000000000000000000000000000000000000000008152600160a060020a039091169063095ea7b3906200030e908490600019906004016200066f565b602060405180830381600087803b1580156200032957600080fd5b505af11580156200033e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620003649190810190620005a1565b50505050506200077a565b600081601401835110151515620003b4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000210906200069e565b506014818301810151910190600160a060020a03165b92915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200041357805160ff191683800117855562000443565b8280016001018555821562000443579182015b828111156200044357825182559160200191906001019062000426565b506200045192915062000455565b5090565b6200047291905b808211156200045157600081556001016200045c565b90565b600062000483825162000711565b9392505050565b600062000483825162000742565b6000601f82018313620004aa57600080fd5b8151620004c1620004bb82620006e9565b620006c2565b91508082526020830160208301858383011115620004de57600080fd5b620004eb83828462000747565b50505092915050565b6000602082840312156200050757600080fd5b600062000515848462000475565b949350505050565b6000806000606084860312156200053357600080fd5b600062000541868662000475565b93505060208401516001604060020a038111156200055e57600080fd5b6200056c8682870162000498565b92505060408401516001604060020a038111156200058957600080fd5b620005978682870162000498565b9150509250925092565b600060208284031215620005b457600080fd5b60006200051584846200048a565b620005cd8162000711565b82525050565b620005cd816200071d565b602681527f475245415445525f4f525f455155414c5f544f5f32305f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b601881527f554e524547495354455245445f41535345545f50524f58590000000000000000602082015260400190565b620005cd8162000472565b604081016200067f8285620005c2565b62000483602083018462000664565b60208101620003ca8284620005d3565b60208082528101620003ca81620005de565b60208082528101620003ca8162000634565b6040518181016001604060020a0381118282101715620006e157600080fd5b604052919050565b60006001604060020a038211156200070057600080fd5b506020601f91909101601f19160190565b600160a060020a031690565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b151590565b60005b83811015620007645781810151838201526020016200074a565b8381111562000774576000848401525b50505050565b6125a2806200078a6000396000f30060806040526004361061006c5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166318978e8281146100c8578063630f1e6c146100f25780638da5cb5b146101125780639395525c14610134578063f2fde38b14610147575b60025473ffffffffffffffffffffffffffffffffffffffff1633146100c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612388565b60405180910390fd5b005b6100db6100d6366004611df1565b610167565b6040516100e9929190612488565b60405180910390f35b3480156100fe57600080fd5b506100c661010d366004611eec565b6102f7565b34801561011e57600080fd5b50610127610388565b6040516100e99190612337565b6100db610142366004611d0b565b6103a4565b34801561015357600080fd5b506100c6610162366004611ce5565b61050a565b61016f6119fa565b6101776119fa565b6000806101826105bb565b60048054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610100600188161502019095169490940493840181900481028201810190925282815261025c939092909183018282801561022d5780601f106102025761010080835404028352916020019161022d565b820191906000526020600020905b81548152906001019060200180831161021057829003601f168201915b50505050508c600081518110151561024157fe5b6020908102909101015161014001519063ffffffff61069616565b156102875761026c8b8b8b6107c3565b935061028084600001518560600151610ac1565b90506102ae565b6102928b8b8b610b03565b9350836060015191506102a68883896107c3565b845190935090505b6102c2846020015184602001518888610d15565b6102e98b60008151811015156102d457fe5b90602001906020020151610140015182610f29565b505097509795505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610348576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612438565b61038383838080601f01602080910402602001604051908101604052809392919081815260200183838082843750879450610f299350505050565b505050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b6103ac6119fa565b6103b46119fa565b60008060006103c16105bb565b60048054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101006001881615020190951694909404938401819004810282018101909252828152610441939092909183018282801561022d5780601f106102025761010080835404028352916020019161022d565b156104925761046a670de0b6b3a7640000610464670de0b6b3a76400008a611045565b3461108f565b92506104778b848c6110e7565b945061048b85600001518660600151610ac1565b90506104d6565b6104ad670d2f13f7789f0000670de0b6b3a76400003461108f565b92506104ba8b848c6110e7565b9450846060015191506104ce89838a6107c3565b855190945090505b6104ea856020015185602001518989610d15565b6104fc8b60008151811015156102d457fe5b505050965096945050505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461055b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612438565b73ffffffffffffffffffffffffffffffffffffffff8116156105b857600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b600034116105f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612398565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0e30db0346040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004016000604051808303818588803b15801561067b57600080fd5b505af115801561068f573d6000803e3d6000fd5b5050505050565b6000815183511480156107ba5750816040518082805190602001908083835b602083106106f257805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016106b5565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0180199092169116179052604051919093018190038120885190955088945090928392508401908083835b6020831061078757805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161074a565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902060001916145b90505b92915050565b6107cb6119fa565b60608060008060008060006107de6119fa565b8a15156107ea57610ab2565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561088e5780601f106108635761010080835404028352916020019161088e565b820191906000526020600020905b81548152906001019060200180831161087157829003601f168201915b505060058054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101006001881615020190951694909404938401819004810282018101909252828152969e509194509250840190508282801561093d5780601f106109125761010080835404028352916020019161093d565b820191906000526020600020905b81548152906001019060200180831161092057829003601f168201915b50505050509650600095508b519450600093505b838514610a7857878c8581518110151561096757fe5b6020908102909101015161014001528b5187908d908690811061098657fe5b60209081029091010151610160015261099f8b87610ac1565b9250610a068c858151811015156109b257fe5b9060200190602002015160a00151610a008e878151811015156109d157fe5b90602001906020020151608001518f888151811015156109ed57fe5b9060200190602002015160e00151610ac1565b8561128b565b9150610a418c85815181101515610a1957fe5b90602001906020020151838c87815181101515610a3257fe5b906020019060200201516112e6565b9050610a4d898261135e565b610a5f89600001518a60600151610ac1565b95508a8610610a6d57610a78565b600190930192610951565b8a861015610ab2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612418565b50505050505050509392505050565b600082821115610afd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123b8565b50900390565b610b0b6119fa565b606080600080600080610b1c6119fa565b60008b6000815181101515610b2d57fe5b6020908102919091018101516101400151600580546040805160026001841615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190931692909204601f8101869004860283018601909152808252929b5092909190830182828015610be55780601f10610bba57610100808354040283529160200191610be5565b820191906000526020600020905b815481529060010190602001808311610bc857829003601f168201915b505050505096508b519550600094505b848614610cdb57878c86815181101515610c0b57fe5b6020908102909101015161014001528b5187908d9087908110610c2a57fe5b6020908102909101015161016001528851610c46908c90610ac1565b9350610c898c86815181101515610c5957fe5b9060200190602002015160a001518d87815181101515610c7557fe5b90602001906020020151608001518661128b565b9250610cb58c86815181101515610c9c57fe5b90602001906020020151848c88815181101515610a3257fe5b9150610cc1898361135e565b5087518a8110610cd057610cdb565b600190940193610bf5565b8a811015610ab2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612418565b600080808066b1a2bc2ec50000861115610d5b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612448565b610d658888611045565b935034841115610da1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123a8565b610dab3485610ac1565b9250610dc086670de0b6b3a76400008a61108f565b915082821115610dfc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612428565b6000831115610f1f576002546040517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90911690632e1a7d4d90610e5b9086906004016124a4565b600060405180830381600087803b158015610e7557600080fd5b505af1158015610e89573d6000803e3d6000fd5b505050506000821115610edb5760405173ffffffffffffffffffffffffffffffffffffffff86169083156108fc029084906000818181858888f19350505050158015610ed9573d6000803e3d6000fd5b505b610ee58383610ac1565b90506000811115610f1f57604051339082156108fc029083906000818181858888f19350505050158015610f1d573d6000803e3d6000fd5b505b5050505050505050565b6000610f3b838263ffffffff6113c016565b604080517f4552433230546f6b656e28616464726573732900000000000000000000000000815290519081900360130190209091507fffffffff0000000000000000000000000000000000000000000000000000000080831691161415610fab57610fa6838361142d565b610383565b604080517f455243373231546f6b656e28616464726573732c75696e7432353629000000008152905190819003601c0190207fffffffff000000000000000000000000000000000000000000000000000000008281169116141561101357610fa6838361161b565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123f8565b600082820183811015611084576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123e8565b8091505b5092915050565b60008083116110ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123d8565b6110dd6110d78584611703565b8461175e565b90505b9392505050565b6110ef6119fa565b60608060008060006110ff6119fa565b89600081518110151561110e57fe5b6020908102919091018101516101400151600580546040805160026001841615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190931692909204601f8101869004860283018601909152808252929950929091908301828280156111c65780601f1061119b576101008083540402835291602001916111c6565b820191906000526020600020905b8154815290600101906020018083116111a957829003601f168201915b5050505050945089519350600092505b82841461127e57858a848151811015156111ec57fe5b602090810290910101516101400152895185908b908590811061120b57fe5b90602001906020020151610160018190525061122b898860200151610ac1565b91506112578a8481518110151561123e57fe5b90602001906020020151838a86815181101515610a3257fe5b9050611263878261135e565b602087015189116112735761127e565b6001909201916111d6565b5050505050509392505050565b60008083116112c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123d8565b6110dd6110d76112d68685611703565b6112e1866001610ac1565b611045565b6112ee6119fa565b606060006112fd868686611775565b600154815191935073ffffffffffffffffffffffffffffffffffffffff1691506080908390602082016000855af1801561135457825184526020830151602085015260408301516040850152606083015160608501525b5050509392505050565b8151815161136c9190611045565b8252602080830151908201516113829190611045565b60208301526040808301519082015161139b9190611045565b6040830152606080830151908201516113b49190611045565b60609092019190915250565b600081600401835110151515611402576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612468565b5001602001517fffffffff000000000000000000000000000000000000000000000000000000001690565b60008061144184601063ffffffff61194716565b604080517f7472616e7366657228616464726573732c75696e7432353629000000000000008152905190819003601901812091935073ffffffffffffffffffffffffffffffffffffffff8416919061149f903390879060240161236d565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931783525181519192909182919080838360005b8381101561154357818101518382015260200161152b565b50505050905090810190601f1680156115705780820380516001836020036101000a031916815260200191505b509150506000604051808303816000865af1925050508015156115bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612408565b3d156115dc575060003d602014156115dc5760206000803e506000515b801515611615576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612408565b50505050565b60008060018314611658576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612478565b61166984601063ffffffff61194716565b915061167c84602463ffffffff6119a816565b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff8316906323b872dd906116d590309033908690600401612345565b600060405180830381600087803b1580156116ef57600080fd5b505af1158015610f1f573d6000803e3d6000fd5b6000808315156117165760009150611088565b5082820282848281151561172657fe5b0414611084576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123e8565b600080828481151561176c57fe5b04949350505050565b604080517fb4be83d5000000000000000000000000000000000000000000000000000000006020808301919091526060602483018181528751608485019081528884015160a48601529488015160c48501529087015160e4840152608087015161010484015260a087015161012484015260c087015161014484015260e08701516101648401526101008701516101848401526101208701516101a4840152610140870180516101c485019081526101608901516101e4860152610180905251805161020485018190529394919384936044870192849261022489019291820191601f82010460005b8181101561187c57835185526020948501949093019260010161185e565b50505050818103610160808401919091528a0151805180835260209283019291820191601f82010460005b818110156118c55783518552602094850194909301926001016118a7565b50505089845250848103602093840190815288518083529093918201918981019190601f82010460005b8181101561190d5783518552602094850194909301926001016118ef565b5050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08883030188525060405250505050509392505050565b600081601401835110151515611989576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612458565b50016014015173ffffffffffffffffffffffffffffffffffffffff1690565b60006107ba83836000816020018351101515156119f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123c8565b50016020015190565b608060405190810160405280600081526020016000815260200160008152602001600081525090565b60006107ba8235612540565b6000601f82018313611a4057600080fd5b8135611a53611a4e826124d9565b6124b2565b81815260209384019390925082018360005b83811015611a915781358601611a7b8882611b41565b8452506020928301929190910190600101611a65565b5050505092915050565b6000601f82018313611aac57600080fd5b8135611aba611a4e826124d9565b81815260209384019390925082018360005b83811015611a915781358601611ae28882611b90565b8452506020928301929190910190600101611acc565b600080601f83018413611b0a57600080fd5b50813567ffffffffffffffff811115611b2257600080fd5b602083019150836001820283011115611b3a57600080fd5b9250929050565b6000601f82018313611b5257600080fd5b8135611b60611a4e826124fa565b91508082526020830160208301858383011115611b7c57600080fd5b611b8783828461255c565b50505092915050565b60006101808284031215611ba357600080fd5b611bae6101806124b2565b90506000611bbc8484611a23565b8252506020611bcd84848301611a23565b6020830152506040611be184828501611a23565b6040830152506060611bf584828501611a23565b6060830152506080611c0984828501611cd9565b60808301525060a0611c1d84828501611cd9565b60a08301525060c0611c3184828501611cd9565b60c08301525060e0611c4584828501611cd9565b60e083015250610100611c5a84828501611cd9565b61010083015250610120611c7084828501611cd9565b6101208301525061014082013567ffffffffffffffff811115611c9257600080fd5b611c9e84828501611b41565b6101408301525061016082013567ffffffffffffffff811115611cc057600080fd5b611ccc84828501611b41565b6101608301525092915050565b60006107ba8235612559565b600060208284031215611cf757600080fd5b6000611d038484611a23565b949350505050565b60008060008060008060c08789031215611d2457600080fd5b863567ffffffffffffffff811115611d3b57600080fd5b611d4789828a01611a9b565b965050602087013567ffffffffffffffff811115611d6457600080fd5b611d7089828a01611a2f565b955050604087013567ffffffffffffffff811115611d8d57600080fd5b611d9989828a01611a9b565b945050606087013567ffffffffffffffff811115611db657600080fd5b611dc289828a01611a2f565b9350506080611dd389828a01611cd9565b92505060a0611de489828a01611a23565b9150509295509295509295565b600080600080600080600060e0888a031215611e0c57600080fd5b873567ffffffffffffffff811115611e2357600080fd5b611e2f8a828b01611a9b565b9750506020611e408a828b01611cd9565b965050604088013567ffffffffffffffff811115611e5d57600080fd5b611e698a828b01611a2f565b955050606088013567ffffffffffffffff811115611e8657600080fd5b611e928a828b01611a9b565b945050608088013567ffffffffffffffff811115611eaf57600080fd5b611ebb8a828b01611a2f565b93505060a0611ecc8a828b01611cd9565b92505060c0611edd8a828b01611a23565b91505092959891949750929550565b600080600060408486031215611f0157600080fd5b833567ffffffffffffffff811115611f1857600080fd5b611f2486828701611af8565b93509350506020611f3786828701611cd9565b9150509250925092565b611f4a81612540565b82525050565b602381527f44454641554c545f46554e4354494f4e5f574554485f434f4e54524143545f4f60208201527f4e4c590000000000000000000000000000000000000000000000000000000000604082015260600190565b601181527f494e56414c49445f4d53475f56414c5545000000000000000000000000000000602082015260400190565b600d81527f4f564552534f4c445f5745544800000000000000000000000000000000000000602082015260400190565b601181527f55494e543235365f554e444552464c4f57000000000000000000000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f33325f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b601081527f4449564953494f4e5f42595f5a45524f00000000000000000000000000000000602082015260400190565b601081527f55494e543235365f4f564552464c4f5700000000000000000000000000000000602082015260400190565b601781527f554e535550504f525445445f41535345545f50524f5859000000000000000000602082015260400190565b600f81527f5452414e534645525f4641494c45440000000000000000000000000000000000602082015260400190565b601481527f434f4d504c4554455f46494c4c5f4641494c4544000000000000000000000000602082015260400190565b601a81527f494e53554646494349454e545f4554485f52454d41494e494e47000000000000602082015260400190565b601381527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000602082015260400190565b601881527f4645455f50455243454e544147455f544f4f5f4c415247450000000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f32305f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b602581527f475245415445525f4f525f455155414c5f544f5f345f4c454e4754485f52455160208201527f5549524544000000000000000000000000000000000000000000000000000000604082015260600190565b600e81527f494e56414c49445f414d4f554e54000000000000000000000000000000000000602082015260400190565b805160808301906122f9848261232e565b50602082015161230c602085018261232e565b50604082015161231f604085018261232e565b50606082015161161560608501825b611f4a81612559565b602081016107bd8284611f41565b606081016123538286611f41565b6123606020830185611f41565b611d03604083018461232e565b6040810161237b8285611f41565b6110e0602083018461232e565b602080825281016107bd81611f50565b602080825281016107bd81611fa6565b602080825281016107bd81611fd6565b602080825281016107bd81612006565b602080825281016107bd81612036565b602080825281016107bd8161208c565b602080825281016107bd816120bc565b602080825281016107bd816120ec565b602080825281016107bd8161211c565b602080825281016107bd8161214c565b602080825281016107bd8161217c565b602080825281016107bd816121ac565b602080825281016107bd816121dc565b602080825281016107bd8161220c565b602080825281016107bd81612262565b602080825281016107bd816122b8565b610100810161249782856122e8565b6110e060808301846122e8565b602081016107bd828461232e565b60405181810167ffffffffffffffff811182821017156124d157600080fd5b604052919050565b600067ffffffffffffffff8211156124f057600080fd5b5060209081020190565b600067ffffffffffffffff82111561251157600080fd5b506020601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160190565b73ffffffffffffffffffffffffffffffffffffffff1690565b90565b828183375060009101525600a265627a7a72305820d9f418f11e0f91f06f6f9d22924be0add925495eeb76a6388b5417adb505eeb36c6578706572696d656e74616cf50037" } } }, - "networks": {} -} + "networks": {} +}
\ No newline at end of file diff --git a/packages/contract-artifacts/package.json b/packages/contract-artifacts/package.json index 53f5d3c47..9c25e3ac5 100644 --- a/packages/contract-artifacts/package.json +++ b/packages/contract-artifacts/package.json @@ -1,6 +1,6 @@ { "name": "@0x/contract-artifacts", - "version": "1.0.1", + "version": "1.1.0", "engines": { "node": ">=6.12" }, diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index c3d986b4a..0346b79b2 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,5 +1,15 @@ [ { + "version": "3.0.1", + "changes": [ + { + "note": "Fix bug in `ForwarderWrapper` where `feeRecipientAddress` was not correctly normalized.", + "pr": 1178 + } + ], + "timestamp": 1541740904 + }, + { "version": "3.0.0", "changes": [ { diff --git a/packages/contract-wrappers/CHANGELOG.md b/packages/contract-wrappers/CHANGELOG.md index 6f3005831..1fe903dd9 100644 --- a/packages/contract-wrappers/CHANGELOG.md +++ b/packages/contract-wrappers/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.1 - _November 9, 2018_ + + * Fix bug in `ForwarderWrapper` where `feeRecipientAddress` was not correctly normalized. (#1178) + ## v3.0.0 - _October 18, 2018_ * Add optional validation to the forwarder wrapper methods @@ -15,6 +19,8 @@ CHANGELOG * Removed `setProvider` method in top-level `ContractWrapper` class and added new `unsubscribeAll` method. (#1105) * Some properties and methods have been renamed. For example, some methods that previously could throw no longer can, and so their names have been updated accordingly. (#1105) * Removed ContractNotFound errors. Checking for this error was somewhat ineffecient. Relevant methods/functions now return the default error from web3-wrapper, which we feel provides enough information. (#1105) + * Add `ForwarderWrapperError` to public interface (#1147) + * Add `ContractWrapperError.SignatureRequestDenied` to public interface (#1147) ## v2.0.2 - _October 4, 2018_ diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json index d427bb628..cb69bbd33 100644 --- a/packages/contract-wrappers/package.json +++ b/packages/contract-wrappers/package.json @@ -1,6 +1,6 @@ { "name": "@0x/contract-wrappers", - "version": "3.0.0", + "version": "3.0.1", "description": "Smart TS wrappers for 0x smart contracts", "keywords": [ "0xproject", @@ -13,7 +13,7 @@ "scripts": { "build": "tsc -b", "build:ci": "yarn build", - "lint": "tslint --project . --exclude **/src/contract_wrappers/**/* --exclude **/lib/**/*", + "lint": "tslint --format stylish --project . --exclude **/lib/**/*", "test:circleci": "run-s test:coverage", "test": "yarn run_mocha", "rebuild_and_test": "run-s build test", @@ -37,10 +37,10 @@ "node": ">=6.0.0" }, "devDependencies": { - "@0x/dev-utils": "^1.0.13", - "@0x/migrations": "^2.0.0", - "@0x/subproviders": "^2.1.0", - "@0x/tslint-config": "^1.0.9", + "@0x/dev-utils": "^1.0.14", + "@0x/migrations": "^2.0.1", + "@0x/subproviders": "^2.1.1", + "@0x/tslint-config": "^1.0.10", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "@types/node": "*", @@ -65,18 +65,18 @@ "web3-provider-engine": "14.0.6" }, "dependencies": { - "@0x/abi-gen-wrappers": "^1.0.1", - "@0x/assert": "^1.0.14", - "@0x/contract-addresses": "^1.0.1", - "@0x/contract-artifacts": "^1.0.1", - "@0x/fill-scenarios": "^1.0.8", - "@0x/json-schemas": "^2.0.0", - "@0x/order-utils": "^2.0.0", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", - "ethereum-types": "^1.1.1", + "@0x/abi-gen-wrappers": "^1.0.2", + "@0x/assert": "^1.0.15", + "@0x/contract-addresses": "^1.1.0", + "@0x/contract-artifacts": "^1.1.0", + "@0x/fill-scenarios": "^1.0.9", + "@0x/json-schemas": "^2.0.1", + "@0x/order-utils": "^2.0.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", + "ethereum-types": "^1.1.2", "ethereumjs-blockstream": "6.0.0", "ethereumjs-util": "^5.1.1", "ethers": "~4.0.4", diff --git a/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts index 7b11f35bc..749aaae10 100644 --- a/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts @@ -28,10 +28,10 @@ export abstract class ContractWrapper { protected _networkId: number; protected _web3Wrapper: Web3Wrapper; private _blockAndLogStreamerIfExists: BlockAndLogStreamer<Block, Log> | undefined; - private _blockPollingIntervalMs: number; + private readonly _blockPollingIntervalMs: number; private _blockAndLogStreamIntervalIfExists?: NodeJS.Timer; - private _filters: { [filterToken: string]: FilterObject }; - private _filterCallbacks: { + private readonly _filters: { [filterToken: string]: FilterObject }; + private readonly _filterCallbacks: { [filterToken: string]: EventCallback<ContractEventArgs>; }; private _onLogAddedSubscriptionToken: string | undefined; diff --git a/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts index adf8c7614..45460bd6d 100644 --- a/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/erc20_proxy_wrapper.ts @@ -34,6 +34,9 @@ export class ERC20ProxyWrapper extends ContractWrapper { */ public async getProxyIdAsync(): Promise<AssetProxyId> { const ERC20ProxyContractInstance = this._getERC20ProxyContract(); + // Note(albrow): Below is a TSLint false positive. Code won't compile if + // you remove the type assertion. + /* tslint:disable-next-line:no-unnecessary-type-assertion */ const proxyId = (await ERC20ProxyContractInstance.getProxyId.callAsync()) as AssetProxyId; return proxyId; } diff --git a/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts index f5fc63b42..5e0ec1951 100644 --- a/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/erc20_token_wrapper.ts @@ -18,12 +18,11 @@ import { } from '../types'; import { assert } from '../utils/assert'; import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; import { ContractWrapper } from './contract_wrapper'; import { ERC20ProxyWrapper } from './erc20_proxy_wrapper'; -const removeUndefinedProperties = _.pickBy; - /** * This class includes all the functionality related to interacting with ERC20 token contracts. * All ERC20 method calls are supported, along with some convenience methods for getting/setting allowances @@ -32,8 +31,8 @@ const removeUndefinedProperties = _.pickBy; export class ERC20TokenWrapper extends ContractWrapper { public abi: ContractAbi = ERC20Token.compilerOutput.abi; public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; - private _tokenContractsByAddress: { [address: string]: ERC20TokenContract }; - private _erc20ProxyWrapper: ERC20ProxyWrapper; + private readonly _tokenContractsByAddress: { [address: string]: ERC20TokenContract }; + private readonly _erc20ProxyWrapper: ERC20ProxyWrapper; /** * Instantiate ERC20TokenWrapper * @param web3Wrapper Web3Wrapper instance to use @@ -108,7 +107,7 @@ export class ERC20TokenWrapper extends ContractWrapper { const txHash = await tokenContract.approve.sendTransactionAsync( normalizedSpenderAddress, amountInBaseUnits, - removeUndefinedProperties({ + utils.removeUndefinedProperties({ from: normalizedOwnerAddress, gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, @@ -278,7 +277,7 @@ export class ERC20TokenWrapper extends ContractWrapper { const txHash = await tokenContract.transfer.sendTransactionAsync( normalizedToAddress, amountInBaseUnits, - removeUndefinedProperties({ + utils.removeUndefinedProperties({ from: normalizedFromAddress, gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, @@ -339,7 +338,7 @@ export class ERC20TokenWrapper extends ContractWrapper { normalizedFromAddress, normalizedToAddress, amountInBaseUnits, - removeUndefinedProperties({ + utils.removeUndefinedProperties({ from: normalizedSenderAddress, gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, diff --git a/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts index 9f3a6930b..12758e191 100644 --- a/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/erc721_proxy_wrapper.ts @@ -34,6 +34,9 @@ export class ERC721ProxyWrapper extends ContractWrapper { */ public async getProxyIdAsync(): Promise<AssetProxyId> { const ERC721ProxyContractInstance = await this._getERC721ProxyContract(); + // Note(albrow): Below is a TSLint false positive. Code won't compile if + // you remove the type assertion. + /* tslint:disable-next-line:no-unnecessary-type-assertion */ const proxyId = (await ERC721ProxyContractInstance.getProxyId.callAsync()) as AssetProxyId; return proxyId; } diff --git a/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts index 1c4b61c0b..1610af47b 100644 --- a/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/erc721_token_wrapper.ts @@ -18,12 +18,11 @@ import { } from '../types'; import { assert } from '../utils/assert'; import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; import { ContractWrapper } from './contract_wrapper'; import { ERC721ProxyWrapper } from './erc721_proxy_wrapper'; -const removeUndefinedProperties = _.pickBy; - /** * This class includes all the functionality related to interacting with ERC721 token contracts. * All ERC721 method calls are supported, along with some convenience methods for getting/setting allowances @@ -31,8 +30,8 @@ const removeUndefinedProperties = _.pickBy; */ export class ERC721TokenWrapper extends ContractWrapper { public abi: ContractAbi = ERC721Token.compilerOutput.abi; - private _tokenContractsByAddress: { [address: string]: ERC721TokenContract }; - private _erc721ProxyWrapper: ERC721ProxyWrapper; + private readonly _tokenContractsByAddress: { [address: string]: ERC721TokenContract }; + private readonly _erc721ProxyWrapper: ERC721ProxyWrapper; /** * Instantiate ERC721TokenWrapper * @param web3Wrapper Web3Wrapper instance to use @@ -235,7 +234,7 @@ export class ERC721TokenWrapper extends ContractWrapper { const txHash = await tokenContract.setApprovalForAll.sendTransactionAsync( normalizedOperatorAddress, isApproved, - removeUndefinedProperties({ + utils.removeUndefinedProperties({ gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, from: normalizedOwnerAddress, @@ -295,7 +294,7 @@ export class ERC721TokenWrapper extends ContractWrapper { const txHash = await tokenContract.approve.sendTransactionAsync( normalizedApprovedAddress, tokenId, - removeUndefinedProperties({ + utils.removeUndefinedProperties({ gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, from: tokenOwnerAddress, @@ -366,7 +365,7 @@ export class ERC721TokenWrapper extends ContractWrapper { ownerAddress, normalizedReceiverAddress, tokenId, - removeUndefinedProperties({ + utils.removeUndefinedProperties({ gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, from: normalizedSenderAddress, diff --git a/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts index d4a08da86..913c47cf7 100644 --- a/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts @@ -8,22 +8,21 @@ import * as _ from 'lodash'; import { BlockRange, ContractWrappersError, EventCallback, IndexedFilterValues, TransactionOpts } from '../types'; import { assert } from '../utils/assert'; +import { utils } from '../utils/utils'; import { ContractWrapper } from './contract_wrapper'; import { ERC20TokenWrapper } from './erc20_token_wrapper'; -const removeUndefinedProperties = _.pickBy; - /** * This class includes all the functionality related to interacting with a wrapped Ether ERC20 token contract. * The caller can convert ETH into the equivalent number of wrapped ETH ERC20 tokens and back. */ export class EtherTokenWrapper extends ContractWrapper { public abi: ContractAbi = WETH9.compilerOutput.abi; - private _etherTokenContractsByAddress: { + private readonly _etherTokenContractsByAddress: { [address: string]: WETH9Contract; } = {}; - private _erc20TokenWrapper: ERC20TokenWrapper; + private readonly _erc20TokenWrapper: ERC20TokenWrapper; /** * Instantiate EtherTokenWrapper. * @param web3Wrapper Web3Wrapper instance to use @@ -67,7 +66,7 @@ export class EtherTokenWrapper extends ContractWrapper { const wethContract = await this._getEtherTokenContractAsync(normalizedEtherTokenAddress); const txHash = await wethContract.deposit.sendTransactionAsync( - removeUndefinedProperties({ + utils.removeUndefinedProperties({ from: normalizedDepositorAddress, value: amountInWei, gas: txOpts.gasLimit, @@ -109,7 +108,7 @@ export class EtherTokenWrapper extends ContractWrapper { const wethContract = await this._getEtherTokenContractAsync(normalizedEtherTokenAddress); const txHash = await wethContract.withdraw.sendTransactionAsync( amountInWei, - removeUndefinedProperties({ + utils.removeUndefinedProperties({ from: normalizedWithdrawerAddress, gas: txOpts.gasLimit, gasPrice: txOpts.gasPrice, diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 907d25aa0..2e978f35b 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -46,8 +46,8 @@ export class ExchangeWrapper extends ContractWrapper { public address: string; public zrxTokenAddress: string; private _exchangeContractIfExists?: ExchangeContract; - private _erc721TokenWrapper: ERC721TokenWrapper; - private _erc20TokenWrapper: ERC20TokenWrapper; + private readonly _erc721TokenWrapper: ERC721TokenWrapper; + private readonly _erc20TokenWrapper: ERC20TokenWrapper; /** * Instantiate ExchangeWrapper * @param web3Wrapper Web3Wrapper instance to use. diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts index 9463a6849..80742e030 100644 --- a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -1,7 +1,7 @@ import { ForwarderContract } from '@0x/abi-gen-wrappers'; import { Forwarder } from '@0x/contract-artifacts'; import { schemas } from '@0x/json-schemas'; -import { AssetProxyId, SignedOrder } from '@0x/types'; +import { SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import { ContractAbi } from 'ethereum-types'; @@ -118,7 +118,7 @@ export class ForwarderWrapper extends ContractWrapper { optimizedFeeOrders, feeSignatures, formattedFeePercentage, - feeRecipientAddress, + normalizedFeeRecipientAddress, { value: ethAmount, from: normalizedTakerAddress, @@ -207,7 +207,7 @@ export class ForwarderWrapper extends ContractWrapper { optimizedFeeOrders, feeSignatures, formattedFeePercentage, - feeRecipientAddress, + normalizedFeeRecipientAddress, { value: ethAmount, from: normalizedTakerAddress, diff --git a/packages/contract-wrappers/src/utils/utils.ts b/packages/contract-wrappers/src/utils/utils.ts index fbacdaa28..0b3270e78 100644 --- a/packages/contract-wrappers/src/utils/utils.ts +++ b/packages/contract-wrappers/src/utils/utils.ts @@ -1,5 +1,6 @@ import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as _ from 'lodash'; import { constants } from './constants'; @@ -14,4 +15,7 @@ export const utils = { numberPercentageToEtherTokenAmountPercentage(percentage: number): BigNumber { return Web3Wrapper.toBaseUnitAmount(constants.ONE_AMOUNT, constants.ETHER_TOKEN_DECIMALS).mul(percentage); }, + removeUndefinedProperties<T extends object>(obj: T): Partial<T> { + return _.pickBy(obj); + }, }; diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 3a5f12c74..bf9bb4826 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "contracts", - "version": "2.1.50", + "version": "2.1.51", "engines": { "node": ">=6.12" }, @@ -23,7 +23,7 @@ "compile": "sol-compiler --contracts-dir contracts", "clean": "shx rm -rf lib generated-artifacts generated-wrappers", "generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers", - "lint": "tslint --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts", + "lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts", "coverage:report:text": "istanbul report text", "coverage:report:html": "istanbul report html && open coverage/index.html", "profiler:report:html": "istanbul report html && open coverage/index.html", @@ -45,12 +45,12 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/contracts/README.md", "devDependencies": { - "@0x/abi-gen": "^1.0.14", - "@0x/dev-utils": "^1.0.13", - "@0x/sol-compiler": "^1.1.8", - "@0x/sol-cov": "^2.1.8", - "@0x/subproviders": "^2.1.0", - "@0x/tslint-config": "^1.0.9", + "@0x/abi-gen": "^1.0.15", + "@0x/dev-utils": "^1.0.14", + "@0x/sol-compiler": "^1.1.9", + "@0x/sol-cov": "^2.1.9", + "@0x/subproviders": "^2.1.1", + "@0x/tslint-config": "^1.0.10", "@types/bn.js": "^4.11.0", "@types/ethereumjs-abi": "^0.6.0", "@types/lodash": "4.14.104", @@ -71,15 +71,15 @@ "yargs": "^10.0.3" }, "dependencies": { - "@0x/base-contract": "^3.0.2", - "@0x/order-utils": "^2.0.0", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/base-contract": "^3.0.3", + "@0x/order-utils": "^2.0.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "@types/js-combinatorics": "^0.5.29", "bn.js": "^4.11.8", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethereumjs-abi": "0.6.5", "ethereumjs-util": "^5.1.1", "ethers": "~4.0.4", diff --git a/packages/dev-tools-pages/package.json b/packages/dev-tools-pages/package.json index 3563b4bef..b1b89ac99 100644 --- a/packages/dev-tools-pages/package.json +++ b/packages/dev-tools-pages/package.json @@ -1,6 +1,6 @@ { "name": "@0x/dev-tools-pages", - "version": "0.0.2", + "version": "0.0.3", "engines": { "node": ">=6.12" }, @@ -11,12 +11,12 @@ "build:ci": "yarn build", "build:dev": "../../node_modules/.bin/webpack --mode development", "clean": "shx rm -f public/bundle*", - "lint": "tslint --project . 'ts/**/*.ts' 'ts/**/*.tsx'", + "lint": "tslint --format stylish --project . 'ts/**/*.ts' 'ts/**/*.tsx'", "dev": "webpack-dev-server --mode development --content-base public" }, "license": "Apache-2.0", "dependencies": { - "@0x/react-shared": "^1.0.17", + "@0x/react-shared": "^1.0.18", "basscss": "^8.0.3", "bowser": "^1.9.3", "less": "^2.7.2", diff --git a/packages/dev-utils/CHANGELOG.json b/packages/dev-utils/CHANGELOG.json index fd5a8d446..269cdc934 100644 --- a/packages/dev-utils/CHANGELOG.json +++ b/packages/dev-utils/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.0.14", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "1.0.13", "changes": [ { diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index a6b60884e..ee614a904 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.14 - _November 9, 2018_ + + * Dependencies updated + ## v1.0.13 - _October 18, 2018_ * Make web3-provider-engine types a 'dependency' so it's available to users of the library (#1105) diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index d1b1efc87..e1b3201d3 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@0x/dev-utils", - "version": "1.0.13", + "version": "1.0.14", "engines": { "node": ">=6.12" }, @@ -17,7 +17,7 @@ "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", "clean": "shx rm -rf lib", - "lint": "tslint --project ." + "lint": "tslint --format stylish --project ." }, "license": "Apache-2.0", "repository": { @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/dev-utils/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "make-promises-safe": "^1.1.0", @@ -41,14 +41,14 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/subproviders": "^2.1.0", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/subproviders": "^2.1.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "@types/web3-provider-engine": "^14.0.0", "chai": "^4.0.1", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "lodash": "^4.17.5" }, "publishConfig": { diff --git a/packages/ethereum-types/CHANGELOG.json b/packages/ethereum-types/CHANGELOG.json index b042c4588..9db75ae9f 100644 --- a/packages/ethereum-types/CHANGELOG.json +++ b/packages/ethereum-types/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.1.2", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "1.1.1", "changes": [ { diff --git a/packages/ethereum-types/CHANGELOG.md b/packages/ethereum-types/CHANGELOG.md index 73ea54204..6ad7b4cc6 100644 --- a/packages/ethereum-types/CHANGELOG.md +++ b/packages/ethereum-types/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.1.2 - _November 9, 2018_ + + * Dependencies updated + ## v1.1.1 - _October 18, 2018_ * Add `JSONRPCResponseError` and error field on `JSONRPCResponsePayload`. (#1102) diff --git a/packages/ethereum-types/package.json b/packages/ethereum-types/package.json index d24e39dfd..1630344db 100644 --- a/packages/ethereum-types/package.json +++ b/packages/ethereum-types/package.json @@ -1,6 +1,6 @@ { "name": "ethereum-types", - "version": "1.1.1", + "version": "1.1.2", "engines": { "node": ">=6.12" }, @@ -11,7 +11,7 @@ "build": "tsc -b", "build:ci": "yarn build", "clean": "shx rm -rf lib generated_docs", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" }, "config": { @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/ethereum-types/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", diff --git a/packages/fill-scenarios/CHANGELOG.json b/packages/fill-scenarios/CHANGELOG.json index b9840957e..8aa4e78d3 100644 --- a/packages/fill-scenarios/CHANGELOG.json +++ b/packages/fill-scenarios/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.0.9", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "1.0.8", "changes": [ { diff --git a/packages/fill-scenarios/CHANGELOG.md b/packages/fill-scenarios/CHANGELOG.md index 20ef1322c..4ff28f82f 100644 --- a/packages/fill-scenarios/CHANGELOG.md +++ b/packages/fill-scenarios/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.9 - _November 9, 2018_ + + * Dependencies updated + ## v1.0.8 - _October 18, 2018_ * Updated to use new @0xproject/contract-artifacts and @0xproject/abi-gen-wrappers packages (#1105) diff --git a/packages/fill-scenarios/package.json b/packages/fill-scenarios/package.json index 0e1303259..e9ca077fb 100644 --- a/packages/fill-scenarios/package.json +++ b/packages/fill-scenarios/package.json @@ -1,6 +1,6 @@ { "name": "@0x/fill-scenarios", - "version": "1.0.8", + "version": "1.0.9", "description": "0x order fill scenario generator", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -8,7 +8,7 @@ "build": "yarn tsc -b", "build:ci": "yarn build", "clean": "shx rm -rf lib src/generated_contract_wrappers", - "lint": "tslint --project ." + "lint": "tslint --format stylish --project ." }, "license": "Apache-2.0", "repository": { @@ -20,7 +20,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/fill-scenarios/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/lodash": "4.14.104", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", @@ -28,15 +28,15 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/abi-gen-wrappers": "^1.0.1", - "@0x/base-contract": "^3.0.2", - "@0x/contract-artifacts": "^1.0.1", - "@0x/order-utils": "^2.0.0", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", - "ethereum-types": "^1.1.1", + "@0x/abi-gen-wrappers": "^1.0.2", + "@0x/base-contract": "^3.0.3", + "@0x/contract-artifacts": "^1.1.0", + "@0x/order-utils": "^2.0.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", + "ethereum-types": "^1.1.2", "ethers": "~4.0.4", "lodash": "^4.17.5" }, diff --git a/packages/instant/.discharge.json b/packages/instant/.dogfood.discharge.json index 9ade97d01..9ade97d01 100644 --- a/packages/instant/.discharge.json +++ b/packages/instant/.dogfood.discharge.json diff --git a/packages/instant/.staging.discharge.json b/packages/instant/.staging.discharge.json new file mode 100644 index 000000000..1026b9986 --- /dev/null +++ b/packages/instant/.staging.discharge.json @@ -0,0 +1,13 @@ +{ + "domain": "0x-instant-staging", + "build_command": "yarn build:umd:prod", + "upload_directory": "public", + "index_key": "index.html", + "error_key": "index.html", + "trailing_slashes": true, + "cache": 3600, + "aws_profile": "default", + "aws_region": "us-east-1", + "cdn": false, + "dns_configured": true +} diff --git a/packages/instant/README.md b/packages/instant/README.md index 07b01ac95..b83a10508 100644 --- a/packages/instant/README.md +++ b/packages/instant/README.md @@ -53,7 +53,15 @@ You can deploy a work-in-progress version of 0x Instant at http://0x-instant-dog To build and deploy the site run ``` -yarn deploy +yarn deploy_dogfood +``` + +We also have a staging bucket that is to be updated less frequently can be used to share instant externally: http://0x-instant-staging.s3-website-us-east-1.amazonaws.com/ + +To build and deploy to this bucket, run + +``` +yarn deploy_staging ``` **NOTE: On deploying the site, it will say the site is available at a non-existent URL. Please ignore and use the (now updated) URL above.** diff --git a/packages/instant/package.json b/packages/instant/package.json index 421802530..3c87743c1 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -1,6 +1,6 @@ { "name": "@0x/instant", - "version": "0.0.3", + "version": "0.0.4", "engines": { "node": ">=6.12" }, @@ -16,13 +16,14 @@ "build:ci": "yarn build", "watch_without_deps": "tsc -w", "dev": "webpack-dev-server --mode development", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "test": "jest", "test:coverage": "jest --coverage", "rebuild_and_test": "run-s clean build test", "test:circleci": "yarn test:coverage", "clean": "shx rm -rf lib coverage scripts", - "deploy": "discharge deploy", + "deploy_dogfood": "discharge deploy -c .dogfood.discharge.json", + "deploy_staging": "discharge deploy -c .staging.discharge.json", "manual:postpublish": "yarn build; node ./scripts/postpublish.js" }, "config": { @@ -44,13 +45,17 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md", "dependencies": { - "@0x/asset-buyer": "^2.1.0", - "@0x/order-utils": "^2.0.0", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", - "ethereum-types": "^1.1.1", + "@0x/assert": "^1.0.15", + "@0x/asset-buyer": "^2.2.0", + "@0x/json-schemas": "^2.0.1", + "@0x/order-utils": "^2.0.1", + "@0x/subproviders": "^2.1.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", + "copy-to-clipboard": "^3.0.8", + "ethereum-types": "^1.1.2", "lodash": "^4.17.10", "polished": "^2.2.0", "react": "^16.5.2", @@ -58,12 +63,12 @@ "react-redux": "^5.0.7", "redux": "^4.0.0", "redux-devtools-extension": "^2.13.5", - "styled-components": "^3.4.9", + "styled-components": "^4.0.2", "ts-optchain": "^0.1.1" }, "devDependencies": { - "@0x/tslint-config": "^1.0.9", - "@static/discharge": "^1.2.2", + "@0x/tslint-config": "^1.0.10", + "@static/discharge": "https://github.com/0xProject/discharge.git", "@types/enzyme": "^3.1.14", "@types/enzyme-adapter-react-16": "^1.0.3", "@types/jest": "^23.3.5", @@ -73,6 +78,7 @@ "@types/react-dom": "^16.0.8", "@types/react-redux": "^6.0.9", "@types/redux": "^3.6.0", + "@types/styled-components": "^4.0.1", "awesome-typescript-loader": "^5.2.1", "enzyme": "^3.6.0", "enzyme-adapter-react-16": "^1.5.0", diff --git a/packages/instant/public/external.css b/packages/instant/public/external.css new file mode 100644 index 000000000..cab11112a --- /dev/null +++ b/packages/instant/public/external.css @@ -0,0 +1,25 @@ +/* + CSS file meant to represent an external (integrators) stylesheet and + help ensure that instant looks consistent across environments. +*/ + +button { + font-size: 50px; + height: 200px; + background-color: red; +} + +input { + padding: 100px; + font-size: 50px; + height: 100px; +} + +div { + padding: 3px; +} + +p { + background-color: green; + margin: 10px; +} diff --git a/packages/instant/public/index.html b/packages/instant/public/index.html index 14555fc64..f6c809e33 100644 --- a/packages/instant/public/index.html +++ b/packages/instant/public/index.html @@ -5,7 +5,10 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>0x Instant Dev Environment</title> + <link rel="stylesheet" href="/external.css"> <script type="text/javascript" src="/main.bundle.js" charset="utf-8"></script> + <script type="text/javascript" src="https://unpkg.com/jsuri@1.3.1/Uri.js" charset="utf-8"></script> + <script type="text/javascript" src="https://unpkg.com/bignumber.js@4.1.0/bignumber.js" charset="utf-8"></script> <style> #zeroExInstantContainer { display: flex; @@ -24,10 +27,110 @@ <body> <div id="zeroExInstantContainer"></div> <script> - zeroExInstant.render({ - liquiditySource: 'https://api.radarrelay.com/0x/v2/', - assetData: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498', + const removeUndefined = (obj) => { + for (let k in obj) if (obj[k] === undefined) delete obj[k]; + return obj; + } + BigNumber.config({ + EXPONENTIAL_AT: 1000, + DECIMAL_PLACES: 78, }); + const providedOrders = [ + // Order selling REP + { + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', + takerAddress: '0x0000000000000000000000000000000000000000', + makerFee: new BigNumber('0'), + takerFee: new BigNumber('0'), + makerAssetAmount: new BigNumber('200000000000000000000'), + takerAssetAmount: new BigNumber('10000000000000000000'), + makerAssetData: '0xf47261b00000000000000000000000008cb3971b8eb709c14616bd556ff6683019e90d9c', + takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', + expirationTimeSeconds: new BigNumber('1601535600'), + feeRecipientAddress: '0x0000000000000000000000000000000000000000', + salt: new BigNumber('3101985707338942582579795423923841749956600670712030922928319824580764688653'), + signature: '0x1bd4d5686fea801fe33c68c4944356085e7e6cb553eb7073160abd815609f714e85fb47f44b7ffd0a2a1321ac40d72d55163869d0a50fdb5a402132150fe33a08403', + exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2' + }, + // Order selling ZRX + { + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', + takerAddress: '0x0000000000000000000000000000000000000000', + makerFee: new BigNumber('0'), + takerFee: new BigNumber('0'), + makerAssetAmount: new BigNumber('300000000000000000000'), + takerAssetAmount: new BigNumber('31000000000000000000'), + makerAssetData: '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa', + takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', + expirationTimeSeconds: new BigNumber('2524636800'), + feeRecipientAddress: '0x0000000000000000000000000000000000000000', + salt: new BigNumber('64592004666704945574675477805199411288137454783320798602050822322450089238268'), + signature: '0x1c13cacddca8d7d8248e91f412377e68f8f1f9891a59a6c1b2eea9f7b33558c30c4fb86a448e08ab7def40a28fb3a3062dcb33bb3c45302447fce5c4288b7c7f5b03', + exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2' + }, + // Order selling GNT + { + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', + takerAddress: '0x0000000000000000000000000000000000000000', + makerFee: new BigNumber('0'), + takerFee: new BigNumber('0'), + makerAssetAmount: new BigNumber('250000000000000000000'), + takerAssetAmount: new BigNumber('10000000000000000000'), + makerAssetData: '0xf47261b000000000000000000000000031fb614e223706f15d0d3c5f4b08bdf0d5c78623', + takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', + expirationTimeSeconds: new BigNumber('1601535600'), + feeRecipientAddress: '0x0000000000000000000000000000000000000000', + salt: new BigNumber('40204378562212615907903051460421336779451270522691667164301816101569427926606'), + signature: '0x1c788bf4b93769da1e8f195f52f0f59b4a298ac6da30cf6d05a87ed4be5ee974f61352ed1bc6a0844d0962b8c894c9ca08e452431255958a4e98dd93cbe1fbc73803', + exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2' + }, + // Order selling MKR + { + senderAddress: '0x0000000000000000000000000000000000000000', + makerAddress: '0x34a745008a643eebc58920eaa29fb1165b4a288e', + takerAddress: '0x0000000000000000000000000000000000000000', + makerFee: new BigNumber('0'), + takerFee: new BigNumber('0'), + makerAssetAmount: new BigNumber('200000000000000000000'), + takerAssetAmount: new BigNumber('5000000000000000000'), + makerAssetData: '0xf47261b00000000000000000000000007b6b10caa9e8e9552ba72638ea5b47c25afea1f3', + takerAssetData: '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c', + expirationTimeSeconds: new BigNumber('1601535600'), + feeRecipientAddress: '0x0000000000000000000000000000000000000000', + salt: new BigNumber('71338269924068280039932133924198049371838034090153601678083172009862985793828'), + signature: '0x1bb3151d57ee1e8fa697767ce83ee4ba77d1ceb8cc1e79c7d77126b3687517704c50c6b3d9cb42c7e7d4478d574b297dfbd1626c5c18a7bc9c2a792c4c07f0797c03', + exchangeAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2' + } + ]; + const queryParams = new Uri(window.location.search); + const renderOptionsDefaults = { + orderSource: 'https://api.radarrelay.com/0x/v2/', + onClose: () => { console.log('0x Instant Closed') } + } + const orderSourceOverride = queryParams.getQueryParamValue('orderSource'); + const availableAssetDatasString = queryParams.getQueryParamValue('availableAssetDatas'); + const feeRecipientOverride = queryParams.getQueryParamValue('feeRecipient'); + const feePercentageOverride = +queryParams.getQueryParamValue('feePercentage'); + let affiliateInfoOverride; + if (feeRecipientOverride !== undefined && feePercentageOverride !== undefined) { + affiliateInfoOverride = { + feeRecipient: feeRecipientOverride, + feePercentage: feePercentageOverride + }; + } + const renderOptionsOverrides = { + orderSource: orderSourceOverride === 'provided' ? providedOrders : orderSourceOverride, + networkId: +queryParams.getQueryParamValue('networkId') || undefined, + defaultAssetBuyAmount: +queryParams.getQueryParamValue('defaultAssetBuyAmount') || undefined, + availableAssetDatas: availableAssetDatasString ? JSON.parse(availableAssetDatasString) : undefined, + defaultSelectedAssetData: queryParams.getQueryParamValue('defaultSelectedAssetData'), + affiliateInfo: affiliateInfoOverride, + } + const renderOptions = Object.assign({}, renderOptionsDefaults, removeUndefined(renderOptionsOverrides)); + zeroExInstant.render(renderOptions); </script> </body> diff --git a/packages/instant/src/components/amount_input.tsx b/packages/instant/src/components/amount_input.tsx deleted file mode 100644 index c89fb05ad..000000000 --- a/packages/instant/src/components/amount_input.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { BigNumber } from '@0x/utils'; -import * as _ from 'lodash'; -import * as React from 'react'; - -import { ColorOption } from '../style/theme'; -import { util } from '../util/util'; - -import { Container, Input } from './ui'; - -export interface AmountInputProps { - fontColor?: ColorOption; - fontSize?: string; - value?: BigNumber; - onChange: (value?: BigNumber) => void; -} - -export class AmountInput extends React.Component<AmountInputProps> { - public static defaultProps = { - onChange: util.boundNoop, - }; - public render(): React.ReactNode { - const { fontColor, fontSize, value } = this.props; - return ( - <Container borderBottom="1px solid rgba(255,255,255,0.3)" display="inline-block"> - <Input - fontColor={fontColor} - fontSize={fontSize} - onChange={this._handleChange} - value={!_.isUndefined(value) ? value.toString() : ''} - placeholder="0.00" - width="2.2em" - /> - </Container> - ); - } - private readonly _handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => { - const value = event.target.value; - let bigNumberValue; - if (!_.isEmpty(value)) { - try { - bigNumberValue = new BigNumber(event.target.value); - } catch { - // We don't want to allow values that can't be a BigNumber, so don't even call onChange. - return; - } - } - this.props.onChange(bigNumberValue); - }; -} diff --git a/packages/instant/src/components/amount_placeholder.tsx b/packages/instant/src/components/amount_placeholder.tsx index 6ef8f0ac3..29ce8fafb 100644 --- a/packages/instant/src/components/amount_placeholder.tsx +++ b/packages/instant/src/components/amount_placeholder.tsx @@ -4,7 +4,7 @@ import { ColorOption } from '../style/theme'; import { Pulse } from './animations/pulse'; -import { Text } from './ui'; +import { Text } from './ui/text'; interface PlainPlaceholder { color: ColorOption; diff --git a/packages/instant/src/components/animations/position_animation.tsx b/packages/instant/src/components/animations/position_animation.tsx new file mode 100644 index 000000000..8b3b294b7 --- /dev/null +++ b/packages/instant/src/components/animations/position_animation.tsx @@ -0,0 +1,110 @@ +import { InterpolationValue } from 'styled-components'; + +import { media, OptionallyScreenSpecific, stylesForMedia } from '../../style/media'; +import { css, keyframes, styled } from '../../style/theme'; + +export interface TransitionInfo { + from: string; + to: string; +} + +const generateTransitionInfoCss = ( + key: keyof TransitionInfo, + top?: TransitionInfo, + bottom?: TransitionInfo, + left?: TransitionInfo, + right?: TransitionInfo, +): string => { + const topStringIfExists = top ? `top: ${top[key]};` : ''; + const bottomStringIfExists = bottom ? `bottom: ${bottom[key]};` : ''; + const leftStringIfExists = left ? `left: ${left[key]};` : ''; + const rightStringIfExists = right ? `right: ${right[key]};` : ''; + return ` + ${topStringIfExists} + ${bottomStringIfExists} + ${leftStringIfExists} + ${rightStringIfExists} + `; +}; + +const slideKeyframeGenerator = ( + position: string, + top?: TransitionInfo, + bottom?: TransitionInfo, + left?: TransitionInfo, + right?: TransitionInfo, +) => keyframes` + from { + position: ${position}; + ${generateTransitionInfoCss('from', top, bottom, left, right)} + } + + to { + position: ${position}; + ${generateTransitionInfoCss('to', top, bottom, left, right)} + } +`; + +export interface PositionAnimationSettings { + top?: TransitionInfo; + bottom?: TransitionInfo; + left?: TransitionInfo; + right?: TransitionInfo; + timingFunction: string; + duration?: string; + position?: string; +} + +const generatePositionAnimationCss = (positionSettings: PositionAnimationSettings) => { + return css` + animation-name: ${slideKeyframeGenerator( + positionSettings.position || 'relative', + positionSettings.top, + positionSettings.bottom, + positionSettings.left, + positionSettings.right, + )}; + animation-duration: ${positionSettings.duration || '0.3s'}; + animation-timing-function: ${positionSettings.timingFunction}; + animation-delay: 0s; + animation-iteration-count: 1; + animation-fill-mode: forwards; + position: ${positionSettings.position || 'relative'}; + width: 100%; + `; +}; + +export interface PositionAnimationProps { + positionSettings: OptionallyScreenSpecific<PositionAnimationSettings>; + zIndex?: OptionallyScreenSpecific<number>; + height?: string; +} + +const defaultAnimation = (positionSettings: OptionallyScreenSpecific<PositionAnimationSettings>) => { + const bestDefault = 'default' in positionSettings ? positionSettings.default : positionSettings; + return generatePositionAnimationCss(bestDefault); +}; +const animationForSize = ( + positionSettings: OptionallyScreenSpecific<PositionAnimationSettings>, + sizeKey: 'sm' | 'md' | 'lg', + mediaFn: (...args: any[]) => InterpolationValue[], +) => { + // checking default makes sure we have a PositionAnimationSettings object + // and then we check to see if we have a setting for the specific `sizeKey` + const animationSettingsForSize = 'default' in positionSettings && positionSettings[sizeKey]; + return animationSettingsForSize && mediaFn`${generatePositionAnimationCss(animationSettingsForSize)}`; +}; + +export const PositionAnimation = + styled.div < + PositionAnimationProps > + ` + && { + ${props => props.zIndex && stylesForMedia<number>('z-index', props.zIndex)} + ${props => defaultAnimation(props.positionSettings)} + ${props => animationForSize(props.positionSettings, 'sm', media.small)} + ${props => animationForSize(props.positionSettings, 'md', media.medium)} + ${props => animationForSize(props.positionSettings, 'lg', media.large)} + ${props => (props.height ? `height: ${props.height};` : '')} + } +`; diff --git a/packages/instant/src/components/animations/slide_animation.tsx b/packages/instant/src/components/animations/slide_animation.tsx new file mode 100644 index 000000000..9adb1c674 --- /dev/null +++ b/packages/instant/src/components/animations/slide_animation.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; + +import { OptionallyScreenSpecific } from '../../style/media'; + +import { PositionAnimation, PositionAnimationSettings } from './position_animation'; + +export type SlideAnimationState = 'slidIn' | 'slidOut' | 'none'; +export interface SlideAnimationProps { + animationState: SlideAnimationState; + slideInSettings: OptionallyScreenSpecific<PositionAnimationSettings>; + slideOutSettings: OptionallyScreenSpecific<PositionAnimationSettings>; + zIndex?: OptionallyScreenSpecific<number>; + height?: string; +} + +export const SlideAnimation: React.StatelessComponent<SlideAnimationProps> = props => { + if (props.animationState === 'none') { + return <React.Fragment>{props.children}</React.Fragment>; + } + const positionSettings = props.animationState === 'slidIn' ? props.slideInSettings : props.slideOutSettings; + return ( + <PositionAnimation height={props.height} positionSettings={positionSettings} zIndex={props.zIndex}> + {props.children} + </PositionAnimation> + ); +}; diff --git a/packages/instant/src/components/animations/slide_animations.tsx b/packages/instant/src/components/animations/slide_animations.tsx deleted file mode 100644 index 1f10a2ed6..000000000 --- a/packages/instant/src/components/animations/slide_animations.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import * as React from 'react'; - -import { keyframes, styled } from '../../style/theme'; - -const slideKeyframeGenerator = (fromY: string, toY: string) => keyframes` - from { - position: relative; - top: ${fromY}; - } - - to { - position: relative; - top: ${toY}; - } -`; - -export interface SlideAnimationProps { - keyframes: string; - animationType: string; - animationDirection?: string; -} - -export const SlideAnimation = - styled.div < - SlideAnimationProps > - ` - animation-name: ${props => props.keyframes}; - animation-duration: 0.3s; - animation-timing-function: ${props => props.animationType}; - animation-delay: 0s; - animation-iteration-count: 1; - animation-fill-mode: ${props => props.animationDirection || 'none'}; - position: relative; -`; - -export interface SlideAnimationComponentProps { - downY: string; -} - -export const SlideUpAnimation: React.StatelessComponent<SlideAnimationComponentProps> = props => ( - <SlideAnimation animationType="ease-in" keyframes={slideKeyframeGenerator(props.downY, '0px')}> - {props.children} - </SlideAnimation> -); - -export const SlideDownAnimation: React.StatelessComponent<SlideAnimationComponentProps> = props => ( - <SlideAnimation - animationDirection="forwards" - animationType="cubic-bezier(0.25, 0.1, 0.25, 1)" - keyframes={slideKeyframeGenerator('0px', props.downY)} - > - {props.children} - </SlideAnimation> -); diff --git a/packages/instant/src/components/asset_amount_input.tsx b/packages/instant/src/components/asset_amount_input.tsx deleted file mode 100644 index c03ef1cf3..000000000 --- a/packages/instant/src/components/asset_amount_input.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { BigNumber } from '@0x/utils'; -import * as _ from 'lodash'; -import * as React from 'react'; - -import { ColorOption } from '../style/theme'; -import { ERC20Asset } from '../types'; -import { assetUtils } from '../util/asset'; -import { util } from '../util/util'; - -import { AmountInput, AmountInputProps } from './amount_input'; -import { Container, Text } from './ui'; - -// Asset amounts only apply to ERC20 assets -export interface AssetAmountInputProps extends AmountInputProps { - asset?: ERC20Asset; - onChange: (value?: BigNumber, asset?: ERC20Asset) => void; -} - -export class AssetAmountInput extends React.Component<AssetAmountInputProps> { - public static defaultProps = { - onChange: util.boundNoop, - }; - public render(): React.ReactNode { - const { asset, onChange, ...rest } = this.props; - return ( - <Container> - <AmountInput {...rest} onChange={this._handleChange} /> - <Container display="inline-block" marginLeft="10px"> - <Text fontSize={rest.fontSize} fontColor={ColorOption.white} textTransform="uppercase"> - {assetUtils.bestNameForAsset(asset)} - </Text> - </Container> - </Container> - ); - } - private readonly _handleChange = (value?: BigNumber): void => { - this.props.onChange(value, this.props.asset); - }; -} diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx index a70269dde..877ab275c 100644 --- a/packages/instant/src/components/buy_button.tsx +++ b/packages/instant/src/components/buy_button.tsx @@ -1,20 +1,29 @@ import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; import * as React from 'react'; +import { oc } from 'ts-optchain'; import { WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX } from '../constants'; import { ColorOption } from '../style/theme'; +import { AffiliateInfo, ZeroExInstantError } from '../types'; +import { gasPriceEstimator } from '../util/gas_price_estimator'; import { util } from '../util/util'; -import { web3Wrapper } from '../util/web3_wrapper'; -import { Button, Text } from './ui'; +import { Button } from './ui/button'; export interface BuyButtonProps { + accountAddress?: string; + accountEthBalanceInWei?: BigNumber; buyQuote?: BuyQuote; - assetBuyer?: AssetBuyer; - onAwaitingSignature: (buyQuote: BuyQuote) => void; - onSignatureDenied: (buyQuote: BuyQuote, preventedError: Error) => void; - onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => void; + assetBuyer: AssetBuyer; + web3Wrapper: Web3Wrapper; + affiliateInfo?: AffiliateInfo; + onValidationPending: (buyQuote: BuyQuote) => void; + onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void; + onSignatureDenied: (buyQuote: BuyQuote) => void; + onBuyProcessing: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => void; onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void; onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void; } @@ -26,35 +35,58 @@ export class BuyButton extends React.Component<BuyButtonProps> { onBuyFailure: util.boundNoop, }; public render(): React.ReactNode { - const shouldDisableButton = _.isUndefined(this.props.buyQuote) || _.isUndefined(this.props.assetBuyer); + const { buyQuote, accountAddress } = this.props; + const shouldDisableButton = _.isUndefined(buyQuote) || _.isUndefined(accountAddress); return ( - <Button width="100%" onClick={this._handleClick} isDisabled={shouldDisableButton}> - <Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px"> - Buy - </Text> + <Button + width="100%" + onClick={this._handleClick} + isDisabled={shouldDisableButton} + fontColor={ColorOption.white} + fontSize="20px" + > + Buy </Button> ); } private readonly _handleClick = async () => { // The button is disabled when there is no buy quote anyway. - const { buyQuote, assetBuyer } = this.props; - if (_.isUndefined(buyQuote) || _.isUndefined(assetBuyer)) { + const { buyQuote, assetBuyer, affiliateInfo, accountAddress, accountEthBalanceInWei, web3Wrapper } = this.props; + if (_.isUndefined(buyQuote) || _.isUndefined(accountAddress)) { + return; + } + this.props.onValidationPending(buyQuote); + const ethNeededForBuy = buyQuote.worstCaseQuoteInfo.totalEthAmount; + // if we don't have a balance for the user, let the transaction through, it will be handled by the wallet + const hasSufficientEth = _.isUndefined(accountEthBalanceInWei) || accountEthBalanceInWei.gte(ethNeededForBuy); + if (!hasSufficientEth) { + this.props.onValidationFail(buyQuote, ZeroExInstantError.InsufficientETH); return; } - let txHash: string | undefined; - this.props.onAwaitingSignature(buyQuote); + const gasInfo = await gasPriceEstimator.getGasInfoAsync(); + const feeRecipient = oc(affiliateInfo).feeRecipient(); try { - txHash = await assetBuyer.executeBuyQuoteAsync(buyQuote); + txHash = await assetBuyer.executeBuyQuoteAsync(buyQuote, { + feeRecipient, + takerAddress: accountAddress, + gasPrice: gasInfo.gasPriceInWei, + }); } catch (e) { - if (e instanceof Error && e.message === AssetBuyerError.SignatureRequestDenied) { - this.props.onSignatureDenied(buyQuote, e); - return; + if (e instanceof Error) { + if (e.message === AssetBuyerError.SignatureRequestDenied) { + this.props.onSignatureDenied(buyQuote); + return; + } else if (e.message === AssetBuyerError.TransactionValueTooLow) { + this.props.onValidationFail(buyQuote, AssetBuyerError.TransactionValueTooLow); + return; + } } throw e; } - - this.props.onBuyProcessing(buyQuote, txHash); + const startTimeUnix = new Date().getTime(); + const expectedEndTimeUnix = startTimeUnix + gasInfo.estimatedTimeMs; + this.props.onBuyProcessing(buyQuote, txHash, startTimeUnix, expectedEndTimeUnix); try { await web3Wrapper.awaitTransactionSuccessAsync(txHash); } catch (e) { diff --git a/packages/instant/src/components/buy_order_progress.tsx b/packages/instant/src/components/buy_order_progress.tsx new file mode 100644 index 000000000..bc7319423 --- /dev/null +++ b/packages/instant/src/components/buy_order_progress.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; + +import { TimedProgressBar } from '../components/timed_progress_bar'; + +import { TimeCounter } from '../components/time_counter'; +import { Container } from '../components/ui/container'; +import { OrderProcessState, OrderState } from '../types'; + +export interface BuyOrderProgressProps { + buyOrderState: OrderState; +} + +export const BuyOrderProgress: React.StatelessComponent<BuyOrderProgressProps> = props => { + const { buyOrderState } = props; + + if ( + buyOrderState.processState === OrderProcessState.Processing || + buyOrderState.processState === OrderProcessState.Success || + buyOrderState.processState === OrderProcessState.Failure + ) { + const progress = buyOrderState.progress; + const hasEnded = buyOrderState.processState !== OrderProcessState.Processing; + const expectedTimeMs = progress.expectedEndTimeUnix - progress.startTimeUnix; + return ( + <Container padding="20px 20px 0px 20px" width="100%"> + <Container marginBottom="5px"> + <TimeCounter estimatedTimeMs={expectedTimeMs} hasEnded={hasEnded} key={progress.startTimeUnix} /> + </Container> + <TimedProgressBar expectedTimeMs={expectedTimeMs} hasEnded={hasEnded} key={progress.startTimeUnix} /> + </Container> + ); + } + + return null; +}; diff --git a/packages/instant/src/components/buy_order_state_button.tsx b/packages/instant/src/components/buy_order_state_button.tsx deleted file mode 100644 index 44115e5a1..000000000 --- a/packages/instant/src/components/buy_order_state_button.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react'; - -import { PlacingOrderButton } from '../components/placing_order_button'; -import { SelectedAssetBuyButton } from '../containers/selected_asset_buy_button'; -import { SelectedAssetRetryButton } from '../containers/selected_asset_retry_button'; -import { SelectedAssetViewTransactionButton } from '../containers/selected_asset_view_transaction_button'; -import { OrderProcessState } from '../types'; - -export interface BuyOrderStateButtonProps { - buyOrderProcessingState: OrderProcessState; -} - -export const BuyOrderStateButton: React.StatelessComponent<BuyOrderStateButtonProps> = props => { - if (props.buyOrderProcessingState === OrderProcessState.FAILURE) { - return <SelectedAssetRetryButton />; - } else if ( - props.buyOrderProcessingState === OrderProcessState.SUCCESS || - props.buyOrderProcessingState === OrderProcessState.PROCESSING - ) { - return <SelectedAssetViewTransactionButton />; - } else if (props.buyOrderProcessingState === OrderProcessState.AWAITING_SIGNATURE) { - return <PlacingOrderButton />; - } - - return <SelectedAssetBuyButton />; -}; diff --git a/packages/instant/src/components/buy_order_state_buttons.tsx b/packages/instant/src/components/buy_order_state_buttons.tsx new file mode 100644 index 000000000..6041bf4f5 --- /dev/null +++ b/packages/instant/src/components/buy_order_state_buttons.tsx @@ -0,0 +1,71 @@ +import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; +import { AffiliateInfo, OrderProcessState, ZeroExInstantError } from '../types'; + +import { BuyButton } from './buy_button'; +import { PlacingOrderButton } from './placing_order_button'; +import { SecondaryButton } from './secondary_button'; + +import { Button } from './ui/button'; +import { Flex } from './ui/flex'; + +export interface BuyOrderStateButtonProps { + accountAddress?: string; + accountEthBalanceInWei?: BigNumber; + buyQuote?: BuyQuote; + buyOrderProcessingState: OrderProcessState; + assetBuyer: AssetBuyer; + web3Wrapper: Web3Wrapper; + affiliateInfo?: AffiliateInfo; + onViewTransaction: () => void; + onValidationPending: (buyQuote: BuyQuote) => void; + onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void; + onSignatureDenied: (buyQuote: BuyQuote) => void; + onBuyProcessing: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => void; + onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void; + onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void; + onRetry: () => void; +} + +export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonProps> = props => { + if (props.buyOrderProcessingState === OrderProcessState.Failure) { + return ( + <Flex justify="space-between"> + <Button width="48%" onClick={props.onRetry} fontColor={ColorOption.white} fontSize="16px"> + Back + </Button> + <SecondaryButton width="48%" onClick={props.onViewTransaction}> + Details + </SecondaryButton> + </Flex> + ); + } else if ( + props.buyOrderProcessingState === OrderProcessState.Success || + props.buyOrderProcessingState === OrderProcessState.Processing + ) { + return <SecondaryButton onClick={props.onViewTransaction}>View Transaction</SecondaryButton>; + } else if (props.buyOrderProcessingState === OrderProcessState.Validating) { + return <PlacingOrderButton />; + } + + return ( + <BuyButton + accountAddress={props.accountAddress} + accountEthBalanceInWei={props.accountEthBalanceInWei} + buyQuote={props.buyQuote} + assetBuyer={props.assetBuyer} + web3Wrapper={props.web3Wrapper} + affiliateInfo={props.affiliateInfo} + onValidationPending={props.onValidationPending} + onValidationFail={props.onValidationFail} + onSignatureDenied={props.onSignatureDenied} + onBuyProcessing={props.onBuyProcessing} + onBuySuccess={props.onBuySuccess} + onBuyFailure={props.onBuyFailure} + /> + ); +}; diff --git a/packages/instant/src/components/css_reset.tsx b/packages/instant/src/components/css_reset.tsx new file mode 100644 index 000000000..0bef85389 --- /dev/null +++ b/packages/instant/src/components/css_reset.tsx @@ -0,0 +1,32 @@ +import { INJECTED_DIV_CLASS } from '../constants'; +import { createGlobalStyle } from '../style/theme'; + +export interface CSSResetProps {} + +/* +* Derived from +* https://github.com/jtrost/Complete-CSS-Reset +*/ +export const CSSReset = createGlobalStyle` + .${INJECTED_DIV_CLASS} { + a, abbr, area, article, aside, audio, b, bdo, blockquote, body, button, + canvas, caption, cite, code, col, colgroup, command, datalist, dd, del, + details, dialog, dfn, div, dl, dt, em, embed, fieldset, figure, form, + h1, h2, h3, h4, h5, h6, head, header, hgroup, hr, html, i, iframe, img, + input, ins, keygen, kbd, label, legend, li, map, mark, menu, meter, nav, + noscript, object, ol, optgroup, option, output, p, param, pre, progress, + q, rp, rt, ruby, samp, section, select, small, span, strong, sub, sup, + table, tbody, td, textarea, tfoot, th, thead, time, tr, ul, var, video { + background: transparent; + border: 0; + font-size: 100%; + font: inherit; + margin: 0; + outline: none; + padding: 0; + text-align: left; + text-decoration: none; + vertical-align: baseline; + } + } +`; diff --git a/packages/instant/src/components/erc20_asset_amount_input.tsx b/packages/instant/src/components/erc20_asset_amount_input.tsx new file mode 100644 index 000000000..520ac33d5 --- /dev/null +++ b/packages/instant/src/components/erc20_asset_amount_input.tsx @@ -0,0 +1,161 @@ +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { ColorOption, transparentWhite } from '../style/theme'; +import { ERC20Asset, SimpleHandler } from '../types'; +import { assetUtils } from '../util/asset'; +import { util } from '../util/util'; + +import { ScalingAmountInput } from './scaling_amount_input'; + +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Icon } from './ui/icon'; +import { Text } from './ui/text'; + +// Asset amounts only apply to ERC20 assets +export interface ERC20AssetAmountInputProps { + asset?: ERC20Asset; + value?: BigNumber; + onChange: (value?: BigNumber, asset?: ERC20Asset) => void; + onSelectAssetClick?: (asset?: ERC20Asset) => void; + startingFontSizePx: number; + fontColor?: ColorOption; + isDisabled: boolean; + numberOfAssetsAvailable?: number; +} + +export interface ERC20AssetAmountInputState { + currentFontSizePx: number; +} + +export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInputProps, ERC20AssetAmountInputState> { + public static defaultProps = { + onChange: util.boundNoop, + isDisabled: false, + }; + constructor(props: ERC20AssetAmountInputProps) { + super(props); + this.state = { + currentFontSizePx: props.startingFontSizePx, + }; + } + public render(): React.ReactNode { + const { asset } = this.props; + return ( + <Container whiteSpace="nowrap"> + {_.isUndefined(asset) ? this._renderTokenSelectionContent() : this._renderContentForAsset(asset)} + </Container> + ); + } + private readonly _renderContentForAsset = (asset: ERC20Asset): React.ReactNode => { + const { onChange, ...rest } = this.props; + const amountBorderBottom = this.props.isDisabled ? '' : `1px solid ${transparentWhite}`; + const onSymbolClick = this._generateSelectAssetClickHandler(); + return ( + <React.Fragment> + <Container borderBottom={amountBorderBottom} display="inline-block"> + <ScalingAmountInput + {...rest} + textLengthThreshold={this._textLengthThresholdForAsset(asset)} + maxFontSizePx={this.props.startingFontSizePx} + onAmountChange={this._handleChange} + onFontSizeChange={this._handleFontSizeChange} + /> + </Container> + <Container + display="inline-block" + marginLeft="8px" + title={assetUtils.bestNameForAsset(asset, undefined)} + > + <Flex inline={true}> + <Text + fontSize={`${this.state.currentFontSizePx}px`} + fontColor={ColorOption.white} + textTransform="uppercase" + onClick={onSymbolClick} + > + {assetUtils.formattedSymbolForAsset(asset)} + </Text> + {this._renderChevronIcon()} + </Flex> + </Container> + </React.Fragment> + ); + }; + private readonly _renderTokenSelectionContent = (): React.ReactNode => { + const { numberOfAssetsAvailable } = this.props; + let text = 'Select Token'; + if (_.isUndefined(numberOfAssetsAvailable)) { + text = 'Loading...'; + } else if (numberOfAssetsAvailable === 0) { + text = 'Assets Unavailable'; + } + return ( + <Flex> + <Text + fontSize="30px" + fontColor={ColorOption.white} + opacity={0.7} + fontWeight="500" + onClick={this._generateSelectAssetClickHandler()} + > + {text} + </Text> + {this._renderChevronIcon()} + </Flex> + ); + }; + private readonly _renderChevronIcon = (): React.ReactNode => { + if (!this._areMultipleAssetsAvailable()) { + return null; + } + return ( + <Container marginLeft="5px"> + <Icon icon="chevron" width={12} stroke={ColorOption.white} onClick={this._handleSelectAssetClick} /> + </Container> + ); + }; + private readonly _handleChange = (value?: BigNumber): void => { + this.props.onChange(value, this.props.asset); + }; + private readonly _handleFontSizeChange = (fontSizePx: number): void => { + this.setState({ + currentFontSizePx: fontSizePx, + }); + }; + private readonly _generateSelectAssetClickHandler = (): SimpleHandler | undefined => { + // We don't want to allow opening the token selection panel if there are no assets. + // Since styles are inferred from the presence of a click handler, we want to return undefined + // instead of providing a noop. + if (!this._areMultipleAssetsAvailable() || _.isUndefined(this.props.onSelectAssetClick)) { + return undefined; + } + return this._handleSelectAssetClick; + }; + private readonly _areMultipleAssetsAvailable = (): boolean => { + const { numberOfAssetsAvailable } = this.props; + return !_.isUndefined(numberOfAssetsAvailable) && numberOfAssetsAvailable > 1; + }; + private readonly _handleSelectAssetClick = (): void => { + if (this.props.onSelectAssetClick) { + this.props.onSelectAssetClick(); + } + }; + // For assets with symbols of different length, + // start scaling the input at different character lengths + private readonly _textLengthThresholdForAsset = (asset?: ERC20Asset): number => { + if (_.isUndefined(asset)) { + return 3; + } + const symbol = asset.metaData.symbol; + if (symbol.length <= 3) { + return 5; + } + if (symbol.length === 5) { + return 3; + } + return 4; + }; +} diff --git a/packages/instant/src/components/erc20_token_selector.tsx b/packages/instant/src/components/erc20_token_selector.tsx new file mode 100644 index 000000000..3503ff31a --- /dev/null +++ b/packages/instant/src/components/erc20_token_selector.tsx @@ -0,0 +1,111 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; +import { ERC20Asset } from '../types'; +import { assetUtils } from '../util/asset'; + +import { SearchInput } from './search_input'; + +import { Circle } from './ui/circle'; +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Text } from './ui/text'; + +export interface ERC20TokenSelectorProps { + tokens: ERC20Asset[]; + onTokenSelect: (token: ERC20Asset) => void; +} + +export interface ERC20TokenSelectorState { + searchQuery?: string; +} + +export class ERC20TokenSelector extends React.Component<ERC20TokenSelectorProps> { + public state: ERC20TokenSelectorState = { + searchQuery: undefined, + }; + public render(): React.ReactNode { + const { tokens, onTokenSelect } = this.props; + return ( + <Container height="100%"> + <SearchInput + placeholder="Search tokens..." + width="100%" + value={this.state.searchQuery} + onChange={this._handleSearchInputChange} + /> + <Container overflow="scroll" height="calc(100% - 80px)" marginTop="10px"> + {_.map(tokens, token => { + if (!this._isTokenQueryMatch(token)) { + return null; + } + return <TokenSelectorRow key={token.assetData} token={token} onClick={onTokenSelect} />; + })} + </Container> + </Container> + ); + } + private readonly _handleSearchInputChange = (event: React.ChangeEvent<HTMLInputElement>): void => { + const searchQuery = event.target.value; + this.setState({ + searchQuery, + }); + }; + private readonly _isTokenQueryMatch = (token: ERC20Asset): boolean => { + const { searchQuery } = this.state; + if (_.isUndefined(searchQuery)) { + return true; + } + const stringToSearch = `${token.metaData.name} ${token.metaData.symbol}`; + return _.includes(stringToSearch.toLowerCase(), searchQuery.toLowerCase()); + }; +} + +interface TokenSelectorRowProps { + token: ERC20Asset; + onClick: (token: ERC20Asset) => void; +} + +class TokenSelectorRow extends React.Component<TokenSelectorRowProps> { + public render(): React.ReactNode { + const { token } = this.props; + const displaySymbol = assetUtils.bestNameForAsset(token); + return ( + <Container + padding="12px 0px" + borderBottom="1px solid" + borderColor={ColorOption.feintGrey} + backgroundColor={ColorOption.white} + width="100%" + onClick={this._handleClick} + darkenOnHover={true} + cursor="pointer" + > + <Container marginLeft="5px"> + <Flex justify="flex-start"> + <Container marginRight="10px"> + <Circle diameter={30} rawColor={token.metaData.primaryColor}> + <Flex height="100%"> + <Text fontColor={ColorOption.white} fontSize="8px"> + {displaySymbol} + </Text> + </Flex> + </Circle> + </Container> + <Text fontSize="14px" fontWeight={700} fontColor={ColorOption.black}> + {displaySymbol} + </Text> + <Container margin="0px 5px"> + <Text fontSize="14px"> - </Text> + </Container> + <Text fontSize="14px">{token.metaData.name}</Text> + </Flex> + </Container> + </Container> + ); + } + private readonly _handleClick = (): void => { + this.props.onClick(this.props.token); + }; +} diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 17ac65429..b07776b2c 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -2,15 +2,17 @@ import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import * as React from 'react'; -import { SelectedAssetAmountInput } from '../containers/selected_asset_amount_input'; +import { SelectedERC20AssetAmountInput } from '../containers/selected_erc20_asset_amount_input'; import { ColorOption } from '../style/theme'; -import { AsyncProcessState, OrderProcessState, OrderState } from '../types'; +import { AsyncProcessState, ERC20Asset, OrderProcessState, OrderState } from '../types'; import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; -import { Container, Flex, Text } from './ui'; +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; import { Icon } from './ui/icon'; import { Spinner } from './ui/spinner'; +import { Text } from './ui/text'; export interface InstantHeadingProps { selectedAssetAmount?: BigNumber; @@ -18,6 +20,7 @@ export interface InstantHeadingProps { ethUsdPrice?: BigNumber; quoteRequestState: AsyncProcessState; buyOrderState: OrderState; + onSelectAssetClick?: (asset?: ERC20Asset) => void; } const PLACEHOLDER_COLOR = ColorOption.white; @@ -48,7 +51,12 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { </Text> </Container> <Flex direction="row" justify="space-between"> - <SelectedAssetAmountInput fontSize="45px" /> + <Flex height="60px"> + <SelectedERC20AssetAmountInput + startingFontSizePx={38} + onSelectAssetClick={this.props.onSelectAssetClick} + /> + </Flex> <Flex direction="column" justify="space-between"> {iconOrAmounts} </Flex> @@ -60,8 +68,8 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { private _renderAmountsSection(): React.ReactNode { return ( <Container> - <Container marginBottom="5px">{this._placeholderOrAmount(this._ethAmount)}</Container> - <Container opacity={0.7}>{this._placeholderOrAmount(this._dollarAmount)}</Container> + <Container marginBottom="5px">{this._renderPlaceholderOrAmount(this._renderEthAmount)}</Container> + <Container opacity={0.7}>{this._renderPlaceholderOrAmount(this._renderDollarAmount)}</Container> </Container> ); } @@ -69,31 +77,31 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { private _renderIcon(): React.ReactNode { const processState = this.props.buyOrderState.processState; - if (processState === OrderProcessState.FAILURE) { - return <Icon icon={'failed'} width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />; - } else if (processState === OrderProcessState.PROCESSING) { + if (processState === OrderProcessState.Failure) { + return <Icon icon="failed" width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />; + } else if (processState === OrderProcessState.Processing) { return <Spinner widthPx={ICON_HEIGHT} heightPx={ICON_HEIGHT} />; - } else if (processState === OrderProcessState.SUCCESS) { - return <Icon icon={'success'} width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />; + } else if (processState === OrderProcessState.Success) { + return <Icon icon="success" width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />; } return undefined; } private _renderTopText(): React.ReactNode { const processState = this.props.buyOrderState.processState; - if (processState === OrderProcessState.FAILURE) { + if (processState === OrderProcessState.Failure) { return 'Order failed'; - } else if (processState === OrderProcessState.PROCESSING) { + } else if (processState === OrderProcessState.Processing) { return 'Processing Order...'; - } else if (processState === OrderProcessState.SUCCESS) { + } else if (processState === OrderProcessState.Success) { return 'Tokens received!'; } return 'I want to buy'; } - private _placeholderOrAmount(amountFunction: () => React.ReactNode): React.ReactNode { - if (this.props.quoteRequestState === AsyncProcessState.PENDING) { + private _renderPlaceholderOrAmount(amountFunction: () => React.ReactNode): React.ReactNode { + if (this.props.quoteRequestState === AsyncProcessState.Pending) { return <AmountPlaceholder isPulsating={true} color={PLACEHOLDER_COLOR} />; } if (_.isUndefined(this.props.selectedAssetAmount)) { @@ -102,7 +110,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { return amountFunction(); } - private readonly _ethAmount = (): React.ReactNode => { + private readonly _renderEthAmount = (): React.ReactNode => { return ( <Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}> {format.ethBaseAmount( @@ -114,7 +122,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { ); }; - private readonly _dollarAmount = (): React.ReactNode => { + private readonly _renderDollarAmount = (): React.ReactNode => { return ( <Text fontSize="16px" fontColor={ColorOption.white}> {format.ethBaseAmountInUsd( diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 704009d89..9abd7137e 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -8,7 +8,10 @@ import { ColorOption } from '../style/theme'; import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; -import { Container, Flex, Text } from './ui'; + +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Text } from './ui/text'; export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; @@ -23,7 +26,7 @@ export class OrderDetails extends React.Component<OrderDetailsProps> { const ethTokenFee = buyQuoteAccessor.feeEthAmount(); const totalEthAmount = buyQuoteAccessor.totalEthAmount(); return ( - <Container padding="20px" width="100%"> + <Container padding="20px" width="100%" flexGrow={1}> <Container marginBottom="10px"> <Text letterSpacing="1px" diff --git a/packages/instant/src/components/payment_method.tsx b/packages/instant/src/components/payment_method.tsx new file mode 100644 index 000000000..8c0b47d72 --- /dev/null +++ b/packages/instant/src/components/payment_method.tsx @@ -0,0 +1,45 @@ +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; +import { Network } from '../types'; + +import { PaymentMethodDropdown } from './payment_method_dropdown'; +import { Circle } from './ui/circle'; +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Text } from './ui/text'; + +export interface PaymentMethodProps {} + +export const PaymentMethod: React.StatelessComponent<PaymentMethodProps> = () => ( + <Container padding="20px" width="100%"> + <Container marginBottom="10px"> + <Flex justify="space-between"> + <Text + letterSpacing="1px" + fontColor={ColorOption.primaryColor} + fontWeight={600} + textTransform="uppercase" + fontSize="14px" + > + Payment Method + </Text> + <Flex> + <Circle color={ColorOption.green} diameter={8} /> + <Container marginLeft="3px"> + <Text fontColor={ColorOption.darkGrey} fontSize="12px"> + MetaMask + </Text> + </Container> + </Flex> + </Flex> + </Container> + <PaymentMethodDropdown + accountAddress="0xa1b2c3d4e5f6g7h8j9k10" + accountEthBalanceInWei={new BigNumber(10500000000000000000)} + network={Network.Mainnet} + /> + </Container> +); diff --git a/packages/instant/src/components/payment_method_dropdown.tsx b/packages/instant/src/components/payment_method_dropdown.tsx new file mode 100644 index 000000000..bdce2a49d --- /dev/null +++ b/packages/instant/src/components/payment_method_dropdown.tsx @@ -0,0 +1,44 @@ +import { BigNumber } from '@0x/utils'; +import copy from 'copy-to-clipboard'; +import * as React from 'react'; + +import { Network } from '../types'; +import { etherscanUtil } from '../util/etherscan'; +import { format } from '../util/format'; + +import { Dropdown, DropdownItemConfig } from './ui/dropdown'; + +export interface PaymentMethodDropdownProps { + accountAddress: string; + accountEthBalanceInWei?: BigNumber; + network: Network; +} + +export class PaymentMethodDropdown extends React.Component<PaymentMethodDropdownProps> { + public render(): React.ReactNode { + const { accountAddress, accountEthBalanceInWei } = this.props; + const value = format.ethAddress(accountAddress); + const label = format.ethBaseAmount(accountEthBalanceInWei, 4, '') as string; + return <Dropdown value={value} label={label} items={this._getDropdownItemConfigs()} />; + } + private readonly _getDropdownItemConfigs = (): DropdownItemConfig[] => { + const viewOnEtherscan = { + text: 'View on Etherscan', + onClick: this._handleEtherscanClick, + }; + const copyAddressToClipboard = { + text: 'Copy address to clipboard', + onClick: this._handleCopyToClipboardClick, + }; + return [viewOnEtherscan, copyAddressToClipboard]; + }; + private readonly _handleEtherscanClick = (): void => { + const { accountAddress, network } = this.props; + const etherscanUrl = etherscanUtil.getEtherScanEthAddressIfExists(accountAddress, network); + window.open(etherscanUrl, '_blank'); + }; + private readonly _handleCopyToClipboardClick = (): void => { + const { accountAddress } = this.props; + copy(accountAddress); + }; +} diff --git a/packages/instant/src/components/placing_order_button.tsx b/packages/instant/src/components/placing_order_button.tsx index 4232e6c22..d774d7d27 100644 --- a/packages/instant/src/components/placing_order_button.tsx +++ b/packages/instant/src/components/placing_order_button.tsx @@ -5,15 +5,12 @@ import { ColorOption } from '../style/theme'; import { Button } from './ui/button'; import { Container } from './ui/container'; import { Spinner } from './ui/spinner'; -import { Text } from './ui/text'; export const PlacingOrderButton: React.StatelessComponent<{}> = props => ( - <Button isDisabled={true} width="100%"> + <Button isDisabled={true} width="100%" fontColor={ColorOption.white} fontSize="20px"> <Container display="inline-block" position="relative" top="3px" marginRight="8px"> <Spinner widthPx={20} heightPx={20} /> </Container> - <Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px"> - Placing Order… - </Text> + Placing Order… </Button> ); diff --git a/packages/instant/src/components/retry_button.tsx b/packages/instant/src/components/retry_button.tsx deleted file mode 100644 index 0d6188e6a..000000000 --- a/packages/instant/src/components/retry_button.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import * as React from 'react'; - -import { SecondaryButton } from './secondary_button'; - -export interface RetryButtonProps { - onClick: () => void; -} - -export const RetryButton: React.StatelessComponent<RetryButtonProps> = props => { - return <SecondaryButton onClick={props.onClick}>Try Again</SecondaryButton>; -}; diff --git a/packages/instant/src/components/scaling_amount_input.tsx b/packages/instant/src/components/scaling_amount_input.tsx new file mode 100644 index 000000000..5dc719293 --- /dev/null +++ b/packages/instant/src/components/scaling_amount_input.tsx @@ -0,0 +1,83 @@ +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { Maybe } from '../types'; + +import { ColorOption } from '../style/theme'; +import { maybeBigNumberUtil } from '../util/maybe_big_number'; +import { util } from '../util/util'; + +import { ScalingInput } from './scaling_input'; + +export interface ScalingAmountInputProps { + isDisabled: boolean; + maxFontSizePx: number; + textLengthThreshold: number; + fontColor?: ColorOption; + value?: BigNumber; + onAmountChange: (value?: BigNumber) => void; + onFontSizeChange: (fontSizePx: number) => void; +} +interface ScalingAmountInputState { + stringValue: string; +} + +const { stringToMaybeBigNumber, areMaybeBigNumbersEqual } = maybeBigNumberUtil; +export class ScalingAmountInput extends React.Component<ScalingAmountInputProps, ScalingAmountInputState> { + public static defaultProps = { + onAmountChange: util.boundNoop, + onFontSizeChange: util.boundNoop, + isDisabled: false, + }; + public constructor(props: ScalingAmountInputProps) { + super(props); + this.state = { + stringValue: _.isUndefined(props.value) ? '' : props.value.toString(), + }; + } + public componentDidUpdate(): void { + const parsedStateValue = stringToMaybeBigNumber(this.state.stringValue); + const currentValue = this.props.value; + + if (!areMaybeBigNumbersEqual(parsedStateValue, currentValue)) { + // we somehow got into the state in which the value passed in and the string value + // in state have differed, reset state + // we dont expect to ever get into this state, but let's make sure + // we reset if we do since we're dealing with important numbers + this.setState({ + stringValue: _.isUndefined(currentValue) ? '' : currentValue.toString(), + }); + } + } + + public render(): React.ReactNode { + const { textLengthThreshold, fontColor, maxFontSizePx, onFontSizeChange } = this.props; + return ( + <ScalingInput + maxFontSizePx={maxFontSizePx} + textLengthThreshold={textLengthThreshold} + onFontSizeChange={onFontSizeChange} + fontColor={fontColor} + onChange={this._handleChange} + value={this.state.stringValue} + placeholder="0.00" + emptyInputWidthCh={3.5} + isDisabled={this.props.isDisabled} + /> + ); + } + private readonly _handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => { + const sanitizedValue = event.target.value.replace(/[^0-9.]/g, ''); // only allow numbers and "." + this.setState({ + stringValue: sanitizedValue, + }); + + // Trigger onAmountChange with a valid BigNumber, or undefined if the sanitizedValue is invalid or empty + const bigNumberValue: Maybe<BigNumber> = _.isEmpty(sanitizedValue) + ? undefined + : stringToMaybeBigNumber(sanitizedValue); + + this.props.onAmountChange(bigNumberValue); + }; +} diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx new file mode 100644 index 000000000..1abadb78b --- /dev/null +++ b/packages/instant/src/components/scaling_input.tsx @@ -0,0 +1,173 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; +import { util } from '../util/util'; + +import { Input } from './ui/input'; + +export enum ScalingInputPhase { + FixedFontSize, + ScalingFontSize, +} + +export interface ScalingSettings { + percentageToReduceFontSizePerCharacter: number; + constantPxToIncreaseWidthPerCharacter: number; +} + +export interface ScalingInputProps { + textLengthThreshold: number; + maxFontSizePx: number; + value: string; + emptyInputWidthCh: number; + onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; + onFontSizeChange: (fontSizePx: number) => void; + fontColor?: ColorOption; + placeholder?: string; + maxLength?: number; + scalingSettings: ScalingSettings; + isDisabled: boolean; +} + +export interface ScalingInputState { + inputWidthPxAtPhaseChange?: number; +} + +export interface ScalingInputSnapshot { + inputWidthPx: number; +} + +// These are magic numbers that were determined experimentally. +const defaultScalingSettings: ScalingSettings = { + percentageToReduceFontSizePerCharacter: 0.125, + constantPxToIncreaseWidthPerCharacter: 4, +}; + +export class ScalingInput extends React.Component<ScalingInputProps, ScalingInputState> { + public static defaultProps = { + onChange: util.boundNoop, + onFontSizeChange: util.boundNoop, + maxLength: 7, + scalingSettings: defaultScalingSettings, + isDisabled: false, + }; + public state: ScalingInputState = { + inputWidthPxAtPhaseChange: undefined, + }; + private readonly _inputRef = React.createRef<HTMLInputElement>(); + public static getPhase(textLengthThreshold: number, value: string): ScalingInputPhase { + if (value.length <= textLengthThreshold) { + return ScalingInputPhase.FixedFontSize; + } + return ScalingInputPhase.ScalingFontSize; + } + public static getPhaseFromProps(props: ScalingInputProps): ScalingInputPhase { + const { value, textLengthThreshold } = props; + return ScalingInput.getPhase(textLengthThreshold, value); + } + public static calculateFontSize( + textLengthThreshold: number, + maxFontSizePx: number, + phase: ScalingInputPhase, + value: string, + percentageToReduceFontSizePerCharacter: number, + ): number { + if (phase !== ScalingInputPhase.ScalingFontSize) { + return maxFontSizePx; + } + const charactersOverMax = value.length - textLengthThreshold; + const scalingFactor = (1 - percentageToReduceFontSizePerCharacter) ** charactersOverMax; + const fontSize = scalingFactor * maxFontSizePx; + return fontSize; + } + public static calculateFontSizeFromProps(props: ScalingInputProps, phase: ScalingInputPhase): number { + const { textLengthThreshold, value, maxFontSizePx, scalingSettings } = props; + return ScalingInput.calculateFontSize( + textLengthThreshold, + maxFontSizePx, + phase, + value, + scalingSettings.percentageToReduceFontSizePerCharacter, + ); + } + public getSnapshotBeforeUpdate(): ScalingInputSnapshot { + return { + inputWidthPx: this._getInputWidthInPx(), + }; + } + public componentDidUpdate( + prevProps: ScalingInputProps, + prevState: ScalingInputState, + snapshot: ScalingInputSnapshot, + ): void { + const prevPhase = ScalingInput.getPhaseFromProps(prevProps); + const curPhase = ScalingInput.getPhaseFromProps(this.props); + // if we went from fixed to scaling, save the width from the transition + if (prevPhase !== ScalingInputPhase.ScalingFontSize && curPhase === ScalingInputPhase.ScalingFontSize) { + this.setState({ + inputWidthPxAtPhaseChange: snapshot.inputWidthPx, + }); + } + // if we went from scaling to fixed, revert back to scaling using `ch` + if (prevPhase === ScalingInputPhase.ScalingFontSize && curPhase !== ScalingInputPhase.ScalingFontSize) { + this.setState({ + inputWidthPxAtPhaseChange: undefined, + }); + } + const prevFontSize = ScalingInput.calculateFontSizeFromProps(prevProps, prevPhase); + const curFontSize = ScalingInput.calculateFontSizeFromProps(this.props, curPhase); + // If font size has changed, notify. + if (prevFontSize !== curFontSize) { + this.props.onFontSizeChange(curFontSize); + } + } + public render(): React.ReactNode { + const { isDisabled, fontColor, onChange, placeholder, value, maxLength } = this.props; + const phase = ScalingInput.getPhaseFromProps(this.props); + return ( + <Input + ref={this._inputRef as any} + fontColor={fontColor} + onChange={onChange} + value={value} + placeholder={placeholder} + fontSize={`${this._calculateFontSize(phase)}px`} + width={this._calculateWidth(phase)} + maxLength={maxLength} + disabled={isDisabled} + /> + ); + } + private readonly _calculateWidth = (phase: ScalingInputPhase): string => { + const { value, textLengthThreshold, scalingSettings } = this.props; + if (_.isEmpty(value)) { + return `${this.props.emptyInputWidthCh}ch`; + } + switch (phase) { + case ScalingInputPhase.FixedFontSize: + return `${value.length}ch`; + case ScalingInputPhase.ScalingFontSize: + const { inputWidthPxAtPhaseChange } = this.state; + if (!_.isUndefined(inputWidthPxAtPhaseChange)) { + const charactersOverMax = value.length - textLengthThreshold; + const scalingAmount = scalingSettings.constantPxToIncreaseWidthPerCharacter * charactersOverMax; + const width = inputWidthPxAtPhaseChange + scalingAmount; + return `${width}px`; + } + return `${textLengthThreshold}ch`; + default: + return '1ch'; + } + }; + private readonly _calculateFontSize = (phase: ScalingInputPhase): number => { + return ScalingInput.calculateFontSizeFromProps(this.props, phase); + }; + private readonly _getInputWidthInPx = (): number => { + const ref = this._inputRef.current; + if (!ref) { + return 0; + } + return ref.getBoundingClientRect().width; + }; +} diff --git a/packages/instant/src/components/search_input.tsx b/packages/instant/src/components/search_input.tsx new file mode 100644 index 000000000..3a693b9f8 --- /dev/null +++ b/packages/instant/src/components/search_input.tsx @@ -0,0 +1,29 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; + +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Icon } from './ui/icon'; +import { Input, InputProps } from './ui/input'; + +export interface SearchInputProps extends InputProps { + backgroundColor?: ColorOption; +} + +export const SearchInput: React.StatelessComponent<SearchInputProps> = props => ( + <Container backgroundColor={props.backgroundColor} borderRadius="3px" padding=".5em .3em"> + <Flex justify="flex-start" align="flex-end"> + <Icon width={14} height={14} icon="search" color={ColorOption.lightGrey} padding="0px 12px" /> + <Input {...props} fontSize="14px" fontColor={props.fontColor} /> + </Flex> + </Container> +); + +SearchInput.displayName = 'SearchInput'; + +SearchInput.defaultProps = { + backgroundColor: ColorOption.lightestGrey, + fontColor: ColorOption.grey, +}; diff --git a/packages/instant/src/components/secondary_button.tsx b/packages/instant/src/components/secondary_button.tsx index 3c139a233..df0539606 100644 --- a/packages/instant/src/components/secondary_button.tsx +++ b/packages/instant/src/components/secondary_button.tsx @@ -4,7 +4,6 @@ import * as React from 'react'; import { ColorOption } from '../style/theme'; import { Button, ButtonProps } from './ui/button'; -import { Text } from './ui/text'; export interface SecondaryButtonProps extends ButtonProps {} @@ -14,13 +13,16 @@ export const SecondaryButton: React.StatelessComponent<SecondaryButtonProps> = p <Button backgroundColor={ColorOption.white} borderColor={ColorOption.lightGrey} - width="100%" + width={props.width} onClick={props.onClick} + fontColor={ColorOption.primaryColor} + fontSize="16px" {...buttonProps} > - <Text fontColor={ColorOption.primaryColor} fontWeight={600} fontSize="16px"> - {props.children} - </Text> + {props.children} </Button> ); }; +SecondaryButton.defaultProps = { + width: '100%', +}; diff --git a/packages/instant/src/components/sliding_error.tsx b/packages/instant/src/components/sliding_error.tsx index 3865a8797..a8d4e391c 100644 --- a/packages/instant/src/components/sliding_error.tsx +++ b/packages/instant/src/components/sliding_error.tsx @@ -1,10 +1,15 @@ import * as React from 'react'; +import { ScreenSpecification } from '../style/media'; import { ColorOption } from '../style/theme'; +import { zIndex } from '../style/z_index'; -import { SlideDownAnimation, SlideUpAnimation } from './animations/slide_animations'; +import { PositionAnimationSettings } from './animations/position_animation'; +import { SlideAnimation, SlideAnimationState } from './animations/slide_animation'; -import { Container, Text } from './ui'; +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Text } from './ui/text'; export interface ErrorProps { icon: string; @@ -18,27 +23,73 @@ export const Error: React.StatelessComponent<ErrorProps> = props => ( backgroundColor={ColorOption.lightOrange} width="100%" borderRadius="6px" + marginTop="10px" marginBottom="10px" > - <Container marginRight="5px" display="inline" top="3px" position="relative"> - <Text fontSize="20px">{props.icon}</Text> - </Container> - <Text fontWeight="500" fontColor={ColorOption.darkOrange}> - {props.message} - </Text> + <Flex justify="flex-start"> + <Container marginRight="5px" display="inline" top="3px" position="relative"> + <Text fontSize="20px">{props.icon}</Text> + </Container> + <Text fontWeight="500" fontColor={ColorOption.darkOrange}> + {props.message} + </Text> + </Flex> </Container> ); -export type SlidingDirection = 'up' | 'down'; export interface SlidingErrorProps extends ErrorProps { - direction: SlidingDirection; + animationState: SlideAnimationState; } export const SlidingError: React.StatelessComponent<SlidingErrorProps> = props => { - const AnimationComponent = props.direction === 'up' ? SlideUpAnimation : SlideDownAnimation; + const slideAmount = '120px'; + + const desktopSlideIn: PositionAnimationSettings = { + timingFunction: 'ease-in', + top: { + from: slideAmount, + to: '0px', + }, + position: 'relative', + }; + const desktopSlideOut: PositionAnimationSettings = { + timingFunction: 'cubic-bezier(0.25, 0.1, 0.25, 1)', + top: { + from: '0px', + to: slideAmount, + }, + position: 'relative', + }; + + const mobileSlideIn: PositionAnimationSettings = { + duration: '0.5s', + timingFunction: 'ease-in', + top: { from: '-120px', to: '0px' }, + position: 'fixed', + }; + const moblieSlideOut: PositionAnimationSettings = { + duration: '0.5s', + timingFunction: 'ease-in', + top: { from: '0px', to: '-120px' }, + position: 'fixed', + }; + + const slideUpSettings: ScreenSpecification<PositionAnimationSettings> = { + default: desktopSlideIn, + sm: mobileSlideIn, + }; + const slideOutSettings: ScreenSpecification<PositionAnimationSettings> = { + default: desktopSlideOut, + sm: moblieSlideOut, + }; return ( - <AnimationComponent downY="120px"> + <SlideAnimation + slideInSettings={slideUpSettings} + slideOutSettings={slideOutSettings} + zIndex={{ sm: zIndex.errorPopup, default: zIndex.errorPopBehind }} + animationState={props.animationState} + > <Error icon={props.icon} message={props.message} /> - </AnimationComponent> + </SlideAnimation> ); }; diff --git a/packages/instant/src/components/sliding_panel.tsx b/packages/instant/src/components/sliding_panel.tsx new file mode 100644 index 000000000..9d16f9560 --- /dev/null +++ b/packages/instant/src/components/sliding_panel.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; +import { zIndex } from '../style/z_index'; + +import { PositionAnimationSettings } from './animations/position_animation'; +import { SlideAnimation, SlideAnimationState } from './animations/slide_animation'; + +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Icon } from './ui/icon'; +import { Text } from './ui/text'; + +export interface PanelProps { + title?: string; + onClose?: () => void; +} + +export const Panel: React.StatelessComponent<PanelProps> = ({ title, children, onClose }) => ( + <Container backgroundColor={ColorOption.white} width="100%" height="100%" zIndex={zIndex.panel} padding="20px"> + <Flex justify="space-between"> + {title && ( + <Container marginTop="3px"> + <Text fontColor={ColorOption.darkGrey} fontSize="18px" fontWeight="600" lineHeight="22px"> + {title} + </Text> + </Container> + )} + <Container position="relative" bottom="7px"> + <Icon width={12} color={ColorOption.lightGrey} icon="closeX" onClick={onClose} /> + </Container> + </Flex> + <Container marginTop="10px" height="100%"> + {children} + </Container> + </Container> +); + +export interface SlidingPanelProps extends PanelProps { + animationState: SlideAnimationState; +} + +export const SlidingPanel: React.StatelessComponent<SlidingPanelProps> = props => { + if (props.animationState === 'none') { + return null; + } + const { animationState, ...rest } = props; + const slideAmount = '100%'; + const slideUpSettings: PositionAnimationSettings = { + duration: '0.3s', + timingFunction: 'ease-in-out', + top: { + from: slideAmount, + to: '0px', + }, + position: 'absolute', + }; + const slideDownSettings: PositionAnimationSettings = { + duration: '0.3s', + timingFunction: 'ease-out', + top: { + from: '0px', + to: slideAmount, + }, + position: 'absolute', + }; + return ( + <SlideAnimation + slideInSettings={slideUpSettings} + slideOutSettings={slideDownSettings} + animationState={animationState} + height="100%" + > + <Panel {...rest} /> + </SlideAnimation> + ); +}; diff --git a/packages/instant/src/components/time_counter.tsx b/packages/instant/src/components/time_counter.tsx new file mode 100644 index 000000000..f9b68163c --- /dev/null +++ b/packages/instant/src/components/time_counter.tsx @@ -0,0 +1,78 @@ +import * as React from 'react'; + +import { ONE_SECOND_MS } from '../constants'; +import { ColorOption } from '../style/theme'; +import { timeUtil } from '../util/time'; + +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Text } from './ui/text'; + +export interface TimeCounterProps { + estimatedTimeMs: number; + hasEnded: boolean; +} +interface TimeCounterState { + elapsedSeconds: number; +} + +export class TimeCounter extends React.Component<TimeCounterProps, TimeCounterState> { + public state = { + elapsedSeconds: 0, + }; + private _timerId?: number; + + public componentDidMount(): void { + this._setupTimerBasedOnProps(); + } + + public componentWillUnmount(): void { + this._clearTimer(); + } + + public componentDidUpdate(prevProps: TimeCounterProps): void { + if (prevProps.hasEnded !== this.props.hasEnded) { + this._setupTimerBasedOnProps(); + } + } + + public render(): React.ReactNode { + const estimatedTimeSeconds = this.props.estimatedTimeMs / ONE_SECOND_MS; + return ( + <Flex justify="space-between"> + <Container> + <Container marginRight="5px" display="inline"> + <Text fontWeight={600} fontColor={ColorOption.grey}> + Est. Time + </Text> + </Container> + <Text fontColor={ColorOption.grey}> + ({timeUtil.secondsToHumanDescription(estimatedTimeSeconds)}) + </Text> + </Container> + <Text fontColor={ColorOption.grey}> + Time: {timeUtil.secondsToStopwatchTime(this.state.elapsedSeconds)} + </Text> + </Flex> + ); + } + + private _setupTimerBasedOnProps(): void { + this.props.hasEnded ? this._clearTimer() : this._newTimer(); + } + + private _newTimer(): void { + this._clearTimer(); + this._timerId = window.setInterval(() => { + this.setState({ + elapsedSeconds: this.state.elapsedSeconds + 1, + }); + }, ONE_SECOND_MS); + } + + private _clearTimer(): void { + if (this._timerId) { + window.clearInterval(this._timerId); + } + } +} diff --git a/packages/instant/src/components/timed_progress_bar.tsx b/packages/instant/src/components/timed_progress_bar.tsx new file mode 100644 index 000000000..59aaa33a1 --- /dev/null +++ b/packages/instant/src/components/timed_progress_bar.tsx @@ -0,0 +1,80 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { PROGRESS_FINISH_ANIMATION_TIME_MS, PROGRESS_STALL_AT_WIDTH } from '../constants'; +import { ColorOption, keyframes, styled } from '../style/theme'; + +import { Container } from './ui/container'; + +export interface TimedProgressBarProps { + expectedTimeMs: number; + hasEnded: boolean; +} + +/** + * Timed Progress Bar + * Goes from 0% -> PROGRESS_STALL_AT_WIDTH over time of expectedTimeMs + * When hasEnded set to true, goes to 100% through animation of PROGRESS_FINISH_ANIMATION_TIME_MS length of time + */ +export class TimedProgressBar extends React.Component<TimedProgressBarProps, {}> { + private readonly _barRef = React.createRef<HTMLDivElement>(); + + public render(): React.ReactNode { + const timedProgressProps = this._calculateTimedProgressProps(); + return ( + <Container width="100%" backgroundColor={ColorOption.lightGrey} borderRadius="6px"> + <TimedProgress {...timedProgressProps} ref={this._barRef as any} /> + </Container> + ); + } + + private _calculateTimedProgressProps(): TimedProgressProps { + if (this.props.hasEnded) { + if (!this._barRef.current) { + throw new Error('ended but no reference'); + } + const fromWidth = `${this._barRef.current.offsetWidth}px`; + return { + timeMs: PROGRESS_FINISH_ANIMATION_TIME_MS, + fromWidth, + toWidth: '100%', + }; + } + + return { + timeMs: this.props.expectedTimeMs, + fromWidth: '0px', + toWidth: PROGRESS_STALL_AT_WIDTH, + }; + } +} + +const expandingWidthKeyframes = (fromWidth: string, toWidth: string) => { + return keyframes` + from { + width: ${fromWidth}; + } + to { + width: ${toWidth}; + } + `; +}; + +interface TimedProgressProps { + timeMs: number; + fromWidth: string; + toWidth: string; +} + +export const TimedProgress = + styled.div < + TimedProgressProps > + ` + && { + background-color: ${props => props.theme[ColorOption.primaryColor]}; + border-radius: 6px; + height: 6px; + animation: ${props => expandingWidthKeyframes(props.fromWidth, props.toWidth)} + ${props => props.timeMs}ms linear 1 forwards; + } +`; diff --git a/packages/instant/src/components/ui/button.tsx b/packages/instant/src/components/ui/button.tsx index 1fcb2591c..b90221bf4 100644 --- a/packages/instant/src/components/ui/button.tsx +++ b/packages/instant/src/components/ui/button.tsx @@ -6,6 +6,8 @@ import { ColorOption, styled } from '../../style/theme'; export interface ButtonProps { backgroundColor?: ColorOption; borderColor?: ColorOption; + fontColor?: ColorOption; + fontSize?: string; width?: string; padding?: string; type?: string; @@ -24,37 +26,49 @@ const darkenOnHoverAmount = 0.1; const darkenOnActiveAmount = 0.2; const saturateOnFocusAmount = 0.2; export const Button = styled(PlainButton)` - cursor: ${props => (props.isDisabled ? 'default' : 'pointer')}; - transition: background-color, opacity 0.5s ease; - padding: ${props => props.padding}; - border-radius: 3px; - outline: none; - width: ${props => props.width}; - background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; - border: ${props => (props.borderColor ? `1px solid ${props.theme[props.borderColor]}` : 'none')}; - &:hover { - background-color: ${props => - !props.isDisabled - ? darken(darkenOnHoverAmount, props.theme[props.backgroundColor || 'white']) - : ''} !important; - } - &:active { - background-color: ${props => - !props.isDisabled ? darken(darkenOnActiveAmount, props.theme[props.backgroundColor || 'white']) : ''}; - } - &:disabled { - opacity: 0.5; - } - &:focus { - background-color: ${props => saturate(saturateOnFocusAmount, props.theme[props.backgroundColor || 'white'])}; + && { + all: initial; + box-sizing: border-box; + font-size: ${props => props.fontSize}; + font-family: 'Inter UI', sans-serif; + font-weight: 600; + color: ${props => props.fontColor && props.theme[props.fontColor]}; + cursor: ${props => (props.isDisabled ? 'default' : 'pointer')}; + transition: background-color, opacity 0.5s ease; + padding: ${props => props.padding}; + border-radius: 3px; + text-align: center; + outline: none; + width: ${props => props.width}; + background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; + border: ${props => (props.borderColor ? `1px solid ${props.theme[props.borderColor]}` : 'none')}; + &:hover { + background-color: ${props => + !props.isDisabled + ? darken(darkenOnHoverAmount, props.theme[props.backgroundColor || 'white']) + : ''} !important; + } + &:active { + background-color: ${props => + !props.isDisabled ? darken(darkenOnActiveAmount, props.theme[props.backgroundColor || 'white']) : ''}; + } + &:disabled { + opacity: 0.5; + } + &:focus { + background-color: ${props => + saturate(saturateOnFocusAmount, props.theme[props.backgroundColor || 'white'])}; + } } `; Button.defaultProps = { backgroundColor: ColorOption.primaryColor, + borderColor: ColorOption.primaryColor, width: 'auto', isDisabled: false, - padding: '1em 2.2em', + padding: '.6em 1.2em', + fontSize: '15px', }; Button.displayName = 'Button'; diff --git a/packages/instant/src/components/ui/circle.tsx b/packages/instant/src/components/ui/circle.tsx new file mode 100644 index 000000000..4f9f56f12 --- /dev/null +++ b/packages/instant/src/components/ui/circle.tsx @@ -0,0 +1,27 @@ +import { ColorOption, styled, Theme, withTheme } from '../../style/theme'; + +export interface CircleProps { + diameter: number; + rawColor?: string; + color?: ColorOption; + theme: Theme; +} + +export const Circle = withTheme( + styled.div < + CircleProps > + ` + && { + width: ${props => props.diameter}px; + height: ${props => props.diameter}px; + background-color: ${props => (props.rawColor ? props.rawColor : props.theme[props.color || ColorOption.white])}; + border-radius: 50%; + } +`, +); + +Circle.displayName = 'Circle'; + +Circle.defaultProps = { + color: ColorOption.white, +}; diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index 5e2218c68..8aa5db9e5 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -1,16 +1,18 @@ -import * as React from 'react'; +import { darken } from 'polished'; +import { MediaChoice, stylesForMedia } from '../../style/media'; import { ColorOption, styled } from '../../style/theme'; import { cssRuleIfExists } from '../../style/util'; export interface ContainerProps { - display?: string; + display?: MediaChoice; position?: string; top?: string; right?: string; bottom?: string; left?: string; - width?: string; + width?: MediaChoice; + height?: MediaChoice; maxWidth?: string; margin?: string; marginTop?: string; @@ -27,38 +29,59 @@ export interface ContainerProps { backgroundColor?: ColorOption; hasBoxShadow?: boolean; zIndex?: number; + whiteSpace?: string; opacity?: number; + cursor?: string; + overflow?: string; + darkenOnHover?: boolean; + boxShadowOnHover?: boolean; + flexGrow?: string | number; } -const PlainContainer: React.StatelessComponent<ContainerProps> = ({ children, className }) => ( - <div className={className}>{children}</div> -); - -export const Container = styled(PlainContainer)` - box-sizing: border-box; - ${props => cssRuleIfExists(props, 'display')} - ${props => cssRuleIfExists(props, 'position')} - ${props => cssRuleIfExists(props, 'top')} - ${props => cssRuleIfExists(props, 'right')} - ${props => cssRuleIfExists(props, 'bottom')} - ${props => cssRuleIfExists(props, 'left')} - ${props => cssRuleIfExists(props, 'width')} - ${props => cssRuleIfExists(props, 'max-width')} - ${props => cssRuleIfExists(props, 'margin')} - ${props => cssRuleIfExists(props, 'margin-top')} - ${props => cssRuleIfExists(props, 'margin-right')} - ${props => cssRuleIfExists(props, 'margin-bottom')} - ${props => cssRuleIfExists(props, 'margin-left')} - ${props => cssRuleIfExists(props, 'padding')} - ${props => cssRuleIfExists(props, 'border-radius')} - ${props => cssRuleIfExists(props, 'border')} - ${props => cssRuleIfExists(props, 'border-top')} - ${props => cssRuleIfExists(props, 'border-bottom')} - ${props => cssRuleIfExists(props, 'z-index')} - ${props => cssRuleIfExists(props, 'opacity')} - ${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')}; - background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; - border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')}; +export const Container = + styled.div < + ContainerProps > + ` + && { + box-sizing: border-box; + ${props => cssRuleIfExists(props, 'flex-grow')} + ${props => cssRuleIfExists(props, 'position')} + ${props => cssRuleIfExists(props, 'top')} + ${props => cssRuleIfExists(props, 'right')} + ${props => cssRuleIfExists(props, 'bottom')} + ${props => cssRuleIfExists(props, 'left')} + ${props => cssRuleIfExists(props, 'max-width')} + ${props => cssRuleIfExists(props, 'margin')} + ${props => cssRuleIfExists(props, 'margin-top')} + ${props => cssRuleIfExists(props, 'margin-right')} + ${props => cssRuleIfExists(props, 'margin-bottom')} + ${props => cssRuleIfExists(props, 'margin-left')} + ${props => cssRuleIfExists(props, 'padding')} + ${props => cssRuleIfExists(props, 'border-radius')} + ${props => cssRuleIfExists(props, 'border')} + ${props => cssRuleIfExists(props, 'border-top')} + ${props => cssRuleIfExists(props, 'border-bottom')} + ${props => cssRuleIfExists(props, 'z-index')} + ${props => cssRuleIfExists(props, 'white-space')} + ${props => cssRuleIfExists(props, 'opacity')} + ${props => cssRuleIfExists(props, 'cursor')} + ${props => cssRuleIfExists(props, 'overflow')} + ${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')}; + ${props => props.display && stylesForMedia<string>('display', props.display)} + ${props => props.width && stylesForMedia<string>('width', props.width)} + ${props => props.height && stylesForMedia<string>('height', props.height)} + background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; + border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')}; + &:hover { + ${props => + props.darkenOnHover + ? `background-color: ${ + props.backgroundColor ? darken(0.05, props.theme[props.backgroundColor]) : 'none' + }` + : ''}; + ${props => (props.boxShadowOnHover ? 'box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)' : '')}; + } + } `; Container.defaultProps = { diff --git a/packages/instant/src/components/ui/dropdown.tsx b/packages/instant/src/components/ui/dropdown.tsx new file mode 100644 index 000000000..3a23f456d --- /dev/null +++ b/packages/instant/src/components/ui/dropdown.tsx @@ -0,0 +1,134 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { ColorOption, completelyTransparent } from '../../style/theme'; +import { zIndex } from '../../style/z_index'; + +import { Container } from './container'; +import { Flex } from './flex'; +import { Icon } from './icon'; +import { Overlay } from './overlay'; +import { Text } from './text'; + +export interface DropdownItemConfig { + text: string; + onClick?: () => void; +} + +export interface DropdownProps { + value: string; + label?: string; + items: DropdownItemConfig[]; +} + +export interface DropdownState { + isOpen: boolean; +} + +export class Dropdown extends React.Component<DropdownProps, DropdownState> { + public static defaultProps = { + items: [], + }; + public state: DropdownState = { + isOpen: false, + }; + public render(): React.ReactNode { + const { value, label, items } = this.props; + const { isOpen } = this.state; + const hasItems = !_.isEmpty(items); + const borderRadius = isOpen ? '4px 4px 0px 0px' : '4px'; + return ( + <React.Fragment> + {isOpen && ( + <Overlay + zIndex={zIndex.dropdownItems - 1} + backgroundColor={completelyTransparent} + onClick={this._closeDropdown} + /> + )} + <Container position="relative"> + <Container + cursor={hasItems ? 'pointer' : undefined} + onClick={this._handleDropdownClick} + hasBoxShadow={isOpen} + boxShadowOnHover={true} + borderRadius={borderRadius} + border="1px solid" + borderColor={ColorOption.feintGrey} + padding="0.8em" + > + <Flex justify="space-between"> + <Text fontSize="16px" fontColor={ColorOption.darkGrey}> + {value} + </Text> + <Container> + {label && ( + <Text fontSize="16px" fontColor={ColorOption.lightGrey}> + {label} + </Text> + )} + {hasItems && ( + <Container marginLeft="5px" display="inline-block" position="relative" bottom="2px"> + <Icon padding="3px" icon="chevron" width={12} stroke={ColorOption.grey} /> + </Container> + )} + </Container> + </Flex> + </Container> + {isOpen && ( + <Container + width="100%" + position="absolute" + onClick={this._closeDropdown} + backgroundColor={ColorOption.white} + hasBoxShadow={true} + zIndex={zIndex.dropdownItems} + > + {_.map(items, (item, index) => ( + <DropdownItem key={item.text} {...item} isLast={index === items.length - 1} /> + ))} + </Container> + )} + </Container> + </React.Fragment> + ); + } + private readonly _handleDropdownClick = (): void => { + if (_.isEmpty(this.props.items)) { + return; + } + this.setState({ + isOpen: !this.state.isOpen, + }); + }; + private readonly _closeDropdown = (): void => { + this.setState({ + isOpen: false, + }); + }; +} + +export interface DropdownItemProps extends DropdownItemConfig { + text: string; + onClick?: () => void; + isLast: boolean; +} + +export const DropdownItem: React.StatelessComponent<DropdownItemProps> = ({ text, onClick, isLast }) => ( + <Container + onClick={onClick} + cursor="pointer" + darkenOnHover={true} + backgroundColor={ColorOption.white} + padding="0.8em" + borderTop="0" + border="1px solid" + borderRadius={isLast ? '0px 0px 4px 4px' : undefined} + width="100%" + borderColor={ColorOption.feintGrey} + > + <Text fontSize="14px" fontColor={ColorOption.darkGrey}> + {text} + </Text> + </Container> +); diff --git a/packages/instant/src/components/ui/flex.tsx b/packages/instant/src/components/ui/flex.tsx index 327e91926..274c46b9e 100644 --- a/packages/instant/src/components/ui/flex.tsx +++ b/packages/instant/src/components/ui/flex.tsx @@ -1,5 +1,4 @@ -import * as React from 'react'; - +import { MediaChoice, stylesForMedia } from '../../style/media'; import { ColorOption, styled } from '../../style/theme'; import { cssRuleIfExists } from '../../style/util'; @@ -8,23 +7,28 @@ export interface FlexProps { flexWrap?: 'wrap' | 'nowrap'; justify?: 'flex-start' | 'center' | 'space-around' | 'space-between' | 'space-evenly' | 'flex-end'; align?: 'flex-start' | 'center' | 'space-around' | 'space-between' | 'space-evenly' | 'flex-end'; - width?: string; + width?: MediaChoice; + height?: MediaChoice; backgroundColor?: ColorOption; - className?: string; + inline?: boolean; + flexGrow?: number | string; } -const PlainFlex: React.StatelessComponent<FlexProps> = ({ children, className }) => ( - <div className={className}>{children}</div> -); - -export const Flex = styled(PlainFlex)` - display: flex; - flex-direction: ${props => props.direction}; - flex-wrap: ${props => props.flexWrap}; - justify-content: ${props => props.justify}; - align-items: ${props => props.align}; - ${props => cssRuleIfExists(props, 'width')} - background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; +export const Flex = + styled.div < + FlexProps > + ` + && { + display: ${props => (props.inline ? 'inline-flex' : 'flex')}; + flex-direction: ${props => props.direction}; + flex-wrap: ${props => props.flexWrap}; + ${props => cssRuleIfExists(props, 'flexGrow')} + justify-content: ${props => props.justify}; + align-items: ${props => props.align}; + background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; + ${props => (props.width ? stylesForMedia('width', props.width) : '')} + ${props => (props.height ? stylesForMedia('height', props.height) : '')} + } `; Flex.defaultProps = { diff --git a/packages/instant/src/components/ui/icon.tsx b/packages/instant/src/components/ui/icon.tsx index 7373c3acd..a88fa87dd 100644 --- a/packages/instant/src/components/ui/icon.tsx +++ b/packages/instant/src/components/ui/icon.tsx @@ -1,19 +1,34 @@ +import * as _ from 'lodash'; import * as React from 'react'; -import { ColorOption } from '../../style/theme'; +import { ColorOption, styled, Theme, withTheme } from '../../style/theme'; type svgRule = 'evenodd' | 'nonzero' | 'inherit'; interface IconInfo { viewBox: string; + path: string; fillRule?: svgRule; clipRule?: svgRule; - path: string; + strokeOpacity?: number; + strokeWidth?: number; + strokeLinecap?: 'butt' | 'round' | 'square' | 'inherit'; + strokeLinejoin?: 'miter' | 'round' | 'bevel' | 'inherit'; } interface IconInfoMapping { + closeX: IconInfo; failed: IconInfo; success: IconInfo; + chevron: IconInfo; + search: IconInfo; } const ICONS: IconInfoMapping = { + closeX: { + viewBox: '0 0 11 11', + fillRule: 'evenodd', + clipRule: 'evenodd', + path: + 'M10.45 10.449C10.7539 10.1453 10.7539 9.65282 10.45 9.34909L6.60068 5.49999L10.45 1.65093C10.7538 1.3472 10.7538 0.854765 10.45 0.551038C10.1462 0.24731 9.65378 0.24731 9.34995 0.551038L5.50058 4.40006L1.65024 0.549939C1.34641 0.246212 0.853973 0.246212 0.550262 0.549939C0.246429 0.853667 0.246429 1.34611 0.550262 1.64983L4.40073 5.49995L0.55014 9.35019C0.246307 9.65392 0.246307 10.1464 0.55014 10.4501C0.853851 10.7538 1.34628 10.7538 1.65012 10.4501L5.5007 6.59987L9.35007 10.449C9.6539 10.7527 10.1463 10.7527 10.45 10.449Z', + }, failed: { viewBox: '0 0 34 34', fillRule: 'evenodd', @@ -28,31 +43,81 @@ const ICONS: IconInfoMapping = { path: 'M17 34C26.3887 34 34 26.3888 34 17C34 7.61121 26.3887 0 17 0C7.61133 0 0 7.61121 0 17C0 26.3888 7.61133 34 17 34ZM25.7539 13.0977C26.2969 12.4718 26.2295 11.5244 25.6035 10.9817C24.9775 10.439 24.0303 10.5063 23.4878 11.1323L15.731 20.0771L12.3936 16.7438C11.8071 16.1583 10.8574 16.1589 10.272 16.7451C9.68652 17.3313 9.6875 18.281 10.2734 18.8665L14.75 23.3373L15.8887 24.4746L16.9434 23.2587L25.7539 13.0977Z', }, + chevron: { + viewBox: '0 0 12 7', + path: 'M11 1L6 6L1 1', + strokeOpacity: 0.5, + strokeWidth: 1.5, + strokeLinecap: 'round', + strokeLinejoin: 'round', + }, + search: { + viewBox: '0 0 14 14', + fillRule: 'evenodd', + clipRule: 'evenodd', + path: + 'M8.39404 5.19727C8.39404 6.96289 6.96265 8.39453 5.19702 8.39453C3.4314 8.39453 2 6.96289 2 5.19727C2 3.43164 3.4314 2 5.19702 2C6.96265 2 8.39404 3.43164 8.39404 5.19727ZM8.09668 9.51074C7.26855 10.0684 6.27075 10.3945 5.19702 10.3945C2.3269 10.3945 0 8.06738 0 5.19727C0 2.32715 2.3269 0 5.19702 0C8.06738 0 10.394 2.32715 10.394 5.19727C10.394 6.27051 10.0686 7.26855 9.51074 8.09668L13.6997 12.2861L12.2854 13.7002L8.09668 9.51074Z', + }, }; export interface IconProps { + className?: string; width: number; - height: number; - color: ColorOption; + height?: number; + color?: ColorOption; + stroke?: ColorOption; icon: keyof IconInfoMapping; + onClick?: (event: React.MouseEvent<HTMLElement>) => void; + padding?: string; + theme: Theme; } -export const Icon: React.SFC<IconProps> = props => { +const PlainIcon: React.StatelessComponent<IconProps> = props => { const iconInfo = ICONS[props.icon]; - + const colorValue = _.isUndefined(props.color) ? undefined : props.theme[props.color]; + const strokeValue = _.isUndefined(props.stroke) ? undefined : props.theme[props.stroke]; return ( - <svg - width={props.width} - height={props.height} - viewBox={iconInfo.viewBox} - fill="none" - xmlns="http://www.w3.org/2000/svg" - > - <path - d={iconInfo.path} - fill={props.color} - fillRule={iconInfo.fillRule || 'nonzero'} - clipRule={iconInfo.clipRule || 'nonzero'} - /> - </svg> + <div onClick={props.onClick} className={props.className}> + <svg + width={props.width} + height={props.height} + viewBox={iconInfo.viewBox} + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <path + d={iconInfo.path} + fill={colorValue} + fillRule={iconInfo.fillRule || 'nonzero'} + clipRule={iconInfo.clipRule || 'nonzero'} + stroke={strokeValue} + strokeOpacity={iconInfo.strokeOpacity} + strokeWidth={iconInfo.strokeWidth} + strokeLinecap={iconInfo.strokeLinecap} + strokeLinejoin={iconInfo.strokeLinejoin} + /> + </svg> + </div> ); }; + +export const Icon = withTheme(styled(PlainIcon)` + && { + display: inline-block; + ${props => (!_.isUndefined(props.onClick) ? 'cursor: pointer' : '')}; + transition: opacity 0.5s ease; + padding: ${props => props.padding}; + opacity: ${props => (!_.isUndefined(props.onClick) ? 0.7 : 1)}; + &:hover { + opacity: 1; + } + &:active { + opacity: 1; + } + } +`); + +Icon.defaultProps = { + padding: '0em 0em', +}; + +Icon.displayName = 'Icon'; diff --git a/packages/instant/src/components/ui/index.ts b/packages/instant/src/components/ui/index.ts deleted file mode 100644 index bf5f6c700..000000000 --- a/packages/instant/src/components/ui/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { Text, Title } from './text'; -export { Button } from './button'; -export { Flex } from './flex'; -export { Container } from './container'; -export { Input } from './input'; diff --git a/packages/instant/src/components/ui/input.tsx b/packages/instant/src/components/ui/input.tsx index f8c6b6ef6..2fb408db4 100644 --- a/packages/instant/src/components/ui/input.tsx +++ b/packages/instant/src/components/ui/input.tsx @@ -12,22 +12,24 @@ export interface InputProps { onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; } -const PlainInput: React.StatelessComponent<InputProps> = ({ value, className, placeholder, onChange }) => ( - <input className={className} value={value} onChange={onChange} placeholder={placeholder} /> -); - -export const Input = styled(PlainInput)` - font-size: ${props => props.fontSize}; - width: ${props => props.width}; - padding: 0.1em 0em; - font-family: 'Inter UI'; - color: ${props => props.theme[props.fontColor || 'white']}; - background: transparent; - outline: none; - border: none; - &::placeholder { +export const Input = + styled.input < + InputProps > + ` + && { + all: initial; + font-size: ${props => props.fontSize}; + width: ${props => props.width}; + padding: 0.1em 0em; + font-family: 'Inter UI'; color: ${props => props.theme[props.fontColor || 'white']}; - opacity: 0.5; + background: transparent; + outline: none; + border: none; + &::placeholder { + color: ${props => props.theme[props.fontColor || 'white']}; + opacity: 0.5; + } } `; diff --git a/packages/instant/src/components/ui/overlay.tsx b/packages/instant/src/components/ui/overlay.tsx new file mode 100644 index 000000000..f67d6fb2f --- /dev/null +++ b/packages/instant/src/components/ui/overlay.tsx @@ -0,0 +1,39 @@ +import * as _ from 'lodash'; + +import { generateMediaWrapper, ScreenWidths } from '../../style/media'; +import { generateOverlayBlack, styled } from '../../style/theme'; +import { zIndex } from '../../style/z_index'; + +export interface OverlayProps { + zIndex?: number; + backgroundColor?: string; + width?: string; + height?: string; + showMaxWidth?: ScreenWidths; +} + +export const Overlay = + styled.div < + OverlayProps > + ` + && { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: ${props => props.zIndex} + background-color: ${props => props.backgroundColor}; + ${props => props.width && `width: ${props.width};`} + ${props => props.height && `height: ${props.height};`} + display: ${props => (props.showMaxWidth ? 'none' : 'block')}; + ${props => props.showMaxWidth && generateMediaWrapper(props.showMaxWidth)`display: block;`} + } +`; + +Overlay.defaultProps = { + zIndex: zIndex.overlayDefault, + backgroundColor: generateOverlayBlack(0.6), +}; + +Overlay.displayName = 'Overlay'; diff --git a/packages/instant/src/components/ui/text.tsx b/packages/instant/src/components/ui/text.tsx index 9fb8ea26f..4fe429d25 100644 --- a/packages/instant/src/components/ui/text.tsx +++ b/packages/instant/src/components/ui/text.tsx @@ -18,40 +18,36 @@ export interface TextProps { fontWeight?: number | string; textDecorationLine?: string; onClick?: (event: React.MouseEvent<HTMLElement>) => void; - hoverColor?: string; noWrap?: boolean; display?: string; } -const PlainText: React.StatelessComponent<TextProps> = ({ children, className, onClick }) => ( - <div className={className} onClick={onClick}> - {children} - </div> -); - const darkenOnHoverAmount = 0.3; -export const Text = styled(PlainText)` - font-family: ${props => props.fontFamily}; - font-style: ${props => props.fontStyle}; - font-weight: ${props => props.fontWeight}; - font-size: ${props => props.fontSize}; - opacity: ${props => props.opacity}; - text-decoration-line: ${props => props.textDecorationLine}; - ${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')}; - ${props => (props.center ? 'text-align: center' : '')}; - color: ${props => props.fontColor && props.theme[props.fontColor]}; - ${props => (props.minHeight ? `min-height: ${props.minHeight}` : '')}; - ${props => (props.onClick ? 'cursor: pointer' : '')}; - transition: color 0.5s ease; - ${props => (props.noWrap ? 'white-space: nowrap' : '')}; - ${props => (props.display ? `display: ${props.display}` : '')}; - ${props => (props.letterSpacing ? `letter-spacing: ${props.letterSpacing}` : '')}; - ${props => (props.textTransform ? `text-transform: ${props.textTransform}` : '')}; - &:hover { - ${props => - props.onClick - ? `color: ${props.hoverColor || darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}` - : ''}; +export const Text = + styled.div < + TextProps > + ` + && { + font-family: 'Inter UI', sans-serif; + font-style: ${props => props.fontStyle}; + font-weight: ${props => props.fontWeight}; + font-size: ${props => props.fontSize}; + opacity: ${props => props.opacity}; + text-decoration-line: ${props => props.textDecorationLine}; + ${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')}; + ${props => (props.center ? 'text-align: center' : '')}; + color: ${props => props.fontColor && props.theme[props.fontColor]}; + ${props => (props.minHeight ? `min-height: ${props.minHeight}` : '')}; + ${props => (props.onClick ? 'cursor: pointer' : '')}; + transition: color 0.5s ease; + ${props => (props.noWrap ? 'white-space: nowrap' : '')}; + ${props => (props.display ? `display: ${props.display}` : '')}; + ${props => (props.letterSpacing ? `letter-spacing: ${props.letterSpacing}` : '')}; + ${props => (props.textTransform ? `text-transform: ${props.textTransform}` : '')}; + &:hover { + ${props => + props.onClick ? `color: ${darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}` : ''}; + } } `; @@ -67,14 +63,3 @@ Text.defaultProps = { }; Text.displayName = 'Text'; - -export const Title: React.StatelessComponent<TextProps> = props => <Text {...props} />; - -Title.defaultProps = { - fontSize: '20px', - fontWeight: 600, - opacity: 1, - fontColor: ColorOption.primaryColor, -}; - -Title.displayName = 'Title'; diff --git a/packages/instant/src/components/view_transaction_button.tsx b/packages/instant/src/components/view_transaction_button.tsx deleted file mode 100644 index 7aa44e657..000000000 --- a/packages/instant/src/components/view_transaction_button.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import * as React from 'react'; - -import { SecondaryButton } from './secondary_button'; - -export interface ViewTransactionButtonProps { - onClick: () => void; -} - -export const ViewTransactionButton: React.StatelessComponent<ViewTransactionButtonProps> = props => { - return <SecondaryButton onClick={props.onClick}>View Transaction</SecondaryButton>; -}; diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx index ffa5a8250..b945f9908 100644 --- a/packages/instant/src/components/zero_ex_instant.tsx +++ b/packages/instant/src/components/zero_ex_instant.tsx @@ -1,75 +1,18 @@ -import { AssetBuyer } from '@0x/asset-buyer'; -import { ObjectMap } from '@0x/types'; import * as React from 'react'; -import { Provider } from 'react-redux'; -import { SelectedAssetThemeProvider } from '../containers/selected_asset_theme_provider'; -import { asyncData } from '../redux/async_data'; -import { INITIAL_STATE, State } from '../redux/reducer'; -import { store, Store } from '../redux/store'; -import { fonts } from '../style/fonts'; -import { AssetMetaData, Network } from '../types'; -import { assetUtils } from '../util/asset'; -import { getProvider } from '../util/provider'; +import { INJECTED_DIV_CLASS } from '../constants'; import { ZeroExInstantContainer } from './zero_ex_instant_container'; - -fonts.include(); - -export type ZeroExInstantProps = ZeroExInstantRequiredProps & Partial<ZeroExInstantOptionalProps>; - -export interface ZeroExInstantRequiredProps { - // TODO: Change API when we allow the selection of different assetDatas - assetData: string; - // TODO: Allow for a function that returns orders - liquiditySource: string; -} - -export interface ZeroExInstantOptionalProps { - additionalAssetMetaDataMap: ObjectMap<AssetMetaData>; - network: Network; -} - -export class ZeroExInstant extends React.Component<ZeroExInstantProps> { - private readonly _store: Store; - private static _mergeInitialStateWithProps(props: ZeroExInstantProps, state: State = INITIAL_STATE): State { - // Create merged object such that properties in props override default settings - const optionalPropsWithDefaults: ZeroExInstantOptionalProps = { - additionalAssetMetaDataMap: props.additionalAssetMetaDataMap || {}, - network: props.network || state.network, - }; - const { network } = optionalPropsWithDefaults; - // TODO: Provider needs to not be hard-coded to injected web3. - const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(getProvider(), props.liquiditySource, { - networkId: network, - }); - const completeAssetMetaDataMap = { - ...props.additionalAssetMetaDataMap, - ...state.assetMetaDataMap, - }; - const storeStateFromProps: State = { - ...state, - assetBuyer, - network, - selectedAsset: assetUtils.createAssetFromAssetData(props.assetData, completeAssetMetaDataMap, network), - assetMetaDataMap: completeAssetMetaDataMap, - }; - return storeStateFromProps; - } - constructor(props: ZeroExInstantProps) { - super(props); - this._store = store.create(ZeroExInstant._mergeInitialStateWithProps(this.props, INITIAL_STATE)); - // tslint:disable-next-line:no-floating-promises - asyncData.fetchAndDispatchToStore(this._store); - } - - public render(): React.ReactNode { - return ( - <Provider store={this._store}> - <SelectedAssetThemeProvider> - <ZeroExInstantContainer /> - </SelectedAssetThemeProvider> - </Provider> - ); - } -} +import { ZeroExInstantProvider, ZeroExInstantProviderProps } from './zero_ex_instant_provider'; + +export type ZeroExInstantProps = ZeroExInstantProviderProps; + +export const ZeroExInstant: React.StatelessComponent<ZeroExInstantProps> = props => { + return ( + <div className={INJECTED_DIV_CLASS}> + <ZeroExInstantProvider {...props}> + <ZeroExInstantContainer /> + </ZeroExInstantProvider> + </div> + ); +}; diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx index 1d17ed12a..5748e064e 100644 --- a/packages/instant/src/components/zero_ex_instant_container.tsx +++ b/packages/instant/src/components/zero_ex_instant_container.tsx @@ -1,35 +1,78 @@ import * as React from 'react'; +import { AvailableERC20TokenSelector } from '../containers/available_erc20_token_selector'; import { LatestBuyQuoteOrderDetails } from '../containers/latest_buy_quote_order_details'; import { LatestError } from '../containers/latest_error'; -import { SelectedAssetBuyOrderStateButton } from '../containers/selected_asset_buy_order_state_button'; +import { SelectedAssetBuyOrderProgress } from '../containers/selected_asset_buy_order_progress'; +import { SelectedAssetBuyOrderStateButtons } from '../containers/selected_asset_buy_order_state_buttons'; import { SelectedAssetInstantHeading } from '../containers/selected_asset_instant_heading'; - import { ColorOption } from '../style/theme'; +import { zIndex } from '../style/z_index'; -import { Container, Flex } from './ui'; +import { SlideAnimationState } from './animations/slide_animation'; +import { CSSReset } from './css_reset'; +import { SlidingPanel } from './sliding_panel'; +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; export interface ZeroExInstantContainerProps {} +export interface ZeroExInstantContainerState { + tokenSelectionPanelAnimationState: SlideAnimationState; +} -export const ZeroExInstantContainer: React.StatelessComponent<ZeroExInstantContainerProps> = props => ( - <Container width="350px"> - <Container zIndex={1} position="relative"> - <LatestError /> - </Container> - <Container - zIndex={2} - position="relative" - backgroundColor={ColorOption.white} - borderRadius="3px" - hasBoxShadow={true} - > - <Flex direction="column" justify="flex-start"> - <SelectedAssetInstantHeading /> - <LatestBuyQuoteOrderDetails /> - <Container padding="20px" width="100%"> - <SelectedAssetBuyOrderStateButton /> +export class ZeroExInstantContainer extends React.Component<ZeroExInstantContainerProps, ZeroExInstantContainerState> { + public state = { + tokenSelectionPanelAnimationState: 'none' as SlideAnimationState, + }; + public render(): React.ReactNode { + return ( + <React.Fragment> + <CSSReset /> + <Container + width={{ default: '350px', sm: '100%' }} + height={{ default: 'auto', sm: '100%' }} + position="relative" + > + <Container position="relative"> + <LatestError /> + </Container> + <Container + zIndex={zIndex.mainContainer} + position="relative" + backgroundColor={ColorOption.white} + borderRadius="3px" + hasBoxShadow={true} + overflow="hidden" + height="100%" + > + <Flex direction="column" justify="flex-start" height="100%"> + <SelectedAssetInstantHeading onSelectAssetClick={this._handleSymbolClick} /> + <SelectedAssetBuyOrderProgress /> + <LatestBuyQuoteOrderDetails /> + <Container padding="20px" width="100%"> + <SelectedAssetBuyOrderStateButtons /> + </Container> + </Flex> + <SlidingPanel + title="Select Token" + animationState={this.state.tokenSelectionPanelAnimationState} + onClose={this._handlePanelClose} + > + <AvailableERC20TokenSelector onTokenSelect={this._handlePanelClose} /> + </SlidingPanel> + </Container> </Container> - </Flex> - </Container> - </Container> -); + </React.Fragment> + ); + } + private readonly _handleSymbolClick = (): void => { + this.setState({ + tokenSelectionPanelAnimationState: 'slidIn', + }); + }; + private readonly _handlePanelClose = (): void => { + this.setState({ + tokenSelectionPanelAnimationState: 'slidOut', + }); + }; +} diff --git a/packages/instant/src/components/zero_ex_instant_overlay.tsx b/packages/instant/src/components/zero_ex_instant_overlay.tsx new file mode 100644 index 000000000..10438ab7a --- /dev/null +++ b/packages/instant/src/components/zero_ex_instant_overlay.tsx @@ -0,0 +1,40 @@ +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; + +import { Container } from './ui/container'; +import { Flex } from './ui/flex'; +import { Icon } from './ui/icon'; +import { Overlay } from './ui/overlay'; +import { ZeroExInstantContainer } from './zero_ex_instant_container'; +import { ZeroExInstantProvider, ZeroExInstantProviderProps } from './zero_ex_instant_provider'; + +export interface ZeroExInstantOverlayProps extends ZeroExInstantProviderProps { + onClose?: () => void; + zIndex?: number; +} + +export const ZeroExInstantOverlay: React.StatelessComponent<ZeroExInstantOverlayProps> = props => { + const { onClose, zIndex, ...rest } = props; + return ( + <ZeroExInstantProvider {...rest}> + <Overlay zIndex={zIndex}> + <Flex height="100vh"> + <Container position="absolute" top="0px" right="0px" display={{ default: 'initial', sm: 'none' }}> + <Icon + height={18} + width={18} + color={ColorOption.white} + icon="closeX" + onClick={onClose} + padding="2em 2em" + /> + </Container> + <Container width={{ default: 'auto', sm: '100%' }} height={{ default: 'auto', sm: '100%' }}> + <ZeroExInstantContainer /> + </Container> + </Flex> + </Overlay> + </ZeroExInstantProvider> + ); +}; diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx new file mode 100644 index 000000000..411f118cc --- /dev/null +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -0,0 +1,149 @@ +import { ObjectMap } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Provider } from 'ethereum-types'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { Provider as ReduxProvider } from 'react-redux'; + +import { ACCOUNT_UPDATE_INTERVAL_TIME_MS, BUY_QUOTE_UPDATE_INTERVAL_TIME_MS } from '../constants'; +import { SelectedAssetThemeProvider } from '../containers/selected_asset_theme_provider'; +import { asyncData } from '../redux/async_data'; +import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer'; +import { store, Store } from '../redux/store'; +import { fonts } from '../style/fonts'; +import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource } from '../types'; +import { assetUtils } from '../util/asset'; +import { errorFlasher } from '../util/error_flasher'; +import { gasPriceEstimator } from '../util/gas_price_estimator'; +import { Heartbeater } from '../util/heartbeater'; +import { generateAccountHeartbeater, generateBuyQuoteHeartbeater } from '../util/heartbeater_factory'; +import { providerStateFactory } from '../util/provider_state_factory'; + +fonts.include(); + +export type ZeroExInstantProviderProps = ZeroExInstantProviderRequiredProps & + Partial<ZeroExInstantProviderOptionalProps>; + +export interface ZeroExInstantProviderRequiredProps { + orderSource: OrderSource; +} + +export interface ZeroExInstantProviderOptionalProps { + provider: Provider; + availableAssetDatas: string[]; + defaultAssetBuyAmount: number; + defaultSelectedAssetData: string; + additionalAssetMetaDataMap: ObjectMap<AssetMetaData>; + networkId: Network; + affiliateInfo: AffiliateInfo; +} + +export class ZeroExInstantProvider extends React.Component<ZeroExInstantProviderProps> { + private readonly _store: Store; + private _accountUpdateHeartbeat?: Heartbeater; + private _buyQuoteHeartbeat?: Heartbeater; + + // TODO(fragosti): Write tests for this beast once we inject a provider. + private static _mergeDefaultStateWithProps( + props: ZeroExInstantProviderProps, + defaultState: DefaultState = DEFAULT_STATE, + ): State { + // use the networkId passed in with the props, otherwise default to that of the default state (1, mainnet) + const networkId = props.networkId || defaultState.network; + // construct the ProviderState + const providerState = providerStateFactory.getInitialProviderState( + props.orderSource, + networkId, + props.provider, + ); + // merge the additional additionalAssetMetaDataMap with our default map + const completeAssetMetaDataMap = { + ...props.additionalAssetMetaDataMap, + ...defaultState.assetMetaDataMap, + }; + // construct the final state + const storeStateFromProps: State = { + ...defaultState, + providerState, + network: networkId, + selectedAsset: _.isUndefined(props.defaultSelectedAssetData) + ? undefined + : assetUtils.createAssetFromAssetDataOrThrow( + props.defaultSelectedAssetData, + completeAssetMetaDataMap, + networkId, + ), + selectedAssetAmount: _.isUndefined(props.defaultAssetBuyAmount) + ? undefined + : new BigNumber(props.defaultAssetBuyAmount), + availableAssets: _.isUndefined(props.availableAssetDatas) + ? undefined + : assetUtils.createAssetsFromAssetDatas(props.availableAssetDatas, completeAssetMetaDataMap, networkId), + assetMetaDataMap: completeAssetMetaDataMap, + affiliateInfo: props.affiliateInfo, + }; + return storeStateFromProps; + } + constructor(props: ZeroExInstantProviderProps) { + super(props); + const initialAppState = ZeroExInstantProvider._mergeDefaultStateWithProps(this.props); + this._store = store.create(initialAppState); + } + public componentDidMount(): void { + const state = this._store.getState(); + // tslint:disable-next-line:no-floating-promises + asyncData.fetchEthPriceAndDispatchToStore(this._store); + // fetch available assets if none are specified + if (_.isUndefined(state.availableAssets)) { + // tslint:disable-next-line:no-floating-promises + asyncData.fetchAvailableAssetDatasAndDispatchToStore(this._store); + } + if (state.providerState.account.state !== AccountState.None) { + this._accountUpdateHeartbeat = generateAccountHeartbeater({ + store: this._store, + shouldPerformImmediatelyOnStart: true, + }); + this._accountUpdateHeartbeat.start(ACCOUNT_UPDATE_INTERVAL_TIME_MS); + } + + this._buyQuoteHeartbeat = generateBuyQuoteHeartbeater({ + store: this._store, + shouldPerformImmediatelyOnStart: false, + }); + this._buyQuoteHeartbeat.start(BUY_QUOTE_UPDATE_INTERVAL_TIME_MS); + // tslint:disable-next-line:no-floating-promises + asyncData.fetchCurrentBuyQuoteAndDispatchToStore({ store: this._store, shouldSetPending: true }); + // warm up the gas price estimator cache just in case we can't + // grab the gas price estimate when submitting the transaction + // tslint:disable-next-line:no-floating-promises + gasPriceEstimator.getGasInfoAsync(); + // tslint:disable-next-line:no-floating-promises + this._flashErrorIfWrongNetwork(); + } + public componentWillUnmount(): void { + if (this._accountUpdateHeartbeat) { + this._accountUpdateHeartbeat.stop(); + } + if (this._buyQuoteHeartbeat) { + this._buyQuoteHeartbeat.stop(); + } + } + public render(): React.ReactNode { + return ( + <ReduxProvider store={this._store}> + <SelectedAssetThemeProvider>{this.props.children}</SelectedAssetThemeProvider> + </ReduxProvider> + ); + } + private readonly _flashErrorIfWrongNetwork = async (): Promise<void> => { + const msToShowError = 30000; // 30 seconds + const state = this._store.getState(); + const network = state.network; + const web3Wrapper = state.providerState.web3Wrapper; + const networkOfProvider = await web3Wrapper.getNetworkIdAsync(); + if (network !== networkOfProvider) { + const errorMessage = `Wrong network detected. Try switching to ${Network[network]}.`; + errorFlasher.flashNewErrorMessage(this._store.dispatch, errorMessage, msToShowError); + } + }; +} diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index 48d0d4aa2..110a8248a 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -1,5 +1,35 @@ import { BigNumber } from '@0x/utils'; + +import { AccountNotReady, AccountState, Network } from './types'; + export const BIG_NUMBER_ZERO = new BigNumber(0); -export const ethDecimals = 18; +export const ETH_DECIMALS = 18; export const DEFAULT_ZERO_EX_CONTAINER_SELECTOR = '#zeroExInstantContainer'; +export const INJECTED_DIV_CLASS = 'zeroExInstantResetRoot'; +export const INJECTED_DIV_ID = 'zeroExInstant'; export const WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX = 'Transaction failed'; +export const GWEI_IN_WEI = new BigNumber(1000000000); +export const ONE_SECOND_MS = 1000; +export const ONE_MINUTE_MS = ONE_SECOND_MS * 60; +export const ACCOUNT_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 5; +export const BUY_QUOTE_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 15; +export const DEFAULT_GAS_PRICE = GWEI_IN_WEI.mul(6); +export const DEFAULT_ESTIMATED_TRANSACTION_TIME_MS = ONE_MINUTE_MS * 2; +export const ETH_GAS_STATION_API_BASE_URL = 'https://ethgasstation.info'; +export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2'; +export const PROGRESS_STALL_AT_WIDTH = '95%'; +export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200; +export const ETHEREUM_NODE_URL_BY_NETWORK = { + [Network.Mainnet]: 'https://mainnet.infura.io/', + [Network.Kovan]: 'https://kovan.infura.io/', +}; +export const BLOCK_POLLING_INTERVAL_MS = 10000; // 10s +export const NO_ACCOUNT: AccountNotReady = { + state: AccountState.None, +}; +export const LOADING_ACCOUNT: AccountNotReady = { + state: AccountState.Loading, +}; +export const LOCKED_ACCOUNT: AccountNotReady = { + state: AccountState.Locked, +}; diff --git a/packages/instant/src/containers/available_erc20_token_selector.ts b/packages/instant/src/containers/available_erc20_token_selector.ts new file mode 100644 index 000000000..4d4218d22 --- /dev/null +++ b/packages/instant/src/containers/available_erc20_token_selector.ts @@ -0,0 +1,45 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; + +import { State } from '../redux/reducer'; +import { ERC20Asset } from '../types'; +import { assetUtils } from '../util/asset'; + +import { ERC20TokenSelector } from '../components/erc20_token_selector'; +import { Action, actions } from '../redux/actions'; + +export interface AvailableERC20TokenSelectorProps { + onTokenSelect?: (token: ERC20Asset) => void; +} + +interface ConnectedState { + tokens: ERC20Asset[]; +} + +interface ConnectedDispatch { + onTokenSelect: (token: ERC20Asset) => void; +} + +const mapStateToProps = (state: State, _ownProps: AvailableERC20TokenSelectorProps): ConnectedState => ({ + tokens: assetUtils.getERC20AssetsFromAssets(state.availableAssets || []), +}); + +const mapDispatchToProps = ( + dispatch: Dispatch<Action>, + ownProps: AvailableERC20TokenSelectorProps, +): ConnectedDispatch => ({ + onTokenSelect: (token: ERC20Asset) => { + dispatch(actions.updateSelectedAsset(token)); + dispatch(actions.resetAmount()); + if (ownProps.onTokenSelect) { + ownProps.onTokenSelect(token); + } + }, +}); + +export const AvailableERC20TokenSelector: React.ComponentClass<AvailableERC20TokenSelectorProps> = connect( + mapStateToProps, + mapDispatchToProps, +)(ERC20TokenSelector); diff --git a/packages/instant/src/containers/latest_buy_quote_order_details.ts b/packages/instant/src/containers/latest_buy_quote_order_details.ts index 092aaaf20..2b59ed3ae 100644 --- a/packages/instant/src/containers/latest_buy_quote_order_details.ts +++ b/packages/instant/src/containers/latest_buy_quote_order_details.ts @@ -22,7 +22,7 @@ const mapStateToProps = (state: State, _ownProps: LatestBuyQuoteOrderDetailsProp // use the worst case quote info buyQuoteInfo: oc(state).latestBuyQuote.worstCaseQuoteInfo(), ethUsdPrice: state.ethUsdPrice, - isLoading: state.quoteRequestState === AsyncProcessState.PENDING, + isLoading: state.quoteRequestState === AsyncProcessState.Pending, }); export const LatestBuyQuoteOrderDetails: React.ComponentClass<LatestBuyQuoteOrderDetailsProps> = connect( diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx index b75ec00aa..c0da181f1 100644 --- a/packages/instant/src/containers/latest_error.tsx +++ b/packages/instant/src/containers/latest_error.tsx @@ -1,36 +1,60 @@ import * as React from 'react'; import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { SlideAnimationState } from '../components/animations/slide_animation'; import { SlidingError } from '../components/sliding_error'; +import { Overlay } from '../components/ui/overlay'; +import { Action } from '../redux/actions'; import { State } from '../redux/reducer'; -import { Asset, DisplayStatus } from '../types'; -import { errorUtil } from '../util/error'; +import { ScreenWidths } from '../style/media'; +import { generateOverlayBlack } from '../style/theme'; +import { zIndex } from '../style/z_index'; +import { Asset, DisplayStatus, Omit } from '../types'; +import { errorFlasher } from '../util/error_flasher'; export interface LatestErrorComponentProps { asset?: Asset; - latestError?: any; - slidingDirection: 'down' | 'up'; + latestErrorMessage?: string; + animationState: SlideAnimationState; + shouldRenderOverlay: boolean; + onOverlayClick: () => void; } export const LatestErrorComponent: React.StatelessComponent<LatestErrorComponentProps> = props => { - if (!props.latestError) { + if (!props.latestErrorMessage) { return <div />; } - const { icon, message } = errorUtil.errorDescription(props.latestError, props.asset); - return <SlidingError direction={props.slidingDirection} icon={icon} message={message} />; + return ( + <React.Fragment> + <SlidingError animationState={props.animationState} icon="😢" message={props.latestErrorMessage} /> + {props.shouldRenderOverlay && ( + <Overlay + onClick={props.onOverlayClick} + zIndex={zIndex.containerOverlay} + showMaxWidth={ScreenWidths.Sm} + backgroundColor={generateOverlayBlack(0.4)} + /> + )} + </React.Fragment> + ); }; -interface ConnectedState { - asset?: Asset; - latestError?: any; - slidingDirection: 'down' | 'up'; -} export interface LatestErrorProps {} +interface ConnectedState extends Omit<LatestErrorComponentProps, 'onOverlayClick'> {} const mapStateToProps = (state: State, _ownProps: LatestErrorProps): ConnectedState => ({ asset: state.selectedAsset, - latestError: state.latestError, - slidingDirection: state.latestErrorDisplay === DisplayStatus.Present ? 'up' : 'down', + latestErrorMessage: state.latestErrorMessage, + animationState: state.latestErrorDisplayStatus === DisplayStatus.Present ? 'slidIn' : 'slidOut', + shouldRenderOverlay: state.latestErrorDisplayStatus === DisplayStatus.Present, +}); + +type ConnectedDispatch = Pick<LatestErrorComponentProps, 'onOverlayClick'>; +const mapDispatchToProps = (dispatch: Dispatch<Action>, _ownProps: LatestErrorProps): ConnectedDispatch => ({ + onOverlayClick: () => { + errorFlasher.clearError(dispatch); + }, }); -export const LatestError = connect(mapStateToProps)(LatestErrorComponent); +export const LatestError = connect(mapStateToProps, mapDispatchToProps)(LatestErrorComponent); diff --git a/packages/instant/src/containers/selected_asset_amount_input.ts b/packages/instant/src/containers/selected_asset_amount_input.ts deleted file mode 100644 index e9dbc61ce..000000000 --- a/packages/instant/src/containers/selected_asset_amount_input.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { AssetBuyer, BuyQuote } from '@0x/asset-buyer'; -import { AssetProxyId } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as _ from 'lodash'; -import * as React from 'react'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; - -import { Action, actions } from '../redux/actions'; -import { State } from '../redux/reducer'; -import { ColorOption } from '../style/theme'; -import { ERC20Asset, OrderProcessState } from '../types'; -import { errorUtil } from '../util/error'; - -import { AssetAmountInput } from '../components/asset_amount_input'; - -export interface SelectedAssetAmountInputProps { - fontColor?: ColorOption; - fontSize?: string; -} - -interface ConnectedState { - assetBuyer?: AssetBuyer; - value?: BigNumber; - asset?: ERC20Asset; -} - -interface ConnectedDispatch { - updateBuyQuote: (assetBuyer?: AssetBuyer, value?: BigNumber, asset?: ERC20Asset) => void; -} - -interface ConnectedProps { - value?: BigNumber; - asset?: ERC20Asset; - onChange: (value?: BigNumber, asset?: ERC20Asset) => void; -} - -type FinalProps = ConnectedProps & SelectedAssetAmountInputProps; - -const mapStateToProps = (state: State, _ownProps: SelectedAssetAmountInputProps): ConnectedState => { - const selectedAsset = state.selectedAsset; - if (_.isUndefined(selectedAsset) || selectedAsset.metaData.assetProxyId !== AssetProxyId.ERC20) { - return { - value: state.selectedAssetAmount, - }; - } - return { - assetBuyer: state.assetBuyer, - value: state.selectedAssetAmount, - asset: selectedAsset as ERC20Asset, - }; -}; - -const updateBuyQuoteAsync = async ( - assetBuyer: AssetBuyer, - dispatch: Dispatch<Action>, - asset: ERC20Asset, - assetAmount: BigNumber, -): Promise<void> => { - // get a new buy quote. - const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetAmount, asset.metaData.decimals); - - // mark quote as pending - dispatch(actions.setQuoteRequestStatePending()); - - let newBuyQuote: BuyQuote | undefined; - try { - newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue); - } catch (error) { - dispatch(actions.setQuoteRequestStateFailure()); - errorUtil.errorFlasher.flashNewError(dispatch, error); - return; - } - // We have a successful new buy quote - errorUtil.errorFlasher.clearError(dispatch); - // invalidate the last buy quote. - dispatch(actions.updateLatestBuyQuote(newBuyQuote)); -}; - -const debouncedUpdateBuyQuoteAsync = _.debounce(updateBuyQuoteAsync, 200, { trailing: true }); - -const mapDispatchToProps = ( - dispatch: Dispatch<Action>, - _ownProps: SelectedAssetAmountInputProps, -): ConnectedDispatch => ({ - updateBuyQuote: (assetBuyer, value, asset) => { - // Update the input - dispatch(actions.updateSelectedAssetAmount(value)); - // invalidate the last buy quote. - dispatch(actions.updateLatestBuyQuote(undefined)); - // reset our buy state - dispatch(actions.updateBuyOrderState({ processState: OrderProcessState.NONE })); - - if (!_.isUndefined(value) && !_.isUndefined(asset) && !_.isUndefined(assetBuyer)) { - // even if it's debounced, give them the illusion it's loading - dispatch(actions.setQuoteRequestStatePending()); - // tslint:disable-next-line:no-floating-promises - debouncedUpdateBuyQuoteAsync(assetBuyer, dispatch, asset, value); - } - }, -}); - -const mergeProps = ( - connectedState: ConnectedState, - connectedDispatch: ConnectedDispatch, - ownProps: SelectedAssetAmountInputProps, -): FinalProps => { - return { - ...ownProps, - asset: connectedState.asset, - value: connectedState.value, - onChange: (value, asset) => { - connectedDispatch.updateBuyQuote(connectedState.assetBuyer, value, asset); - }, - }; -}; - -export const SelectedAssetAmountInput: React.ComponentClass<SelectedAssetAmountInputProps> = connect( - mapStateToProps, - mapDispatchToProps, - mergeProps, -)(AssetAmountInput); diff --git a/packages/instant/src/containers/selected_asset_buy_button.ts b/packages/instant/src/containers/selected_asset_buy_button.ts deleted file mode 100644 index adcbd61bc..000000000 --- a/packages/instant/src/containers/selected_asset_buy_button.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { AssetBuyer, BuyQuote } from '@0x/asset-buyer'; -import * as _ from 'lodash'; -import * as React from 'react'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; - -import { Action, actions } from '../redux/actions'; -import { State } from '../redux/reducer'; -import { OrderProcessState, OrderState } from '../types'; - -import { BuyButton } from '../components/buy_button'; - -export interface SelectedAssetBuyButtonProps {} - -interface ConnectedState { - assetBuyer?: AssetBuyer; - buyQuote?: BuyQuote; -} - -interface ConnectedDispatch { - onAwaitingSignature: (buyQuote: BuyQuote) => void; - onSignatureDenied: (buyQuote: BuyQuote, error: Error) => void; - onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => void; - onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void; - onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void; -} - -const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyButtonProps): ConnectedState => ({ - assetBuyer: state.assetBuyer, - buyQuote: state.latestBuyQuote, -}); - -const mapDispatchToProps = (dispatch: Dispatch<Action>, ownProps: SelectedAssetBuyButtonProps): ConnectedDispatch => ({ - onAwaitingSignature: (buyQuote: BuyQuote) => { - const newOrderState: OrderState = { processState: OrderProcessState.AWAITING_SIGNATURE }; - dispatch(actions.updateBuyOrderState(newOrderState)); - }, - onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => { - const newOrderState: OrderState = { processState: OrderProcessState.PROCESSING, txHash }; - dispatch(actions.updateBuyOrderState(newOrderState)); - }, - onBuySuccess: (buyQuote: BuyQuote, txHash: string) => - dispatch(actions.updateBuyOrderState({ processState: OrderProcessState.SUCCESS, txHash })), - onBuyFailure: (buyQuote: BuyQuote, txHash: string) => - dispatch(actions.updateBuyOrderState({ processState: OrderProcessState.FAILURE, txHash })), - onSignatureDenied: (buyQuote, error) => { - dispatch(actions.resetAmount()); - dispatch(actions.setError(error)); - }, -}); - -export const SelectedAssetBuyButton: React.ComponentClass<SelectedAssetBuyButtonProps> = connect( - mapStateToProps, - mapDispatchToProps, -)(BuyButton); diff --git a/packages/instant/src/containers/selected_asset_buy_order_progress.ts b/packages/instant/src/containers/selected_asset_buy_order_progress.ts new file mode 100644 index 000000000..7c8c24676 --- /dev/null +++ b/packages/instant/src/containers/selected_asset_buy_order_progress.ts @@ -0,0 +1,13 @@ +import { connect } from 'react-redux'; + +import { BuyOrderProgress } from '../components/buy_order_progress'; +import { State } from '../redux/reducer'; +import { OrderState } from '../types'; + +interface ConnectedState { + buyOrderState: OrderState; +} +const mapStateToProps = (state: State, _ownProps: {}): ConnectedState => ({ + buyOrderState: state.buyOrderState, +}); +export const SelectedAssetBuyOrderProgress = connect(mapStateToProps)(BuyOrderProgress); diff --git a/packages/instant/src/containers/selected_asset_buy_order_state_button.tsx b/packages/instant/src/containers/selected_asset_buy_order_state_button.tsx deleted file mode 100644 index 7faa79912..000000000 --- a/packages/instant/src/containers/selected_asset_buy_order_state_button.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import * as _ from 'lodash'; -import * as React from 'react'; -import { connect } from 'react-redux'; - -import { State } from '../redux/reducer'; -import { OrderProcessState } from '../types'; - -import { BuyOrderStateButton } from '../components/buy_order_state_button'; - -interface ConnectedState { - buyOrderProcessingState: OrderProcessState; -} -export interface SelectedAssetButtonProps {} -const mapStateToProps = (state: State, _ownProps: SelectedAssetButtonProps): ConnectedState => ({ - buyOrderProcessingState: state.buyOrderState.processState, -}); - -export const SelectedAssetBuyOrderStateButton: React.ComponentClass<SelectedAssetButtonProps> = connect( - mapStateToProps, -)(BuyOrderStateButton); diff --git a/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts new file mode 100644 index 000000000..610335243 --- /dev/null +++ b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts @@ -0,0 +1,104 @@ +import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; + +import { BuyOrderStateButtons } from '../components/buy_order_state_buttons'; +import { Action, actions } from '../redux/actions'; +import { State } from '../redux/reducer'; +import { AccountState, AffiliateInfo, OrderProcessState, ZeroExInstantError } from '../types'; +import { errorFlasher } from '../util/error_flasher'; +import { etherscanUtil } from '../util/etherscan'; + +interface ConnectedState { + accountAddress?: string; + accountEthBalanceInWei?: BigNumber; + buyQuote?: BuyQuote; + buyOrderProcessingState: OrderProcessState; + assetBuyer: AssetBuyer; + web3Wrapper: Web3Wrapper; + affiliateInfo?: AffiliateInfo; + onViewTransaction: () => void; +} + +interface ConnectedDispatch { + onValidationPending: (buyQuote: BuyQuote) => void; + onSignatureDenied: (buyQuote: BuyQuote) => void; + onBuyProcessing: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => void; + onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void; + onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void; + onRetry: () => void; + onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void; +} +export interface SelectedAssetBuyOrderStateButtons {} +const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButtons): ConnectedState => { + const assetBuyer = state.providerState.assetBuyer; + const web3Wrapper = state.providerState.web3Wrapper; + const account = state.providerState.account; + const accountAddress = account.state === AccountState.Ready ? account.address : undefined; + const accountEthBalanceInWei = account.state === AccountState.Ready ? account.ethBalanceInWei : undefined; + return { + accountAddress, + accountEthBalanceInWei, + buyOrderProcessingState: state.buyOrderState.processState, + assetBuyer, + web3Wrapper, + buyQuote: state.latestBuyQuote, + affiliateInfo: state.affiliateInfo, + onViewTransaction: () => { + if ( + state.buyOrderState.processState === OrderProcessState.Processing || + state.buyOrderState.processState === OrderProcessState.Success || + state.buyOrderState.processState === OrderProcessState.Failure + ) { + const etherscanUrl = etherscanUtil.getEtherScanTxnAddressIfExists( + state.buyOrderState.txHash, + assetBuyer.networkId, + ); + if (etherscanUrl) { + window.open(etherscanUrl, '_blank'); + return; + } + } + }, + }; +}; + +const mapDispatchToProps = ( + dispatch: Dispatch<Action>, + ownProps: SelectedAssetBuyOrderStateButtons, +): ConnectedDispatch => ({ + onValidationPending: (buyQuote: BuyQuote) => { + dispatch(actions.setBuyOrderStateValidating()); + }, + onBuyProcessing: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => { + dispatch(actions.setBuyOrderStateProcessing(txHash, startTimeUnix, expectedEndTimeUnix)); + }, + onBuySuccess: (buyQuote: BuyQuote, txHash: string) => dispatch(actions.setBuyOrderStateSuccess(txHash)), + onBuyFailure: (buyQuote: BuyQuote, txHash: string) => dispatch(actions.setBuyOrderStateFailure(txHash)), + onSignatureDenied: () => { + dispatch(actions.resetAmount()); + const errorMessage = 'You denied this transaction'; + errorFlasher.flashNewErrorMessage(dispatch, errorMessage); + }, + onValidationFail: (buyQuote, error) => { + dispatch(actions.setBuyOrderStateNone()); + if (error === ZeroExInstantError.InsufficientETH) { + const errorMessage = "You don't have enough ETH"; + errorFlasher.flashNewErrorMessage(dispatch, errorMessage); + } else { + errorFlasher.flashNewErrorMessage(dispatch); + } + }, + onRetry: () => { + dispatch(actions.resetAmount()); + }, +}); + +export const SelectedAssetBuyOrderStateButtons: React.ComponentClass<SelectedAssetBuyOrderStateButtons> = connect( + mapStateToProps, + mapDispatchToProps, +)(BuyOrderStateButtons); diff --git a/packages/instant/src/containers/selected_asset_instant_heading.ts b/packages/instant/src/containers/selected_asset_instant_heading.ts index 6b2a29b07..a407279e6 100644 --- a/packages/instant/src/containers/selected_asset_instant_heading.ts +++ b/packages/instant/src/containers/selected_asset_instant_heading.ts @@ -5,11 +5,13 @@ import { connect } from 'react-redux'; import { oc } from 'ts-optchain'; import { State } from '../redux/reducer'; -import { AsyncProcessState, OrderState } from '../types'; +import { AsyncProcessState, ERC20Asset, OrderState } from '../types'; import { InstantHeading } from '../components/instant_heading'; -export interface InstantHeadingProps {} +export interface InstantHeadingProps { + onSelectAssetClick?: (asset?: ERC20Asset) => void; +} interface ConnectedState { selectedAssetAmount?: BigNumber; diff --git a/packages/instant/src/containers/selected_asset_retry_button.tsx b/packages/instant/src/containers/selected_asset_retry_button.tsx deleted file mode 100644 index b2b140be6..000000000 --- a/packages/instant/src/containers/selected_asset_retry_button.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as _ from 'lodash'; -import * as React from 'react'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; - -import { Action, actions } from '../redux/actions'; - -import { RetryButton } from '../components/retry_button'; - -export interface SelectedAssetRetryButtonProps {} - -interface ConnectedDispatch { - onClick: () => void; -} - -const mapDispatchToProps = ( - dispatch: Dispatch<Action>, - _ownProps: SelectedAssetRetryButtonProps, -): ConnectedDispatch => ({ - onClick: () => dispatch(actions.resetAmount()), -}); - -export const SelectedAssetRetryButton: React.ComponentClass<SelectedAssetRetryButtonProps> = connect( - undefined, - mapDispatchToProps, -)(RetryButton); diff --git a/packages/instant/src/containers/selected_asset_view_transaction_button.tsx b/packages/instant/src/containers/selected_asset_view_transaction_button.tsx deleted file mode 100644 index 064b877be..000000000 --- a/packages/instant/src/containers/selected_asset_view_transaction_button.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import * as _ from 'lodash'; -import * as React from 'react'; -import { connect } from 'react-redux'; - -import { State } from '../redux/reducer'; - -import { ViewTransactionButton } from '../components/view_transaction_button'; -import { OrderProcessState } from '../types'; -import { etherscanUtil } from '../util/etherscan'; - -export interface SelectedAssetViewTransactionButtonProps {} - -interface ConnectedState { - onClick: () => void; -} - -const mapStateToProps = (state: State, _ownProps: {}): ConnectedState => ({ - onClick: () => { - if ( - state.assetBuyer && - (state.buyOrderState.processState === OrderProcessState.PROCESSING || - state.buyOrderState.processState === OrderProcessState.SUCCESS) - ) { - const etherscanUrl = etherscanUtil.getEtherScanTxnAddressIfExists( - state.buyOrderState.txHash, - state.assetBuyer.networkId, - ); - if (etherscanUrl) { - window.open(etherscanUrl, '_blank'); - return; - } - } - }, -}); - -export const SelectedAssetViewTransactionButton: React.ComponentClass< - SelectedAssetViewTransactionButtonProps -> = connect(mapStateToProps)(ViewTransactionButton); diff --git a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts new file mode 100644 index 000000000..93ff3db70 --- /dev/null +++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts @@ -0,0 +1,116 @@ +import { AssetBuyer } from '@0x/asset-buyer'; +import { AssetProxyId } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; + +import { ERC20AssetAmountInput } from '../components/erc20_asset_amount_input'; +import { Action, actions } from '../redux/actions'; +import { State } from '../redux/reducer'; +import { ColorOption } from '../style/theme'; +import { AffiliateInfo, ERC20Asset, OrderProcessState } from '../types'; +import { buyQuoteUpdater } from '../util/buy_quote_updater'; + +export interface SelectedERC20AssetAmountInputProps { + fontColor?: ColorOption; + startingFontSizePx: number; + onSelectAssetClick?: (asset?: ERC20Asset) => void; +} + +interface ConnectedState { + assetBuyer: AssetBuyer; + value?: BigNumber; + asset?: ERC20Asset; + isDisabled: boolean; + numberOfAssetsAvailable?: number; + affiliateInfo?: AffiliateInfo; +} + +interface ConnectedDispatch { + updateBuyQuote: ( + assetBuyer: AssetBuyer, + value?: BigNumber, + asset?: ERC20Asset, + affiliateInfo?: AffiliateInfo, + ) => void; +} + +interface ConnectedProps { + value?: BigNumber; + asset?: ERC20Asset; + onChange: (value?: BigNumber, asset?: ERC20Asset) => void; + isDisabled: boolean; + numberOfAssetsAvailable?: number; +} + +type FinalProps = ConnectedProps & SelectedERC20AssetAmountInputProps; + +const mapStateToProps = (state: State, _ownProps: SelectedERC20AssetAmountInputProps): ConnectedState => { + const processState = state.buyOrderState.processState; + const isEnabled = processState === OrderProcessState.None || processState === OrderProcessState.Failure; + const isDisabled = !isEnabled; + const selectedAsset = + !_.isUndefined(state.selectedAsset) && state.selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20 + ? (state.selectedAsset as ERC20Asset) + : undefined; + const numberOfAssetsAvailable = _.isUndefined(state.availableAssets) ? undefined : state.availableAssets.length; + const assetBuyer = state.providerState.assetBuyer; + return { + assetBuyer, + value: state.selectedAssetAmount, + asset: selectedAsset, + isDisabled, + numberOfAssetsAvailable, + affiliateInfo: state.affiliateInfo, + }; +}; + +const debouncedUpdateBuyQuoteAsync = _.debounce(buyQuoteUpdater.updateBuyQuoteAsync.bind(buyQuoteUpdater), 200, { + trailing: true, +}) as typeof buyQuoteUpdater.updateBuyQuoteAsync; + +const mapDispatchToProps = ( + dispatch: Dispatch<Action>, + _ownProps: SelectedERC20AssetAmountInputProps, +): ConnectedDispatch => ({ + updateBuyQuote: (assetBuyer, value, asset, affiliateInfo) => { + // Update the input + dispatch(actions.updateSelectedAssetAmount(value)); + // invalidate the last buy quote. + dispatch(actions.updateLatestBuyQuote(undefined)); + // reset our buy state + dispatch(actions.setBuyOrderStateNone()); + + if (!_.isUndefined(value) && value.greaterThan(0) && !_.isUndefined(asset)) { + // even if it's debounced, give them the illusion it's loading + dispatch(actions.setQuoteRequestStatePending()); + // tslint:disable-next-line:no-floating-promises + debouncedUpdateBuyQuoteAsync(assetBuyer, dispatch, asset, value, true, affiliateInfo); + } + }, +}); + +const mergeProps = ( + connectedState: ConnectedState, + connectedDispatch: ConnectedDispatch, + ownProps: SelectedERC20AssetAmountInputProps, +): FinalProps => { + return { + ...ownProps, + asset: connectedState.asset, + value: connectedState.value, + onChange: (value, asset) => { + connectedDispatch.updateBuyQuote(connectedState.assetBuyer, value, asset, connectedState.affiliateInfo); + }, + isDisabled: connectedState.isDisabled, + numberOfAssetsAvailable: connectedState.numberOfAssetsAvailable, + }; +}; + +export const SelectedERC20AssetAmountInput: React.ComponentClass<SelectedERC20AssetAmountInputProps> = connect( + mapStateToProps, + mapDispatchToProps, + mergeProps, +)(ERC20AssetAmountInput); diff --git a/packages/instant/src/data/asset_data_network_mapping.ts b/packages/instant/src/data/asset_data_network_mapping.ts index e8ccbf011..4fd0a25ed 100644 --- a/packages/instant/src/data/asset_data_network_mapping.ts +++ b/packages/instant/src/data/asset_data_network_mapping.ts @@ -8,8 +8,59 @@ interface AssetDataByNetwork { } export const assetDataNetworkMapping: AssetDataByNetwork[] = [ + // ZRX { [Network.Mainnet]: '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498', [Network.Kovan]: '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa', }, + // SPANK + { + [Network.Mainnet]: '0xf47261b000000000000000000000000042d6622dece394b54999fbd73d108123806f6a18', + [Network.Kovan]: '0xf47261b00000000000000000000000007c9eee8448f3a7d1193389652d863b27e543272d', + }, + // OMG + { + [Network.Mainnet]: '0xf47261b0000000000000000000000000d26114cd6ee289accf82350c8d8487fedb8a0c07', + [Network.Kovan]: '0xf47261b000000000000000000000000046096d8ec059dbaae2950b30e01634ff0dc652ec', + }, + // MKR + { + [Network.Mainnet]: '0xf47261b00000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2', + // 0x Kovan MKR + [Network.Kovan]: '0xf47261b00000000000000000000000007b6b10caa9e8e9552ba72638ea5b47c25afea1f3', + }, + // BAT + { + [Network.Mainnet]: '0xf47261b00000000000000000000000000d8775f648430679a709e98d2b0cb6250d2887ef', + [Network.Kovan]: '0xf47261b0000000000000000000000000c87faa7a58f0adf306bad9e7d892fb045a20e5af', + }, + // SNT + { + [Network.Mainnet]: '0xf47261b0000000000000000000000000744d70fdbe2ba4cf95131626614a1763df805b9e', + [Network.Kovan]: '0xf47261b00000000000000000000000009cfe76a718ea75e3e8ce4fc7ad0fef84be70919b', + }, + // MANA + { + [Network.Mainnet]: '0xf47261b00000000000000000000000000f5d2fb29fb7d3cfee444a200298f468908cc942', + [Network.Kovan]: '0xf47261b0000000000000000000000000c64edfc78321673435fbeebdaaa7f9d755963542', + }, + // GNT + { + [Network.Mainnet]: '0xf47261b0000000000000000000000000a74476443119a942de498590fe1f2454d7d4ac0d', + // 0x Kovan GNT + [Network.Kovan]: '0xf47261b000000000000000000000000031fb614e223706f15d0d3c5f4b08bdf0d5c78623', + }, + // SUB + { + [Network.Mainnet]: '0xf47261b000000000000000000000000012480e24eb5bec1a9d4369cab6a80cad3c0a377a', + }, + // Dentacoin + { + [Network.Mainnet]: '0xf47261b000000000000000000000000008d32b0da63e2C3bcF8019c9c5d849d7a9d791e6', + }, + // REP + { + [Network.Kovan]: '0xf47261b00000000000000000000000008cb3971b8eb709c14616bd556ff6683019e90d9c', + [Network.Mainnet]: '0xf47261b00000000000000000000000001985365e9f78359a9b6ad760e32412f4a445e862', + }, ]; diff --git a/packages/instant/src/data/asset_meta_data_map.ts b/packages/instant/src/data/asset_meta_data_map.ts index 3a820a0c4..970b6c383 100644 --- a/packages/instant/src/data/asset_meta_data_map.ts +++ b/packages/instant/src/data/asset_meta_data_map.ts @@ -10,5 +10,76 @@ export const assetMetaDataMap: ObjectMap<AssetMetaData> = { decimals: 18, primaryColor: 'rgb(54, 50, 60)', symbol: 'zrx', + name: '0x', + }, + '0xf47261b000000000000000000000000042d6622dece394b54999fbd73d108123806f6a18': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: '#ec3e6c', + symbol: 'spank', + name: 'Spank', + }, + '0xf47261b0000000000000000000000000d26114cd6ee289accf82350c8d8487fedb8a0c07': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: '#2e61ea', + symbol: 'omg', + name: 'OmiseGo', + }, + '0xf47261b00000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: '#87e4ca', + symbol: 'mkr', + name: 'Maker', + }, + '0xf47261b00000000000000000000000000d8775f648430679a709e98d2b0cb6250d2887ef': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: '#9c326c', + symbol: 'bat', + name: 'Basic Attention Token', + }, + '0xf47261b0000000000000000000000000744d70fdbe2ba4cf95131626614a1763df805b9e': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: '#5663b0', + symbol: 'snt', + name: 'Status', + }, + '0xf47261b00000000000000000000000000f5d2fb29fb7d3cfee444a200298f468908cc942': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: '#f08839', + symbol: 'mana', + name: 'Decentraland', + }, + '0xf47261b0000000000000000000000000a74476443119a942de498590fe1f2454d7d4ac0d': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: '#263469', + symbol: 'gnt', + name: 'Golem', + }, + '0xf47261b000000000000000000000000012480e24eb5bec1a9d4369cab6a80cad3c0a377a': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: '#de5445', + symbol: 'sub', + name: 'Substratum', + }, + '0xf47261b000000000000000000000000008d32b0da63e2C3bcF8019c9c5d849d7a9d791e6': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: '#000', + symbol: 'dentacoin', + name: 'Dentacoin', + }, + '0xf47261b00000000000000000000000001985365e9f78359a9b6ad760e32412f4a445e862': { + assetProxyId: AssetProxyId.ERC20, + decimals: 18, + primaryColor: '#512D80', + symbol: 'rep', + name: 'Augur', }, }; diff --git a/packages/instant/src/index.ts b/packages/instant/src/index.ts index 54059cdad..6e611dae8 100644 --- a/packages/instant/src/index.ts +++ b/packages/instant/src/index.ts @@ -1 +1,2 @@ export { ZeroExInstant, ZeroExInstantProps } from './components/zero_ex_instant'; +export { ZeroExInstantOverlay, ZeroExInstantOverlayProps } from './components/zero_ex_instant_overlay'; diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts index f648b37f2..0274db30c 100644 --- a/packages/instant/src/index.umd.ts +++ b/packages/instant/src/index.umd.ts @@ -1,9 +1,56 @@ +import * as _ from 'lodash'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR } from './constants'; -import { ZeroExInstant, ZeroExInstantProps } from './index'; +import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR, INJECTED_DIV_CLASS, INJECTED_DIV_ID } from './constants'; +import { ZeroExInstantOverlay, ZeroExInstantOverlayProps } from './index'; +import { assert } from './util/assert'; -export const render = (props: ZeroExInstantProps, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => { - ReactDOM.render(React.createElement(ZeroExInstant, props), document.querySelector(selector)); +export const render = (props: ZeroExInstantOverlayProps, selector: string = DEFAULT_ZERO_EX_CONTAINER_SELECTOR) => { + assert.isValidOrderSource('orderSource', props.orderSource); + if (!_.isUndefined(props.defaultSelectedAssetData)) { + assert.isHexString('defaultSelectedAssetData', props.defaultSelectedAssetData); + } + if (!_.isUndefined(props.additionalAssetMetaDataMap)) { + assert.isValidAssetMetaDataMap('props.additionalAssetMetaDataMap', props.additionalAssetMetaDataMap); + } + if (!_.isUndefined(props.defaultAssetBuyAmount)) { + assert.isNumber('props.defaultAssetBuyAmount', props.defaultAssetBuyAmount); + } + if (!_.isUndefined(props.networkId)) { + assert.isNumber('props.networkId', props.networkId); + } + if (!_.isUndefined(props.availableAssetDatas)) { + assert.areValidAssetDatas('availableAssetDatas', props.availableAssetDatas); + } + if (!_.isUndefined(props.onClose)) { + assert.isFunction('props.onClose', props.onClose); + } + if (!_.isUndefined(props.zIndex)) { + assert.isNumber('props.zIndex', props.zIndex); + } + if (!_.isUndefined(props.affiliateInfo)) { + assert.isValidAffiliateInfo('props.affiliateInfo', props.affiliateInfo); + } + if (!_.isUndefined(props.provider)) { + assert.isWeb3Provider('props.provider', props.provider); + } + assert.isString('selector', selector); + const appendToIfExists = document.querySelector(selector); + assert.assert(!_.isNull(appendToIfExists), `Could not find div with selector: ${selector}`); + const appendTo = appendToIfExists as Element; + const injectedDiv = document.createElement('div'); + injectedDiv.setAttribute('id', INJECTED_DIV_ID); + injectedDiv.setAttribute('class', INJECTED_DIV_CLASS); + appendTo.appendChild(injectedDiv); + const instantOverlayProps = { + ...props, + onClose: () => { + appendTo.removeChild(injectedDiv); + if (!_.isUndefined(props.onClose)) { + props.onClose(); + } + }, + }; + ReactDOM.render(React.createElement(ZeroExInstantOverlay, instantOverlayProps), injectedDiv); }; diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts index 5a4099f15..8947c6c97 100644 --- a/packages/instant/src/redux/actions.ts +++ b/packages/instant/src/redux/actions.ts @@ -2,7 +2,7 @@ import { BuyQuote } from '@0x/asset-buyer'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; -import { ActionsUnion, OrderState } from '../types'; +import { ActionsUnion, AddressAndEthBalanceInWei, Asset } from '../types'; export interface PlainAction<T extends string> { type: T; @@ -21,28 +21,48 @@ function createAction<T extends string, P>(type: T, data?: P): PlainAction<T> | } export enum ActionTypes { + SET_ACCOUNT_STATE_LOADING = 'SET_ACCOUNT_STATE_LOADING', + SET_ACCOUNT_STATE_LOCKED = 'SET_ACCOUNT_STATE_LOCKED', + SET_ACCOUNT_STATE_READY = 'SET_ACCOUNT_STATE_READY', + UPDATE_ACCOUNT_ETH_BALANCE = 'UPDATE_ACCOUNT_ETH_BALANCE', UPDATE_ETH_USD_PRICE = 'UPDATE_ETH_USD_PRICE', UPDATE_SELECTED_ASSET_AMOUNT = 'UPDATE_SELECTED_ASSET_AMOUNT', - UPDATE_BUY_ORDER_STATE = 'UPDATE_BUY_ORDER_STATE', + SET_BUY_ORDER_STATE_NONE = 'SET_BUY_ORDER_STATE_NONE', + SET_BUY_ORDER_STATE_VALIDATING = 'SET_BUY_ORDER_STATE_VALIDATING', + SET_BUY_ORDER_STATE_PROCESSING = 'SET_BUY_ORDER_STATE_PROCESSING', + SET_BUY_ORDER_STATE_FAILURE = 'SET_BUY_ORDER_STATE_FAILURE', + SET_BUY_ORDER_STATE_SUCCESS = 'SET_BUY_ORDER_STATE_SUCCESS', UPDATE_LATEST_BUY_QUOTE = 'UPDATE_LATEST_BUY_QUOTE', UPDATE_SELECTED_ASSET = 'UPDATE_SELECTED_ASSET', + SET_AVAILABLE_ASSETS = 'SET_AVAILABLE_ASSETS', SET_QUOTE_REQUEST_STATE_PENDING = 'SET_QUOTE_REQUEST_STATE_PENDING', SET_QUOTE_REQUEST_STATE_FAILURE = 'SET_QUOTE_REQUEST_STATE_FAILURE', - SET_ERROR = 'SET_ERROR', + SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE', HIDE_ERROR = 'HIDE_ERROR', CLEAR_ERROR = 'CLEAR_ERROR', RESET_AMOUNT = 'RESET_AMOUNT', } export const actions = { + setAccountStateLoading: () => createAction(ActionTypes.SET_ACCOUNT_STATE_LOADING), + setAccountStateLocked: () => createAction(ActionTypes.SET_ACCOUNT_STATE_LOCKED), + setAccountStateReady: (address: string) => createAction(ActionTypes.SET_ACCOUNT_STATE_READY, address), + updateAccountEthBalance: (addressAndBalance: AddressAndEthBalanceInWei) => + createAction(ActionTypes.UPDATE_ACCOUNT_ETH_BALANCE, addressAndBalance), updateEthUsdPrice: (price?: BigNumber) => createAction(ActionTypes.UPDATE_ETH_USD_PRICE, price), updateSelectedAssetAmount: (amount?: BigNumber) => createAction(ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, amount), - updateBuyOrderState: (orderState: OrderState) => createAction(ActionTypes.UPDATE_BUY_ORDER_STATE, orderState), + setBuyOrderStateNone: () => createAction(ActionTypes.SET_BUY_ORDER_STATE_NONE), + setBuyOrderStateValidating: () => createAction(ActionTypes.SET_BUY_ORDER_STATE_VALIDATING), + setBuyOrderStateProcessing: (txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => + createAction(ActionTypes.SET_BUY_ORDER_STATE_PROCESSING, { txHash, startTimeUnix, expectedEndTimeUnix }), + setBuyOrderStateFailure: (txHash: string) => createAction(ActionTypes.SET_BUY_ORDER_STATE_FAILURE, txHash), + setBuyOrderStateSuccess: (txHash: string) => createAction(ActionTypes.SET_BUY_ORDER_STATE_SUCCESS, txHash), updateLatestBuyQuote: (buyQuote?: BuyQuote) => createAction(ActionTypes.UPDATE_LATEST_BUY_QUOTE, buyQuote), - updateSelectedAsset: (assetData?: string) => createAction(ActionTypes.UPDATE_SELECTED_ASSET, assetData), + updateSelectedAsset: (asset: Asset) => createAction(ActionTypes.UPDATE_SELECTED_ASSET, asset), + setAvailableAssets: (availableAssets: Asset[]) => createAction(ActionTypes.SET_AVAILABLE_ASSETS, availableAssets), setQuoteRequestStatePending: () => createAction(ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING), setQuoteRequestStateFailure: () => createAction(ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE), - setError: (error?: any) => createAction(ActionTypes.SET_ERROR, error), + setErrorMessage: (errorMessage: string) => createAction(ActionTypes.SET_ERROR_MESSAGE, errorMessage), hideError: () => createAction(ActionTypes.HIDE_ERROR), clearError: () => createAction(ActionTypes.CLEAR_ERROR), resetAmount: () => createAction(ActionTypes.RESET_AMOUNT), diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index 4ed89bdc3..b920ac914 100644 --- a/packages/instant/src/redux/async_data.ts +++ b/packages/instant/src/redux/async_data.ts @@ -1,22 +1,103 @@ +import { AssetProxyId } from '@0x/types'; +import * as _ from 'lodash'; + import { BIG_NUMBER_ZERO } from '../constants'; +import { AccountState, ERC20Asset, OrderProcessState } from '../types'; +import { assetUtils } from '../util/asset'; +import { buyQuoteUpdater } from '../util/buy_quote_updater'; import { coinbaseApi } from '../util/coinbase_api'; +import { errorFlasher } from '../util/error_flasher'; -import { ActionTypes } from './actions'; - +import { actions } from './actions'; import { Store } from './store'; export const asyncData = { - fetchAndDispatchToStore: async (store: Store) => { - let ethUsdPrice = BIG_NUMBER_ZERO; + fetchEthPriceAndDispatchToStore: async (store: Store) => { + try { + const ethUsdPrice = await coinbaseApi.getEthUsdPrice(); + store.dispatch(actions.updateEthUsdPrice(ethUsdPrice)); + } catch (e) { + const errorMessage = 'Error fetching ETH/USD price'; + errorFlasher.flashNewErrorMessage(store.dispatch, errorMessage); + store.dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO)); + } + }, + fetchAvailableAssetDatasAndDispatchToStore: async (store: Store) => { + const { providerState, assetMetaDataMap, network } = store.getState(); + const assetBuyer = providerState.assetBuyer; try { - ethUsdPrice = await coinbaseApi.getEthUsdPrice(); + const assetDatas = await assetBuyer.getAvailableAssetDatasAsync(); + const assets = assetUtils.createAssetsFromAssetDatas(assetDatas, assetMetaDataMap, network); + store.dispatch(actions.setAvailableAssets(assets)); } catch (e) { - // ignore - } finally { - store.dispatch({ - type: ActionTypes.UPDATE_ETH_USD_PRICE, - data: ethUsdPrice, - }); + const errorMessage = 'Could not find any assets'; + errorFlasher.flashNewErrorMessage(store.dispatch, errorMessage); + // On error, just specify that none are available + store.dispatch(actions.setAvailableAssets([])); + } + }, + fetchAccountInfoAndDispatchToStore: async (options: { store: Store; shouldSetToLoading: boolean }) => { + const { store, shouldSetToLoading } = options; + const { providerState } = store.getState(); + const web3Wrapper = providerState.web3Wrapper; + const provider = providerState.provider; + if (shouldSetToLoading && providerState.account.state !== AccountState.Loading) { + store.dispatch(actions.setAccountStateLoading()); + } + let availableAddresses: string[]; + try { + // TODO(bmillman): Add support at the web3Wrapper level for calling `eth_requestAccounts` instead of calling enable here + const isPrivacyModeEnabled = !_.isUndefined((provider as any).enable); + availableAddresses = isPrivacyModeEnabled + ? await (provider as any).enable() + : await web3Wrapper.getAvailableAddressesAsync(); + } catch (e) { + store.dispatch(actions.setAccountStateLocked()); + return; + } + if (!_.isEmpty(availableAddresses)) { + const activeAddress = availableAddresses[0]; + store.dispatch(actions.setAccountStateReady(activeAddress)); + // tslint:disable-next-line:no-floating-promises + asyncData.fetchAccountBalanceAndDispatchToStore(store); + } else { + store.dispatch(actions.setAccountStateLocked()); + } + }, + fetchAccountBalanceAndDispatchToStore: async (store: Store) => { + const { providerState } = store.getState(); + const web3Wrapper = providerState.web3Wrapper; + const account = providerState.account; + if (account.state !== AccountState.Ready) { + return; + } + try { + const address = account.address; + const ethBalanceInWei = await web3Wrapper.getBalanceInWeiAsync(address); + store.dispatch(actions.updateAccountEthBalance({ address, ethBalanceInWei })); + } catch (e) { + // leave balance as is + return; + } + }, + fetchCurrentBuyQuoteAndDispatchToStore: async (options: { store: Store; shouldSetPending: boolean }) => { + const { store, shouldSetPending } = options; + const { buyOrderState, providerState, selectedAsset, selectedAssetAmount, affiliateInfo } = store.getState(); + const assetBuyer = providerState.assetBuyer; + if ( + !_.isUndefined(selectedAssetAmount) && + !_.isUndefined(selectedAsset) && + buyOrderState.processState === OrderProcessState.None && + selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20 + ) { + await buyQuoteUpdater.updateBuyQuoteAsync( + assetBuyer, + store.dispatch, + selectedAsset as ERC20Asset, + selectedAssetAmount, + shouldSetPending, + affiliateInfo, + ); } }, }; diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index 25d0092b2..ef46fdd9d 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -1,10 +1,16 @@ -import { AssetBuyer, BuyQuote } from '@0x/asset-buyer'; -import { ObjectMap } from '@0x/types'; +import { BuyQuote } from '@0x/asset-buyer'; +import { AssetProxyId, ObjectMap } from '@0x/types'; import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; +import { LOADING_ACCOUNT, LOCKED_ACCOUNT } from '../constants'; import { assetMetaDataMap } from '../data/asset_meta_data_map'; import { + Account, + AccountReady, + AccountState, + AffiliateInfo, Asset, AssetMetaData, AsyncProcessState, @@ -12,112 +18,240 @@ import { Network, OrderProcessState, OrderState, + ProviderState, } from '../types'; -import { assetUtils } from '../util/asset'; import { Action, ActionTypes } from './actions'; -export interface State { +// State that is required and we have defaults for, before props are passed in +export interface DefaultState { network: Network; - assetBuyer?: AssetBuyer; assetMetaDataMap: ObjectMap<AssetMetaData>; - selectedAsset?: Asset; - selectedAssetAmount?: BigNumber; buyOrderState: OrderState; - ethUsdPrice?: BigNumber; - latestBuyQuote?: BuyQuote; + latestErrorDisplayStatus: DisplayStatus; quoteRequestState: AsyncProcessState; - latestError?: any; - latestErrorDisplay: DisplayStatus; } -export const INITIAL_STATE: State = { +// State that is required but needs to be derived from the props +interface PropsDerivedState { + providerState: ProviderState; +} + +// State that is optional +interface OptionalState { + selectedAsset: Asset; + availableAssets: Asset[]; + selectedAssetAmount: BigNumber; + ethUsdPrice: BigNumber; + latestBuyQuote: BuyQuote; + latestErrorMessage: string; + affiliateInfo: AffiliateInfo; +} + +export type State = DefaultState & PropsDerivedState & Partial<OptionalState>; + +export const DEFAULT_STATE: DefaultState = { network: Network.Mainnet, - selectedAssetAmount: undefined, assetMetaDataMap, - buyOrderState: { processState: OrderProcessState.NONE }, - ethUsdPrice: undefined, - latestBuyQuote: undefined, - latestError: undefined, - latestErrorDisplay: DisplayStatus.Hidden, - quoteRequestState: AsyncProcessState.NONE, + buyOrderState: { processState: OrderProcessState.None }, + latestErrorDisplayStatus: DisplayStatus.Hidden, + quoteRequestState: AsyncProcessState.None, }; -export const reducer = (state: State = INITIAL_STATE, action: Action): State => { - switch (action.type) { - case ActionTypes.UPDATE_ETH_USD_PRICE: - return { - ...state, - ethUsdPrice: action.data, - }; - case ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT: - return { - ...state, - selectedAssetAmount: action.data, - }; - case ActionTypes.UPDATE_LATEST_BUY_QUOTE: - return { - ...state, - latestBuyQuote: action.data, - quoteRequestState: AsyncProcessState.SUCCESS, - }; - case ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING: - return { - ...state, - latestBuyQuote: undefined, - quoteRequestState: AsyncProcessState.PENDING, - }; - case ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE: - return { - ...state, - latestBuyQuote: undefined, - quoteRequestState: AsyncProcessState.FAILURE, - }; - case ActionTypes.UPDATE_BUY_ORDER_STATE: - return { - ...state, - buyOrderState: action.data, - }; - case ActionTypes.SET_ERROR: - return { - ...state, - latestError: action.data, - latestErrorDisplay: DisplayStatus.Present, - }; - case ActionTypes.HIDE_ERROR: - return { - ...state, - latestErrorDisplay: DisplayStatus.Hidden, - }; - case ActionTypes.CLEAR_ERROR: - return { - ...state, - latestError: undefined, - latestErrorDisplay: DisplayStatus.Hidden, - }; - case ActionTypes.UPDATE_SELECTED_ASSET: - const newSelectedAssetData = action.data; - let newSelectedAsset: Asset | undefined; - if (!_.isUndefined(newSelectedAssetData)) { - newSelectedAsset = assetUtils.createAssetFromAssetData( - newSelectedAssetData, - state.assetMetaDataMap, - state.network, - ); +export const createReducer = (initialState: State) => { + const reducer = (state: State = initialState, action: Action): State => { + switch (action.type) { + case ActionTypes.SET_ACCOUNT_STATE_LOADING: + return reduceStateWithAccount(state, LOADING_ACCOUNT); + case ActionTypes.SET_ACCOUNT_STATE_LOCKED: + return reduceStateWithAccount(state, LOCKED_ACCOUNT); + case ActionTypes.SET_ACCOUNT_STATE_READY: { + const account: AccountReady = { + state: AccountState.Ready, + address: action.data, + }; + return reduceStateWithAccount(state, account); + } + case ActionTypes.UPDATE_ACCOUNT_ETH_BALANCE: { + const { address, ethBalanceInWei } = action.data; + const currentAccount = state.providerState.account; + if (currentAccount.state !== AccountState.Ready || currentAccount.address !== address) { + return state; + } else { + const newAccount: AccountReady = { + ...currentAccount, + ethBalanceInWei, + }; + return reduceStateWithAccount(state, newAccount); + } } - return { - ...state, - selectedAsset: newSelectedAsset, - }; - case ActionTypes.RESET_AMOUNT: - return { - ...state, - latestBuyQuote: undefined, - quoteRequestState: AsyncProcessState.NONE, - buyOrderState: { processState: OrderProcessState.NONE }, - selectedAssetAmount: undefined, - }; - default: - return state; + case ActionTypes.UPDATE_ETH_USD_PRICE: + return { + ...state, + ethUsdPrice: action.data, + }; + case ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT: + return { + ...state, + selectedAssetAmount: action.data, + }; + case ActionTypes.UPDATE_LATEST_BUY_QUOTE: + const newBuyQuoteIfExists = action.data; + const shouldUpdate = + _.isUndefined(newBuyQuoteIfExists) || doesBuyQuoteMatchState(newBuyQuoteIfExists, state); + if (shouldUpdate) { + return { + ...state, + latestBuyQuote: newBuyQuoteIfExists, + quoteRequestState: AsyncProcessState.Success, + }; + } else { + return state; + } + case ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING: + return { + ...state, + latestBuyQuote: undefined, + quoteRequestState: AsyncProcessState.Pending, + }; + case ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE: + return { + ...state, + latestBuyQuote: undefined, + quoteRequestState: AsyncProcessState.Failure, + }; + case ActionTypes.SET_BUY_ORDER_STATE_NONE: + return { + ...state, + buyOrderState: { processState: OrderProcessState.None }, + }; + case ActionTypes.SET_BUY_ORDER_STATE_VALIDATING: + return { + ...state, + buyOrderState: { processState: OrderProcessState.Validating }, + }; + case ActionTypes.SET_BUY_ORDER_STATE_PROCESSING: + const processingData = action.data; + const { startTimeUnix, expectedEndTimeUnix } = processingData; + return { + ...state, + buyOrderState: { + processState: OrderProcessState.Processing, + txHash: processingData.txHash, + progress: { + startTimeUnix, + expectedEndTimeUnix, + }, + }, + }; + case ActionTypes.SET_BUY_ORDER_STATE_FAILURE: + const failureTxHash = action.data; + if ('txHash' in state.buyOrderState) { + if (state.buyOrderState.txHash === failureTxHash) { + const { txHash, progress } = state.buyOrderState; + return { + ...state, + buyOrderState: { + processState: OrderProcessState.Failure, + txHash, + progress, + }, + }; + } + } + return state; + case ActionTypes.SET_BUY_ORDER_STATE_SUCCESS: + const successTxHash = action.data; + if ('txHash' in state.buyOrderState) { + if (state.buyOrderState.txHash === successTxHash) { + const { txHash, progress } = state.buyOrderState; + return { + ...state, + buyOrderState: { + processState: OrderProcessState.Success, + txHash, + progress, + }, + }; + } + } + return state; + case ActionTypes.SET_ERROR_MESSAGE: + return { + ...state, + latestErrorMessage: action.data, + latestErrorDisplayStatus: DisplayStatus.Present, + }; + case ActionTypes.HIDE_ERROR: + return { + ...state, + latestErrorDisplayStatus: DisplayStatus.Hidden, + }; + case ActionTypes.CLEAR_ERROR: + return { + ...state, + latestErrorMessage: undefined, + latestErrorDisplayStatus: DisplayStatus.Hidden, + }; + case ActionTypes.UPDATE_SELECTED_ASSET: + return { + ...state, + selectedAsset: action.data, + }; + case ActionTypes.RESET_AMOUNT: + return { + ...state, + latestBuyQuote: undefined, + quoteRequestState: AsyncProcessState.None, + buyOrderState: { processState: OrderProcessState.None }, + selectedAssetAmount: undefined, + }; + case ActionTypes.SET_AVAILABLE_ASSETS: + return { + ...state, + availableAssets: action.data, + }; + default: + return state; + } + }; + return reducer; +}; + +const reduceStateWithAccount = (state: State, account: Account) => { + const oldProviderState = state.providerState; + const newProviderState: ProviderState = { + ...oldProviderState, + account, + }; + return { + ...state, + providerState: newProviderState, + }; +}; + +const doesBuyQuoteMatchState = (buyQuote: BuyQuote, state: State): boolean => { + const selectedAssetIfExists = state.selectedAsset; + const selectedAssetAmountIfExists = state.selectedAssetAmount; + // if no selectedAsset or selectedAssetAmount exists on the current state, return false + if (_.isUndefined(selectedAssetIfExists) || _.isUndefined(selectedAssetAmountIfExists)) { + return false; + } + // if buyQuote's assetData does not match that of the current selected asset, return false + if (selectedAssetIfExists.assetData !== buyQuote.assetData) { + return false; + } + // if ERC20 and buyQuote's assetBuyAmount does not match selectedAssetAmount, return false + // if ERC721, return true + const selectedAssetMetaData = selectedAssetIfExists.metaData; + if (selectedAssetMetaData.assetProxyId === AssetProxyId.ERC20) { + const selectedAssetAmountBaseUnits = Web3Wrapper.toBaseUnitAmount( + selectedAssetAmountIfExists, + selectedAssetMetaData.decimals, + ); + const doesAssetAmountMatch = selectedAssetAmountBaseUnits.eq(buyQuote.assetBuyAmount); + return doesAssetAmountMatch; + } else { + return true; } }; diff --git a/packages/instant/src/redux/store.ts b/packages/instant/src/redux/store.ts index 01deb8690..20710765d 100644 --- a/packages/instant/src/redux/store.ts +++ b/packages/instant/src/redux/store.ts @@ -2,12 +2,13 @@ import * as _ from 'lodash'; import { createStore, Store as ReduxStore } from 'redux'; import { devToolsEnhancer } from 'redux-devtools-extension/developmentOnly'; -import { reducer, State } from './reducer'; +import { createReducer, State } from './reducer'; export type Store = ReduxStore<State>; export const store = { - create: (state: State): Store => { - return createStore(reducer, state, devToolsEnhancer({})); + create: (initialState: State): Store => { + const reducer = createReducer(initialState); + return createStore(reducer, initialState, devToolsEnhancer({})); }, }; diff --git a/packages/instant/src/style/fonts.ts b/packages/instant/src/style/fonts.ts index 975a30a61..92450502d 100644 --- a/packages/instant/src/style/fonts.ts +++ b/packages/instant/src/style/fonts.ts @@ -1,10 +1,10 @@ -import { injectGlobal } from './theme'; - export const fonts = { include: () => { // Inject the inter-ui font into the page - return injectGlobal` - @import url('https://rsms.me/inter/inter-ui.css'); - `; + const appendTo = document.head || document.getElementsByTagName('head')[0] || document.body; + const style = document.createElement('style'); + style.type = 'text/css'; + style.appendChild(document.createTextNode(`@import url('https://rsms.me/inter/inter-ui.css')`)); + appendTo.appendChild(style); }, }; diff --git a/packages/instant/src/style/media.ts b/packages/instant/src/style/media.ts new file mode 100644 index 000000000..bbf376694 --- /dev/null +++ b/packages/instant/src/style/media.ts @@ -0,0 +1,51 @@ +import { InterpolationValue } from 'styled-components'; + +import { css } from './theme'; + +export enum ScreenWidths { + Sm = 40, + Md = 52, + Lg = 64, +} + +export const generateMediaWrapper = (screenWidth: ScreenWidths) => (...args: any[]) => css` + @media (max-width: ${screenWidth}em) { + ${css.apply(css, args)}; + } +`; + +export const media = { + small: generateMediaWrapper(ScreenWidths.Sm), + medium: generateMediaWrapper(ScreenWidths.Md), + large: generateMediaWrapper(ScreenWidths.Lg), +}; + +export interface ScreenSpecification<T> { + default: T; + sm?: T; + md?: T; + lg?: T; +} +export type OptionallyScreenSpecific<T> = T | ScreenSpecification<T>; +export type MediaChoice = OptionallyScreenSpecific<string>; +/** + * Given a css property name and a OptionallyScreenSpecific value, + * generates css properties with screen-specific viewport styling + */ +export function stylesForMedia<T extends string | number>( + cssPropertyName: string, + choice: OptionallyScreenSpecific<T>, +): InterpolationValue[] { + if (typeof choice === 'object') { + return css` + ${cssPropertyName}: ${choice.default}; + ${choice.lg && media.large`${cssPropertyName}: ${choice.lg}`} + ${choice.md && media.medium`${cssPropertyName}: ${choice.md}`} + ${choice.sm && media.small`${cssPropertyName}: ${choice.sm}`} + `; + } else { + return css` + ${cssPropertyName}: ${choice}; + `; + } +} diff --git a/packages/instant/src/style/theme.ts b/packages/instant/src/style/theme.ts index d26c816c1..1e9f55e00 100644 --- a/packages/instant/src/style/theme.ts +++ b/packages/instant/src/style/theme.ts @@ -1,6 +1,6 @@ import * as styledComponents from 'styled-components'; -const { default: styled, css, injectGlobal, keyframes, ThemeProvider } = styledComponents; +const { default: styled, css, keyframes, withTheme, createGlobalStyle, ThemeProvider } = styledComponents; export type Theme = { [key in ColorOption]: string }; @@ -10,22 +10,35 @@ export enum ColorOption { lightGrey = 'lightGrey', grey = 'grey', feintGrey = 'feintGrey', + lightestGrey = 'lightestGrey', darkGrey = 'darkGrey', white = 'white', lightOrange = 'lightOrange', darkOrange = 'darkOrange', + green = 'green', + red = 'red', } export const theme: Theme = { - primaryColor: '#512D80', + primaryColor: '#333', black: 'black', lightGrey: '#999999', grey: '#666666', feintGrey: '#DEDEDE', + lightestGrey: '#EEEEEE', darkGrey: '#333333', white: 'white', lightOrange: '#F9F2ED', darkOrange: '#F2994C', + green: '#3CB34F', + red: '#D00000', }; -export { styled, css, injectGlobal, keyframes, ThemeProvider }; +export const transparentWhite = 'rgba(255,255,255,0.3)'; +export const completelyTransparent = 'rga(0, 0, 0, 0)'; + +export const generateOverlayBlack = (opacity = 0.6) => { + return `rgba(0, 0, 0, ${opacity})`; +}; + +export { styled, css, keyframes, withTheme, createGlobalStyle, ThemeProvider }; diff --git a/packages/instant/src/style/z_index.ts b/packages/instant/src/style/z_index.ts new file mode 100644 index 000000000..ba2d27a17 --- /dev/null +++ b/packages/instant/src/style/z_index.ts @@ -0,0 +1,9 @@ +export const zIndex = { + errorPopBehind: 10, + mainContainer: 20, + dropdownItems: 30, + panel: 40, + containerOverlay: 45, + errorPopup: 50, + overlayDefault: 100, +}; diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index c63371fb4..b43a82d46 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -1,27 +1,38 @@ -import { AssetProxyId, ObjectMap } from '@0x/types'; +import { AssetBuyer, BigNumber } from '@0x/asset-buyer'; +import { AssetProxyId, ObjectMap, SignedOrder } from '@0x/types'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { Provider } from 'ethereum-types'; // Reusable +export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; +export type Maybe<T> = T | undefined; export enum AsyncProcessState { - NONE = 'None', - PENDING = 'Pending', - SUCCESS = 'Success', - FAILURE = 'Failure', + None = 'NONE', + Pending = 'PENDING', + Success = 'SUCCESS', + Failure = 'FAILURE', } export enum OrderProcessState { - NONE = 'None', - AWAITING_SIGNATURE = 'Awaiting Signature', - PROCESSING = 'Processing', - SUCCESS = 'Success', - FAILURE = 'Failure', + None = 'NONE', + Validating = 'VALIDATING', + Processing = 'PROCESSING', + Success = 'SUCCESS', + Failure = 'FAILURE', +} + +export interface SimulatedProgress { + startTimeUnix: number; + expectedEndTimeUnix: number; } interface OrderStatePreTx { - processState: OrderProcessState.NONE | OrderProcessState.AWAITING_SIGNATURE; + processState: OrderProcessState.None | OrderProcessState.Validating; } interface OrderStatePostTx { - processState: OrderProcessState.PROCESSING | OrderProcessState.SUCCESS | OrderProcessState.FAILURE; + processState: OrderProcessState.Processing | OrderProcessState.Success | OrderProcessState.Failure; txHash: string; + progress: SimulatedProgress; } export type OrderState = OrderStatePreTx | OrderStatePostTx; @@ -39,12 +50,13 @@ export interface ERC20AssetMetaData { decimals: number; primaryColor?: string; symbol: string; + name: string; } export interface ERC721AssetMetaData { assetProxyId: AssetProxyId.ERC721; name: string; - representationUrl?: string; + imageUrl?: string; primaryColor?: string; } @@ -72,4 +84,44 @@ export enum Network { export enum ZeroExInstantError { AssetMetaDataNotAvailable = 'ASSET_META_DATA_NOT_AVAILABLE', + InsufficientETH = 'INSUFFICIENT_ETH', +} + +export type SimpleHandler = () => void; + +export interface AffiliateInfo { + feeRecipient: string; + feePercentage: number; +} + +export interface ProviderState { + provider: Provider; + assetBuyer: AssetBuyer; + web3Wrapper: Web3Wrapper; + account: Account; +} + +export enum AccountState { + None = 'NONE,', + Loading = 'LOADING', + Ready = 'READY', + Locked = 'LOCKED', +} + +export interface AccountReady { + state: AccountState.Ready; + address: string; + ethBalanceInWei?: BigNumber; +} +export interface AccountNotReady { + state: AccountState.None | AccountState.Loading | AccountState.Locked; +} + +export type Account = AccountReady | AccountNotReady; + +export type OrderSource = string | SignedOrder[]; + +export interface AddressAndEthBalanceInWei { + address: string; + ethBalanceInWei: BigNumber; } diff --git a/packages/instant/src/util/address.ts b/packages/instant/src/util/address.ts new file mode 100644 index 000000000..b21863a8e --- /dev/null +++ b/packages/instant/src/util/address.ts @@ -0,0 +1,6 @@ +import { Web3Wrapper } from '@0x/web3-wrapper'; + +export const getBestAddress = async (web3Wrapper: Web3Wrapper): Promise<string | undefined> => { + const addresses = await web3Wrapper.getAvailableAddressesAsync(); + return addresses[0]; +}; diff --git a/packages/instant/src/util/assert.ts b/packages/instant/src/util/assert.ts new file mode 100644 index 000000000..971c1eb96 --- /dev/null +++ b/packages/instant/src/util/assert.ts @@ -0,0 +1,55 @@ +import { assert as sharedAssert } from '@0x/assert'; +import { schemas } from '@0x/json-schemas'; +import { assetDataUtils } from '@0x/order-utils'; +import { AssetProxyId, ObjectMap, SignedOrder } from '@0x/types'; +import * as _ from 'lodash'; + +import { AffiliateInfo, AssetMetaData } from '../types'; + +export const assert = { + ...sharedAssert, + isValidOrderSource(variableName: string, orderSource: string | SignedOrder[]): void { + if (_.isString(orderSource)) { + sharedAssert.isUri(variableName, orderSource); + return; + } + sharedAssert.doesConformToSchema(variableName, orderSource, schemas.signedOrdersSchema); + }, + areValidAssetDatas(variableName: string, assetDatas: string[]): void { + _.forEach(assetDatas, (assetData, index) => assert.isHexString(`${variableName}[${index}]`, assetData)); + }, + isValidAssetMetaDataMap(variableName: string, metaDataMap: ObjectMap<AssetMetaData>): void { + _.forEach(metaDataMap, (metaData, assetData) => { + assert.isHexString(`key ${assetData} of ${variableName}`, assetData); + assert.isValidAssetMetaData(`${variableName}.${assetData}`, metaData); + const assetDataProxyId = assetDataUtils.decodeAssetProxyId(assetData); + assert.assert( + metaData.assetProxyId === assetDataProxyId, + `Expected meta data for assetData ${assetData} to have asset proxy id of ${assetDataProxyId}, but instead got ${ + metaData.assetProxyId + }`, + ); + }); + }, + isValidAssetMetaData(variableName: string, metaData: AssetMetaData): void { + assert.isHexString(`${variableName}.assetProxyId`, metaData.assetProxyId); + if (!_.isUndefined(metaData.primaryColor)) { + assert.isString(`${variableName}.primaryColor`, metaData.primaryColor); + } + if (metaData.assetProxyId === AssetProxyId.ERC20) { + assert.isNumber(`${variableName}.decimals`, metaData.decimals); + assert.isString(`${variableName}.symbol`, metaData.symbol); + } else if (metaData.assetProxyId === AssetProxyId.ERC721) { + assert.isString(`${variableName}.name`, metaData.name); + assert.isUri(`${variableName}.imageUrl`, metaData.imageUrl); + } + }, + isValidAffiliateInfo(variableName: string, affiliateInfo: AffiliateInfo): void { + assert.isETHAddressHex(`${variableName}.recipientAddress`, affiliateInfo.feeRecipient); + assert.isNumber(`${variableName}.percentage`, affiliateInfo.feePercentage); + assert.assert( + affiliateInfo.feePercentage >= 0 && affiliateInfo.feePercentage <= 0.05, + `Expected ${variableName}.percentage to be between 0 and 0.05, but is ${affiliateInfo.feePercentage}`, + ); + }, +}; diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index 4e3b2b946..fbfbb19f3 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -2,10 +2,34 @@ import { AssetProxyId, ObjectMap } from '@0x/types'; import * as _ from 'lodash'; import { assetDataNetworkMapping } from '../data/asset_data_network_mapping'; -import { Asset, AssetMetaData, Network, ZeroExInstantError } from '../types'; +import { Asset, AssetMetaData, ERC20Asset, Network, ZeroExInstantError } from '../types'; export const assetUtils = { - createAssetFromAssetData: ( + createAssetsFromAssetDatas: ( + assetDatas: string[], + assetMetaDataMap: ObjectMap<AssetMetaData>, + network: Network, + ): Asset[] => { + const arrayOfAssetOrUndefined = _.map(assetDatas, assetData => + assetUtils.createAssetFromAssetDataIfExists(assetData, assetMetaDataMap, network), + ); + return _.compact(arrayOfAssetOrUndefined); + }, + createAssetFromAssetDataIfExists: ( + assetData: string, + assetMetaDataMap: ObjectMap<AssetMetaData>, + network: Network, + ): Asset | undefined => { + const metaData = assetUtils.getMetaDataIfExists(assetData, assetMetaDataMap, network); + if (_.isUndefined(metaData)) { + return; + } + return { + assetData, + metaData, + }; + }, + createAssetFromAssetDataOrThrow: ( assetData: string, assetMetaDataMap: ObjectMap<AssetMetaData>, network: Network, @@ -16,16 +40,33 @@ export const assetUtils = { }; }, getMetaDataOrThrow: (assetData: string, metaDataMap: ObjectMap<AssetMetaData>, network: Network): AssetMetaData => { + const metaDataIfExists = assetUtils.getMetaDataIfExists(assetData, metaDataMap, network); + if (_.isUndefined(metaDataIfExists)) { + throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable); + } + return metaDataIfExists; + }, + getMetaDataIfExists: ( + assetData: string, + metaDataMap: ObjectMap<AssetMetaData>, + network: Network, + ): AssetMetaData | undefined => { let mainnetAssetData: string | undefined = assetData; if (network !== Network.Mainnet) { - mainnetAssetData = assetUtils.getAssociatedAssetDataIfExists(assetData, network); + const mainnetAssetDataIfExists = assetUtils.getAssociatedAssetDataIfExists( + assetData.toLowerCase(), + network, + ); + // Just so we don't fail in the case where we are on a non-mainnet network, + // but pass in a valid mainnet assetData. + mainnetAssetData = mainnetAssetDataIfExists || assetData; } if (_.isUndefined(mainnetAssetData)) { - throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable); + return; } - const metaData = metaDataMap[mainnetAssetData]; + const metaData = metaDataMap[mainnetAssetData.toLowerCase()]; if (_.isUndefined(metaData)) { - throw new Error(ZeroExInstantError.AssetMetaDataNotAvailable); + return; } return metaData; }, @@ -43,6 +84,16 @@ export const assetUtils = { return defaultName; } }, + formattedSymbolForAsset: (asset?: ERC20Asset, defaultName: string = '???'): string => { + if (_.isUndefined(asset)) { + return defaultName; + } + const symbol = asset.metaData.symbol; + if (symbol.length <= 5) { + return symbol; + } + return `${symbol.slice(0, 3)}…`; + }, getAssociatedAssetDataIfExists: (assetData: string, network: Network): string | undefined => { const assetDataGroupIfExists = _.find(assetDataNetworkMapping, value => value[network] === assetData); if (_.isUndefined(assetDataGroupIfExists)) { @@ -50,4 +101,11 @@ export const assetUtils = { } return assetDataGroupIfExists[Network.Mainnet]; }, + getERC20AssetsFromAssets: (assets: Asset[]): ERC20Asset[] => { + const erc20sOrUndefined = _.map( + assets, + asset => (asset.metaData.assetProxyId === AssetProxyId.ERC20 ? (asset as ERC20Asset) : undefined), + ); + return _.compact(erc20sOrUndefined); + }, }; diff --git a/packages/instant/src/util/asset_buyer_factory.ts b/packages/instant/src/util/asset_buyer_factory.ts new file mode 100644 index 000000000..5ba46223c --- /dev/null +++ b/packages/instant/src/util/asset_buyer_factory.ts @@ -0,0 +1,17 @@ +import { AssetBuyer, AssetBuyerOpts } from '@0x/asset-buyer'; +import { Provider } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { Network, OrderSource } from '../types'; + +export const assetBuyerFactory = { + getAssetBuyer: (provider: Provider, orderSource: OrderSource, network: Network): AssetBuyer => { + const assetBuyerOptions: Partial<AssetBuyerOpts> = { + networkId: network, + }; + const assetBuyer = _.isString(orderSource) + ? AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(provider, orderSource, assetBuyerOptions) + : AssetBuyer.getAssetBuyerForProvidedOrders(provider, orderSource, assetBuyerOptions); + return assetBuyer; + }, +}; diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts new file mode 100644 index 000000000..c33e28f1c --- /dev/null +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -0,0 +1,59 @@ +import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as _ from 'lodash'; +import { Dispatch } from 'redux'; +import { oc } from 'ts-optchain'; + +import { Action, actions } from '../redux/actions'; +import { AffiliateInfo, ERC20Asset } from '../types'; +import { assetUtils } from '../util/asset'; +import { errorFlasher } from '../util/error_flasher'; + +export const buyQuoteUpdater = { + updateBuyQuoteAsync: async ( + assetBuyer: AssetBuyer, + dispatch: Dispatch<Action>, + asset: ERC20Asset, + assetAmount: BigNumber, + setPending = true, + affiliateInfo?: AffiliateInfo, + ): Promise<void> => { + // get a new buy quote. + const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetAmount, asset.metaData.decimals); + if (setPending) { + // mark quote as pending + dispatch(actions.setQuoteRequestStatePending()); + } + const feePercentage = oc(affiliateInfo).feePercentage(); + let newBuyQuote: BuyQuote | undefined; + try { + newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue, { feePercentage }); + } catch (error) { + dispatch(actions.setQuoteRequestStateFailure()); + let errorMessage; + if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { + const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); + errorMessage = `Not enough ${assetName} available`; + } else if (error.message === AssetBuyerError.InsufficientZrxLiquidity) { + errorMessage = 'Not enough ZRX available'; + } else if ( + error.message === AssetBuyerError.StandardRelayerApiError || + error.message.startsWith(AssetBuyerError.AssetUnavailable) + ) { + const assetName = assetUtils.bestNameForAsset(asset, 'This asset'); + errorMessage = `${assetName} is currently unavailable`; + } + if (!_.isUndefined(errorMessage)) { + errorFlasher.flashNewErrorMessage(dispatch, errorMessage); + } else { + throw error; + } + return; + } + // We have a successful new buy quote + errorFlasher.clearError(dispatch); + // invalidate the last buy quote. + dispatch(actions.updateLatestBuyQuote(newBuyQuote)); + }, +}; diff --git a/packages/instant/src/util/coinbase_api.ts b/packages/instant/src/util/coinbase_api.ts index 080421f98..faac8d82d 100644 --- a/packages/instant/src/util/coinbase_api.ts +++ b/packages/instant/src/util/coinbase_api.ts @@ -1,9 +1,10 @@ -import { BigNumber } from '@0x/utils'; +import { BigNumber, fetchAsync } from '@0x/utils'; + +import { COINBASE_API_BASE_URL } from '../constants'; -const baseEndpoint = 'https://api.coinbase.com/v2'; export const coinbaseApi = { getEthUsdPrice: async (): Promise<BigNumber> => { - const res = await fetch(`${baseEndpoint}/prices/ETH-USD/buy`); + const res = await fetchAsync(`${COINBASE_API_BASE_URL}/prices/ETH-USD/buy`); const resJson = await res.json(); return new BigNumber(resJson.data.amount); }, diff --git a/packages/instant/src/util/error.ts b/packages/instant/src/util/error.ts deleted file mode 100644 index 64c1f4885..000000000 --- a/packages/instant/src/util/error.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { AssetBuyerError } from '@0x/asset-buyer'; -import { Dispatch } from 'redux'; - -import { Action, actions } from '../redux/actions'; -import { Asset } from '../types'; - -import { assetUtils } from './asset'; - -class ErrorFlasher { - private _timeoutId?: number; - public flashNewError(dispatch: Dispatch<Action>, error: any, delayMs: number = 7000): void { - this._clearTimeout(); - - // dispatch new message - dispatch(actions.setError(error)); - - this._timeoutId = window.setTimeout(() => { - dispatch(actions.hideError()); - }, delayMs); - } - public clearError(dispatch: Dispatch<Action>): void { - this._clearTimeout(); - dispatch(actions.hideError()); - } - private _clearTimeout(): void { - if (this._timeoutId) { - window.clearTimeout(this._timeoutId); - } - } -} - -const humanReadableMessageForError = (error: Error, asset?: Asset): string | undefined => { - const hasInsufficientLiquidity = - error.message === AssetBuyerError.InsufficientAssetLiquidity || - error.message === AssetBuyerError.InsufficientZrxLiquidity; - if (hasInsufficientLiquidity) { - const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); - return `Not enough ${assetName} available`; - } - - if ( - error.message === AssetBuyerError.StandardRelayerApiError || - error.message.startsWith(AssetBuyerError.AssetUnavailable) - ) { - const assetName = assetUtils.bestNameForAsset(asset, 'This asset'); - return `${assetName} is currently unavailable`; - } - - if (error.message === AssetBuyerError.SignatureRequestDenied) { - return 'You denied this transaction'; - } - - return undefined; -}; - -export const errorUtil = { - errorFlasher: new ErrorFlasher(), - errorDescription: (error?: any, asset?: Asset): { icon: string; message: string } => { - let bestMessage: string | undefined; - if (error instanceof Error) { - bestMessage = humanReadableMessageForError(error, asset); - } - return { - icon: '😢', - message: bestMessage || 'Something went wrong...', - }; - }, -}; diff --git a/packages/instant/src/util/error_flasher.ts b/packages/instant/src/util/error_flasher.ts new file mode 100644 index 000000000..068c12fe2 --- /dev/null +++ b/packages/instant/src/util/error_flasher.ts @@ -0,0 +1,26 @@ +import { Dispatch } from 'redux'; + +import { Action, actions } from '../redux/actions'; + +class ErrorFlasher { + private _timeoutId?: number; + public flashNewErrorMessage(dispatch: Dispatch<Action>, errorMessage?: string, delayMs: number = 7000): void { + this._clearTimeout(); + // dispatch new message + dispatch(actions.setErrorMessage(errorMessage || 'Something went wrong...')); + this._timeoutId = window.setTimeout(() => { + dispatch(actions.hideError()); + }, delayMs); + } + public clearError(dispatch: Dispatch<Action>): void { + this._clearTimeout(); + dispatch(actions.hideError()); + } + private _clearTimeout(): void { + if (this._timeoutId) { + window.clearTimeout(this._timeoutId); + } + } +} + +export const errorFlasher = new ErrorFlasher(); diff --git a/packages/instant/src/util/etherscan.ts b/packages/instant/src/util/etherscan.ts index cfc2578a3..4d62c4d9f 100644 --- a/packages/instant/src/util/etherscan.ts +++ b/packages/instant/src/util/etherscan.ts @@ -21,4 +21,11 @@ export const etherscanUtil = { } return `https://${prefix}etherscan.io/tx/${txHash}`; }, + getEtherScanEthAddressIfExists: (ethAddress: string, networkId: number) => { + const prefix = etherscanPrefix(networkId); + if (_.isUndefined(prefix)) { + return; + } + return `https://${prefix}etherscan.io/address/${ethAddress}`; + }, }; diff --git a/packages/instant/src/util/format.ts b/packages/instant/src/util/format.ts index 8482b1526..44661d697 100644 --- a/packages/instant/src/util/format.ts +++ b/packages/instant/src/util/format.ts @@ -2,7 +2,7 @@ import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; -import { ethDecimals } from '../constants'; +import { ETH_DECIMALS } from '../constants'; export const format = { ethBaseAmount: ( @@ -13,7 +13,7 @@ export const format = { if (_.isUndefined(ethBaseAmount)) { return defaultText; } - const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseAmount, ethDecimals); + const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseAmount, ETH_DECIMALS); return format.ethUnitAmount(ethUnitAmount, decimalPlaces); }, ethUnitAmount: ( @@ -24,7 +24,7 @@ export const format = { if (_.isUndefined(ethUnitAmount)) { return defaultText; } - const roundedAmount = ethUnitAmount.round(decimalPlaces); + const roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces); return `${roundedAmount} ETH`; }, ethBaseAmountInUsd: ( @@ -36,7 +36,7 @@ export const format = { if (_.isUndefined(ethBaseAmount) || _.isUndefined(ethUsdPrice)) { return defaultText; } - const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseAmount, ethDecimals); + const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseAmount, ETH_DECIMALS); return format.ethUnitAmountInUsd(ethUnitAmount, ethUsdPrice, decimalPlaces); }, ethUnitAmountInUsd: ( @@ -50,4 +50,7 @@ export const format = { } return `$${ethUnitAmount.mul(ethUsdPrice).toFixed(decimalPlaces)}`; }, + ethAddress: (address: string): string => { + return `0x${address.slice(2, 7)}…${address.slice(-5)}`; + }, }; diff --git a/packages/instant/src/util/gas_price_estimator.ts b/packages/instant/src/util/gas_price_estimator.ts new file mode 100644 index 000000000..6b15809a3 --- /dev/null +++ b/packages/instant/src/util/gas_price_estimator.ts @@ -0,0 +1,62 @@ +import { BigNumber, fetchAsync } from '@0x/utils'; + +import { + DEFAULT_ESTIMATED_TRANSACTION_TIME_MS, + DEFAULT_GAS_PRICE, + ETH_GAS_STATION_API_BASE_URL, + GWEI_IN_WEI, +} from '../constants'; + +interface EthGasStationResult { + average: number; + fastestWait: number; + fastWait: number; + fast: number; + safeLowWait: number; + blockNum: number; + avgWait: number; + block_time: number; + speed: number; + fastest: number; + safeLow: number; +} + +interface GasInfo { + gasPriceInWei: BigNumber; + estimatedTimeMs: number; +} + +const fetchFastAmountInWeiAsync = async (): Promise<GasInfo> => { + const res = await fetchAsync(`${ETH_GAS_STATION_API_BASE_URL}/json/ethgasAPI.json`); + const gasInfo = (await res.json()) as EthGasStationResult; + // Eth Gas Station result is gwei * 10 + const gasPriceInGwei = new BigNumber(gasInfo.fast / 10); + // Time is in minutes + const estimatedTimeMs = gasInfo.fastWait * 60 * 1000; // Minutes to MS + return { gasPriceInWei: gasPriceInGwei.mul(GWEI_IN_WEI), estimatedTimeMs }; +}; + +export class GasPriceEstimator { + private _lastFetched?: GasInfo; + public async getGasInfoAsync(): Promise<GasInfo> { + let fetchedAmount: GasInfo | undefined; + try { + fetchedAmount = await fetchFastAmountInWeiAsync(); + } catch { + fetchedAmount = undefined; + } + + if (fetchedAmount) { + this._lastFetched = fetchedAmount; + } + + return ( + fetchedAmount || + this._lastFetched || { + gasPriceInWei: DEFAULT_GAS_PRICE, + estimatedTimeMs: DEFAULT_ESTIMATED_TRANSACTION_TIME_MS, + } + ); + } +} +export const gasPriceEstimator = new GasPriceEstimator(); diff --git a/packages/instant/src/util/heartbeater.ts b/packages/instant/src/util/heartbeater.ts new file mode 100644 index 000000000..e700d489e --- /dev/null +++ b/packages/instant/src/util/heartbeater.ts @@ -0,0 +1,35 @@ +import { intervalUtils } from '@0x/utils'; +import * as _ from 'lodash'; + +type HeartbeatableFunction = () => Promise<void>; +export class Heartbeater { + private _intervalId?: NodeJS.Timer; + private readonly _performImmediatelyOnStart: boolean; + private readonly _performFunction: HeartbeatableFunction; + + public constructor(performingFunctionAsync: HeartbeatableFunction, performImmediatelyOnStart: boolean) { + this._performFunction = performingFunctionAsync; + this._performImmediatelyOnStart = performImmediatelyOnStart; + } + + public start(intervalTimeMs: number): void { + if (!_.isUndefined(this._intervalId)) { + throw new Error('Heartbeat is running, please stop before restarting'); + } + + if (this._performImmediatelyOnStart) { + // tslint:disable-next-line:no-floating-promises + this._performFunction(); + } + + // tslint:disable-next-line:no-unbound-method + this._intervalId = intervalUtils.setAsyncExcludingInterval(this._performFunction, intervalTimeMs, _.noop); + } + + public stop(): void { + if (this._intervalId) { + intervalUtils.clearInterval(this._intervalId); + } + this._intervalId = undefined; + } +} diff --git a/packages/instant/src/util/heartbeater_factory.ts b/packages/instant/src/util/heartbeater_factory.ts new file mode 100644 index 000000000..96a8ac4e6 --- /dev/null +++ b/packages/instant/src/util/heartbeater_factory.ts @@ -0,0 +1,22 @@ +import { asyncData } from '../redux/async_data'; +import { Store } from '../redux/store'; + +import { Heartbeater } from './heartbeater'; + +export interface HeartbeatFactoryOptions { + store: Store; + shouldPerformImmediatelyOnStart: boolean; +} +export const generateAccountHeartbeater = (options: HeartbeatFactoryOptions): Heartbeater => { + const { store, shouldPerformImmediatelyOnStart } = options; + return new Heartbeater(async () => { + await asyncData.fetchAccountInfoAndDispatchToStore({ store, shouldSetToLoading: false }); + }, shouldPerformImmediatelyOnStart); +}; + +export const generateBuyQuoteHeartbeater = (options: HeartbeatFactoryOptions): Heartbeater => { + const { store, shouldPerformImmediatelyOnStart } = options; + return new Heartbeater(async () => { + await asyncData.fetchCurrentBuyQuoteAndDispatchToStore({ store, shouldSetPending: false }); + }, shouldPerformImmediatelyOnStart); +}; diff --git a/packages/instant/src/util/maybe_big_number.ts b/packages/instant/src/util/maybe_big_number.ts new file mode 100644 index 000000000..9d3746e10 --- /dev/null +++ b/packages/instant/src/util/maybe_big_number.ts @@ -0,0 +1,25 @@ +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; + +import { Maybe } from '../types'; + +export const maybeBigNumberUtil = { + // converts a string to a Maybe<BigNumber> + // if string is a NaN, considered undefined + stringToMaybeBigNumber: (stringValue: string): Maybe<BigNumber> => { + let validBigNumber: BigNumber; + try { + validBigNumber = new BigNumber(stringValue); + } catch { + return undefined; + } + + return validBigNumber.isNaN() ? undefined : validBigNumber; + }, + areMaybeBigNumbersEqual: (val1: Maybe<BigNumber>, val2: Maybe<BigNumber>): boolean => { + if (!_.isUndefined(val1) && !_.isUndefined(val2)) { + return val1.equals(val2); + } + return _.isUndefined(val1) && _.isUndefined(val2); + }, +}; diff --git a/packages/instant/src/util/provider.ts b/packages/instant/src/util/provider.ts deleted file mode 100644 index 49705fd11..000000000 --- a/packages/instant/src/util/provider.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Provider } from 'ethereum-types'; - -export const getProvider = (): Provider => { - const injectedWeb3 = (window as any).web3 || undefined; - try { - // Use MetaMask/Mist provider - return injectedWeb3.currentProvider; - } catch (err) { - // Throws when user doesn't have MetaMask/Mist running - throw new Error(`No injected web3 found: ${err}`); - } -}; diff --git a/packages/instant/src/util/provider_factory.ts b/packages/instant/src/util/provider_factory.ts new file mode 100644 index 000000000..603f7674d --- /dev/null +++ b/packages/instant/src/util/provider_factory.ts @@ -0,0 +1,34 @@ +import { EmptyWalletSubprovider, RPCSubprovider, Web3ProviderEngine } from '@0x/subproviders'; +import { Provider } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { BLOCK_POLLING_INTERVAL_MS, ETHEREUM_NODE_URL_BY_NETWORK } from '../constants'; +import { Maybe, Network } from '../types'; + +export const providerFactory = { + getInjectedProviderIfExists: (): Maybe<Provider> => { + const injectedProviderIfExists = (window as any).ethereum; + if (!_.isUndefined(injectedProviderIfExists)) { + return injectedProviderIfExists; + } + const injectedWeb3IfExists = (window as any).web3; + if (!_.isUndefined(injectedWeb3IfExists) && !_.isUndefined(injectedWeb3IfExists.currentProvider)) { + return injectedWeb3IfExists.currentProvider; + } + return undefined; + }, + getFallbackNoSigningProvider: (network: Network): Provider => { + const providerEngine = new Web3ProviderEngine({ + pollingInterval: BLOCK_POLLING_INTERVAL_MS, + }); + // Intercept calls to `eth_accounts` and always return empty + providerEngine.addProvider(new EmptyWalletSubprovider()); + // Construct an RPC subprovider, all data based requests will be sent via the RPCSubprovider + // TODO(bmillman): make this more resilient to infura failures + const rpcUrl = ETHEREUM_NODE_URL_BY_NETWORK[network]; + providerEngine.addProvider(new RPCSubprovider(rpcUrl)); + // // Start the Provider Engine + providerEngine.start(); + return providerEngine; + }, +}; diff --git a/packages/instant/src/util/provider_state_factory.ts b/packages/instant/src/util/provider_state_factory.ts new file mode 100644 index 000000000..3281f6bfb --- /dev/null +++ b/packages/instant/src/util/provider_state_factory.ts @@ -0,0 +1,63 @@ +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { Provider } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { LOADING_ACCOUNT, NO_ACCOUNT } from '../constants'; +import { Maybe, Network, OrderSource, ProviderState } from '../types'; + +import { assetBuyerFactory } from './asset_buyer_factory'; +import { providerFactory } from './provider_factory'; + +export const providerStateFactory = { + getInitialProviderState: (orderSource: OrderSource, network: Network, provider?: Provider): ProviderState => { + if (!_.isUndefined(provider)) { + return providerStateFactory.getInitialProviderStateFromProvider(orderSource, network, provider); + } + const providerStateFromWindowIfExits = providerStateFactory.getInitialProviderStateFromWindowIfExists( + orderSource, + network, + ); + if (providerStateFromWindowIfExits) { + return providerStateFromWindowIfExits; + } else { + return providerStateFactory.getInitialProviderStateFallback(orderSource, network); + } + }, + getInitialProviderStateFromProvider: ( + orderSource: OrderSource, + network: Network, + provider: Provider, + ): ProviderState => { + const providerState: ProviderState = { + provider, + web3Wrapper: new Web3Wrapper(provider), + assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network), + account: LOADING_ACCOUNT, + }; + return providerState; + }, + getInitialProviderStateFromWindowIfExists: (orderSource: OrderSource, network: Network): Maybe<ProviderState> => { + const injectedProviderIfExists = providerFactory.getInjectedProviderIfExists(); + if (!_.isUndefined(injectedProviderIfExists)) { + const providerState: ProviderState = { + provider: injectedProviderIfExists, + web3Wrapper: new Web3Wrapper(injectedProviderIfExists), + assetBuyer: assetBuyerFactory.getAssetBuyer(injectedProviderIfExists, orderSource, network), + account: LOADING_ACCOUNT, + }; + return providerState; + } else { + return undefined; + } + }, + getInitialProviderStateFallback: (orderSource: OrderSource, network: Network): ProviderState => { + const provider = providerFactory.getFallbackNoSigningProvider(network); + const providerState: ProviderState = { + provider, + web3Wrapper: new Web3Wrapper(provider), + assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network), + account: NO_ACCOUNT, + }; + return providerState; + }, +}; diff --git a/packages/instant/src/util/time.ts b/packages/instant/src/util/time.ts new file mode 100644 index 000000000..bfe69cad5 --- /dev/null +++ b/packages/instant/src/util/time.ts @@ -0,0 +1,39 @@ +const secondsToMinutesAndRemainingSeconds = (seconds: number): { minutes: number; remainingSeconds: number } => { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds - minutes * 60; + + return { + minutes, + remainingSeconds, + }; +}; + +const padZero = (aNumber: number): string => { + return aNumber < 10 ? `0${aNumber}` : aNumber.toString(); +}; + +export const timeUtil = { + // converts seconds to human readable version of seconds or minutes + secondsToHumanDescription: (seconds: number): string => { + const { minutes, remainingSeconds } = secondsToMinutesAndRemainingSeconds(seconds); + + if (minutes === 0) { + const suffix = seconds > 1 ? 's' : ''; + return `${seconds} second${suffix}`; + } + + const minuteSuffix = minutes > 1 ? 's' : ''; + const minuteText = `${minutes} minute${minuteSuffix}`; + + const secondsSuffix = remainingSeconds > 1 ? 's' : ''; + const secondsText = remainingSeconds === 0 ? '' : ` ${remainingSeconds} second${secondsSuffix}`; + + return `${minuteText}${secondsText}`; + }, + // converts seconds to stopwatch time (i.e. 05:30 and 00:30) + // only goes up to minutes, not hours + secondsToStopwatchTime: (seconds: number): string => { + const { minutes, remainingSeconds } = secondsToMinutesAndRemainingSeconds(seconds); + return `${padZero(minutes)}:${padZero(remainingSeconds)}`; + }, +}; diff --git a/packages/instant/src/util/web3_wrapper.ts b/packages/instant/src/util/web3_wrapper.ts deleted file mode 100644 index 24dcd9076..000000000 --- a/packages/instant/src/util/web3_wrapper.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Web3Wrapper } from '@0x/web3-wrapper'; - -import { getProvider } from './provider'; - -export const web3Wrapper = new Web3Wrapper(getProvider()); diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts index c7db7eba7..4229b24ed 100644 --- a/packages/instant/test/util/asset.test.ts +++ b/packages/instant/test/util/asset.test.ts @@ -9,6 +9,7 @@ const ZRX_META_DATA: ERC20AssetMetaData = { assetProxyId: AssetProxyId.ERC20, symbol: 'zrx', decimals: 18, + name: '0x', }; const ZRX_ASSET: Asset = { assetData: ZRX_ASSET_DATA, diff --git a/packages/instant/test/util/error.test.ts b/packages/instant/test/util/error.test.ts deleted file mode 100644 index 90e9c5fb4..000000000 --- a/packages/instant/test/util/error.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { AssetBuyerError } from '@0x/asset-buyer'; -import { AssetProxyId } from '@0x/types'; - -import { Asset } from '../../src/types'; -import { errorUtil } from '../../src/util/error'; - -const ZRX_ASSET_DATA = '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; -const ZRX_ASSET: Asset = { - assetData: ZRX_ASSET_DATA, - metaData: { - assetProxyId: AssetProxyId.ERC20, - symbol: 'zrx', - decimals: 18, - }, -}; - -describe('errorUtil', () => { - describe('errorFlasher', () => { - it('should return error and asset name for InsufficientAssetLiquidity', () => { - const insufficientAssetError = new Error(AssetBuyerError.InsufficientAssetLiquidity); - expect(errorUtil.errorDescription(insufficientAssetError, ZRX_ASSET).message).toEqual( - 'Not enough ZRX available', - ); - }); - it('should return error default name for InsufficientAssetLiquidity', () => { - const insufficientZrxError = new Error(AssetBuyerError.InsufficientZrxLiquidity); - expect(errorUtil.errorDescription(insufficientZrxError).message).toEqual( - 'Not enough of this asset available', - ); - }); - it('should return asset name for InsufficientAssetLiquidity', () => { - const insufficientZrxError = new Error(AssetBuyerError.InsufficientZrxLiquidity); - expect(errorUtil.errorDescription(insufficientZrxError, ZRX_ASSET).message).toEqual( - 'Not enough ZRX available', - ); - }); - it('should return unavailable error and asset name for StandardRelayerApiError', () => { - const standardRelayerError = new Error(AssetBuyerError.StandardRelayerApiError); - expect(errorUtil.errorDescription(standardRelayerError, ZRX_ASSET).message).toEqual( - 'ZRX is currently unavailable', - ); - }); - it('should return error for AssetUnavailable error', () => { - const assetUnavailableError = new Error(`${AssetBuyerError.AssetUnavailable}: For assetData ${ZRX_ASSET}`); - expect(errorUtil.errorDescription(assetUnavailableError, ZRX_ASSET).message).toEqual( - 'ZRX is currently unavailable', - ); - }); - it('should return default for AssetUnavailable error', () => { - const assetUnavailableError = new Error(`${AssetBuyerError.AssetUnavailable}: For assetData xyz`); - expect(errorUtil.errorDescription(assetUnavailableError, undefined).message).toEqual( - 'This asset is currently unavailable', - ); - }); - }); -}); diff --git a/packages/instant/test/util/format.test.ts b/packages/instant/test/util/format.test.ts index 141df9275..c346b7604 100644 --- a/packages/instant/test/util/format.test.ts +++ b/packages/instant/test/util/format.test.ts @@ -1,15 +1,15 @@ import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; -import { ethDecimals } from '../../src/constants'; +import { ETH_DECIMALS } from '../../src/constants'; import { format } from '../../src/util/format'; const BIG_NUMBER_ONE = new BigNumber(1); const BIG_NUMBER_DECIMAL = new BigNumber(0.432414); const BIG_NUMBER_IRRATIONAL = new BigNumber(5.3014059295032); -const ONE_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_ONE, ethDecimals); -const DECIMAL_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_DECIMAL, ethDecimals); -const IRRATIONAL_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_IRRATIONAL, ethDecimals); +const ONE_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_ONE, ETH_DECIMALS); +const DECIMAL_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_DECIMAL, ETH_DECIMALS); +const IRRATIONAL_ETH_IN_BASE_UNITS = Web3Wrapper.toBaseUnitAmount(BIG_NUMBER_IRRATIONAL, ETH_DECIMALS); const BIG_NUMBER_FAKE_ETH_USD_PRICE = new BigNumber(2.534); describe('format', () => { @@ -20,8 +20,8 @@ describe('format', () => { it('converts .432414 ETH in base units to the string `.4324 ETH`', () => { expect(format.ethBaseAmount(DECIMAL_ETH_IN_BASE_UNITS)).toBe('0.4324 ETH'); }); - it('converts 5.3014059295032 ETH in base units to the string `5.3014 ETH`', () => { - expect(format.ethBaseAmount(IRRATIONAL_ETH_IN_BASE_UNITS)).toBe('5.3014 ETH'); + it('converts 5.3014059295032 ETH in base units to the string `5.301 ETH`', () => { + expect(format.ethBaseAmount(IRRATIONAL_ETH_IN_BASE_UNITS)).toBe('5.301 ETH'); }); it('returns defaultText param when ethBaseAmount is not defined', () => { const defaultText = 'defaultText'; @@ -38,8 +38,8 @@ describe('format', () => { it('converts BigNumer(.432414) to the string `.4324 ETH`', () => { expect(format.ethUnitAmount(BIG_NUMBER_DECIMAL)).toBe('0.4324 ETH'); }); - it('converts BigNumber(5.3014059295032) to the string `5.3014 ETH`', () => { - expect(format.ethUnitAmount(BIG_NUMBER_IRRATIONAL)).toBe('5.3014 ETH'); + it('converts BigNumber(5.3014059295032) to the string `5.301 ETH`', () => { + expect(format.ethUnitAmount(BIG_NUMBER_IRRATIONAL)).toBe('5.301 ETH'); }); it('returns defaultText param when ethUnitAmount is not defined', () => { const defaultText = 'defaultText'; diff --git a/packages/instant/test/util/time.test.ts b/packages/instant/test/util/time.test.ts new file mode 100644 index 000000000..fcb4e1875 --- /dev/null +++ b/packages/instant/test/util/time.test.ts @@ -0,0 +1,48 @@ +import { timeUtil } from '../../src/util/time'; + +describe('timeUtil', () => { + describe('secondsToHumanDescription', () => { + const numsToResults: { + [aNumber: number]: string; + } = { + 1: '1 second', + 59: '59 seconds', + 60: '1 minute', + 119: '1 minute 59 seconds', + 120: '2 minutes', + 121: '2 minutes 1 second', + 122: '2 minutes 2 seconds', + }; + + const nums = Object.keys(numsToResults); + nums.forEach(aNum => { + const numInt = parseInt(aNum, 10); + it(`should work for ${aNum} seconds`, () => { + const expectedResult = numsToResults[numInt]; + expect(timeUtil.secondsToHumanDescription(numInt)).toEqual(expectedResult); + }); + }); + }); + describe('secondsToStopwatchTime', () => { + const numsToResults: { + [aNumber: number]: string; + } = { + 1: '00:01', + 59: '00:59', + 60: '01:00', + 119: '01:59', + 120: '02:00', + 121: '02:01', + 2701: '45:01', + }; + + const nums = Object.keys(numsToResults); + nums.forEach(aNum => { + const numInt = parseInt(aNum, 10); + it(`should work for ${aNum} seconds`, () => { + const expectedResult = numsToResults[numInt]; + expect(timeUtil.secondsToStopwatchTime(numInt)).toEqual(expectedResult); + }); + }); + }); +}); diff --git a/packages/json-schemas/CHANGELOG.json b/packages/json-schemas/CHANGELOG.json index 3bd6552e3..f25a487b8 100644 --- a/packages/json-schemas/CHANGELOG.json +++ b/packages/json-schemas/CHANGELOG.json @@ -7,7 +7,8 @@ "Improve schemas by enforcing that amounts that must be whole numbers (e.g Order asset amounts) no longer allow decimal amounts", "pr": 1173 } - ] + ], + "timestamp": 1541740904 }, { "version": "2.0.0", diff --git a/packages/json-schemas/CHANGELOG.md b/packages/json-schemas/CHANGELOG.md index 21efe1be0..a1c784640 100644 --- a/packages/json-schemas/CHANGELOG.md +++ b/packages/json-schemas/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.1 - _November 9, 2018_ + + * Improve schemas by enforcing that amounts that must be whole numbers (e.g Order asset amounts) no longer allow decimal amounts (#1173) + ## v2.0.0 - _October 18, 2018_ * Convert all schemas to JSON files so that they can be used with `json-schema` implemenations in other programming languages. (#1145) diff --git a/packages/json-schemas/package.json b/packages/json-schemas/package.json index 97cf607c1..c87cbb65a 100644 --- a/packages/json-schemas/package.json +++ b/packages/json-schemas/package.json @@ -1,6 +1,6 @@ { "name": "@0x/json-schemas", - "version": "2.0.0", + "version": "2.0.1", "engines": { "node": ">=6.12" }, @@ -10,7 +10,7 @@ "scripts": { "build": "tsc -b", "build:ci": "yarn build", - "lint": "tslint --project . --exclude **/schemas/**/*", + "lint": "tslint --format stylish --project . --exclude **/schemas/**/*", "test": "yarn run_mocha", "rebuild_and_test": "run-s clean build test", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", @@ -39,14 +39,14 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/json-schemas/README.md", "dependencies": { - "@0x/typescript-typings": "^3.0.3", + "@0x/typescript-typings": "^3.0.4", "@types/node": "*", "jsonschema": "^1.2.0", "lodash.values": "^4.3.0" }, "devDependencies": { - "@0x/tslint-config": "^1.0.9", - "@0x/utils": "^2.0.3", + "@0x/tslint-config": "^1.0.10", + "@0x/utils": "^2.0.4", "@types/lodash.foreach": "^4.5.3", "@types/lodash.values": "^4.3.3", "@types/mocha": "^2.2.42", diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json index 04bdeba4c..35a0834f2 100644 --- a/packages/metacoin/package.json +++ b/packages/metacoin/package.json @@ -1,13 +1,13 @@ { "name": "@0x/metacoin", - "version": "0.0.24", + "version": "0.0.25", "engines": { "node": ">=6.12" }, "private": true, "description": "Example solidity project using 0x dev tools", "scripts": { - "lint": "tslint --project . --exclude **/src/contract_wrappers/**/*", + "lint": "tslint --format stylish --project . --exclude **/src/contract_wrappers/**/*", "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", "pre_build": "run-s compile generate_contract_wrappers copy_artifacts", @@ -29,25 +29,25 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "@0x/abi-gen": "^1.0.14", - "@0x/base-contract": "^3.0.2", - "@0x/sol-cov": "^2.1.8", - "@0x/subproviders": "^2.1.0", - "@0x/tslint-config": "^1.0.9", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/abi-gen": "^1.0.15", + "@0x/base-contract": "^3.0.3", + "@0x/sol-cov": "^2.1.9", + "@0x/subproviders": "^2.1.1", + "@0x/tslint-config": "^1.0.10", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "@types/mocha": "^5.2.2", "copyfiles": "^2.0.0", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethers": "~4.0.4", "lodash": "^4.17.5", "run-s": "^0.0.0" }, "devDependencies": { - "@0x/dev-utils": "^1.0.13", - "@0x/sol-compiler": "^1.1.8", + "@0x/dev-utils": "^1.0.14", + "@0x/sol-compiler": "^1.1.9", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.1", diff --git a/packages/migrations/CHANGELOG.json b/packages/migrations/CHANGELOG.json index 6d93feb1e..b7128f8e1 100644 --- a/packages/migrations/CHANGELOG.json +++ b/packages/migrations/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "2.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "2.0.0", "changes": [ { diff --git a/packages/migrations/CHANGELOG.md b/packages/migrations/CHANGELOG.md index 8d3346972..387ae0abb 100644 --- a/packages/migrations/CHANGELOG.md +++ b/packages/migrations/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.1 - _November 9, 2018_ + + * Dependencies updated + ## v2.0.0 - _October 18, 2018_ * Contract artifacts have been moved to the new @0xproject/contract-artifacts package. v1 migrations have been removed. `runMigrationsAsync` returns the addresses of the contracts that were deployed. (#1105) diff --git a/packages/migrations/package.json b/packages/migrations/package.json index 353697bfc..d8c5fb6c2 100644 --- a/packages/migrations/package.json +++ b/packages/migrations/package.json @@ -1,6 +1,6 @@ { "name": "@0x/migrations", - "version": "2.0.0", + "version": "2.0.1", "engines": { "node": ">=6.12" }, @@ -11,15 +11,15 @@ "build": "tsc -b", "build:ci": "yarn build", "clean": "shx rm -rf lib", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "migrate:v2": "run-s build script:migrate:v2", "script:migrate:v2": "node ./lib/migrate.js --contracts-version 2.0.0" }, "license": "Apache-2.0", "devDependencies": { - "@0x/dev-utils": "^1.0.13", - "@0x/tslint-config": "^1.0.9", - "@0x/types": "^1.2.0", + "@0x/dev-utils": "^1.0.14", + "@0x/tslint-config": "^1.0.10", + "@0x/types": "^1.2.1", "@types/yargs": "^10.0.0", "make-promises-safe": "^1.1.0", "npm-run-all": "^4.1.2", @@ -29,18 +29,18 @@ "yargs": "^10.0.3" }, "dependencies": { - "@0x/abi-gen-wrappers": "^1.0.1", - "@0x/base-contract": "^3.0.2", - "@0x/contract-addresses": "^1.0.1", - "@0x/contract-artifacts": "^1.0.1", - "@0x/order-utils": "^2.0.0", - "@0x/sol-compiler": "^1.1.8", - "@0x/subproviders": "^2.1.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/abi-gen-wrappers": "^1.0.2", + "@0x/base-contract": "^3.0.3", + "@0x/contract-addresses": "^1.1.0", + "@0x/contract-artifacts": "^1.1.0", + "@0x/order-utils": "^2.0.1", + "@0x/sol-compiler": "^1.1.9", + "@0x/subproviders": "^2.1.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "@ledgerhq/hw-app-eth": "^4.3.0", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethers": "~4.0.4", "lodash": "^4.17.5" }, diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index ba5f9ca6a..a31085014 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@0x/monorepo-scripts", - "version": "1.0.12", + "version": "1.0.13", "engines": { "node": ">=6.12" }, @@ -11,7 +11,7 @@ "scripts": { "build": "tsc -b", "build:ci": "yarn build", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "clean": "shx rm -rf lib", "test:publish": "run-s build script:publish", "find_unused_deps": "run-s build script:find_unused_deps", diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 22af2412b..abcf3d392 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "2.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "2.0.0", "changes": [ { diff --git a/packages/order-utils/CHANGELOG.md b/packages/order-utils/CHANGELOG.md index afd5c4adc..31bd02595 100644 --- a/packages/order-utils/CHANGELOG.md +++ b/packages/order-utils/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.1 - _November 9, 2018_ + + * Dependencies updated + ## v2.0.0 - _October 18, 2018_ * Added `ecSignOrderAsync` to first sign an order using `eth_signTypedData` and fallback to `eth_sign`. (#1102) diff --git a/packages/order-utils/package.json b/packages/order-utils/package.json index 69f14a79e..d6fb9892e 100644 --- a/packages/order-utils/package.json +++ b/packages/order-utils/package.json @@ -1,6 +1,6 @@ { "name": "@0x/order-utils", - "version": "2.0.0", + "version": "2.0.1", "engines": { "node": ">=6.12" }, @@ -17,7 +17,7 @@ "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", "clean": "shx rm -rf lib generated_docs", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" }, "config": { @@ -35,8 +35,8 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md", "devDependencies": { - "@0x/dev-utils": "^1.0.13", - "@0x/tslint-config": "^1.0.9", + "@0x/dev-utils": "^1.0.14", + "@0x/tslint-config": "^1.0.10", "@types/bn.js": "^4.11.0", "@types/lodash": "4.14.104", "chai": "^4.0.1", @@ -53,18 +53,18 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/abi-gen-wrappers": "^1.0.1", - "@0x/assert": "^1.0.14", - "@0x/base-contract": "^3.0.2", - "@0x/contract-artifacts": "^1.0.1", - "@0x/json-schemas": "^2.0.0", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/abi-gen-wrappers": "^1.0.2", + "@0x/assert": "^1.0.15", + "@0x/base-contract": "^3.0.3", + "@0x/contract-artifacts": "^1.1.0", + "@0x/json-schemas": "^2.0.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "@types/node": "*", "bn.js": "^4.11.8", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethereumjs-abi": "0.6.5", "ethereumjs-util": "^5.1.1", "ethers": "~4.0.4", diff --git a/packages/order-utils/test/asset_data_utils_test.ts b/packages/order-utils/test/asset_data_utils_test.ts index f8b850604..f175b7a38 100644 --- a/packages/order-utils/test/asset_data_utils_test.ts +++ b/packages/order-utils/test/asset_data_utils_test.ts @@ -1,6 +1,7 @@ import * as chai from 'chai'; -import { ERC20AssetData } from '@0x/types'; +import { ERC20AssetData, ERC721AssetData } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import { assetDataUtils } from '../src/asset_data_utils'; @@ -14,18 +15,36 @@ const KNOWN_ENCODINGS = [ address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48', }, + { + address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', + tokenId: new BigNumber(1), + assetData: + '0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001', + }, ]; const ERC20_ASSET_PROXY_ID = '0xf47261b0'; +const ERC721_ASSET_PROXY_ID = '0x02571792'; describe('assetDataUtils', () => { - it('should encode', () => { + it('should encode ERC20', () => { const assetData = assetDataUtils.encodeERC20AssetData(KNOWN_ENCODINGS[0].address); expect(assetData).to.equal(KNOWN_ENCODINGS[0].assetData); }); - it('should decode', () => { + it('should decode ERC20', () => { const assetData: ERC20AssetData = assetDataUtils.decodeERC20AssetData(KNOWN_ENCODINGS[0].assetData); expect(assetData.tokenAddress).to.equal(KNOWN_ENCODINGS[0].address); expect(assetData.assetProxyId).to.equal(ERC20_ASSET_PROXY_ID); }); + it('should encode ERC721', () => { + const assetData = assetDataUtils.encodeERC721AssetData(KNOWN_ENCODINGS[1].address, KNOWN_ENCODINGS[1] + .tokenId as BigNumber); + expect(assetData).to.equal(KNOWN_ENCODINGS[1].assetData); + }); + it('should decode ERC721', () => { + const assetData: ERC721AssetData = assetDataUtils.decodeERC721AssetData(KNOWN_ENCODINGS[1].assetData); + expect(assetData.tokenAddress).to.equal(KNOWN_ENCODINGS[1].address); + expect(assetData.assetProxyId).to.equal(ERC721_ASSET_PROXY_ID); + expect(assetData.tokenId).to.be.bignumber.equal(KNOWN_ENCODINGS[1].tokenId); + }); }); diff --git a/packages/order-watcher/CHANGELOG.json b/packages/order-watcher/CHANGELOG.json index d574f4a18..c662903a3 100644 --- a/packages/order-watcher/CHANGELOG.json +++ b/packages/order-watcher/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "2.2.1", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "2.2.0", "changes": [ { diff --git a/packages/order-watcher/CHANGELOG.md b/packages/order-watcher/CHANGELOG.md index 299af5437..b271956c7 100644 --- a/packages/order-watcher/CHANGELOG.md +++ b/packages/order-watcher/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.2.1 - _November 9, 2018_ + + * Dependencies updated + ## v2.2.0 - _October 18, 2018_ * Added getStats function and returns a Stats object (#1118) diff --git a/packages/order-watcher/package.json b/packages/order-watcher/package.json index acc70544e..174e0a3a6 100644 --- a/packages/order-watcher/package.json +++ b/packages/order-watcher/package.json @@ -1,6 +1,6 @@ { "name": "@0x/order-watcher", - "version": "2.2.0", + "version": "2.2.1", "description": "An order watcher daemon that watches for order validity", "keywords": [ "0x", @@ -14,7 +14,7 @@ "scripts": { "build": "yarn tsc -b", "build:ci": "yarn build", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "test:circleci": "run-s test:coverage", "test": "yarn run_mocha", "rebuild_and_test": "run-s build test", @@ -33,9 +33,9 @@ "node": ">=6.0.0" }, "devDependencies": { - "@0x/dev-utils": "^1.0.13", - "@0x/migrations": "^2.0.0", - "@0x/tslint-config": "^1.0.9", + "@0x/dev-utils": "^1.0.14", + "@0x/migrations": "^2.0.1", + "@0x/tslint-config": "^1.0.10", "@types/bintrees": "^1.0.2", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", @@ -57,21 +57,21 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/abi-gen-wrappers": "^1.0.1", - "@0x/assert": "^1.0.14", - "@0x/base-contract": "^3.0.2", - "@0x/contract-addresses": "^1.0.1", - "@0x/contract-artifacts": "^1.0.1", - "@0x/contract-wrappers": "^3.0.0", - "@0x/fill-scenarios": "^1.0.8", - "@0x/json-schemas": "^2.0.0", - "@0x/order-utils": "^2.0.0", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/abi-gen-wrappers": "^1.0.2", + "@0x/assert": "^1.0.15", + "@0x/base-contract": "^3.0.3", + "@0x/contract-addresses": "^1.1.0", + "@0x/contract-artifacts": "^1.1.0", + "@0x/contract-wrappers": "^3.0.1", + "@0x/fill-scenarios": "^1.0.9", + "@0x/json-schemas": "^2.0.1", + "@0x/order-utils": "^2.0.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "bintrees": "^1.0.2", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethereumjs-blockstream": "6.0.0", "ethers": "~4.0.4", "lodash": "^4.17.5" diff --git a/packages/react-docs/CHANGELOG.json b/packages/react-docs/CHANGELOG.json index 97485be96..228dfab4f 100644 --- a/packages/react-docs/CHANGELOG.json +++ b/packages/react-docs/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.0.15", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "1.0.14", "changes": [ diff --git a/packages/react-docs/CHANGELOG.md b/packages/react-docs/CHANGELOG.md index 096bd8460..991b3a490 100644 --- a/packages/react-docs/CHANGELOG.md +++ b/packages/react-docs/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.15 - _November 9, 2018_ + + * Dependencies updated + ## v1.0.14 - _October 18, 2018_ * Dependencies updated diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json index d3ccfa8da..827a54fdb 100644 --- a/packages/react-docs/package.json +++ b/packages/react-docs/package.json @@ -1,6 +1,6 @@ { "name": "@0x/react-docs", - "version": "1.0.14", + "version": "1.0.15", "engines": { "node": ">=6.12" }, @@ -8,7 +8,7 @@ "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "build": "tsc -b", "build:ci": "yarn build", "clean": "shx rm -rf lib" @@ -24,8 +24,8 @@ "url": "https://github.com/0xProject/0x-monorepo.git" }, "devDependencies": { - "@0x/dev-utils": "^1.0.13", - "@0x/tslint-config": "^1.0.9", + "@0x/dev-utils": "^1.0.14", + "@0x/tslint-config": "^1.0.10", "@types/compare-versions": "^3.0.0", "@types/styled-components": "^4.0.0", "make-promises-safe": "^1.1.0", @@ -34,9 +34,9 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/react-shared": "^1.0.17", - "@0x/types": "^1.2.0", - "@0x/utils": "^2.0.3", + "@0x/react-shared": "^1.0.18", + "@0x/types": "^1.2.1", + "@0x/utils": "^2.0.4", "@types/lodash": "4.14.104", "@types/material-ui": "^0.20.0", "@types/node": "*", diff --git a/packages/react-shared/CHANGELOG.json b/packages/react-shared/CHANGELOG.json index 8578b4603..4dd11398b 100644 --- a/packages/react-shared/CHANGELOG.json +++ b/packages/react-shared/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.0.18", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "1.0.17", "changes": [ { diff --git a/packages/react-shared/CHANGELOG.md b/packages/react-shared/CHANGELOG.md index fc3f7e32c..54ca77b16 100644 --- a/packages/react-shared/CHANGELOG.md +++ b/packages/react-shared/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.18 - _November 9, 2018_ + + * Dependencies updated + ## v1.0.17 - _October 18, 2018_ * Dependencies updated diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index 7a150bf35..35a69f105 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -1,6 +1,6 @@ { "name": "@0x/react-shared", - "version": "1.0.17", + "version": "1.0.18", "engines": { "node": ">=6.12" }, @@ -8,7 +8,7 @@ "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "build": "tsc", "build:ci": "yarn build", "watch_without_deps": "tsc -w", @@ -25,15 +25,15 @@ "url": "https://github.com/0xProject/0x-monorepo.git" }, "devDependencies": { - "@0x/dev-utils": "^1.0.13", - "@0x/tslint-config": "^1.0.9", + "@0x/dev-utils": "^1.0.14", + "@0x/tslint-config": "^1.0.10", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "^5.9.1", "typescript": "3.0.1" }, "dependencies": { - "@0x/types": "^1.2.0", + "@0x/types": "^1.2.1", "@material-ui/core": "^3.0.1", "@types/is-mobile": "0.3.0", "@types/lodash": "4.14.104", diff --git a/packages/sol-compiler/CHANGELOG.json b/packages/sol-compiler/CHANGELOG.json index d945c305b..724e68a68 100644 --- a/packages/sol-compiler/CHANGELOG.json +++ b/packages/sol-compiler/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.1.9", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "1.1.8", "changes": [ diff --git a/packages/sol-compiler/CHANGELOG.md b/packages/sol-compiler/CHANGELOG.md index 1a86d097d..322313f9e 100644 --- a/packages/sol-compiler/CHANGELOG.md +++ b/packages/sol-compiler/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.1.9 - _November 9, 2018_ + + * Dependencies updated + ## v1.1.8 - _October 18, 2018_ * Dependencies updated diff --git a/packages/sol-compiler/package.json b/packages/sol-compiler/package.json index 4c4370b1f..766c789df 100644 --- a/packages/sol-compiler/package.json +++ b/packages/sol-compiler/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-compiler", - "version": "1.1.8", + "version": "1.1.9", "engines": { "node": ">=6.12" }, @@ -19,7 +19,7 @@ "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", "clean": "shx rm -rf lib generated_docs", "migrate": "npm run build; node lib/src/cli.js migrate", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "test:circleci": "yarn test:coverage", "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" }, @@ -42,8 +42,8 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-compiler/README.md", "devDependencies": { - "@0x/dev-utils": "^1.0.13", - "@0x/tslint-config": "^1.0.9", + "@0x/dev-utils": "^1.0.14", + "@0x/tslint-config": "^1.0.10", "@types/mkdirp": "^0.5.2", "@types/require-from-string": "^1.2.0", "@types/semver": "^5.5.0", @@ -65,16 +65,16 @@ "zeppelin-solidity": "1.8.0" }, "dependencies": { - "@0x/assert": "^1.0.14", - "@0x/json-schemas": "^2.0.0", - "@0x/sol-resolver": "^1.0.15", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/assert": "^1.0.15", + "@0x/json-schemas": "^2.0.1", + "@0x/sol-resolver": "^1.0.16", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "@types/yargs": "^11.0.0", "chalk": "^2.3.0", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethereumjs-util": "^5.1.1", "lodash": "^4.17.5", "mkdirp": "^0.5.1", diff --git a/packages/sol-cov/CHANGELOG.json b/packages/sol-cov/CHANGELOG.json index cbee0233b..e331d608f 100644 --- a/packages/sol-cov/CHANGELOG.json +++ b/packages/sol-cov/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "2.1.9", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "2.1.8", "changes": [ { diff --git a/packages/sol-cov/CHANGELOG.md b/packages/sol-cov/CHANGELOG.md index 11ed486e2..e9d9d6952 100644 --- a/packages/sol-cov/CHANGELOG.md +++ b/packages/sol-cov/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.1.9 - _November 9, 2018_ + + * Dependencies updated + ## v2.1.8 - _October 18, 2018_ * Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library (#1105) diff --git a/packages/sol-cov/package.json b/packages/sol-cov/package.json index a56713d69..34e7a8559 100644 --- a/packages/sol-cov/package.json +++ b/packages/sol-cov/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-cov", - "version": "2.1.8", + "version": "2.1.9", "engines": { "node": ">=6.12" }, @@ -11,7 +11,7 @@ "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", "pre_build": "run-s copy_test_fixtures", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "test": "run-s compile_test run_mocha", "rebuild_and_test": "run-s clean build test", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", @@ -42,14 +42,14 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/sol-cov/README.md", "dependencies": { - "@0x/dev-utils": "^1.0.13", - "@0x/sol-compiler": "^1.1.8", - "@0x/subproviders": "^2.1.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/dev-utils": "^1.0.14", + "@0x/sol-compiler": "^1.1.9", + "@0x/subproviders": "^2.1.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "@types/solidity-parser-antlr": "^0.2.0", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethereumjs-util": "^5.1.1", "glob": "^7.1.2", "istanbul": "^0.4.5", @@ -61,7 +61,7 @@ "solidity-parser-antlr": "^0.2.12" }, "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/istanbul": "^0.4.30", "@types/loglevel": "^1.5.3", "@types/mkdirp": "^0.5.1", diff --git a/packages/sol-doc/CHANGELOG.json b/packages/sol-doc/CHANGELOG.json index e117ff291..d54569a4d 100644 --- a/packages/sol-doc/CHANGELOG.json +++ b/packages/sol-doc/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.0.4", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "1.0.3", "changes": [ diff --git a/packages/sol-doc/CHANGELOG.md b/packages/sol-doc/CHANGELOG.md index 9adc02b19..d44af5d7f 100644 --- a/packages/sol-doc/CHANGELOG.md +++ b/packages/sol-doc/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.4 - _November 9, 2018_ + + * Dependencies updated + ## v1.0.3 - _October 18, 2018_ * Dependencies updated diff --git a/packages/sol-doc/package.json b/packages/sol-doc/package.json index de10b9e17..0eb5d6fd5 100644 --- a/packages/sol-doc/package.json +++ b/packages/sol-doc/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-doc", - "version": "1.0.3", + "version": "1.0.4", "description": "Solidity documentation generator", "main": "lib/src/index.js", "types": "lib/src/index.d.js", @@ -11,7 +11,7 @@ "test:circleci": "yarn test:coverage", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", - "lint": "tslint --project . --format stylish", + "lint": "tslint --format stylish --project .", "clean": "shx rm -rf lib", "generate-v1-protocol-docs": "(cd ../contracts/src/1.0.0; node ../../../../node_modules/.bin/sol-doc --contracts-dir . --contracts Exchange/Exchange_v1.sol TokenRegistry/TokenRegistry.sol TokenTransferProxy/TokenTransferProxy_v1.sol) > v1.0.0.json", "generate-v2-protocol-docs": "(cd ../contracts/src/2.0.0; node ../../../../node_modules/.bin/sol-doc --contracts-dir . --contracts Exchange/Exchange.sol AssetProxy/ERC20Proxy.sol AssetProxy/ERC721Proxy.sol OrderValidator/OrderValidator.sol Forwarder/Forwarder.sol AssetProxyOwner/AssetProxyOwner.sol) > v2.0.0.json", @@ -25,16 +25,16 @@ "author": "F. Eugene Aumson", "license": "Apache-2.0", "dependencies": { - "@0x/sol-compiler": "^1.1.8", - "@0x/types": "^1.2.0", - "@0x/utils": "^2.0.3", - "ethereum-types": "^1.1.1", + "@0x/sol-compiler": "^1.1.9", + "@0x/types": "^1.2.1", + "@0x/utils": "^2.0.4", + "ethereum-types": "^1.1.2", "ethereumjs-util": "^5.1.1", "lodash": "^4.17.10", "yargs": "^12.0.2" }, "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "chai": "^4.1.2", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.2", diff --git a/packages/sol-resolver/CHANGELOG.json b/packages/sol-resolver/CHANGELOG.json index a72ca6d63..a6ff84237 100644 --- a/packages/sol-resolver/CHANGELOG.json +++ b/packages/sol-resolver/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.0.16", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "1.0.15", "changes": [ diff --git a/packages/sol-resolver/CHANGELOG.md b/packages/sol-resolver/CHANGELOG.md index 671d3ea84..f8c5a7692 100644 --- a/packages/sol-resolver/CHANGELOG.md +++ b/packages/sol-resolver/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.16 - _November 9, 2018_ + + * Dependencies updated + ## v1.0.15 - _October 18, 2018_ * Dependencies updated diff --git a/packages/sol-resolver/package.json b/packages/sol-resolver/package.json index 4bd63f406..7572072c4 100644 --- a/packages/sol-resolver/package.json +++ b/packages/sol-resolver/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-resolver", - "version": "1.0.15", + "version": "1.0.16", "engines": { "node": ">=6.12" }, @@ -11,7 +11,7 @@ "build": "tsc -b", "build:ci": "yarn build", "clean": "shx rm -rf lib", - "lint": "tslint --project ." + "lint": "tslint --format stylish --project ." }, "repository": { "type": "git", @@ -23,15 +23,15 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/resolver/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", "typescript": "3.0.1" }, "dependencies": { - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", "lodash": "^4.17.5" }, "publishConfig": { diff --git a/packages/sra-spec/CHANGELOG.json b/packages/sra-spec/CHANGELOG.json index a068df3fb..e053b3163 100644 --- a/packages/sra-spec/CHANGELOG.json +++ b/packages/sra-spec/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.0.8", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "1.0.7", "changes": [ { diff --git a/packages/sra-spec/CHANGELOG.md b/packages/sra-spec/CHANGELOG.md index ba4cddec2..02d22431f 100644 --- a/packages/sra-spec/CHANGELOG.md +++ b/packages/sra-spec/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.8 - _November 9, 2018_ + + * Dependencies updated + ## v1.0.7 - _October 18, 2018_ * Make @loopback/openapi-v3-types a 'dependency' so it's available to users of the library (#1105) diff --git a/packages/sra-spec/package.json b/packages/sra-spec/package.json index 0c55465d7..e3b6ad979 100644 --- a/packages/sra-spec/package.json +++ b/packages/sra-spec/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sra-spec", - "version": "1.0.7", + "version": "1.0.8", "engines": { "node": ">=6.12" }, @@ -10,7 +10,7 @@ "scripts": { "serve": "redoc-cli serve lib/api.json --watch", "watch_without_deps": "run-p build:watch serve", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "test": "swagger-cli validate lib/api.json", "rebuild_and_test": "run-s clean build test", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", @@ -35,11 +35,11 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/sra-spec/README.md", "dependencies": { - "@0x/json-schemas": "^2.0.0", + "@0x/json-schemas": "^2.0.1", "@loopback/openapi-v3-types": "^0.8.2" }, "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/mocha": "^2.2.42", "@types/node": "^10.5.3", "chai": "^4.0.1", diff --git a/packages/sra-spec/src/json-schemas.ts b/packages/sra-spec/src/json-schemas.ts index 9eb32de59..f9342ca9e 100644 --- a/packages/sra-spec/src/json-schemas.ts +++ b/packages/sra-spec/src/json-schemas.ts @@ -2,6 +2,7 @@ import { schemas as jsonSchemas } from '@0x/json-schemas'; // Only include schemas we actually need const { + wholeNumberSchema, numberSchema, addressSchema, hexSchema, @@ -28,6 +29,7 @@ const { } = jsonSchemas; const usedSchemas = { + wholeNumberSchema, numberSchema, addressSchema, hexSchema, diff --git a/packages/subproviders/CHANGELOG.json b/packages/subproviders/CHANGELOG.json index b145bbbe5..816c776a5 100644 --- a/packages/subproviders/CHANGELOG.json +++ b/packages/subproviders/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "2.1.1", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "2.1.0", "changes": [ { diff --git a/packages/subproviders/CHANGELOG.md b/packages/subproviders/CHANGELOG.md index f9e3e63a6..72ac9d1f7 100644 --- a/packages/subproviders/CHANGELOG.md +++ b/packages/subproviders/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.1.1 - _November 9, 2018_ + + * Dependencies updated + ## v2.1.0 - _October 18, 2018_ * Add `MetamaskSubprovider` to handle inconsistent JSON RPC behaviour (#1102) diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index 4eef09fe6..1a25b5e32 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -1,6 +1,6 @@ { "name": "@0x/subproviders", - "version": "2.1.0", + "version": "2.1.1", "engines": { "node": ">=6.12" }, @@ -11,7 +11,7 @@ "build": "tsc -b", "build:ci": "yarn build", "clean": "shx rm -rf lib generated_docs", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "run_mocha_unit": "mocha --require source-map-support/register --require make-promises-safe lib/test/unit/**/*_test.js --timeout 10000 --bail --exit", "run_mocha_integration": "mocha --require source-map-support/register --require make-promises-safe lib/test/integration/**/*_test.js --timeout 10000 --bail --exit", "test": "npm run test:unit", @@ -29,13 +29,13 @@ } }, "dependencies": { - "@0x/assert": "^1.0.14", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/assert": "^1.0.15", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "@ledgerhq/hw-app-eth": "^4.3.0", - "@ledgerhq/hw-transport-u2f": "^4.3.0", + "@ledgerhq/hw-transport-u2f": "4.24.0", "@types/eth-lightwallet": "^3.0.0", "@types/ganache-core": "^2.1.0", "@types/hdkey": "^0.7.0", @@ -43,7 +43,7 @@ "bip39": "^2.5.0", "bn.js": "^4.11.8", "eth-lightwallet": "^3.0.1", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethereumjs-tx": "^1.3.5", "ethereumjs-util": "^5.1.1", "ganache-core": "^2.2.1", @@ -54,7 +54,7 @@ "web3-provider-engine": "14.0.6" }, "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/bip39": "^2.4.0", "@types/bn.js": "^4.11.0", "@types/ethereumjs-tx": "^1.0.0", diff --git a/packages/testnet-faucets/package.json b/packages/testnet-faucets/package.json index e683ae25a..c46f5465a 100644 --- a/packages/testnet-faucets/package.json +++ b/packages/testnet-faucets/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@0x/testnet-faucets", - "version": "1.0.52", + "version": "1.0.53", "engines": { "node": ">=6.12" }, @@ -12,19 +12,19 @@ "build:ci": "yarn build", "dev": "node ../../node_modules/gulp/bin/gulp.js run", "start": "node ./server/server.js", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "clean": "shx rm -rf server" }, "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { - "0x.js": "^2.0.0", - "@0x/subproviders": "^2.1.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "0x.js": "^2.0.1", + "@0x/subproviders": "^2.1.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "body-parser": "^1.17.1", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethereumjs-tx": "^1.3.5", "ethereumjs-util": "^5.1.1", "express": "^4.15.2", @@ -32,7 +32,7 @@ "rollbar": "^0.6.5" }, "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/body-parser": "^1.16.1", "@types/express": "^4.0.35", "@types/lodash": "4.14.104", diff --git a/packages/tslint-config/CHANGELOG.json b/packages/tslint-config/CHANGELOG.json index 99fbc7989..9f504216c 100644 --- a/packages/tslint-config/CHANGELOG.json +++ b/packages/tslint-config/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.0.10", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "1.0.9", "changes": [ diff --git a/packages/tslint-config/CHANGELOG.md b/packages/tslint-config/CHANGELOG.md index f0b429d29..f5cacb5d1 100644 --- a/packages/tslint-config/CHANGELOG.md +++ b/packages/tslint-config/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.10 - _November 9, 2018_ + + * Dependencies updated + ## v1.0.9 - _October 18, 2018_ * Dependencies updated diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json index 0a8c1b245..2914f31e2 100644 --- a/packages/tslint-config/package.json +++ b/packages/tslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@0x/tslint-config", - "version": "1.0.9", + "version": "1.0.10", "engines": { "node": ">=6.12" }, @@ -10,7 +10,7 @@ "build": "tsc -b", "build:ci": "yarn build", "clean": "shx rm -rf lib", - "lint": "tslint --project ." + "lint": "tslint --format stylish --project ." }, "repository": { "type": "git", diff --git a/packages/types/CHANGELOG.json b/packages/types/CHANGELOG.json index 84a06faa8..6d9f83c57 100644 --- a/packages/types/CHANGELOG.json +++ b/packages/types/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.2.1", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "version": "1.2.0", "changes": [ { diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index d1b8ea79c..556bf1799 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.2.1 - _November 9, 2018_ + + * Dependencies updated + ## v1.2.0 - _October 18, 2018_ * Added `EIP712Parameter` `EIP712Types` `EIP712TypedData` for EIP712 signing (#1102) diff --git a/packages/types/package.json b/packages/types/package.json index fdd68a486..c32e9a6f7 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@0x/types", - "version": "1.2.0", + "version": "1.2.1", "engines": { "node": ">=6.12" }, @@ -11,7 +11,7 @@ "build": "tsc -b", "build:ci": "yarn build", "clean": "shx rm -rf lib", - "lint": "tslint --project ." + "lint": "tslint --format stylish --project ." }, "license": "Apache-2.0", "repository": { @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/types/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", @@ -32,7 +32,7 @@ "dependencies": { "@types/node": "*", "bignumber.js": "~4.1.0", - "ethereum-types": "^1.1.1" + "ethereum-types": "^1.1.2" }, "publishConfig": { "access": "public" diff --git a/packages/typescript-typings/CHANGELOG.json b/packages/typescript-typings/CHANGELOG.json index e21e788b5..5653b397d 100644 --- a/packages/typescript-typings/CHANGELOG.json +++ b/packages/typescript-typings/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "3.0.4", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "3.0.3", "changes": [ diff --git a/packages/typescript-typings/CHANGELOG.md b/packages/typescript-typings/CHANGELOG.md index df58bfc57..62e6665be 100644 --- a/packages/typescript-typings/CHANGELOG.md +++ b/packages/typescript-typings/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.4 - _November 9, 2018_ + + * Dependencies updated + ## v3.0.3 - _October 18, 2018_ * Dependencies updated diff --git a/packages/typescript-typings/package.json b/packages/typescript-typings/package.json index 6b9e67602..9dbbe1370 100644 --- a/packages/typescript-typings/package.json +++ b/packages/typescript-typings/package.json @@ -1,6 +1,6 @@ { "name": "@0x/typescript-typings", - "version": "3.0.3", + "version": "3.0.4", "engines": { "node": ">=6.12" }, @@ -27,7 +27,7 @@ "@types/bn.js": "^4.11.0", "@types/react": "*", "bignumber.js": "~4.1.0", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "popper.js": "1.14.3" }, "devDependencies": { diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index 7594581d4..6c9da5f37 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "2.0.4", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { "timestamp": 1539871071, "version": "2.0.3", "changes": [ diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 45b2cd0c2..4fdd13d9c 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.4 - _November 9, 2018_ + + * Dependencies updated + ## v2.0.3 - _October 18, 2018_ * Dependencies updated diff --git a/packages/utils/package.json b/packages/utils/package.json index f89cfda17..24c2496b0 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@0x/utils", - "version": "2.0.3", + "version": "2.0.4", "engines": { "node": ">=6.12" }, @@ -11,7 +11,7 @@ "build": "tsc -b", "build:ci": "yarn build", "clean": "shx rm -rf lib", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "test": "yarn run_mocha", "test:circleci": "yarn test:coverage", "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --bail --exit", @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/utils/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/detect-node": "2.0.0", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", @@ -41,13 +41,13 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", "@types/node": "*", "abortcontroller-polyfill": "^1.1.9", "bignumber.js": "~4.1.0", "detect-node": "2.0.3", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethereumjs-util": "^5.1.1", "ethers": "~4.0.4", "isomorphic-fetch": "^2.2.1", diff --git a/packages/utils/src/configured_bignumber.ts b/packages/utils/src/configured_bignumber.ts index 2b22b6938..34b57d303 100644 --- a/packages/utils/src/configured_bignumber.ts +++ b/packages/utils/src/configured_bignumber.ts @@ -11,4 +11,27 @@ BigNumber.config({ DECIMAL_PLACES: 78, }); +// Set a debug print function for NodeJS +// Upstream issue: https://github.com/MikeMcl/bignumber.js/issues/188 +import isNode = require('detect-node'); +if (isNode) { + // Dynamically load a NodeJS specific module. + // Typescript requires all imports to be global, so we need to use + // `const` here and disable the tslint warning. + // tslint:disable-next-line: no-var-requires + const util = require('util'); + + // Set a custom util.inspect function + // HACK: We add a function to the BigNumber class by assigning to the + // prototype. The function name is a symbol provided by Node. + (BigNumber.prototype as any)[util.inspect.custom] = function(): string { + // HACK: When executed, `this` will refer to the BigNumber instance. + // This is also why we need a function expression instead of an + // arrow function, as the latter does not have a `this`. + // Return the readable string representation + // tslint:disable-next-line: no-invalid-this + return this.toString(); + }; +} + export { BigNumber }; diff --git a/packages/web3-wrapper/CHANGELOG.json b/packages/web3-wrapper/CHANGELOG.json index 6b554110f..7b21da73a 100644 --- a/packages/web3-wrapper/CHANGELOG.json +++ b/packages/web3-wrapper/CHANGELOG.json @@ -7,7 +7,8 @@ "Fix bug in `getTransactionByHashAsync` which was causing the return value to have the wrong type (raw fields instead of unmarshalled fields).", "pr": 1177 } - ] + ], + "timestamp": 1541740904 }, { "version": "3.1.0", diff --git a/packages/web3-wrapper/CHANGELOG.md b/packages/web3-wrapper/CHANGELOG.md index 90d62953a..ebbef2c48 100644 --- a/packages/web3-wrapper/CHANGELOG.md +++ b/packages/web3-wrapper/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.1.1 - _November 9, 2018_ + + * Fix bug in `getTransactionByHashAsync` which was causing the return value to have the wrong type (raw fields instead of unmarshalled fields). (#1177) + ## v3.1.0 - _October 18, 2018_ * Add `signTypedData` to perform EIP712 `eth_signTypedData`. (#1102) diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index 579957747..2b87cc92a 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -1,6 +1,6 @@ { "name": "@0x/web3-wrapper", - "version": "3.1.0", + "version": "3.1.1", "engines": { "node": ">=6.12" }, @@ -11,7 +11,7 @@ "build": "tsc -b", "build:ci": "yarn build", "clean": "shx rm -rf lib generated_docs", - "lint": "tslint --project .", + "lint": "tslint --format stylish --project .", "test": "yarn run_mocha", "rebuild_and_test": "run-s clean build test", "test:circleci": "yarn test:coverage", @@ -35,7 +35,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/web3-wrapper/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^1.0.10", "@types/ganache-core": "^2.1.0", "@types/lodash": "4.14.104", "chai": "^4.0.1", @@ -53,11 +53,11 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/assert": "^1.0.14", - "@0x/json-schemas": "^2.0.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "ethereum-types": "^1.1.1", + "@0x/assert": "^1.0.15", + "@0x/json-schemas": "^2.0.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "ethereum-types": "^1.1.2", "ethereumjs-util": "^5.1.1", "ethers": "~4.0.4", "lodash": "^4.17.5" diff --git a/packages/website/package.json b/packages/website/package.json index 771b1b908..fd671d85d 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,40 +1,41 @@ { "name": "@0x/website", - "version": "0.0.55", + "version": "0.0.56", "engines": { "node": ">=6.12" }, "private": true, "description": "Website and 0x portal dapp", "scripts": { - "build": "node --max_old_space_size=8192 ../../node_modules/.bin/webpack --mode production", + "build": "yarn build:dev", + "build:prod": "node --max_old_space_size=8192 ../../node_modules/.bin/webpack --mode production", "build:dev": "../../node_modules/.bin/webpack --mode development", "clean": "shx rm -f public/bundle*", - "lint": "tslint --project . 'ts/**/*.ts' 'ts/**/*.tsx'", + "lint": "tslint --format stylish --project . 'ts/**/*.ts' 'ts/**/*.tsx'", "dev": "webpack-dev-server --mode development --content-base public --https", - "deploy_dogfood": "npm run build; aws s3 sync ./public/. s3://dogfood.0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers", - "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": "DEPLOY_ROLLBAR_SOURCEMAPS=true 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 --exclude *.map.js" + "deploy_dogfood": "npm run build:prod; aws s3 sync ./public/. s3://dogfood.0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers", + "deploy_staging": "npm run build:prod; 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": "DEPLOY_ROLLBAR_SOURCEMAPS=true npm run build:prod; aws s3 sync ./public/. s3://0xproject.com --profile 0xproject --region us-east-1 --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --exclude *.map.js" }, "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { - "@0x/contract-wrappers": "^3.0.0", - "@0x/json-schemas": "^2.0.0", - "@0x/order-utils": "^2.0.0", - "@0x/react-docs": "^1.0.14", - "@0x/react-shared": "^1.0.17", - "@0x/subproviders": "^2.1.0", - "@0x/types": "^1.2.0", - "@0x/typescript-typings": "^3.0.3", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/contract-wrappers": "^3.0.1", + "@0x/json-schemas": "^2.0.1", + "@0x/order-utils": "^2.0.1", + "@0x/react-docs": "^1.0.15", + "@0x/react-shared": "^1.0.18", + "@0x/subproviders": "^2.1.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", "accounting": "^0.4.1", "basscss": "^8.0.3", "blockies": "^0.0.2", "bowser": "^1.9.3", "deep-equal": "^1.0.1", - "ethereum-types": "^1.1.1", + "ethereum-types": "^1.1.2", "ethereumjs-util": "^5.1.1", "find-versions": "^2.0.0", "jsonschema": "^1.2.0", diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 62e16dd1d..b43c41739 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -20,7 +20,7 @@ import { Web3ProviderEngine, } from '@0x/subproviders'; import { SignedOrder, Token as ZeroExToken } from '@0x/types'; -import { BigNumber, intervalUtils, logUtils, promisify } from '@0x/utils'; +import { BigNumber, intervalUtils, logUtils } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import { BlockParam, LogWithDecodedArgs, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import * as _ from 'lodash'; @@ -38,9 +38,9 @@ import { BlockchainErrs, ContractInstance, Fill, + InjectedProvider, InjectedProviderObservable, InjectedProviderUpdate, - InjectedWeb3, Providers, ProviderType, Side, @@ -83,6 +83,7 @@ export class Blockchain { private _ledgerSubprovider: LedgerSubprovider; private _defaultGasPrice: BigNumber; private _watchGasPriceIntervalId: NodeJS.Timer; + private _injectedProviderIfExists?: InjectedProvider; private static _getNameGivenProvider(provider: Provider): string { const providerType = utils.getProviderType(provider); const providerNameIfExists = providerToName[providerType]; @@ -91,48 +92,12 @@ export class Blockchain { } return providerNameIfExists; } - private static _getInjectedWeb3(): InjectedWeb3 { - const injectedWeb3IfExists = (window as any).web3; - // Our core assumptions about the injected web3 object is that it has the following - // properties and methods. - if ( - _.isUndefined(injectedWeb3IfExists) || - _.isUndefined(injectedWeb3IfExists.version) || - _.isUndefined(injectedWeb3IfExists.version.getNetwork) || - _.isUndefined(injectedWeb3IfExists.currentProvider) - ) { - return undefined; - } - return injectedWeb3IfExists; - } - private static async _getInjectedWeb3ProviderNetworkIdIfExistsAsync(): Promise<number | undefined> { - // 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 injectedWeb3IfExists = Blockchain._getInjectedWeb3(); - let networkIdIfExists: number; - if (!_.isUndefined(injectedWeb3IfExists)) { - try { - networkIdIfExists = _.parseInt( - await promisify<string>( - injectedWeb3IfExists.version.getNetwork.bind(injectedWeb3IfExists.version), - )(), - ); - } catch (err) { - // Ignore error and proceed with networkId undefined - } - } - return networkIdIfExists; - } private static async _getProviderAsync( - injectedWeb3: InjectedWeb3, - networkIdIfExists: number, + injectedProviderIfExists?: InjectedProvider, + networkIdIfExists?: number, shouldUserLedgerProvider: boolean = false, ): Promise<[Provider, LedgerSubprovider | undefined]> { - const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3); + const doesInjectedProviderExist = !_.isUndefined(injectedProviderIfExists); const isNetworkIdAvailable = !_.isUndefined(networkIdIfExists); const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists]; const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId); @@ -156,16 +121,16 @@ export class Blockchain { provider.addProvider(new RedundantSubprovider(rpcSubproviders)); provider.start(); return [provider, ledgerSubprovider]; - } else if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) { + } else if (doesInjectedProviderExist && 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. const provider = new Web3ProviderEngine(); - const providerName = this._getNameGivenProvider(injectedWeb3.currentProvider); + const providerName = this._getNameGivenProvider(injectedProviderIfExists); // Wrap Metamask in a compatability wrapper MetamaskSubprovider (to handle inconsistencies) const signerSubprovider = providerName === constants.PROVIDER_NAME_METAMASK - ? new MetamaskSubprovider(injectedWeb3.currentProvider) - : new SignerSubprovider(injectedWeb3.currentProvider); + ? new MetamaskSubprovider(injectedProviderIfExists) + : new SignerSubprovider(injectedProviderIfExists); provider.addProvider(signerSubprovider); provider.addProvider(new FilterSubprovider()); const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => { @@ -174,9 +139,9 @@ export class Blockchain { provider.addProvider(new RedundantSubprovider(rpcSubproviders)); provider.start(); return [provider, undefined]; - } else if (doesInjectedWeb3Exist) { + } else if (doesInjectedProviderExist) { // Since no public node for this network, all requests go to injectedWeb3 instance - return [injectedWeb3.currentProvider, undefined]; + return [injectedProviderIfExists, undefined]; } 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 @@ -261,7 +226,7 @@ export class Blockchain { const shouldUserLedgerProvider = false; this._dispatcher.updateBlockchainIsLoaded(false); // We don't want to be out of sync with the network the injected provider declares. - const networkId = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync(); + const networkId = await this._getInjectedProviderNetworkIdIfExistsAsync(); await this._resetOrInitializeAsync(networkId, shouldPollUserAddress, shouldUserLedgerProvider); } public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> { @@ -611,6 +576,45 @@ export class Blockchain { this._dispatcher.updateBlockchainIsLoaded(true); } + private async _getInjectedProviderIfExistsAsync(): Promise<InjectedProvider | undefined> { + if (!_.isUndefined(this._injectedProviderIfExists)) { + return this._injectedProviderIfExists; + } + let injectedProviderIfExists = (window as any).ethereum; + if (!_.isUndefined(injectedProviderIfExists)) { + if (!_.isUndefined(injectedProviderIfExists.enable)) { + try { + await injectedProviderIfExists.enable(); + } catch (err) { + errorReporter.report(err); + } + } + } else { + const injectedWeb3IfExists = (window as any).web3; + if (!_.isUndefined(injectedWeb3IfExists.currentProvider)) { + injectedProviderIfExists = injectedWeb3IfExists.currentProvider; + } else { + return undefined; + } + } + this._injectedProviderIfExists = injectedProviderIfExists; + return injectedProviderIfExists; + } + private async _getInjectedProviderNetworkIdIfExistsAsync(): Promise<number | undefined> { + // 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 injectedProviderIfExists = await this._getInjectedProviderIfExistsAsync(); + let networkIdIfExists: number; + if (!_.isUndefined(injectedProviderIfExists)) { + try { + const injectedWeb3Wrapper = new Web3Wrapper(injectedProviderIfExists); + networkIdIfExists = await injectedWeb3Wrapper.getNetworkIdAsync(); + } catch (err) { + // Ignore error and proceed with networkId undefined + } + } + return networkIdIfExists; + } private async _showEtherScanLinkAndAwaitTransactionMinedAsync( txHash: string, ): Promise<TransactionReceiptWithDecodedLogs> { @@ -804,17 +808,17 @@ export class Blockchain { } private async _onPageLoadInitFireAndForgetAsync(): Promise<void> { await utils.onPageLoadPromise; // wait for page to load - const networkIdIfExists = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync(); + const networkIdIfExists = await this._getInjectedProviderNetworkIdIfExistsAsync(); this.networkId = !_.isUndefined(networkIdIfExists) ? networkIdIfExists : constants.NETWORK_ID_MAINNET; - const injectedWeb3IfExists = Blockchain._getInjectedWeb3(); - if (!_.isUndefined(injectedWeb3IfExists) && !_.isUndefined(injectedWeb3IfExists.currentProvider)) { - const injectedProviderObservable = injectedWeb3IfExists.currentProvider.publicConfigStore; + const injectedProviderIfExists = await this._getInjectedProviderIfExistsAsync(); + if (!_.isUndefined(injectedProviderIfExists)) { + const injectedProviderObservable = injectedProviderIfExists.publicConfigStore; if (!_.isUndefined(injectedProviderObservable) && _.isUndefined(this._injectedProviderObservable)) { this._injectedProviderObservable = injectedProviderObservable; this._injectedProviderObservable.subscribe(this._injectedProviderUpdateHandler); } } - this._updateProviderName(injectedWeb3IfExists); + this._updateProviderName(injectedProviderIfExists); const shouldPollUserAddress = true; const shouldUseLedgerProvider = false; this._startWatchingGasPrice(); @@ -851,12 +855,14 @@ export class Blockchain { } this._dispatcher.updateUserWeiBalance(undefined); this.networkId = networkId; - const injectedWeb3IfExists = Blockchain._getInjectedWeb3(); + const injectedProviderIfExists = await this._getInjectedProviderIfExistsAsync(); const [provider, ledgerSubproviderIfExists] = await Blockchain._getProviderAsync( - injectedWeb3IfExists, + injectedProviderIfExists, networkId, shouldUserLedgerProvider, ); + this._web3Wrapper = new Web3Wrapper(provider); + this.networkId = await this._web3Wrapper.getNetworkIdAsync(); if (!_.isUndefined(this._contractWrappers)) { this._contractWrappers.unsubscribeAll(); } @@ -867,7 +873,6 @@ export class Blockchain { if (!_.isUndefined(this._blockchainWatcher)) { this._blockchainWatcher.destroy(); } - this._web3Wrapper = new Web3Wrapper(provider); this._blockchainWatcher = new BlockchainWatcher(this._dispatcher, this._web3Wrapper, shouldPollUserAddress); if (shouldUserLedgerProvider && !_.isUndefined(ledgerSubproviderIfExists)) { delete this._userAddressIfExists; @@ -879,7 +884,7 @@ export class Blockchain { const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync(); this._userAddressIfExists = userAddresses[0]; this._dispatcher.updateUserAddress(this._userAddressIfExists); - if (!_.isUndefined(injectedWeb3IfExists)) { + if (!_.isUndefined(injectedProviderIfExists)) { this._dispatcher.updateProviderType(ProviderType.Injected); } await this.fetchTokenInformationAsync(); @@ -888,10 +893,10 @@ export class Blockchain { this._dispatcher.updateNetworkId(networkId); await this._rehydrateStoreWithContractEventsAsync(); } - private _updateProviderName(injectedWeb3IfExists: InjectedWeb3): void { - const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3IfExists); - const providerName = doesInjectedWeb3Exist - ? Blockchain._getNameGivenProvider(injectedWeb3IfExists.currentProvider) + private _updateProviderName(injectedProviderIfExists?: InjectedProvider): void { + const doesInjectedProviderExist = !_.isUndefined(injectedProviderIfExists); + const providerName = doesInjectedProviderExist + ? Blockchain._getNameGivenProvider(injectedProviderIfExists) : constants.PROVIDER_NAME_PUBLIC; this._dispatcher.updateInjectedProviderName(providerName); } diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx index ec73e4199..3d0203573 100644 --- a/packages/website/ts/components/fill_order.tsx +++ b/packages/website/ts/components/fill_order.tsx @@ -203,7 +203,10 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { .tokenAddress; const makerToken = this.props.tokenByAddress[makerTokenAddress]; const makerAssetToken = { - amount: orderMakerAmount.times(takerAssetToken.amount).div(orderTakerAmount), + amount: orderMakerAmount + .times(takerAssetToken.amount) + .div(orderTakerAmount) + .floor(), symbol: makerToken.symbol, }; const fillAssetToken = { diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 22bfd2cb4..ce4b50a58 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -616,14 +616,6 @@ export interface InjectedProvider extends Provider { publicConfigStore?: InjectedProviderObservable; } -// Minimal expected interface for an injected web3 object -export interface InjectedWeb3 { - currentProvider: InjectedProvider; - version: { - getNetwork(cd: (err: Error, networkId: string) => void): void; - }; -} - export interface TutorialInfo { iconUrl: string; description: string; diff --git a/python-packages/order_utils/LICENSE b/python-packages/order_utils/LICENSE new file mode 100644 index 000000000..9096fefaa --- /dev/null +++ b/python-packages/order_utils/LICENSE @@ -0,0 +1,13 @@ +Copyright 2017 ZeroEx Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.
\ No newline at end of file diff --git a/python-packages/order_utils/README.md b/python-packages/order_utils/README.md new file mode 100644 index 000000000..4c5f7627c --- /dev/null +++ b/python-packages/order_utils/README.md @@ -0,0 +1,45 @@ +## 0x-order-utils + +0x order-related utilities for those developing on top of 0x protocol. + +Read the [documentation](https://0x.readthedocs.io/projects/order-utils/en/latest/) + +## Installing + +```bash +pip install 0x-order-utils +``` + +## Contributing + +We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Code and Dependencies + +Ensure that you have installed Python >=3.6 and Docker. Then: + +```bash +pip install -e .[dev] +``` + +### Test + +Tests depend on a running ganache instance with the 0x contracts deployed in it. For convenience, a docker container is provided that has ganache-cli and a snapshot containing the necessary contracts. A shortcut is provided to run that docker container: `./setup.py ganache`. With that running, the tests can be run with `./setup.py test`. + +### Clean + +`./setup.py clean --all` + +### Lint + +`./setup.py lint` + +### Build Documentation + +`./setup.py build_sphinx` + +### More + +See `./setup.py --help-commands` for more info. diff --git a/python-packages/order_utils/setup.py b/python-packages/order_utils/setup.py index 1a094cfe1..1b07b612c 100755 --- a/python-packages/order_utils/setup.py +++ b/python-packages/order_utils/setup.py @@ -4,12 +4,13 @@ import subprocess # nosec from shutil import rmtree -from os import environ, path, remove, walk +from os import environ, path +from pathlib import Path from sys import argv from distutils.command.clean import clean import distutils.command.build_py -from setuptools import setup +from setuptools import find_packages, setup from setuptools.command.test import test as TestCommand @@ -20,13 +21,15 @@ class TestCommandExtension(TestCommand): """Invoke pytest.""" import pytest - pytest.main() + exit(pytest.main()) # pylint: disable=too-many-ancestors class LintCommand(distutils.command.build_py.build_py): """Custom setuptools command class for running linters.""" + description = "Run linters" + def run(self): """Run linter shell commands.""" lint_commands = [ @@ -57,8 +60,15 @@ class LintCommand(distutils.command.build_py.build_py): import eth_abi eth_abi_dir = path.dirname(path.realpath(eth_abi.__file__)) - with open(path.join(eth_abi_dir, "py.typed"), "a"): - pass + Path(path.join(eth_abi_dir, "py.typed")).touch() + + # HACK(gene): until eth_utils fixes + # https://github.com/ethereum/eth-utils/issues/140 , we need to simply + # create an empty file `py.typed` in the eth_abi package directory. + import eth_utils + + eth_utils_dir = path.dirname(path.realpath(eth_utils.__file__)) + Path(path.join(eth_utils_dir, "py.typed")).touch() for lint_command in lint_commands: print( @@ -73,31 +83,84 @@ class CleanCommandExtension(clean): def run(self): """Run the regular clean, followed by our custom commands.""" super().run() - rmtree("build", ignore_errors=True) + rmtree("dist", ignore_errors=True) rmtree(".mypy_cache", ignore_errors=True) rmtree(".tox", ignore_errors=True) rmtree(".pytest_cache", ignore_errors=True) - rmtree("src/order_utils.egg-info", ignore_errors=True) - # delete all .pyc files - for root, _, files in walk("."): - for file in files: - (_, extension) = path.splitext(file) - if extension == ".pyc": - remove(path.join(root, file)) + rmtree("src/0x_order_utils.egg-info", ignore_errors=True) + + +# pylint: disable=too-many-ancestors +class TestPublishCommand(distutils.command.build_py.build_py): + """Custom command to publish to test.pypi.org.""" + + description = ( + "Publish dist/* to test.pypi.org. Run sdist & bdist_wheel first." + ) + + def run(self): + """Run twine to upload to test.pypi.org.""" + subprocess.check_call( # nosec + ( + "twine upload --repository-url https://test.pypi.org/legacy/" + + " --verbose dist/*" + ).split() + ) + + +# pylint: disable=too-many-ancestors +class PublishCommand(distutils.command.build_py.build_py): + """Custom command to publish to pypi.org.""" + + description = "Publish dist/* to pypi.org. Run sdist & bdist_wheel first." + + def run(self): + """Run twine to upload to pypi.org.""" + subprocess.check_call("twine upload dist/*".split()) # nosec + + +# pylint: disable=too-many-ancestors +class GanacheCommand(distutils.command.build_py.build_py): + """Custom command to publish to pypi.org.""" + + description = "Run ganache daemon to support tests." + + def run(self): + """Run ganache.""" + cmd_line = ( + "docker run -d -p 8545:8545 0xorg/ganache-cli --gasLimit" + + " 10000000 --db /snapshot --noVMErrorsOnRPCResponse -p 8545" + + " --networkId 50 -m" + ).split() + cmd_line.append( + "concert load couple harbor equip island argue ramp clarify fence" + + " smart topic" + ) + subprocess.call(cmd_line) # nosec + + +with open("README.md", "r") as file_handle: + README_MD = file_handle.read() setup( - name="order_utils", - version="1.0.0", + name="0x-order-utils", + version="0.1.0", description="Order utilities for 0x applications", + long_description=README_MD, + long_description_content_type="text/markdown", + url="https://github.com/0xproject/0x-monorepo/python-packages/order_utils", author="F. Eugene Aumson", + author_email="feuGeneA@users.noreply.github.com", cmdclass={ "clean": CleanCommandExtension, "lint": LintCommand, "test": TestCommandExtension, + "test_publish": TestPublishCommand, + "publish": PublishCommand, + "ganache": GanacheCommand, }, - include_package_data=True, - install_requires=["eth-abi", "web3"], + install_requires=["eth-abi", "eth_utils", "mypy_extensions", "web3"], extras_require={ "dev": [ "bandit", @@ -112,18 +175,23 @@ setup( "pytest", "sphinx", "tox", + "twine", ] }, python_requires=">=3.6, <4", - package_data={"zero_ex.order_utils": ["py.typed"]}, + package_data={ + "zero_ex.order_utils": ["py.typed"], + "zero_ex.contract_artifacts": ["artifacts/*"], + }, package_dir={"": "src"}, license="Apache 2.0", keywords=( "ethereum cryptocurrency 0x decentralized blockchain dex exchange" ), - packages=["zero_ex.order_utils"], + namespace_packages=["zero_ex"], + packages=find_packages("src"), classifiers=[ - "Development Status :: 1 - Planning", + "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Intended Audience :: Financial and Insurance Industry", "License :: OSI Approved :: Apache Software License", @@ -133,7 +201,10 @@ setup( "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Topic :: Internet :: WWW/HTTP", "Topic :: Office/Business :: Financial", + "Topic :: Other/Nonlisted Topic", + "Topic :: Security :: Cryptography", "Topic :: Software Development :: Libraries", "Topic :: Utilities", ], diff --git a/python-packages/order_utils/src/conf.py b/python-packages/order_utils/src/conf.py index e74a29d00..6b6776d01 100644 --- a/python-packages/order_utils/src/conf.py +++ b/python-packages/order_utils/src/conf.py @@ -3,16 +3,17 @@ # Reference: http://www.sphinx-doc.org/en/master/config from typing import List +import pkg_resources # pylint: disable=invalid-name # because these variables are not named in upper case, as globals should be. -project = "order_utils.py" +project = "0x-order-utils" # pylint: disable=redefined-builtin copyright = "2018, ZeroEx, Intl." author = "F. Eugene Aumson" -version = "" # The short X.Y version +version = pkg_resources.get_distribution("0x-order-utils").version release = "" # The full version, including alpha/beta/rc tags extensions = [ diff --git a/python-packages/order_utils/src/zero_ex/py.typed b/python-packages/order_utils/src/doc_static/.gitkeep index e69de29bb..e69de29bb 100644 --- a/python-packages/order_utils/src/zero_ex/py.typed +++ b/python-packages/order_utils/src/doc_static/.gitkeep diff --git a/python-packages/order_utils/src/doc_templates/.gitkeep b/python-packages/order_utils/src/doc_templates/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/order_utils/src/doc_templates/.gitkeep diff --git a/python-packages/order_utils/src/index.rst b/python-packages/order_utils/src/index.rst index e09abcca2..b99addabd 100644 --- a/python-packages/order_utils/src/index.rst +++ b/python-packages/order_utils/src/index.rst @@ -1,7 +1,7 @@ .. source for the sphinx-generated build/docs/web/index.html -order_utils.py -============== +Python zero_ex.order_utils +========================== .. toctree:: :maxdepth: 2 @@ -15,7 +15,12 @@ order_utils.py .. autoclass:: zero_ex.order_utils.asset_data_utils.ERC20AssetData -See source for properties. Sphinx does not easily generate class property docs; pull requests welcome. +.. autoclass:: zero_ex.order_utils.asset_data_utils.ERC721AssetData + +See source for class properties. Sphinx does not easily generate class property docs; pull requests welcome. + +.. automodule:: zero_ex.order_utils.signature_utils + :members: Indices and tables ================== diff --git a/python-packages/order_utils/src/zero_ex/__init__.py b/python-packages/order_utils/src/zero_ex/__init__.py index c3ed1562a..e90d833db 100644 --- a/python-packages/order_utils/src/zero_ex/__init__.py +++ b/python-packages/order_utils/src/zero_ex/__init__.py @@ -1 +1,2 @@ """0x Python API.""" +__import__("pkg_resources").declare_namespace(__name__) diff --git a/python-packages/order_utils/src/zero_ex/contract_artifacts/__init__.py b/python-packages/order_utils/src/zero_ex/contract_artifacts/__init__.py new file mode 100644 index 000000000..ed45d2c8e --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/contract_artifacts/__init__.py @@ -0,0 +1 @@ +"""Solc-generated artifacts for 0x smart contracts.""" diff --git a/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts b/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts new file mode 120000 index 000000000..82d28ba87 --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts @@ -0,0 +1 @@ +../../../../../packages/contract-artifacts/artifacts
\ No newline at end of file diff --git a/python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py b/python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py index 745d014e6..08c1b0ea5 100644 --- a/python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py +++ b/python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py @@ -31,3 +31,28 @@ def assert_is_list(value: Any, name: str) -> None: f"expected variable '{name}', with value {str(value)}, to have" + f" type 'list', not '{type(value).__name__}'" ) + + +def assert_is_int(value: Any, name: str) -> None: + """If :param value: isn't of type int, raise a TypeError. + + >>> try: assert_is_int('asdf', 'var') + ... except TypeError as type_error: print(str(type_error)) + ... + expected variable 'var', with value asdf, to have type 'int', not 'str' + """ + if not isinstance(value, int): + raise TypeError( + f"expected variable '{name}', with value {str(value)}, to have" + + f" type 'int', not '{type(value).__name__}'" + ) + + +def assert_is_hex_string(value: Any, name: str) -> None: + """Assert that :param value: is a string of hex chars. + + If :param value: isn't a str, raise a TypeError. If it is a string but + contains non-hex characters ("0x" prefix permitted), raise a ValueError. + """ + assert_is_string(value, name) + int(value, 16) # raises a ValueError if value isn't a base-16 str diff --git a/python-packages/order_utils/src/zero_ex/order_utils/__init__.py b/python-packages/order_utils/src/zero_ex/order_utils/__init__.py index f014af0f6..80445cb6e 100644 --- a/python-packages/order_utils/src/zero_ex/order_utils/__init__.py +++ b/python-packages/order_utils/src/zero_ex/order_utils/__init__.py @@ -1 +1,11 @@ -"""Order utilities for 0x applications.""" +"""Order utilities for 0x applications. + +Some methods require the caller to pass in a `Web3.HTTPProvider` object. For +local testing one may construct such a provider pointing at an instance of +`ganache-cli <https://www.npmjs.com/package/ganache-cli>`_ which has the 0x +contracts deployed on it. For convenience, a docker container is provided for +just this purpose. To start it: ``docker run -d -p 8545:8545 0xorg/ganache-cli +--gasLimit 10000000 --db /snapshot --noVMErrorsOnRPCResponse -p 8545 +--networkId 50 -m "concert load couple harbor equip island argue ramp clarify +fence smart topic"``. +""" diff --git a/python-packages/order_utils/src/zero_ex/order_utils/asset_data_utils.py b/python-packages/order_utils/src/zero_ex/order_utils/asset_data_utils.py index 451de39af..e6f9a07c1 100644 --- a/python-packages/order_utils/src/zero_ex/order_utils/asset_data_utils.py +++ b/python-packages/order_utils/src/zero_ex/order_utils/asset_data_utils.py @@ -5,10 +5,11 @@ from mypy_extensions import TypedDict import eth_abi from zero_ex.dev_utils import abi_utils -from zero_ex.dev_utils.type_assertions import assert_is_string +from zero_ex.dev_utils.type_assertions import assert_is_string, assert_is_int ERC20_ASSET_DATA_BYTE_LENGTH = 36 +ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH = 53 SELECTOR_LENGTH = 10 @@ -19,6 +20,14 @@ class ERC20AssetData(TypedDict): token_address: str +class ERC721AssetData(TypedDict): + """Object interface to ERC721 asset data.""" + + asset_proxy_id: str + token_address: str + token_id: int + + def encode_erc20_asset_data(token_address: str) -> str: """Encode an ERC20 token address into an asset data string. @@ -39,7 +48,7 @@ def encode_erc20_asset_data(token_address: str) -> str: def decode_erc20_asset_data(asset_data: str) -> ERC20AssetData: # docstring considered all one line by pylint: disable=line-too-long - """Decode an ERC20 assetData hex string. + """Decode an ERC20 asset data hex string. :param asset_data: String produced by prior call to encode_erc20_asset_data() @@ -55,7 +64,7 @@ def decode_erc20_asset_data(asset_data: str) -> ERC20AssetData: + f" Got {str(len(asset_data))}." ) - asset_proxy_id: str = asset_data[0:10] + asset_proxy_id: str = asset_data[0:SELECTOR_LENGTH] if asset_proxy_id != abi_utils.method_id("ERC20Token", ["address"]): raise ValueError( "Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be" @@ -70,3 +79,65 @@ def decode_erc20_asset_data(asset_data: str) -> ERC20AssetData: )[0] return {"asset_proxy_id": asset_proxy_id, "token_address": token_address} + + +def encode_erc721_asset_data(token_address: str, token_id: int) -> str: + # docstring considered all one line by pylint: disable=line-too-long + """Encode an ERC721 asset data hex string. + + :param token_address: the ERC721 token's contract address. + :param token_id: the identifier of the asset's instance of the token. + :rtype: hex encoded asset data string, usable in the makerAssetData or + takerAssetData fields in a 0x order. + + >>> encode_erc721_asset_data('0x1dc4c1cefef38a777b15aa20260a54e584b16c48', 1) + '0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001' + """ # noqa: E501 (line too long) + assert_is_string(token_address, "token_address") + assert_is_int(token_id, "token_id") + + return ( + "0x" + + abi_utils.simple_encode( + "ERC721Token(address,uint256)", token_address, token_id + ).hex() + ) + + +def decode_erc721_asset_data(asset_data: str) -> ERC721AssetData: + # docstring considered all one line by pylint: disable=line-too-long + """Decode an ERC721 asset data hex string. + + >>> decode_erc721_asset_data('0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001') + {'asset_proxy_id': '0x02571792', 'token_address': '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', 'token_id': 1} + """ # noqa: E501 (line too long) + assert_is_string(asset_data, "asset_data") + + if len(asset_data) < ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH: + raise ValueError( + "Could not decode ERC721 Asset Data. Expected length of encoded" + + f"data to be at least {ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH}. " + + f"Got {len(asset_data)}." + ) + + asset_proxy_id: str = asset_data[0:SELECTOR_LENGTH] + # prefer `black` formatting. pylint: disable=C0330 + if asset_proxy_id != abi_utils.method_id( + "ERC721Token", ["address", "uint256"] + ): + raise ValueError( + "Could not decode ERC721 Asset Data. Expected Asset Proxy Id to be" + + f" ERC721 (" + + f"{abi_utils.method_id('ERC721Token', ['address', 'uint256'])}" + + f"), but got {asset_proxy_id}" + ) + + (token_address, token_id) = eth_abi.decode_abi( + ["address", "uint256"], bytes.fromhex(asset_data[SELECTOR_LENGTH:]) + ) + + return { + "asset_proxy_id": asset_proxy_id, + "token_address": token_address, + "token_id": token_id, + } diff --git a/python-packages/order_utils/src/zero_ex/order_utils/py.typed b/python-packages/order_utils/src/zero_ex/order_utils/py.typed new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/order_utils/py.typed diff --git a/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py b/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py new file mode 100644 index 000000000..12525ba88 --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py @@ -0,0 +1,88 @@ +"""Signature utilities.""" + +from typing import Dict, Tuple +import json +from pkg_resources import resource_string + +from eth_utils import is_address, to_checksum_address +from web3 import Web3 +import web3.exceptions +from web3.utils import datatypes + +from zero_ex.dev_utils.type_assertions import assert_is_hex_string + + +# prefer `black` formatting. pylint: disable=C0330 +EXCHANGE_ABI = json.loads( + resource_string("zero_ex.contract_artifacts", "artifacts/Exchange.json") +)["compilerOutput"]["abi"] + +network_to_exchange_addr: Dict[str, str] = { + "1": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b", + "3": "0x4530c0483a1633c7a1c97d2c53721caff2caaaaf", + "42": "0x35dd2932454449b14cee11a94d3674a936d5d7b2", + "50": "0x48bacb9266a570d521063ef5dd96e61686dbe788", +} + + +# prefer `black` formatting. pylint: disable=C0330 +def is_valid_signature( + provider: Web3.HTTPProvider, data: str, signature: str, signer_address: str +) -> Tuple[bool, str]: + # docstring considered all one line by pylint: disable=line-too-long + """Check the validity of the supplied signature. + + Check if the supplied ``signature`` corresponds to signing ``data`` with + the private key corresponding to ``signer_address``. + + :param provider: A Web3 provider able to access the 0x Exchange contract. + :param data: The hex encoded data signed by the supplied signature. + :param signature: The hex encoded signature. + :param signer_address: The hex encoded address that signed the data to + produce the supplied signature. + :rtype: Boolean indicating whether the given signature is valid. + + >>> is_valid_signature( + ... Web3.HTTPProvider("http://127.0.0.1:8545"), + ... '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0', + ... '0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403', + ... '0x5409ed021d9299bf6814279a6a1411a7e866a631', + ... ) + (True, '') + """ # noqa: E501 (line too long) + # TODO: make this provider check more flexible. pylint: disable=fixme + # https://app.asana.com/0/684263176955174/901300863045491/f + if not isinstance(provider, Web3.HTTPProvider): + raise TypeError("provider is not a Web3.HTTPProvider") + assert_is_hex_string(data, "data") + assert_is_hex_string(signature, "signature") + assert_is_hex_string(signer_address, "signer_address") + if not is_address(signer_address): + raise ValueError("signer_address is not a valid address") + + web3_instance = Web3(provider) + # false positive from pylint: disable=no-member + network_id = web3_instance.net.version + contract_address = network_to_exchange_addr[network_id] + # false positive from pylint: disable=no-member + contract: datatypes.Contract = web3_instance.eth.contract( + address=to_checksum_address(contract_address), abi=EXCHANGE_ABI + ) + try: + return ( + contract.call().isValidSignature( + data, to_checksum_address(signer_address), signature + ), + "", + ) + except web3.exceptions.BadFunctionCallOutput as exception: + known_revert_reasons = [ + "LENGTH_GREATER_THAN_0_REQUIRED", + "SIGNATURE_UNSUPPORTED", + "LENGTH_0_REQUIRED", + "LENGTH_65_REQUIRED", + ] + for known_revert_reason in known_revert_reasons: + if known_revert_reason in str(exception): + return (False, known_revert_reason) + return (False, f"Unknown: {exception}") diff --git a/python-packages/order_utils/stubs/setuptools/__init__.pyi b/python-packages/order_utils/stubs/setuptools/__init__.pyi index baa349d70..8ea8d32b7 100644 --- a/python-packages/order_utils/stubs/setuptools/__init__.pyi +++ b/python-packages/order_utils/stubs/setuptools/__init__.pyi @@ -1,6 +1,8 @@ from distutils.dist import Distribution -from typing import Any +from typing import Any, List def setup(**attrs: Any) -> Distribution: ... class Command: ... + +def find_packages(where: str) -> List[str]: ... diff --git a/python-packages/order_utils/stubs/web3/__init__.pyi b/python-packages/order_utils/stubs/web3/__init__.pyi index c6f357009..fcecc7434 100644 --- a/python-packages/order_utils/stubs/web3/__init__.pyi +++ b/python-packages/order_utils/stubs/web3/__init__.pyi @@ -1,10 +1,26 @@ -from typing import Optional, Union +from typing import Dict, Optional, Union + +from web3.utils import datatypes + class Web3: + class HTTPProvider: ... + + def __init__(self, provider: HTTPProvider) -> None: ... + @staticmethod def sha3( primitive: Optional[Union[bytes, int, None]] = None, text: Optional[str] = None, hexstr: Optional[str] = None ) -> bytes: ... + + class net: + version: str + ... + + class eth: + @staticmethod + def contract(address: str, abi: Dict) -> datatypes.Contract: ... + ... ... diff --git a/python-packages/order_utils/stubs/web3/exceptions.pyi b/python-packages/order_utils/stubs/web3/exceptions.pyi new file mode 100644 index 000000000..83abf973d --- /dev/null +++ b/python-packages/order_utils/stubs/web3/exceptions.pyi @@ -0,0 +1,2 @@ +class BadFunctionCallOutput(Exception): + ... diff --git a/python-packages/order_utils/stubs/web3/utils/__init__.pyi b/python-packages/order_utils/stubs/web3/utils/__init__.pyi new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/order_utils/stubs/web3/utils/__init__.pyi diff --git a/python-packages/order_utils/stubs/web3/utils/datatypes.pyi b/python-packages/order_utils/stubs/web3/utils/datatypes.pyi new file mode 100644 index 000000000..70baff372 --- /dev/null +++ b/python-packages/order_utils/stubs/web3/utils/datatypes.pyi @@ -0,0 +1,3 @@ +class Contract: + def call(self): ... + ... diff --git a/python-packages/order_utils/test/test_asset_data_utils.py b/python-packages/order_utils/test/test_asset_data_utils.py index eeada5873..079368714 100644 --- a/python-packages/order_utils/test/test_asset_data_utils.py +++ b/python-packages/order_utils/test/test_asset_data_utils.py @@ -3,9 +3,12 @@ import pytest from zero_ex.order_utils.asset_data_utils import ( - encode_erc20_asset_data, decode_erc20_asset_data, + decode_erc721_asset_data, + encode_erc20_asset_data, + encode_erc721_asset_data, ERC20_ASSET_DATA_BYTE_LENGTH, + ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH, ) @@ -33,3 +36,37 @@ def test_decode_erc20_asset_data_invalid_proxy_id(): decode_erc20_asset_data( "0xffffffff" + (" " * ERC20_ASSET_DATA_BYTE_LENGTH) ) + + +def test_encode_erc721_asset_data_type_error_on_token_address(): + """Test that passing a non-string for token_address raises a TypeError.""" + with pytest.raises(TypeError): + encode_erc721_asset_data(123, 123) + + +def test_encode_erc721_asset_data_type_error_on_token_id(): + """Test that passing a non-int for token_id raises a TypeError.""" + with pytest.raises(TypeError): + encode_erc721_asset_data("asdf", "asdf") + + +def test_decode_erc721_asset_data_type_error(): + """Test that passing a non-string for asset_data raises a TypeError.""" + with pytest.raises(TypeError): + decode_erc721_asset_data(123) + + +def test_decode_erc721_asset_data_with_asset_data_too_short(): + """Test that passing in too short of a string raises a ValueError.""" + with pytest.raises(ValueError): + decode_erc721_asset_data( + " " * (ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH - 1) + ) + + +def test_decode_erc721_asset_data_invalid_proxy_id(): + """Test that passing in too short of a string raises a ValueError.""" + with pytest.raises(ValueError): + decode_erc721_asset_data( + "0xffffffff" + " " * (ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH - 1) + ) diff --git a/python-packages/order_utils/test/test_doctest.py b/python-packages/order_utils/test/test_doctest.py index ba5da5418..2b0350ac0 100644 --- a/python-packages/order_utils/test/test_doctest.py +++ b/python-packages/order_utils/test/test_doctest.py @@ -1,24 +1,18 @@ -"""Exercise doctests for order_utils module.""" +"""Exercise doctests for all of our modules.""" from doctest import testmod +import pkgutil -from zero_ex.dev_utils import abi_utils, type_assertions -from zero_ex.order_utils import asset_data_utils +import zero_ex -def test_doctest_asset_data_utils(): - """Invoke doctest on the asset_data_utils module.""" - (failure_count, _) = testmod(asset_data_utils) - assert failure_count == 0 - - -def test_doctest_abi_utils(): - """Invoke doctest on the abi_utils module.""" - (failure_count, _) = testmod(abi_utils) - assert failure_count == 0 - - -def test_doctest_type_assertions(): - """Invoke doctest on the type_assertions module.""" - (failure_count, _) = testmod(type_assertions) - assert failure_count == 0 +def test_all_doctests(): + """Gather zero_ex.* modules and doctest them.""" + # prefer `black` formatting. pylint: disable=bad-continuation + for (importer, modname, _) in pkgutil.walk_packages( + path=zero_ex.__path__, prefix="zero_ex." + ): + module = importer.find_module(modname).load_module(modname) + print(module) + (failure_count, _) = testmod(module) + assert failure_count == 0 diff --git a/python-packages/order_utils/test/test_signature_utils.py b/python-packages/order_utils/test/test_signature_utils.py new file mode 100644 index 000000000..b688e03a1 --- /dev/null +++ b/python-packages/order_utils/test/test_signature_utils.py @@ -0,0 +1,128 @@ +"""Tests of zero_ex.order_utils.signature_utils.""" + +import pytest +from web3 import Web3 + +from zero_ex.order_utils.signature_utils import is_valid_signature + + +def test_is_valid_signature__provider_wrong_type(): + """Test that giving a non-HTTPProvider raises a TypeError.""" + with pytest.raises(TypeError): + is_valid_signature( + 123, + "0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b" + + "0", + "0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b" + + "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace" + + "225403", + "0x5409ed021d9299bf6814279a6a1411a7e866a631", + ) + + +def test_is_valid_signature__data_not_string(): + """Test that giving non-string `data` raises a TypeError.""" + with pytest.raises(TypeError): + is_valid_signature( + Web3.HTTPProvider("http://127.0.0.1:8545"), + 123, + "0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b" + + "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace" + + "225403", + "0x5409ed021d9299bf6814279a6a1411a7e866a631", + ) + + +def test_is_valid_signature__data_not_hex_string(): + """Test that giving non-hex-string `data` raises a ValueError.""" + with pytest.raises(ValueError): + is_valid_signature( + Web3.HTTPProvider("http://127.0.0.1:8545"), + "jjj", + "0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b" + + "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace" + + "225403", + "0x5409ed021d9299bf6814279a6a1411a7e866a631", + ) + + +def test_is_valid_signature__signature_not_string(): + """Test that passng a non-string signature raises a TypeError.""" + with pytest.raises(TypeError): + is_valid_signature( + Web3.HTTPProvider("http://127.0.0.1:8545"), + "0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b" + + "0", + 123, + "0x5409ed021d9299bf6814279a6a1411a7e866a631", + ) + + +def test_is_valid_signature__signature_not_hex_string(): + """Test that passing a non-hex-string signature raises a ValueError.""" + with pytest.raises(ValueError): + is_valid_signature( + Web3.HTTPProvider("http://127.0.0.1:8545"), + "0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b" + + "0", + "jjj", + "0x5409ed021d9299bf6814279a6a1411a7e866a631", + ) + + +def test_is_valid_signature__signer_address_not_string(): + """Test that giving a non-address `signer_address` raises a ValueError.""" + with pytest.raises(TypeError): + is_valid_signature( + Web3.HTTPProvider("http://127.0.0.1:8545"), + "0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b" + + "0", + "0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b" + + "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace" + + "225403", + 123, + ) + + +def test_is_valid_signature__signer_address_not_hex_string(): + """Test that giving a non-hex-str `signer_address` raises a ValueError.""" + with pytest.raises(ValueError): + is_valid_signature( + Web3.HTTPProvider("http://127.0.0.1:8545"), + "0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b" + + "0", + "0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b" + + "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace" + + "225403", + "jjj", + ) + + +def test_is_valid_signature__signer_address_not_valid_address(): + """Test that giving a non-address for `signer_address` raises an error.""" + with pytest.raises(ValueError): + is_valid_signature( + Web3.HTTPProvider("http://127.0.0.1:8545"), + "0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b" + + "0", + "0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351b" + + "c3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace" + + "225403", + "0xff", + ) + + +def test_is_valid_signature__unsupported_sig_types(): + """Test that passing in a sig w/invalid type raises error. + + To induce this error, the last byte of the signature is tweaked from 03 to + ff.""" + (is_valid, reason) = is_valid_signature( + Web3.HTTPProvider("http://127.0.0.1:8545"), + "0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0", + "0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc334" + + "0349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254ff", + "0x5409ed021d9299bf6814279a6a1411a7e866a631", + ) + assert is_valid is False + assert reason == "SIGNATURE_UNSUPPORTED" diff --git a/python-packages/order_utils/tox.ini b/python-packages/order_utils/tox.ini index 1cce32b5f..ba7d55b56 100644 --- a/python-packages/order_utils/tox.ini +++ b/python-packages/order_utils/tox.ini @@ -10,3 +10,16 @@ envlist = py37 commands = pip install -e .[dev] python setup.py test + +[testenv:run_tests_against_test_deployment] +commands = + # install dependencies from real PyPI + pip install eth-abi mypy_extensions web3 pytest + # install package-under-test from test PyPI + pip install --index-url https://test.pypi.org/legacy/ 0x-order-utils + pytest test + +[testenv:run_tests_against_deployment] +commands = + pip install 0x-order-utils + pytest test diff --git a/readthedocs.yaml b/readthedocs.yaml new file mode 100644 index 000000000..7023141da --- /dev/null +++ b/readthedocs.yaml @@ -0,0 +1,7 @@ +build: + image: latest + +python: + version: 3.6 + +requirements_file: requirements.readthedocs.txt diff --git a/requirements.readthedocs.txt b/requirements.readthedocs.txt new file mode 100644 index 000000000..4228f6562 --- /dev/null +++ b/requirements.readthedocs.txt @@ -0,0 +1 @@ +./python-packages/order_utils @@ -484,6 +484,12 @@ dependencies: "@babel/highlight" "^7.0.0" +"@babel/helper-annotate-as-pure@^7.0.0": + version "7.0.0" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" + dependencies: + "@babel/types" "^7.0.0" + "@babel/highlight@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" @@ -504,6 +510,24 @@ dependencies: regenerator-runtime "^0.12.0" +"@babel/types@^7.0.0": + version "7.1.3" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.1.3.tgz#3a767004567060c2f40fca49a304712c525ee37d" + dependencies: + esutils "^2.0.2" + lodash "^4.17.10" + to-fast-properties "^2.0.0" + +"@emotion/is-prop-valid@^0.6.8": + version "0.6.8" + resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.6.8.tgz#68ad02831da41213a2089d2cab4e8ac8b30cbd85" + dependencies: + "@emotion/memoize" "^0.6.6" + +"@emotion/memoize@^0.6.6": + version "0.6.6" + resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz#004b98298d04c7ca3b4f50ca2035d4f60d2eed1b" + "@ledgerhq/hw-app-eth@^4.3.0": version "4.7.3" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-4.7.3.tgz#d352e19658ae296532e522c53c8ec2a1a77b64e5" @@ -517,13 +541,19 @@ "@ledgerhq/hw-transport" "^4.7.3" node-hid "^0.7.2" -"@ledgerhq/hw-transport-u2f@^4.3.0": - version "4.7.3" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-4.7.3.tgz#32be84bd2829f0ad0745604355f73a169dceb5e5" +"@ledgerhq/hw-transport-u2f@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-4.24.0.tgz#d67cfc4abf6d9a900ed45f2e2df7fe06dfdff5c7" dependencies: - "@ledgerhq/hw-transport" "^4.7.3" + "@ledgerhq/hw-transport" "^4.24.0" u2f-api "0.2.7" +"@ledgerhq/hw-transport@^4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-4.24.0.tgz#8def925d8c2e1f73d15128d9e27ead729870be58" + dependencies: + events "^3.0.0" + "@ledgerhq/hw-transport@^4.7.3": version "4.7.3" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-4.7.3.tgz#e89cd90d30aaf008adf9fdfa4af317a48fe798ca" @@ -1124,15 +1154,15 @@ dependencies: samsam "1.3.0" -"@static/discharge@^1.2.2": +"@static/discharge@https://github.com/0xProject/discharge.git": version "1.2.2" - resolved "https://registry.npmjs.org/@static/discharge/-/discharge-1.2.2.tgz#dc941e0c02c421b338b83ac2e59e140d7214c971" + resolved "https://github.com/0xProject/discharge.git#3aed990822cabbb79b71b52700fdef08cd9eb400" dependencies: - aws-sdk "^2.304.0" + aws-sdk "^2.347.0" execa "^1.0.0" glob "^7.1.3" inquirer "^6.2.0" - listr "^0.14.1" + listr "^0.14.2" lodash.differenceby "^4.8.0" lodash.intersectionby "^4.7.0" lodash.intersectionwith "^4.4.0" @@ -1579,6 +1609,13 @@ "@types/node" "*" "@types/react" "*" +"@types/styled-components@^4.0.1": + version "4.0.1" + resolved "https://registry.npmjs.org/@types/styled-components/-/styled-components-4.0.1.tgz#5eb9a5474dbde3becab2bcc8f04e0b8e8dcf8c06" + dependencies: + "@types/node" "*" + "@types/react" "*" + "@types/tmp@^0.0.33": version "0.0.33" resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.0.33.tgz#1073c4bc824754ae3d10cfab88ab0237ba964e4d" @@ -1869,6 +1906,13 @@ aes-js@^0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-0.2.4.tgz#94b881ab717286d015fa219e08fb66709dda5a3d" +<<<<<<< HEAD +======= +aes-js@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.1.tgz#89fd1f94ae51b4c72d62466adc1a7323ff52f072" + +>>>>>>> development agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -2293,9 +2337,9 @@ aws-sdk@^2.127.0: uuid "3.1.0" xml2js "0.4.19" -aws-sdk@^2.304.0: - version "2.338.0" - resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.338.0.tgz#f1b1347f78defa27b92b030b5787fad0f89ac83b" +aws-sdk@^2.347.0: + version "2.348.0" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.348.0.tgz#69c5b6dd77a5eac85b54bf7cc1640c19d762d3ee" dependencies: buffer "4.9.1" events "1.1.1" @@ -2532,6 +2576,13 @@ babel-plugin-jest-hoist@^23.2.0: version "23.2.0" resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167" +"babel-plugin-styled-components@>= 1": + version "1.8.0" + resolved "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.8.0.tgz#9dd054c8e86825203449a852a5746f29f2dab857" + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + lodash "^4.17.10" + babel-plugin-syntax-async-functions@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" @@ -3322,6 +3373,17 @@ bs58check@^1.0.8: bs58 "^3.1.0" create-hash "^1.1.0" +<<<<<<< HEAD +======= +bs58check@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" + dependencies: + bs58 "^4.0.0" + create-hash "^1.1.0" + safe-buffer "^5.1.2" + +>>>>>>> development bser@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" @@ -4351,9 +4413,9 @@ copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" -copy-to-clipboard@^3: +copy-to-clipboard@^3, copy-to-clipboard@^3.0.8: version "3.0.8" - resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9" + resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9" dependencies: toggle-selection "^1.0.3" @@ -4581,6 +4643,14 @@ css-to-react-native@^2.0.3: fbjs "^0.8.5" postcss-value-parser "^3.3.0" +css-to-react-native@^2.2.2: + version "2.2.2" + resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.2.2.tgz#c077d0f7bf3e6c915a539e7325821c9dd01f9965" + dependencies: + css-color-keywords "^1.0.0" + fbjs "^0.8.5" + postcss-value-parser "^3.3.0" + css-vendor@^0.3.8: version "0.3.8" resolved "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz#6421cfd3034ce664fe7673972fd0119fc28941fa" @@ -5901,6 +5971,22 @@ ethereumjs-wallet@0.6.0: utf8 "^2.1.1" uuid "^2.0.1" +<<<<<<< HEAD +======= +ethereumjs-wallet@~0.6.0: + version "0.6.2" + resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.2.tgz#67244b6af3e8113b53d709124b25477b64aeccda" + dependencies: + aes-js "^3.1.1" + bs58check "^2.1.2" + ethereumjs-util "^5.2.0" + hdkey "^1.0.0" + safe-buffer "^5.1.2" + scrypt.js "^0.2.0" + utf8 "^3.0.0" + uuid "^3.3.2" + +>>>>>>> development ethers@~4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.4.tgz#d3f85e8b27f4b59537e06526439b0fb15b44dc65" @@ -5978,6 +6064,10 @@ events@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/events/-/events-2.0.0.tgz#cbbb56bf3ab1ac18d71c43bb32c86255062769f2" +events@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" + eventsource@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232" @@ -6705,7 +6795,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep: ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default" ethereumjs-util "^5.2.0" ethereumjs-vm "2.3.5" - ethereumjs-wallet "0.6.0" + ethereumjs-wallet "~0.6.0" fake-merkle-patricia-tree "~1.0.1" heap "~0.2.6" js-scrypt "^0.2.0" @@ -7430,6 +7520,17 @@ hdkey@^0.7.0, hdkey@^0.7.1: coinstring "^2.0.0" secp256k1 "^3.0.1" +<<<<<<< HEAD +======= +hdkey@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-1.1.0.tgz#e74e7b01d2c47f797fa65d1d839adb7a44639f29" + dependencies: + coinstring "^2.0.0" + safe-buffer "^5.1.1" + secp256k1 "^3.0.1" + +>>>>>>> development he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -9403,7 +9504,7 @@ listr@^0.12.0: stream-to-observable "^0.1.0" strip-ansi "^3.0.1" -listr@^0.14.1: +listr@^0.14.2: version "0.14.2" resolved "https://registry.npmjs.org/listr/-/listr-0.14.2.tgz#cbe44b021100a15376addfc2d79349ee430bfe14" dependencies: @@ -14394,19 +14495,18 @@ styled-components@^3.3.3: stylis-rule-sheet "^0.0.10" supports-color "^3.2.3" -styled-components@^3.4.9: - version "3.4.10" - resolved "https://registry.npmjs.org/styled-components/-/styled-components-3.4.10.tgz#9a654c50ea2b516c36ade57ddcfa296bf85c96e1" +styled-components@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/styled-components/-/styled-components-4.0.2.tgz#7d4409ada019cdd34c25ba68c4577ea95dbcf0c5" dependencies: - buffer "^5.0.3" - css-to-react-native "^2.0.3" - fbjs "^0.8.16" - hoist-non-react-statics "^2.5.0" + "@emotion/is-prop-valid" "^0.6.8" + babel-plugin-styled-components ">= 1" + css-to-react-native "^2.2.2" + memoize-one "^4.0.0" prop-types "^15.5.4" react-is "^16.3.1" stylis "^3.5.0" stylis-rule-sheet "^0.0.10" - supports-color "^3.2.3" stylis-rule-sheet@^0.0.10: version "0.0.10" @@ -14849,6 +14949,10 @@ to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + to-no-case@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a" @@ -15513,6 +15617,13 @@ utf8@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96" +<<<<<<< HEAD +======= +utf8@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + +>>>>>>> development util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" |