diff options
author | Fabio Berger <me@fabioberger.com> | 2018-12-19 00:59:15 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-12-19 00:59:15 +0800 |
commit | 622b9f662e74d571da745047ede097c7a392d09e (patch) | |
tree | 68a9517882b04a3d428f6996e0790ceb82e89be4 /packages | |
parent | e295eeb8938468b1527d5d81f212766cef40bc81 (diff) | |
parent | 67df5a433d68a2af1a3a03a8bf431629a534dc97 (diff) | |
download | dexon-sol-tools-622b9f662e74d571da745047ede097c7a392d09e.tar.gz dexon-sol-tools-622b9f662e74d571da745047ede097c7a392d09e.tar.zst dexon-sol-tools-622b9f662e74d571da745047ede097c7a392d09e.zip |
Merge branch 'development' into features/orderwatcher_ws
* development: (107 commits)
Fix OrderWatcher title to fix sidebar top
Fix version picker so it doesn't overflow onto two lines
Fix bug in pull_missing_blocks with incorrect start block (#1438)
Pull approval events for ZRX and DAI (#1430)
fix semicolon and apply prettier
Fix dex order quote/base asset assigning (#1432)
Apply prettier
Publish
Updated CHANGELOGS
Rename contracts CHANGELOGs to DEPLOYs
Move Forwarder CHANGELOG entries to extensions CHANGELOG
Make contracts packages not private
Publish
Updated CHANGELOGS
Show @ price in light grey
Updated CHANGELOGS
typeof -> isString
add special case to scrape OHLCV for eth/usd (#1428)
run linter
simplify scaling input logic
...
Diffstat (limited to 'packages')
176 files changed, 2943 insertions, 805 deletions
diff --git a/packages/0x.js/CHANGELOG.json b/packages/0x.js/CHANGELOG.json index 4ee1e92be..32351ad82 100644 --- a/packages/0x.js/CHANGELOG.json +++ b/packages/0x.js/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "2.0.8", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "2.0.7", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "2.0.6", "changes": [ diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md index 463ff923d..2923fdf03 100644 --- a/packages/0x.js/CHANGELOG.md +++ b/packages/0x.js/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.8 - _December 13, 2018_ + + * Dependencies updated + +## v2.0.7 - _December 11, 2018_ + + * Dependencies updated + ## v2.0.6 - _November 28, 2018_ * Dependencies updated diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index aa038c302..2960c9e4b 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -1,6 +1,6 @@ { "name": "0x.js", - "version": "2.0.6", + "version": "2.0.8", "engines": { "node": ">=6.12" }, @@ -42,11 +42,11 @@ }, "license": "Apache-2.0", "devDependencies": { - "@0x/abi-gen-wrappers": "^2.0.0", + "@0x/abi-gen-wrappers": "^2.0.2", "@0x/contract-addresses": "^2.0.0", - "@0x/dev-utils": "^1.0.19", - "@0x/migrations": "^2.2.0", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/migrations": "^2.2.2", + "@0x/tslint-config": "^2.0.0", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "@types/node": "*", @@ -72,18 +72,18 @@ "webpack": "^4.20.2" }, "dependencies": { - "@0x/assert": "^1.0.18", - "@0x/base-contract": "^3.0.8", - "@0x/contract-wrappers": "^4.1.1", - "@0x/order-utils": "^3.0.4", - "@0x/order-watcher": "^2.2.6", - "@0x/subproviders": "^2.1.6", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "@0x/assert": "^1.0.20", + "@0x/base-contract": "^3.0.10", + "@0x/contract-wrappers": "^4.1.3", + "@0x/order-utils": "^3.0.7", + "@0x/order-watcher": "^2.2.8", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/web3-provider-engine": "^14.0.0", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "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 6905a7537..d46e828f4 100644 --- a/packages/abi-gen-wrappers/CHANGELOG.json +++ b/packages/abi-gen-wrappers/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "2.0.2", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "2.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "version": "2.0.0", "changes": [ { diff --git a/packages/abi-gen-wrappers/CHANGELOG.md b/packages/abi-gen-wrappers/CHANGELOG.md index 30a10d6bd..c13c7a60f 100644 --- a/packages/abi-gen-wrappers/CHANGELOG.md +++ b/packages/abi-gen-wrappers/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.2 - _December 13, 2018_ + + * Dependencies updated + +## v2.0.1 - _December 11, 2018_ + + * Dependencies updated + ## v2.0.0 - _November 28, 2018_ * Update Exchange artifact to receive ZRX asset data as a constructor argument (#1309) diff --git a/packages/abi-gen-wrappers/package.json b/packages/abi-gen-wrappers/package.json index e4f103cf7..25ba3a6e0 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": "2.0.0", + "version": "2.0.2", "engines": { "node": ">=6.12" }, @@ -30,19 +30,19 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/abi-gen-wrappers/README.md", "devDependencies": { - "@0x/abi-gen": "^1.0.17", + "@0x/abi-gen": "^1.0.19", "@0x/abi-gen-templates": "^1.0.1", - "@0x/tslint-config": "^1.0.10", - "@0x/types": "^1.3.0", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", - "ethereum-types": "^1.1.2", + "@0x/tslint-config": "^2.0.0", + "@0x/types": "^1.4.1", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "ethereum-types": "^1.1.4", "ethers": "~4.0.4", "lodash": "^4.17.5", "shx": "^0.2.2" }, "dependencies": { - "@0x/base-contract": "^3.0.8" + "@0x/base-contract": "^3.0.10" }, "publishConfig": { "access": "public" diff --git a/packages/abi-gen/CHANGELOG.json b/packages/abi-gen/CHANGELOG.json index 2b4455bed..253fb124d 100644 --- a/packages/abi-gen/CHANGELOG.json +++ b/packages/abi-gen/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "1.0.19", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "1.0.18", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1542821676, "version": "1.0.17", "changes": [ diff --git a/packages/abi-gen/CHANGELOG.md b/packages/abi-gen/CHANGELOG.md index f939199fd..4ca19ad7e 100644 --- a/packages/abi-gen/CHANGELOG.md +++ b/packages/abi-gen/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.19 - _December 13, 2018_ + + * Dependencies updated + +## v1.0.18 - _December 11, 2018_ + + * Dependencies updated + ## v1.0.17 - _November 21, 2018_ * Dependencies updated diff --git a/packages/abi-gen/package.json b/packages/abi-gen/package.json index 485e72730..b122c742d 100644 --- a/packages/abi-gen/package.json +++ b/packages/abi-gen/package.json @@ -1,6 +1,6 @@ { "name": "@0x/abi-gen", - "version": "1.0.17", + "version": "1.0.19", "engines": { "node": ">=6.12" }, @@ -31,10 +31,10 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/abi-gen/README.md", "dependencies": { - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", "chalk": "^2.3.0", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "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.10", + "@0x/tslint-config": "^2.0.0", "@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 2b3fc68a4..3805a044f 100644 --- a/packages/assert/CHANGELOG.json +++ b/packages/assert/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "1.0.20", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "1.0.19", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1542821676, "version": "1.0.18", "changes": [ diff --git a/packages/assert/CHANGELOG.md b/packages/assert/CHANGELOG.md index 017b1c6ef..ef84c48d4 100644 --- a/packages/assert/CHANGELOG.md +++ b/packages/assert/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.20 - _December 13, 2018_ + + * Dependencies updated + +## v1.0.19 - _December 11, 2018_ + + * Dependencies updated + ## v1.0.18 - _November 21, 2018_ * Dependencies updated diff --git a/packages/assert/package.json b/packages/assert/package.json index 90b07ae76..cec1748cd 100644 --- a/packages/assert/package.json +++ b/packages/assert/package.json @@ -1,6 +1,6 @@ { "name": "@0x/assert", - "version": "1.0.18", + "version": "1.0.20", "engines": { "node": ">=6.12" }, @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/assert/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@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.1.2", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", + "@0x/json-schemas": "^2.1.4", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", "lodash": "^4.17.5", "valid-url": "^1.0.9" }, diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index 4ff83018e..470d9b03b 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -1,11 +1,21 @@ [ { + "version": "3.0.4", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { "version": "3.0.3", "changes": [ { "note": "Update SRA order provider to include Dai" } - ] + ], + "timestamp": 1544570656 }, { "timestamp": 1543401373, diff --git a/packages/asset-buyer/CHANGELOG.md b/packages/asset-buyer/CHANGELOG.md index be3ef67d1..cadb1acf8 100644 --- a/packages/asset-buyer/CHANGELOG.md +++ b/packages/asset-buyer/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.4 - _December 13, 2018_ + + * Dependencies updated + +## v3.0.3 - _December 11, 2018_ + + * Update SRA order provider to include Dai + ## v3.0.2 - _November 28, 2018_ * Dependencies updated diff --git a/packages/asset-buyer/package.json b/packages/asset-buyer/package.json index 780b2e3e2..401aec120 100644 --- a/packages/asset-buyer/package.json +++ b/packages/asset-buyer/package.json @@ -1,6 +1,6 @@ { "name": "@0x/asset-buyer", - "version": "3.0.2", + "version": "3.0.4", "engines": { "node": ">=6.12" }, @@ -36,21 +36,21 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/asset-buyer/README.md", "dependencies": { - "@0x/assert": "^1.0.18", - "@0x/connect": "^3.0.8", - "@0x/contract-wrappers": "^4.1.1", - "@0x/json-schemas": "^2.1.2", - "@0x/order-utils": "^3.0.4", - "@0x/subproviders": "^2.1.6", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", - "ethereum-types": "^1.1.2", + "@0x/assert": "^1.0.20", + "@0x/connect": "^3.0.10", + "@0x/contract-wrappers": "^4.1.3", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "ethereum-types": "^1.1.4", "lodash": "^4.17.5" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/lodash": "^4.14.116", "@types/mocha": "^2.2.42", "@types/node": "*", diff --git a/packages/base-contract/CHANGELOG.json b/packages/base-contract/CHANGELOG.json index e4dff5530..a4cd17d42 100644 --- a/packages/base-contract/CHANGELOG.json +++ b/packages/base-contract/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "3.0.10", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "3.0.9", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "3.0.8", "changes": [ diff --git a/packages/base-contract/CHANGELOG.md b/packages/base-contract/CHANGELOG.md index f61b6c6ce..a8ce346d7 100644 --- a/packages/base-contract/CHANGELOG.md +++ b/packages/base-contract/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.10 - _December 13, 2018_ + + * Dependencies updated + +## v3.0.9 - _December 11, 2018_ + + * Dependencies updated + ## v3.0.8 - _November 28, 2018_ * Dependencies updated diff --git a/packages/base-contract/package.json b/packages/base-contract/package.json index 2a331b3cb..87d70dd5f 100644 --- a/packages/base-contract/package.json +++ b/packages/base-contract/package.json @@ -1,6 +1,6 @@ { "name": "@0x/base-contract", - "version": "3.0.8", + "version": "3.0.10", "engines": { "node": ">=6.12" }, @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/base-contract/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@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.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", - "ethereum-types": "^1.1.2", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "ethereum-types": "^1.1.4", "ethers": "~4.0.4", "lodash": "^4.17.5" }, diff --git a/packages/connect/CHANGELOG.json b/packages/connect/CHANGELOG.json index 3abb895a7..e5bde40ae 100644 --- a/packages/connect/CHANGELOG.json +++ b/packages/connect/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "3.0.10", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "3.0.9", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "3.0.8", "changes": [ diff --git a/packages/connect/CHANGELOG.md b/packages/connect/CHANGELOG.md index 1dfc2672d..a5f95b988 100644 --- a/packages/connect/CHANGELOG.md +++ b/packages/connect/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.10 - _December 13, 2018_ + + * Dependencies updated + +## v3.0.9 - _December 11, 2018_ + + * Dependencies updated + ## v3.0.8 - _November 28, 2018_ * Dependencies updated diff --git a/packages/connect/package.json b/packages/connect/package.json index 2f3d30d84..4985d0410 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -1,6 +1,6 @@ { "name": "@0x/connect", - "version": "3.0.8", + "version": "3.0.10", "engines": { "node": ">=6.12" }, @@ -44,12 +44,12 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/connect/README.md", "dependencies": { - "@0x/assert": "^1.0.18", - "@0x/json-schemas": "^2.1.2", - "@0x/order-utils": "^3.0.4", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", + "@0x/assert": "^1.0.20", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", "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.10", + "@0x/tslint-config": "^2.0.0", "@types/fetch-mock": "^6.0.3", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 006a0904d..9475e5a88 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "4.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "4.1.2", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "4.1.1", "changes": [ diff --git a/packages/contract-wrappers/CHANGELOG.md b/packages/contract-wrappers/CHANGELOG.md index ebdcc9638..595fbcc31 100644 --- a/packages/contract-wrappers/CHANGELOG.md +++ b/packages/contract-wrappers/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v4.1.3 - _December 13, 2018_ + + * Dependencies updated + +## v4.1.2 - _December 11, 2018_ + + * Dependencies updated + ## v4.1.1 - _November 28, 2018_ * Dependencies updated diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json index e11d1a63e..f3f7301fd 100644 --- a/packages/contract-wrappers/package.json +++ b/packages/contract-wrappers/package.json @@ -1,6 +1,6 @@ { "name": "@0x/contract-wrappers", - "version": "4.1.1", + "version": "4.1.3", "description": "Smart TS wrappers for 0x smart contracts", "keywords": [ "0xproject", @@ -37,10 +37,10 @@ "node": ">=6.0.0" }, "devDependencies": { - "@0x/dev-utils": "^1.0.19", - "@0x/migrations": "^2.2.0", - "@0x/subproviders": "^2.1.6", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/migrations": "^2.2.2", + "@0x/subproviders": "^2.1.8", + "@0x/tslint-config": "^2.0.0", "@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": "^2.0.0", - "@0x/assert": "^1.0.18", + "@0x/abi-gen-wrappers": "^2.0.2", + "@0x/assert": "^1.0.20", "@0x/contract-addresses": "^2.0.0", "@0x/contract-artifacts": "^1.1.2", - "@0x/fill-scenarios": "^1.0.14", - "@0x/json-schemas": "^2.1.2", - "@0x/order-utils": "^3.0.4", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", - "ethereum-types": "^1.1.2", + "@0x/fill-scenarios": "^1.0.16", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "ethereum-types": "^1.1.4", "ethereumjs-blockstream": "6.0.0", "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 4b13beb01..754db3208 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.8", + "version": "0.0.10", "engines": { "node": ">=6.12" }, @@ -16,7 +16,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@0x/react-shared": "^1.0.23", + "@0x/react-shared": "^1.0.25", "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 417a3c65e..b6cc74ce7 100644 --- a/packages/dev-utils/CHANGELOG.json +++ b/packages/dev-utils/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "1.0.21", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "1.0.20", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "1.0.19", "changes": [ diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 1842c6824..9a55e4e58 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.21 - _December 13, 2018_ + + * Dependencies updated + +## v1.0.20 - _December 11, 2018_ + + * Dependencies updated + ## v1.0.19 - _November 28, 2018_ * Dependencies updated diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index a3b5c9090..a3eb4651a 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@0x/dev-utils", - "version": "1.0.19", + "version": "1.0.21", "engines": { "node": ">=6.12" }, @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/dev-utils/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@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.6", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/web3-provider-engine": "^14.0.0", "chai": "^4.0.1", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "lodash": "^4.17.5" }, "publishConfig": { diff --git a/packages/ethereum-types/CHANGELOG.json b/packages/ethereum-types/CHANGELOG.json index 9db75ae9f..9a0f2ad49 100644 --- a/packages/ethereum-types/CHANGELOG.json +++ b/packages/ethereum-types/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "1.1.4", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "1.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "version": "1.1.2", "changes": [ { diff --git a/packages/ethereum-types/CHANGELOG.md b/packages/ethereum-types/CHANGELOG.md index 6ad7b4cc6..1d27757f3 100644 --- a/packages/ethereum-types/CHANGELOG.md +++ b/packages/ethereum-types/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.1.4 - _December 13, 2018_ + + * Dependencies updated + +## v1.1.3 - _December 11, 2018_ + + * Dependencies updated + ## v1.1.2 - _November 9, 2018_ * Dependencies updated diff --git a/packages/ethereum-types/package.json b/packages/ethereum-types/package.json index 1630344db..69ae64c2b 100644 --- a/packages/ethereum-types/package.json +++ b/packages/ethereum-types/package.json @@ -1,6 +1,6 @@ { "name": "ethereum-types", - "version": "1.1.2", + "version": "1.1.4", "engines": { "node": ">=6.12" }, @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/ethereum-types/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "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 58ba49509..ca256399a 100644 --- a/packages/fill-scenarios/CHANGELOG.json +++ b/packages/fill-scenarios/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "1.0.16", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "1.0.15", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "1.0.14", "changes": [ diff --git a/packages/fill-scenarios/CHANGELOG.md b/packages/fill-scenarios/CHANGELOG.md index aa7df302e..e9b88547d 100644 --- a/packages/fill-scenarios/CHANGELOG.md +++ b/packages/fill-scenarios/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.16 - _December 13, 2018_ + + * Dependencies updated + +## v1.0.15 - _December 11, 2018_ + + * Dependencies updated + ## v1.0.14 - _November 28, 2018_ * Dependencies updated diff --git a/packages/fill-scenarios/package.json b/packages/fill-scenarios/package.json index b29eb674c..7a9d21e0a 100644 --- a/packages/fill-scenarios/package.json +++ b/packages/fill-scenarios/package.json @@ -1,6 +1,6 @@ { "name": "@0x/fill-scenarios", - "version": "1.0.14", + "version": "1.0.16", "description": "0x order fill scenario generator", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,7 +20,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/fill-scenarios/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@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": "^2.0.0", - "@0x/base-contract": "^3.0.8", + "@0x/abi-gen-wrappers": "^2.0.2", + "@0x/base-contract": "^3.0.10", "@0x/contract-artifacts": "^1.1.2", - "@0x/order-utils": "^3.0.4", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", - "ethereum-types": "^1.1.2", + "@0x/order-utils": "^3.0.7", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "ethereum-types": "^1.1.4", "ethers": "~4.0.4", "lodash": "^4.17.5" }, diff --git a/packages/instant/README.md b/packages/instant/README.md index 2092b45d9..32abf76e0 100644 --- a/packages/instant/README.md +++ b/packages/instant/README.md @@ -1,5 +1,11 @@ ## @0x/instant +## Integration + +Looking to integrate 0x Instant into your web application or site? Check out the dedicated [instant documentation](https://0xproject.com/wiki#Get-Started-With-Instant) to get started. The documentation covers instant and related topics in depth. For a more "drag and drop" experience, check out our [configurator tool](https://0xproject.com/instant#configure). For on demand developer support, join our [Discord](https://discordapp.com/invite/d3FTX3M). + +Check out a live sample integration [here](https://www.rexrelay.com/instant). + ## Installation The package is available as a UMD module named `zeroExInstant` at https://instant.0xproject.com/instant.js. diff --git a/packages/instant/package.json b/packages/instant/package.json index 9303276b4..0a5e152ca 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -1,6 +1,6 @@ { "name": "@0x/instant", - "version": "1.0.2", + "version": "1.0.4", "engines": { "node": ">=6.12" }, @@ -24,7 +24,10 @@ }, "config": { "postpublish": { - "assets": ["packages/instant/umd/instant.js", "packages/instant/umd/instant.js.map"] + "assets": [ + "packages/instant/umd/instant.js", + "packages/instant/umd/instant.js.map" + ] } }, "repository": { @@ -38,18 +41,18 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md", "dependencies": { - "@0x/assert": "^1.0.18", - "@0x/asset-buyer": "^3.0.2", - "@0x/json-schemas": "^2.1.2", - "@0x/order-utils": "^3.0.4", - "@0x/subproviders": "^2.1.6", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "@0x/assert": "^1.0.20", + "@0x/asset-buyer": "^3.0.4", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "bowser": "^1.9.4", "copy-to-clipboard": "^3.0.8", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "lodash": "^4.17.5", "polished": "^2.2.0", "react": "^16.5.2", @@ -62,7 +65,7 @@ "ts-optchain": "^0.1.1" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@static/discharge": "https://github.com/0xProject/discharge.git", "@types/enzyme": "^3.1.14", "@types/enzyme-adapter-react-16": "^1.0.3", diff --git a/packages/instant/src/components/erc20_token_selector.tsx b/packages/instant/src/components/erc20_token_selector.tsx index f7d5a4fe4..cb8a8c797 100644 --- a/packages/instant/src/components/erc20_token_selector.tsx +++ b/packages/instant/src/components/erc20_token_selector.tsx @@ -7,7 +7,6 @@ import { analytics } from '../util/analytics'; 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'; @@ -123,10 +122,20 @@ interface TokenSelectorRowIconProps { token: ERC20Asset; } +const getTokenIcon = (symbol: string): React.StatelessComponent | undefined => { + try { + return require(`../assets/icons/${symbol}.svg`) as React.StatelessComponent; + } catch (e) { + // Can't find icon + return undefined; + } +}; + const TokenSelectorRowIcon: React.StatelessComponent<TokenSelectorRowIconProps> = props => { const { token } = props; const iconUrlIfExists = token.metaData.iconUrl; - const TokenIcon = require(`../assets/icons/${token.metaData.symbol}.svg`); + + const TokenIcon = getTokenIcon(token.metaData.symbol); const displaySymbol = assetUtils.bestNameForAsset(token); if (!_.isUndefined(iconUrlIfExists)) { return <img src={iconUrlIfExists} />; diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 117f9dd5f..5b1f9592d 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -61,12 +61,19 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { } private _renderAmountsSection(): React.ReactNode { - return ( - <Container> - <Container marginBottom="5px">{this._renderPlaceholderOrAmount(this._renderEthAmount)}</Container> - <Container opacity={0.7}>{this._renderPlaceholderOrAmount(this._renderDollarAmount)}</Container> - </Container> - ); + if ( + _.isUndefined(this.props.totalEthBaseUnitAmount) && + this.props.quoteRequestState !== AsyncProcessState.Pending + ) { + return null; + } else { + return ( + <Container> + <Container marginBottom="5px">{this._renderPlaceholderOrAmount(this._renderEthAmount)}</Container> + <Container opacity={0.7}>{this._renderPlaceholderOrAmount(this._renderDollarAmount)}</Container> + </Container> + ); + } } private _renderIcon(): React.ReactNode { @@ -106,20 +113,23 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> { } private readonly _renderEthAmount = (): React.ReactNode => { + const ethAmount = format.ethBaseUnitAmount( + this.props.totalEthBaseUnitAmount, + 4, + <AmountPlaceholder isPulsating={false} color={PLACEHOLDER_COLOR} />, + ); + + const fontSize = _.isString(ethAmount) && ethAmount.length >= 13 ? '14px' : '16px'; return ( <Text - fontSize="16px" + fontSize={fontSize} textAlign="right" width="100%" fontColor={ColorOption.white} fontWeight={500} noWrap={true} > - {format.ethBaseUnitAmount( - this.props.totalEthBaseUnitAmount, - 4, - <AmountPlaceholder isPulsating={false} color={PLACEHOLDER_COLOR} />, - )} + {ethAmount} </Text> ); }; diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index a8e0e2513..9c10ef9e6 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -4,124 +4,227 @@ import * as _ from 'lodash'; import * as React from 'react'; import { oc } from 'ts-optchain'; -import { BIG_NUMBER_ZERO } from '../constants'; +import { BIG_NUMBER_ZERO, DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; import { ColorOption } from '../style/theme'; +import { BaseCurrency } from '../types'; import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; +import { SectionHeader } from './section_header'; import { Container } from './ui/container'; import { Flex } from './ui/flex'; -import { Text } from './ui/text'; +import { Text, TextProps } from './ui/text'; export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; selectedAssetUnitAmount?: BigNumber; ethUsdPrice?: BigNumber; isLoading: boolean; + assetName?: string; + baseCurrency: BaseCurrency; + onBaseCurrencySwitchEth: () => void; + onBaseCurrencySwitchUsd: () => void; } export class OrderDetails extends React.Component<OrderDetailsProps> { public render(): React.ReactNode { - const { buyQuoteInfo, ethUsdPrice, selectedAssetUnitAmount } = this.props; - const buyQuoteAccessor = oc(buyQuoteInfo); - const assetEthBaseUnitAmount = buyQuoteAccessor.assetEthAmount(); - const feeEthBaseUnitAmount = buyQuoteAccessor.feeEthAmount(); - const totalEthBaseUnitAmount = buyQuoteAccessor.totalEthAmount(); - const pricePerTokenEth = - !_.isUndefined(assetEthBaseUnitAmount) && - !_.isUndefined(selectedAssetUnitAmount) && - !selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO) - ? assetEthBaseUnitAmount.div(selectedAssetUnitAmount).ceil() - : undefined; + const shouldShowUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice(); return ( <Container width="100%" flexGrow={1} padding="20px 20px 0px 20px"> - <Container marginBottom="10px"> - <Text - letterSpacing="1px" - fontColor={ColorOption.primaryColor} - fontWeight={600} - textTransform="uppercase" - fontSize="14px" - > - Order Details - </Text> - </Container> - <EthAmountRow - rowLabel="Token Price" - ethAmount={pricePerTokenEth} - ethUsdPrice={ethUsdPrice} - isLoading={this.props.isLoading} + <Container marginBottom="10px">{this._renderHeader()}</Container> + {shouldShowUsdError ? this._renderErrorFetchingUsdPrice() : this._renderRows()} + </Container> + ); + } + + private _renderRows(): React.ReactNode { + const { buyQuoteInfo } = this.props; + return ( + <React.Fragment> + <OrderDetailsRow + labelText={this._assetAmountLabel()} + primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.assetEthAmount)} /> - <EthAmountRow - rowLabel="Fee" - ethAmount={feeEthBaseUnitAmount} - ethUsdPrice={ethUsdPrice} - isLoading={this.props.isLoading} + <OrderDetailsRow + labelText="Fee" + primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.feeEthAmount)} /> - <EthAmountRow - rowLabel="Total Cost" - ethAmount={totalEthBaseUnitAmount} - ethUsdPrice={ethUsdPrice} - shouldEmphasize={true} - isLoading={this.props.isLoading} + <OrderDetailsRow + labelText="Total Cost" + isLabelBold={true} + primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.totalEthAmount)} + isPrimaryValueBold={true} + secondaryValue={this._totalCostSecondaryValue()} /> - </Container> + </React.Fragment> ); } -} -export interface EthAmountRowProps { - rowLabel: string; - ethAmount?: BigNumber; - isEthAmountInBaseUnits?: boolean; - ethUsdPrice?: BigNumber; - shouldEmphasize?: boolean; - isLoading: boolean; + private _renderErrorFetchingUsdPrice(): React.ReactNode { + return ( + <Text> + There was an error fetching the USD price. + <Text + onClick={this.props.onBaseCurrencySwitchEth} + fontWeight={700} + fontColor={ColorOption.primaryColor} + > + Click here + </Text> + {' to view ETH prices'} + </Text> + ); + } + + private _hadErrorFetchingUsdPrice(): boolean { + return this.props.ethUsdPrice ? this.props.ethUsdPrice.equals(BIG_NUMBER_ZERO) : false; + } + + private _totalCostSecondaryValue(): React.ReactNode { + const secondaryCurrency = this.props.baseCurrency === BaseCurrency.USD ? BaseCurrency.ETH : BaseCurrency.USD; + + const canDisplayCurrency = + secondaryCurrency === BaseCurrency.ETH || + (secondaryCurrency === BaseCurrency.USD && this.props.ethUsdPrice && !this._hadErrorFetchingUsdPrice()); + + if (this.props.buyQuoteInfo && canDisplayCurrency) { + return this._displayAmount(secondaryCurrency, this.props.buyQuoteInfo.totalEthAmount); + } else { + return undefined; + } + } + + private _displayAmountOrPlaceholder(weiAmount?: BigNumber): React.ReactNode { + const { baseCurrency, isLoading } = this.props; + + if (_.isUndefined(weiAmount)) { + return ( + <Container opacity={0.5}> + <AmountPlaceholder color={ColorOption.lightGrey} isPulsating={isLoading} /> + </Container> + ); + } + + return this._displayAmount(baseCurrency, weiAmount); + } + + private _displayAmount(currency: BaseCurrency, weiAmount: BigNumber): React.ReactNode { + switch (currency) { + case BaseCurrency.USD: + return format.ethBaseUnitAmountInUsd(weiAmount, this.props.ethUsdPrice, 2, ''); + case BaseCurrency.ETH: + return format.ethBaseUnitAmount(weiAmount, 4, ''); + } + } + + private _assetAmountLabel(): React.ReactNode { + const { assetName, baseCurrency } = this.props; + const numTokens = this.props.selectedAssetUnitAmount; + + // Display as 0 if we have a selected asset + const displayNumTokens = + assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(numTokens) + ? new BigNumber(0) + : numTokens; + if (!_.isUndefined(displayNumTokens)) { + let numTokensWithSymbol: React.ReactNode = displayNumTokens.toString(); + if (assetName) { + numTokensWithSymbol += ` ${assetName}`; + } + const pricePerTokenWei = this._pricePerTokenWei(); + if (pricePerTokenWei) { + const atPriceDisplay = ( + <Text fontColor={ColorOption.lightGrey}> + @ {this._displayAmount(baseCurrency, pricePerTokenWei)} + </Text> + ); + numTokensWithSymbol = ( + <React.Fragment> + {numTokensWithSymbol} {atPriceDisplay} + </React.Fragment> + ); + } + return numTokensWithSymbol; + } + return 'Token Amount'; + } + + private _pricePerTokenWei(): BigNumber | undefined { + const buyQuoteAccessor = oc(this.props.buyQuoteInfo); + const assetTotalInWei = buyQuoteAccessor.assetEthAmount(); + const selectedAssetUnitAmount = this.props.selectedAssetUnitAmount; + return !_.isUndefined(assetTotalInWei) && + !_.isUndefined(selectedAssetUnitAmount) && + !selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO) + ? assetTotalInWei.div(selectedAssetUnitAmount).ceil() + : undefined; + } + + private _baseCurrencyChoice(choice: BaseCurrency): React.ReactNode { + const onClick = + choice === BaseCurrency.ETH ? this.props.onBaseCurrencySwitchEth : this.props.onBaseCurrencySwitchUsd; + const isSelected = this.props.baseCurrency === choice; + + const textStyle: TextProps = { onClick, fontSize: '12px' }; + if (isSelected) { + textStyle.fontColor = ColorOption.primaryColor; + textStyle.fontWeight = 700; + } else { + textStyle.fontColor = ColorOption.lightGrey; + } + return <Text {...textStyle}>{choice}</Text>; + } + + private _renderHeader(): React.ReactNode { + return ( + <Flex justify="space-between"> + <SectionHeader>Order Details</SectionHeader> + <Container> + {this._baseCurrencyChoice(BaseCurrency.ETH)} + <Container marginLeft="5px" marginRight="5px" display="inline"> + <Text fontSize="12px" fontColor={ColorOption.feintGrey}> + / + </Text> + </Container> + {this._baseCurrencyChoice(BaseCurrency.USD)} + </Container> + </Flex> + ); + } } -export class EthAmountRow extends React.Component<EthAmountRowProps> { - public static defaultProps = { - shouldEmphasize: false, - isEthAmountInBaseUnits: true, - }; +export interface OrderDetailsRowProps { + labelText: React.ReactNode; + isLabelBold?: boolean; + isPrimaryValueBold?: boolean; + primaryValue: React.ReactNode; + secondaryValue?: React.ReactNode; +} +export class OrderDetailsRow extends React.Component<OrderDetailsRowProps, {}> { public render(): React.ReactNode { - const { rowLabel, ethAmount, isEthAmountInBaseUnits, shouldEmphasize, isLoading } = this.props; - - const fontWeight = shouldEmphasize ? 700 : 400; - const ethFormatter = isEthAmountInBaseUnits ? format.ethBaseUnitAmount : format.ethUnitAmount; return ( <Container padding="10px 0px" borderTop="1px dashed" borderColor={ColorOption.feintGrey}> <Flex justify="space-between"> - <Text fontWeight={fontWeight} fontColor={ColorOption.grey}> - {rowLabel} + <Text fontWeight={this.props.isLabelBold ? 700 : 400} fontColor={ColorOption.grey}> + {this.props.labelText} </Text> - <Container> - {this._renderUsdSection()} - <Text fontWeight={fontWeight} fontColor={ColorOption.grey}> - {ethFormatter( - ethAmount, - 4, - <Container opacity={0.5}> - <AmountPlaceholder color={ColorOption.lightGrey} isPulsating={isLoading} /> - </Container>, - )} - </Text> - </Container> + <Container>{this._renderValues()}</Container> </Flex> </Container> ); } - private _renderUsdSection(): React.ReactNode { - const usdFormatter = this.props.isEthAmountInBaseUnits - ? format.ethBaseUnitAmountInUsd - : format.ethUnitAmountInUsd; - const shouldHideUsdPriceSection = _.isUndefined(this.props.ethUsdPrice) || _.isUndefined(this.props.ethAmount); - return shouldHideUsdPriceSection ? null : ( + + private _renderValues(): React.ReactNode { + const secondaryValueNode: React.ReactNode = this.props.secondaryValue && ( <Container marginRight="3px" display="inline-block"> - <Text fontColor={ColorOption.lightGrey}> - ({usdFormatter(this.props.ethAmount, this.props.ethUsdPrice)}) - </Text> + <Text fontColor={ColorOption.lightGrey}>({this.props.secondaryValue})</Text> </Container> ); + return ( + <React.Fragment> + {secondaryValueNode} + <Text fontWeight={this.props.isPrimaryValueBold ? 700 : 400}>{this.props.primaryValue}</Text> + </React.Fragment> + ); } } diff --git a/packages/instant/src/components/payment_method.tsx b/packages/instant/src/components/payment_method.tsx index 7c93f1d1c..abadf4bd6 100644 --- a/packages/instant/src/components/payment_method.tsx +++ b/packages/instant/src/components/payment_method.tsx @@ -8,6 +8,7 @@ import { envUtil } from '../util/env'; import { CoinbaseWalletLogo } from './coinbase_wallet_logo'; import { MetaMaskLogo } from './meta_mask_logo'; import { PaymentMethodDropdown } from './payment_method_dropdown'; +import { SectionHeader } from './section_header'; import { Circle } from './ui/circle'; import { Container } from './ui/container'; import { Flex } from './ui/flex'; @@ -29,15 +30,7 @@ export class PaymentMethod extends React.Component<PaymentMethodProps> { <Container width="100%" height="120px" padding="20px 20px 0px 20px"> <Container marginBottom="12px"> <Flex justify="space-between"> - <Text - letterSpacing="1px" - fontColor={ColorOption.primaryColor} - fontWeight={600} - textTransform="uppercase" - fontSize="14px" - > - {this._renderTitleText()} - </Text> + <SectionHeader>{this._renderTitleText()}</SectionHeader> {this._renderTitleLabel()} </Flex> </Container> diff --git a/packages/instant/src/components/scaling_amount_input.tsx b/packages/instant/src/components/scaling_amount_input.tsx index 86aca5a65..4feb0502d 100644 --- a/packages/instant/src/components/scaling_amount_input.tsx +++ b/packages/instant/src/components/scaling_amount_input.tsx @@ -58,6 +58,7 @@ export class ScalingAmountInput extends React.Component<ScalingAmountInputProps, const { textLengthThreshold, fontColor, maxFontSizePx, onFontSizeChange } = this.props; return ( <ScalingInput + type="number" maxFontSizePx={maxFontSizePx} textLengthThreshold={textLengthThreshold} onFontSizeChange={onFontSizeChange} diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx index 791692257..00aea37da 100644 --- a/packages/instant/src/components/scaling_input.tsx +++ b/packages/instant/src/components/scaling_input.tsx @@ -1,3 +1,4 @@ +import { ObjectMap } from '@0x/types'; import * as _ from 'lodash'; import * as React from 'react'; @@ -13,10 +14,15 @@ export enum ScalingInputPhase { export interface ScalingSettings { percentageToReduceFontSizePerCharacter: number; - constantPxToIncreaseWidthPerCharacter: number; + // 1ch = the width of the 0 chararacter. + // Allow to customize 'char' length for different characters. + characterWidthOverrides: ObjectMap<number>; + // How much room to leave to the right of the scaling input. + additionalInputSpaceInCh: number; } export interface ScalingInputProps { + type?: string; textLengthThreshold: number; maxFontSizePx: number; value: string; @@ -31,32 +37,29 @@ export interface ScalingInputProps { hasAutofocus: 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, + percentageToReduceFontSizePerCharacter: 0.1, + characterWidthOverrides: { + '1': 0.7, + '.': 0.4, + }, + additionalInputSpaceInCh: 0.4, }; -export class ScalingInput extends React.Component<ScalingInputProps, ScalingInputState> { +export class ScalingInput extends React.Component<ScalingInputProps> { public static defaultProps = { onChange: util.boundNoop, onFontSizeChange: util.boundNoop, - maxLength: 7, + maxLength: 9, scalingSettings: defaultScalingSettings, isDisabled: false, hasAutofocus: false, }; - public state: ScalingInputState = { - inputWidthPxAtPhaseChange: undefined, - }; private readonly _inputRef = React.createRef<HTMLInputElement>(); public static getPhase(textLengthThreshold: number, value: string): ScalingInputPhase { if (value.length <= textLengthThreshold) { @@ -93,36 +96,15 @@ export class ScalingInput extends React.Component<ScalingInputProps, ScalingInpu scalingSettings.percentageToReduceFontSizePerCharacter, ); } - public getSnapshotBeforeUpdate(): ScalingInputSnapshot { - return { - inputWidthPx: this._getInputWidthInPx(), - }; - } public componentDidMount(): void { // Trigger an initial notification of the calculated fontSize. const currentPhase = ScalingInput.getPhaseFromProps(this.props); const currentFontSize = ScalingInput.calculateFontSizeFromProps(this.props, currentPhase); this.props.onFontSizeChange(currentFontSize); } - public componentDidUpdate( - prevProps: ScalingInputProps, - prevState: ScalingInputState, - snapshot: ScalingInputSnapshot, - ): void { + public componentDidUpdate(prevProps: ScalingInputProps): 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. @@ -131,13 +113,14 @@ export class ScalingInput extends React.Component<ScalingInputProps, ScalingInpu } } public render(): React.ReactNode { - const { hasAutofocus, isDisabled, fontColor, onChange, placeholder, value, maxLength } = this.props; + const { type, hasAutofocus, isDisabled, fontColor, placeholder, value, maxLength } = this.props; const phase = ScalingInput.getPhaseFromProps(this.props); return ( <Input + type={type} ref={this._inputRef as any} fontColor={fontColor} - onChange={onChange} + onChange={this._handleChange} value={value} placeholder={placeholder} fontSize={`${this._calculateFontSize(phase)}px`} @@ -149,32 +132,34 @@ export class ScalingInput extends React.Component<ScalingInputProps, ScalingInpu ); } private readonly _calculateWidth = (phase: ScalingInputPhase): string => { - const { value, textLengthThreshold, scalingSettings } = this.props; + const { value, 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`; + const lengthInCh = _.reduce( + value.split(''), + (sum, char) => { + const widthOverride = scalingSettings.characterWidthOverrides[char]; + if (!_.isUndefined(widthOverride)) { + // tslint is confused + // tslint:disable-next-line:restrict-plus-operands + return sum + widthOverride; } - return `${textLengthThreshold}ch`; - } + return sum + 1; + }, + scalingSettings.additionalInputSpaceInCh, + ); + return `${lengthInCh}ch`; }; 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; + private readonly _handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => { + const value = event.target.value; + const { maxLength } = this.props; + if (!_.isUndefined(value) && !_.isUndefined(maxLength) && value.length > maxLength) { + return; } - return ref.getBoundingClientRect().width; + this.props.onChange(event); }; } diff --git a/packages/instant/src/components/section_header.tsx b/packages/instant/src/components/section_header.tsx new file mode 100644 index 000000000..d0974ebdc --- /dev/null +++ b/packages/instant/src/components/section_header.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; + +import { Text } from './ui/text'; + +export interface SectionHeaderProps {} +export const SectionHeader: React.StatelessComponent<SectionHeaderProps> = props => { + return ( + <Text + letterSpacing="1px" + fontColor={ColorOption.primaryColor} + fontWeight={600} + textTransform="uppercase" + fontSize="12px" + > + {props.children} + </Text> + ); +}; diff --git a/packages/instant/src/components/ui/input.tsx b/packages/instant/src/components/ui/input.tsx index 62c70f9e1..53c43ea0b 100644 --- a/packages/instant/src/components/ui/input.tsx +++ b/packages/instant/src/components/ui/input.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { ColorOption, styled } from '../../style/theme'; -export interface InputProps { +export interface InputProps extends React.HTMLAttributes<HTMLInputElement> { tabIndex?: number; className?: string; value?: string; @@ -32,6 +32,10 @@ export const Input = color: ${props => props.theme[props.fontColor || 'white']} !important; opacity: 0.5 !important; } + &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } } `; diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index 7ae27de23..2de327cd7 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -38,6 +38,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider props.orderSource, networkId, props.provider, + props.walletDisplayName, ); // merge the additional additionalAssetMetaDataMap with our default map const completeAssetMetaDataMap = { @@ -121,6 +122,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider window, state.selectedAsset, this.props.affiliateInfo, + state.baseCurrency, ), ); analytics.trackInstantOpened(); diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index f83eb4ac7..975dfcbea 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -17,6 +17,7 @@ export const ONE_MINUTE_MS = ONE_SECOND_MS * 60; export const GIT_SHA = process.env.GIT_SHA; export const NODE_ENV = process.env.NODE_ENV; export const NPM_PACKAGE_VERSION = process.env.NPM_PACKAGE_VERSION; +export const DEFAULT_UNKOWN_ASSET_NAME = '???'; 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); diff --git a/packages/instant/src/containers/connected_account_payment_method.ts b/packages/instant/src/containers/connected_account_payment_method.ts index bb68fdd57..f648f0b54 100644 --- a/packages/instant/src/containers/connected_account_payment_method.ts +++ b/packages/instant/src/containers/connected_account_payment_method.ts @@ -58,7 +58,7 @@ const mergeProps = ( ...ownProps, network: connectedState.network, account: connectedState.providerState.account, - walletDisplayName: connectedState.walletDisplayName || connectedState.providerState.name, + walletDisplayName: connectedState.providerState.displayName, onUnlockWalletClick: () => connectedDispatch.unlockWalletAndDispatchToStore(connectedState.providerState), onInstallWalletClick: () => { const isMobile = envUtil.isMobileOperatingSystem(); 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 5dfe535e7..148735c47 100644 --- a/packages/instant/src/containers/latest_buy_quote_order_details.ts +++ b/packages/instant/src/containers/latest_buy_quote_order_details.ts @@ -1,32 +1,41 @@ -import { BuyQuoteInfo } from '@0x/asset-buyer'; -import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; import { oc } from 'ts-optchain'; +import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; -import { OrderDetails } from '../components/order_details'; -import { AsyncProcessState } from '../types'; +import { OrderDetails, OrderDetailsProps } from '../components/order_details'; +import { AsyncProcessState, BaseCurrency, Omit } from '../types'; +import { assetUtils } from '../util/asset'; -export interface LatestBuyQuoteOrderDetailsProps {} - -interface ConnectedState { - buyQuoteInfo?: BuyQuoteInfo; - selectedAssetUnitAmount?: BigNumber; - ethUsdPrice?: BigNumber; - isLoading: boolean; -} +type DispatchProperties = 'onBaseCurrencySwitchEth' | 'onBaseCurrencySwitchUsd'; +interface ConnectedState extends Omit<OrderDetailsProps, DispatchProperties> {} const mapStateToProps = (state: State, _ownProps: LatestBuyQuoteOrderDetailsProps): ConnectedState => ({ // use the worst case quote info buyQuoteInfo: oc(state).latestBuyQuote.worstCaseQuoteInfo(), selectedAssetUnitAmount: state.selectedAssetUnitAmount, ethUsdPrice: state.ethUsdPrice, isLoading: state.quoteRequestState === AsyncProcessState.Pending, + assetName: assetUtils.bestNameForAsset(state.selectedAsset), + baseCurrency: state.baseCurrency, }); +interface ConnectedDispatch extends Pick<OrderDetailsProps, DispatchProperties> {} +const mapDispatchToProps = (dispatch: Dispatch<Action>): ConnectedDispatch => ({ + onBaseCurrencySwitchEth: () => { + dispatch(actions.updateBaseCurrency(BaseCurrency.ETH)); + }, + onBaseCurrencySwitchUsd: () => { + dispatch(actions.updateBaseCurrency(BaseCurrency.USD)); + }, +}); + +export interface LatestBuyQuoteOrderDetailsProps {} export const LatestBuyQuoteOrderDetails: React.ComponentClass<LatestBuyQuoteOrderDetailsProps> = connect( mapStateToProps, + mapDispatchToProps, )(OrderDetails); diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts index 77e3dec12..9d7a61fc7 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, AddressAndEthBalanceInWei, Asset, StandardSlidingPanelContent } from '../types'; +import { ActionsUnion, AddressAndEthBalanceInWei, Asset, BaseCurrency, StandardSlidingPanelContent } from '../types'; export interface PlainAction<T extends string> { type: T; @@ -43,6 +43,7 @@ export enum ActionTypes { RESET_AMOUNT = 'RESET_AMOUNT', OPEN_STANDARD_SLIDING_PANEL = 'OPEN_STANDARD_SLIDING_PANEL', CLOSE_STANDARD_SLIDING_PANEL = 'CLOSE_STANDARD_SLIDING_PANEL', + UPDATE_BASE_CURRENCY = 'UPDATE_BASE_CURRENCY', } export const actions = { @@ -72,4 +73,5 @@ export const actions = { openStandardSlidingPanel: (content: StandardSlidingPanelContent) => createAction(ActionTypes.OPEN_STANDARD_SLIDING_PANEL, content), closeStandardSlidingPanel: () => createAction(ActionTypes.CLOSE_STANDARD_SLIDING_PANEL), + updateBaseCurrency: (baseCurrency: BaseCurrency) => createAction(ActionTypes.UPDATE_BASE_CURRENCY, baseCurrency), }; diff --git a/packages/instant/src/redux/analytics_middleware.ts b/packages/instant/src/redux/analytics_middleware.ts index 3f7a51707..a86a16b1a 100644 --- a/packages/instant/src/redux/analytics_middleware.ts +++ b/packages/instant/src/redux/analytics_middleware.ts @@ -99,6 +99,9 @@ export const analyticsMiddleware: Middleware = store => next => middlewareAction analytics.trackInstallWalletModalClosed(); } break; + case ActionTypes.UPDATE_BASE_CURRENCY: + analytics.trackBaseCurrencyChanged(curState.baseCurrency); + analytics.addEventProperties({ baseCurrency: curState.baseCurrency }); } return nextAction; diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index c67b222d1..884ab103d 100644 --- a/packages/instant/src/redux/async_data.ts +++ b/packages/instant/src/redux/async_data.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { Dispatch } from 'redux'; import { BIG_NUMBER_ZERO } from '../constants'; -import { AccountState, ERC20Asset, OrderProcessState, ProviderState, QuoteFetchOrigin } from '../types'; +import { AccountState, BaseCurrency, ERC20Asset, OrderProcessState, ProviderState, QuoteFetchOrigin } from '../types'; import { analytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { buyQuoteUpdater } from '../util/buy_quote_updater'; @@ -24,7 +24,9 @@ export const asyncData = { const errorMessage = 'Error fetching ETH/USD price'; errorFlasher.flashNewErrorMessage(dispatch, errorMessage); dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO)); + dispatch(actions.updateBaseCurrency(BaseCurrency.ETH)); errorReporter.report(e); + analytics.trackUsdPriceFailed(); } }, fetchAvailableAssetDatasAndDispatchToStore: async (state: State, dispatch: Dispatch) => { @@ -97,6 +99,7 @@ export const asyncData = { if ( !_.isUndefined(selectedAssetUnitAmount) && !_.isUndefined(selectedAsset) && + selectedAssetUnitAmount.greaterThan(BIG_NUMBER_ZERO) && buyOrderState.processState === OrderProcessState.None && selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20 ) { diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index a9a407b7d..8c13c9c72 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -14,6 +14,7 @@ import { Asset, AssetMetaData, AsyncProcessState, + BaseCurrency, DisplayStatus, Network, OrderProcessState, @@ -33,6 +34,7 @@ export interface DefaultState { latestErrorDisplayStatus: DisplayStatus; quoteRequestState: AsyncProcessState; standardSlidingPanelSettings: StandardSlidingPanelSettings; + baseCurrency: BaseCurrency; } // State that is required but needs to be derived from the props @@ -64,6 +66,7 @@ export const DEFAULT_STATE: DefaultState = { animationState: 'none', content: StandardSlidingPanelContent.None, }, + baseCurrency: BaseCurrency.USD, }; export const createReducer = (initialState: State) => { @@ -243,6 +246,11 @@ export const createReducer = (initialState: State) => { animationState: 'slidOut', }, }; + case ActionTypes.UPDATE_BASE_CURRENCY: + return { + ...state, + baseCurrency: action.data, + }; default: return state; } diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index e65961e95..e7c920f36 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -26,6 +26,11 @@ export enum QuoteFetchOrigin { Heartbeat = 'Heartbeat', } +export enum BaseCurrency { + USD = 'USD', + ETH = 'ETH', +} + export interface SimulatedProgress { startTimeUnix: number; expectedEndTimeUnix: number; @@ -102,6 +107,7 @@ export interface AffiliateInfo { export interface ProviderState { name: string; + displayName: string; provider: Provider; assetBuyer: AssetBuyer; web3Wrapper: Web3Wrapper; diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index 6da52db16..6c63907dc 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -6,6 +6,7 @@ import { GIT_SHA, HEAP_ENABLED, INSTANT_DISCHARGE_TARGET, NODE_ENV, NPM_PACKAGE_ import { AffiliateInfo, Asset, + BaseCurrency, Network, OrderProcessState, OrderSource, @@ -37,6 +38,7 @@ enum EventNames { ACCOUNT_UNLOCK_REQUESTED = 'Account - Unlock Requested', ACCOUNT_UNLOCK_DENIED = 'Account - Unlock Denied', ACCOUNT_ADDRESS_CHANGED = 'Account - Address Changed', + BASE_CURRENCY_CHANGED = 'Base Currency - Changed', PAYMENT_METHOD_DROPDOWN_OPENED = 'Payment Method - Dropdown Opened', PAYMENT_METHOD_OPENED_ETHERSCAN = 'Payment Method - Opened Etherscan', PAYMENT_METHOD_COPIED_ADDRESS = 'Payment Method - Copied Address', @@ -47,6 +49,7 @@ enum EventNames { BUY_TX_SUBMITTED = 'Buy - Tx Submitted', BUY_TX_SUCCEEDED = 'Buy - Tx Succeeded', BUY_TX_FAILED = 'Buy - Tx Failed', + USD_PRICE_FETCH_FAILED = 'USD Price - Fetch Failed', INSTALL_WALLET_CLICKED = 'Install Wallet - Clicked', INSTALL_WALLET_MODAL_OPENED = 'Install Wallet - Modal - Opened', INSTALL_WALLET_MODAL_CLICKED_EXPLANATION = 'Install Wallet - Modal - Clicked Explanation', @@ -106,6 +109,7 @@ export interface AnalyticsEventOptions { ethAddress?: string; networkId?: number; providerName?: string; + providerDisplayName?: string; gitSha?: string; npmVersion?: string; instantEnvironment?: string; @@ -117,6 +121,7 @@ export interface AnalyticsEventOptions { selectedAssetSymbol?: string; selectedAssetData?: string; selectedAssetDecimals?: number; + baseCurrency?: string; } export enum TokenSelectorClosedVia { ClickedX = 'Clicked X', @@ -140,6 +145,7 @@ export const analytics = { window: Window, selectedAsset?: Asset, affiliateInfo?: AffiliateInfo, + baseCurrency?: BaseCurrency, ): AnalyticsEventOptions => { const affiliateAddress = affiliateInfo ? affiliateInfo.feeRecipient : 'none'; const affiliateFeePercent = affiliateInfo ? parseFloat(affiliateInfo.feePercentage.toFixed(4)) : 0; @@ -149,6 +155,7 @@ export const analytics = { embeddedUrl: window.location.href, networkId: network, providerName: providerState.name, + providerDisplayName: providerState.displayName, gitSha: GIT_SHA, npmVersion: NPM_PACKAGE_VERSION, orderSource: orderSourceName, @@ -157,6 +164,7 @@ export const analytics = { selectedAssetName: selectedAsset ? selectedAsset.metaData.name : 'none', selectedAssetData: selectedAsset ? selectedAsset.assetData : 'none', instantEnvironment: INSTANT_DISCHARGE_TARGET || `Local ${NODE_ENV}`, + baseCurrency, }; return eventOptions; }, @@ -168,6 +176,8 @@ export const analytics = { trackAccountUnlockDenied: trackingEventFnWithoutPayload(EventNames.ACCOUNT_UNLOCK_DENIED), trackAccountAddressChanged: (address: string) => trackingEventFnWithPayload(EventNames.ACCOUNT_ADDRESS_CHANGED)({ address }), + trackBaseCurrencyChanged: (currencyChangedTo: BaseCurrency) => + trackingEventFnWithPayload(EventNames.BASE_CURRENCY_CHANGED)({ currencyChangedTo }), trackPaymentMethodDropdownOpened: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_DROPDOWN_OPENED), trackPaymentMethodOpenedEtherscan: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_OPENED_ETHERSCAN), trackPaymentMethodCopiedAddress: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_COPIED_ADDRESS), @@ -228,4 +238,5 @@ export const analytics = { fetchOrigin, }); }, + trackUsdPriceFailed: trackingEventFnWithoutPayload(EventNames.USD_PRICE_FETCH_FAILED), }; diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index 13f84ef74..faaeb7c22 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -2,6 +2,7 @@ import { AssetBuyerError } from '@0x/asset-buyer'; import { AssetProxyId, ObjectMap } from '@0x/types'; import * as _ from 'lodash'; +import { DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; import { assetDataNetworkMapping } from '../data/asset_data_network_mapping'; import { Asset, AssetMetaData, ERC20Asset, Network, ZeroExInstantError } from '../types'; @@ -71,7 +72,7 @@ export const assetUtils = { } return metaData; }, - bestNameForAsset: (asset?: Asset, defaultName: string = '???'): string => { + bestNameForAsset: (asset?: Asset, defaultName: string = DEFAULT_UNKOWN_ASSET_NAME): string => { if (_.isUndefined(asset)) { return defaultName; } diff --git a/packages/instant/src/util/env.ts b/packages/instant/src/util/env.ts index 4a32f9cb1..0fda0cc0e 100644 --- a/packages/instant/src/util/env.ts +++ b/packages/instant/src/util/env.ts @@ -62,4 +62,11 @@ export const envUtil = { } return PROVIDER_TYPE_TO_NAME[providerTypeIfExists]; }, + getProviderDisplayName(provider: Provider): string { + const providerTypeIfExists = envUtil.getProviderType(provider); + if (_.isUndefined(providerTypeIfExists)) { + return 'Wallet'; + } + return PROVIDER_TYPE_TO_NAME[providerTypeIfExists]; + }, }; diff --git a/packages/instant/src/util/format.ts b/packages/instant/src/util/format.ts index e9c432b2f..4adb63e21 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 { ETH_DECIMALS } from '../constants'; +import { BIG_NUMBER_ZERO, ETH_DECIMALS } from '../constants'; export const format = { ethBaseUnitAmount: ( @@ -20,24 +20,38 @@ export const format = { ethUnitAmount?: BigNumber, decimalPlaces: number = 4, defaultText: React.ReactNode = '0 ETH', + minUnitAmountToDisplay: BigNumber = new BigNumber('0.00001'), ): React.ReactNode => { if (_.isUndefined(ethUnitAmount)) { return defaultText; } - const roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces); - return `${roundedAmount} ETH`; + let roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces); + + if (roundedAmount.eq(BIG_NUMBER_ZERO) && ethUnitAmount.greaterThan(BIG_NUMBER_ZERO)) { + // Sometimes for small ETH amounts (i.e. 0.000045) the amount rounded to 4 decimalPlaces is 0 + // If that is the case, show to 1 significant digit + roundedAmount = new BigNumber(ethUnitAmount.toPrecision(1)); + } + + const displayAmount = + roundedAmount.greaterThan(BIG_NUMBER_ZERO) && roundedAmount.lessThan(minUnitAmountToDisplay) + ? `< ${minUnitAmountToDisplay.toString()}` + : roundedAmount.toString(); + + return `${displayAmount} ETH`; }, ethBaseUnitAmountInUsd: ( ethBaseUnitAmount?: BigNumber, ethUsdPrice?: BigNumber, decimalPlaces: number = 2, defaultText: React.ReactNode = '$0.00', + minUnitAmountToDisplay: BigNumber = new BigNumber('0.00001'), ): React.ReactNode => { if (_.isUndefined(ethBaseUnitAmount) || _.isUndefined(ethUsdPrice)) { return defaultText; } const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseUnitAmount, ETH_DECIMALS); - return format.ethUnitAmountInUsd(ethUnitAmount, ethUsdPrice, decimalPlaces); + return format.ethUnitAmountInUsd(ethUnitAmount, ethUsdPrice, decimalPlaces, minUnitAmountToDisplay); }, ethUnitAmountInUsd: ( ethUnitAmount?: BigNumber, @@ -48,7 +62,13 @@ export const format = { if (_.isUndefined(ethUnitAmount) || _.isUndefined(ethUsdPrice)) { return defaultText; } - return `$${ethUnitAmount.mul(ethUsdPrice).toFixed(decimalPlaces)}`; + const rawUsdPrice = ethUnitAmount.mul(ethUsdPrice); + const roundedUsdPrice = rawUsdPrice.toFixed(decimalPlaces); + if (roundedUsdPrice === '0.00' && rawUsdPrice.gt(BIG_NUMBER_ZERO)) { + return '<$0.01'; + } else { + return `$${roundedUsdPrice}`; + } }, ethAddress: (address: string): string => { return `0x${address.slice(2, 7)}…${address.slice(-5)}`; diff --git a/packages/instant/src/util/provider_state_factory.ts b/packages/instant/src/util/provider_state_factory.ts index 7c788dff2..bd2d6dad5 100644 --- a/packages/instant/src/util/provider_state_factory.ts +++ b/packages/instant/src/util/provider_state_factory.ts @@ -10,27 +10,40 @@ import { assetBuyerFactory } from './asset_buyer_factory'; import { providerFactory } from './provider_factory'; export const providerStateFactory = { - getInitialProviderState: (orderSource: OrderSource, network: Network, provider?: Provider): ProviderState => { + getInitialProviderState: ( + orderSource: OrderSource, + network: Network, + provider?: Provider, + walletDisplayName?: string, + ): ProviderState => { if (!_.isUndefined(provider)) { - return providerStateFactory.getInitialProviderStateFromProvider(orderSource, network, provider); + return providerStateFactory.getInitialProviderStateFromProvider( + orderSource, + network, + provider, + walletDisplayName, + ); } const providerStateFromWindowIfExits = providerStateFactory.getInitialProviderStateFromWindowIfExists( orderSource, network, + walletDisplayName, ); if (providerStateFromWindowIfExits) { return providerStateFromWindowIfExits; } else { - return providerStateFactory.getInitialProviderStateFallback(orderSource, network); + return providerStateFactory.getInitialProviderStateFallback(orderSource, network, walletDisplayName); } }, getInitialProviderStateFromProvider: ( orderSource: OrderSource, network: Network, provider: Provider, + walletDisplayName?: string, ): ProviderState => { const providerState: ProviderState = { name: envUtil.getProviderName(provider), + displayName: walletDisplayName || envUtil.getProviderDisplayName(provider), provider, web3Wrapper: new Web3Wrapper(provider), assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network), @@ -38,11 +51,16 @@ export const providerStateFactory = { }; return providerState; }, - getInitialProviderStateFromWindowIfExists: (orderSource: OrderSource, network: Network): Maybe<ProviderState> => { + getInitialProviderStateFromWindowIfExists: ( + orderSource: OrderSource, + network: Network, + walletDisplayName?: string, + ): Maybe<ProviderState> => { const injectedProviderIfExists = providerFactory.getInjectedProviderIfExists(); if (!_.isUndefined(injectedProviderIfExists)) { const providerState: ProviderState = { name: envUtil.getProviderName(injectedProviderIfExists), + displayName: walletDisplayName || envUtil.getProviderDisplayName(injectedProviderIfExists), provider: injectedProviderIfExists, web3Wrapper: new Web3Wrapper(injectedProviderIfExists), assetBuyer: assetBuyerFactory.getAssetBuyer(injectedProviderIfExists, orderSource, network), @@ -53,10 +71,15 @@ export const providerStateFactory = { return undefined; } }, - getInitialProviderStateFallback: (orderSource: OrderSource, network: Network): ProviderState => { + getInitialProviderStateFallback: ( + orderSource: OrderSource, + network: Network, + walletDisplayName?: string, + ): ProviderState => { const provider = providerFactory.getFallbackNoSigningProvider(network); const providerState: ProviderState = { name: 'Fallback', + displayName: walletDisplayName || envUtil.getProviderDisplayName(provider), provider, web3Wrapper: new Web3Wrapper(provider), assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network), diff --git a/packages/instant/test/util/format.test.ts b/packages/instant/test/util/format.test.ts index fe0a63e6e..38bf356ec 100644 --- a/packages/instant/test/util/format.test.ts +++ b/packages/instant/test/util/format.test.ts @@ -41,6 +41,18 @@ describe('format', () => { it('converts BigNumber(5.3014059295032) to the string `5.301 ETH`', () => { expect(format.ethUnitAmount(BIG_NUMBER_IRRATIONAL)).toBe('5.301 ETH'); }); + it('shows 1 significant digit when rounded amount would be 0', () => { + expect(format.ethUnitAmount(new BigNumber(0.00003))).toBe('0.00003 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.000034))).toBe('0.00003 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.000035))).toBe('0.00004 ETH'); + }); + it('shows < 0.00001 when hits threshold', () => { + expect(format.ethUnitAmount(new BigNumber(0.000011))).toBe('0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.00001))).toBe('0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.000009))).toBe('< 0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.0000000009))).toBe('< 0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0))).toBe('0 ETH'); + }); it('returns defaultText param when ethUnitAmount is not defined', () => { const defaultText = 'defaultText'; expect(format.ethUnitAmount(undefined, 4, defaultText)).toBe(defaultText); @@ -86,6 +98,12 @@ describe('format', () => { it('correctly formats 5.3014059295032 ETH to usd according to some price', () => { expect(format.ethUnitAmountInUsd(BIG_NUMBER_IRRATIONAL, BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('$13.43'); }); + it('correctly formats amount that is less than 1 cent', () => { + expect(format.ethUnitAmountInUsd(new BigNumber(0.000001), BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('<$0.01'); + }); + it('correctly formats exactly 1 cent', () => { + expect(format.ethUnitAmountInUsd(new BigNumber(0.0039), BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('$0.01'); + }); it('returns defaultText param when ethUnitAmountInUsd or ethUsdPrice is not defined', () => { const defaultText = 'defaultText'; expect(format.ethUnitAmountInUsd(undefined, undefined, 2, defaultText)).toBe(defaultText); diff --git a/packages/json-schemas/CHANGELOG.json b/packages/json-schemas/CHANGELOG.json index 17cabc473..201190145 100644 --- a/packages/json-schemas/CHANGELOG.json +++ b/packages/json-schemas/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "2.1.4", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "2.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1542821676, "version": "2.1.2", "changes": [ diff --git a/packages/json-schemas/CHANGELOG.md b/packages/json-schemas/CHANGELOG.md index 2f39c9596..7e407bdda 100644 --- a/packages/json-schemas/CHANGELOG.md +++ b/packages/json-schemas/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.1.4 - _December 13, 2018_ + + * Dependencies updated + +## v2.1.3 - _December 11, 2018_ + + * Dependencies updated + ## v2.1.2 - _November 21, 2018_ * Dependencies updated diff --git a/packages/json-schemas/package.json b/packages/json-schemas/package.json index da3231b57..57dd9dc00 100644 --- a/packages/json-schemas/package.json +++ b/packages/json-schemas/package.json @@ -1,6 +1,6 @@ { "name": "@0x/json-schemas", - "version": "2.1.2", + "version": "2.1.4", "engines": { "node": ">=6.12" }, @@ -39,14 +39,14 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/json-schemas/README.md", "dependencies": { - "@0x/typescript-typings": "^3.0.4", + "@0x/typescript-typings": "^3.0.6", "@types/node": "*", "jsonschema": "^1.2.0", "lodash.values": "^4.3.0" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", - "@0x/utils": "^2.0.6", + "@0x/tslint-config": "^2.0.0", + "@0x/utils": "^2.0.8", "@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 7622fa5d4..0e9fa4920 100644 --- a/packages/metacoin/package.json +++ b/packages/metacoin/package.json @@ -1,6 +1,6 @@ { "name": "@0x/metacoin", - "version": "0.0.30", + "version": "0.0.32", "engines": { "node": ">=6.12" }, @@ -29,26 +29,26 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "@0x/abi-gen": "^1.0.17", + "@0x/abi-gen": "^1.0.19", "@0x/abi-gen-templates": "^1.0.1", - "@0x/base-contract": "^3.0.8", - "@0x/sol-cov": "^2.1.14", - "@0x/subproviders": "^2.1.6", - "@0x/tslint-config": "^1.0.10", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "@0x/base-contract": "^3.0.10", + "@0x/sol-cov": "^2.1.16", + "@0x/subproviders": "^2.1.8", + "@0x/tslint-config": "^2.0.0", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/mocha": "^5.2.2", "copyfiles": "^2.0.0", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "ethers": "~4.0.4", "lodash": "^4.17.5", "run-s": "^0.0.0" }, "devDependencies": { - "@0x/dev-utils": "^1.0.19", - "@0x/sol-compiler": "^1.1.14", + "@0x/dev-utils": "^1.0.21", + "@0x/sol-compiler": "^1.1.16", "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 56705fc1a..e7b18f12b 100644 --- a/packages/migrations/CHANGELOG.json +++ b/packages/migrations/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "2.2.2", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "2.2.1", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "version": "2.2.0", "changes": [ { diff --git a/packages/migrations/CHANGELOG.md b/packages/migrations/CHANGELOG.md index 3808b2d3d..0b7b9a364 100644 --- a/packages/migrations/CHANGELOG.md +++ b/packages/migrations/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.2.2 - _December 13, 2018_ + + * Dependencies updated + +## v2.2.1 - _December 11, 2018_ + + * Dependencies updated + ## v2.2.0 - _November 28, 2018_ * Add CLI `0x-migrate` for running the 0x migrations in a language-agnostic way (#1324) diff --git a/packages/migrations/package.json b/packages/migrations/package.json index f4dd1f9f9..72ffe67b2 100644 --- a/packages/migrations/package.json +++ b/packages/migrations/package.json @@ -1,6 +1,6 @@ { "name": "@0x/migrations", - "version": "2.2.0", + "version": "2.2.2", "engines": { "node": ">=6.12" }, @@ -26,9 +26,9 @@ }, "license": "Apache-2.0", "devDependencies": { - "@0x/dev-utils": "^1.0.19", - "@0x/tslint-config": "^1.0.10", - "@0x/types": "^1.3.0", + "@0x/dev-utils": "^1.0.21", + "@0x/tslint-config": "^2.0.0", + "@0x/types": "^1.4.1", "@types/yargs": "^10.0.0", "make-promises-safe": "^1.1.0", "npm-run-all": "^4.1.2", @@ -39,18 +39,18 @@ "yargs": "^10.0.3" }, "dependencies": { - "@0x/abi-gen-wrappers": "^2.0.0", - "@0x/base-contract": "^3.0.8", + "@0x/abi-gen-wrappers": "^2.0.2", + "@0x/base-contract": "^3.0.10", "@0x/contract-addresses": "^2.0.0", "@0x/contract-artifacts": "^1.1.2", - "@0x/order-utils": "^3.0.4", - "@0x/sol-compiler": "^1.1.14", - "@0x/subproviders": "^2.1.6", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "@0x/order-utils": "^3.0.7", + "@0x/sol-compiler": "^1.1.16", + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@ledgerhq/hw-app-eth": "^4.3.0", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "ethers": "~4.0.4", "lodash": "^4.17.5" }, diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index 9af18a6b7..0483e87c8 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.14", + "version": "1.0.15", "engines": { "node": ">=6.12" }, diff --git a/packages/monorepo-scripts/src/test_installation.ts b/packages/monorepo-scripts/src/test_installation.ts index 96875d0f9..5ae13b198 100644 --- a/packages/monorepo-scripts/src/test_installation.ts +++ b/packages/monorepo-scripts/src/test_installation.ts @@ -98,7 +98,7 @@ async function testInstallPackageAsync( const lastChangelogVersion = JSON.parse(fs.readFileSync(changelogPath).toString())[0].version; const packageName = installablePackage.packageJson.name; utils.log(`Testing ${packageName}@${lastChangelogVersion}`); - const packageDirName = path.join(...(packageName + '-test').split('/')); + const packageDirName = path.join(...`${packageName}-test`.split('/')); // NOTE(fabio): The `testDirectory` needs to be somewhere **outside** the monorepo root directory. // Otherwise, it will have access to the hoisted `node_modules` directory and the Typescript missing // type errors will not be caught. diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 6c8fd6239..11889b92e 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,5 +1,33 @@ [ { + "version": "3.0.7", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "3.0.6", + "changes": [ + { + "note": "Fix bug in wallet signature type verification", + "pr": 1414 + } + ], + "timestamp": 1544570656 + }, + { + "timestamp": 1544482891, + "version": "3.0.5", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { "timestamp": 1543401373, "version": "3.0.4", "changes": [ diff --git a/packages/order-utils/CHANGELOG.md b/packages/order-utils/CHANGELOG.md index 5eae590b5..bc8b48767 100644 --- a/packages/order-utils/CHANGELOG.md +++ b/packages/order-utils/CHANGELOG.md @@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.7 - _December 13, 2018_ + + * Dependencies updated + +## v3.0.6 - _December 11, 2018_ + + * Fix bug in wallet signature type verification (#1414) + +## v3.0.5 - _December 10, 2018_ + + * Dependencies updated + ## v3.0.4 - _November 28, 2018_ * Dependencies updated diff --git a/packages/order-utils/package.json b/packages/order-utils/package.json index 50229dafb..400c9b66f 100644 --- a/packages/order-utils/package.json +++ b/packages/order-utils/package.json @@ -1,6 +1,6 @@ { "name": "@0x/order-utils", - "version": "3.0.4", + "version": "3.0.7", "engines": { "node": ">=6.12" }, @@ -35,8 +35,8 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md", "devDependencies": { - "@0x/dev-utils": "^1.0.19", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/tslint-config": "^2.0.0", "@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": "^2.0.0", - "@0x/assert": "^1.0.18", - "@0x/base-contract": "^3.0.8", + "@0x/abi-gen-wrappers": "^2.0.2", + "@0x/assert": "^1.0.20", + "@0x/base-contract": "^3.0.10", "@0x/contract-artifacts": "^1.1.2", - "@0x/json-schemas": "^2.1.2", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "@0x/json-schemas": "^2.1.4", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/node": "*", "bn.js": "^4.11.8", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "ethereumjs-abi": "0.6.5", "ethereumjs-util": "^5.1.1", "ethers": "~4.0.4", diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 96d90e21a..131144d48 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -115,7 +115,7 @@ export const signatureUtils = { assert.isHexString('signature', signature); assert.isETHAddressHex('signerAddress', signerAddress); // tslint:disable-next-line:custom-no-magic-numbers - const signatureWithoutType = signature.slice(-2); + const signatureWithoutType = signature.slice(0, -2); const walletContract = new IWalletContract(artifacts.IWallet.compilerOutput.abi, signerAddress, provider); const isValid = await walletContract.isValidSignature.callAsync(data, signatureWithoutType); return isValid; diff --git a/packages/order-utils/test/order_hash_test.ts b/packages/order-utils/test/order_hash_test.ts index a85d4c81a..30fb15a37 100644 --- a/packages/order-utils/test/order_hash_test.ts +++ b/packages/order-utils/test/order_hash_test.ts @@ -70,7 +70,7 @@ describe('Order hashing', () => { }); it('returns true if order hash is correct', () => { const orderHashLength = 65; - const isValid = orderHashUtils.isValidOrderHash('0x' + Array(orderHashLength).join('0')); + const isValid = orderHashUtils.isValidOrderHash(`0x${Array(orderHashLength).join('0')}`); expect(isValid).to.be.true(); }); }); diff --git a/packages/order-watcher/CHANGELOG.json b/packages/order-watcher/CHANGELOG.json index 4e56dc400..c1fd8d4a9 100644 --- a/packages/order-watcher/CHANGELOG.json +++ b/packages/order-watcher/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "2.2.8", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "2.2.7", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "2.2.6", "changes": [ diff --git a/packages/order-watcher/CHANGELOG.md b/packages/order-watcher/CHANGELOG.md index 37b4a7438..4e49b4637 100644 --- a/packages/order-watcher/CHANGELOG.md +++ b/packages/order-watcher/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.2.8 - _December 13, 2018_ + + * Dependencies updated + +## v2.2.7 - _December 11, 2018_ + + * Dependencies updated + ## v2.2.6 - _November 28, 2018_ * Dependencies updated diff --git a/packages/order-watcher/package.json b/packages/order-watcher/package.json index cfaf5d724..16a46294e 100644 --- a/packages/order-watcher/package.json +++ b/packages/order-watcher/package.json @@ -1,6 +1,6 @@ { "name": "@0x/order-watcher", - "version": "2.2.6", + "version": "2.2.8", "description": "An order watcher daemon that watches for order validity", "keywords": [ "0x", @@ -33,9 +33,9 @@ "node": ">=6.0.0" }, "devDependencies": { - "@0x/dev-utils": "^1.0.19", - "@0x/migrations": "^2.2.0", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/migrations": "^2.2.2", + "@0x/tslint-config": "^2.0.0", "@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": "^2.0.0", - "@0x/assert": "^1.0.18", - "@0x/base-contract": "^3.0.8", + "@0x/abi-gen-wrappers": "^2.0.2", + "@0x/assert": "^1.0.20", + "@0x/base-contract": "^3.0.10", "@0x/contract-addresses": "^2.0.0", "@0x/contract-artifacts": "^1.1.2", - "@0x/contract-wrappers": "^4.1.1", - "@0x/fill-scenarios": "^1.0.14", - "@0x/json-schemas": "^2.1.2", - "@0x/order-utils": "^3.0.4", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "@0x/contract-wrappers": "^4.1.3", + "@0x/fill-scenarios": "^1.0.16", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "bintrees": "^1.0.2", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "ethereumjs-blockstream": "6.0.0", "ethers": "~4.0.4", "lodash": "^4.17.5", diff --git a/packages/pipeline/migrations/1544131464368-CreateERC20ApprovalEvents.ts b/packages/pipeline/migrations/1544131464368-CreateERC20ApprovalEvents.ts new file mode 100644 index 000000000..2e84e0ec8 --- /dev/null +++ b/packages/pipeline/migrations/1544131464368-CreateERC20ApprovalEvents.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner, Table } from 'typeorm'; + +const erc20ApprovalEvents = new Table({ + name: 'raw.erc20_approval_events', + columns: [ + { name: 'token_address', type: 'varchar(42)', isPrimary: true }, + { name: 'log_index', type: 'integer', isPrimary: true }, + { name: 'block_number', type: 'bigint', isPrimary: true }, + + { name: 'raw_data', type: 'varchar' }, + { name: 'transaction_hash', type: 'varchar' }, + { name: 'owner_address', type: 'varchar(42)' }, + { name: 'spender_address', type: 'varchar(42)' }, + { name: 'amount', type: 'numeric' }, + ], +}); + +export class CreateERC20TokenApprovalEvents1544131464368 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise<any> { + await queryRunner.createTable(erc20ApprovalEvents); + } + + public async down(queryRunner: QueryRunner): Promise<any> { + await queryRunner.dropTable(erc20ApprovalEvents); + } +} diff --git a/packages/pipeline/migrations/1544131658904-TokenOrderbookSnapshotAddOrderType.ts b/packages/pipeline/migrations/1544131658904-TokenOrderbookSnapshotAddOrderType.ts new file mode 100644 index 000000000..a501ec6d8 --- /dev/null +++ b/packages/pipeline/migrations/1544131658904-TokenOrderbookSnapshotAddOrderType.ts @@ -0,0 +1,33 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TokenOrderbookSnapshotAddOrderType1544131658904 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise<any> { + await queryRunner.query( + `ALTER TABLE raw.token_orderbook_snapshots + DROP CONSTRAINT "PK_8a16487e7cb6862ec5a84ed3495", + ADD PRIMARY KEY (observed_timestamp, source, order_type, price, base_asset_symbol, quote_asset_symbol); + `, + ); + await queryRunner.query( + `ALTER TABLE raw.token_orderbook_snapshots + ALTER COLUMN quote_asset_address DROP NOT NULL, + ALTER COLUMN base_asset_address DROP NOT NULL; + `, + ); + } + + public async down(queryRunner: QueryRunner): Promise<any> { + await queryRunner.query( + `ALTER TABLE raw.token_orderbook_snapshots + ALTER COLUMN quote_asset_address SET NOT NULL, + ALTER COLUMN base_asset_address SET NOT NULL; + `, + ); + await queryRunner.query( + `ALTER TABLE raw.token_orderbook_snapshots + DROP CONSTRAINT token_orderbook_snapshots_pkey, + ADD CONSTRAINT "PK_8a16487e7cb6862ec5a84ed3495" PRIMARY KEY (observed_timestamp, source, price, base_asset_symbol, quote_asset_symbol); + `, + ); + } +} diff --git a/packages/pipeline/package.json b/packages/pipeline/package.json index 0539618d4..a40f3d21c 100644 --- a/packages/pipeline/package.json +++ b/packages/pipeline/package.json @@ -1,6 +1,6 @@ { "name": "@0x/pipeline", - "version": "1.0.0", + "version": "1.0.2", "private": true, "description": "Data pipeline for offline analysis", "scripts": { @@ -27,7 +27,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^2.0.0", "@types/axios": "^0.14.0", "@types/ramda": "^0.25.38", "chai": "^4.1.2", @@ -39,22 +39,23 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/connect": "^3.0.2", + "@0x/connect": "^3.0.10", + "@0x/contract-addresses": "^2.0.0", "@0x/contract-artifacts": "^1.0.1", "@0x/contract-wrappers": "^3.0.0", - "@0x/dev-utils": "^1.0.13", + "@0x/dev-utils": "^1.0.21", "@0x/order-utils": "^2.0.0", - "@0x/subproviders": "^2.1.0", - "@0x/types": "^1.2.0", - "@0x/utils": "^2.0.3", - "@0x/web3-wrapper": "^3.1.0", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/dockerode": "^2.5.9", "@types/p-limit": "^2.0.0", "async-parallel": "^1.2.3", "axios": "^0.18.0", + "bottleneck": "^2.13.2", "dockerode": "^2.5.7", - "ethereum-types": "^1.0.6", - "p-limit": "^2.0.0", + "ethereum-types": "^1.1.4", "pg": "^7.5.0", "prettier": "^1.15.3", "ramda": "^0.25.0", diff --git a/packages/pipeline/src/data_sources/bloxy/index.ts b/packages/pipeline/src/data_sources/bloxy/index.ts index 31cd5bfd6..94468d25a 100644 --- a/packages/pipeline/src/data_sources/bloxy/index.ts +++ b/packages/pipeline/src/data_sources/bloxy/index.ts @@ -116,7 +116,7 @@ export class BloxySource { }, }); if (isError(resp.data)) { - throw new Error('Error in Bloxy API response: ' + resp.data.error); + throw new Error(`Error in Bloxy API response: ${resp.data.error}`); } return resp.data; } diff --git a/packages/pipeline/src/data_sources/contract-wrappers/erc20_events.ts b/packages/pipeline/src/data_sources/contract-wrappers/erc20_events.ts new file mode 100644 index 000000000..e0098122f --- /dev/null +++ b/packages/pipeline/src/data_sources/contract-wrappers/erc20_events.ts @@ -0,0 +1,45 @@ +import { + ContractWrappers, + ERC20TokenApprovalEventArgs, + ERC20TokenEvents, + ERC20TokenWrapper, +} from '@0x/contract-wrappers'; +import { Web3ProviderEngine } from '@0x/subproviders'; +import { LogWithDecodedArgs } from 'ethereum-types'; + +import { GetEventsFunc, getEventsWithPaginationAsync } from './utils'; + +export class ERC20EventsSource { + private readonly _erc20Wrapper: ERC20TokenWrapper; + private readonly _tokenAddress: string; + constructor(provider: Web3ProviderEngine, networkId: number, tokenAddress: string) { + const contractWrappers = new ContractWrappers(provider, { networkId }); + this._erc20Wrapper = contractWrappers.erc20Token; + this._tokenAddress = tokenAddress; + } + + public async getApprovalEventsAsync( + startBlock: number, + endBlock: number, + ): Promise<Array<LogWithDecodedArgs<ERC20TokenApprovalEventArgs>>> { + return getEventsWithPaginationAsync( + this._getApprovalEventsForRangeAsync.bind(this) as GetEventsFunc<ERC20TokenApprovalEventArgs>, + startBlock, + endBlock, + ); + } + + // Gets all approval events of for a specific sub-range. This getter + // function will be called during each step of pagination. + private async _getApprovalEventsForRangeAsync( + fromBlock: number, + toBlock: number, + ): Promise<Array<LogWithDecodedArgs<ERC20TokenApprovalEventArgs>>> { + return this._erc20Wrapper.getLogsAsync<ERC20TokenApprovalEventArgs>( + this._tokenAddress, + ERC20TokenEvents.Approval, + { fromBlock, toBlock }, + {}, + ); + } +} diff --git a/packages/pipeline/src/data_sources/contract-wrappers/exchange_events.ts b/packages/pipeline/src/data_sources/contract-wrappers/exchange_events.ts index 1717eb8b3..58691e2ab 100644 --- a/packages/pipeline/src/data_sources/contract-wrappers/exchange_events.ts +++ b/packages/pipeline/src/data_sources/contract-wrappers/exchange_events.ts @@ -8,78 +8,52 @@ import { ExchangeWrapper, } from '@0x/contract-wrappers'; import { Web3ProviderEngine } from '@0x/subproviders'; -import { Web3Wrapper } from '@0x/web3-wrapper'; import { LogWithDecodedArgs } from 'ethereum-types'; -import { EXCHANGE_START_BLOCK } from '../../utils'; - -const BLOCK_FINALITY_THRESHOLD = 10; // When to consider blocks as final. Used to compute default toBlock. -const NUM_BLOCKS_PER_QUERY = 20000; // Number of blocks to query for events at a time. +import { GetEventsFunc, getEventsWithPaginationAsync } from './utils'; export class ExchangeEventsSource { private readonly _exchangeWrapper: ExchangeWrapper; - private readonly _web3Wrapper: Web3Wrapper; constructor(provider: Web3ProviderEngine, networkId: number) { - this._web3Wrapper = new Web3Wrapper(provider); const contractWrappers = new ContractWrappers(provider, { networkId }); this._exchangeWrapper = contractWrappers.exchange; } public async getFillEventsAsync( - fromBlock?: number, - toBlock?: number, + startBlock: number, + endBlock: number, ): Promise<Array<LogWithDecodedArgs<ExchangeFillEventArgs>>> { - return this._getEventsAsync<ExchangeFillEventArgs>(ExchangeEvents.Fill, fromBlock, toBlock); + const getFillEventsForRangeAsync = this._makeGetterFuncForEventType<ExchangeFillEventArgs>(ExchangeEvents.Fill); + return getEventsWithPaginationAsync(getFillEventsForRangeAsync, startBlock, endBlock); } public async getCancelEventsAsync( - fromBlock?: number, - toBlock?: number, + startBlock: number, + endBlock: number, ): Promise<Array<LogWithDecodedArgs<ExchangeCancelEventArgs>>> { - return this._getEventsAsync<ExchangeCancelEventArgs>(ExchangeEvents.Cancel, fromBlock, toBlock); + const getCancelEventsForRangeAsync = this._makeGetterFuncForEventType<ExchangeCancelEventArgs>( + ExchangeEvents.Cancel, + ); + return getEventsWithPaginationAsync(getCancelEventsForRangeAsync, startBlock, endBlock); } public async getCancelUpToEventsAsync( - fromBlock?: number, - toBlock?: number, + startBlock: number, + endBlock: number, ): Promise<Array<LogWithDecodedArgs<ExchangeCancelUpToEventArgs>>> { - return this._getEventsAsync<ExchangeCancelUpToEventArgs>(ExchangeEvents.CancelUpTo, fromBlock, toBlock); - } - - private async _getEventsAsync<ArgsType extends ExchangeEventArgs>( - eventName: ExchangeEvents, - fromBlock: number = EXCHANGE_START_BLOCK, - toBlock?: number, - ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { - const calculatedToBlock = - toBlock === undefined - ? (await this._web3Wrapper.getBlockNumberAsync()) - BLOCK_FINALITY_THRESHOLD - : toBlock; - let events: Array<LogWithDecodedArgs<ArgsType>> = []; - for (let currFromBlock = fromBlock; currFromBlock <= calculatedToBlock; currFromBlock += NUM_BLOCKS_PER_QUERY) { - events = events.concat( - await this._getEventsForRangeAsync<ArgsType>( - eventName, - currFromBlock, - Math.min(currFromBlock + NUM_BLOCKS_PER_QUERY - 1, calculatedToBlock), - ), - ); - } - return events; + const getCancelUpToEventsForRangeAsync = this._makeGetterFuncForEventType<ExchangeCancelUpToEventArgs>( + ExchangeEvents.CancelUpTo, + ); + return getEventsWithPaginationAsync(getCancelUpToEventsForRangeAsync, startBlock, endBlock); } - private async _getEventsForRangeAsync<ArgsType extends ExchangeEventArgs>( - eventName: ExchangeEvents, - fromBlock: number, - toBlock: number, - ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { - return this._exchangeWrapper.getLogsAsync<ArgsType>( - eventName, - { - fromBlock, - toBlock, - }, - {}, - ); + // Returns a getter function which gets all events of a specific type for a + // specific sub-range. This getter function will be called during each step + // of pagination. + private _makeGetterFuncForEventType<ArgsType extends ExchangeEventArgs>( + eventType: ExchangeEvents, + ): GetEventsFunc<ArgsType> { + return async (fromBlock: number, toBlock: number) => + this._exchangeWrapper.getLogsAsync<ArgsType>(eventType, { fromBlock, toBlock }, {}); } } diff --git a/packages/pipeline/src/data_sources/contract-wrappers/utils.ts b/packages/pipeline/src/data_sources/contract-wrappers/utils.ts new file mode 100644 index 000000000..67660a37e --- /dev/null +++ b/packages/pipeline/src/data_sources/contract-wrappers/utils.ts @@ -0,0 +1,67 @@ +import { DecodedLogArgs, LogWithDecodedArgs } from 'ethereum-types'; + +const NUM_BLOCKS_PER_QUERY = 10000; // Number of blocks to query for events at a time. +const NUM_RETRIES = 3; // Number of retries if a request fails or times out. + +export type GetEventsFunc<ArgsType extends DecodedLogArgs> = ( + fromBlock: number, + toBlock: number, +) => Promise<Array<LogWithDecodedArgs<ArgsType>>>; + +/** + * Gets all events between the given startBlock and endBlock by querying for + * NUM_BLOCKS_PER_QUERY at a time. Accepts a getter function in order to + * maximize code re-use and allow for getting different types of events for + * different contracts. If the getter function throws with a retryable error, + * it will automatically be retried up to NUM_RETRIES times. + * @param getEventsAsync A getter function which will be called for each step during pagination. + * @param startBlock The start of the entire block range to get events for. + * @param endBlock The end of the entire block range to get events for. + */ +export async function getEventsWithPaginationAsync<ArgsType extends DecodedLogArgs>( + getEventsAsync: GetEventsFunc<ArgsType>, + startBlock: number, + endBlock: number, +): Promise<Array<LogWithDecodedArgs<ArgsType>>> { + let events: Array<LogWithDecodedArgs<ArgsType>> = []; + for (let fromBlock = startBlock; fromBlock <= endBlock; fromBlock += NUM_BLOCKS_PER_QUERY) { + const toBlock = Math.min(fromBlock + NUM_BLOCKS_PER_QUERY - 1, endBlock); + const eventsInRange = await _getEventsWithRetriesAsync(getEventsAsync, NUM_RETRIES, fromBlock, toBlock); + events = events.concat(eventsInRange); + } + return events; +} + +/** + * Calls the getEventsAsync function and retries up to numRetries times if it + * throws with an error that is considered retryable. + * @param getEventsAsync a function that will be called on each iteration. + * @param numRetries the maximum number times to retry getEventsAsync if it fails with a retryable error. + * @param fromBlock the start of the sub-range of blocks we are getting events for. + * @param toBlock the end of the sub-range of blocks we are getting events for. + */ +export async function _getEventsWithRetriesAsync<ArgsType extends DecodedLogArgs>( + getEventsAsync: GetEventsFunc<ArgsType>, + numRetries: number, + fromBlock: number, + toBlock: number, +): Promise<Array<LogWithDecodedArgs<ArgsType>>> { + let eventsInRange: Array<LogWithDecodedArgs<ArgsType>> = []; + for (let i = 0; i <= numRetries; i++) { + try { + eventsInRange = await getEventsAsync(fromBlock, toBlock); + } catch (err) { + if (isErrorRetryable(err) && i < numRetries) { + continue; + } else { + throw err; + } + } + break; + } + return eventsInRange; +} + +function isErrorRetryable(err: Error): boolean { + return err.message.includes('network timeout'); +} diff --git a/packages/pipeline/src/data_sources/idex/index.ts b/packages/pipeline/src/data_sources/idex/index.ts new file mode 100644 index 000000000..c1e53c08d --- /dev/null +++ b/packages/pipeline/src/data_sources/idex/index.ts @@ -0,0 +1,82 @@ +import { fetchAsync } from '@0x/utils'; + +const IDEX_BASE_URL = 'https://api.idex.market'; +const MARKETS_URL = `${IDEX_BASE_URL}/returnTicker`; +const ORDERBOOK_URL = `${IDEX_BASE_URL}/returnOrderBook`; +const MAX_ORDER_COUNT = 100; // Maximum based on https://github.com/AuroraDAO/idex-api-docs#returnorderbook +export const IDEX_SOURCE = 'idex'; + +export interface IdexMarketsResponse { + [marketName: string]: IdexMarket; +} + +export interface IdexMarket { + last: string; + high: string; + low: string; + lowestAsk: string; + highestBid: string; + percentChange: string; + baseVolume: string; + quoteVolume: string; +} + +export interface IdexOrderbook { + asks: IdexOrder[]; + bids: IdexOrder[]; +} + +export interface IdexOrder { + price: string; + amount: string; + total: string; + orderHash: string; + params: IdexOrderParam; +} + +export interface IdexOrderParam { + tokenBuy: string; + buySymbol: string; + buyPrecision: number; + amountBuy: string; + tokenSell: string; + sellSymbol: string; + sellPrecision: number; + amountSell: string; + expires: number; + nonce: number; + user: string; +} + +// tslint:disable:prefer-function-over-method +// ^ Keep consistency with other sources and help logical organization +export class IdexSource { + /** + * Call Idex API to find out which markets they are maintaining orderbooks for. + */ + public async getMarketsAsync(): Promise<string[]> { + const params = { method: 'POST' }; + const resp = await fetchAsync(MARKETS_URL, params); + const respJson: IdexMarketsResponse = await resp.json(); + const markets: string[] = Object.keys(respJson); + return markets; + } + + /** + * Retrieve orderbook from Idex API for a given market. + * @param marketId String identifying the market we want data for. Eg. 'REP_AUG' + */ + public async getMarketOrderbookAsync(marketId: string): Promise<IdexOrderbook> { + const params = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + market: marketId, + count: MAX_ORDER_COUNT, + }), + }; + const resp = await fetchAsync(ORDERBOOK_URL, params); + const respJson: IdexOrderbook = await resp.json(); + return respJson; + } +} diff --git a/packages/pipeline/src/data_sources/oasis/index.ts b/packages/pipeline/src/data_sources/oasis/index.ts new file mode 100644 index 000000000..3b30e9dfd --- /dev/null +++ b/packages/pipeline/src/data_sources/oasis/index.ts @@ -0,0 +1,103 @@ +import { fetchAsync } from '@0x/utils'; + +const OASIS_BASE_URL = 'https://data.makerdao.com/v1'; +const OASIS_MARKET_QUERY = `query { + oasisMarkets(period: "1 week") { + nodes { + id + base + quote + buyVol + sellVol + price + high + low + } + } +}`; +const OASIS_ORDERBOOK_QUERY = `query ($market: String!) { + allOasisOrders(condition: { market: $market }) { + totalCount + nodes { + market + offerId + price + amount + act + } + } +}`; +export const OASIS_SOURCE = 'oasis'; + +export interface OasisMarket { + id: string; // market symbol e.g MKRDAI + base: string; // base symbol e.g MKR + quote: string; // quote symbol e.g DAI + buyVol: number; // total buy volume (base) + sellVol: number; // total sell volume (base) + price: number; // volume weighted price (quote) + high: number; // max sell price + low: number; // min buy price +} + +export interface OasisMarketResponse { + data: { + oasisMarkets: { + nodes: OasisMarket[]; + }; + }; +} + +export interface OasisOrder { + offerId: number; // Offer Id + market: string; // Market symbol (base/quote) + price: string; // Offer price (quote) + amount: string; // Offer amount (base) + act: string; // Action (ask|bid) +} + +export interface OasisOrderbookResponse { + data: { + allOasisOrders: { + totalCount: number; + nodes: OasisOrder[]; + }; + }; +} + +// tslint:disable:prefer-function-over-method +// ^ Keep consistency with other sources and help logical organization +export class OasisSource { + /** + * Call Ddex API to find out which markets they are maintaining orderbooks for. + */ + public async getActiveMarketsAsync(): Promise<OasisMarket[]> { + const params = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: OASIS_MARKET_QUERY }), + }; + const resp = await fetchAsync(OASIS_BASE_URL, params); + const respJson: OasisMarketResponse = await resp.json(); + const markets = respJson.data.oasisMarkets.nodes; + return markets; + } + + /** + * Retrieve orderbook from Oasis API for a given market. + * @param marketId String identifying the market we want data for. Eg. 'REPAUG'. + */ + public async getMarketOrderbookAsync(marketId: string): Promise<OasisOrder[]> { + const input = { + market: marketId, + }; + const params = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: OASIS_ORDERBOOK_QUERY, variables: input }), + }; + const resp = await fetchAsync(OASIS_BASE_URL, params); + const respJson: OasisOrderbookResponse = await resp.json(); + return respJson.data.allOasisOrders.nodes; + } +} diff --git a/packages/pipeline/src/data_sources/ohlcv_external/crypto_compare.ts b/packages/pipeline/src/data_sources/ohlcv_external/crypto_compare.ts index 8804c34d0..85042501b 100644 --- a/packages/pipeline/src/data_sources/ohlcv_external/crypto_compare.ts +++ b/packages/pipeline/src/data_sources/ohlcv_external/crypto_compare.ts @@ -1,6 +1,6 @@ // tslint:disable:no-duplicate-imports import { fetchAsync } from '@0x/utils'; -import promiseLimit = require('p-limit'); +import Bottleneck from 'bottleneck'; import { stringify } from 'querystring'; import * as R from 'ramda'; @@ -33,43 +33,41 @@ export interface CryptoCompareOHLCVParams { toTs?: number; } -const ONE_WEEK = 7 * 24 * 60 * 60 * 1000; // tslint:disable-line:custom-no-magic-numbers const ONE_HOUR = 60 * 60 * 1000; // tslint:disable-line:custom-no-magic-numbers const ONE_SECOND = 1000; const ONE_HOUR_AGO = new Date().getTime() - ONE_HOUR; const HTTP_OK_STATUS = 200; const CRYPTO_COMPARE_VALID_EMPTY_RESPONSE_TYPE = 96; +const MAX_PAGE_SIZE = 2000; export class CryptoCompareOHLCVSource { - public readonly interval = ONE_WEEK; // the hourly API returns data for one week at a time - public readonly default_exchange = 'CCCAGG'; public readonly intervalBetweenRecords = ONE_HOUR; + public readonly defaultExchange = 'CCCAGG'; + public readonly interval = this.intervalBetweenRecords * MAX_PAGE_SIZE; // the hourly API returns data for one interval at a time private readonly _url: string = 'https://min-api.cryptocompare.com/data/histohour?'; // rate-limit for all API calls through this class instance - private readonly _promiseLimit: (fetchFn: () => Promise<Response>) => Promise<Response>; - constructor(maxConcurrentRequests: number = 50) { - this._promiseLimit = promiseLimit(maxConcurrentRequests); + private readonly _limiter: Bottleneck; + constructor(maxReqsPerSecond: number) { + this._limiter = new Bottleneck({ + minTime: ONE_SECOND / maxReqsPerSecond, + reservoir: 30, + reservoirRefreshAmount: 30, + reservoirRefreshInterval: ONE_SECOND, + }); } // gets OHLCV records starting from pair.latest public async getHourlyOHLCVAsync(pair: TradingPair): Promise<CryptoCompareOHLCVRecord[]> { const params = { - e: this.default_exchange, + e: this.defaultExchange, fsym: pair.fromSymbol, tsym: pair.toSymbol, + limit: MAX_PAGE_SIZE, toTs: Math.floor((pair.latestSavedTime + this.interval) / ONE_SECOND), // CryptoCompare uses timestamp in seconds. not ms }; const url = this._url + stringify(params); - - // go through the instance-wide rate-limit - const fetchPromise: Promise<Response> = this._promiseLimit(() => { - // tslint:disable-next-line:no-console - console.log(`Scraping Crypto Compare at ${url}`); - return fetchAsync(url); - }); - - const response = await Promise.resolve(fetchPromise); + const response = await this._limiter.schedule(() => fetchAsync(url)); if (response.status !== HTTP_OK_STATUS) { throw new Error(`HTTP error while scraping Crypto Compare: [${response}]`); } diff --git a/packages/pipeline/src/data_sources/paradex/index.ts b/packages/pipeline/src/data_sources/paradex/index.ts index 69a03d553..46d448f4b 100644 --- a/packages/pipeline/src/data_sources/paradex/index.ts +++ b/packages/pipeline/src/data_sources/paradex/index.ts @@ -1,9 +1,9 @@ import { fetchAsync, logUtils } from '@0x/utils'; const PARADEX_BASE_URL = 'https://api.paradex.io/consumer/v0'; -const ACTIVE_MARKETS_URL = PARADEX_BASE_URL + '/markets'; -const ORDERBOOK_ENDPOINT = PARADEX_BASE_URL + '/orderbook'; -const TOKEN_INFO_ENDPOINT = PARADEX_BASE_URL + '/tokens'; +const ACTIVE_MARKETS_URL = `${PARADEX_BASE_URL}/markets`; +const ORDERBOOK_ENDPOINT = `${PARADEX_BASE_URL}/orderbook`; +const TOKEN_INFO_ENDPOINT = `${PARADEX_BASE_URL}/tokens`; export const PARADEX_SOURCE = 'paradex'; export type ParadexActiveMarketsResponse = ParadexMarket[]; diff --git a/packages/pipeline/src/entities/erc20_approval_event.ts b/packages/pipeline/src/entities/erc20_approval_event.ts new file mode 100644 index 000000000..69cdfcb0b --- /dev/null +++ b/packages/pipeline/src/entities/erc20_approval_event.ts @@ -0,0 +1,26 @@ +import { BigNumber } from '@0x/utils'; +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { bigNumberTransformer, numberToBigIntTransformer } from '../utils'; + +@Entity({ name: 'erc20_approval_events', schema: 'raw' }) +export class ERC20ApprovalEvent { + @PrimaryColumn({ name: 'token_address' }) + public tokenAddress!: string; + @PrimaryColumn({ name: 'log_index' }) + public logIndex!: number; + @PrimaryColumn({ name: 'block_number', transformer: numberToBigIntTransformer }) + public blockNumber!: number; + + @Column({ name: 'raw_data' }) + public rawData!: string; + + @Column({ name: 'transaction_hash' }) + public transactionHash!: string; + @Column({ name: 'owner_address' }) + public ownerAddress!: string; + @Column({ name: 'spender_address' }) + public spenderAddress!: string; + @Column({ name: 'amount', type: 'numeric', transformer: bigNumberTransformer }) + public amount!: BigNumber; +} diff --git a/packages/pipeline/src/entities/index.ts b/packages/pipeline/src/entities/index.ts index db0814e38..cc3de78bb 100644 --- a/packages/pipeline/src/entities/index.ts +++ b/packages/pipeline/src/entities/index.ts @@ -14,5 +14,6 @@ export { SraOrdersObservedTimeStamp, createObservedTimestampForOrder } from './s export { TokenMetadata } from './token_metadata'; export { TokenOrderbookSnapshot } from './token_order'; export { Transaction } from './transaction'; +export { ERC20ApprovalEvent } from './erc20_approval_event'; export type ExchangeEvent = ExchangeFillEvent | ExchangeCancelEvent | ExchangeCancelUpToEvent; diff --git a/packages/pipeline/src/entities/token_order.ts b/packages/pipeline/src/entities/token_order.ts index 557705767..4b8f0abc3 100644 --- a/packages/pipeline/src/entities/token_order.ts +++ b/packages/pipeline/src/entities/token_order.ts @@ -10,20 +10,20 @@ export class TokenOrderbookSnapshot { public observedTimestamp!: number; @PrimaryColumn({ name: 'source' }) public source!: string; - @Column({ name: 'order_type' }) + @PrimaryColumn({ name: 'order_type' }) public orderType!: OrderType; @PrimaryColumn({ name: 'price', type: 'numeric', transformer: bigNumberTransformer }) public price!: BigNumber; @PrimaryColumn({ name: 'base_asset_symbol' }) public baseAssetSymbol!: string; - @Column({ name: 'base_asset_address' }) - public baseAssetAddress!: string; + @Column({ nullable: true, type: String, name: 'base_asset_address' }) + public baseAssetAddress!: string | null; @Column({ name: 'base_volume', type: 'numeric', transformer: bigNumberTransformer }) public baseVolume!: BigNumber; @PrimaryColumn({ name: 'quote_asset_symbol' }) public quoteAssetSymbol!: string; - @Column({ name: 'quote_asset_address' }) - public quoteAssetAddress!: string; + @Column({ nullable: true, type: String, name: 'quote_asset_address' }) + public quoteAssetAddress!: string | null; @Column({ name: 'quote_volume', type: 'numeric', transformer: bigNumberTransformer }) public quoteVolume!: BigNumber; } diff --git a/packages/pipeline/src/ormconfig.ts b/packages/pipeline/src/ormconfig.ts index 9f7815b4e..fe11d81d5 100644 --- a/packages/pipeline/src/ormconfig.ts +++ b/packages/pipeline/src/ormconfig.ts @@ -3,6 +3,7 @@ import { ConnectionOptions } from 'typeorm'; import { Block, DexTrade, + ERC20ApprovalEvent, ExchangeCancelEvent, ExchangeCancelUpToEvent, ExchangeFillEvent, @@ -21,6 +22,7 @@ const entities = [ ExchangeCancelEvent, ExchangeCancelUpToEvent, ExchangeFillEvent, + ERC20ApprovalEvent, OHLCVExternal, Relayer, SraOrder, diff --git a/packages/pipeline/src/parsers/ddex_orders/index.ts b/packages/pipeline/src/parsers/ddex_orders/index.ts index 81132e8f0..d7b97efbe 100644 --- a/packages/pipeline/src/parsers/ddex_orders/index.ts +++ b/packages/pipeline/src/parsers/ddex_orders/index.ts @@ -1,7 +1,8 @@ import { BigNumber } from '@0x/utils'; -import * as R from 'ramda'; -import { DdexMarket, DdexOrder, DdexOrderbook } from '../../data_sources/ddex'; +import { aggregateOrders } from '../utils'; + +import { DdexMarket, DdexOrderbook } from '../../data_sources/ddex'; import { TokenOrderbookSnapshot as TokenOrder } from '../../entities'; import { OrderType } from '../../types'; @@ -28,19 +29,6 @@ export function parseDdexOrders( } /** - * Aggregates orders by price point for consistency with other exchanges. - * Querying the Ddex API at level 3 setting returns a breakdown of - * individual orders at each price point. Other exchanges only give total amount - * at each price point. Returns an array of <price, amount> tuples. - * @param ddexOrders A list of Ddex orders awaiting aggregation. - */ -export function aggregateOrders(ddexOrders: DdexOrder[]): Array<[string, BigNumber]> { - const sumAmount = (acc: BigNumber, order: DdexOrder): BigNumber => acc.plus(order.amount); - const aggregatedPricePoints = R.reduceBy(sumAmount, new BigNumber(0), R.prop('price'), ddexOrders); - return Object.entries(aggregatedPricePoints); -} - -/** * Parse a single aggregated Ddex order in order to form a tokenOrder entity * which can be saved into the database. * @param ddexMarket An object containing information about the market where these @@ -66,12 +54,14 @@ export function parseDdexOrder( tokenOrder.orderType = orderType; tokenOrder.price = price; - tokenOrder.baseAssetSymbol = ddexMarket.baseToken; - tokenOrder.baseAssetAddress = ddexMarket.baseTokenAddress; - tokenOrder.baseVolume = price.times(amount); + // ddex currently confuses quote and base assets. + // We switch them here to maintain our internal consistency. + tokenOrder.baseAssetSymbol = ddexMarket.quoteToken; + tokenOrder.baseAssetAddress = ddexMarket.quoteTokenAddress; + tokenOrder.baseVolume = amount; - tokenOrder.quoteAssetSymbol = ddexMarket.quoteToken; - tokenOrder.quoteAssetAddress = ddexMarket.quoteTokenAddress; - tokenOrder.quoteVolume = amount; + tokenOrder.quoteAssetSymbol = ddexMarket.baseToken; + tokenOrder.quoteAssetAddress = ddexMarket.baseTokenAddress; + tokenOrder.quoteVolume = price.times(amount); return tokenOrder; } diff --git a/packages/pipeline/src/parsers/events/erc20_events.ts b/packages/pipeline/src/parsers/events/erc20_events.ts new file mode 100644 index 000000000..caf9984d0 --- /dev/null +++ b/packages/pipeline/src/parsers/events/erc20_events.ts @@ -0,0 +1,34 @@ +import { ERC20TokenApprovalEventArgs } from '@0x/contract-wrappers'; +import { LogWithDecodedArgs } from 'ethereum-types'; +import * as R from 'ramda'; + +import { ERC20ApprovalEvent } from '../../entities'; + +/** + * Parses raw event logs for an ERC20 approval event and returns an array of + * ERC20ApprovalEvent entities. + * @param eventLogs Raw event logs (e.g. returned from contract-wrappers). + */ +export const parseERC20ApprovalEvents: ( + eventLogs: Array<LogWithDecodedArgs<ERC20TokenApprovalEventArgs>>, +) => ERC20ApprovalEvent[] = R.map(_convertToERC20ApprovalEvent); + +/** + * Converts a raw event log for an ERC20 approval event into an + * ERC20ApprovalEvent entity. + * @param eventLog Raw event log (e.g. returned from contract-wrappers). + */ +export function _convertToERC20ApprovalEvent( + eventLog: LogWithDecodedArgs<ERC20TokenApprovalEventArgs>, +): ERC20ApprovalEvent { + const erc20ApprovalEvent = new ERC20ApprovalEvent(); + erc20ApprovalEvent.tokenAddress = eventLog.address as string; + erc20ApprovalEvent.blockNumber = eventLog.blockNumber as number; + erc20ApprovalEvent.logIndex = eventLog.logIndex as number; + erc20ApprovalEvent.rawData = eventLog.data as string; + erc20ApprovalEvent.transactionHash = eventLog.transactionHash; + erc20ApprovalEvent.ownerAddress = eventLog.args._owner; + erc20ApprovalEvent.spenderAddress = eventLog.args._spender; + erc20ApprovalEvent.amount = eventLog.args._value; + return erc20ApprovalEvent; +} diff --git a/packages/pipeline/src/parsers/events/exchange_events.ts b/packages/pipeline/src/parsers/events/exchange_events.ts new file mode 100644 index 000000000..e18106c75 --- /dev/null +++ b/packages/pipeline/src/parsers/events/exchange_events.ts @@ -0,0 +1,133 @@ +import { ExchangeCancelEventArgs, ExchangeCancelUpToEventArgs, ExchangeFillEventArgs } from '@0x/contract-wrappers'; +import { assetDataUtils } from '@0x/order-utils'; +import { AssetProxyId, ERC721AssetData } from '@0x/types'; +import { LogWithDecodedArgs } from 'ethereum-types'; +import * as R from 'ramda'; + +import { ExchangeCancelEvent, ExchangeCancelUpToEvent, ExchangeFillEvent } from '../../entities'; +import { bigNumbertoStringOrNull } from '../../utils'; + +/** + * Parses raw event logs for a fill event and returns an array of + * ExchangeFillEvent entities. + * @param eventLogs Raw event logs (e.g. returned from contract-wrappers). + */ +export const parseExchangeFillEvents: ( + eventLogs: Array<LogWithDecodedArgs<ExchangeFillEventArgs>>, +) => ExchangeFillEvent[] = R.map(_convertToExchangeFillEvent); + +/** + * Parses raw event logs for a cancel event and returns an array of + * ExchangeCancelEvent entities. + * @param eventLogs Raw event logs (e.g. returned from contract-wrappers). + */ +export const parseExchangeCancelEvents: ( + eventLogs: Array<LogWithDecodedArgs<ExchangeCancelEventArgs>>, +) => ExchangeCancelEvent[] = R.map(_convertToExchangeCancelEvent); + +/** + * Parses raw event logs for a CancelUpTo event and returns an array of + * ExchangeCancelUpToEvent entities. + * @param eventLogs Raw event logs (e.g. returned from contract-wrappers). + */ +export const parseExchangeCancelUpToEvents: ( + eventLogs: Array<LogWithDecodedArgs<ExchangeCancelUpToEventArgs>>, +) => ExchangeCancelUpToEvent[] = R.map(_convertToExchangeCancelUpToEvent); + +/** + * Converts a raw event log for a fill event into an ExchangeFillEvent entity. + * @param eventLog Raw event log (e.g. returned from contract-wrappers). + */ +export function _convertToExchangeFillEvent(eventLog: LogWithDecodedArgs<ExchangeFillEventArgs>): ExchangeFillEvent { + const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.makerAssetData); + const makerAssetType = makerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721'; + const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.takerAssetData); + const takerAssetType = takerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721'; + const exchangeFillEvent = new ExchangeFillEvent(); + exchangeFillEvent.contractAddress = eventLog.address as string; + exchangeFillEvent.blockNumber = eventLog.blockNumber as number; + exchangeFillEvent.logIndex = eventLog.logIndex as number; + exchangeFillEvent.rawData = eventLog.data as string; + exchangeFillEvent.transactionHash = eventLog.transactionHash; + exchangeFillEvent.makerAddress = eventLog.args.makerAddress; + exchangeFillEvent.takerAddress = eventLog.args.takerAddress; + exchangeFillEvent.feeRecipientAddress = eventLog.args.feeRecipientAddress; + exchangeFillEvent.senderAddress = eventLog.args.senderAddress; + exchangeFillEvent.makerAssetFilledAmount = eventLog.args.makerAssetFilledAmount; + exchangeFillEvent.takerAssetFilledAmount = eventLog.args.takerAssetFilledAmount; + exchangeFillEvent.makerFeePaid = eventLog.args.makerFeePaid; + exchangeFillEvent.takerFeePaid = eventLog.args.takerFeePaid; + exchangeFillEvent.orderHash = eventLog.args.orderHash; + exchangeFillEvent.rawMakerAssetData = eventLog.args.makerAssetData; + exchangeFillEvent.makerAssetType = makerAssetType; + exchangeFillEvent.makerAssetProxyId = makerAssetData.assetProxyId; + exchangeFillEvent.makerTokenAddress = makerAssetData.tokenAddress; + // tslint has a false positive here. Type assertion is required. + // tslint:disable-next-line:no-unnecessary-type-assertion + exchangeFillEvent.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId); + exchangeFillEvent.rawTakerAssetData = eventLog.args.takerAssetData; + exchangeFillEvent.takerAssetType = takerAssetType; + exchangeFillEvent.takerAssetProxyId = takerAssetData.assetProxyId; + exchangeFillEvent.takerTokenAddress = takerAssetData.tokenAddress; + // tslint:disable-next-line:no-unnecessary-type-assertion + exchangeFillEvent.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId); + return exchangeFillEvent; +} + +/** + * Converts a raw event log for a cancel event into an ExchangeCancelEvent + * entity. + * @param eventLog Raw event log (e.g. returned from contract-wrappers). + */ +export function _convertToExchangeCancelEvent( + eventLog: LogWithDecodedArgs<ExchangeCancelEventArgs>, +): ExchangeCancelEvent { + const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.makerAssetData); + const makerAssetType = makerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721'; + const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.takerAssetData); + const takerAssetType = takerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721'; + const exchangeCancelEvent = new ExchangeCancelEvent(); + exchangeCancelEvent.contractAddress = eventLog.address as string; + exchangeCancelEvent.blockNumber = eventLog.blockNumber as number; + exchangeCancelEvent.logIndex = eventLog.logIndex as number; + exchangeCancelEvent.rawData = eventLog.data as string; + exchangeCancelEvent.transactionHash = eventLog.transactionHash; + exchangeCancelEvent.makerAddress = eventLog.args.makerAddress; + exchangeCancelEvent.takerAddress = eventLog.args.takerAddress; + exchangeCancelEvent.feeRecipientAddress = eventLog.args.feeRecipientAddress; + exchangeCancelEvent.senderAddress = eventLog.args.senderAddress; + exchangeCancelEvent.orderHash = eventLog.args.orderHash; + exchangeCancelEvent.rawMakerAssetData = eventLog.args.makerAssetData; + exchangeCancelEvent.makerAssetType = makerAssetType; + exchangeCancelEvent.makerAssetProxyId = makerAssetData.assetProxyId; + exchangeCancelEvent.makerTokenAddress = makerAssetData.tokenAddress; + // tslint:disable-next-line:no-unnecessary-type-assertion + exchangeCancelEvent.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId); + exchangeCancelEvent.rawTakerAssetData = eventLog.args.takerAssetData; + exchangeCancelEvent.takerAssetType = takerAssetType; + exchangeCancelEvent.takerAssetProxyId = takerAssetData.assetProxyId; + exchangeCancelEvent.takerTokenAddress = takerAssetData.tokenAddress; + // tslint:disable-next-line:no-unnecessary-type-assertion + exchangeCancelEvent.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId); + return exchangeCancelEvent; +} + +/** + * Converts a raw event log for a cancelUpTo event into an + * ExchangeCancelUpToEvent entity. + * @param eventLog Raw event log (e.g. returned from contract-wrappers). + */ +export function _convertToExchangeCancelUpToEvent( + eventLog: LogWithDecodedArgs<ExchangeCancelUpToEventArgs>, +): ExchangeCancelUpToEvent { + const exchangeCancelUpToEvent = new ExchangeCancelUpToEvent(); + exchangeCancelUpToEvent.contractAddress = eventLog.address as string; + exchangeCancelUpToEvent.blockNumber = eventLog.blockNumber as number; + exchangeCancelUpToEvent.logIndex = eventLog.logIndex as number; + exchangeCancelUpToEvent.rawData = eventLog.data as string; + exchangeCancelUpToEvent.transactionHash = eventLog.transactionHash; + exchangeCancelUpToEvent.makerAddress = eventLog.args.makerAddress; + exchangeCancelUpToEvent.senderAddress = eventLog.args.senderAddress; + exchangeCancelUpToEvent.orderEpoch = eventLog.args.orderEpoch; + return exchangeCancelUpToEvent; +} diff --git a/packages/pipeline/src/parsers/events/index.ts b/packages/pipeline/src/parsers/events/index.ts index e18106c75..3f9915e8b 100644 --- a/packages/pipeline/src/parsers/events/index.ts +++ b/packages/pipeline/src/parsers/events/index.ts @@ -1,133 +1,2 @@ -import { ExchangeCancelEventArgs, ExchangeCancelUpToEventArgs, ExchangeFillEventArgs } from '@0x/contract-wrappers'; -import { assetDataUtils } from '@0x/order-utils'; -import { AssetProxyId, ERC721AssetData } from '@0x/types'; -import { LogWithDecodedArgs } from 'ethereum-types'; -import * as R from 'ramda'; - -import { ExchangeCancelEvent, ExchangeCancelUpToEvent, ExchangeFillEvent } from '../../entities'; -import { bigNumbertoStringOrNull } from '../../utils'; - -/** - * Parses raw event logs for a fill event and returns an array of - * ExchangeFillEvent entities. - * @param eventLogs Raw event logs (e.g. returned from contract-wrappers). - */ -export const parseExchangeFillEvents: ( - eventLogs: Array<LogWithDecodedArgs<ExchangeFillEventArgs>>, -) => ExchangeFillEvent[] = R.map(_convertToExchangeFillEvent); - -/** - * Parses raw event logs for a cancel event and returns an array of - * ExchangeCancelEvent entities. - * @param eventLogs Raw event logs (e.g. returned from contract-wrappers). - */ -export const parseExchangeCancelEvents: ( - eventLogs: Array<LogWithDecodedArgs<ExchangeCancelEventArgs>>, -) => ExchangeCancelEvent[] = R.map(_convertToExchangeCancelEvent); - -/** - * Parses raw event logs for a CancelUpTo event and returns an array of - * ExchangeCancelUpToEvent entities. - * @param eventLogs Raw event logs (e.g. returned from contract-wrappers). - */ -export const parseExchangeCancelUpToEvents: ( - eventLogs: Array<LogWithDecodedArgs<ExchangeCancelUpToEventArgs>>, -) => ExchangeCancelUpToEvent[] = R.map(_convertToExchangeCancelUpToEvent); - -/** - * Converts a raw event log for a fill event into an ExchangeFillEvent entity. - * @param eventLog Raw event log (e.g. returned from contract-wrappers). - */ -export function _convertToExchangeFillEvent(eventLog: LogWithDecodedArgs<ExchangeFillEventArgs>): ExchangeFillEvent { - const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.makerAssetData); - const makerAssetType = makerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721'; - const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.takerAssetData); - const takerAssetType = takerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721'; - const exchangeFillEvent = new ExchangeFillEvent(); - exchangeFillEvent.contractAddress = eventLog.address as string; - exchangeFillEvent.blockNumber = eventLog.blockNumber as number; - exchangeFillEvent.logIndex = eventLog.logIndex as number; - exchangeFillEvent.rawData = eventLog.data as string; - exchangeFillEvent.transactionHash = eventLog.transactionHash; - exchangeFillEvent.makerAddress = eventLog.args.makerAddress; - exchangeFillEvent.takerAddress = eventLog.args.takerAddress; - exchangeFillEvent.feeRecipientAddress = eventLog.args.feeRecipientAddress; - exchangeFillEvent.senderAddress = eventLog.args.senderAddress; - exchangeFillEvent.makerAssetFilledAmount = eventLog.args.makerAssetFilledAmount; - exchangeFillEvent.takerAssetFilledAmount = eventLog.args.takerAssetFilledAmount; - exchangeFillEvent.makerFeePaid = eventLog.args.makerFeePaid; - exchangeFillEvent.takerFeePaid = eventLog.args.takerFeePaid; - exchangeFillEvent.orderHash = eventLog.args.orderHash; - exchangeFillEvent.rawMakerAssetData = eventLog.args.makerAssetData; - exchangeFillEvent.makerAssetType = makerAssetType; - exchangeFillEvent.makerAssetProxyId = makerAssetData.assetProxyId; - exchangeFillEvent.makerTokenAddress = makerAssetData.tokenAddress; - // tslint has a false positive here. Type assertion is required. - // tslint:disable-next-line:no-unnecessary-type-assertion - exchangeFillEvent.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId); - exchangeFillEvent.rawTakerAssetData = eventLog.args.takerAssetData; - exchangeFillEvent.takerAssetType = takerAssetType; - exchangeFillEvent.takerAssetProxyId = takerAssetData.assetProxyId; - exchangeFillEvent.takerTokenAddress = takerAssetData.tokenAddress; - // tslint:disable-next-line:no-unnecessary-type-assertion - exchangeFillEvent.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId); - return exchangeFillEvent; -} - -/** - * Converts a raw event log for a cancel event into an ExchangeCancelEvent - * entity. - * @param eventLog Raw event log (e.g. returned from contract-wrappers). - */ -export function _convertToExchangeCancelEvent( - eventLog: LogWithDecodedArgs<ExchangeCancelEventArgs>, -): ExchangeCancelEvent { - const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.makerAssetData); - const makerAssetType = makerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721'; - const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.takerAssetData); - const takerAssetType = takerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721'; - const exchangeCancelEvent = new ExchangeCancelEvent(); - exchangeCancelEvent.contractAddress = eventLog.address as string; - exchangeCancelEvent.blockNumber = eventLog.blockNumber as number; - exchangeCancelEvent.logIndex = eventLog.logIndex as number; - exchangeCancelEvent.rawData = eventLog.data as string; - exchangeCancelEvent.transactionHash = eventLog.transactionHash; - exchangeCancelEvent.makerAddress = eventLog.args.makerAddress; - exchangeCancelEvent.takerAddress = eventLog.args.takerAddress; - exchangeCancelEvent.feeRecipientAddress = eventLog.args.feeRecipientAddress; - exchangeCancelEvent.senderAddress = eventLog.args.senderAddress; - exchangeCancelEvent.orderHash = eventLog.args.orderHash; - exchangeCancelEvent.rawMakerAssetData = eventLog.args.makerAssetData; - exchangeCancelEvent.makerAssetType = makerAssetType; - exchangeCancelEvent.makerAssetProxyId = makerAssetData.assetProxyId; - exchangeCancelEvent.makerTokenAddress = makerAssetData.tokenAddress; - // tslint:disable-next-line:no-unnecessary-type-assertion - exchangeCancelEvent.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId); - exchangeCancelEvent.rawTakerAssetData = eventLog.args.takerAssetData; - exchangeCancelEvent.takerAssetType = takerAssetType; - exchangeCancelEvent.takerAssetProxyId = takerAssetData.assetProxyId; - exchangeCancelEvent.takerTokenAddress = takerAssetData.tokenAddress; - // tslint:disable-next-line:no-unnecessary-type-assertion - exchangeCancelEvent.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId); - return exchangeCancelEvent; -} - -/** - * Converts a raw event log for a cancelUpTo event into an - * ExchangeCancelUpToEvent entity. - * @param eventLog Raw event log (e.g. returned from contract-wrappers). - */ -export function _convertToExchangeCancelUpToEvent( - eventLog: LogWithDecodedArgs<ExchangeCancelUpToEventArgs>, -): ExchangeCancelUpToEvent { - const exchangeCancelUpToEvent = new ExchangeCancelUpToEvent(); - exchangeCancelUpToEvent.contractAddress = eventLog.address as string; - exchangeCancelUpToEvent.blockNumber = eventLog.blockNumber as number; - exchangeCancelUpToEvent.logIndex = eventLog.logIndex as number; - exchangeCancelUpToEvent.rawData = eventLog.data as string; - exchangeCancelUpToEvent.transactionHash = eventLog.transactionHash; - exchangeCancelUpToEvent.makerAddress = eventLog.args.makerAddress; - exchangeCancelUpToEvent.senderAddress = eventLog.args.senderAddress; - exchangeCancelUpToEvent.orderEpoch = eventLog.args.orderEpoch; - return exchangeCancelUpToEvent; -} +export { parseExchangeCancelEvents, parseExchangeCancelUpToEvents, parseExchangeFillEvents } from './exchange_events'; +export { parseERC20ApprovalEvents } from './erc20_events'; diff --git a/packages/pipeline/src/parsers/idex_orders/index.ts b/packages/pipeline/src/parsers/idex_orders/index.ts new file mode 100644 index 000000000..dfe27455c --- /dev/null +++ b/packages/pipeline/src/parsers/idex_orders/index.ts @@ -0,0 +1,77 @@ +import { BigNumber } from '@0x/utils'; + +import { aggregateOrders } from '../utils'; + +import { IdexOrder, IdexOrderbook, IdexOrderParam } from '../../data_sources/idex'; +import { TokenOrderbookSnapshot as TokenOrder } from '../../entities'; +import { OrderType } from '../../types'; + +/** + * Marque function of this file. + * 1) Takes in orders from an orderbook, + * 2) Aggregates them by price point, + * 3) Parses them into entities which are then saved into the database. + * @param idexOrderbook raw orderbook that we pull from the Idex API. + * @param observedTimestamp Time at which the orders for the market were pulled. + * @param source The exchange where these orders are placed. In this case 'idex'. + */ +export function parseIdexOrders(idexOrderbook: IdexOrderbook, observedTimestamp: number, source: string): TokenOrder[] { + const aggregatedBids = aggregateOrders(idexOrderbook.bids); + // Any of the bid orders' params will work + const idexBidOrder = idexOrderbook.bids[0]; + const parsedBids = + aggregatedBids.length > 0 + ? aggregatedBids.map(order => parseIdexOrder(idexBidOrder.params, observedTimestamp, 'bid', source, order)) + : []; + + const aggregatedAsks = aggregateOrders(idexOrderbook.asks); + // Any of the ask orders' params will work + const idexAskOrder = idexOrderbook.asks[0]; + const parsedAsks = + aggregatedAsks.length > 0 + ? aggregatedAsks.map(order => parseIdexOrder(idexAskOrder.params, observedTimestamp, 'ask', source, order)) + : []; + return parsedBids.concat(parsedAsks); +} + +/** + * Parse a single aggregated Idex order in order to form a tokenOrder entity + * which can be saved into the database. + * @param idexOrderParam An object containing information about the market where these + * trades have been placed. + * @param observedTimestamp The time when the API response returned back to us. + * @param orderType 'bid' or 'ask' enum. + * @param source Exchange where these orders were placed. + * @param idexOrder A <price, amount> tuple which we will convert to volume-basis. + */ +export function parseIdexOrder( + idexOrderParam: IdexOrderParam, + observedTimestamp: number, + orderType: OrderType, + source: string, + idexOrder: [string, BigNumber], +): TokenOrder { + const tokenOrder = new TokenOrder(); + const price = new BigNumber(idexOrder[0]); + const amount = idexOrder[1]; + + tokenOrder.source = source; + tokenOrder.observedTimestamp = observedTimestamp; + tokenOrder.orderType = orderType; + tokenOrder.price = price; + tokenOrder.baseVolume = amount; + tokenOrder.quoteVolume = price.times(amount); + + if (orderType === 'bid') { + tokenOrder.baseAssetSymbol = idexOrderParam.buySymbol; + tokenOrder.baseAssetAddress = idexOrderParam.tokenBuy; + tokenOrder.quoteAssetSymbol = idexOrderParam.sellSymbol; + tokenOrder.quoteAssetAddress = idexOrderParam.tokenSell; + } else { + tokenOrder.baseAssetSymbol = idexOrderParam.sellSymbol; + tokenOrder.baseAssetAddress = idexOrderParam.tokenSell; + tokenOrder.quoteAssetSymbol = idexOrderParam.buySymbol; + tokenOrder.quoteAssetAddress = idexOrderParam.tokenBuy; + } + return tokenOrder; +} diff --git a/packages/pipeline/src/parsers/oasis_orders/index.ts b/packages/pipeline/src/parsers/oasis_orders/index.ts new file mode 100644 index 000000000..13997f31b --- /dev/null +++ b/packages/pipeline/src/parsers/oasis_orders/index.ts @@ -0,0 +1,71 @@ +import { BigNumber } from '@0x/utils'; +import * as R from 'ramda'; + +import { aggregateOrders } from '../utils'; + +import { OasisMarket, OasisOrder } from '../../data_sources/oasis'; +import { TokenOrderbookSnapshot as TokenOrder } from '../../entities'; +import { OrderType } from '../../types'; + +/** + * Marque function of this file. + * 1) Takes in orders from an orderbook, + * 2) Aggregates them according to price point, + * 3) Builds TokenOrder entity with other information attached. + * @param oasisOrderbook A raw orderbook that we pull from the Oasis API. + * @param oasisMarket An object containing market data also directly from the API. + * @param observedTimestamp Time at which the orders for the market were pulled. + * @param source The exchange where these orders are placed. In this case 'oasis'. + */ +export function parseOasisOrders( + oasisOrderbook: OasisOrder[], + oasisMarket: OasisMarket, + observedTimestamp: number, + source: string, +): TokenOrder[] { + const aggregatedBids = aggregateOrders(R.filter(R.propEq('act', 'bid'), oasisOrderbook)); + const aggregatedAsks = aggregateOrders(R.filter(R.propEq('act', 'ask'), oasisOrderbook)); + const parsedBids = aggregatedBids.map(order => + parseOasisOrder(oasisMarket, observedTimestamp, 'bid', source, order), + ); + const parsedAsks = aggregatedAsks.map(order => + parseOasisOrder(oasisMarket, observedTimestamp, 'ask', source, order), + ); + return parsedBids.concat(parsedAsks); +} + +/** + * Parse a single aggregated Oasis order to form a tokenOrder entity + * which can be saved into the database. + * @param oasisMarket An object containing information about the market where these + * trades have been placed. + * @param observedTimestamp The time when the API response returned back to us. + * @param orderType 'bid' or 'ask' enum. + * @param source Exchange where these orders were placed. + * @param oasisOrder A <price, amount> tuple which we will convert to volume-basis. + */ +export function parseOasisOrder( + oasisMarket: OasisMarket, + observedTimestamp: number, + orderType: OrderType, + source: string, + oasisOrder: [string, BigNumber], +): TokenOrder { + const tokenOrder = new TokenOrder(); + const price = new BigNumber(oasisOrder[0]); + const amount = oasisOrder[1]; + + tokenOrder.source = source; + tokenOrder.observedTimestamp = observedTimestamp; + tokenOrder.orderType = orderType; + tokenOrder.price = price; + + tokenOrder.baseAssetSymbol = oasisMarket.base; + tokenOrder.baseAssetAddress = null; // Oasis doesn't provide address information + tokenOrder.baseVolume = amount; + + tokenOrder.quoteAssetSymbol = oasisMarket.quote; + tokenOrder.quoteAssetAddress = null; // Oasis doesn't provide address information + tokenOrder.quoteVolume = price.times(amount); + return tokenOrder; +} diff --git a/packages/pipeline/src/parsers/paradex_orders/index.ts b/packages/pipeline/src/parsers/paradex_orders/index.ts index 7966658a7..5ceeb64a4 100644 --- a/packages/pipeline/src/parsers/paradex_orders/index.ts +++ b/packages/pipeline/src/parsers/paradex_orders/index.ts @@ -57,10 +57,10 @@ export function parseParadexOrder( tokenOrder.baseAssetSymbol = paradexMarket.baseToken; tokenOrder.baseAssetAddress = paradexMarket.baseTokenAddress as string; - tokenOrder.baseVolume = price.times(amount); + tokenOrder.baseVolume = amount; tokenOrder.quoteAssetSymbol = paradexMarket.quoteToken; tokenOrder.quoteAssetAddress = paradexMarket.quoteTokenAddress as string; - tokenOrder.quoteVolume = amount; + tokenOrder.quoteVolume = price.times(amount); return tokenOrder; } diff --git a/packages/pipeline/src/parsers/token_metadata/index.ts b/packages/pipeline/src/parsers/token_metadata/index.ts index 3b3e05d76..65e0aaa6e 100644 --- a/packages/pipeline/src/parsers/token_metadata/index.ts +++ b/packages/pipeline/src/parsers/token_metadata/index.ts @@ -1,9 +1,8 @@ -import { BigNumber } from '@0x/utils'; import * as R from 'ramda'; import { MetamaskTrustedTokenMeta, ZeroExTrustedTokenMeta } from '../../data_sources/trusted_tokens'; import { TokenMetadata } from '../../entities'; -import {} from '../../utils'; +import { toBigNumberOrNull } from '../../utils'; /** * Parses Metamask's trusted tokens list. @@ -26,7 +25,7 @@ function parseMetamaskTrustedToken(resp: MetamaskTrustedTokenMeta, address: stri const trustedToken = new TokenMetadata(); trustedToken.address = address; - trustedToken.decimals = new BigNumber(resp.decimals); + trustedToken.decimals = toBigNumberOrNull(resp.decimals); trustedToken.symbol = resp.symbol; trustedToken.name = resp.name; trustedToken.authority = 'metamask'; @@ -38,7 +37,7 @@ function parseZeroExTrustedToken(resp: ZeroExTrustedTokenMeta): TokenMetadata { const trustedToken = new TokenMetadata(); trustedToken.address = resp.address; - trustedToken.decimals = new BigNumber(resp.decimals); + trustedToken.decimals = toBigNumberOrNull(resp.decimals); trustedToken.symbol = resp.symbol; trustedToken.name = resp.name; trustedToken.authority = '0x'; diff --git a/packages/pipeline/src/parsers/utils.ts b/packages/pipeline/src/parsers/utils.ts new file mode 100644 index 000000000..860729e9f --- /dev/null +++ b/packages/pipeline/src/parsers/utils.ts @@ -0,0 +1,28 @@ +import { BigNumber } from '@0x/utils'; + +export interface GenericRawOrder { + price: string; + amount: string; +} + +/** + * Aggregates individual orders by price point. Filters zero amount orders. + * @param rawOrders An array of objects that have price and amount information. + */ +export function aggregateOrders(rawOrders: GenericRawOrder[]): Array<[string, BigNumber]> { + const aggregatedOrders = new Map<string, BigNumber>(); + rawOrders.forEach(order => { + const amount = new BigNumber(order.amount); + if (amount.isZero()) { + return; + } + // Use string instead of BigNum to aggregate by value instead of variable. + // Convert to BigNumber first to consolidate different string + // representations of the same number. Eg. '0.0' and '0.00'. + const price = new BigNumber(order.price).toString(); + + const existingAmount = aggregatedOrders.get(price) || new BigNumber(0); + aggregatedOrders.set(price, amount.plus(existingAmount)); + }); + return Array.from(aggregatedOrders.entries()); +} diff --git a/packages/pipeline/src/scripts/pull_competing_dex_trades.ts b/packages/pipeline/src/scripts/pull_competing_dex_trades.ts index 4e4c12dd0..1478d5615 100644 --- a/packages/pipeline/src/scripts/pull_competing_dex_trades.ts +++ b/packages/pipeline/src/scripts/pull_competing_dex_trades.ts @@ -15,11 +15,11 @@ let connection: Connection; (async () => { connection = await createConnection(ormConfig as ConnectionOptions); - await getAndSaveTrades(); + await getAndSaveTradesAsync(); process.exit(0); })().catch(handleError); -async function getAndSaveTrades(): Promise<void> { +async function getAndSaveTradesAsync(): Promise<void> { const apiKey = process.env.BLOXY_API_KEY; if (apiKey === undefined) { throw new Error('Missing required env var: BLOXY_API_KEY'); diff --git a/packages/pipeline/src/scripts/pull_ddex_orderbook_snapshots.ts b/packages/pipeline/src/scripts/pull_ddex_orderbook_snapshots.ts index 7868e9c5a..4e00f258f 100644 --- a/packages/pipeline/src/scripts/pull_ddex_orderbook_snapshots.ts +++ b/packages/pipeline/src/scripts/pull_ddex_orderbook_snapshots.ts @@ -25,7 +25,7 @@ let connection: Connection; const markets = await ddexSource.getActiveMarketsAsync(); for (const marketsChunk of R.splitEvery(MARKET_ORDERBOOK_REQUEST_BATCH_SIZE, markets)) { await Promise.all( - marketsChunk.map(async (market: DdexMarket) => getAndSaveMarketOrderbook(ddexSource, market)), + marketsChunk.map(async (market: DdexMarket) => getAndSaveMarketOrderbookAsync(ddexSource, market)), ); await new Promise<void>(resolve => setTimeout(resolve, MILLISEC_MARKET_ORDERBOOK_REQUEST_DELAY)); } @@ -38,7 +38,7 @@ let connection: Connection; * @param ddexSource Data source which can query Ddex API. * @param market Object from Ddex API containing market data. */ -async function getAndSaveMarketOrderbook(ddexSource: DdexSource, market: DdexMarket): Promise<void> { +async function getAndSaveMarketOrderbookAsync(ddexSource: DdexSource, market: DdexMarket): Promise<void> { const orderBook = await ddexSource.getMarketOrderbookAsync(market.id); const observedTimestamp = Date.now(); diff --git a/packages/pipeline/src/scripts/pull_erc20_events.ts b/packages/pipeline/src/scripts/pull_erc20_events.ts new file mode 100644 index 000000000..bd520c610 --- /dev/null +++ b/packages/pipeline/src/scripts/pull_erc20_events.ts @@ -0,0 +1,96 @@ +import { getContractAddressesForNetworkOrThrow } from '@0x/contract-addresses'; +import { web3Factory } from '@0x/dev-utils'; +import { Web3ProviderEngine } from '@0x/subproviders'; +import { logUtils } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import 'reflect-metadata'; +import { Connection, ConnectionOptions, createConnection } from 'typeorm'; + +import { ERC20EventsSource } from '../data_sources/contract-wrappers/erc20_events'; +import { ERC20ApprovalEvent } from '../entities'; +import * as ormConfig from '../ormconfig'; +import { parseERC20ApprovalEvents } from '../parsers/events'; +import { handleError, INFURA_ROOT_URL } from '../utils'; + +const NETWORK_ID = 1; +const START_BLOCK_OFFSET = 100; // Number of blocks before the last known block to consider when updating fill events. +const BATCH_SAVE_SIZE = 1000; // Number of events to save at once. +const BLOCK_FINALITY_THRESHOLD = 10; // When to consider blocks as final. Used to compute default endBlock. + +let connection: Connection; + +interface Token { + // name is used for logging only. + name: string; + address: string; + defaultStartBlock: number; +} + +const tokensToGetApprovalEvents: Token[] = [ + { + name: 'WETH', + address: getContractAddressesForNetworkOrThrow(NETWORK_ID).etherToken, + defaultStartBlock: 4719568, // Block when the WETH contract was deployed. + }, + { + name: 'ZRX', + address: getContractAddressesForNetworkOrThrow(NETWORK_ID).zrxToken, + defaultStartBlock: 4145415, // Block when the ZRX contract was deployed. + }, + { + name: 'DAI', + address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + defaultStartBlock: 4752008, // Block when the DAI contract was deployed. + }, +]; + +(async () => { + connection = await createConnection(ormConfig as ConnectionOptions); + const provider = web3Factory.getRpcProvider({ + rpcUrl: INFURA_ROOT_URL, + }); + const endBlock = await calculateEndBlockAsync(provider); + for (const token of tokensToGetApprovalEvents) { + await getAndSaveApprovalEventsAsync(provider, token, endBlock); + } + process.exit(0); +})().catch(handleError); + +async function getAndSaveApprovalEventsAsync( + provider: Web3ProviderEngine, + token: Token, + endBlock: number, +): Promise<void> { + logUtils.log(`Getting approval events for ${token.name}...`); + logUtils.log('Checking existing approval events...'); + const repository = connection.getRepository(ERC20ApprovalEvent); + const startBlock = (await getStartBlockAsync(token)) || token.defaultStartBlock; + + logUtils.log(`Getting approval events starting at ${startBlock}...`); + const eventsSource = new ERC20EventsSource(provider, NETWORK_ID, token.address); + const eventLogs = await eventsSource.getApprovalEventsAsync(startBlock, endBlock); + + logUtils.log(`Parsing ${eventLogs.length} approval events...`); + const events = parseERC20ApprovalEvents(eventLogs); + logUtils.log(`Retrieved and parsed ${events.length} total approval events.`); + await repository.save(events, { chunk: Math.ceil(events.length / BATCH_SAVE_SIZE) }); +} + +async function calculateEndBlockAsync(provider: Web3ProviderEngine): Promise<number> { + const web3Wrapper = new Web3Wrapper(provider); + const currentBlock = await web3Wrapper.getBlockNumberAsync(); + return currentBlock - BLOCK_FINALITY_THRESHOLD; +} + +async function getStartBlockAsync(token: Token): Promise<number | null> { + const queryResult = await connection.query( + `SELECT block_number FROM raw.erc20_approval_events WHERE token_address = $1 ORDER BY block_number DESC LIMIT 1`, + [token.address], + ); + if (queryResult.length === 0) { + logUtils.log(`No existing approval events found for ${token.name}.`); + return null; + } + const lastKnownBlock = queryResult[0].block_number; + return lastKnownBlock - START_BLOCK_OFFSET; +} diff --git a/packages/pipeline/src/scripts/pull_missing_events.ts b/packages/pipeline/src/scripts/pull_exchange_events.ts index 80abbb8b0..e98fc6629 100644 --- a/packages/pipeline/src/scripts/pull_missing_events.ts +++ b/packages/pipeline/src/scripts/pull_exchange_events.ts @@ -1,5 +1,7 @@ // tslint:disable:no-console import { web3Factory } from '@0x/dev-utils'; +import { Web3ProviderEngine } from '@0x/subproviders'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import R = require('ramda'); import 'reflect-metadata'; import { Connection, ConnectionOptions, createConnection, Repository } from 'typeorm'; @@ -12,6 +14,7 @@ import { EXCHANGE_START_BLOCK, handleError, INFURA_ROOT_URL } from '../utils'; const START_BLOCK_OFFSET = 100; // Number of blocks before the last known block to consider when updating fill events. const BATCH_SAVE_SIZE = 1000; // Number of events to save at once. +const BLOCK_FINALITY_THRESHOLD = 10; // When to consider blocks as final. Used to compute default endBlock. let connection: Connection; @@ -20,43 +23,44 @@ let connection: Connection; const provider = web3Factory.getRpcProvider({ rpcUrl: INFURA_ROOT_URL, }); + const endBlock = await calculateEndBlockAsync(provider); const eventsSource = new ExchangeEventsSource(provider, 1); - await getFillEventsAsync(eventsSource); - await getCancelEventsAsync(eventsSource); - await getCancelUpToEventsAsync(eventsSource); + await getFillEventsAsync(eventsSource, endBlock); + await getCancelEventsAsync(eventsSource, endBlock); + await getCancelUpToEventsAsync(eventsSource, endBlock); process.exit(0); })().catch(handleError); -async function getFillEventsAsync(eventsSource: ExchangeEventsSource): Promise<void> { +async function getFillEventsAsync(eventsSource: ExchangeEventsSource, endBlock: number): Promise<void> { console.log('Checking existing fill events...'); const repository = connection.getRepository(ExchangeFillEvent); const startBlock = await getStartBlockAsync(repository); console.log(`Getting fill events starting at ${startBlock}...`); - const eventLogs = await eventsSource.getFillEventsAsync(startBlock); + const eventLogs = await eventsSource.getFillEventsAsync(startBlock, endBlock); console.log('Parsing fill events...'); const events = parseExchangeFillEvents(eventLogs); console.log(`Retrieved and parsed ${events.length} total fill events.`); await saveEventsAsync(startBlock === EXCHANGE_START_BLOCK, repository, events); } -async function getCancelEventsAsync(eventsSource: ExchangeEventsSource): Promise<void> { +async function getCancelEventsAsync(eventsSource: ExchangeEventsSource, endBlock: number): Promise<void> { console.log('Checking existing cancel events...'); const repository = connection.getRepository(ExchangeCancelEvent); const startBlock = await getStartBlockAsync(repository); console.log(`Getting cancel events starting at ${startBlock}...`); - const eventLogs = await eventsSource.getCancelEventsAsync(startBlock); + const eventLogs = await eventsSource.getCancelEventsAsync(startBlock, endBlock); console.log('Parsing cancel events...'); const events = parseExchangeCancelEvents(eventLogs); console.log(`Retrieved and parsed ${events.length} total cancel events.`); await saveEventsAsync(startBlock === EXCHANGE_START_BLOCK, repository, events); } -async function getCancelUpToEventsAsync(eventsSource: ExchangeEventsSource): Promise<void> { +async function getCancelUpToEventsAsync(eventsSource: ExchangeEventsSource, endBlock: number): Promise<void> { console.log('Checking existing CancelUpTo events...'); const repository = connection.getRepository(ExchangeCancelUpToEvent); const startBlock = await getStartBlockAsync(repository); console.log(`Getting CancelUpTo events starting at ${startBlock}...`); - const eventLogs = await eventsSource.getCancelUpToEventsAsync(startBlock); + const eventLogs = await eventsSource.getCancelUpToEventsAsync(startBlock, endBlock); console.log('Parsing CancelUpTo events...'); const events = parseExchangeCancelUpToEvents(eventLogs); console.log(`Retrieved and parsed ${events.length} total CancelUpTo events.`); @@ -134,3 +138,9 @@ async function saveIndividuallyWithFallbackAsync<T extends ExchangeEvent>( } } } + +async function calculateEndBlockAsync(provider: Web3ProviderEngine): Promise<number> { + const web3Wrapper = new Web3Wrapper(provider); + const currentBlock = await web3Wrapper.getBlockNumberAsync(); + return currentBlock - BLOCK_FINALITY_THRESHOLD; +} diff --git a/packages/pipeline/src/scripts/pull_idex_orderbook_snapshots.ts b/packages/pipeline/src/scripts/pull_idex_orderbook_snapshots.ts new file mode 100644 index 000000000..490b17766 --- /dev/null +++ b/packages/pipeline/src/scripts/pull_idex_orderbook_snapshots.ts @@ -0,0 +1,63 @@ +import { logUtils } from '@0x/utils'; +import * as R from 'ramda'; +import { Connection, ConnectionOptions, createConnection } from 'typeorm'; + +import { IDEX_SOURCE, IdexSource } from '../data_sources/idex'; +import { TokenOrderbookSnapshot as TokenOrder } from '../entities'; +import * as ormConfig from '../ormconfig'; +import { parseIdexOrders } from '../parsers/idex_orders'; +import { handleError } from '../utils'; + +// Number of orders to save at once. +const BATCH_SAVE_SIZE = 1000; + +// Number of markets to retrieve orderbooks for at once. +const MARKET_ORDERBOOK_REQUEST_BATCH_SIZE = 100; + +// Delay between market orderbook requests. +const MILLISEC_MARKET_ORDERBOOK_REQUEST_DELAY = 2000; + +let connection: Connection; + +(async () => { + connection = await createConnection(ormConfig as ConnectionOptions); + const idexSource = new IdexSource(); + logUtils.log('Getting all IDEX markets'); + const markets = await idexSource.getMarketsAsync(); + logUtils.log(`Got ${markets.length} markets.`); + for (const marketsChunk of R.splitEvery(MARKET_ORDERBOOK_REQUEST_BATCH_SIZE, markets)) { + await Promise.all( + marketsChunk.map(async (marketId: string) => getAndSaveMarketOrderbookAsync(idexSource, marketId)), + ); + await new Promise<void>(resolve => setTimeout(resolve, MILLISEC_MARKET_ORDERBOOK_REQUEST_DELAY)); + } + process.exit(0); +})().catch(handleError); + +/** + * Retrieve orderbook from Idex API for a given market. Parse orders and insert + * them into our database. + * @param idexSource Data source which can query Idex API. + * @param marketId String representing market of interest, eg. 'ETH_TIC'. + */ +async function getAndSaveMarketOrderbookAsync(idexSource: IdexSource, marketId: string): Promise<void> { + logUtils.log(`${marketId}: Retrieving orderbook.`); + const orderBook = await idexSource.getMarketOrderbookAsync(marketId); + const observedTimestamp = Date.now(); + + if (!R.has('bids', orderBook) || !R.has('asks', orderBook)) { + logUtils.warn(`${marketId}: Orderbook faulty.`); + return; + } + + logUtils.log(`${marketId}: Parsing orders.`); + const orders = parseIdexOrders(orderBook, observedTimestamp, IDEX_SOURCE); + + if (orders.length > 0) { + logUtils.log(`${marketId}: Saving ${orders.length} orders.`); + const TokenOrderRepository = connection.getRepository(TokenOrder); + await TokenOrderRepository.save(orders, { chunk: Math.ceil(orders.length / BATCH_SAVE_SIZE) }); + } else { + logUtils.log(`${marketId}: 0 orders to save.`); + } +} diff --git a/packages/pipeline/src/scripts/pull_missing_blocks.ts b/packages/pipeline/src/scripts/pull_missing_blocks.ts index b7bd51f08..bb5385126 100644 --- a/packages/pipeline/src/scripts/pull_missing_blocks.ts +++ b/packages/pipeline/src/scripts/pull_missing_blocks.ts @@ -9,7 +9,7 @@ import { Web3Source } from '../data_sources/web3'; import { Block } from '../entities'; import * as ormConfig from '../ormconfig'; import { parseBlock } from '../parsers/web3'; -import { EXCHANGE_START_BLOCK, handleError, INFURA_ROOT_URL } from '../utils'; +import { handleError, INFURA_ROOT_URL } from '../utils'; // Number of blocks to save at once. const BATCH_SAVE_SIZE = 1000; @@ -24,10 +24,10 @@ let connection: Connection; (async () => { connection = await createConnection(ormConfig as ConnectionOptions); const provider = web3Factory.getRpcProvider({ - rpcUrl: `${INFURA_ROOT_URL}/${process.env.INFURA_API_KEY}`, + rpcUrl: INFURA_ROOT_URL, }); const web3Source = new Web3Source(provider); - await getAllMissingBlocks(web3Source); + await getAllMissingBlocksAsync(web3Source); process.exit(0); })().catch(handleError); @@ -35,35 +35,44 @@ interface MissingBlocksResponse { block_number: string; } -async function getAllMissingBlocks(web3Source: Web3Source): Promise<void> { +async function getAllMissingBlocksAsync(web3Source: Web3Source): Promise<void> { const blocksRepository = connection.getRepository(Block); - let fromBlock = EXCHANGE_START_BLOCK; while (true) { - const blockNumbers = await getMissingBlockNumbers(fromBlock); + const blockNumbers = await getMissingBlockNumbersAsync(); if (blockNumbers.length === 0) { // There are no more missing blocks. We're done. break; } - await getAndSaveBlocks(web3Source, blocksRepository, blockNumbers); - fromBlock = Math.max(...blockNumbers) + 1; + await getAndSaveBlocksAsync(web3Source, blocksRepository, blockNumbers); } const totalBlocks = await blocksRepository.count(); console.log(`Done saving blocks. There are now ${totalBlocks} total blocks.`); } -async function getMissingBlockNumbers(fromBlock: number): Promise<number[]> { - console.log(`Checking for missing blocks starting at ${fromBlock}...`); +async function getMissingBlockNumbersAsync(): Promise<number[]> { + // Note(albrow): The easiest way to get all the blocks we need is to + // consider all the events tables together in a single query. If this query + // gets too slow, we should consider re-architecting so that we can work on + // getting the blocks for one type of event at a time. const response = (await connection.query( - 'SELECT DISTINCT(block_number) FROM raw.exchange_fill_events WHERE block_number NOT IN (SELECT number FROM raw.blocks) AND block_number >= $1 ORDER BY block_number ASC LIMIT $2', - [fromBlock, MAX_BLOCKS_PER_QUERY], + `WITH all_events AS ( + SELECT block_number FROM raw.exchange_fill_events + UNION SELECT block_number FROM raw.exchange_cancel_events + UNION SELECT block_number FROM raw.exchange_cancel_up_to_events + UNION SELECT block_number FROM raw.erc20_approval_events + ) + SELECT DISTINCT(block_number) FROM all_events + WHERE block_number NOT IN (SELECT number FROM raw.blocks) + ORDER BY block_number ASC LIMIT $1`, + [MAX_BLOCKS_PER_QUERY], )) as MissingBlocksResponse[]; const blockNumberStrings = R.pluck('block_number', response); const blockNumbers = R.map(parseInt, blockNumberStrings); - console.log(`Found ${blockNumbers.length} missing blocks in the given range.`); + console.log(`Found ${blockNumbers.length} missing blocks.`); return blockNumbers; } -async function getAndSaveBlocks( +async function getAndSaveBlocksAsync( web3Source: Web3Source, blocksRepository: Repository<Block>, blockNumbers: number[], diff --git a/packages/pipeline/src/scripts/pull_oasis_orderbook_snapshots.ts b/packages/pipeline/src/scripts/pull_oasis_orderbook_snapshots.ts new file mode 100644 index 000000000..c4dcf6c83 --- /dev/null +++ b/packages/pipeline/src/scripts/pull_oasis_orderbook_snapshots.ts @@ -0,0 +1,58 @@ +import { logUtils } from '@0x/utils'; +import * as R from 'ramda'; +import { Connection, ConnectionOptions, createConnection } from 'typeorm'; + +import { OASIS_SOURCE, OasisMarket, OasisSource } from '../data_sources/oasis'; +import { TokenOrderbookSnapshot as TokenOrder } from '../entities'; +import * as ormConfig from '../ormconfig'; +import { parseOasisOrders } from '../parsers/oasis_orders'; +import { handleError } from '../utils'; + +// Number of orders to save at once. +const BATCH_SAVE_SIZE = 1000; + +// Number of markets to retrieve orderbooks for at once. +const MARKET_ORDERBOOK_REQUEST_BATCH_SIZE = 50; + +// Delay between market orderbook requests. +const MILLISEC_MARKET_ORDERBOOK_REQUEST_DELAY = 1000; + +let connection: Connection; + +(async () => { + connection = await createConnection(ormConfig as ConnectionOptions); + const oasisSource = new OasisSource(); + logUtils.log('Getting all active Oasis markets'); + const markets = await oasisSource.getActiveMarketsAsync(); + logUtils.log(`Got ${markets.length} markets.`); + for (const marketsChunk of R.splitEvery(MARKET_ORDERBOOK_REQUEST_BATCH_SIZE, markets)) { + await Promise.all( + marketsChunk.map(async (market: OasisMarket) => getAndSaveMarketOrderbookAsync(oasisSource, market)), + ); + await new Promise<void>(resolve => setTimeout(resolve, MILLISEC_MARKET_ORDERBOOK_REQUEST_DELAY)); + } + process.exit(0); +})().catch(handleError); + +/** + * Retrieve orderbook from Oasis API for a given market. Parse orders and insert + * them into our database. + * @param oasisSource Data source which can query Oasis API. + * @param marketId String identifying market we want data for. eg. 'REPAUG'. + */ +async function getAndSaveMarketOrderbookAsync(oasisSource: OasisSource, market: OasisMarket): Promise<void> { + logUtils.log(`${market.id}: Retrieving orderbook.`); + const orderBook = await oasisSource.getMarketOrderbookAsync(market.id); + const observedTimestamp = Date.now(); + + logUtils.log(`${market.id}: Parsing orders.`); + const orders = parseOasisOrders(orderBook, market, observedTimestamp, OASIS_SOURCE); + + if (orders.length > 0) { + logUtils.log(`${market.id}: Saving ${orders.length} orders.`); + const TokenOrderRepository = connection.getRepository(TokenOrder); + await TokenOrderRepository.save(orders, { chunk: Math.ceil(orders.length / BATCH_SAVE_SIZE) }); + } else { + logUtils.log(`${market.id}: 0 orders to save.`); + } +} diff --git a/packages/pipeline/src/scripts/pull_ohlcv_cryptocompare.ts b/packages/pipeline/src/scripts/pull_ohlcv_cryptocompare.ts index 7377a64d8..d44eb5cc6 100644 --- a/packages/pipeline/src/scripts/pull_ohlcv_cryptocompare.ts +++ b/packages/pipeline/src/scripts/pull_ohlcv_cryptocompare.ts @@ -11,7 +11,7 @@ import { fetchOHLCVTradingPairsAsync, TradingPair } from '../utils/get_ohlcv_tra const SOURCE_NAME = 'CryptoCompare'; const TWO_HOURS_AGO = new Date().getTime() - 2 * 60 * 60 * 1000; // tslint:disable-line:custom-no-magic-numbers -const MAX_CONCURRENT_REQUESTS = parseInt(process.env.CRYPTOCOMPARE_MAX_CONCURRENT_REQUESTS || '14', 10); // tslint:disable-line:custom-no-magic-numbers +const MAX_REQS_PER_SECOND = parseInt(process.env.CRYPTOCOMPARE_MAX_REQS_PER_SECOND || '15', 10); // tslint:disable-line:custom-no-magic-numbers const EARLIEST_BACKFILL_DATE = process.env.OHLCV_EARLIEST_BACKFILL_DATE || '2014-06-01'; const EARLIEST_BACKFILL_TIME = new Date(EARLIEST_BACKFILL_DATE).getTime(); @@ -20,7 +20,7 @@ let connection: Connection; (async () => { connection = await createConnection(ormConfig as ConnectionOptions); const repository = connection.getRepository(OHLCVExternal); - const source = new CryptoCompareOHLCVSource(MAX_CONCURRENT_REQUESTS); + const source = new CryptoCompareOHLCVSource(MAX_REQS_PER_SECOND); const jobTime = new Date().getTime(); const tradingPairs = await fetchOHLCVTradingPairsAsync(connection, SOURCE_NAME, EARLIEST_BACKFILL_TIME); @@ -63,7 +63,7 @@ async function fetchAndSaveAsync( console.log(`Retrieved ${records.length} records for ${JSON.stringify(pair)}`); if (records.length > 0) { const metadata: OHLCVMetadata = { - exchange: source.default_exchange, + exchange: source.defaultExchange, fromSymbol: pair.fromSymbol, toSymbol: pair.toSymbol, source: SOURCE_NAME, diff --git a/packages/pipeline/src/scripts/pull_paradex_orderbook_snapshots.ts b/packages/pipeline/src/scripts/pull_paradex_orderbook_snapshots.ts index bae1fbede..34345f355 100644 --- a/packages/pipeline/src/scripts/pull_paradex_orderbook_snapshots.ts +++ b/packages/pipeline/src/scripts/pull_paradex_orderbook_snapshots.ts @@ -29,7 +29,7 @@ let connection: Connection; const tokenInfoResponse = await paradexSource.getTokenInfoAsync(); const extendedMarkets = addTokenAddresses(markets, tokenInfoResponse); await Promise.all( - extendedMarkets.map(async (market: ParadexMarket) => getAndSaveMarketOrderbook(paradexSource, market)), + extendedMarkets.map(async (market: ParadexMarket) => getAndSaveMarketOrderbookAsync(paradexSource, market)), ); process.exit(0); })().catch(handleError); @@ -70,7 +70,7 @@ function addTokenAddresses( * @param paradexSource Data source which can query the Paradex API. * @param market Object from the Paradex API with information about the market in question. */ -async function getAndSaveMarketOrderbook(paradexSource: ParadexSource, market: ParadexMarket): Promise<void> { +async function getAndSaveMarketOrderbookAsync(paradexSource: ParadexSource, market: ParadexMarket): Promise<void> { const paradexOrderbookResponse = await paradexSource.getMarketOrderbookAsync(market.symbol); const observedTimestamp = Date.now(); diff --git a/packages/pipeline/src/scripts/pull_trusted_tokens.ts b/packages/pipeline/src/scripts/pull_trusted_tokens.ts index 1befc4437..5906deee6 100644 --- a/packages/pipeline/src/scripts/pull_trusted_tokens.ts +++ b/packages/pipeline/src/scripts/pull_trusted_tokens.ts @@ -16,12 +16,12 @@ let connection: Connection; (async () => { connection = await createConnection(ormConfig as ConnectionOptions); - await getMetamaskTrustedTokens(); - await getZeroExTrustedTokens(); + await getMetamaskTrustedTokensAsync(); + await getZeroExTrustedTokensAsync(); process.exit(0); })().catch(handleError); -async function getMetamaskTrustedTokens(): Promise<void> { +async function getMetamaskTrustedTokensAsync(): Promise<void> { // tslint:disable-next-line:no-console console.log('Getting latest metamask trusted tokens list ...'); const trustedTokensRepository = connection.getRepository(TokenMetadata); @@ -37,7 +37,7 @@ async function getMetamaskTrustedTokens(): Promise<void> { console.log('Done saving metamask trusted tokens.'); } -async function getZeroExTrustedTokens(): Promise<void> { +async function getZeroExTrustedTokensAsync(): Promise<void> { // tslint:disable-next-line:no-console console.log('Getting latest 0x trusted tokens list ...'); const trustedTokensRepository = connection.getRepository(TokenMetadata); diff --git a/packages/pipeline/src/scripts/update_relayer_info.ts b/packages/pipeline/src/scripts/update_relayer_info.ts index f8918728d..41d29b385 100644 --- a/packages/pipeline/src/scripts/update_relayer_info.ts +++ b/packages/pipeline/src/scripts/update_relayer_info.ts @@ -17,11 +17,11 @@ let connection: Connection; (async () => { connection = await createConnection(ormConfig as ConnectionOptions); - await getRelayers(); + await getRelayersAsync(); process.exit(0); })().catch(handleError); -async function getRelayers(): Promise<void> { +async function getRelayersAsync(): Promise<void> { console.log('Getting latest relayer info...'); const relayerRepository = connection.getRepository(Relayer); const relayerSource = new RelayerRegistrySource(RELAYER_REGISTRY_URL); diff --git a/packages/pipeline/src/utils/get_ohlcv_trading_pairs.ts b/packages/pipeline/src/utils/get_ohlcv_trading_pairs.ts index 9d3ef2fba..19f81344e 100644 --- a/packages/pipeline/src/utils/get_ohlcv_trading_pairs.ts +++ b/packages/pipeline/src/utils/get_ohlcv_trading_pairs.ts @@ -23,6 +23,18 @@ interface CryptoCompareCoin { const TO_CURRENCIES = ['USD', 'EUR', 'ETH', 'USDT']; const ETHEREUM_IDENTIFIER = '7605'; const HTTP_OK_STATUS = 200; + +interface StaticPair { + fromSymbol: string; + toSymbol: string; +} +const SPECIAL_CASES: StaticPair[] = [ + { + fromSymbol: 'ETH', + toSymbol: 'USD', + }, +]; + /** * Get trading pairs with latest scraped time for OHLCV records * @param conn a typeorm Connection to postgres @@ -44,6 +56,7 @@ export async function fetchOHLCVTradingPairsAsync( FROM raw.ohlcv_external GROUP BY from_symbol, to_symbol;`); + // build addressable index: { fromsym: { tosym: time }} const latestTradingPairsIndex: { [fromSym: string]: { [toSym: string]: number } } = {}; latestTradingPairs.forEach(pair => { const latestIndex: { [toSym: string]: number } = latestTradingPairsIndex[pair.from_symbol] || {}; @@ -51,6 +64,13 @@ export async function fetchOHLCVTradingPairsAsync( latestTradingPairsIndex[pair.from_symbol] = latestIndex; }); + // match time to special cases + const specialCases: TradingPair[] = SPECIAL_CASES.map(pair => { + const latestSavedTime = + R.path<number>([pair.fromSymbol, pair.toSymbol], latestTradingPairsIndex) || earliestBackfillTime; + return R.assoc('latestSavedTime', latestSavedTime, pair); + }); + // get token symbols used by Crypto Compare const allCoinsResp = await fetchAsync(COINLIST_API); if (allCoinsResp.status !== HTTP_OK_STATUS) { @@ -66,27 +86,31 @@ export async function fetchOHLCVTradingPairsAsync( }); // fetch all tokens that are traded on 0x - const rawTokenAddresses: Array<{ tokenaddress: string }> = await conn.query( + const rawEventTokenAddresses: Array<{ tokenaddress: string }> = await conn.query( `SELECT DISTINCT(maker_token_address) as tokenaddress FROM raw.exchange_fill_events UNION SELECT DISTINCT(taker_token_address) as tokenaddress FROM raw.exchange_fill_events`, ); - const tokenAddresses = R.pluck('tokenaddress', rawTokenAddresses); + + // tslint:disable-next-line:no-unbound-method + const eventTokenAddresses = R.pluck('tokenaddress', rawEventTokenAddresses).map(R.toLower); // join token addresses with CC symbols - const allTokenSymbols: string[] = tokenAddresses - .map(tokenAddress => erc20CoinsIndex.get(tokenAddress.toLowerCase()) || '') - .filter(x => x); + const eventTokenSymbols: string[] = eventTokenAddresses + .filter(tokenAddress => erc20CoinsIndex.has(tokenAddress)) + .map(tokenAddress => erc20CoinsIndex.get(tokenAddress) as string); - // generate list of all tokens with time of latest existing record OR default earliest time - const allTradingPairCombinations: TradingPair[] = R.chain(sym => { + // join traded tokens with fiat and latest backfill time + const eventTradingPairs: TradingPair[] = R.chain(sym => { return TO_CURRENCIES.map(fiat => { - return { + const pair = { fromSymbol: sym, toSymbol: fiat, latestSavedTime: R.path<number>([sym, fiat], latestTradingPairsIndex) || earliestBackfillTime, }; + return pair; }); - }, allTokenSymbols); + }, eventTokenSymbols); - return allTradingPairCombinations; + // join with special cases + return R.concat(eventTradingPairs, specialCases); } diff --git a/packages/pipeline/src/utils/index.ts b/packages/pipeline/src/utils/index.ts index 2096a0a39..094c0178e 100644 --- a/packages/pipeline/src/utils/index.ts +++ b/packages/pipeline/src/utils/index.ts @@ -15,6 +15,21 @@ export function bigNumbertoStringOrNull(n: BigNumber): string | null { } /** + * If value is null or undefined, returns null. Otherwise converts value to a + * BigNumber. + * @param value A string or number to be converted to a BigNumber + */ +export function toBigNumberOrNull(value: string | number | null): BigNumber | null { + switch (value) { + case null: + case undefined: + return null; + default: + return new BigNumber(value); + } +} + +/** * Logs an error by intelligently checking for `message` and `stack` properties. * Intended for use with top-level immediately invoked asynchronous functions. * @param e the error to log. diff --git a/packages/pipeline/test/data_sources/contract-wrappers/utils_test.ts b/packages/pipeline/test/data_sources/contract-wrappers/utils_test.ts new file mode 100644 index 000000000..06f1a5e86 --- /dev/null +++ b/packages/pipeline/test/data_sources/contract-wrappers/utils_test.ts @@ -0,0 +1,109 @@ +// tslint:disable:custom-no-magic-numbers +import * as chai from 'chai'; +import { LogWithDecodedArgs } from 'ethereum-types'; +import 'mocha'; + +import { _getEventsWithRetriesAsync } from '../../../src/data_sources/contract-wrappers/utils'; +import { chaiSetup } from '../../utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +const retryableMessage = 'network timeout: (simulated network timeout error)'; +const retryableError = new Error(retryableMessage); + +describe('data_sources/contract-wrappers/utils', () => { + describe('_getEventsWithRetriesAsync', () => { + it('sends a single request if it was successful', async () => { + // Pre-declare values for the fromBlock and toBlock arguments. + const expectedFromBlock = 100; + const expectedToBlock = 200; + const expectedLogs: Array<LogWithDecodedArgs<any>> = [ + { + logIndex: 123, + transactionIndex: 456, + transactionHash: '0x6dd106d002873746072fc5e496dd0fb2541b68c77bcf9184ae19a42fd33657fe', + blockHash: '0x6dd106d002873746072fc5e496dd0fb2541b68c77bcf9184ae19a42fd33657ff', + blockNumber: 789, + address: '0x6dd106d002873746072fc5e496dd0fb2541b68c77bcf9184ae19a42fd3365800', + data: 'fake raw data', + topics: [], + event: 'TEST_EVENT', + args: [1, 2, 3], + }, + ]; + + // mockGetEventsAsync checks its arguments, increments `callCount` + // and returns `expectedLogs`. + let callCount = 0; + const mockGetEventsAsync = async ( + fromBlock: number, + toBlock: number, + ): Promise<Array<LogWithDecodedArgs<any>>> => { + expect(fromBlock).equals(expectedFromBlock); + expect(toBlock).equals(expectedToBlock); + callCount += 1; + return expectedLogs; + }; + + // Make sure that we get what we expected and that the mock function + // was called exactly once. + const gotLogs = await _getEventsWithRetriesAsync(mockGetEventsAsync, 3, expectedFromBlock, expectedToBlock); + expect(gotLogs).deep.equals(expectedLogs); + expect(callCount).equals( + 1, + 'getEventsAsync function was called more than once even though it was successful', + ); + }); + it('retries and eventually succeeds', async () => { + const numRetries = 5; + let callCount = 0; + // mockGetEventsAsync throws unless callCount == numRetries + 1. + const mockGetEventsAsync = async ( + _fromBlock: number, + _toBlock: number, + ): Promise<Array<LogWithDecodedArgs<any>>> => { + callCount += 1; + if (callCount === numRetries + 1) { + return []; + } + throw retryableError; + }; + await _getEventsWithRetriesAsync(mockGetEventsAsync, numRetries, 100, 300); + expect(callCount).equals(numRetries + 1, 'getEventsAsync function was called the wrong number of times'); + }); + it('throws for non-retryable errors', async () => { + const numRetries = 5; + const expectedMessage = 'Non-retryable error'; + // mockGetEventsAsync always throws a non-retryable error. + const mockGetEventsAsync = async ( + _fromBlock: number, + _toBlock: number, + ): Promise<Array<LogWithDecodedArgs<any>>> => { + throw new Error(expectedMessage); + }; + // Note(albrow): This does actually return a promise (or at least a + // "promise-like object" and is a false positive in TSLint. + // tslint:disable-next-line:await-promise + await expect(_getEventsWithRetriesAsync(mockGetEventsAsync, numRetries, 100, 300)).to.be.rejectedWith( + expectedMessage, + ); + }); + it('throws after too many retries', async () => { + const numRetries = 5; + // mockGetEventsAsync always throws a retryable error. + const mockGetEventsAsync = async ( + _fromBlock: number, + _toBlock: number, + ): Promise<Array<LogWithDecodedArgs<any>>> => { + throw retryableError; + }; + // Note(albrow): This does actually return a promise (or at least a + // "promise-like object" and is a false positive in TSLint. + // tslint:disable-next-line:await-promise + await expect(_getEventsWithRetriesAsync(mockGetEventsAsync, numRetries, 100, 300)).to.be.rejectedWith( + retryableMessage, + ); + }); + }); +}); diff --git a/packages/pipeline/test/data_sources/ohlcv_external/crypto_compare_test.ts b/packages/pipeline/test/data_sources/ohlcv_external/crypto_compare_test.ts index cb374bbb1..2efe3f5ec 100644 --- a/packages/pipeline/test/data_sources/ohlcv_external/crypto_compare_test.ts +++ b/packages/pipeline/test/data_sources/ohlcv_external/crypto_compare_test.ts @@ -13,7 +13,7 @@ const expect = chai.expect; describe('ohlcv_external data source (Crypto Compare)', () => { describe('generateBackfillIntervals', () => { it('generates pairs with intervals to query', () => { - const source = new CryptoCompareOHLCVSource(); + const source = new CryptoCompareOHLCVSource(20); const pair: TradingPair = { fromSymbol: 'ETH', toSymbol: 'ZRX', @@ -31,7 +31,7 @@ describe('ohlcv_external data source (Crypto Compare)', () => { }); it('returns single pair if no backfill is needed', () => { - const source = new CryptoCompareOHLCVSource(); + const source = new CryptoCompareOHLCVSource(20); const pair: TradingPair = { fromSymbol: 'ETH', toSymbol: 'ZRX', diff --git a/packages/pipeline/test/entities/erc20_approval_events_test.ts b/packages/pipeline/test/entities/erc20_approval_events_test.ts new file mode 100644 index 000000000..1ecf41ee5 --- /dev/null +++ b/packages/pipeline/test/entities/erc20_approval_events_test.ts @@ -0,0 +1,29 @@ +import { BigNumber } from '@0x/utils'; +import 'mocha'; +import 'reflect-metadata'; + +import { ERC20ApprovalEvent } from '../../src/entities'; +import { createDbConnectionOnceAsync } from '../db_setup'; +import { chaiSetup } from '../utils/chai_setup'; + +import { testSaveAndFindEntityAsync } from './util'; + +chaiSetup.configure(); + +// tslint:disable:custom-no-magic-numbers +describe('ERC20ApprovalEvent entity', () => { + it('save/find', async () => { + const connection = await createDbConnectionOnceAsync(); + const event = new ERC20ApprovalEvent(); + event.tokenAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; + event.blockNumber = 6281577; + event.rawData = '0x000000000000000000000000000000000000000000000002b9cba5ee21ad3df9'; + event.logIndex = 43; + event.transactionHash = '0xcb46b19c786376a0a0140d51e3e606a4c4f926d8ca5434e96d2f69d04d8d9c7f'; + event.ownerAddress = '0x0b65c5f6f3a05d6be5588a72b603360773b3fe04'; + event.spenderAddress = '0x448a5065aebb8e423f0896e6c5d525c040f59af3'; + event.amount = new BigNumber('50281464906893835769'); + const blocksRepository = connection.getRepository(ERC20ApprovalEvent); + await testSaveAndFindEntityAsync(blocksRepository, event); + }); +}); diff --git a/packages/pipeline/test/parsers/bloxy/index_test.ts b/packages/pipeline/test/parsers/bloxy/index_test.ts index 2b8d68f98..6aabb091d 100644 --- a/packages/pipeline/test/parsers/bloxy/index_test.ts +++ b/packages/pipeline/test/parsers/bloxy/index_test.ts @@ -7,7 +7,6 @@ import * as R from 'ramda'; import { BLOXY_DEX_TRADES_URL, BloxyTrade } from '../../../src/data_sources/bloxy'; import { DexTrade } from '../../../src/entities'; import { _parseBloxyTrade } from '../../../src/parsers/bloxy'; -import { _convertToExchangeFillEvent } from '../../../src/parsers/events'; import { chaiSetup } from '../../utils/chai_setup'; chaiSetup.configure(); diff --git a/packages/pipeline/test/parsers/ddex_orders/index_test.ts b/packages/pipeline/test/parsers/ddex_orders/index_test.ts index 213100f44..4a4a86bf8 100644 --- a/packages/pipeline/test/parsers/ddex_orders/index_test.ts +++ b/packages/pipeline/test/parsers/ddex_orders/index_test.ts @@ -4,7 +4,7 @@ import 'mocha'; import { DdexMarket } from '../../../src/data_sources/ddex'; import { TokenOrderbookSnapshot as TokenOrder } from '../../../src/entities'; -import { aggregateOrders, parseDdexOrder } from '../../../src/parsers/ddex_orders'; +import { parseDdexOrder } from '../../../src/parsers/ddex_orders'; import { OrderType } from '../../../src/types'; import { chaiSetup } from '../../utils/chai_setup'; @@ -13,19 +13,6 @@ const expect = chai.expect; // tslint:disable:custom-no-magic-numbers describe('ddex_orders', () => { - describe('aggregateOrders', () => { - it('aggregates orders by price point', () => { - const input = [ - { price: '1', amount: '20', orderId: 'testtest' }, - { price: '1', amount: '30', orderId: 'testone' }, - { price: '2', amount: '100', orderId: 'testtwo' }, - ]; - const expected = [['1', new BigNumber(50)], ['2', new BigNumber(100)]]; - const actual = aggregateOrders(input); - expect(actual).deep.equal(expected); - }); - }); - describe('parseDdexOrder', () => { it('converts ddexOrder to TokenOrder entity', () => { const ddexOrder: [string, BigNumber] = ['0.5', new BigNumber(10)]; @@ -52,12 +39,14 @@ describe('ddex_orders', () => { expected.observedTimestamp = observedTimestamp; expected.orderType = 'bid'; expected.price = new BigNumber(0.5); - expected.baseAssetSymbol = 'DEF'; - expected.baseAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; - expected.baseVolume = new BigNumber(5); - expected.quoteAssetSymbol = 'ABC'; - expected.quoteAssetAddress = '0x0000000000000000000000000000000000000000'; - expected.quoteVolume = new BigNumber(10); + // ddex currently confuses base and quote assets. + // Switch them to maintain our internal consistency. + expected.baseAssetSymbol = 'ABC'; + expected.baseAssetAddress = '0x0000000000000000000000000000000000000000'; + expected.baseVolume = new BigNumber(10); + expected.quoteAssetSymbol = 'DEF'; + expected.quoteAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; + expected.quoteVolume = new BigNumber(5); const actual = parseDdexOrder(ddexMarket, observedTimestamp, orderType, source, ddexOrder); expect(actual).deep.equal(expected); diff --git a/packages/pipeline/test/parsers/events/erc20_events_test.ts b/packages/pipeline/test/parsers/events/erc20_events_test.ts new file mode 100644 index 000000000..962c50f98 --- /dev/null +++ b/packages/pipeline/test/parsers/events/erc20_events_test.ts @@ -0,0 +1,54 @@ +import { ERC20TokenApprovalEventArgs } from '@0x/contract-wrappers'; +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import { LogWithDecodedArgs } from 'ethereum-types'; +import 'mocha'; + +import { ERC20ApprovalEvent } from '../../../src/entities'; +import { _convertToERC20ApprovalEvent } from '../../../src/parsers/events/erc20_events'; +import { _convertToExchangeFillEvent } from '../../../src/parsers/events/exchange_events'; +import { chaiSetup } from '../../utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +// tslint:disable:custom-no-magic-numbers +describe('erc20_events', () => { + describe('_convertToERC20ApprovalEvent', () => { + it('converts LogWithDecodedArgs to ERC20ApprovalEvent entity', () => { + const input: LogWithDecodedArgs<ERC20TokenApprovalEventArgs> = { + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + blockHash: '0xd2d7aafaa7102aec0bca8ef026d5a85133e87892334c46ee1e92e42912991c9b', + blockNumber: 6281577, + data: '0x000000000000000000000000000000000000000000000002b9cba5ee21ad3df9', + logIndex: 43, + topics: [ + '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', + '0x0000000000000000000000000b65c5f6f3a05d6be5588a72b603360773b3fe04', + '0x000000000000000000000000448a5065aebb8e423f0896e6c5d525c040f59af3', + ], + transactionHash: '0xcb46b19c786376a0a0140d51e3e606a4c4f926d8ca5434e96d2f69d04d8d9c7f', + transactionIndex: 103, + event: 'Approval', + args: { + _owner: '0x0b65c5f6f3a05d6be5588a72b603360773b3fe04', + _spender: '0x448a5065aebb8e423f0896e6c5d525c040f59af3', + _value: new BigNumber('50281464906893835769'), + }, + }; + + const expected = new ERC20ApprovalEvent(); + expected.tokenAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; + expected.blockNumber = 6281577; + expected.rawData = '0x000000000000000000000000000000000000000000000002b9cba5ee21ad3df9'; + expected.logIndex = 43; + expected.transactionHash = '0xcb46b19c786376a0a0140d51e3e606a4c4f926d8ca5434e96d2f69d04d8d9c7f'; + expected.ownerAddress = '0x0b65c5f6f3a05d6be5588a72b603360773b3fe04'; + expected.spenderAddress = '0x448a5065aebb8e423f0896e6c5d525c040f59af3'; + expected.amount = new BigNumber('50281464906893835769'); + + const actual = _convertToERC20ApprovalEvent(input); + expect(actual).deep.equal(expected); + }); + }); +}); diff --git a/packages/pipeline/test/parsers/events/index_test.ts b/packages/pipeline/test/parsers/events/exchange_events_test.ts index 7e439ce39..5d4b185a5 100644 --- a/packages/pipeline/test/parsers/events/index_test.ts +++ b/packages/pipeline/test/parsers/events/exchange_events_test.ts @@ -5,7 +5,7 @@ import { LogWithDecodedArgs } from 'ethereum-types'; import 'mocha'; import { ExchangeFillEvent } from '../../../src/entities'; -import { _convertToExchangeFillEvent } from '../../../src/parsers/events'; +import { _convertToExchangeFillEvent } from '../../../src/parsers/events/exchange_events'; import { chaiSetup } from '../../utils/chai_setup'; chaiSetup.configure(); diff --git a/packages/pipeline/test/parsers/idex_orders/index_test.ts b/packages/pipeline/test/parsers/idex_orders/index_test.ts new file mode 100644 index 000000000..d54ecb9a8 --- /dev/null +++ b/packages/pipeline/test/parsers/idex_orders/index_test.ts @@ -0,0 +1,87 @@ +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { IdexOrderParam } from '../../../src/data_sources/idex'; +import { TokenOrderbookSnapshot as TokenOrder } from '../../../src/entities'; +import { parseIdexOrder } from '../../../src/parsers/idex_orders'; +import { OrderType } from '../../../src/types'; +import { chaiSetup } from '../../utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +// tslint:disable:custom-no-magic-numbers +describe('idex_orders', () => { + describe('parseIdexOrder', () => { + // for market listed as 'DEF_ABC'. + it('correctly converts bid type idexOrder to TokenOrder entity', () => { + const idexOrder: [string, BigNumber] = ['0.5', new BigNumber(10)]; + const idexOrderParam: IdexOrderParam = { + tokenBuy: '0x0000000000000000000000000000000000000000', + buySymbol: 'ABC', + buyPrecision: 2, + amountBuy: '10', + tokenSell: '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81', + sellSymbol: 'DEF', + sellPrecision: 2, + amountSell: '5', + expires: Date.now() + 100000, + nonce: 1, + user: '0x212345667543456435324564345643453453333', + }; + const observedTimestamp: number = Date.now(); + const orderType: OrderType = 'bid'; + const source: string = 'idex'; + + const expected = new TokenOrder(); + expected.source = 'idex'; + expected.observedTimestamp = observedTimestamp; + expected.orderType = 'bid'; + expected.price = new BigNumber(0.5); + expected.baseAssetSymbol = 'ABC'; + expected.baseAssetAddress = '0x0000000000000000000000000000000000000000'; + expected.baseVolume = new BigNumber(10); + expected.quoteAssetSymbol = 'DEF'; + expected.quoteAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; + expected.quoteVolume = new BigNumber(5); + + const actual = parseIdexOrder(idexOrderParam, observedTimestamp, orderType, source, idexOrder); + expect(actual).deep.equal(expected); + }); + it('correctly converts ask type idexOrder to TokenOrder entity', () => { + const idexOrder: [string, BigNumber] = ['0.5', new BigNumber(10)]; + const idexOrderParam: IdexOrderParam = { + tokenBuy: '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81', + buySymbol: 'DEF', + buyPrecision: 2, + amountBuy: '5', + tokenSell: '0x0000000000000000000000000000000000000000', + sellSymbol: 'ABC', + sellPrecision: 2, + amountSell: '10', + expires: Date.now() + 100000, + nonce: 1, + user: '0x212345667543456435324564345643453453333', + }; + const observedTimestamp: number = Date.now(); + const orderType: OrderType = 'ask'; + const source: string = 'idex'; + + const expected = new TokenOrder(); + expected.source = 'idex'; + expected.observedTimestamp = observedTimestamp; + expected.orderType = 'ask'; + expected.price = new BigNumber(0.5); + expected.baseAssetSymbol = 'ABC'; + expected.baseAssetAddress = '0x0000000000000000000000000000000000000000'; + expected.baseVolume = new BigNumber(10); + expected.quoteAssetSymbol = 'DEF'; + expected.quoteAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; + expected.quoteVolume = new BigNumber(5); + + const actual = parseIdexOrder(idexOrderParam, observedTimestamp, orderType, source, idexOrder); + expect(actual).deep.equal(expected); + }); + }); +}); diff --git a/packages/pipeline/test/parsers/oasis_orders/index_test.ts b/packages/pipeline/test/parsers/oasis_orders/index_test.ts new file mode 100644 index 000000000..433bfb665 --- /dev/null +++ b/packages/pipeline/test/parsers/oasis_orders/index_test.ts @@ -0,0 +1,49 @@ +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { OasisMarket } from '../../../src/data_sources/oasis'; +import { TokenOrderbookSnapshot as TokenOrder } from '../../../src/entities'; +import { parseOasisOrder } from '../../../src/parsers/oasis_orders'; +import { OrderType } from '../../../src/types'; +import { chaiSetup } from '../../utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +// tslint:disable:custom-no-magic-numbers +describe('oasis_orders', () => { + describe('parseOasisOrder', () => { + it('converts oasisOrder to TokenOrder entity', () => { + const oasisOrder: [string, BigNumber] = ['0.5', new BigNumber(10)]; + const oasisMarket: OasisMarket = { + id: 'ABCDEF', + base: 'DEF', + quote: 'ABC', + buyVol: 100, + sellVol: 200, + price: 1, + high: 1, + low: 0, + }; + const observedTimestamp: number = Date.now(); + const orderType: OrderType = 'bid'; + const source: string = 'oasis'; + + const expected = new TokenOrder(); + expected.source = 'oasis'; + expected.observedTimestamp = observedTimestamp; + expected.orderType = 'bid'; + expected.price = new BigNumber(0.5); + expected.baseAssetSymbol = 'DEF'; + expected.baseAssetAddress = null; + expected.baseVolume = new BigNumber(10); + expected.quoteAssetSymbol = 'ABC'; + expected.quoteAssetAddress = null; + expected.quoteVolume = new BigNumber(5); + + const actual = parseOasisOrder(oasisMarket, observedTimestamp, orderType, source, oasisOrder); + expect(actual).deep.equal(expected); + }); + }); +}); diff --git a/packages/pipeline/test/parsers/paradex_orders/index_test.ts b/packages/pipeline/test/parsers/paradex_orders/index_test.ts index 1522806bf..6b811b90d 100644 --- a/packages/pipeline/test/parsers/paradex_orders/index_test.ts +++ b/packages/pipeline/test/parsers/paradex_orders/index_test.ts @@ -42,10 +42,10 @@ describe('paradex_orders', () => { expected.price = new BigNumber(0.1245); expected.baseAssetSymbol = 'DEF'; expected.baseAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; - expected.baseVolume = new BigNumber(412 * 0.1245); + expected.baseVolume = new BigNumber(412); expected.quoteAssetSymbol = 'ABC'; expected.quoteAssetAddress = '0x0000000000000000000000000000000000000000'; - expected.quoteVolume = new BigNumber(412); + expected.quoteVolume = new BigNumber(412 * 0.1245); const actual = parseParadexOrder(paradexMarket, observedTimestamp, orderType, source, paradexOrder); expect(actual).deep.equal(expected); diff --git a/packages/pipeline/test/parsers/utils/index_test.ts b/packages/pipeline/test/parsers/utils/index_test.ts new file mode 100644 index 000000000..5a0d0f182 --- /dev/null +++ b/packages/pipeline/test/parsers/utils/index_test.ts @@ -0,0 +1,30 @@ +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { aggregateOrders, GenericRawOrder } from '../../../src/parsers/utils'; +import { chaiSetup } from '../../utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +// tslint:disable:custom-no-magic-numbers +describe('aggregateOrders', () => { + it('aggregates order by price point', () => { + const input = [ + { price: '1', amount: '20', orderHash: 'testtest', total: '20' }, + { price: '1', amount: '30', orderHash: 'testone', total: '30' }, + { price: '2', amount: '100', orderHash: 'testtwo', total: '200' }, + ]; + const expected = [['1', new BigNumber(50)], ['2', new BigNumber(100)]]; + const actual = aggregateOrders(input); + expect(actual).deep.equal(expected); + }); + + it('handles empty orders gracefully', () => { + const input: GenericRawOrder[] = []; + const expected: Array<[string, BigNumber]> = []; + const actual = aggregateOrders(input); + expect(actual).deep.equal(expected); + }); +}); diff --git a/packages/react-docs/CHANGELOG.json b/packages/react-docs/CHANGELOG.json index d456a3b53..9d8b5bc88 100644 --- a/packages/react-docs/CHANGELOG.json +++ b/packages/react-docs/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "1.0.22", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "1.0.21", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "1.0.20", "changes": [ diff --git a/packages/react-docs/CHANGELOG.md b/packages/react-docs/CHANGELOG.md index e48f43fb8..5c702d562 100644 --- a/packages/react-docs/CHANGELOG.md +++ b/packages/react-docs/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.22 - _December 13, 2018_ + + * Dependencies updated + +## v1.0.21 - _December 11, 2018_ + + * Dependencies updated + ## v1.0.20 - _November 28, 2018_ * Dependencies updated diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json index 968ac4e34..f58a51c90 100644 --- a/packages/react-docs/package.json +++ b/packages/react-docs/package.json @@ -1,6 +1,6 @@ { "name": "@0x/react-docs", - "version": "1.0.20", + "version": "1.0.22", "engines": { "node": ">=6.12" }, @@ -24,8 +24,8 @@ "url": "https://github.com/0xProject/0x-monorepo.git" }, "devDependencies": { - "@0x/dev-utils": "^1.0.19", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/tslint-config": "^2.0.0", "@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.23", - "@0x/types": "^1.3.0", - "@0x/utils": "^2.0.6", + "@0x/react-shared": "^1.0.25", + "@0x/types": "^1.4.1", + "@0x/utils": "^2.0.8", "@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 a376bae29..23e0d7f7e 100644 --- a/packages/react-shared/CHANGELOG.json +++ b/packages/react-shared/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "1.0.25", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "1.0.24", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "1.0.23", "changes": [ diff --git a/packages/react-shared/CHANGELOG.md b/packages/react-shared/CHANGELOG.md index a983e0af2..8afa94e04 100644 --- a/packages/react-shared/CHANGELOG.md +++ b/packages/react-shared/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.25 - _December 13, 2018_ + + * Dependencies updated + +## v1.0.24 - _December 11, 2018_ + + * Dependencies updated + ## v1.0.23 - _November 28, 2018_ * Dependencies updated diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index b5816ad98..17defe35a 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -1,6 +1,6 @@ { "name": "@0x/react-shared", - "version": "1.0.23", + "version": "1.0.25", "engines": { "node": ">=6.12" }, @@ -25,15 +25,15 @@ "url": "https://github.com/0xProject/0x-monorepo.git" }, "devDependencies": { - "@0x/dev-utils": "^1.0.19", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/tslint-config": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "^5.9.1", "typescript": "3.0.1" }, "dependencies": { - "@0x/types": "^1.3.0", + "@0x/types": "^1.4.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 fe077b6cc..0a757f519 100644 --- a/packages/sol-compiler/CHANGELOG.json +++ b/packages/sol-compiler/CHANGELOG.json @@ -1,12 +1,22 @@ [ { + "version": "1.1.16", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { "version": "1.1.15", "changes": [ { "note": "Fix bug where we were appending base path to absolute imports (e.g NPM imports)", "pr": 1311 } - ] + ], + "timestamp": 1544570656 }, { "timestamp": 1543401373, diff --git a/packages/sol-compiler/CHANGELOG.md b/packages/sol-compiler/CHANGELOG.md index a1782bb3b..b2066448d 100644 --- a/packages/sol-compiler/CHANGELOG.md +++ b/packages/sol-compiler/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.1.16 - _December 13, 2018_ + + * Dependencies updated + +## v1.1.15 - _December 11, 2018_ + + * Fix bug where we were appending base path to absolute imports (e.g NPM imports) (#1311) + ## v1.1.14 - _November 28, 2018_ * Dependencies updated diff --git a/packages/sol-compiler/package.json b/packages/sol-compiler/package.json index d27c0ee31..0ad620b1f 100644 --- a/packages/sol-compiler/package.json +++ b/packages/sol-compiler/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-compiler", - "version": "1.1.14", + "version": "1.1.16", "engines": { "node": ">=6.12" }, @@ -42,8 +42,8 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-compiler/README.md", "devDependencies": { - "@0x/dev-utils": "^1.0.19", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/tslint-config": "^2.0.0", "@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.18", - "@0x/json-schemas": "^2.1.2", - "@0x/sol-resolver": "^1.0.17", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "@0x/assert": "^1.0.20", + "@0x/json-schemas": "^2.1.4", + "@0x/sol-resolver": "^1.1.1", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/yargs": "^11.0.0", "chalk": "^2.3.0", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "lodash": "^4.17.5", "mkdirp": "^0.5.1", diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index cba67f292..85df8209e 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -400,7 +400,7 @@ export class Compiler { * while others are absolute ("Token.sol", "@0x/contracts/Wallet.sol") * And we need to append the base path for relative imports. */ - importPath = path.resolve('/' + contractFolder, importPath).replace('/', ''); + importPath = path.resolve(`/${contractFolder}`, importPath).replace('/', ''); } if (_.isUndefined(sourcesToAppendTo[importPath])) { diff --git a/packages/sol-cov/CHANGELOG.json b/packages/sol-cov/CHANGELOG.json index bc8aa71e1..b7973c135 100644 --- a/packages/sol-cov/CHANGELOG.json +++ b/packages/sol-cov/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "2.1.16", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "2.1.15", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "2.1.14", "changes": [ diff --git a/packages/sol-cov/CHANGELOG.md b/packages/sol-cov/CHANGELOG.md index 25ba93026..879ef9c95 100644 --- a/packages/sol-cov/CHANGELOG.md +++ b/packages/sol-cov/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.1.16 - _December 13, 2018_ + + * Dependencies updated + +## v2.1.15 - _December 11, 2018_ + + * Dependencies updated + ## v2.1.14 - _November 28, 2018_ * Dependencies updated diff --git a/packages/sol-cov/package.json b/packages/sol-cov/package.json index 73c11980f..3ade51c80 100644 --- a/packages/sol-cov/package.json +++ b/packages/sol-cov/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-cov", - "version": "2.1.14", + "version": "2.1.16", "engines": { "node": ">=6.12" }, @@ -42,14 +42,14 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/sol-cov/README.md", "dependencies": { - "@0x/dev-utils": "^1.0.19", - "@0x/sol-compiler": "^1.1.14", - "@0x/subproviders": "^2.1.6", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "@0x/dev-utils": "^1.0.21", + "@0x/sol-compiler": "^1.1.16", + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/solidity-parser-antlr": "^0.2.0", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "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.10", + "@0x/tslint-config": "^2.0.0", "@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 332aeb025..e8fef746e 100644 --- a/packages/sol-doc/CHANGELOG.json +++ b/packages/sol-doc/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "1.0.11", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "1.0.10", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "1.0.9", "changes": [ diff --git a/packages/sol-doc/CHANGELOG.md b/packages/sol-doc/CHANGELOG.md index 5a1df59c7..1b4a938af 100644 --- a/packages/sol-doc/CHANGELOG.md +++ b/packages/sol-doc/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.11 - _December 13, 2018_ + + * Dependencies updated + +## v1.0.10 - _December 11, 2018_ + + * Dependencies updated + ## v1.0.9 - _November 28, 2018_ * Dependencies updated diff --git a/packages/sol-doc/package.json b/packages/sol-doc/package.json index edf1707d6..c83c122df 100644 --- a/packages/sol-doc/package.json +++ b/packages/sol-doc/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-doc", - "version": "1.0.9", + "version": "1.0.11", "description": "Solidity documentation generator", "main": "lib/src/index.js", "types": "lib/src/index.d.js", @@ -25,16 +25,16 @@ "author": "F. Eugene Aumson", "license": "Apache-2.0", "dependencies": { - "@0x/sol-compiler": "^1.1.14", - "@0x/types": "^1.3.0", - "@0x/utils": "^2.0.6", - "ethereum-types": "^1.1.2", + "@0x/sol-compiler": "^1.1.16", + "@0x/types": "^1.4.1", + "@0x/utils": "^2.0.8", + "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "lodash": "^4.17.10", "yargs": "^12.0.2" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "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 4c9e612d7..85398e624 100644 --- a/packages/sol-resolver/CHANGELOG.json +++ b/packages/sol-resolver/CHANGELOG.json @@ -1,12 +1,22 @@ [ { + "version": "1.1.1", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { "version": "1.1.0", "changes": [ { "note": "NPMResolver now supports scoped packages", "pr": 1311 } - ] + ], + "timestamp": 1544570656 }, { "timestamp": 1542821676, diff --git a/packages/sol-resolver/CHANGELOG.md b/packages/sol-resolver/CHANGELOG.md index b83275e5a..98435be19 100644 --- a/packages/sol-resolver/CHANGELOG.md +++ b/packages/sol-resolver/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.1.1 - _December 13, 2018_ + + * Dependencies updated + +## v1.1.0 - _December 11, 2018_ + + * NPMResolver now supports scoped packages (#1311) + ## v1.0.17 - _November 21, 2018_ * Dependencies updated diff --git a/packages/sol-resolver/package.json b/packages/sol-resolver/package.json index 5c5eec2cf..0163765d9 100644 --- a/packages/sol-resolver/package.json +++ b/packages/sol-resolver/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-resolver", - "version": "1.0.17", + "version": "1.1.1", "engines": { "node": ">=6.12" }, @@ -23,15 +23,15 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/resolver/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", "typescript": "3.0.1" }, "dependencies": { - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", "lodash": "^4.17.5" }, "publishConfig": { diff --git a/packages/sra-spec/CHANGELOG.json b/packages/sra-spec/CHANGELOG.json index 49d1f1c83..393054465 100644 --- a/packages/sra-spec/CHANGELOG.json +++ b/packages/sra-spec/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "1.0.13", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "1.0.12", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1542821676, "version": "1.0.11", "changes": [ diff --git a/packages/sra-spec/CHANGELOG.md b/packages/sra-spec/CHANGELOG.md index 77b22515f..fd673b837 100644 --- a/packages/sra-spec/CHANGELOG.md +++ b/packages/sra-spec/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.13 - _December 13, 2018_ + + * Dependencies updated + +## v1.0.12 - _December 11, 2018_ + + * Dependencies updated + ## v1.0.11 - _November 21, 2018_ * Dependencies updated diff --git a/packages/sra-spec/package.json b/packages/sra-spec/package.json index 1623e2574..3a6ee96d3 100644 --- a/packages/sra-spec/package.json +++ b/packages/sra-spec/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sra-spec", - "version": "1.0.11", + "version": "1.0.13", "engines": { "node": ">=6.12" }, @@ -35,11 +35,11 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/sra-spec/README.md", "dependencies": { - "@0x/json-schemas": "^2.1.2", + "@0x/json-schemas": "^2.1.4", "@loopback/openapi-v3-types": "^0.8.2" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/mocha": "^2.2.42", "@types/node": "^10.5.3", "chai": "^4.0.1", diff --git a/packages/subproviders/CHANGELOG.json b/packages/subproviders/CHANGELOG.json index 6da170be3..938b2a717 100644 --- a/packages/subproviders/CHANGELOG.json +++ b/packages/subproviders/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "2.1.8", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "2.1.7", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "timestamp": 1543401373, "version": "2.1.6", "changes": [ diff --git a/packages/subproviders/CHANGELOG.md b/packages/subproviders/CHANGELOG.md index 01dd8d652..002c76395 100644 --- a/packages/subproviders/CHANGELOG.md +++ b/packages/subproviders/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.1.8 - _December 13, 2018_ + + * Dependencies updated + +## v2.1.7 - _December 11, 2018_ + + * Dependencies updated + ## v2.1.6 - _November 28, 2018_ * Dependencies updated diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index 86f3738af..90ef6b677 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -1,6 +1,6 @@ { "name": "@0x/subproviders", - "version": "2.1.6", + "version": "2.1.8", "engines": { "node": ">=6.12" }, @@ -29,11 +29,11 @@ } }, "dependencies": { - "@0x/assert": "^1.0.18", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "@0x/assert": "^1.0.20", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@ledgerhq/hw-app-eth": "^4.3.0", "@ledgerhq/hw-transport-u2f": "4.24.0", "@types/eth-lightwallet": "^3.0.0", @@ -43,7 +43,7 @@ "bip39": "^2.5.0", "bn.js": "^4.11.8", "eth-lightwallet": "^3.0.1", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "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.10", + "@0x/tslint-config": "^2.0.0", "@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 8c4b942a3..70d785c74 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.58", + "version": "1.0.60", "engines": { "node": ">=6.12" }, @@ -18,13 +18,13 @@ "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { - "0x.js": "^2.0.6", - "@0x/subproviders": "^2.1.6", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "0x.js": "^2.0.8", + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "body-parser": "^1.17.1", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "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.10", + "@0x/tslint-config": "^2.0.0", "@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 9f504216c..0070a5b81 100644 --- a/packages/tslint-config/CHANGELOG.json +++ b/packages/tslint-config/CHANGELOG.json @@ -1,5 +1,15 @@ [ { + "version": "2.0.0", + "changes": [ + { + "note": "Improve async-suffix rule to check functions too, not just methods", + "pr": 1425 + } + ], + "timestamp": 1544739608 + }, + { "version": "1.0.10", "changes": [ { diff --git a/packages/tslint-config/CHANGELOG.md b/packages/tslint-config/CHANGELOG.md index f5cacb5d1..3cebb1e95 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 +## v2.0.0 - _December 13, 2018_ + + * Improve async-suffix rule to check functions too, not just methods (#1425) + ## v1.0.10 - _November 9, 2018_ * Dependencies updated diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json index 2914f31e2..64ec1e967 100644 --- a/packages/tslint-config/package.json +++ b/packages/tslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@0x/tslint-config", - "version": "1.0.10", + "version": "2.0.0", "engines": { "node": ">=6.12" }, diff --git a/packages/tslint-config/rules/walkers/async_suffix.ts b/packages/tslint-config/rules/walkers/async_suffix.ts index eaec9c5f6..4e12152e8 100644 --- a/packages/tslint-config/rules/walkers/async_suffix.ts +++ b/packages/tslint-config/rules/walkers/async_suffix.ts @@ -3,24 +3,33 @@ import * as Lint from 'tslint'; import * as ts from 'typescript'; export class AsyncSuffixWalker extends Lint.RuleWalker { - public static FAILURE_STRING = 'async functions must have an Async suffix'; + public static FAILURE_STRING = 'async functions/methods must have an Async suffix'; + public visitFunctionDeclaration(node: ts.FunctionDeclaration): void { + this._visitFunctionOrMethodDeclaration(node); + super.visitFunctionDeclaration(node); + } public visitMethodDeclaration(node: ts.MethodDeclaration): void { - const methodNameNode = node.name; - const methodName = methodNameNode.getText(); - if (!_.isUndefined(node.type)) { - if (node.type.kind === ts.SyntaxKind.TypeReference) { - // tslint:disable-next-line:no-unnecessary-type-assertion - const returnTypeName = (node.type as ts.TypeReferenceNode).typeName.getText(); - if (returnTypeName === 'Promise' && !methodName.endsWith('Async')) { - const failure = this.createFailure( - methodNameNode.getStart(), - methodNameNode.getWidth(), - AsyncSuffixWalker.FAILURE_STRING, - ); - this.addFailure(failure); + this._visitFunctionOrMethodDeclaration(node); + super.visitMethodDeclaration(node); + } + private _visitFunctionOrMethodDeclaration(node: ts.MethodDeclaration | ts.FunctionDeclaration): void { + const nameNode = node.name; + if (!_.isUndefined(nameNode)) { + const name = nameNode.getText(); + if (!_.isUndefined(node.type)) { + if (node.type.kind === ts.SyntaxKind.TypeReference) { + // tslint:disable-next-line:no-unnecessary-type-assertion + const returnTypeName = (node.type as ts.TypeReferenceNode).typeName.getText(); + if (returnTypeName === 'Promise' && !name.endsWith('Async')) { + const failure = this.createFailure( + nameNode.getStart(), + nameNode.getWidth(), + AsyncSuffixWalker.FAILURE_STRING, + ); + this.addFailure(failure); + } } } } - super.visitMethodDeclaration(node); } } diff --git a/packages/tslint-config/tslint.json b/packages/tslint-config/tslint.json index fd1849dd0..e8de6221e 100644 --- a/packages/tslint-config/tslint.json +++ b/packages/tslint-config/tslint.json @@ -92,6 +92,7 @@ "prefer-function-over-method": true, "prefer-object-spread": true, "prefer-readonly": true, + "prefer-template": true, "promise-function-async": true, "quotemark": [true, "single", "avoid-escape", "jsx-double"], "restrict-plus-operands": true, diff --git a/packages/types/CHANGELOG.json b/packages/types/CHANGELOG.json index b09859101..f1cd2f18e 100644 --- a/packages/types/CHANGELOG.json +++ b/packages/types/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "1.4.1", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { "version": "1.4.0", "changes": [ { @@ -10,7 +19,8 @@ "note": "Add RevertReasons for DutchAuction contract", "pr": 1225 } - ] + ], + "timestamp": 1544570656 }, { "version": "1.3.0", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index f133c05f1..5170eb4db 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -5,6 +5,15 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.4.1 - _December 13, 2018_ + + * Dependencies updated + +## v1.4.0 - _December 11, 2018_ + + * Add `LengthMismatch` and `LengthGreaterThan3Required` revert reasons (#1224) + * Add RevertReasons for DutchAuction contract (#1225) + ## v1.3.0 - _November 21, 2018_ * Add the `SimpleContractArtifact` type, which describes the artifact format published in the `@0x/contract-artifacts` package (#1298) diff --git a/packages/types/package.json b/packages/types/package.json index 46e268a66..3c4bb6fe6 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@0x/types", - "version": "1.3.0", + "version": "1.4.1", "engines": { "node": ">=6.12" }, @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/types/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "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.2" + "ethereum-types": "^1.1.4" }, "publishConfig": { "access": "public" diff --git a/packages/typescript-typings/CHANGELOG.json b/packages/typescript-typings/CHANGELOG.json index 5653b397d..fadf5ad14 100644 --- a/packages/typescript-typings/CHANGELOG.json +++ b/packages/typescript-typings/CHANGELOG.json @@ -1,5 +1,23 @@ [ { + "version": "3.0.6", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "3.0.5", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { "version": "3.0.4", "changes": [ { diff --git a/packages/typescript-typings/CHANGELOG.md b/packages/typescript-typings/CHANGELOG.md index 62e6665be..1663d40af 100644 --- a/packages/typescript-typings/CHANGELOG.md +++ b/packages/typescript-typings/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.6 - _December 13, 2018_ + + * Dependencies updated + +## v3.0.5 - _December 11, 2018_ + + * Dependencies updated + ## v3.0.4 - _November 9, 2018_ * Dependencies updated diff --git a/packages/typescript-typings/package.json b/packages/typescript-typings/package.json index 9dbbe1370..c67b660c8 100644 --- a/packages/typescript-typings/package.json +++ b/packages/typescript-typings/package.json @@ -1,6 +1,6 @@ { "name": "@0x/typescript-typings", - "version": "3.0.4", + "version": "3.0.6", "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.2", + "ethereum-types": "^1.1.4", "popper.js": "1.14.3" }, "devDependencies": { diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index 08801a891..fe66d3f31 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -1,13 +1,22 @@ [ { - "timestamp": 1543448882, + "version": "2.0.8", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { "version": "2.0.7", "changes": [ { "note": "Optimized ABI Encoder/Decoder. Generates compressed calldata to save gas. Generates human-readable calldata to aid development." } - ] + ], + "timestamp": 1544570656 }, { "timestamp": 1542821676, diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index c5c42161a..c0437392d 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.8 - _December 13, 2018_ + + * Dependencies updated + +## v2.0.7 - _December 11, 2018_ + + * Optimized ABI Encoder/Decoder. Generates compressed calldata to save gas. Generates human-readable calldata to aid development. + ## v2.0.6 - _November 21, 2018_ * Dependencies updated diff --git a/packages/utils/package.json b/packages/utils/package.json index 1f4d85843..a25dc9cff 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@0x/utils", - "version": "2.0.6", + "version": "2.0.8", "engines": { "node": ">=6.12" }, @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/utils/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/detect-node": "2.0.0", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", @@ -44,13 +44,13 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", "@types/node": "*", "abortcontroller-polyfill": "^1.1.9", "bignumber.js": "~4.1.0", "detect-node": "2.0.3", - "ethereum-types": "^1.1.2", + "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "ethers": "~4.0.4", "isomorphic-fetch": "^2.2.1", @@ -60,4 +60,4 @@ "publishConfig": { "access": "public" } -}
\ No newline at end of file +} diff --git a/packages/utils/src/abi_utils.ts b/packages/utils/src/abi_utils.ts index 598ea5fcc..3e6fc9665 100644 --- a/packages/utils/src/abi_utils.ts +++ b/packages/utils/src/abi_utils.ts @@ -26,7 +26,7 @@ function parseEthersParams(params: DataItem[]): { names: ParamName[]; types: str const result = parseEthersParams(param.components); names.push({ name: param.name || null, names: result.names }); - types.push('tuple(' + result.types.join(',') + ')' + suffix); + types.push(`tuple(${result.types.join(',')})${suffix}`); } else { names.push(param.name || null); types.push(param.type); @@ -120,7 +120,7 @@ function splitTupleTypes(type: string): string[] { if (_.endsWith(type, '[]')) { throw new Error('Internal error: array types are not supported'); } else if (!_.startsWith(type, 'tuple(')) { - throw new Error('Internal error: expected tuple type but got non-tuple type: ' + type); + throw new Error(`Internal error: expected tuple type but got non-tuple type: ${type}`); } // Trim the outtermost tuple(). const trimmedType = type.substring('tuple('.length, type.length - 1); diff --git a/packages/utils/test/abi_encoder/evm_data_types_test.ts b/packages/utils/test/abi_encoder/evm_data_types_test.ts index 9ef80a560..7185851a8 100644 --- a/packages/utils/test/abi_encoder/evm_data_types_test.ts +++ b/packages/utils/test/abi_encoder/evm_data_types_test.ts @@ -901,7 +901,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const bytesLength = 40; - const args = '0x' + '61'.repeat(bytesLength); + const args = `0x${'61'.repeat(bytesLength)}`; // Encode Args and validate result const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = @@ -993,7 +993,7 @@ describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => { // Construct args to be encoded // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes. const strLength = 40; - const args = '0x' + 'a'.repeat(strLength); + const args = `0x${'a'.repeat(strLength)}`; // Encode Args and validate result const encodedArgs = dataType.encode(args, encodingRules); const expectedEncodedArgs = diff --git a/packages/web3-wrapper/CHANGELOG.json b/packages/web3-wrapper/CHANGELOG.json index ed484c8e8..6b76626a9 100644 --- a/packages/web3-wrapper/CHANGELOG.json +++ b/packages/web3-wrapper/CHANGELOG.json @@ -1,12 +1,22 @@ [ { + "version": "3.2.1", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { "version": "3.2.0", "changes": [ { "note": "Return `value` and `gasPrice` as BigNumbers to avoid loss of precision errors", "pr": 1402 } - ] + ], + "timestamp": 1544570656 }, { "version": "3.1.6", diff --git a/packages/web3-wrapper/CHANGELOG.md b/packages/web3-wrapper/CHANGELOG.md index fffaf1d0a..76ca80e69 100644 --- a/packages/web3-wrapper/CHANGELOG.md +++ b/packages/web3-wrapper/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.2.1 - _December 13, 2018_ + + * Dependencies updated + +## v3.2.0 - _December 11, 2018_ + + * Return `value` and `gasPrice` as BigNumbers to avoid loss of precision errors (#1402) + ## v3.1.6 - _November 28, 2018_ * Unmarshall mined transaction receipts (#1308) diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index 218d85bfc..e5af24965 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -1,6 +1,6 @@ { "name": "@0x/web3-wrapper", - "version": "3.1.6", + "version": "3.2.1", "engines": { "node": ">=6.12" }, @@ -36,7 +36,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/web3-wrapper/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/ganache-core": "^2.1.0", "@types/lodash": "4.14.104", "chai": "^4.0.1", @@ -54,11 +54,11 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/assert": "^1.0.18", - "@0x/json-schemas": "^2.1.2", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "ethereum-types": "^1.1.2", + "@0x/assert": "^1.0.20", + "@0x/json-schemas": "^2.1.4", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "ethereum-types": "^1.1.4", "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 5d2e563e9..c24a0ba6e 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,6 +1,6 @@ { "name": "@0x/website", - "version": "0.0.61", + "version": "0.0.63", "engines": { "node": ">=6.12" }, @@ -20,24 +20,24 @@ "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { - "@0x/asset-buyer": "^3.0.2", + "@0x/asset-buyer": "^3.0.4", "@0x/contract-addresses": "^2.0.0", - "@0x/contract-wrappers": "^4.1.1", - "@0x/json-schemas": "^2.1.2", - "@0x/order-utils": "^3.0.4", - "@0x/react-docs": "^1.0.20", - "@0x/react-shared": "^1.0.23", - "@0x/subproviders": "^2.1.6", - "@0x/types": "^1.3.0", - "@0x/typescript-typings": "^3.0.4", - "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.6", + "@0x/contract-wrappers": "^4.1.3", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/react-docs": "^1.0.22", + "@0x/react-shared": "^1.0.25", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.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.2", + "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "find-versions": "^2.0.0", "jsonschema": "^1.2.0", diff --git a/packages/website/public/images/developers/tutorials/integrate_0x_instant.svg b/packages/website/public/images/developers/tutorials/integrate_0x_instant.svg new file mode 100644 index 000000000..e9c9278a8 --- /dev/null +++ b/packages/website/public/images/developers/tutorials/integrate_0x_instant.svg @@ -0,0 +1,3 @@ +<svg width="32" height="39" viewBox="0 0 32 39" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M16 1L1 23.242H16L16.0412 38L31 15.7159H16V1Z" stroke="#3289F1" stroke-width="1.5" stroke-miterlimit="10" stroke-linejoin="round"/> +</svg> diff --git a/packages/website/translations/english.json b/packages/website/translations/english.json index 78f29d0f6..2914ffead 100644 --- a/packages/website/translations/english.json +++ b/packages/website/translations/english.json @@ -92,6 +92,9 @@ "ORDER_BASICS_DESCRIPTION": "Tutorial on how to create, validate and fill an order using 0x", "USE_NETWORKED_LIQUIDITY": "use networked liquidity", "USE_NETWORKED_LIQUIDITY_DESCRIPTION": "Learn how to tap into networked liquidity using the Standard Relayer API", + "INTEGRATE_0X_INSTANT": "add seamless purchasing of crypto assets to your website or app", + "INTEGRATE_0X_INSTANT_DESCRIPTION": + "learn how to use 0x Instant or AssetBuyer to give your users the power of purchasing crypto assets using 0x", "VIEW_ALL_DOCUMENTATION": "view all documentation", "SANDBOX": "0x.js sandbox", "GITHUB": "github", diff --git a/packages/website/ts/components/documentation/sidebar_header.tsx b/packages/website/ts/components/documentation/sidebar_header.tsx index 9ced52c74..0ab24ab5e 100644 --- a/packages/website/ts/components/documentation/sidebar_header.tsx +++ b/packages/website/ts/components/documentation/sidebar_header.tsx @@ -24,7 +24,7 @@ export const SidebarHeader: React.StatelessComponent<SidebarHeaderProps> = ({ return ( <Container> <Container className="flex justify-bottom"> - <Container className="left pl1" width="150px"> + <Container className="col col-7 pl1"> <Text fontColor={colors.lightLinkBlue} fontSize={screenWidth === ScreenWidths.Sm ? '20px' : '22px'} @@ -37,12 +37,14 @@ export const SidebarHeader: React.StatelessComponent<SidebarHeaderProps> = ({ {!_.isUndefined(docsVersion) && !_.isUndefined(availableDocVersions) && !_.isUndefined(onVersionSelected) && ( - <div className="right" style={{ alignSelf: 'flex-end', paddingBottom: 4 }}> - <VersionDropDown - selectedVersion={docsVersion} - versions={availableDocVersions} - onVersionSelected={onVersionSelected} - /> + <div className="col col-5 pl1" style={{ alignSelf: 'flex-end', paddingBottom: 4 }}> + <Container className="right"> + <VersionDropDown + selectedVersion={docsVersion} + versions={availableDocVersions} + onVersionSelected={onVersionSelected} + /> + </Container> </div> )} </Container> diff --git a/packages/website/ts/containers/order_watcher_documentation.ts b/packages/website/ts/containers/order_watcher_documentation.ts index ac92e6a22..683e1fe9f 100644 --- a/packages/website/ts/containers/order_watcher_documentation.ts +++ b/packages/website/ts/containers/order_watcher_documentation.ts @@ -24,7 +24,7 @@ const docsInfoConfig: DocsInfoConfig = { id: DocPackages.OrderWatcher, packageName: '@0x/order-watcher', type: SupportedDocJson.TypeDoc, - displayName: 'OrderWatcher', + displayName: 'Order Watcher', packageUrl: 'https://github.com/0xProject/0x-monorepo', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], diff --git a/packages/website/ts/pages/documentation/docs_home.tsx b/packages/website/ts/pages/documentation/docs_home.tsx index f68d2892f..9dc779e96 100644 --- a/packages/website/ts/pages/documentation/docs_home.tsx +++ b/packages/website/ts/pages/documentation/docs_home.tsx @@ -44,6 +44,14 @@ const TUTORIALS: TutorialInfo[] = [ to: `${WebsitePaths.Wiki}#Find,-Submit,-Fill-Order-From-Relayer`, }, }, + { + iconUrl: '/images/developers/tutorials/integrate_0x_instant.svg', + description: Key.Integrate0xInstantDescription, + link: { + title: Key.Integrate0xInstant, + to: `${WebsitePaths.Wiki}#Get-Started-With-Instant`, + }, + }, ]; const CATEGORY_TO_PACKAGES: ObjectMap<Package[]> = { diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index b20dd7095..2967fdac5 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -474,6 +474,8 @@ export enum Key { OrderBasicsDescription = 'ORDER_BASICS_DESCRIPTION', UseNetworkedLiquidity = 'USE_NETWORKED_LIQUIDITY', UseNetworkedLiquidityDescription = 'USE_NETWORKED_LIQUIDITY_DESCRIPTION', + Integrate0xInstant = 'INTEGRATE_0X_INSTANT', + Integrate0xInstantDescription = 'INTEGRATE_0X_INSTANT_DESCRIPTION', ViewAllDocumentation = 'VIEW_ALL_DOCUMENTATION', Sandbox = 'SANDBOX', Github = 'GITHUB', |